<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>웨안뒈?</title>
        <link>https://velog.io/</link>
        <description>항상 진심이지만 뭔가 안풀리는 개발 (주의! - 코린이가 배우고 이해한 내용을 끄적이는 공간이므로 실제 개념과 일부 다를 수 있음!)</description>
        <lastBuildDate>Sun, 04 Dec 2022 14:14:48 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>웨안뒈?</title>
            <url>https://velog.velcdn.com/images/ming_gry/profile/2826d03f-178b-4fc7-ad89-0526c2fedb30/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 웨안뒈?. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ming_gry" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[항해99 11주차 WIL - Https 와 NginX]]></title>
            <link>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-11%EC%A3%BC%EC%B0%A8-WIL</link>
            <guid>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-11%EC%A3%BC%EC%B0%A8-WIL</guid>
            <pubDate>Sun, 04 Dec 2022 14:14:48 GMT</pubDate>
            <description><![CDATA[<p>실전프로젝트 4주차에 우리를 가장 괴롭혔던 Https 와 NginX 에 대해 써보고자 한다. 백엔드는 물론 프론트엔드의 Https 배포도 AWS 에서 인증서 발급이 되지 않는다는 심각한 문제가 있어서 정말 고생을 많이했다... 결국 프론트엔드는 CloudType 이라는 서비스를 이용해 비교적 손쉽게 배포할 수 있었으나 백엔드는 NginX 설정에 엄청 애를 먹어서 결코 쉽지 않았던 것 같다.</p>
<p>결국 우리가 사용한 NginX 설정에 대한 포스팅을 하기에 앞서 Https 와 NginX 에 대해 먼저 알아보도록 하자.</p>
<h1 id="1-https">1) Https</h1>
<h2 id="1-1-https-란">1-1) Https 란?</h2>
<p>Https 란 HyperText Transfer Protocol over Secure Socket Layer 의 약자로, Http over SSL, Http over TLS, Http Secure 로 불리기도 한다. 암호화된 Http Protocol 이라는 뜻으로 SSL / TLS 로 Http 통신을 암호화해 악의적인 사용자로부터 Http 통신을 보호하는 방식을 말한다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/bdbee0c0-356d-402c-9c71-d3bfdf204216/image.png" alt=""></p>
<p>로그인, 비밀번호 변경, 결제 등의 민감한 정보가 단순 Http 로 전송되는 경우가 있다고 가정해보자. Http 는 평문 데이터 전송이기 때문에 악의적인 사용자가 이 Http 통신을 가로챈다면 Http Body, Header 등에 들어있는 민감한 정보가 유출될 가능성이 있다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/5edeb22b-2fbd-40bd-a828-1743b949ce9d/image.png" alt=""></p>
<blockquote>
<p>위의 사진의 자물쇠 모양이 Https 가 적용되었음을 알려주고 있다.</p>
</blockquote>
<p>Https 는 과거에도 있긴 했지만 2014년 구글에서 Https 를 적용한 사이트에 대해 SEO(검색 엔진 최적화) 가산점을 주는 정책으로 인해 더욱 보편화되었다.</p>
<h2 id="1-2-ssl--tls">1-2) SSL / TLS</h2>
<p>SSL 과 TLS 가 무엇이길래 Http 통신을 암호화할 수 있을까?</p>
<p>SSL 은 Secure Socket Layer 의 약자로, 보안 소켓 계층이라는 뜻이다. 인터넷 상에서 데이터를 안전하게 전송하기 위한 인터넷 암호화 통신 프로토콜로써 데이터 보안을 위해서 개발된 통신 레이어다.</p>
<p>이는 Netscape 社 가 1995년 처음 개발하였는데, 이를 IETF(국제 인터넷 표준화 기구) 에서 인터넷 표준화 시킨 것이 TLS 이다. 오늘날 SSL 로 불리는 것은 모두 TLS 이며 같은 의미로 통용되곤 한다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/1e176d0d-04c6-486e-beef-ff3b0587151b/image.png" alt=""></p>
<p>SSL 프로토콜은 OSI 7계층 모델의 어느 한 계층에서 동작한다기 보다는 응용계층과 전송계층 사이에 독립적인 프로토콜 계층을 만들어서 동작한다. 응용계층의 프로토콜들은 TCP 가 아닌 SSL 에 보내지고, SSL 은 응용계층에서 받은 데이터를 암호화하여 TCP 로 전달한다. 전달 받을 때 역시 TCP 로부터 받은 데이터를 복호화해 응용계층에 전달한다.</p>
<p>그래서 URL 도 Http:// 가 아닌 Https:// 로 시작하며, 기본 포트 번호도 80이 아닌 443을 사용한다.</p>
<p>아래는 이를 이해하기 위해 참고하면 좋을 자료들이다.</p>
<blockquote>
<p>[10분 테코톡] 👶에단의 TLS : <a href="https://www.youtube.com/watch?v=EPcQqkqqouk">https://www.youtube.com/watch?v=EPcQqkqqouk</a>
[10분 테코톡] 🔮 히히의 OSI 7 Layer : <a href="https://www.youtube.com/watch?v=1pfTxp25MA8&amp;list=PLq6YHA1IXXnknjlwxAJqW7oq6pNjcOgwT&amp;index=61">https://www.youtube.com/watch?v=1pfTxp25MA8&amp;list=PLq6YHA1IXXnknjlwxAJqW7oq6pNjcOgwT&amp;index=61</a>
[네트워크] OSI 7계층 : <a href="https://yjkim97.tistory.com/35">https://yjkim97.tistory.com/35</a>
[네트워크] 전송 계층 : <a href="https://velog.io/@gndan4/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%A0%84%EC%86%A1-%EA%B3%84%EC%B8%B5">https://velog.io/@gndan4/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%A0%84%EC%86%A1-%EA%B3%84%EC%B8%B5</a>
[네트워크] 응용 계층 : <a href="https://velog.io/@gndan4/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%9D%91%EC%9A%A9-%EA%B3%84%EC%B8%B5">https://velog.io/@gndan4/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%9D%91%EC%9A%A9-%EA%B3%84%EC%B8%B5</a></p>
</blockquote>
<h2 id="1-3-https-동작-과정">1-3) Https 동작 과정</h2>
<p>Https 에서는 대칭키, 비대칭키가 모두 사용되는데 암호화 관련 기술이므로 이에 대한 추가적인 내용은 아래를 참고하도록 하자.</p>
<blockquote>
<p>[10분 테코톡] 🔐 마갸의 암호 : <a href="https://www.youtube.com/watch?v=itehKMMBVjc">https://www.youtube.com/watch?v=itehKMMBVjc</a>
암호화 알고리즘 종류 : <a href="https://jusungpark.tistory.com/34">https://jusungpark.tistory.com/34</a>
[Programming] 암호화 알고리즘 종류와 분류 : <a href="https://velog.io/@inyong_pang/Programming-%EC%95%94%ED%98%B8%ED%99%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A2%85%EB%A5%98%EC%99%80-%EB%B6%84%EB%A5%98">https://velog.io/@inyong_pang/Programming-%EC%95%94%ED%98%B8%ED%99%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A2%85%EB%A5%98%EC%99%80-%EB%B6%84%EB%A5%98</a></p>
</blockquote>
<h3 id="대칭키와-비대칭키">대칭키와 비대칭키</h3>
<p>대칭키는 클라이언트와 서버가 동일한 키를 사용해 암호화와 복호화를 진행한다. 동일한 키로 암호화와 복호화를 모두 진행하기 때문에 키가 노출되면 정보가 탈취될 위험이 있지만 연산속도는 빠르다.</p>
<p>비대칭키는 1개의 쌍으로 구성된 공개키와 개인키로 암호화와 복호화를 한다. 공개키는 말 그대로 모두에게 공개 가능한 키이고, 개인키는 자신만 알고 있어야 하는 하나의 고유한 키이다. 대칭키와 다르게 공개키로 암호화를 하면 개인키로만 복호화할 수 있으며, 개인키로 암호화할 경우 공개키로만 복호화가 가능하다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/96c81179-d67e-482d-9f9b-a9625beac48f/image.png" alt=""></p>
<h3 id="ca">CA</h3>
<p>CA 는 Certificate Authority 의 약자로, SSL 인증서를 발급해주는 기관을 말한다. 이 인증서는 클라이언트가 접속한 서버가 클라이언트가 의도한 서버가 맞는지 보장하는 역할을 한다. 아무나 쉽게 CA 가 될 수 있는 것도 아닐 뿐더러 CA 목록들은 크롬, 엣지, IE 등 브라우저에 내장되어 있어 SSL 인증서가 CA 에서 발급된 것이 맞는지 검증해준다.</p>
<p>SSL 인증서에는 서비스의 정보 (인증서를 발급한 CA, 서비스의 도메인 등) 과 서버 측 공개키 (공개키의 내용, 공개키의 암호화 방법) 가 포함되어 있다. 또한 CA 는 서버 측에서 사용할 비밀키까지 발급해준다.</p>
<h3 id="더-자세히-알아보기">더 자세히 알아보기</h3>
<p>Https 에 대칭키와 비대칭키가 사용되고, SSL 인증서에는 어떠한 내용이 담기는지까지 확인했다. 이제 진짜 Https 가 어떻게 동작하는지 확인해보자. 여기서 대칭키와 비대칭키(공개키와 개인키) 가 굉장히 헷갈릴 수 있으므로 이에 유념하도록 하자.</p>
<p>글로는 잘 이해가 되지 않는다면 얄코님의 영상을 참고하도록 하자.</p>
<blockquote>
<p>HTTPS가 뭐고 왜 쓰나요? (Feat. 대칭키 vs. 비대칭키) : <a href="https://youtu.be/H6lpFRpyl14?t=453">https://youtu.be/H6lpFRpyl14?t=453</a></p>
</blockquote>
<p>Https 통신은 Handshake, Session, Session 종료의 단계로 이루어진다.
먼저, Handshake 단계이다.</p>
<h4 id="handshake">Handshake</h4>
<ol>
<li><p>클라이언트가 서버에 접속, 클라이언트는 서버에 아래의 정보를 전달한다.</p>
<ul>
<li>클라이언트 측에서 생성한 랜덤 데이터 (Handshake 3번에서 사용)</li>
<li>클라이언트가 지원하는 암호화 방식들 (클라이언트와 서버가 지원하는 암호화 방식이 다를 수 있기 때문)</li>
<li>세션아이디 (이미 SSL Handshake 을 했다면 비용과 시간을 줄이기 위해 사용)</li>
</ul>
</li>
<li><p>서버가 클라이언트에 대해 응답, 서버는 클라이언트에 아래의 정보를 전달한다.</p>
<ul>
<li>서버 측에서 생성한 랜덤 데이터 (마찬가지로 Handshake 3번에서 사용)</li>
<li>서버가 선택한 암호화 방식 (클라이언트가 지원하는 암호화 방식 중 서버 측에서도 사용가능한 암호화 방식을 선택해 보냄)</li>
<li>SSL 인증서</li>
</ul>
</li>
<li><p>서버에서 보낸 SSL 인증서가 CA 에 의해서 발급된 것인지 확인 후 서버에 대칭키 전송</p>
<ul>
<li>브라우저에 내장된 CA 목록을 확인</li>
<li>CA 목록과 일치하면 브라우저에 내장된 CA 의 공개키를 이용해 인증서 복호화 (SSL 인증서는 CA 의 개인키로 암호화되어 있음)</li>
<li>서버와 클라이언트에서 생성한 랜덤 데이터를 조합해 대칭키(Pre Master Secret) 생성</li>
<li>대칭키를 SSL 인증서 내에 포함된 서버의 공개키로 암호화</li>
</ul>
</li>
<li><p>클라이언트가 전송한 Pre Master Secret 를 개인키로 복호화 후 Master Secret 값으로 만들어 Session Key 생성 → Session Key 값을 이용해 데이터를 대칭키 방식으로 암호화하여 데이터를 주고 받는다.</p>
</li>
<li><p>Handshake 종료</p>
</li>
</ol>
<p>생각보다 복잡한 과정이 소요된다. 여기에서 중요한 것은, 대칭키와 비대칭키가 혼합되어 사용된다는 점이다. 매번 비대칭키를 사용하지 않는 것은 역시나 비용과 시간 때문이다. 대신 3번에서 보듯이 클라이언트에서 서버로 대칭키를 보낼 때 서버의 공개키를 이용해 비대칭키 방식으로 암호화해 보내는 방식을 사용한다.</p>
<h4 id="session">Session</h4>
<p>실제로 서버와 클라이언트가 데이터를 주고 받는 단계이다. 위에서 생성한 Session Key 값을 이용해 대칭키 방식으로 데이터를 암호화 한다.</p>
<h4 id="session-종료">Session 종료</h4>
<p>데이터의 전송이 끝나면 SSL 통신이 끝났음을 서로에게 알리고 대칭키인 Session Key 를 폐기한다.</p>
<h1 id="2-nginx">2) NginX</h1>
<h2 id="2-1-nginx-란">2-1) NginX 란?</h2>
<p>NginX 란 트래픽이 많은 WAS 를 도와주기 위해 개발된 이벤트 기반 구조의 경량화 Web Server이다. 라고 단순하게 정의할 순 있겠지만 먼저 Web Server 와 WAS 가 어떤 것인지 알아보고 NginX 의 탄생 배경을 살펴봄으로써 NginX 가 지원하는 다양한 기능에 대해 알아보자.</p>
<h2 id="2-2-webserver-와-was">2-2) WebServer 와 WAS</h2>
<h3 id="정적-페이지와-동적-페이지">정적 페이지와 동적 페이지</h3>
<p>웹페이지는 정해진 url 에 http 요청을 보내면 그에 맞는 html 을 내려받는 식으로 작동한다. 여기서 웹페이지는 동작 방식에 따라 정적 페이지냐 동적 페이지냐로 나뉠 수 있다.</p>
<h4 id="정적-페이지">정적 페이지</h4>
<p>정적 페이지는 아래의 그림과 같이 파일 경로에 해당하는 컨텐츠를 응답받는다. 예를 들면 어느 회사 홈페이지의 회사 소개 페이지와 같이 항상 동일한 페이지를 띄워주는 것이다. 항상 동일한 페이지를 띄워주면 되기 때문에 html, css, image 파일과 같은 파일들을 띄워주면 된다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/0ac25245-ae75-4c65-9174-fb72474e018e/image.png" alt="">
<img src="https://velog.velcdn.com/images/ming_gry/post/e783c6bb-cd6a-43f4-9127-2561514bccc0/image.png" alt=""></p>
<h4 id="동적-페이지">동적 페이지</h4>
<p>반면 동적 페이지는 요청하는 내용에 맞게 동적인 컨텐츠를 응답받는다. 사진에서 보다시피 uid=abc 혹은 uid=user2 와 같이 특정 값에 해당하는 컨텐츠가 반환되어 페이지화 되는 것이다. 예를 들면 게시판에 뼈대는 같지만 글 마다 다른 내용이 들어가는 것 혹은 계속 새로운 내용이 업데이트 되는 특정 유저의 SNS 화면 같은 경우가 이에 해당할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/5bd76bc7-0859-4eb3-a13a-9bb9cb59ecf9/image.png" alt="">
<img src="https://velog.velcdn.com/images/ming_gry/post/65f1f411-2eb4-4979-a6b7-da1c7bc9e904/image.png" alt=""></p>
<p>자세한 내용은 아래의 영상을 참고하면 이해가 더욱 쉽게될 것이다.</p>
<blockquote>
<p>정적 웹은 뭐고 동적 웹은 뭔가요? : <a href="https://www.youtube.com/watch?v=C06xRvXIAUk">https://www.youtube.com/watch?v=C06xRvXIAUk</a></p>
</blockquote>
<h3 id="webserver-와-was-의-차이">WebServer 와 WAS 의 차이</h3>
<p>쉽게 말하면 WebServer 는 정적 컨텐츠를 전달하는 서버이고, WAS 는 동적 컨텐츠를 전달하는 서버이다. 다시 말해 WAS 는 DB 조회나 다양한 로직 처리가 된 동적 컨텐츠를 전달하는 셈이다.</p>
<h4 id="webserver">WebServer</h4>
<p>웹 서버를 사용하는 데에는 크게 두 가지 방법이 있다. 첫째는 위에서 말했듯이 WAS 를 거치지 않고 바로 정적 컨텐츠를 전달하는 서버이고, 두 번째는 클라이언트의 요청을 WAS 로 보내어 WAS 가 처리한 결과를 응답하여 WAS 의 기능을 보조하는 것이다.</p>
<p>가장 대표적인 웹 서버로는 Apache 와 NginX 정도를 들 수 있을 것이다.</p>
<h4 id="was">WAS</h4>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/40f8e14e-e456-412a-bc7b-68eca6b3b2c6/image.png" alt=""></p>
<p>WAS 는 Web Server 와 Web Container 로 이루어져, 정적 페이지에서 담기 어려운 비즈니스 로직이나 DB 조회 같은 동적 컨텐츠를 제공한다.</p>
<p>내가 사용했던 SpringBoot 의 Apache Tomcat 의 경우, 웹 서버는 Apache 가 담당하고 WAS 는 Tomcat 이 담당하는 격이다.</p>
<p>그렇다면 웹 서버는 프론트엔드 서버, WAS 는 백엔드 서버라고 정의할 수 있을까? 솔직히 말하면 이 부분에 대한 대답이 굉장히 모호하다. Apache 와 NginX 에서 제공하는 기능이 다르고, 프론트엔드에서 동적 컨텐츠를 제공할 수도 있기 때문에 명확하게 딱 정의를 내릴 수는 없는 것 같다.</p>
<p>확실한 건 정적 컨텐츠를 담당하는 쪽이 웹 서버이고, 비즈니스 로직을 담당하여 동적 컨텐츠를 담당하는 부분이 WAS 라는 것이라고 생각한다. <del>이에 대한 답을 아시는 분은 댓글 남겨주세요...!!</del></p>
<p>웹 서버와 WAS 관련 내용은 아래의 영상을 통해 조금 더 자세히 알아보도록 하자.</p>
<blockquote>
<p>[10분 테코톡] 👳‍♂️ 알리의 Web Server vs WAS : <a href="https://www.youtube.com/watch?v=mcnJcjbfjrs&amp;list=PLq6YHA1IXXnknjlwxAJqW7oq6pNjcOgwT&amp;index=2">https://www.youtube.com/watch?v=mcnJcjbfjrs&amp;list=PLq6YHA1IXXnknjlwxAJqW7oq6pNjcOgwT&amp;index=2</a></p>
</blockquote>
<p>그렇다면 내가 사용했던 SpringBoot 에 Apache Tomcat 에는 Apache 라고 하는 웹 서버가 있는 데 왜 굳이 여기에 NginX 를 사용해야 했을까?</p>
<p>이에 대한 답은 아래를 보며 확인해보도록 하자.</p>
<h2 id="2-3-nginx-의-탄생-배경">2-3) NginX 의 탄생 배경</h2>
<p>결론부터 말하자면, NginX 는 90년대 후반부터 200년대 초반을 주름잡던 Apache Server 를 보조하기 위해 만들어졌다.</p>
<p>Apache Server 는 과거 Unix 기반의 NCSA HTTPd 라는 서버를 보완하여 만들어진 서버였다. Apache Server 가 인기가 많았던 이유 중 하나는 다양한 모듈을 만들어 서버에 빠르게 기능을 추가할 수 있다는 장점이 있었다. 그러나 이것이 나중에 발목을 잡는 하나의 요소가 된다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/971880f6-343c-4a6b-881a-1eff23d8c8cc/image.png" alt=""></p>
<p>또한 Apache Server 는 프로세스 / 스레드 기반의 방식으로 처리했다.</p>
<p>클라이언트의 요청이 올 때마다 Connection 을 형성하기 위해 Process 를 할당하는 방식이다. 이는 Unix 계열 OS 가 Connection 을 형성하는 모델과 같다고 한다. 그러나 Process 를 만드는 데에는 시간이 오래 걸리기 때문에 미리 Process 를 만들어 새로운 Connection 이 생성되면 미리 만들어놓은 Process 를 할당해주게 되었다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/194e7cfe-ec79-4659-88d2-0457a245e92f/image.png" alt="">
<img src="https://velog.velcdn.com/images/ming_gry/post/34745bf6-9e71-43ec-8ea4-3dad1383a4c9/image.png" alt=""></p>
<p>그러나 밀레니엄을 맞는 1999년에 컴퓨터의 보급이 확장되면서 서버에 연결된 Connection 이 늘어나고 C10K(Connection 10,000 Problem) 문제가 발생된다. 동시에 연결된 Connection 이 만 단위를 넘어가는 순간 서버가 더 이상 Connection 을 생성하지 못하는 것이었다.</p>
<p>자세한 C10K 의 이유는 아래와 같다.</p>
<blockquote>
<ol>
<li>메모리 부족 : 동시에 연결된 Connection 이 많아져 생성된 Process 가 많아짐</li>
<li>무거운 프로그램 : 다양한 모듈로 인해 Process 가 차지하는 리소스가 부족해짐</li>
<li>CPU 부하 증가 : 많은 Connection 에서 요청이 들어와 Context Switching 횟수가 증가함</li>
</ol>
</blockquote>
<p>이러한 Apache 의 문제점을 보완하기 위해 2004년, 드디어 NginX 가 세상에 나왔다. Apache Server 앞에 NginX 를 둠으로써 수 많은 Connection 을 NginX 가 대신하여 Apache Server 의 부하를 낮추기 위함이었다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/fcfe59ce-3804-4029-a6d6-e5de14270e14/image.png" alt=""></p>
<p>NginX 자체가 웹 서버의 역할도 하기 때문에 정적 컨텐츠는 자체적으로 처리하고 동적 컨텐츠가 필요할 때만 Apache Server 에 데이터를 요청하는 방식으로 동작했다.</p>
<h2 id="2-4-nginx-의-구조와-프로세스-관리">2-4) NginX 의 구조와 프로세스 관리</h2>
<p>NginX 가 이렇게 동시 Connection 을 유지할 수 있는 비결은 NginX 의 구조와 프로세스 관리에 있다고 할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/301715ae-2aeb-49ad-af2f-56a02cee4052/image.png" alt=""></p>
<p>위 사진에서 보는 것과 같이 NginX 는 설정 파일을 읽고 설정에 맞게 Worker Process 를 생성하는 Master Process 와 실제 일을 하는 Worker Process 로 구성되어 있다.</p>
<p>Worker Process 는 Listen 소켓을 배정받아 새로운 클라이언트의 요청이 들어오면 Connection 을 형성하고 처리한다. Connection 은 Keep-Alive 시간만큼 유지되지만 Apache Server 와 다르게 Connection 이 생성되었다고 해서 Worker Process 가 해당 Connection 만 붙잡고 있는 것은 아니다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/3dc36bc5-f92b-4e23-bfc2-15fd0df717ae/image.png" alt=""></p>
<p>위 사진과 같이 형성된 Connection 으로부터 어떠한 요청도 없다면 새로운 Connection 을 형성하거나 이미 만들어진 다른 Connection 으로부터 들어온 요청을 처리한다. NginX 에서는 이러한 Connection 형성과 제거, 그리고 새로운 요청을 처리하는 것을 Event 라고 한다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/45607d38-b45d-47a5-8fec-bd4aed52e6dc/image.png" alt=""></p>
<p>이 이벤트들은 OS 커널이 큐 형식으로 Worker Process 에 전달한다. 이렇게 Worker Process 가 계속 일하게 하는 데, 기존 Apache Server 와 달리 유휴 Process 가 크게 줄어 Process 를 더욱 효율적으로 사용하게 되는 것이다.</p>
<p>또한 Disk I/O 와 같이 시간이 오래걸리는 작업의 경우에는 Thread Pool 에 작업을 위임하고 다음 Event 를 처리하기도 한다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/8269a966-4d59-45f4-a6b4-ad49ed1c016d/image.png" alt=""></p>
<p>Apache Server 가 Connection 에 Process 를 할당해 위와 같은 구조로 요청을 처리했다면,</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/0b29e69b-9ea2-4227-86ac-644191936387/image.png" alt=""></p>
<p>NginX 는 여러 개의 Connection 을 Event 방식으로 비동기 처리하는 방식으로 움직이기 때문에 2-1) 에서 서술한 것과 같이 경량화된 Web Server 로 사용할 수 있게 되었다.</p>
<p>NginX 의 역사와 구조 관련 내용은 아래의 영상을 참고하도록 하자.</p>
<blockquote>
<p>[10분 테코톡] 🤫 피케이의 Nginx : <a href="https://www.youtube.com/watch?v=6FAwAXXj5N0">https://www.youtube.com/watch?v=6FAwAXXj5N0</a></p>
</blockquote>
<h2 id="2-5-nginx-의-다양한-기능">2-5) NginX 의 다양한 기능</h2>
<p>NginX 는 WAS 를 지원하기 위해 다양한 기능을 제공하는 데, 그 중 대표적인 기능 세 가지에 대해 서술해보고자 한다.</p>
<h3 id="리버스-프록시">리버스 프록시</h3>
<p>프록시란 대리자라는 뜻으로, 프록시 객체나 프록시 서버 등을 예로 들 수 있겠다. 여기서 리버스 프록시란 프록시 서버의 한 방식으로 서버 앞에서 중계 및 대리 역할을 하는 서버를 말한다. 반대로 포워드 프록시는 클라이언트의 중계 및 대리 역할을 한다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/942820f9-3120-4998-b401-36b9aef84d60/image.png" alt=""></p>
<p>포워드 프록시는 로컬 네트워크와 인터넷을 오가는 트래픽을 가로채는 것을 말한다. 학창시절에 컴퓨터실이나 회사 등에서 유해 사이트나 게임 사이트에 들어가지 못하도록 막는 역할을 하는 것을 떠올리면 쉽게 이해할 수 있을 것 같다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/a9b00d29-9a42-4c4c-a664-237a3a237633/image.png" alt=""></p>
<p>리버스 프록시는 위에서 말했듯이 서버와 인터넷 사이에 위치해서 서버의 요청이나 응답을 대신 받아 처리하는 방식이다. 클라이언트는 리버스 프록시 서버에 요청을 하기 때문에 실제 서버의 IP 를 감출 수 있고, 이를 통해 보안을 높일 수 있다.</p>
<h3 id="로드-밸런싱">로드 밸런싱</h3>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/5ed0d467-b25b-493c-9645-40e8ccaa5fd0/image.png" alt=""></p>
<p>이렇듯 리버스 프록시 서버에서 요청을 가로채기 때문에 부하 분산 역할도 가능하다. 한 대의 서버로 감당하기 힘든 트래픽이 몰린다면 다른 서버로 요청을 분산시켜 부하를 줄이는 것이다.</p>
<p>리버스 프록시, 로드 밸런싱 관련 내용은 아래의 영상을 참고해보도록 하자.</p>
<blockquote>
<p>[10분 테코톡] 🌟조앤의 Forward Proxy vs Reverse Proxy vs Load Balancer
 : <a href="https://www.youtube.com/watch?v=u4O4zHdiFhk&amp;list=PLq6YHA1IXXnknjlwxAJqW7oq6pNjcOgwT&amp;index=11">https://www.youtube.com/watch?v=u4O4zHdiFhk&amp;list=PLq6YHA1IXXnknjlwxAJqW7oq6pNjcOgwT&amp;index=11</a></p>
</blockquote>
<h3 id="ssl-termination">SSL Termination</h3>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/3534ffd0-10ae-4d52-9b35-82fe99b849a9/image.png" alt=""></p>
<p>위의 사진은 NginX 관련 설명은 아니지만 SSL Termination 관련 설명이 잘되어 있는 것 같아 가져왔다. 위에서 설명했듯이 Https 연결 과정은 생각보다 복잡한 과정을 수반한다. 따라서 각 WAS 에 SSL 인증서를 두는 것보다 리버스 프록시 서버에 SSL 인증서를 두는 방법을 사용하는 것이다. 이렇게 된다면 클라이언트와 리버스 프록시 서버 간에는 Https 통신을 하되 리버스 프록시 서버와 각 WAS 는 Http 통신을 함으로써 Https 통신에 대한 WAS 의 부하를 상당히 낮출 수 있다.</p>
<p>글을 써보고 나니 리버스 프록시 서버의 파생 기능 정도로 보여지는데 이 외에도 캐싱, Http2 등 다양한 기능을 제공해주고 있으니 NginX 공식 패이지의 블로그나 공식 문서를 참고해보도록 하자.</p>
<blockquote>
<p>NginX Blog : <a href="https://www.nginx.com/category/tech/">https://www.nginx.com/category/tech/</a>
NginX 공식 문서 : <a href="https://docs.nginx.com/">https://docs.nginx.com/</a></p>
</blockquote>
<p>이 중에서 내가 사용한 기능은 리버스 프록시와 SSL Termination 기능이었다. 초기 서비스라 동시 접속자가 많지 않을 것이라고 생각해 로드 밸런싱 기능은 사용하지 않았다. <del>조금 늦긴 했지만 한 대의 EC2 안에 NginX 와 WAS 를 모두 설치한 것이 Configuration 오류를 초래했던 것 같고 추후 성능 저하의 원인이 될 가능성이 생길 수도 있었을 것 같다는 생각을 했다. 물론 동작은 했다...</del></p>
<h1 id="3-nginx-로-https-웹-서비스-배포하기">3) NginX 로 Https 웹 서비스 배포하기</h1>
<h2 id="3-1-nginx-세팅">3-1) NginX 세팅</h2>
<p>우리는 AWS 에서 알 수 없는 이유로 인해 인증서 발급이 되지 않아 Let&#39;s Encrypt 로 인증서를 발급하였다. 이 방법은 구글링에 다양한 방법이 나와있으므로 따로 포스팅하지 않고 Let&#39;s Encrypt 인증서가 발급되었다는 가정 하에 NginX 세팅을 진행하도록 하겠다.</p>
<h3 id="nginx-설치">NginX 설치</h3>
<p>WAS 를 설치하는 법은 넘어가고 아래의 명령어를 입력해 NginX 서버를 설치한다.</p>
<pre><code class="language-bash">$ sudo apt update
$ sudo apt install nginx</code></pre>
<h3 id="nginx-리버스-프록시-설정">NginX 리버스 프록시 설정</h3>
<p>설치된 디렉토리 중 /etc/nginx/conf.d 로 이동하여 설정 파일을 생성후 이를 실행해 아래의 내용을 채우면 된다.</p>
<pre><code class="language-bash">$ cd /etc/nginx/conf.d
$ vim default.conf</code></pre>
<pre><code class="language-bash">server {
    listen 80;
    server_name your.domain.com;

    location / {
        proxy_pass http://192.168.XXX.XXX;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
    }
}</code></pre>
<p>server_name 은 SSL 을 적용할 도메인이고, Certbot 이 server_name 을 기준으로 NginX 설정 파일을 찾아 설정을 자동으로 추가해준다. proxy_pass 에는 WAS 가 설치된 서버의 주소를 적는다. 여기서는 임의의 서버 주소를 입력했지만 나의 경우에는 EC2 한 대에 NginX 와 WAS 모두를 설치했기 때문에 Local IP 인 127.0.0.1 을 입력했다.</p>
<h3 id="ssl-인증서-발급-및-certbot-설치">SSL 인증서 발급 및 Certbot 설치</h3>
<p>Certbot 은 인증서를 자동 발급할 수 있도록 도와주는 도구이다. 아래의 명령어를 입력해 Certbot 을 설치하자.</p>
<pre><code class="language-bash">$ sudo snap install certbot --classic</code></pre>
<p>그리고 아래의 명령을 실행해서 SSL 인증서를 발급받으면 된다. 이 때 적용할 도메인에 대한 A 레코드가 반드시 적용되어 있어야 한다.</p>
<pre><code class="language-bash">$ sudo certbot --nginx</code></pre>
<p>여기까지 완료되었다면 Certbot 은 Let&#39;s Encrypt 를 통해 자동으로 SSL 인증서를 발급해온다. 그 후 Certbot 이 default.conf 에 Https 적용을 위한 설정을 자동으로 한 것을 확인할 수 있다.</p>
<pre><code class="language-bash">server {
    server_name your.domain.com;

    location / {
        proxy_pass http://192.168.XXX.XXX:8080;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/your.domain.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/your.domain.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = your.domain.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80;
    server_name your.domain.com;
    return 404; # managed by Certbot
}</code></pre>
<h3 id="ssl-인증서-자동-갱신-설정">SSL 인증서 자동 갱신 설정</h3>
<p>Let&#39;s Encrypt 는 무료 서비스로 보안 이슈 발생 시 보상금이 없고 90일 짜리 단기 인증서를 발급해준다. 90 일 마다 SSL 인증서를 수동으로 갱신할 수 없으므로 자동화하는 방법을 사용해보자.</p>
<p>Crontab 이란 Linux 에서 제공해주는 기능으로 특정 시간이나 주기로 명령을 수행해준다. 아래의 명령으로 cron job 하나를 생성한다.</p>
<pre><code class="language-bash">$ crontab -e</code></pre>
<p>그 이후 vim 을 선택해 매월, 매일 0시 0분에 certbot 을 실행하여 SSL 인증서를 갱신하고 NginX 설정 파일을 Reload 해주는 명령어를 입력하자.</p>
<pre><code class="language-bash">0 0 * * * certbot renew --post-hook &quot;sudo service nginx reload&quot;</code></pre>
<p>사실 NginX 설정에 정말 많은 오류와 시행착오를 겪었기 때문에 이를 제대로 다뤘다고 말하긴 어려울 것 같다. 대신 수 많은 구글링을 통해 통하는(?) 방법과 포스팅을 찾았고 이를 기반으로 글을 작성했다. 원작자의 글은 아래를 참고하자.</p>
<blockquote>
<p>Nginx와 Let&#39;s Encrypt로 HTTPS 웹 서비스 배포하기 (feat. Certbot) : <a href="https://hudi.blog/https-with-nginx-and-lets-encrypt/">https://hudi.blog/https-with-nginx-and-lets-encrypt/</a></p>
</blockquote>
<h2 id="마무리">마무리</h2>
<p>Https 와 NginX 를 공부해보기 전에는 이렇게나 내용이 방대하고 어려울지 생각을 못했었다. 그러나 덕분에 또 한 번 성장하는 계기를 가질 수 있었고 많은 내용을 배울 수 있었다. 다만 NginX Configuration 에 정말 많은 어려움을 겪으면서 Linux 공부와 NginX 를 더 다양한 방법으로 다뤄보고 싶다는 생각이 들었다. 특히나 로드 밸런싱 관련 기능을 사용해보지 못했기 때문에 이 부분에 대해서 더 다뤄보고 싶다. 이에 대해서는 추후 공부를 통해 추가 포스팅할 수 있도록 하겠다.</p>
<blockquote>
<p>참고 : 
[10분 테코톡] 🍭 다니의 HTTPS : <a href="https://www.youtube.com/watch?v=wPdH7lJ8jf0">https://www.youtube.com/watch?v=wPdH7lJ8jf0</a>
HTTPS가 뭐고 왜 쓰나요? (Feat. 대칭키 vs. 비대칭키) : <a href="https://www.youtube.com/watch?v=H6lpFRpyl14">https://www.youtube.com/watch?v=H6lpFRpyl14</a>
[10분 테코톡] 👶에단의 TLS : <a href="https://www.youtube.com/watch?v=EPcQqkqqouk">https://www.youtube.com/watch?v=EPcQqkqqouk</a>
[10분 테코톡] 🔐 마갸의 암호 : <a href="https://www.youtube.com/watch?v=itehKMMBVjc">https://www.youtube.com/watch?v=itehKMMBVjc</a>
[10분 테코톡] 🌟조앤의 Forward Proxy vs Reverse Proxy vs Load Balancer : <a href="https://www.youtube.com/watch?v=u4O4zHdiFhk&amp;list=PLq6YHA1IXXnknjlwxAJqW7oq6pNjcOgwT&amp;index=10">https://www.youtube.com/watch?v=u4O4zHdiFhk&amp;list=PLq6YHA1IXXnknjlwxAJqW7oq6pNjcOgwT&amp;index=10</a>
[10분 테코톡] 🔮 히히의 OSI 7 Layer : <a href="https://www.youtube.com/watch?v=1pfTxp25MA8&amp;list=PLq6YHA1IXXnknjlwxAJqW7oq6pNjcOgwT&amp;index=61">https://www.youtube.com/watch?v=1pfTxp25MA8&amp;list=PLq6YHA1IXXnknjlwxAJqW7oq6pNjcOgwT&amp;index=61</a>
정적 웹은 뭐고 동적 웹은 뭔가요? : <a href="https://www.youtube.com/watch?v=C06xRvXIAUk">https://www.youtube.com/watch?v=C06xRvXIAUk</a>
[10분 테코톡] 👳‍♂️ 알리의 Web Server vs WAS : <a href="https://www.youtube.com/watch?v=mcnJcjbfjrs">https://www.youtube.com/watch?v=mcnJcjbfjrs</a>
[10분 테코톡] 🤫 피케이의 Nginx : <a href="https://www.youtube.com/watch?v=6FAwAXXj5N0">https://www.youtube.com/watch?v=6FAwAXXj5N0</a>
HTTPS, SSL(Secure Socket Layer) 개념 : <a href="https://www.crocus.co.kr/1387">https://www.crocus.co.kr/1387</a>
[네트워크] OSI 7 계층 : <a href="https://yjkim97.tistory.com/35">https://yjkim97.tistory.com/35</a>
[네트워크] 전송 계층 : <a href="https://velog.io/@gndan4/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%A0%84%EC%86%A1-%EA%B3%84%EC%B8%B5">https://velog.io/@gndan4/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%A0%84%EC%86%A1-%EA%B3%84%EC%B8%B5</a>
[네트워크] 응용 계층 : <a href="https://velog.io/@gndan4/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%9D%91%EC%9A%A9-%EA%B3%84%EC%B8%B5">https://velog.io/@gndan4/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%9D%91%EC%9A%A9-%EA%B3%84%EC%B8%B5</a>
HTTP vs HTTPS의 차이점을 알아보자 : <a href="https://devjem.tistory.com/3">https://devjem.tistory.com/3</a>
[Web] HTTP와 HTTPS의 개념 및 차이점 : <a href="https://mangkyu.tistory.com/98">https://mangkyu.tistory.com/98</a>
HTTPS와 SSL/TLS의 의미 및 차이점 : <a href="https://bumday.tistory.com/43">https://bumday.tistory.com/43</a>
네트워크 - HTTP/HTTPS 차이점, HTTPS란? : <a href="https://coding-start.tistory.com/208">https://coding-start.tistory.com/208</a>
HTTPS와 SSL 인증서 : <a href="https://www.opentutorials.org/course/228/4894">https://www.opentutorials.org/course/228/4894</a>
암호화 알고리즘 종류 : <a href="https://jusungpark.tistory.com/34">https://jusungpark.tistory.com/34</a>
[Programming] 암호화 알고리즘 종류와 분류 : <a href="https://velog.io/@inyong_pang/Programming-%EC%95%94%ED%98%B8%ED%99%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A2%85%EB%A5%98%EC%99%80-%EB%B6%84%EB%A5%98">https://velog.io/@inyong_pang/Programming-%EC%95%94%ED%98%B8%ED%99%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A2%85%EB%A5%98%EC%99%80-%EB%B6%84%EB%A5%98</a>
SSL이란?, SSL과 TLS 정의 및 차이 : <a href="https://kanoos-stu.tistory.com/46">https://kanoos-stu.tistory.com/46</a>
HTTPS는 프론트엔드, 백엔드 어디에 적용되어야 하나? : <a href="https://velog.io/@pjh612/HTTPS%EB%8A%94-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%96%B4%EB%94%94%EC%97%90-%EC%A0%81%EC%9A%A9%EB%90%98%EC%96%B4%EC%95%BC-%ED%95%98%EB%82%98">https://velog.io/@pjh612/HTTPS%EB%8A%94-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%96%B4%EB%94%94%EC%97%90-%EC%A0%81%EC%9A%A9%EB%90%98%EC%96%B4%EC%95%BC-%ED%95%98%EB%82%98</a>
[NginX] NginX 시작하기 1/3 - 기초편 : <a href="https://blog.naver.com/PostView.nhn?blogId=pjt3591oo&amp;logNo=222242046633&amp;parentCategoryNo=&amp;categoryNo=92&amp;viewDate=&amp;isShowPopularPosts=false&amp;from=postView">https://blog.naver.com/PostView.nhn?blogId=pjt3591oo&amp;logNo=222242046633&amp;parentCategoryNo=&amp;categoryNo=92&amp;viewDate=&amp;isShowPopularPosts=false&amp;from=postView</a>
Nginx란 무엇인가? : <a href="https://dkswnkk.tistory.com/513">https://dkswnkk.tistory.com/513</a>
Web Server와 WAS의 차이 : <a href="https://dkswnkk.tistory.com/503?category=551275">https://dkswnkk.tistory.com/503?category=551275</a>
[Nginx] 웹 서버 Nginx 에 대해서... : <a href="https://hyeo-noo.tistory.com/205#Nginx%EB%-E%--%-F%--%---">https://hyeo-noo.tistory.com/205#Nginx%EB%-E%--%-F%--%---</a>
웹 서버와 NginX : <a href="https://tecoble.techcourse.co.kr/post/2021-07-30-web-server-and-nginx/">https://tecoble.techcourse.co.kr/post/2021-07-30-web-server-and-nginx/</a>
[7장] 5-4. SSL Termination 및 보안 기능 : <a href="https://nulls.co.kr/AWS/234">https://nulls.co.kr/AWS/234</a>
SSL 보안인증서 무료와 유료 차이점이 있나요? : <a href="https://sir.kr/qa/426007">https://sir.kr/qa/426007</a>
Nginx와 Let&#39;s Encrypt로 HTTPS 웹 서비스 배포하기 (feat. Certbot) : <a href="https://hudi.blog/https-with-nginx-and-lets-encrypt/">https://hudi.blog/https-with-nginx-and-lets-encrypt/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해99 10주차 WIL - SSE 와 ConcurrentHashMap]]></title>
            <link>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-10%EC%A3%BC%EC%B0%A8-WIL</link>
            <guid>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-10%EC%A3%BC%EC%B0%A8-WIL</guid>
            <pubDate>Sun, 27 Nov 2022 15:30:08 GMT</pubDate>
            <description><![CDATA[<p>실전 프로젝트의 3주차 과정을 거치며 MVP 중간 발표까지 마쳤다. 발표 자료는 현재의 아키텍쳐, 기술적 의사 결정과 추후 도전 계획에 대해 발표하였다. 발표 내용은 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/9817f1e0-289a-4310-b9aa-3b0726cdb0e3/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/7f4197c5-f733-4404-90f5-ae0d81ea5cb2/image.png" alt=""></p>
<blockquote>
<p><strong>중간 발표 시연 영상</strong> : <a href="https://youtu.be/r061p4BKMb4">https://youtu.be/r061p4BKMb4</a></p>
</blockquote>
<p>3주차에 가장 신경 쓴 부분은 SSE 에 대한 내용이었다. 그러나 발표 내용에는 들어가지 않은 이유는 사실 내가 설명할 자신이 없어서였다... 물론 WebSocket 과 SSE 의 차이에 대한 것은 충분히 발표할 수 있었지만 내부 구현과 ConcurrentHashMap 에 대해 자세히 설명해보라고 하면 자신이 없었다. <del>SSE 가 어떤 것이고, SSE 객체에 대해서는 어느 정도 이해를 한 것 같은데, 솔직히 말하면 코드를 거의 가져다 쓴 수준이고 내부 구현은 아직도 설명이 어렵다.</del></p>
<p>그래서 다시 공부하는 겸 WebSocket 과 SSE 의 차이, ConcurrentHashMap 에 대해서 포스팅해보고자 한다. SSE 알림 기능 구현은 아래의 포스팅을 참고하자. <del>다른 버전도 있으나 ConcurrentHashMap 을 쓴 이 버전이 공부하는 사람 입장에서는 더 좋다고 생각한다.</del></p>
<blockquote>
<p><strong>알림 기능을 구현해보자 - SSE(Server-Sent-Events)!</strong> : <a href="https://gilssang97.tistory.com/69">https://gilssang97.tistory.com/69</a></p>
</blockquote>
<h1 id="1-sse">1) SSE</h1>
<h2 id="1-1-sse-란">1-1) SSE 란?</h2>
<p><strong>SSE</strong> 란 Server Sent Event 의 약자로, <strong>서버에서 보내는 이벤트</strong>라는 뜻이다. <strong>서버에서 클라이언트로 일방적으로 보내주는 방식</strong>인데 실시간 알림 기능이라고 하면 폴링, 스트리밍, 웹 소켓 등으로도 구현이 가능하지만 우리가 구현하려는 기능은 클라이언트로부터 따로 받아서 처리할 데이터가 없으므로 SSE 로 알림 기능을 구현하였다. <del>사실 이것도 프론트엔드에서 시간 부족으로 백엔드에서 코드 짜서 Postman 테스트 정도만 했지 서비스에서 구현하진 못했다ㅠㅠ</del></p>
<p>내가 생각한 SSE 알림 기능의 플로우는 다음과 같다.</p>
<blockquote>
<ol>
<li>유저의 활동에 따라 뱃지가 지급됨</li>
<li>뱃지 생성 실시간 알림 팝업이 뜸 (SSE 로 구현, 알림은 저장되지 않음)</li>
<li>알림 팝업에 이벤트 피드 생성 여부를 확인함</li>
<li>이벤트 피드 확인 버튼을 누르면 생성된 뱃지에 따라 이벤트 피드가 생성됨</li>
</ol>
</blockquote>
<p>물론 이벤트 피드 생성을 위해 팝업에는 관련 API 가 포함된 버튼이 있어야되긴 할 것이다. 그러나 그 버튼을 누른다면 http 로 받으면 된다는 생각이었고, 굳이 WebSocket 을 쓸 필요는 없다고 생각했다. 일방적으로 서버에서 실시간 알림을 생성해서 띄워주면 된다는 생각이었다.</p>
<h2 id="1-2-다른-방식과-비교">1-2) 다른 방식과 비교</h2>
<p><strong>http 프로토콜의 특징 중 하나는 비연결성</strong>이다. 아예 연결이 되어있지 않다라는 뜻이 아니라 클라이언트가 <strong>1회 요청하면 1회 응답하고 연결이 종료</strong>된다는 뜻이다. 그렇기 때문에 실시간 알림 기능에서는 그 외에 다른 방식을 사용해야 한다.</p>
<p>위의 블로그에 워낙 정리가 잘 되어 있어 사진도 가져왔다.</p>
<blockquote>
<p>출처 - <strong>알림 기능을 구현해보자 - SSE(Server-Sent-Events)!</strong> : <a href="https://gilssang97.tistory.com/69">https://gilssang97.tistory.com/69</a></p>
</blockquote>
<h3 id="폴링">폴링</h3>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/d4514723-ea58-4c53-81d8-aa31d3e6e71a/image.png" alt=""></p>
<p>위에서 말했듯이 <strong>http 는 비연결성</strong>을 띈다. 이를 해결하기 위해 <strong>주기적으로 서버에 요청을 날려 어떤 이벤트가 발생한다면 이에 대해 응답해주는 방식</strong>이 폴링이다. 서버에 요청을 날리는 순간에는 Connection 이 생성되어 연결성이 생기기 때문이다.</p>
<p>그러나 <strong>클라이언트가 늘어나고, 요청이 많아지면서 서버에는 많은 부하</strong>가 갈 수밖에 없다. 또한 <strong>지속적으로 Connection 을 맺고 끊는 것에 대한 서버의 부담</strong>이 커진다. 특히나 <strong>Connection 이 끊기고 새로 생기는 도중에 Event 가 발생</strong>하면 클라이언트는 해당 <strong>Event 를 놓치게 된다.</strong> <strong>실시간성을 보장받을 수 없는 것</strong>이다.</p>
<h3 id="롱폴링">롱폴링</h3>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/3d66a3d0-4bf6-4488-b997-e08baf3aa5ea/image.png" alt=""></p>
<p><strong>폴링과 마찬가지로 주기적으로 서버에 요청</strong>을 날리는 것은 같으나, <strong>Connection 을 열어두는 시간을 더 늘리는 것</strong>을 말한다. 아무래도 <strong>Connection 시간이 길다보니 요청의 숫자가 폴링에 비해 줄어들 수 있다.</strong></p>
<p>그러나 결국 롱폴링도 폴링이기 때문에 <strong>폴링이 가지는 모든 단점을 갖는다.</strong> 그리고 <strong>Connection 을 유지하는 시간이 짧다면 폴링과 사실상 다를게 없다.</strong> 게다가 <strong>Connection 이 지속적으로 연결되어 있기 때문에 이에 대한 서버의 부하도 줄어들지 않는다.</strong></p>
<h3 id="스트리밍">스트리밍</h3>
<p>사람들이 흔히 아는 그 스트리밍과 비슷한 부분도 있고 다른 부분도 있다. 스트리밍은 요청에 대한 응답으로 Connection 을 종료하지 않고 <strong>Connection 을 유지한 상태에서 Event 가 발생하면 지속적으로 응답을 보내주는 형식</strong>이다.</p>
<p>폴링 / 롱폴링 / 스트리밍 모두 <strong>http 를 통해 통신</strong>하기 때문에 <strong>Request 와 Response 헤더가 모두 불필요하게 크다.</strong></p>
<h3 id="웹소켓">웹소켓</h3>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/398227ce-b994-4cef-9799-4a410c2600df/image.png" alt=""></p>
<p><strong>웹소켓 또한 프로토콜의 일종</strong>으로, 위의 방식들의 단점을 보완하면서도 클라이언트와 서버 간의 효율적인 양방향 통신을 구현하기 위한 기술이다.</p>
<p>웹소켓은 <strong>최초 접속에는 http 를 이용해 handshaking 후 양방향 통신</strong>이 이루어진다. 실<strong>시간으로 양방향 통신이 가능하기 때문에 Connection 을 지속적으로 맺고 끊는 서버의 부담을 줄일 수 있다.</strong> 또한 불필요하게 컸던 <strong>Request 와 Response 헤더의 크기를 줄일 수</strong> 있다.</p>
<p><strong>웹소켓은 양방향 통신에 적합</strong>하므로, <strong>채팅과 같이 실시간으로 데이터를 주고 받는 데에는 용이</strong>할 수 있다. 하지만 서버에서 <strong>단순히 알림을 보내는 데에는 적합하지 않다.</strong></p>
<h3 id="sse">SSE</h3>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/55620a34-e8aa-4f22-a740-a4747d97070e/image.png" alt=""></p>
<p>위에서 썼듯이, SSE 는 웹소켓과 다르게 서버로부터 <strong>데이터만 받을 수 있게 된다.</strong> 웹소켓과 달리 별도의 프로토콜을 사용하지 않고 <strong>http 만 사용하기 때문에 훨씬 가볍다.</strong></p>
<p>종합해봤을 때 우리 프로젝트의 뱃지 생성 알림에 가장 알맞은 기술은 SSE 라고 생각이 들어 이를 사용했다.</p>
<h1 id="2-concurrenthashmap">2) ConcurrentHashMap</h1>
<p>이걸 공부하면서 스레드와 동기화 관련 내용을 정말 많이 공부할 수 있었다. 물론 이해하기 정말 어려웠고 시간도 정말 오래 걸렸지만, 앞으로 개발자로 사는 데에 많은 도움을 줄 수 있을 것이라고 생각해 더 깊이 더 열심히 공부했던 것 같다. <del>개발 입문 이래 이해하기 가장 어려웠던 내용이 아닌가 싶다... 나중에 Database Lock 도 사용해보고 추가 포스팅 해야겠다.</del></p>
<p>아무래도 포스팅 전에 ConcurrentHashMap 제대로 이해하고 싶어서 스레드와 Concurrency Control, Java 에서 Concurrency 를 어떻게 관리하는지에 대한 기본 지식이 필요해 이에 대해 먼저 공부를 했다. 공부했던 내용과 순서는 아래와 같다. 댓글은 남기지 못했지만 여기에서나마 정말 좋은 영상을 만들어주신 유튜브 쉬운코드님께 감사의 말씀을 전하고 싶다.</p>
<blockquote>
<p><strong>프로세스, 스레드, 멀티태스킹, 멀티스레딩, 멀티프로세싱, 멀티프로그래밍</strong> : <a href="https://www.youtube.com/watch?v=QmtYKZC0lMU&amp;list=PLcXyemr8ZeoT-_8yBc_p_lVwRRqUaN8ET&amp;index=6">https://www.youtube.com/watch?v=QmtYKZC0lMU&amp;list=PLcXyemr8ZeoT-_8yBc_p_lVwRRqUaN8ET&amp;index=6</a>
<strong>스레드 풀(thread pool)은 왜 쓰는 걸까요?</strong> : <a href="https://www.youtube.com/watch?v=B4Of4UgLfWc&amp;list=PLcXyemr8ZeoT-_8yBc_p_lVwRRqUaN8ET&amp;index=19">https://www.youtube.com/watch?v=B4Of4UgLfWc&amp;list=PLcXyemr8ZeoT-_8yBc_p_lVwRRqUaN8ET&amp;index=19</a>
<strong>컨텍스트 스위칭 뽀개기</strong> : <a href="https://www.youtube.com/watch?v=Xh9Nt7y07FE&amp;list=PLcXyemr8ZeoT-_8yBc_p_lVwRRqUaN8ET&amp;index=7">https://www.youtube.com/watch?v=Xh9Nt7y07FE&amp;list=PLcXyemr8ZeoT-_8yBc_p_lVwRRqUaN8ET&amp;index=7</a>
<strong>동기화(synchronization), 경쟁 조건(race condition), 임계 영역(critical section)</strong> : <a href="https://www.youtube.com/watch?v=vp0Gckz3z64&amp;list=PLcXyemr8ZeoT-_8yBc_p_lVwRRqUaN8ET&amp;index=9">https://www.youtube.com/watch?v=vp0Gckz3z64&amp;list=PLcXyemr8ZeoT-_8yBc_p_lVwRRqUaN8ET&amp;index=9</a>
<strong>스핀락(spinlock) 뮤텍스(mutex) 세마포(semaphore)</strong> : <a href="https://www.youtube.com/watch?v=gTkvX2Awj6g&amp;list=PLcXyemr8ZeoT-_8yBc_p_lVwRRqUaN8ET&amp;index=10">https://www.youtube.com/watch?v=gTkvX2Awj6g&amp;list=PLcXyemr8ZeoT-_8yBc_p_lVwRRqUaN8ET&amp;index=10</a>
<strong>모니터가 어떻게 동기화에 사용되는지 아주 자세히 설명합니다!</strong> : <a href="https://www.youtube.com/watch?v=Dms1oBmRAlo&amp;list=PLcXyemr8ZeoT-_8yBc_p_lVwRRqUaN8ET&amp;index=11">https://www.youtube.com/watch?v=Dms1oBmRAlo&amp;list=PLcXyemr8ZeoT-_8yBc_p_lVwRRqUaN8ET&amp;index=11</a>
<strong>데드락(교착상태)</strong> : <a href="https://www.youtube.com/watch?v=_dzRW48NB9M&amp;list=PLcXyemr8ZeoT-_8yBc_p_lVwRRqUaN8ET&amp;index=15">https://www.youtube.com/watch?v=_dzRW48NB9M&amp;list=PLcXyemr8ZeoT-_8yBc_p_lVwRRqUaN8ET&amp;index=15</a></p>
</blockquote>
<h2 id="2-1-thread-safe">2-1) Thread-Safe</h2>
<p><strong>ConcurrentHashMap 의 가장 큰 특징은 Thread Safe</strong> 하다는 것이다. <del>물론 HashTable 도 Thread Safe 하지만</del> 그래서 이에 대해 먼저 짚고 넘어가도록 하겠다.</p>
<p><strong>Thread Safe</strong> 란 <strong>멀티 스레드 프로그래밍에서 어떤 함수나 변수, 혹은 객체가 여러 스레드로부터 동시에 접근이 이루어져도 프로그램의 실행에 문제가 없음</strong>을 뜻한다. 라고 위키피디아는 정의했다.</p>
<p>결국 <strong>두 개 이상의 스레드가 Race Condition 에 들어</strong>가거나 <strong>동시에 접근</strong>해도 <strong>연산 결과의 정합성이 보장되는 것</strong>이 Thread Safe 라고 할 수 있겠다.</p>
<p>그렇다면 내가 개발한 <strong>SpringBoot 서버는 멀티 스레드 환경</strong>인가? 맞다. SpringBoot 에서는 Tomcat 을 사용하는데, <strong>Tomcat 이 자동으로 스레드 풀을 생성</strong>해주고 <strong>각 Request 별 스레드를 할당해 요청을 처리</strong>한다.</p>
<h2 id="2-2-hashmap">2-2) HashMap</h2>
<p><strong>HashMap</strong> 은 Thread Safe 하지 않아 <strong>싱글 스레드 환경에서 사용하는 것이 좋다.</strong> <strong>동기화 처리는 하지 않기 때문에 데이터 탐색하는 속도는 빠르지만 신뢰성과 안정성이 떨어진다.</strong></p>
<pre><code class="language-java">public class HashMap&lt;K,V&gt; extends AbstractMap&lt;K,V&gt; implements Map&lt;K,V&gt;, Cloneable, Serializable {

    ...

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
        Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) &amp; hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node&lt;K,V&gt; e; K k;
            if (p.hash == hash &amp;&amp;
                ((k = p.key) == key || (key != null &amp;&amp; key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode&lt;K,V&gt;)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount &gt;= TREEIFY_THRESHOLD - 1)
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &amp;&amp;
                        ((k = e.key) == key || (key != null &amp;&amp; key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) {
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size &gt; threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

    ...
}</code></pre>
<p>코드에서 보다시피 volatile, syncronized, CAS 중 어떤 것도 들어있지 않다.</p>
<h2 id="2-3-hashtable">2-3) HashTable</h2>
<p><strong>HashTable</strong> 은 <strong>Thread Safe 하기 때문에 멀티 스레드 환경에서 사용</strong>할 수 있다. get(), put(), remove() 등 <strong>데이터를 다루는 메소드에 syncronized 키워드</strong>가 들어있어 <strong>메소드를 호출하기 전에 스레드 간 동기화 락</strong>을 건다.</p>
<p>이 덕분에 HashTable 은 Thread Safe 할 수 있지만, <strong>메소드 레벨에 syncronized 키워드가 들어있기 때문에 비교적 동작이 느리다.</strong></p>
<pre><code class="language-java">public class Hashtable&lt;K,V&gt; extends Dictionary&lt;K,V&gt; implements Map&lt;K,V&gt;, Cloneable, java.io.Serializable {

    ...

    public synchronized V put(K key, V value) { //접근제한자 뒤에 synchronized 키워드를 사용했다.
        if (value == null) {
            throw new NullPointerException();
        }

        Entry&lt;?,?&gt; tab[] = table;
        int hash = key.hashCode();
        int index = (hash &amp; 0x7FFFFFFF) % tab.length;
        @SuppressWarnings(&quot;unchecked&quot;)
        Entry&lt;K,V&gt; entry = (Entry&lt;K,V&gt;)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) &amp;&amp; entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }

    ...
}</code></pre>
<h2 id="2-4-concurrenthashmap">2-4) ConcurrentHashMap</h2>
<p><strong>ConcurrentHashMap</strong> 또한 <strong>Thread Safe 하기 때문에 멀티 스레드 환경에서 사용</strong>할 수 있다. HashTable 과 다른 점은 메소드 단에서 syncronized 키워드를 쓰지 않고 <strong>특정 상황에만 syncronized 키워드를 사용하기 때문에 Thread Safe</strong> 하면서도 HashTable 보다 <strong>비교적 빠르다는 특징</strong>을 가지고 있다.</p>
<p>정확히 말하면, <strong>get() 에는 아예 syncronized 키워드가 존재하지 않고, put() 중간에 syncronized 키워드가 존재</strong>한다. <del>물론 remove(), replace() 등에도 syncronized 키워드는 들어가긴 하지만 마찬가지로 메소드 중간에 syncronized 키워드가 존재한다.</del></p>
<p>ConcurrentHashMap 의 <strong>put() 메소드의 구현 방식</strong>을 보면 어떻게 동작하는지 더 자세히 알 수 있다. 이를 크게 두 가지 경우로 나눌 수 있는데, 빈 해시 버킷에 노드를 삽입하는 경우와 이미 기존 노드가 있는 경우이다.</p>
<p>이것도 마찬가지로 너무 정리가 잘되어 있어서 사진을 가져왔다.</p>
<blockquote>
<p>출처 - <strong>[java] ConcurrentHashMap 동기화 방식</strong> : <a href="https://pplenty.tistory.com/17">https://pplenty.tistory.com/17</a></p>
</blockquote>
<h3 id="빈-해시-버킷에-노드를-삽입하는-경우">빈 해시 버킷에 노드를 삽입하는 경우</h3>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/500170d1-33cb-4d44-a56e-1aa4030ec66f/image.png" alt=""></p>
<p>(1) table 은 ConcurrentHashMap 에서 내부적으로 관리하는 Node 의 가변 Array 이며, 무한 루프를 통해 삽입될 bucket 을 확인한다.
(2) 새로운 노드를 삽입하기 위해 해당 버킷 값을 tabAt() 를 통해 해당 bucket 을 가져오고 bucket == null 로 비어있는지 확인한다.
(3) bucket 이 비어있는 경우 casTabAt() 을 통해 Node 를 담고 있는 volatile 변수 (ConcurrentHashMap 클래스에 필드에 선언되어 있음) 에 접근하고 Node 와 기대값 null 을 비교하여 같으면 Node를 생성해 넣고, 아니면 (1) 로 돌아가 재시도한다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/f248d095-750f-4e46-8640-3d83c02cbef2/image.png" alt=""></p>
<p>volatile 변수에 2번 접근하는 동안 원자성(atomic) 을 보장하기 위해 기대되는 값과 비교(Compare) 하여 맞는 경우에 새로운 노드를 넣는다. (Swap)</p>
<p>결국 여기서는 <strong>lock 을 사용하지 않고 Compare And Swap 을 이용</strong>하여 새로운 노드를 해시 버킷에 삽입하는 것이다.</p>
<p>Volatile 과 Compare And Swap 관련된 정보는 아래의 포스팅을 참고하자.</p>
<blockquote>
<p><strong>Java volatile이란?</strong> : <a href="https://nesoy.github.io/articles/2018-06/Java-volatile">https://nesoy.github.io/articles/2018-06/Java-volatile</a>
<strong>CAS(Compare And Swap) 알고리즘</strong> : <a href="https://chickenpaella.tistory.com/97">https://chickenpaella.tistory.com/97</a></p>
</blockquote>
<h3 id="이미-기존-노드가-있는-경우">이미 기존 노드가 있는 경우</h3>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/07b7c385-7d6b-47eb-9cb5-7264ccfb9289/image.png" alt=""></p>
<p><strong>기존 노드가 있는 경우에 드디어 synchronized 키워드</strong>가 등장한다. HashMap 과 구현이 비슷한데, 동일한 Key 이면 Node 를 새로운 노드로 바꾸고, 해시 충돌(hash collision) 인 경우에는 Separate Chaining 에 추가 하거나 TreeNode 에 추가한다. TREEIFY_THRESHOLD 값에 따라 체이닝을 트리로 바꾼다.</p>
<h2 id="마무리">마무리</h2>
<p><strong>싱글 스레드 환경이면 HashMap</strong> 을, <strong>멀티 스레드 환경이라면</strong> HashTable 보다는 <strong>ConcurrentHashMap</strong> 이 더 좋다. 위에도 서술했듯이 ConcurrentHashMap 은 특정 상황을 제외하고는 lock 을 걸지 않기 때문에 HashTable 보다 비교적 빠르게 작동할 수 있다.</p>
<blockquote>
<p>참고 : 
[Spring + SSE] Server-Sent Events를 이용한 실시간 알림 : <a href="https://velog.io/@max9106/Spring-SSE-Server-Sent-Events%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%95%8C%EB%A6%BC">https://velog.io/@max9106/Spring-SSE-Server-Sent-Events%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%95%8C%EB%A6%BC</a>
알림 기능을 구현해보자 - SSE(Server-Sent-Events)! : <a href="https://gilssang97.tistory.com/69">https://gilssang97.tistory.com/69</a>
웹소켓 과 SSE(Server-Sent-Event) 차이점 알아보고 사용해보기 : <a href="https://surviveasdev.tistory.com/entry/%EC%9B%B9%EC%86%8C%EC%BC%93-%EA%B3%BC-SSEServer-Sent-Event-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B3%A0-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0">https://surviveasdev.tistory.com/entry/%EC%9B%B9%EC%86%8C%EC%BC%93-%EA%B3%BC-SSEServer-Sent-Event-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B3%A0-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</a>
[Java] ConcurrentHashMap 이란 무엇일까? : <a href="https://devlog-wjdrbs96.tistory.com/269">https://devlog-wjdrbs96.tistory.com/269</a>
HashMap vs HashTable vs ConcurrentHashMap : <a href="https://tecoble.techcourse.co.kr/post/2021-11-26-hashmap-hashtable-concurrenthashmap/">https://tecoble.techcourse.co.kr/post/2021-11-26-hashmap-hashtable-concurrenthashmap/</a>
[java] ConcurrentHashMap 동기화 방식 : <a href="https://pplenty.tistory.com/17">https://pplenty.tistory.com/17</a>
[Java] ConcurrentHashMap는 어떻게 Thread-safe 한가? : <a href="https://velog.io/@alsgus92/ConcurrentHashMap%EC%9D%98-Thread-safe-%EC%9B%90%EB%A6%AC">https://velog.io/@alsgus92/ConcurrentHashMap%EC%9D%98-Thread-safe-%EC%9B%90%EB%A6%AC</a>
[OS] 쓰레드 세이프(Tread Safe)란? : <a href="https://wooono.tistory.com/523">https://wooono.tistory.com/523</a>
[OS] Thread Safe란? : <a href="https://gompangs.tistory.com/entry/OS-Thread-Safe%EB%9E%80">https://gompangs.tistory.com/entry/OS-Thread-Safe%EB%9E%80</a>
Thread safe와 동기화 객체 : <a href="https://velog.io/@hoo00nn/Thread-safe%EC%99%80-%EB%8F%99%EA%B8%B0%ED%99%94-%EA%B0%9D%EC%B2%B4">https://velog.io/@hoo00nn/Thread-safe%EC%99%80-%EB%8F%99%EA%B8%B0%ED%99%94-%EA%B0%9D%EC%B2%B4</a>
스레드 안전이란 무엇인가? : <a href="https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=complusblog&amp;logNo=220985528418">https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=complusblog&amp;logNo=220985528418</a>
스레드 안전(Thread-Safety)란? : <a href="https://developer-ellen.tistory.com/205">https://developer-ellen.tistory.com/205</a>
[Java] 멀티 스레드 : <a href="https://velog.io/@sezzzini/Java-%EB%A9%80%ED%8B%B0-%EC%8A%A4%EB%A0%88%EB%93%9C">https://velog.io/@sezzzini/Java-%EB%A9%80%ED%8B%B0-%EC%8A%A4%EB%A0%88%EB%93%9C</a>
JAVA 쓰레드란(Thread) ? - JAVA에서 멀티쓰레드 사용하기 : <a href="https://honbabzone.com/java/java-thread/">https://honbabzone.com/java/java-thread/</a>
스프링부트는 어떻게 다중 유저 요청을 처리할까? : <a href="https://velog.io/@sihyung92/how-does-springboot-handle-multiple-requests">https://velog.io/@sihyung92/how-does-springboot-handle-multiple-requests</a>
SpringBoot 멀티스레드 공부 및 실험 : <a href="https://4whomtbts.tistory.com/118">https://4whomtbts.tistory.com/118</a>
Compare and Swap(CAS) Algorithm : <a href="https://itchallenger.tistory.com/entry/Compare-and-SwapCAS-Algorithm">https://itchallenger.tistory.com/entry/Compare-and-SwapCAS-Algorithm</a>
Java Atomic Type 이해하기(AtomicBoolean, AtomicInteger) : <a href="https://readystory.tistory.com/53">https://readystory.tistory.com/53</a>
CAS(Compare And Swap) 알고리즘 : <a href="https://chickenpaella.tistory.com/97">https://chickenpaella.tistory.com/97</a>
Java volatile이란? : <a href="https://nesoy.github.io/articles/2018-06/Java-volatile">https://nesoy.github.io/articles/2018-06/Java-volatile</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해99 9주차 WIL - AOP 에 대한 나의 오해와 EventListener]]></title>
            <link>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-9%EC%A3%BC%EC%B0%A8-WIL</link>
            <guid>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-9%EC%A3%BC%EC%B0%A8-WIL</guid>
            <pubDate>Sun, 20 Nov 2022 11:42:43 GMT</pubDate>
            <description><![CDATA[<p>이번 주차엔 AOP 에 대해 오해하고 있어 이 부분에서 고생을 좀 했었다. 이에 대한 내용을 정리해 포스팅해보도록 하겠다.</p>
<h1 id="1-aop">1) AOP</h1>
<h2 id="1-1-aop-란">1-1) AOP 란?</h2>
<p><strong>AOP</strong> 란 Aspect Oriented Programming 의 약자로, <strong>관점 지향 프로그래밍</strong>이라는 뜻을 갖고 있다. <strong>프로그래밍 로직을 핵심적인 관점과, 부가적인 관점</strong>으로 나누고 그 <strong>관점을 기준으로 모듈화</strong> 한다는 뜻을 갖고 있다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/1a923b0b-6aef-4cee-8dc4-53a6e5498111/image.png" alt=""></p>
<h2 id="1-2-나의-오해">1-2) 나의 오해</h2>
<p>여기서부터 나의 오해가 시작됐다. 지난 포스팅을 보면 우리는 투두 완료, 피드 작성, 댓글 및 리액션 추가 등 유저의 활동에 따라 뱃지를 지급하는 기능을 만들기로 하였다. 내가 이 기능 개발을 맡게 되었는데, 이 부분에서 나는 <strong>뱃지 지급</strong>이 투두, 피드, 댓글 등과 같이 <strong>핵심적인 기능이 아니라 부가적인 기능이라고 생각</strong>하여 AOP 를 적용하였다.</p>
<p>결론부터 말하자면 <strong>핵심 기능과 부가 기능에 대한 오해</strong>를 하고 있었던 것이다. 어쨌든 <strong>핵심기능</strong>이라 하면, <strong>각 API 별 수행하는 비즈니스 로직</strong>이고 <strong>부가기능</strong>은 <strong>핵심기능을 보조하는 기능</strong>으로 로그 기록, API 수행 시간 저장 같은 기능을 맡게 된다.</p>
<p>어쨌든 <strong>뱃지 지급 기능 또한 핵심 기능</strong>에 속한다는 것이었다. <strong>왜? 유저와 관련이 있는 기능이기 때문</strong>이다. <strong>로그 기록이나 API 수행 시간</strong> 같은 것은 서버에서 <strong>개발자가 필요한 것이지 유저와는 관련이 없기 때문에 부가 기능</strong>에 속하는 것이다.</p>
<p>그럼에도 불구하고 잘했던 점은 어쨌든 TodoService 나 FeedService 등의 관심사와 뱃지를 지급하는 관심사는 다르기 때문에, 이를 모듈화 하여 BadgeEventListener 에서 이를 만들도록 했다는 것이다. 또한 BadgeService 를 DI 하지 않고 새로운 기술을 써봤다는 것도 의의가 있다고 생각한다.</p>
<h2 id="1-3-aop-의-한계">1-3) AOP 의 한계(?)</h2>
<p>어쨌든 EventListner 를 쓰기 전에 <strong>AOP</strong> 로 개발을 진행했다. 그러나 <strong>@Transactional 먹히지 않아</strong> 머릿 속이 새하얘졌다. 분명 내 코드에는 문제가 없는데 왜 먹히지 않는 것인지 도저히 이해할 수 없었다. 기술 멘토에게 이를 질문했지만 답장이 없었고...... 무한 구글링과 스택 오버 플로우에서도 마땅한 이유를 찾을 수 없었다. 사실 아직도 왜 안되는지는 잘 모르겠다.</p>
<p>내가 짰던 코드와 문제 상황은 아래와 같았다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/bc064e9b-64ba-4db5-be5a-6e108d17681a/image.PNG" alt="">
<img src="https://velog.velcdn.com/images/ming_gry/post/a87b5998-d40f-496c-b69e-aef145e17f70/image.PNG" alt="">
<img src="https://velog.velcdn.com/images/ming_gry/post/8de011c1-8bda-40d9-9d77-96a17be6d765/image.PNG" alt="">
<img src="https://velog.velcdn.com/images/ming_gry/post/ee86874a-3047-4702-975a-c606d2f1d22c/image.PNG" alt=""></p>
<p>정말 저 메소드의 아무 것도 작동하지 않았다. 하다 못해 에러가 발생하던지 print 메소드라도 작동을 해줬으면 좋았으련만, IntelliJ 가 옆에서 띄워주는 것처럼 This advice advises no methods 라는 오류 메세지만 있을 뿐이었다.</p>
<p>정확하진 않지만 결국 <strong>내가 내린 결론</strong>은, <strong>AOP 에서는 @Transactional 이 작동하지 않는다</strong> 라는 것이다. 이게 맞는 건진 사실 아직도 잘 모르겠다. 그러나 확실한 건 나는 작동하지 않았다는 것이다. <del>물론 이유가 있었겠지...?</del></p>
<p>추측컨데 @Transactional 이 AOP 로 작동한다는 것과 관련이 있을 수도 있다고 생각한다. <del>AOP 에선 AOP 가 안 먹히는 문제가 아닐까?</del> 또, AOP 내부 메소드에서는 DB 와 관련된 작동을 못하도록 만들어놨을 수 있다고 추측해본다. <strong>이 문제에 대한 답을 알고 계신 분이 있다면 댓글로 알려주시길 진심으로 부탁</strong>드린다.</p>
<h1 id="2-aop-가-아닌-다른-기술---eventlistener">2) AOP 가 아닌 다른 기술 - EventListener</h1>
<h2 id="2-1-eventlistener">2-1) EventListener</h2>
<p>결국 돌고 돌아 EventListner 라는 기능을 알게 되었다. 어떤 경로로 이걸 알았는지 기억은 나지 않지만 아마도 <strong>Service 간 강결합 문제</strong>를 검색하다가 알게 되었던 것 같다.</p>
<p>왜냐하면 위에도 기술했지만 <strong>TodoService 나 FeedService 에 BadgeService 를 DI 하여 구현한다면 아래와 같은 코드</strong>가 나왔을 것이다.</p>
<pre><code class="language-java">@Service
@RequiredArgsConstructor
public class TodoService {

    private final TodoRepository feedRepository;
    private final BadgeService badgeService; // BadgeService DI

    @Transactional
    public ResponseEntity&lt;?&gt; completedTodo(Long id, MemberDetailsImpl memberDetails) {

        Todo todo = todoRepository.findById(id).orElseThrow(
                () -&gt; new DoBlockExceptions(ErrorCodes.NOT_FOUND_TODO)
        );

        if (!todo.getMember().getId().equals(memberDetails.getMember().getId())) {
            throw new DoBlockExceptions(ErrorCodes.NOT_VALID_WRITER);
        }

        if (LocalDate.now(ZoneId.of(&quot;Asia/Tokyo&quot;)).isBefore(todo.getTodoDate().getDate())) {
            throw new DoBlockExceptions(ErrorCodes.NOT_ABLE_COMPLETE_TODO);
        }

        todo.completeTask();

        badgeService.createTodoBadge(memberDetails); // 이 부분이 문제다

        if (todo.isCompleted()) {
            return ResponseEntity.ok(&quot;투두가 완료되었습니다.&quot;);
        } else return ResponseEntity.ok(&quot;투두 완료가 취소되었습니다.&quot;);
    }
}</code></pre>
<p>위에 있는 badgeService.createTodoBadge() 메소드가 문제다. <strong>분명 TodoService 는 Badge 와는 아무런 관련이 없는데 Todo 완료와 Badge 생성 로직이 섞여있고 BadgeService 가 TodoService 에 강하게 의존하는 모습</strong>이다. <strong>이것을 EventListner 로 해결</strong>할 수 있다.</p>
<p>EventPublisher 가 받을 Event Class 는 아래와 같이 정의했다. Inner Class 를 사용해 Class 안에서 다양한 Event 들을 정의하고 관리할 수 있도록 했다. 위의 코드에서는 CompletedTodoBadgeEvent 를 넣어줬다. 다양한 Badge Type 구현을 위해 Enum 또한 사용했다.</p>
<pre><code class="language-java">public class BadgeEvents {

    @Getter
    @AllArgsConstructor
    public static class CreateBadgeEvent{

        private BadgeType badgeType;
        private Member member;
    }

    @Getter
    @AllArgsConstructor
    public static class CompletedTodoBadgeEvent {

        private MemberDetailsImpl memberDetails;
    }

    @Getter
    @AllArgsConstructor
    public static class CreateFeedBadgeEvent{

        private MemberDetailsImpl memberDetails;
    }
}</code></pre>
<pre><code class="language-java">@Getter
@AllArgsConstructor
public enum BadgeType {

    CTT(&quot;갓생스타터&quot;, &quot;일정 3개를 달성하셨네요! 갓생은 이제부터 시작입니다&quot;),
    CTTY(&quot;계란 한판&quot;, &quot;일정 30개를 달성하셨네요! 시간 참 빠르죠? 벌써 계란 한판&quot;),
    CTF(&quot;진짜 갓생러&quot;, &quot;일정 50개를 달성하셨네요! 진짜 갓생러가 나타났다&quot;);

    private final String badgeName;
    private final String badgeDetail;
}</code></pre>
<pre><code class="language-java">@Service
@RequiredArgsConstructor
public class TodoService {

    private final TodoRepository feedRepository;
    private final ApplicationEventPublisher applicationEventPublisher; //EventPublisher DI

    @Transactional
    public ResponseEntity&lt;?&gt; completedTodo(Long id, MemberDetailsImpl memberDetails) {

        Todo todo = todoRepository.findById(id).orElseThrow(
                () -&gt; new DoBlockExceptions(ErrorCodes.NOT_FOUND_TODO)
        );

        if (!todo.getMember().getId().equals(memberDetails.getMember().getId())) {
            throw new DoBlockExceptions(ErrorCodes.NOT_VALID_WRITER);
        }

        if (LocalDate.now(ZoneId.of(&quot;Asia/Tokyo&quot;)).isBefore(todo.getTodoDate().getDate())) {
            throw new DoBlockExceptions(ErrorCodes.NOT_ABLE_COMPLETE_TODO);
        }

        todo.completeTask();

        //EventPublisher 에서 EventPublish, Event Class Type 을 넣어 필요한 인자값을 전달하도록 했다.
        applicationEventPublisher.publishEvent(new BadgeEvents.CompletedTodoBadgeEvent(memberDetails));

        if (todo.isCompleted()) {
            return ResponseEntity.ok(&quot;투두가 완료되었습니다.&quot;);
        } else return ResponseEntity.ok(&quot;투두 완료가 취소되었습니다.&quot;);
    }
}</code></pre>
<p>TodoService 에서 Event 를 Publish 했으니 발행된 이벤트를 처리해줄 EventListner 가 필요하다. 그 구현은 아래와 같다.</p>
<pre><code class="language-java">@Component
@RequiredArgsConstructor
public class BadgeEventListener {

    private final TodoRepository todoRepository;
    private final BadgeRepository badgeRepository;

    @Async
    @Transactional
    @EventListener(classes = BadgeEvents.CompletedTodoBadgeEvent.class)
    public void createTodoBadges(BadgeEvents.CompletedTodoBadgeEvent badgeEvents){

        long completedTodo = todoRepository.countAllByMemberAndCompleted(badgeEvents.getMemberDetails().getMember(), true);

        createBadges(completedTodo, 3L, BadgeType.CTT, badgeEvents.getMemberDetails().getMember());

        createBadges(completedTodo, 30L, BadgeType.CTTY, badgeEvents.getMemberDetails().getMember());

        createBadges(completedTodo, 50L, BadgeType.CTF, badgeEvents.getMemberDetails().getMember());
    }

    @Transactional
    public void createBadges(Long count, Long limit, BadgeType badgeType, Member member){

        if(Objects.equals(count, limit) &amp;&amp; !badgesRepository.existsByBadgeTypeAndMember(badgeType, member)){

            Badges badges = Badges.builder()
                    .member(member)
                    .badgeType(badgeType)
                    .build();

            badgesRepository.save(badges);
    }
}</code></pre>
<p>완료된 TodoRepository 에서 완료된 Todo 의 갯수를 찾고 createBadges() 메소드에 이를 넘겨 Badge 를 저장하는 방식이다. 조금 비효율적으로 보일 수도 있는데, 이렇게 한 이유는 이미 발급된 뱃지가 있다면 생성되지 않게 하기 위해서이다.</p>
<p>현재 BadgeType.CTT 는 투두 완료를 3개 했을 때 발급이 되는데, 이 투두 완료는 언제든 취소할 수 있기 때문에 뱃지를 한 번 발급 받은 후 완료한 투두를 모두 취소하고 다시 3개를 완료하게 된다면 뱃지가 또 발급될 가능성이 있기 때문이다.</p>
<p>그리고 실제 코드에는 이 예시 코드보다 더 많은 뱃지들이 존재하므로 메소드 재사용성을 위해 이렇게 만들었다. 어차피 IF 문을 계속 돌릴 바에 이게 더 깔끔하다고 생각하기 때문이다.</p>
<p>또 위의 코드에서는 <strong>@Async 를 사용해 비동기화 처리</strong>를 해주었는데, 이에 대한 자세한 내용은 아래의 포스팅을 참고하도록 하자.</p>
<p>자세히 다루진 않겠지만 이렇게 비동기 처리 해준 이유는 <strong>EventListener 가 작동하는 동안 기존의 메소드가 완료되지 않고 계속 스레드를 갖고 있는 상태</strong>가 되기 때문이다. 다시 말해 <strong>EventListener 가 동작하는 시간 만큼 응답이 늦어지기 때문</strong>인 것이다. 그렇기 때문에 @Async 를 사용해 스레드를 분리하도록 했다.</p>
<blockquote>
<p><strong>[Spring] @Async를 이용한 비동기 처리에 대해</strong> : <a href="https://bepoz-study-diary.tistory.com/399">https://bepoz-study-diary.tistory.com/399</a>
<strong>ApplicationEventPublisher 기반으로 강결합 및 트랜잭션 문제 해결</strong> : <a href="https://cheese10yun.github.io/event-transaction/#null">https://cheese10yun.github.io/event-transaction/#null</a></p>
</blockquote>
<p>그러나 여기에 비밀이 있는데, <strong>정상적으로 DB 에 꽂히기는 하지만 약간의 문제</strong>가 있다. 그래서 난 @EventListener 가 아니라 <strong>@TransacitionalEventListener 를 사용</strong>했다.</p>
<h2 id="2-2-transactionaleventlistener">2-2) TransactionalEventListener</h2>
<p><strong>TransactionalEventListner</strong> 는 EventListener 와 하는 동작은 똑같지만 E<strong>ventPublish 에서 Transaction 의 상태에 따라 동작</strong>하도록 한다.</p>
<p>위의 코드로 보자면 TodoService 의 completeTodo() 메소드의 @Transactional 작동 상태에 따라 EventListner 가 동작하는 것이다. 이에 따른 옵션은 4가지가 있다.</p>
<blockquote>
<ol>
<li>BEFORE_COMMIT : 커밋 직전에 수행 (트랜잭션 진입 전이 아님)</li>
<li>AFTER_COMMIT : 커밋 직후 수행</li>
<li>AFTER_ROLLBACK : 롤백 직후 수행</li>
<li>AFTER_COMPLETION : 트랜잭션이 완료된 뒤 수행</li>
</ol>
</blockquote>
<p>기본 값은 AFTER_COMMIT 인데, 이외에 다른 옵션을 적용하고자 하면 아래와 같이 써주면 된다.</p>
<pre><code class="language-java">@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)</code></pre>
<p>이를 쓰는 이유는 다음과 같다. <strong>@Transactional 이 있는 메소드에서 예외가 발생</strong>되거나, <strong>Rollback 이 됐을 때는 EventListener 가 작동하면 안되기 때문</strong>이다. 어쨌든 @Transactional 이 붙어있는 메소드에 EventListener 를 사용한다면 가급적 @TransactionalEventListener 를 쓰는 걸 추천한다.</p>
<pre><code class="language-java">@Component
@RequiredArgsConstructor
public class BadgeEventListener {

    private final TodoRepository todoRepository;
    private final BadgeRepository badgeRepository;

    @Async
    @Transactional
    @TransactionalEventListener(classes = BadgeEvents.CompletedTodoBadgeEvent.class)
    public void createTodoBadges(BadgeEvents.CompletedTodoBadgeEvent badgeEvents){

        long completedTodo = todoRepository.countAllByMemberAndCompleted(badgeEvents.getMemberDetails().getMember(), true);

        createBadges(completedTodo, 3L, BadgeType.CTT, badgeEvents.getMemberDetails().getMember());

        createBadges(completedTodo, 30L, BadgeType.CTTY, badgeEvents.getMemberDetails().getMember());

        createBadges(completedTodo, 50L, BadgeType.CTF, badgeEvents.getMemberDetails().getMember());
    }

    @Transactional
    public void createBadges(Long count, Long limit, BadgeType badgeType, Member member){

        if(Objects.equals(count, limit) &amp;&amp; !badgesRepository.existsByBadgeTypeAndMember(badgeType, member)){

            Badges badges = Badges.builder()
                    .member(member)
                    .badgeType(badgeType)
                    .build();

            badgesRepository.save(badges);
    }
}</code></pre>
<p>그러나 재밌는 것은 분명히 <strong>@EventListener 에서 @TransactionalEventListener 로 바꿨을 뿐인데</strong> @EventListener 를 썼을 때는 <strong>DB 에 정상적으로 꽂히던 것이 제대로 꽂히질 않는다!</strong> <del>여기서 살짝 멘붕...</del></p>
<h2 id="2-3-transaction-전파-속성">2-3) Transaction 전파 속성</h2>
<p>위에서 EventListener 가 작동하지 않는 이유는 Transaction 의 전파 속성 때문이다. 사실 이 포스팅까지 하면 굉장히 내용이 길어질 것 같기 때문에 자세한 설명은 아래의 포스팅을 참고하도록 하자.</p>
<blockquote>
<p><strong>[Spring] 스프링의 트랜잭션 전파 속성(Transaction propagation) 완벽하게 이해하기</strong> : <a href="https://mangkyu.tistory.com/269">https://mangkyu.tistory.com/269</a></p>
</blockquote>
<p>어쨌든 @TransactionalEventListener 와 @Async 까지 써서 코드를 정말 잘 짰다고 생각했는데, 제대로 동작하지 않아 정말 슬펐다... <strong>그래도 다행인건</strong> print 를 찍어봤을 때, AOP 와는 다르게 <strong>print 는 정상적으로 작동하고 DB 에 save 만 되지 않는다</strong>는 사실을 확인할 수 있었다.</p>
<p>그래서 @Transactional 에 대해 다시 공부해본 결과, <strong>트랜잭션 전파 속성</strong>이라는 것이 있다는 걸 알게 됐다. 이를 공부하다 보니 <strong>TransactionalEventListener 는 EventPublisher 의 Transaction 상태에 따라 작동</strong>하는 것인데, <strong>이 때문이 아닐까? 라는 생각에 도달</strong>할 수 있었다. 결과만 말하면 <strong>Transaction에 REQUIRES_NEW 옵션을 사용해 성공</strong>할 수 있었다. <del>뒷걸음치다가 쥐 잡은 격이었는데, 원래 이렇게 써야된다는 것을 이번에 다시 공부하면서 처음 알았다.</del></p>
<p>기존의 EventListener 에 적용한 @Transactional 은 기본 옵션인 REQUIRED 옵션에 해당한다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/4a2a9fa0-3361-43aa-9a6b-88548326976d/image.png" alt=""></p>
<p><strong>REQUIRED 옵션은 논리 트랜잭션은 다수이나 실제적인 물리 트랜잭션은 1개로 묶이게 된다.</strong> 만약 여기서 로직2 에 예외가 발생하면 로직1 도 로직2 와 같은 물리 트랜잭션에 속해있으므로 함께 롤백된다. 커밋도 마찬가지로 로직2 까지 커밋이 완료되어야 로직1 이 커밋되는 형식이다.</p>
<p>따라서 <strong>REQUIRES_NEW 옵션을 사용해 논리 트랜잭션에 맞게 물리 트랜잭션도 함께 분리</strong>했다. 여기에도 비밀이 있는데 REQUIRED 와 REQUIRES_NEW 를 함께 쓰면 또 무슨 비밀이 있다고 한다... <del>이번에 공부하면서 알았다.</del> 이는 아래의 포스팅을 참고하도록 하자.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/c2735b13-e60d-44db-a30c-c8aa1e52d9e1/image.png" alt=""></p>
<p>어쨌든 이를 적용해 아래의 코드가 최종 코드로 정상 작동하는 것을 확인했다.</p>
<pre><code class="language-java">@Component
@RequiredArgsConstructor
public class BadgeEventListener {

    private final TodoRepository todoRepository;
    private final BadgeRepository badgeRepository;

    @Async
    @Transactional(Transactional.TxType.REQUIRES_NEW)
    @TransactionalEventListener(classes = BadgeEvents.CompletedTodoBadgeEvent.class)
    public void createTodoBadges(BadgeEvents.CompletedTodoBadgeEvent badgeEvents){

        long completedTodo = todoRepository.countAllByMemberAndCompleted(badgeEvents.getMemberDetails().getMember(), true);

        createBadges(completedTodo, 3L, BadgeType.CTT, badgeEvents.getMemberDetails().getMember());

        createBadges(completedTodo, 30L, BadgeType.CTTY, badgeEvents.getMemberDetails().getMember());

        createBadges(completedTodo, 50L, BadgeType.CTF, badgeEvents.getMemberDetails().getMember());
    }

    @Transactional
    public void createBadges(Long count, Long limit, BadgeType badgeType, Member member){

        if(Objects.equals(count, limit) &amp;&amp; !badgesRepository.existsByBadgeTypeAndMember(badgeType, member)){

            Badges badges = Badges.builder()
                    .member(member)
                    .badgeType(badgeType)
                    .build();

            badgesRepository.save(badges);
    }
}</code></pre>
<blockquote>
<p><strong>[Spring] REQUIRES_NEW 옵션만으론 자식이 롤백될 때 부모도</strong> : <a href="https://kth990303.tistory.com/387">https://kth990303.tistory.com/387</a>
<strong>Transactional REQUIRES_NEW에 대한 오해</strong> : <a href="https://woodcock.tistory.com/40">https://woodcock.tistory.com/40</a>
<strong>[Spring] Transaction PROPAGATION.REQUIRES_NEW 의 &#39;독립&#39;이란 의미?</strong> : <a href="https://truehong.tistory.com/140">https://truehong.tistory.com/140</a></p>
</blockquote>
<p>그렇다면 <strong>도대체 왜 TransactionalEventListener 에서는 Transaction 전파 속성을 바꿔줘야</strong>할까? 그 이유는 <strong>TransactionalEventListener 는 부모 트랜잭션</strong> (위에서는 TodoService 의 completedTodo() 메소드) <strong>커밋 후에 실행</strong>되기 때문에 <strong>이벤트 리스너의 트랜잭션을 분리시켜주어야</strong> 하기 때문이다!</p>
<p>원래는 내가 쓴 코드를 공개하고 싶지 않아서 이렇게까지 길게 쓸 의도를 갖고 있지 않았는데... 하다 보니 길어지기 되었다. 그러나 이 기능을 맡게 된 덕분에 내가 AOP 에 대해 오해하고 있었다는 사실과 EventListener 기능, 그리고 Transaction 전파 속성까지 공부할 수 있는 아주 뜻 깊은 시간이었고, 지금 취준으로 인해 공부가 미진했는데 이에 대해 다시금 되새기며 내 것으로 만들 수 있는 좋은 시간이었다.</p>
<p>내가 고생한 내용은 깃허브를 공개해놓을 테니 여기서 보도록 하자!</p>
<blockquote>
<p>Do!Block BE Github : <a href="https://github.com/Hanghae99-DoBlock/BE/blob/main/src/main/java/com/sparta/doblock/events/listener/BadgeEventListener.java">https://github.com/Hanghae99-DoBlock/BE/blob/main/src/main/java/com/sparta/doblock/events/listener/BadgeEventListener.java</a></p>
</blockquote>
<blockquote>
<p>참고 : 
[Spring] 스프링 AOP (Spring AOP) 총정리 : 개념, 프록시 기반 AOP, @AOP  : <a href="https://engkimbs.tistory.com/746">https://engkimbs.tistory.com/746</a>
[SpringBoot] AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)의 개념 및 사용 방법 예제 코드 : <a href="https://mangkyu.tistory.com/121">https://mangkyu.tistory.com/121</a>
Spring Event + Async + AOP 적용해보기 : <a href="https://supawer0728.github.io/2018/03/24/spring-event/">https://supawer0728.github.io/2018/03/24/spring-event/</a>
Spring 의 @EventListener : <a href="https://sunghs.tistory.com/139">https://sunghs.tistory.com/139</a>
ApplicationEventPublisher 기반으로 강결합 및 트랜잭션 문제 해결 : <a href="https://cheese10yun.github.io/event-transaction/#null">https://cheese10yun.github.io/event-transaction/#null</a>
Spring - Event Driven : <a href="https://velog.io/@backtony/Spring-Event-Driven">https://velog.io/@backtony/Spring-Event-Driven</a>
[Spring] @Async Annotation(비동기 메소드 사용하기) : <a href="https://velog.io/@gillog/Spring-Async-Annotation%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%A9%94%EC%86%8C%EB%93%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0">https://velog.io/@gillog/Spring-Async-Annotation%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%A9%94%EC%86%8C%EB%93%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</a>
[Spring] @Async를 이용한 비동기 처리에 대해 : <a href="https://bepoz-study-diary.tistory.com/399">https://bepoz-study-diary.tistory.com/399</a>
Spring @EventListener : <a href="https://brunch.co.kr/@springboot/422">https://brunch.co.kr/@springboot/422</a>
[Spring] 스프링의 트랜잭션 전파 속성(Transaction propagation) 완벽하게 이해하기 : <a href="https://mangkyu.tistory.com/269">https://mangkyu.tistory.com/269</a>
Transactional REQUIRES_NEW에 대한 오해 : <a href="https://woodcock.tistory.com/40">https://woodcock.tistory.com/40</a>
[Spring] Transaction PROPAGATION.REQUIRES_NEW 의 &#39;독립&#39;이란 의미? : <a href="https://truehong.tistory.com/140">https://truehong.tistory.com/140</a>
[Spring] @TransactionalEventListener : <a href="https://parkadd.tistory.com/108">https://parkadd.tistory.com/108</a>
ApplicationEventPublisher를 통한 문제 해결 : <a href="https://blog.naver.com/PostView.nhn?blogId=anstnsp&amp;logNo=222361322823">https://blog.naver.com/PostView.nhn?blogId=anstnsp&amp;logNo=222361322823</a>
Spring Boot 이벤트핸들링 과 @TransactionalEventListener, @Transactional : <a href="https://sukyology.tistory.com/18">https://sukyology.tistory.com/18</a>
[Spring] REQUIRES_NEW 옵션만으론 자식이 롤백될 때 부모도 : <a href="https://kth990303.tistory.com/387">https://kth990303.tistory.com/387</a>
[프로젝트] TransactinoalEventListener (+ 트랜잭션 전파 속성) : <a href="https://pomo0703.tistory.com/196">https://pomo0703.tistory.com/196</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해99 8주차 WIL - 프로젝트 준비하기]]></title>
            <link>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-8%EC%A3%BC%EC%B0%A8-WIL</link>
            <guid>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-8%EC%A3%BC%EC%B0%A8-WIL</guid>
            <pubDate>Mon, 14 Nov 2022 03:30:35 GMT</pubDate>
            <description><![CDATA[<p>이쯤 읽으셨다면 이제 항해를 하면서 제때 WIL 을 쓰지 못했다는 사실을 다들 아실 것 같다. <del>절대 게을러서... 가 맞을지도 몰라요. 킹치만 INFP 는 게으른 완벽주의자 랍니다...</del> 뭐가 어떻게 됐든 제때 쓰지 못했다는 사실은 변하지 않는다. 그럼에도 불구하고 여유가 어느 정도 있는 상태에서 내가 걸어왔던 시간을 제대로 곱씹고 기록으로 남기고 싶은 마음이 컸기 때문이라고 이해해 주실 거라 믿는다.</p>
<p>실전 프로젝트에서는 그 동안의 프로젝트와는 다르게 팀장과 부팀장이 항해 측에서 선별하는 것이 아니라 지원을 받는 형식으로 이뤄졌다. 클론 프로젝트를 진행하면서 만났던 부팀장이 굉장히 열정적일 뿐 아니라 실력적으로도 부족함이 없다고 생각해 다른 조에 뺏기고 싶지 않았다. 그리고 무엇보다 내가 팀장을 하고 싶었다.</p>
<p>팀장이 하고 싶었던 이유에는 다양한 이유가 있었지만 팀장을 하면서 팀원들 보다 더 고생하면서 배우고 도움이 되고 싶은 마음, 한두 가지의 기술에 몰두하는 것이 아니라 팀 전체적으로 전반적인 내용을 공부하고 싶은 마음이 있었다. 그리고 개발뿐 아니라 대학부터 회사까지 다양한 프로젝트를 해왔기 때문에 잘 할 수 있을 것이라 생각했기 때문이다.</p>
<p>어쨌든 실전 프로젝트의 팀장으로서 6주라는 짧다면 짧고 길다면 긴 프로젝트의 첫 주차에 내가 가장 많이 신경 쓴 부분에 대해서 말해보고자 한다.</p>
<h1 id="1-서비스-기획">1) 서비스 기획</h1>
<p>가장 공을 많이 들인 부분이라고 해도 부족함이 없는 부분이 이 부분이었다. 어떤 프로젝트를 어떻게 할 것인가? 에 대한 부분이었다. 기획이 잘못되어 다들 개발했던 것을 모두 엎어야 되는 상황이 오는 것은 피하고 싶었다. 이런 마음가짐으로 다양한 의견들을 모으다 보니 장장 이틀에 걸친 기획 회의를 할 수밖에 없었다. 빡센 회의에 모두가 학을 뗐지만 결국엔 디자이너, 프론트엔드, 백엔드 모두가 만족할 수 있는 기획을 할 수 있었다고 자부한다.</p>
<h2 id="1-1-방향-설정">1-1) 방향 설정</h2>
<p>정말 부끄러운 얘기지만 나는 팀장을 맡을 만한 리더십이나 카리스마를 가진 사람은 아니다. 대신 구성원들의 얘기를 잘 듣고 우리가 나아가고자 하는 방향성과 맞는가에 대한 고민을 하는 사람에 가까운 것 같다. </p>
<p>그래서 나의 입맛대로 어떤 주제의 프로젝트를 하자! 라고 확실하게 밀어 붙이지 않았고 다른 구성원들의 아이디어를 적극적으로 듣고 반영하고자 했다. 그러기 위해서 주제보다는 하나의 비전을, 그리고 우리가 앞으로 나아가야할 방향을 설정하는 것이 중요하다고 생각했다. 이 부분에 대해서는 다행히 모든 팀원들이 동의를 해주어 첫 단추가 잘 끼워질 수 있었다.</p>
<p>결국 기획 회의를 시작하면서 구성원 모두가 가장 중요하게 생각한 부분은 아래와 같다.</p>
<blockquote>
<ol>
<li>MVP 관점에서 개발할 수 있는가?</li>
<li>기본 CRUD 기능부터 Challenge 기능까지 다양하게 경험할 수 있는가?</li>
<li>우리가 하고 싶은가?</li>
</ol>
</blockquote>
<p>MVP 관점에 대한 건 지난 7주차 WIL 에서 포스팅 했기 때문에 여기서 다루진 않겠다. 우리가 하고 싶은가에 대한 것도 너무 당연하기 때문에 넘어가도록 하겠다. 여기서 기본 CRUD 부터 차근차근히 나아가고자 하는 이유는 따로 있었다.</p>
<p>부트캠프, 특히 항해99의 특성 상 짧은 시간 내에 기능을 구현해야 하는 경우가 굉장히 많다보니 기본 CRUD 를 제대로 이해하지 못하고 넘어간 팀원들도 있었다. 반면, 기본 CRUD 를 넘어 Challenge 기능에 도전하고 싶은 사람들도 있었다.</p>
<p>결국엔 MVP 관점과 연결되는 얘기이긴 하지만 화상 채팅 서비스와 같이 Challenge 기능이 주 기능인 서비스가 아니라 기본에 충실하되 Challenge 기능도 풍성한 서비스를 하고 싶다는 것이 나를 포함한 모든 팀원들의 의견이었다.</p>
<h2 id="1-2-주제-선정">1-2) 주제 선정</h2>
<p>다행히도 브레인스토밍을 할 때 팀원들의 아이디어가 정말 많았다. 우리 동네 카페 리뷰 서비스, 스토리 플레이를 오마쥬 한 서비스, 각종 영양제 성분 및 리뷰 서비스, 여행지 코스 추천 서비스 등 여러가지가 나왔다.</p>
<p>그러나 모두 위의 조건을 만족할 순 없었다. 카페 리뷰는 주제가 너무 뻔할 것 같고, 영양제 성분 리뷰는 우리가 전문적인 지식이 없다는 점, 여행지 코스 추천 서비스는 메인 기능이 Challenge 기능이 될 것 같다는 점이 걸렸다.</p>
<p>결국 정해진 주제는 프론트엔드에서 질리도록 해왔던 TO DO LIST 였다. 그러나 이 TO DO LIST 에 차별성을 주고자 내가 완료한 TO DO LIST 를 피드 형식으로 공유할 수 있는 SNS 를 만들고자 했다. 이 경우에 MVP 관점에서 개발을 할 수 있을 뿐 아니라 기본 CRUD 부터 Challenge 기능까지 다양하게 경험할 수 있었다. </p>
<p>특히나 좋았던 것은 운동, 공부, 업무 등 내가 오늘 달성한 과제를 SNS 에 올리는 사람들이 있기 때문에 이에 대한 확실한 니즈가 있다는 것이었다.</p>
<p>그러나 이런 SNS 가 사용자들에게 뻔하게 느껴지지 않게 우리 만의 아이덴티티를 입혀 확실히 각인 시키는 것이 굉장히 중요했다. 결국엔 브랜딩이 문제였는데, 그래서 이를 위한 네이밍이 굉장히 중요했다.</p>
<p>네이밍을 위한 후보들은 아래와 같았다.</p>
<blockquote>
<ol>
<li>Grow Share (함께 나누며 성장하자는 뜻)</li>
<li>다잇수할 (할 수 있다를 거꾸로 함)</li>
<li>스윋 (Study With - 를 한글로 씀)</li>
<li>내가해냄 (최고심 이모티콘 느낌)</li>
<li>페이스메이커(같이 달리며 페이스를 조절해주는 동료)</li>
<li>두블럭 (Do! Block 이라는 뜻, 피드 대신 블럭을 쌓는다고 표현)</li>
</ol>
</blockquote>
<p>자칫 경쟁을 유도하는 SNS 서비스처럼 느껴질 수 있기 때문에 최대한 함께 성장하는 쪽에 포커싱을 했다. 물론 경쟁이 성장을 일으키는 요소는 맞지만 SNS 의 역기능보다는 순기능에 초점을 맞추고자 했기 때문이다. 결국 위의 후보들 중에서 치열한 투표를 통해 선정된 네이밍은 Do! Block 이었다. <del>내가 아이디어를 냈다 (｡･∀･)ﾉﾞ</del></p>
<p>도전을 유도하는 이름이며, 피드 대신 블럭을 쌓는다고 표현하여 마케팅하기도 좋고 컨셉도 확실해 사용자의 뇌리에 쉽게 박히리라 생각했기 때문이다. 특히나 디자이너도 함께하는 프로젝트였기 때문에 이런 확실한 컨셉이 있을 때에 디자인하기에도 훨씬 수월할 것이기 때문이었다.</p>
<p>능력있는 부조장 덕분에 귀염뽀짝한 로고도 만들 수 있었다. <del>저 하찮은 웃음... 하앜</del></p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/3bf75bdd-110b-444b-97b9-5f3bebd8a241/image.png" alt=""></p>
<h2 id="1-3-기능-설정">1-3) 기능 설정</h2>
<p>선정된 주제에 맞게 기능을 설정했다. 피드형 TO DO LIST SNS 이기 때문에 이에 맞는 기능들이 필요했다. 주제 선정은 다 함께 정하는 느낌이 강했다면 기능 쪽에는 자신이 개발해보고 싶은 기능에 대한 어필이 강하게 들어왔다.</p>
<p>정말 기본 기능인 투두 리스트 기능, 그룹 투두, 그룹 투두 오프라인 모임을 위한 지도 기능, 드래그 앤 드롭 기능, 파일 공유 및 업로드 등등 여러가지 아이디어가 들어왔지만 결국 길고 긴 토론을 통해 정말 핵심적인 기능에만 집중하기로 했다.</p>
<p>이 또한 지난 포스팅에서와 마찬가지로 기본 기능, 추가 기능, Challenge 기능으로 묶어 보려 한다. 물론 1주 짜리 클론 프로젝트와 6주 짜리 실전 프로젝트는 갭이 있기 때문에 이 부분을 염두하고 봐주셨으면 좋겠다.</p>
<blockquote>
<ul>
<li>기본 기능</li>
</ul>
</blockquote>
<ol>
<li>회원 관리 (Spring Security 로그인, 이메일 중복체크, 닉네임 중복체크, 회원가입)</li>
<li>프로필 (프로필 페이지, 정보 변경, 비밀번호 변경, 팔로우 / 팔로우 취소, 팔로잉 / 팔로워 리스트)</li>
<li>투두 리스트 (투두 작성 / 완료 / 수정 / 삭제, 투두 일별 조회)</li>
<li>피드 (피드 작성 준비 / 작성 완료 / 수정 / 삭제 / 단건 조회 / 팔로잉 피드 조회)</li>
<li>리액션 (LIKE, HEART, SMILE, PARTY, FIRE 작성 / 수정 / 삭제 / 조회 / 리스트 조회)</li>
<li>댓글 (피드 댓글 작성 / 수정 / 삭제 / 조회)</br></li>
</ol>
<ul>
<li>추가 기능</li>
</ul>
<ol>
<li>회원 관리 (소셜 로그인 - 카카오 / 네이버 / 구글, AccessToken 재발급)</li>
<li>프로필 (내 피드 보기, 뱃지 생성 / 조회 / 설정, 관심사 태그 설정)</li>
<li>투두 리스트 (드래그 앤 드롭으로 일별 투두 순서 변경)</li>
<li>피드 (추천 피드, 팔로잉 피드, 피드 무한 스크롤, 피드 태그 설정)</li>
<li>검색 (태그 검색 - 피드 Response, 유저 검색 - 유저 프로필 Response, 검색 결과 무한스크롤)</br></li>
</ol>
<ul>
<li>Challenge 기능</li>
</ul>
<ol>
<li>이스터 에그 (카카오 페이 기능, Mock-up 결제 완료 시 뱃지 획득 및 피드 특수 스킨 오픈)</li>
<li>뱃지 획득 시 알림 팝업 기능 및 이벤트 피드 생성 (SSE 활용, 사용자가 원할 시 뱃지에 맞는 이벤트 피드 자동 생성)</li>
<li>유저 간 채팅 기능 (채팅방 생성 / 목록 조회 / 메세지 조회 / 메세지 송신)</li>
</ol>
<p>기본 기능 만으로도 정말 풍성하고 꽉 찬 기능이라고 생각된다. 리액션의 경우에는 좋아요만 넣지 않고 여러 가지 항목을 넣고 싶다고 하여 LIKE, HEART, SMILE, PARTY, FIRE 와 같이 피드 작성자와 조회자 간의 긍정적인 피드백을 유도할 수 있도록 했다.</p>
<p>피드 부분에 피드 작성 준비 API 가 조금 특이한 부분이라고 여겨질 수 있을 것이다. 그러나 주제 자체가 내가 완료한 투두를 피드에 공유하겠다는 취지이기 때문에 내가 오늘 완료한 투두가 피드에서 선택될 수 있도록 만들어야 했다. 그랬기 때문에 이 부분이 들어가야만 했고, 피드 작성 완료와 다른 API 가 추가로 들어갔다.</p>
<p>추가 기능으로 보자면 역시나 소셜 로그인, 내 피드 보기, 드래그 앤 드롭 기능, 추천 피드 등으로 유저의 편의성 개선을 목적으로 했다. 여기에서는 유저의 활동을 촉진시키고자 투두 완료, 피드 작성, 댓글 작성 등 유저의 활동에 따라 뱃지를 부여하는 기능을 만들었다. Steam 의 메달이나 달성 과제 같은 느낌이다. 특히 이 뱃지를 자신의 대표 뱃지로 설정하여 피드를 작성하면 이것을 자랑할 수 있도록 하였고, 프로필 창에서 유저가 획득한 뱃지를 확인할 수 있도록 하였다.</p>
<p>또한 유저 별 자신의 관심사 태그를 설정하면 그에 맞게 추천 피드를 보내주는 기능을 추가했다. 팔로잉 피드만 있다면 누군가의 추천이나 친구 추가에 의해서만 피드를 볼 수 있다는 얘기가 되는데, 그럴 경우 내가 볼 수 있는 피드의 수가 너무 한정적일 거라는 계산이었다. 그래서 유저가 관심사 태그를 설정하면 피드에 설정된 태그에 맞게 추천해주는 기능을 넣은 것이다.</p>
<p>검색 기능도 넣어서 이런 부분을 충족시켜주고자 했다. 다양한 관심사를 가진 많은 사람들이 소통할 수 있는 SNS 를 만들고 싶었기 때문이다. 다만 내용으로 검색하는 것은 효율성이 떨어진다고 판단하여 태그 검색과 유저 검색으로만 범위를 한정시켰다. <del>인스타그램도 내용 검색은 없다. 물론 인덱싱을 걸진 못했지만 인덱싱하는 데에도 문제가 있을 수 있다고 생각했기 때문이다.</del></p>
<p>Challenge 기능에는 정말 있으면 좋고 없어도 기능에는 전혀 지장이 없으며, 구현하는 데에 난이도가 높은 기능을 선정했다. 사용자가 이스터에그를 찾으면 카카오 페이 Mock-up 결제를 하는 창이 띄워지며 결제를 완료하면 뱃지가 획득되고 감사의 뜻을 담은 피드 스킨을 지급하는 방식의 기능이었다. 물론 결제 전에 사용자에게 충분히 취지를 설명하는 팝업을 띄울 예정이었다.</p>
<p>유저가 뱃지를 획득하게 되면 SSE 로 바로 획득 알림 및 이벤트 피드를 남길 것인지 묻는 팝업을 띄워주는 기능도 이에 넣었다. 이벤트 피드를 남기겠다고 하면 뱃지 종류에 따라 이벤트 피드가 자동으로 생성되며 이 이벤트 피드에 팔로워들이 리액션 및 댓글을 남기며 유저의 성장과 피드백, 활동을 촉진시키고자 한 장치였다. 여기서 이벤트 피드 만의 독특한 피드 스킨이나 효과로 이를 더욱 촉진하고자 했다.</p>
<p>유저 간 채팅 기능도 마찬가지로 유저 간 소통을 높이기 위한 기능으로, 달성한 목표의 노하우를 나누거나 친목을 강화하고자 한 기능이었다.</p>
<p>이렇게나 기획을 열심히 했음에도 불구하고 시간 부족과 잦은 디자인 변경에 의해 프론트엔드에서 Challenge 기능에는 손도 대지 못했다. 이 부분은 디자이너 개인의 문제인지 디자이너 피드백을 해주는 멘토의 문제인지 항해99 측의 운영 미숙인지 아직도 잘 모르겠다. 어쨌든 이 부분에 대해서는 추후에 포스팅을 남기도록 하겠다.</p>
<blockquote>
<p>API 명세서 : <a href="https://legendary-scaffold-c21.notion.site/a7f31d0b36c344ed9cb4b9d89ce5a18c?v=56734b61f9264de9ab271b251c76eaa0">https://legendary-scaffold-c21.notion.site/a7f31d0b36c344ed9cb4b9d89ce5a18c?v=56734b61f9264de9ab271b251c76eaa0</a></p>
</blockquote>
<h1 id="2-git-전략">2) Git 전략</h1>
<p>누워서 침 뱉는 얘기긴 하지만 마지막 실전 프로젝트에 들어왔지만 여전히 Git 을 제대로 쓰지 못하는 사람들이 수두룩 했다. 어디선가 Push 가 되지 않아요! 어디서 꼬인지 모르겠어요! Conflict 가 났는데 이거 어떻게 해야돼요! 라는 말이 들려오기 일쑤였다.</p>
<p>그래서 팀원들에게 많이 강조한 부분이 Git 전략과 사용법이었다. 절대 Main 이나 Master Branch 에는 PR 을 않는다. Release Branch 에서 Feature 별 Branch 를 따로 두어 개발 후 코드 리뷰 후 Release Branch 에 PR 및 Merge 를 진행한다. Conflict 를 두려워하지 않는다. Pull, Push, Commit 전에 항상 Patch 를 진행한다. 등등 많은 부분을 얘기했다.</p>
<p>아래의 블로그 들을 공유하며 Git 전략과 코드 리뷰 문화를 정착시키려 했다. 알아볼 수 없는 Commit 메세지와 보기만해도 머리가 아픈 Git Flow 들이 더 이상 우리를 괴롭히지 않도록 하기 위해서였다.</p>
<p>그 외에 Issues, Project 의 칸반보드, Wiki 등도 적극 사용할 수 있도록 권장했다.</p>
<p>특히나 코드 리뷰를 적극 권장하였는데, 내가 배운 것을 정리하며 한 번 더 공부할 수 있을 뿐 아니라 상대방이 배운 내용을 함께 습득할 수 있는 기회이기 때문이었다. 각자 맡은 부분만 공부하다보면 다른 부분에 대한 갈증이 생기기 마련이다. 코드 리뷰가 이를 해결해줄 수 있을 것이라 믿었다.</p>
<blockquote>
<p>우린 Git-flow를 사용하고 있어요 : <a href="https://techblog.woowahan.com/2553/">https://techblog.woowahan.com/2553/</a>
브랜치 전략 수립을 위한 전문가의 조언들 : <a href="https://blog.hwahae.co.kr/all/tech/tech-tech/9507">https://blog.hwahae.co.kr/all/tech/tech-tech/9507</a>
코드리뷰 문화 정착하기 : <a href="https://blog.cowkite.com/blog/2003062358/">https://blog.cowkite.com/blog/2003062358/</a>
GitHub으로 협업하기: 클론부터 코드 리뷰까지 : <a href="https://xo.dev/github-collaboration-guide/">https://xo.dev/github-collaboration-guide/</a></p>
</blockquote>
<h1 id="3-erd-설계">3) ERD 설계</h1>
<p>프로젝트를 위한 모든 판은 깔렸다. DDD 에 대해선 제대로 이해하지 못하고 있지만 각 Entity 설계가 완성되어야 기능 개발이 원활할 것이라고 생각되어 내가 주도적으로 설계를 마쳤다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/61d54960-0240-445f-a79f-4166cbeca739/image.png" alt=""></p>
<p>물론 이 ERD 는 최종판으로, 중간에 많이 바뀌었지만 여기에서 가장 신경을 많이 쓴 부분은 가급적 양방향 관계를 지양한다는 것과 Tag 부분 설계였다. </p>
<p>필요에 의해서 양방향 관계를 쓸 수도 있었겠지만 워낙에 양방향 관계를 극혐하시는 분에게 심하게 대인 경험과 딱히 필요한 곳이 없다는 이유에서 이렇게 만들었다.</p>
<p>지금 보니 화살표 모양에 다른게 들어가는게 맞았을 것 같긴 하지만 어쨌든 관계도와 대략적인 Entity Field Type 까지 잘 들어있는 ERD 인 것 같다. <del>이때는 SQL 지식이 많이 부족해서 관련 데이터 타입을 넣지 못했다.</del></p>
<p>Tag 에 관한 얘기를 해보자면, 유저가 설정한 관심사 태그로 해당 피드를 Response 해줘야하는 상황이 생겼다. 그렇다면 여기서 과연 태그와 피드, 태그와 유저의 관계는 어떻게 될 것인가?</p>
<p>정말 많은 고민을 했지만 결국엔 동일한 태그를 가진 피드, 유저가 발생할 수밖에 없다고 생각했다. 그렇다면 N:M 관계가 형성될 것인데, 이를 해결할 수 있는 방법은 1:N:1 로 Mapper Class 를 둬야한다는 것이었다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/5ad2b14d-0d43-4023-8aed-5237c7a82293/image.png" alt=""></p>
<p>따라서 MemberTagMapper, FeedTagMapper 를 두어 유저가 설정한 태그로 FeedTagMapper 를 불러와 해당 피드를 Response 하는 것으로 설계를 마쳤다. 그러나 이 부분이 초기 개발에 있어, 특히 무한 스크롤 기능에 꽤나 애를 먹게 하는 요소로 작용했는데 이는 추후에 포스팅하도록 하겠다.</p>
<p>이렇게 포스팅을 보고나니 사실 나는 개발자보다 PM 으로서의 재능이 더 있는건 아닐까......? 라는 생각이 들긴 하는데 이 부분은 추후 코드와 기능 관련 포스팅에서 <del>완전히 박살나는 건 나였고^^</del> 검증하도록 하겠다.</p>
<p>어쨌든 첫 단추가 잘 꿰어졌으니 진짜 앞으로 달려나갈 차례이다. 다들 화이팅!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해99 7주차 WIL - MVP]]></title>
            <link>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-7%EC%A3%BC%EC%B0%A8-WIL</link>
            <guid>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-7%EC%A3%BC%EC%B0%A8-WIL</guid>
            <pubDate>Tue, 08 Nov 2022 03:54:15 GMT</pubDate>
            <description><![CDATA[<p>역시나 내 블로그는 한발 늦게 작성 됐다... 항해 과정 중에는 정말이지 너무 바빠서 내 입맛에 맞도록 블로그를 작성할 수가 없다. 내가 충분히 이해가 된 상태에서 최대한 심도있게 남이 볼 수 있을만큼 작성해야된다고 생각하기 때문이다.</p>
<p>어쨌든 클론 프로젝트를 하며 느낀 점을 얘기해보고자 한다.</p>
<p>이번 프로젝트는 클론 프로젝트로, 뜻하지 않게 팀장을 맡게 되었다...... 왜 내가 팀장이 된지는 잘 모르겠지만 <del>근데 진짜 이상하다 대학교때도 난 조장을 놓친 적이 한 번도 없다</del> 팀장으로서 그리고 팀원으로서 잘한 점과 못한 점에 대해 얘기해보고자 한다.</p>
<h1 id="잘했던-점">잘했던 점</h1>
<p>먼저 조원으로서 잘했던 점에 대해 얘기해보고자 한다. 사실 이런 프로젝트도 경험이 적은데 개발자로서의 팀장은 처음이라 잘했던 점보다는 아쉬웠던 점이 더 크게 와닿았고 팀원들에게 정말 미안했다.</p>
<h2 id="카카오-로그인과-카카오-페이-기능">카카오 로그인과 카카오 페이 기능</h2>
<p>Oauth2 를 이용한 카카오 로그인 기능은 항해 측에서 제공하는 기본 강의에 들어있었으나 이에 성공한 팀이 단 한 팀도 없었다. 그러나 일주일 내내 이 기능에 메달린 결과, 프론트 부조장과 내가 힘을 합쳐 카카오 로그인 기능에 성공할 수 있었다.</p>
<p>카카오 페이 또한 카카오 로그인 기능에서 일부만 고쳐서 백엔드에서는 이에 성공할 수 있었다. Postman 테스트에서는 아주 정상적으로 작동했다. 그러나 과정 자체가 카카오 로그인 보다는 조금 더 복잡했기 때문에 시간 상의 문제로 프론트엔드 와의 통신에는 실패할 수밖에 없었다.</p>
<p>카카오 로그인은 최대한 서버에서 프론트엔드의 손이 덜 가도록 만들 수 있었으나, 카카오 페이는 그게 쉽지 않았다. <del>물론 실전 프로젝트에서는 그 문제를 일부 해결했으나, 역시나 시간 문제로 프론트엔드와 연결을 해보지는 못했다.</del></p>
<p>카카오 페이 API 의 대략적인 도식은 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/8734ea30-5fec-4809-a4e2-f79dc6caff5d/image.png" alt=""></p>
<p>빨간 색으로 처리된 부분이 프론트엔드에서 해줬어야 하는 부분인데 충분히 서버에서도 해줄 수 있는 부분이라고 생각됐기 때문이다. 나의 최종적인 목표는 아래와 같았다. <del>결국엔 시간 부족 문제로 실패했지만, 실전 프로젝트에서는 백엔드 쪽에선 아래의 최종 목표에는 성공했다. 역시나 프론트엔드에서 처리가 되진 않았지만ㅠㅠ</del></p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/545c0e6c-649e-42b0-a128-20902ca1584a/image.png" alt=""></p>
<p>백엔드에서 프론트엔드에 url 을 전달하면 브라우저에 그 url 로 리다이렉트를 시켜주는 것이 아니라 백엔드에서 리다이렉트를 바로 띄워주어 서버로 바로 정보를 전달받도록 하는 것이다. 그렇게 되면 프론트엔드에서 처리해줘야 할 지점이 굉장히 줄어들 수 있다. 어쨌든 그 부분은 구현하지 못했다. </p>
<p>최종 목표에는 도달하지 못했지만 어찌됐든 백엔드에서 굴러가기는 하는 카카오 페이 기능을 만들고  Postman 테스트에는 성공하긴 했다. 하지만 프론트엔드와의 연결을 하지 못해 정말 아쉬웠다. 그럼에도 불구하고 카카오 로그인에는 성공했기 때문에 그 선에서 팀원들 모두 만족할 수 있었다.</p>
<p>코드를 남기자니 너무 복잡해서 이에 대한 설명은 다른 포스팅으로 남기도록 하고, 깃허브 주소를 남기도록 하겠다.</p>
<blockquote>
<p><strong>코딩 갤럭시 클론 프로젝트</strong> : <a href="https://github.com/HangHae99-Clone-Coding-Galaxy/BE">https://github.com/HangHae99-Clone-Coding-Galaxy/BE</a></p>
</blockquote>
<h2 id="예외처리">예외처리</h2>
<p>이에 대한 자세한 포스팅은 나중에 하겠지만, 어쨌든 다른 팀에서는 소홀했던 예외처리에 대한 부분을 챙길 수 있었던 점이 잘한 점이라 할 수 있겠다. 특히나 예외처리를 할 수 있는 방법이 꽤나 여러가지가 있을 수 있다는 점은 배울 수 있었다.</p>
<p>우리가 배웠던 예외처리는 각 예외 별 Class 를 만들고 그 Class 들을 하나의 패키지로 묶어서 정리하는 방법이었는데, 나는 이 방법이 패키지를 열어서 Class 하나 하나 다 뜯어봐야 한다는 점이 굉장히 맘에 안들었다. 그래서 InnerClass 를 사용해 하나의 Exception Class 를 만들고 그 안에 Custom Class 를 사용해 예외 처리를 사용했다.</p>
<p>어쨌든 이에 대한 포스팅도 최종 실전 프로젝트에 대한 포스팅을 하면서 남겨놓도록 하겠다. 자세한 코드는 위의 깃허브 링크에 나와있다.</p>
<h1 id="아쉬웠던-점">아쉬웠던 점</h1>
<p>사실 이번 프로젝트 WIL 에서 가장 하고 싶었던 포스팅은 바로 <strong>MVP 에 대한 내용</strong>이다. 물론 나도 이에 대한 중요성은 익히 들어 알고는 있었지만 어떻게 실행하는 지에 대한 내용은 <strong>사실 잘 몰랐다.</strong> 그러나 이번 프로젝트를 하며 <strong>정말 뼈저리게 느끼고 후회했다.</strong> 물론 내가 하고 싶어 한 팀장은 아니었으나, 어쨌든 팀장이라는 직책을 맡은 자로서 팀원들에게 제대로 된 길을 제시해주지 못했다는게 정말 후회스럽고 미안했다. 그래서 이에 대한 내용을 적어보고자 한다.</p>
<h2 id="mvp">MVP</h2>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/33b86046-5bba-4161-a824-50f7852db207/image.png" alt=""></p>
<p>구글에 <strong>MVP</strong> 라는 키워드를 검색해보면 Most Valuable Player 가 가장 상단에 뜰 것 같지만 의외로 Minimum Viable Product 가 가장 먼저 나온다. 한글로는 <strong>최소 기능 제품</strong>이라는 뜻인데, 검색 결과를 보면 이에 대해 정말 많고 다양한 방법론들이 나온다.</p>
<p>그 방법론들 모두 뛰어난 사람들이 오랫동안 집약시켜 놓은 방법이겠지만, 사실 와닿지가 않는다. 내가 느끼기에 <strong>개발 프로젝트에 필요한 MVP</strong> 라 함은 처음부터 어렵고 복잡한 기능에 초점을 맞추기 보단 <strong>일단 굴러가게 만들어야 한다</strong> 라고 생각한다.</p>
<p>우리가 만들고자 했던 클론 프로젝트의 주제는 코딩 강좌 사이트인 &#39;코딩 애플&#39; 이었다. 그래서 우리가 만들고자 했던 기능은 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/1b9973e0-0d3a-48bb-a80b-71d5caac787d/image.png" alt=""></p>
<blockquote>
<p><strong>클론코딩 SA - 코딩갤럭시</strong> : <a href="https://time-helicona-5e0.notion.site/6-SA-c02a0e2736ed42128c530350b1c4d153">https://time-helicona-5e0.notion.site/6-SA-c02a0e2736ed42128c530350b1c4d153</a></p>
</blockquote>
<p>프로젝트 당시에 썼던 노션 페이지 그대로 가져왔다. 지금보니 저게 기본 CRUD 인가? 라는 생각이 들 정도로 MVP 에 무지했던 것 같다. 이제 와서 저 <strong>기능들을 MVP 로 순으로 개발</strong>에 들어갈꺼라고 하면 다음과 같을 것이다. 가장 위에 있는 것부터 개발하는 것으로 나열하겠다.</p>
<blockquote>
<ul>
<li><strong>기본 기능</strong></li>
</ul>
</blockquote>
<ol>
<li>SpringSecurity 기본 로그인</li>
<li>마이 페이지 - 닉네임 변경</li>
<li>강의 게시판</li>
<li>리뷰 및 별점</br></li>
</ol>
<ul>
<li><strong>추가 기능</strong></li>
</ul>
<ol>
<li>마이 페이지 - 프로필 사진 변경, 내 리뷰 보기</li>
<li>강의 게시판 - 썸네일 및 영상 업로드</li>
<li>리뷰 및 별점 - Pagination</li>
<li>강의 검색 기능</br></li>
</ol>
<ul>
<li><strong>Challenge 기능</strong></li>
</ul>
<ol>
<li>카카오 로그인</li>
<li>카카오 페이 결제 기능 (Mock-up API 사용)</li>
<li>강의 게시판 - 결제 여부에 따라 강의 Open / Close</li>
</ol>
<p>조금은 MVP 스러운 개발을 할 수 있을 것처럼 보이지 않는가? <strong>기본 기능만으로도</strong> 영상은 없지만 줄글 형태로 강의를 볼 수 있으며, 리뷰 및 별점까지 쓸 수 있는 <strong>강의 게시판 형태의 사이트</strong>가 완성될 것이다.</p>
<p>그 이후 <strong>추가 기능</strong>으로 보면 내 리뷰 보기와 Pagination, 강의 검색 기능을 추가해 <strong>사용자 편의성을 높일 수 있도록 한 것</strong>을 볼 수 있다. 특히 단순 게시판 형태에서 <strong>사진과 영상까지 포함된 게시판으로 업그레이드</strong> 된 것을 확인할 수 있을 것이다.</p>
<p>그리고 나서야 최종적으로 카카오 로그인 기능과 Mock-up API 를 활용한 카카오 페이 결제 기능을 통해 결제 여부에 따라 <strong>유료 강의가 Open 되는 형식으로 기능이 업그레이드</strong> 되는 것을 볼 수 있다.</p>
<p>위의 사진처럼 로그인 기능, 마이 페이지, 강의, 결제 기능을 <strong>모두 완성시킨 뒤 이어 붙이는게 아니라 조금씩 기능을 향상</strong>시켜 나가는 것이다. 바퀴, 차체, 구동부를 모두 완성 시킨 하나의 자동차를 만드는게 아니라 처음엔 킥보드, 자전거, 자동차의 형태로 진화시켜 나가는 것처럼 말이다.</p>
<p>이것이 내가 깨달은 MVP 이며, 실전 프로젝트에서 조장을 맡게된 이유와도 같다. 이번 프로젝트에서는 타의로 조장이 되었으나, 덕분에 내가 깨달은 바가 있고 이에 다음 프로젝트에서는 똑같은 실수를 반복하지 않고 프로젝트를 충분히 잘 이끌 수 있다는 자신감이 생겼기 때문이다.</p>
<h1 id="프론트엔드의-고충">프론트엔드의 고충</h1>
<p>이번 프로젝트를 하면서 깨닫게 된 것이 또 있다면 프론트엔드가 정말 개발하기 어렵다는 것을 깨달았다. 난 물론 백엔드 개발자 이지만, 왜 이렇게 개발 속도가 더딘가를 생각할 겨를이 없을 정도로 그냥 옆에서 보기만 해도 그들의 고충이 느껴진다.</p>
<p>가뜩이나 디자이너도 없는 상태에서 와이어프레임과 CSS 를 잡고 기능 개발까지 해야되는 상황에 깊이 공감할 수 있었다. 솔직히 백엔드 코드야 한 번 잘 짜놓으면 다른 프로젝트에서도 얼마든지 활용이 가능한데, 프론트엔드는 그게 힘들다는 생각이 들었다. 서비스 별로, 하다 못해 같은 서비스의 페이지 하나도 서로 다르기 때문이다.</p>
<p>물론 백엔드도 SQL, AWS, CI/CD, SSL, NginX 등 할 것도 많고 러닝 커브도 높지만 정말 한 번 제대로 이해하고 넘어가면 이만큼 쉬울 수가 없다고 생각하기 때문이다.</p>
<p>어쨌든 프로젝트를 어떻게 끌어야 되며, 어떻게 소통하고 같은 팀원을 이해할 수 있었던, 그리고 실제로 이를 체득하는 시간이었던 뜻 깊은 시간이었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해99 6주차 WIL - 협업을 배우다]]></title>
            <link>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-6%EC%A3%BC%EC%B0%A8-WIL</link>
            <guid>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-6%EC%A3%BC%EC%B0%A8-WIL</guid>
            <pubDate>Sun, 30 Oct 2022 15:28:59 GMT</pubDate>
            <description><![CDATA[<p>실전 프로젝트가 끝나고 나서야 블로그를 작성하느라 기억이 가물가물하긴 하다. 그래도 최대한 그때의 마음을 담아 작성해보도록 하겠다.</p>
<p>비전공자로서 제로베이스에서 시작해 드디어 미니 프로젝트 주차까지 왔다. 일단 버티자라는 마음으로 여기까지 왔는데, 그래도 실력순으로 줄을 세워보자면 하위권에 있는 것 같진 않았다. 프로젝트에 들어서며 가장 먼저 한 생각은 동료들에게 많이 배우고 내가 알고 있는 것을 나누고 다 함께 성장하자는 마인드였다.</p>
<p>그러나 협업은 맘처럼 쉬운 게 아니었고, 특히나 우리 조에서는 크고 작은 불협화음이 많아 특히나 협업이 쉽지 않았다. 혹여나 함께 했던 분들이 이 글을 보게 된다면 마음이 많이 아프실 수 있으므로 특정인에 대한 얘기는 하지 않으려 한다. <del>글로 뒷담화 하는 것 같아서 찌질하기도 하고...</del></p>
<p>그래서 내가 미니 프로젝트를 하면서 느낀 협업을 몇 가지 키워드로 얘기해 보려 한다.</p>
<h1 id="협업">협업</h1>
<p>누구나 협업이 잘 이루어지길 바랄 것이다. 그러나 누구나 협업을 잘하는 것은 아닌 것 같다. 전 직장에서 많이 느꼈지만 나 아닌 누군가와 함께 일한다는 것이 얼마나 힘든 일인지 다시 한 번 깨달았다. 그럼에도 불구하고 내가 생각했을 땐 아래의 키워드 들만 기억하고 노력해도 원활하진 않아도 삐걱대진 않는 프로젝트를 할 수 있을 것 같다. </p>
<h2 id="이해">이해</h2>
<p>상대방은 내가 아니라는 이해가 필요하다. 굉장히 추상적이고 두루뭉술한 얘기지만 만고의 진리라고 생각하는 말 중에 하나이다. 상대방은 내가 아니기 때문에 내가 생각하는 것, 느끼는 것, 알고 있는 것, 싫어하는 것, 좋아하는 것이 모두 다 다를 수 있다. 그렇기 때문에 소통이 중요한 것이다.</p>
<p>그러나 상대방과 내가 다르다는 것을 이해하고, 상대방과 나의 다름을 인정하지 않는 소통은 오히려 소통을 저해한다고 생각한다. 서로를 이해하고 인정하지 않기 때문에 같은 목표를 향해 나아가도 모자랄 판에 앉아서 싸움이나 하게 되는 것이다.</p>
<p>언제든 나와 다른 생각을 받아들일 준비가 되어 있어야 하며, 나와 다른 생각을 하는 것이 잘못된 것이 아니라는 마음가짐이 필요한 것 같다.</p>
<h2 id="소통">소통</h2>
<p>내가 생각하는 것, 느끼는 것, 알고 있는 것, 싫어하는 것, 좋아하는 것을 잘 말해주어야 한다. 잘 이라는 것이 또 굉장히 어려울 수 있는데, 내가 생각한 잘 소통하는 방법이란 부드러운 톤 앤 매너로 말하는 것이라 생각한다. 언제든 상대방이 기분 나쁘지 않게 얘기할 수 있으며, 상대방의 말을 잘 이해할 수 있는 자세가 필요한 것 같다.</p>
<p>원활한 소통을 위해 가장 좋은 방법은 상대방에게 되묻는 방법인 것 같다. 전 직장에서 굉장히 유용하게 써먹었고, 이걸로 정말 많은 것을 배울 수 있었다. 아 제가 이해한 것은 여기에서 OOO 을 하는 거고, 제가 이렇게 했을 때 OOO 되면 그쪽에서는 OOO 해주신다는 말씀이신 거죠? 라고 내가 이해한 바를 되묻기만 해도 나도 말하면서 축약한 핵심 짚으며 상대방의 의도를 되짚을 수 있고, 상대방도 아 이 사람이 제대로 이해하고 있구나 혹은 이 부분에 대해서는 다시 알려주어야겠구나 라고 인식하게 된다.</p>
<p>이것이 이해와 또 연결되는데, 나는 A 라고 얘기했어도 상대방 입장에서는 B 라고 인식할 수도 있기 때문이다. 순전히 나의 설명이나 전달력의 부족일 수도 있는 것이고 이걸 넘어서 상대방의 인식 체계가 나와는 다르기 때문이다.</p>
<p>전 직장 얘기를 자꾸 들먹이고 싶진 않지만, 가장 많이 들었던 이야기 중 하나는 후배들에게 무언가를 알려줄 때 이해가 될 때까지 100번 1000번을 알려주어야 된다는 말이었다. 내가 커리큘럼에 나와있는 데로 알려주는 강사가 아니기 때문에 매번 똑같은 걸 알려주더라도 놓치는 것이 있을 수 있기 때문이라는 뜻이었던 것 같다. 무엇보다 상대방과 나의 인식과 이해의 수준의 차이가 있을 수밖에 없다는 뜻도 있었으리라 생각된다.</p>
<h2 id="공유">공유</h2>
<p>사실 지금도 그렇지만 난 보고라는 것을 굉장히 싫어한다. 보고 보다는 공유라는 말을 더 좋아하기도 하고 보고는 상하체계가 나눠져 있어 검사 받고 평가 받는 기분이 들지만 공유는 <del>잘생겼어</del> 내가 가진 무언가를 다른 사람들에게 나눠 준다는 기분이 들기 때문이다.</p>
<p>어쨌든 주기적인 회의를 통해 현재의 상황을 공유하고, 잘 됐으면 잘된 점을 안 됐으면 안된 점을 공유해 앞으로 나아가고자 해야한다고 생각한다. 우리 모두 완벽하지 않고 배움의 정도도 다르고 코딩 실력도 다 다르기 때문에 이게 가장 중요한 요소 중 하나라고 생각한다.</p>
<p>사실 공유라고 썼지만 개발자 입장에서는 코드 리뷰 혹은 진행 상황 공유 정도라고 받아 들이는 게 좋을 것 같다. 이렇게 하면서 내가 상대방에게 설명할 때 나도 공부를 하게 되고, 상대방도 몰랐던 것에 대해 알게 되며 혹여나 상대방이 더 좋은 방법을 알고 있다면 그것으로 고칠 수도 있게 된다.</p>
<p>전 직장에서는 상하 체계가 굉장히 확실했기 때문에 공유 보다는 보고의 성격이 강했다. 또한 아무리 내가 빠르게 일을 진행해도 협력 업체에서 이를 빠르게 진행해주지 않는다면 굉장히 시간이 오래 걸려 무능력한 사람으로 찍히기 일쑤였다. 그래서 완료된 업무만 보고하는 경향이 강했는데, 퇴사 후에 이 부분에 대해 많이 후회했었다.</p>
<p>어차피 퇴사할 회사인데, 무능력한 사람으로 찍히고 속 시원히 다른 사람들에게 공유하고 함께 머리를 맞댈 수 있었다면 얼마나 좋았을까 라는 생각 때문이었다. 매일을 혼자 끙끙 앓고 협력 업체의 일도 내 일처럼  하다가 주 7-80 시간은 기본으로 일했으니 말이다. <del>지금 생각해보니 정신이 아찔해진다.</del></p>
<p>어쨌든 내가 부족하고 모자란 부분도 가감없이 보여줄 수 있는 배짱과 내가 알고 있는 부분은 시원하게 알려줄 수 있는 자신감이 필요한 것 같다. 저 사람이 모르는 것을 내가 알고 있을 수도 있고, 내가 모르는 것을 저 사람이 알고 있을 수도 있기 때문에 서로가 서로에게 배우며 동료로 성장해 나갈 수 있다고 생각하기 때문이다.</p>
<p>그렇다고 상대방의 호의를 권리로 여겨서는 안될 것이다. 그렇게 되는 순간 동료가 아니라 나보다 실력도 모자란데 보고 받길 원하는 뭔가 이상한 관계가 될 수도 있을 수 있기 때문이다.</p>
<h2 id="마치며">마치며</h2>
<p>쓰다보니 주저리 주저리 말이 많아진 것 같다. 요약하자면 협업의 기본은 이해이며, 이해를 바탕으로 소통하고 공유해야 삐걱대지 않는 소통을 할 수 있다고 생각한다. 모두들 쉽지 않겠지만 나와 모두의 성장을 바라면서!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해99 5주차 WIL - CORS]]></title>
            <link>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-5%EC%A3%BC%EC%B0%A8-WIL</link>
            <guid>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-5%EC%A3%BC%EC%B0%A8-WIL</guid>
            <pubDate>Sun, 23 Oct 2022 14:24:04 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅은 프론트와 백엔드가 함께하는 프로젝트를 해봤다면 한 번쯤 겪어보고 들어봤을 CORS 에 대한 포스팅이다. 이것도 길다면 길겠지만 지난 포스팅들보다는 분량 조절을 좀 해보려고 한다.</p>
<h1 id="1-cors">1) CORS</h1>
<h2 id="1-1-cors-란">1-1) CORS 란?</h2>
<h3 id="cors-란">CORS 란?</h3>
<p><strong>CORS 란</strong> Cross Origin Resource Sharing 의 약자로, 교차 출처 리소스 공유라는 뜻을 가지고 있다. 말이 어려울 수 있는데 &#39;<strong>다른 출처의 리소스를 공유하겠다</strong>&#39; 라는 뜻으로 받아들이는 것이 조금 더 이해가 쉬울 것 같다.</p>
<p>그럼 이 다른 출처라는 건 또 뭐고, CORS 는 왜 있는 것이고 어떻게 생겨먹었으며 어떻게 해결해 줘야 할까? 이제 밑에서 그 얘기를 더 해보도록 하자.</p>
<h3 id="출처origin-란">출처(Origin) 란?</h3>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/1b4d48f6-be28-481a-8ac9-77a68e35b9cb/image.png" alt=""></p>
<p><strong>프로토콜, 호스트, 포트를 통틀어 Origin</strong> 이라고 한다. 이 <strong>Origin 이 같은 것을 출처가 같다</strong>고 말하고, <strong>다르면 Cross Origin</strong> 이 되는 것이다. 우리가 프로젝트를 하며 <strong>React</strong> 의 localhost <strong>3000 포트</strong>와 <strong>SpringBoot</strong> 의 <strong>8080 포트가 달랐기 때문에 CORS 에러가 발생</strong>하게 된 것이다. 아래의 예시에서 같은 출처는 어떤 것인지 살펴보자.</p>
<pre><code>1. https://localhost
2. http://localhost:80
3. http://localhost
4. http://127.0.0.1
5. http://localhost/api/cors</code></pre><p>위의 예시에서 <strong>같은 출처의 url 은 2, 3, 5번</strong>이다. <strong>1번은 프로토콜</strong>이 다르며, <strong>4번</strong>의 IP 주소는 localhost 가 맞기는 하나, <strong>브라우저에서는 String Value 로 같은 출처를 판별</strong>하기 때문에 다른 것으로 판단한다. 여기서 2번과 3번은 포트가 있느냐 없느냐의 차이인데, 보통 <strong>http 는 host 뒤에 80 포트</strong>를 작성해주어도 되고 <strong>생략해서 써도 문제가 없다.</strong> 비슷한 예로 <strong>https 는 443 포트를 생략</strong>해서 쓴다. https 를 사용하는 서비스 호스트 뒤에 443 포트를 달고 접속을 해도 접속이 가능한 것을 확인할 수 있다.</p>
<p>결국엔 <strong>CORS</strong> 란 이렇게 <strong>출처가 다른 Origin 이 서로 리소스를 주고 받을 수 있도록 허용해주는 정책</strong>이라고 할 수 있겠다.</p>
<h2 id="1-2-sop">1-2) SOP</h2>
<p><strong>SOP</strong> 란 Same Origin Policy 의 약자로, 동일 출처 정책 정도로 해석해볼 수 있겠다. 위의 예시처럼 <strong>같은 출처에서만 리소스를 공유할 수 있도록 하는 정책</strong>이다. 2011년 RFC 6454에서 처음 등장하였는데, 그 이유는 <strong>XSS 와 CSRF 와 같은 보안 공격을 막기 위함</strong>이다. 그렇기 때문에 우리는 <strong>CORS 로 다른 출처의 리소스를 주고 받을 수 있도록 허용해줘야</strong> 한다.</p>
<p>XSS 와 CSRF 에 대한 내용은 아래의 포스팅에서 더욱 자세히 살펴보도록 하자.</p>
<blockquote>
<p><strong>XSS :</strong> <a href="https://junhyunny.github.io/information/security/spring-mvc/reflected-cross-site-scripting/">https://junhyunny.github.io/information/security/spring-mvc/reflected-cross-site-scripting/</a>
*<em>CSRF(Cross-Site Request Forgery) 공격과 방어 : *</em><a href="https://junhyunny.github.io/information/security/spring-boot/spring-security/cross-site-reqeust-forgery/">https://junhyunny.github.io/information/security/spring-boot/spring-security/cross-site-reqeust-forgery/</a></p>
</blockquote>
<h2 id="1-3-cors-를-더-깊이-살펴보기">1-3) CORS 를 더 깊이 살펴보기</h2>
<p>CORS 는 Request 유형에 따라 3가지로 나눌 수 있고 Response 에는 지정된 필드들이 들어가는데 일단 Response 부터 살펴보고 각 Request 유형별로 어떻게 동작하는지 살펴보도록 하자.</p>
<h3 id="cors-response">CORS Response</h3>
<p>CORS Response 를 하기 위해서는 서버 개발자가 추가로 작업을 해줘야 한다. 작업된 Response 는 HttpHeader 에 담겨 전달된다.</p>
<blockquote>
<ul>
<li><strong>Access-Control-Allow-Origin</strong> : 허용된 Origin 요청, 요청 Origin 이 서버에 의해 허용되지 않으면 이 헤더 필드가 존재하지 않거나, 필드 Key 값은 존재하지만 Value 에 요청 Origin 값이 들어있지 않다.</li>
</ul>
</blockquote>
<ul>
<li><strong>Access-Control-Allow-Credentials</strong> : 요청된 credential mode 에 따라 true / false 값이 설정되어 Response 된다.</li>
<li><strong>Access-Control-Allow-Methods</strong> : 요청한 url 에 대해서 어떤 http method 가 허용되는지 응답한다.</li>
<li><strong>Access-Contrl-Max-Age</strong> : Preflight 결과를 캐시하는 시간을 설정한다.</li>
<li><strong>Access-Control-Expose-Headers</strong> : 클라이언트에게 보여줄 헤더 목록을 설정한다.</li>
</ul>
<h3 id="preflight-request">Preflight Request</h3>
<p>기본적으로 모든 CORS 요청은 Preflight 요청 후에 본 요청을 보낸다. 단어 뜻에서처럼 사전에 미리 보내는 <strong>예비 요청</strong>이다.</p>
<p>Preflight 요청은 우리가 알고 있는 Http Method 중 Post, Get, Patch 등이 아닌 <strong>Options 라는 메소드를 사용</strong>해 현재 브라우저의 Origin 을 서버에 보낸다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/52f2f3bb-f4cc-47fe-9552-ea520827127f/image.png" alt=""></p>
<p>워낙 이미지를 잘 그리셔서 가져왔다. 빨간색 예비 요청에 나와있는 것처럼 현재 브라우저의 Origin 을 보내면 서버는 Access-Control-Allow-Origin 으로 허용된 출처를 응답해준다. 여기서는 와일드카드로 모든 출처가 허용됐기 때문에 예비 요청에 성공했다. 예비 요청에 성공하면 본 요청을 보내 서버에 원래 요청하려 했던 정보들이 넘어간다.</p>
<p>만약 여기서 Access-Control-Allow-Origin 이 와일드카드가 아니라 아래와 같다고 해보자. 여기서 눈 여겨 봐야할 것은 마찬가지로 200 Ok 가 왔지만 CORS 에러가 발생했다는 점이다.</p>
<blockquote>
<p>OPTIONS <a href="https://evanmoon.tistory.com/rss">https://evanmoon.tistory.com/rss</a> 200 OK
Access-Control-Allow-Origin: <a href="https://evanmoon.tistory.com">https://evanmoon.tistory.com</a>
Content-Encoding: gzip
Content-Length: 699
Content-Type: text/xml; charset=utf-8
Date: Sun, 24 May 2020 11:52:33 GMT
P3P: CP=&#39;ALL DSP COR MON LAW OUR LEG DEL&#39;
Server: Apache
Vary: Accept-Encoding
X-UA-Compatible: IE=Edge</p>
</blockquote>
<p>그 이유는 <strong>브라우저는 자신이 보낸 예비 요청의 Origin</strong> 과 <strong>서버가 응답해준 허용 Origin 을 비교</strong>한 후 이것이 <strong>같으면 본 요청을 보내게</strong> 되는데, <strong>Options 요청에는 성공했지만 Origin 이 서로 다르기 때문에 200 Ok 가 오고 CORS 에러가 발생</strong>한 것이다.</p>
<p>그렇다면 CORS 는 <strong>왜 굳이 본 요청 전에 예비 요청</strong>을 하면서 불필요해 보이는 리소스를 낭비하는 걸까? 아이러니하게도 <strong>서버를 보호하기 위해서</strong>다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/a315de41-331d-4edb-8228-a9f6e80b6555/image.png" alt="">
위와 같이 <strong>Preflight 가 없이 본 요청</strong>만 가게 된다면 <strong>서버에서 무언가 다 작업이 일어난 후에 Client 측에서 CORS 에러가 터지</strong>게 된다. 만약 <strong>본 요청이 Delete 라던지 Post 라고 하면 이미 서버에서는 관련 작업이 다 일어났는데 Client 는 에러 때문에 이를 확인할 수 없는 상황</strong>이 된다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/0bebbd31-c739-4618-b6ca-441d6c764b2f/image.png" alt=""></p>
<p>따라서 위와 같이 <strong>Preflight 요청</strong>을 보내 <strong>CORS 에러</strong>를 띄워주어 <strong>본 요청이 서버에 갈 수 없도록 막는 것</strong>이다.</p>
<p>그렇다고 매번 Preflight 요청을 보내야만 할까? 그건 아니다. <strong>Access-Contrl-Max-Age 를 설정</strong>해주면 <strong>Preflight 결과를 캐싱해 해당 시간 만큼은 본 요청만으로 접근이 가능</strong>하다.</p>
<h3 id="simple-request">Simple Request</h3>
<p>Simple Request 는 Preflight 없이 본 요청으로부터 Access-Control-Allow-Origin 을 받고 CORS 정책 위반 여부를 검사하는 방식이다. 아래의 그림과 같은 방식이다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/a315de41-331d-4edb-8228-a9f6e80b6555/image.png" alt=""></p>
<p>하지만 아무 때나 단순 요청을 사용할 수 있는 것은 아니고, <strong>특정 조건을 만족하는 경우에만 사용이 가능</strong>하다. <strong>사실상 거의 사용이 불가능</strong>할 정도이다.</p>
<blockquote>
<ol>
<li>요청의 메소드는 GET, HEAD, POST 중 하나여야 한다.</li>
<li>Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width를 제외한 헤더를 사용하면 안된다.</li>
<li>만약 Content-Type를 사용하는 경우에는 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용된다.</li>
</ol>
</blockquote>
<p>1번 조건이야 Delete, Put, Patch 만 안쓰면 된다고 하지만, 2번 조건에서 Authorization 헤더도 사용 불가능하고, 3번에서 application/json 과 같은 요청도 보낼 수 없기 때문에 이 조건을 모두 만족 시키기란 어렵다.</p>
<p>Simple Request 는 <strong>그냥 이런게 있구나</strong> 정도만 하고 넘어가는게 나을 것 같다.</p>
<h3 id="credential-request">Credential Request</h3>
<p><strong>Header 에 인증 관련 정보를 함께 보낼 때 사용하는 요청</strong>이다. 동작 방식은 Preflight 와 비슷하지만 추가 옵션들이 들어간다. 그렇기 때문에 인증 관련 정보를 함께 보낼 때는 프론트와 백엔드 모두 관련 옵션을 설정해주어야 한다.</p>
<p>다만 이 옵션을 설정했을 경우에 반드시 해줘야 하는 하는 것이 두 가지 있다.</p>
<blockquote>
<ol>
<li><strong>Access-Control-Allow-Origin</strong>에는 * 를 사용할 수 없으며, <strong>명시적인 URL</strong>이어야한다.</li>
<li><strong>응답 헤더</strong>에는 반드시 <strong>Access-Control-Allow-Credentials: true가 존재</strong>해야한다.</li>
</ol>
</blockquote>
<p>이에 대해 설정하는 법은 아래에서 더 다뤄보도록 하고, 프론트 측에서는 axios, fetch, jquery, XMLHttpRequest 등에 따라 방법이 조금씩 다르기 때문에 아래의 포스팅을 참고하도록 하자.</p>
<blockquote>
<p><strong>[AXIOS] 📚 CORS 쿠키 전송하기 (withCredentials 옵션)</strong> : <a href="https://inpa.tistory.com/entry/AXIOS-%F0%9F%93%9A-CORS-%EC%BF%A0%ED%82%A4-%EC%A0%84%EC%86%A1withCredentials-%EC%98%B5%EC%85%98">https://inpa.tistory.com/entry/AXIOS-%F0%9F%93%9A-CORS-%EC%BF%A0%ED%82%A4-%EC%A0%84%EC%86%A1withCredentials-%EC%98%B5%EC%85%98</a></p>
</blockquote>
<h2 id="1-4-cors-를-해결하는-법">1-4) CORS 를 해결하는 법</h2>
<p>CORS 를 해결하는 법은 크게 3가지가 있다.</p>
<blockquote>
<ol>
<li>Proxy 우회 (localhost 개발 환경에서만 가능)</li>
<li>어노테이션 활용</li>
<li>Web Mvc Configuration 설정</li>
</ol>
</blockquote>
<p>일단 1번의 Proxy 를 우회하는 방법은 개발 환경에서만 사용 가능하고, <del>현직자에 따르면</del> 정석적인 방법은 아니라고 한다. 어차피 개발 환경에서 잠시 사용하고 배포를 하면 문제가 다시 발생하기 때문이 아닐까라고 생각한다. 그래서 여기에서는 다루지 않고 2번과 3번에 대해 포스팅하도록 하겠다.</p>
<h3 id="crossorigin-어노테이션-활용">@CrossOrigin 어노테이션 활용</h3>
<p>@CrossOrigin 을 Controller 에 사용해 <strong>Controller 전체에 사용</strong>할 수도, 아니면 <strong>Controller 하위 Method 에 사용</strong>할 수도 있다. 물론 하위 옵션을 통해 origins, methods, allowedHeaders, exposedHeaders, allowCredential, maxAge 를 설정할 수 있다. 옵션들의 차이는 아래에서 더 살펴보도록 하자.</p>
<pre><code class="language-java">@RestController
@RequiredArgsConstructor
@RequestMapping(&quot;/api/post&quot;)
@CrossOrigin(origins = &quot;http://example.com&quot;, maxAge = 3600) //Controller 전체에 적용
public class PostController {

    private final PostService postService;

    @PostMapping
    public ResponseEntity&lt;?&gt; createPost(@RequestBody PostRequestDto postRequestDto) {
        return postService.createPost(postRequestDto);
    }

    @GetMapping
    public ResponseEntity&lt;?&gt; getPostList() {
        return postService.getPostList(postRequestDto);
    }
</code></pre>
<pre><code class="language-java">@RestController
@RequiredArgsConstructor
@RequestMapping(&quot;/api/post&quot;)
public class PostController {

    private final PostService postService;

    @PostMapping
    @CrossOrigin(origins = &quot;http://example.com&quot;, maxAge = 2400) //Method 에 적용
    public ResponseEntity&lt;?&gt; createPost(@RequestBody PostRequestDto postRequestDto) {
        return postService.createPost(postRequestDto);
    }

    @GetMapping
    public ResponseEntity&lt;?&gt; getPostList() {
        return postService.getPostList(postRequestDto);
    }
</code></pre>
<h3 id="webmvcconfiguration-설정">WebMvcConfiguration 설정</h3>
<p>위의 <strong>어노테이션을 사용하는 방법</strong>은 <strong>Controller 별, Method 별로 CORS 허용 방식을 다르게 가져갈 때 유용</strong>할 것이다. 그러나 <strong>CORS 허용을 전역적으로 사용</strong>하고 싶다면 이 방법이 훨씬 편하고 유지보수 하기에 수월하다. Configuration Class 하나만 설정해주면 되기 때문이다. WebMvcConfigurer 를 implements 하고 addCorsMapping 을 Override 해주면 끝이다. <del>이렇게 하는 이유는 Spring이 아닌 SpringBoot 를 사용하기 때문인데, 이러다 또 박찬호가 될 수 있으므로 각설하도록 하겠다.</del></p>
<pre><code class="language-java">@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry corsRegistry){
        corsRegistry.addMapping(&quot;/**&quot;)
                .allowedOrigins(&quot;http://localhost:3000&quot;)
                .allowedMethods(&quot;*&quot;)
                .exposedHeaders(&quot;Authorization&quot;, &quot;RefreshToken&quot;)
                .allowCredentials(true);
    }
}</code></pre>
<p>addMapping 은 Origins 이후에 오는 Path 를, allowedMethods 는 허용해주고자 하는 Method 를 가리킨다.  여기서는 와일드카드를 사용해 모두 열어주었다.</p>
<p>allowedOrigins 는 알다시피 허용해주고자 하는 출처이다. 현재는 <a href="http://localhost:3000">http://localhost:3000</a> 만 허용해주었지만  그 외에 다른 url 을 허용해주고 싶다면 콤마 이후에 쌍따옴표로 url 을 추가해주면 된다.</p>
<p>allowCredentials 를 true 로 설정하여 Authorization 과 RefreshToken 이 헤더에 담길 수 있도록 하였다.</p>
<p>여기서는 maxAge 를 설정해주지 않았지만 따로 설정을 해주어 Preflight 캐싱 시간을 설정할 수도 있다.</p>
<p>allowedHeaders 와 exposedHeaders 의 차이점은 요청의 header 냐 응답의 header 냐의 차이라고 볼 수 있다. 해당 옵션에 들어가서 보면 이렇게 나와있다.</p>
<pre><code class="language-java">    /**
     Set the list of headers that a pre-flight request can list as allowed
     for use during an actual request.
     The special value &quot;*&quot; may be used to allow all headers.
     A header name is not required to be listed if it is one of:
     Cache-Control, Content-Language, Expires,
     Last-Modified, or Pragma as per the CORS spec.
     By default all headers are allowed.
     */
    public CorsRegistration allowedHeaders(String... headers) {
        this.config.setAllowedHeaders(Arrays.asList(headers));
        return this;
    }

    /**
    비행 전 요청이 실제 요청 중에 사용할 수 있도록 허용된 것으로 나열할 수 있는 헤더 목록을 설정합니다.
    특수 값 &quot;*&quot;는 모든 헤더를 허용하는 데 사용될 수 있습니다.
    헤더 이름이 CORS 사양에 따라 캐시 제어, 콘텐츠 언어, 만료, 최종 수정 또는 프라그마 중 
    하나인 경우에는 나열할 필요가 없습니다.
    기본적으로 모든 헤더가 허용됩니다.
    */

    /**
     Set the list of response headers other than &quot;simple&quot; headers, i.e.
     Cache-Control, Content-Language, Content-Type,
     Expires, Last-Modified, or Pragma, that an
     actual response might have and can be exposed.
     The special value &quot;*&quot; allows all headers to be exposed for
     non-credentialed requests.
     By default this is not set.
     */
    public CorsRegistration exposedHeaders(String... headers) {
        this.config.setExposedHeaders(Arrays.asList(headers));
        return this;
    }

    /**
    단순 헤더를 제외한 응답 헤더 목록을 설정합니다. 
    실제 응답이 있을 수 있고 노출될 수 있는 캐시 제어, 내용 언어, 내용 유형, 만료, 최종 수정 또는 프래그마.
    특수 값 &quot;*&quot;를 사용하면 자격 증명이 없는 요청에 대해 모든 헤더를 노출할 수 있습니다.
    기본적으로 이 값은 설정되지 않습니다.
    */</code></pre>
<p>파파고로 어느 정도 해석을 해봤는데 요약해보자면 allowedHeaders 는 RequestHeader 로 허용할 목록을 설정하는 것이고 exposedHeaders 는 ResponseHeader 로 허용할 목록을 설정한다고 볼 수 있다.</p>
<p>실제로 아래 포스팅의 사례에서처럼 exposedHeaders 에 JWT 등을 기재하지 않을 경우 브라우저에서 읽을 수 없는 것으로 확인되었다. </p>
<blockquote>
<p><strong>SpringBoot에서 CORS할 때 header, preflight 이슈 해결하기</strong> : <a href="https://velog.io/@ojwman/spring-boot-cors-header-preflight">https://velog.io/@ojwman/spring-boot-cors-header-preflight</a></p>
</blockquote>
<blockquote>
<p>참고 : 
[10분 테코톡] 🌳 나봄의 CORS : <a href="https://www.youtube.com/watch?v=-2TgkKYmJt4">https://www.youtube.com/watch?v=-2TgkKYmJt4</a>
[HTTP] Cross Origin Resource Sharing, CORS란? : <a href="https://wonit.tistory.com/307">https://wonit.tistory.com/307</a>
[Spring Boot] CORS 를 해결하는 3가지 방법 (Filter, @CrossOrigin, WebMvcConfigurer) : <a href="https://wonit.tistory.com/572">https://wonit.tistory.com/572</a>
[Spring Boot] 4. 로컬 개발을 위한 CORS 설정 - (1) w3c recommendation : <a href="https://letsmakemyselfprogrammer.tistory.com/44">https://letsmakemyselfprogrammer.tistory.com/44</a>
[Spring Security] CORS : <a href="https://velog.io/@chullll/Spring-Security-CORS">https://velog.io/@chullll/Spring-Security-CORS</a>
CORS는 왜 이렇게 우리를 힘들게 하는걸까? : <a href="https://evan-moon.github.io/2020/05/21/about-cors/#credentialed-request">https://evan-moon.github.io/2020/05/21/about-cors/#credentialed-request</a>
[AXIOS] 📚 CORS 쿠키 전송하기 (withCredentials 옵션) : <a href="https://inpa.tistory.com/entry/AXIOS-%F0%9F%93%9A-CORS-%EC%BF%A0%ED%82%A4-%EC%A0%84%EC%86%A1withCredentials-%EC%98%B5%EC%85%98">https://inpa.tistory.com/entry/AXIOS-%F0%9F%93%9A-CORS-%EC%BF%A0%ED%82%A4-%EC%A0%84%EC%86%A1withCredentials-%EC%98%B5%EC%85%98</a>
SpringBoot에서 CORS할 때 header, preflight 이슈 해결하기 : <a href="https://velog.io/@ojwman/spring-boot-cors-header-preflight">https://velog.io/@ojwman/spring-boot-cors-header-preflight</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해99 4주차 WIL - SQL / ORM / MVC]]></title>
            <link>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-4%EC%A3%BC%EC%B0%A8-WIL</link>
            <guid>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-4%EC%A3%BC%EC%B0%A8-WIL</guid>
            <pubDate>Sun, 16 Oct 2022 05:35:30 GMT</pubDate>
            <description><![CDATA[<p>어느 정도 수준까지 블로그를 작성해야 하는지는 항상 고민거리다. 어쨌든 이번 포스팅도 내가 공부한 만큼, 그리고 내가 설명 가능한 부분까지 작성해 보겠다.</p>
<h1 id="1-sql">1) SQL</h1>
<h2 id="1-1-sql-이란">1-1) SQL 이란?</h2>
<h3 id="sql-의-뜻">SQL 의 뜻</h3>
<p><strong>SQL 이란</strong> Structured Query Language 의 약자로, &#39;구조화 질의어&#39; 라는 뜻이다. <strong>RDBMS 의 데이터를 관리</strong>하기 위해 설계된 특수 목적의 <strong>프로그래밍 언어</strong>이다.</p>
<blockquote>
<p><strong>SQL</strong> : <a href="https://ko.wikipedia.org/wiki/SQL">https://ko.wikipedia.org/wiki/SQL</a></p>
</blockquote>
<p>역시나 이렇게 간단하게 끝날 내 포스팅이 아니다. RDBMS 와 SQL 이 어떤 것인지 조금 더 자세히 알아보자.</p>
<h3 id="db-와-dbms-rdbms">DB 와 DBMS, RDBMS</h3>
<p><strong>DB</strong>(Database) 는 사실 그 정의를 논하는 것이 무의미할 정도로 많은 사람들이 알고 사용하는 단어인 것 같다. 그럼에도 불구하고 아래의 유튜브에서 정의내린 것을 빌려 말하자면, 전자적으로 저장되고 사용되는 <strong>관련있는 데이터들의 조직화된 집합</strong>을 말한다.</p>
<p><strong>DBMS</strong>(Database Management Systems) 는 영어 뜻대로 <strong>DB 를 관리</strong>할 수 있도록 만들어주는 <strong>소프트웨어 시스템</strong>이다.</p>
<p>그 중에서도 <strong>RDBMS</strong>(Relational Database Management Systems) 는 관계형 데이터베이스 시스템으로, 데이터베이스 <strong>테이블이</strong> 마치 엑셀처럼 <strong>행과 열로 구분</strong>되는 것을 말한다. 특히 각 행이 구분될 수 있도록 그 <strong>행이 갖는 고유한 값인 PK(Primary Key, 기본 키)</strong> 를 두고, <strong>FK(Foreign Key, 외래 키)</strong> 를 사용해 <strong>관련있는 테이블 사이의 관계</strong>를 만들 수 있다는 특징이 있다.</p>
<p>결국, <strong>SQL</strong> 은 이 <strong>RDBMS</strong> 를 관리<strong>(데이터의 삽입, 수정, 삭제 등)</strong> 을 할 수 있도록 하는 <strong>프로그래밍 언어</strong>라는 뜻이다. </p>
<p>그렇다면 SQL 로 RDBMS 를 어떻게 관리하는 가에 대한 내용은 아래에서 조금 더 자세히 설명하도록 하겠다.</p>
<blockquote>
<p><strong>백엔드에서 사용되는 데이터베이스(database) 기본 개념</strong> : <a href="https://www.youtube.com/watch?v=aL0XXc1yGPs&amp;list=WL&amp;index=18">https://www.youtube.com/watch?v=aL0XXc1yGPs&amp;list=WL&amp;index=18</a></p>
</blockquote>
<h2 id="1-2-sql-을-공부해야-하는-이유">1-2) SQL 을 공부해야 하는 이유</h2>
<p><strong>항해 과정 중 미니 프로젝트와 클론 프로젝트 중</strong>에는 SQL 공부가 필요하다는 생각을 전혀 해본 적이 없었다. <strong>JPA 만으로도 충분</strong>했기 때문이다. 그러나 <strong>실전 프로젝트</strong>에 들어가며 <strong>JPA 의 한계</strong>를 맞이하게 되었고, <strong>성능 개선을 위해</strong> Query Tuning 및 QueryDSL 사용이 필요했고, <strong>SQL 문 공부가 필요한 시점</strong>이 찾아오게 되었다.</p>
<p>이 글을 보고 있는 당신이 <strong>아직 초보자</strong>이고 아직 <strong>JPA 로도 충분</strong>하다면 지금은 프로젝트에 충실히 임하고, <strong>정말 필요할 때 다시 공부</strong>하기를 권장한다. 아직 Spring 이나 Java 도 익숙하지 않은데 SQL 까지 공부하려면 정말 숨이 턱 막힌다. </p>
<p>일단은 <strong>JPA 의 다양한 사용법</strong> (findTop5, findByNameLike 등과 같은) 까지 <strong>충분히 익힌 후에 공부</strong>해도 늦지 않다고 생각한다. 그리고 JPA 의 다양한 사용법을 익힌다면 SQL 도 충분히 이해가 잘 될 정도로 성장해 있을 것이다.</p>
<p>중요한 것은 <strong>DB 는 백엔드와 맞닿아 있기 때문</strong>에 좋든 싫든 개발 및 현업 중에 <strong>반드시 알고 사용할 수 있어야 한다</strong>는 것이다. <del>물론 아직 현업자는 아니지만...</del> 위에서도 썼고, 아래에서도 다루겠지만 JPA 의 한계가 명확하기 때문에 <strong>SQL 을 다룰 줄 아는 것이 백엔드 개발자의 기본 소양</strong> 이라고 생각한다. <del>아직 엄청 잘 다루는 것은 아니지만...</del></p>
<p>어쨌든 여기에서는 복잡한 SQL 문을 다루거나 Query Tuning 및 QueryDSL 은 다루지 않을 것이고, 간단한 테이블을 만들어보며 SQL 문이 어떻게 작성되고 어떻게 생겼는지 구경(?) 해보도록 하자.</p>
<blockquote>
<p><strong>[Spring JPA] Query Method</strong> : <a href="https://velog.io/@seongwon97/Spring-Boot-Query-Method">https://velog.io/@seongwon97/Spring-Boot-Query-Method</a></p>
</blockquote>
<h3 id="rdbms-테이블-만들기">RDBMS 테이블 만들기</h3>
<h4 id="erd-설계하기">ERD 설계하기</h4>
<p><strong>ERD</strong> 란 Entity Relationship Diagram 의 약자로, <strong>개체 관계 다이어그램</strong>이라는 뜻을 갖고 있다. 예를 들어 우리가 Spring 과 RDBMS, 그중에서도 MySQL 을 사용하여 게시판 기능을 만든다고 해보자. 특히 해당 게시판에는 댓글을 작성할 수 있다고 가정해 보면, <strong>하나의 게시글은 여러 개의 댓글을 포함하는 관계</strong>를 맺게 될 것이다. 이러한 <strong>관계를 다이어그램으로 나타낸 것</strong>이 ERD 이다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/101178be-9974-4450-8326-1ff884815ade/image.png" alt=""></p>
<p>사진에서 보다시피 <strong>Post 테이블</strong>에는 <strong>제목에 해당하는 title</strong> 과 <strong>내용에 해당하는 postContent</strong> 를, <strong>Reply 테이블</strong>에는 <strong>댓글에 해당하는 replyContent</strong> 를 입력하여 간단한 테이블 설계까지 마쳤다. 여기서 특히 주목해야할 점은 <strong>게시글과 댓글은 1:N 의 관계</strong>를 맺고 있는 것을 볼 수 있다. 이 사진은 아래의 예시에서도 계속 사용할 것이기 때문에 잘 기억해놓도록 하자.</p>
<p>이 포스팅에서는 연관관계에 대한 내용을 다루고자 하는 것이 아니기 때문에 관련 내용은 아래의 포스팅을 참고하도록 하자.</p>
<blockquote>
<p><strong>연관관계 매핑 기초</strong> : <a href="https://catsbi.oopy.io/ed9236a0-6521-471d-8a0d-b852147b5980">https://catsbi.oopy.io/ed9236a0-6521-471d-8a0d-b852147b5980</a>
<strong>JPA 연관 관계 한방에 정리</strong> : <a href="https://jeong-pro.tistory.com/231">https://jeong-pro.tistory.com/231</a>
<strong>[DB] 📈 데이터 모델링 개념 &amp; ERD 다이어그램 그리기 💯 총정리</strong> : <a href="https://inpa.tistory.com/entry/DB-%F0%9F%93%9A-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AA%A8%EB%8D%B8%EB%A7%81-1N-%EA%B4%80%EA%B3%84-%F0%9F%93%88-ERD-%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8">https://inpa.tistory.com/entry/DB-%F0%9F%93%9A-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AA%A8%EB%8D%B8%EB%A7%81-1N-%EA%B4%80%EA%B3%84-%F0%9F%93%88-ERD-%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8</a></p>
</blockquote>
<h4 id="rdbms-data-type">RDBMS Data Type</h4>
<p>위의 사진에서 보다시피, 조금 생소한 녀석이 눈에 띈다. long 옆에 BIGINT 로, String 옆에는 VARCHAR(255) 라고 되어 있는데, 이렇게 작성해 놓은 이유는 <strong>Java 에서 사용하는 데이터 타입과 RDBMS 에서 사용하는 데이터 타입이 다르기 때문</strong>이다.</p>
<p>RDBMS 종류에 따라 데이터 타입이 일부 지원하지 않거나 다른 경우도 있지만 거의 표준에 가까운 MySQL 의 데이터 타입 및 Java 와 비교 자료가 있으니 아래의 포스팅을 참고해보도록 하자.</p>
<p>어쨌든 지금 하려는 것은 SQL 문을 작성하여 직접 MySQL 에 테이블을 생성한다는 가정이니 Java 와 비교 자료는 아래의 JPA 에서 확인하는 것이 좋을 것 같다.</p>
<blockquote>
<p><strong>MYSQL 데이터 타입</strong> : <a href="https://blog.martinwork.co.kr/mysql/2020/01/17/mysql-data-type.html">https://blog.martinwork.co.kr/mysql/2020/01/17/mysql-data-type.html</a>
<strong>MySQL 데이터 타입 Java 데이터 타입 비교</strong> : <a href="https://goldfishhead.tistory.com/2">https://goldfishhead.tistory.com/2</a>
<strong>SQL의 개념과 SQL로 데이터베이스를 정의하는 법</strong> : <a href="https://www.youtube.com/watch?v=c8WNbcxkRhY&amp;list=WL&amp;index=31">https://www.youtube.com/watch?v=c8WNbcxkRhY&amp;list=WL&amp;index=31</a></p>
</blockquote>
<h4 id="sql-문-작성해보기">SQL 문 작성해보기</h4>
<p>위의 SQL의 개념과  SQL 로 데이터베이스를 정의하는 법을 꼭 보길 바란다. 정말 정리가 잘되어 있다. 어쨌든 해당 영상을 참고해 <strong>Constraint</strong> 중 하나인 Not Null 을 추가하고 <strong>Foreign Key</strong> 의 Cascade 옵션을 사용하여 PK의 업데이트와 삭제가 Reply 테이블에 반영되도록 하였다.</p>
<p>게시글의 제목과 내용에 각각 50자, 255자의 제한을 걸어주었고, 댓글에도 100자 제한을 걸어주었다.</p>
<pre><code class="language-sql">create table Post ( //테이블 생성 메소드 입력
    id    BIGINT    PRIMARY KEY, //Primary Key 정의, 확장성을 생각해 long 에 대응되는 BIGINT 사용
    title    VARCHAR(50) NOT NULL, //가변 문자열 50 자 제한
    post_content    VARCHAR(255) NOT NULL //가변 문자열 255 자 제한
);</code></pre>
<pre><code class="language-sql">create table Reply ( //테이블 생성 메소드 입력
    id    BIGINT    PRIMARY KEY, //Primary Key 정의, 확장성을 생각해 long 에 대응되는 BIGINT 사용
    post_id    BIGINT //post 테이블 PK 의 타입을 그대로 가져옴
    FOREIGN KEY (post_id)
        references Post(id) //post 테이블의 id 를 Foreign Key 로 사용
        on delete CASCADE //PK 삭제 시 Cascade 옵션 적용
        on update CASCADE //PK 변경 시 Cascade 옵션 적용
    reply_content    VARCHAR(100) NOT NULL, //가변 문자열 100 자 제한
);</code></pre>
<p>간단한 테이블을 만드는 코드라 그렇게 어렵진 않은 것 같다. <del>그렇다고 잘 짠 것 같지도 않지만...</del> 어떤가 생각보다 SQL 을 짜보는 것도 할만한 것 같지 않은가? 생각보다 굉장히 직관적인 코드라서 한 번 배워두면 굉장히 유용할 것이다.</p>
<h3 id="crud-해보기">CRUD 해보기</h3>
<h4 id="create">Create</h4>
<pre><code class="language-sql">insert into Post //정해진 attribute 순서대로 입력
    values (1, &#39;SQL 에 대한 모든 것&#39;, &#39;SQL 문 그까이꺼 별 거 없죠?&#39;);</code></pre>
<pre><code class="language-sql">insert into Post (title, post_content, id) //attribute 순서 custom
    values (&#39;ORM 은 뭔데?&#39;, &#39;ORM 도 해야되는데 큰일 났어요&#39;, 2);</code></pre>
<pre><code class="language-sql">insert into Post values //다중 tuple 입력
    (3, &#39;No-SQL 은 뭘까?&#39;, &#39;NO! SQL 아닙니다. Not only SQL 입니다.&#39;),
    (4, &#39;JDBC 는?&#39;, &#39;Java Database Connectivity 랍니다.&#39;),
    (5, &#39;MVC Pattern 이란?&#39;, &#39;Model, View, Controller라는 디자인 패턴이죠&#39;);</code></pre>
<p><strong>insert into 후에 테이블 이름</strong>을 입력하고 <strong>value 에 입력하려는 값</strong>을 넣었다. 맨 위에 입력한 SQL 문 처럼 정해진 순서대로 입력할 수도, attribute 순서를 내 맘대로 입력할 수도(물론 입력하지 않는다면 null 이 입력된다, 위의 경우에는 모든 attribute 에 not null constraints 가 걸려있기 때문에 null 을 입력하면 에러가 발생한다), 다중으로 여러 개의 데이터를 입력할 수도 있다.</p>
<pre><code class="language-sql">insert into Reply values 
    (1, 2, &#39;괜찮아요! 하면 되죠!&#39;),
    (2, 5, &#39;MVVC 패턴이라는 것도 있던데요!!&#39;),
    (3, 1, &#39;그러게요ㅋㅋㅋ 생각보다 할 만 하네요&#39;),
    (4, 3, &#39;NO-SQL 의 종류도 궁금해요&#39;),
    (5, 4, &#39;지금까지 JDBC 와 JPA 를 혼동해서 알고 있었습니다...&#39;),
    (6, 4, &#39;이게 끝인가요?&#39;);</code></pre>
<p>그리고 댓글 테이블에도 순서대로 1~5 까지의 댓글을 만들고, Post 의 PK 를 입력해 연관 관계를 나타내주었다. 댓글 1번은 2번 게시글과, 댓글 5번과 6번은 4번 게시글에 대응되는 댓글이다.</p>
<h4 id="read">Read</h4>
<pre><code class="language-sql">select * from Post;</code></pre>
<p><strong>select 는 RDB 의 열</strong>을 선택하는 것이고, <strong>from 은 테이블을 선택하는 명령어</strong>이다. 여기서 select 에 와일드 카드인 * 를 입력하여 모든 열을 불러오도록 했고 from 에 Post 를 넣어 Post 테이블의 모든 열을 불러오도록 했다. 뒤에 어떠한 조건절도 없으므로 아래처럼 모든 행이 검색되어 나온다.</p>
<pre><code class="language-sql">id    |    title    |    post_content
1    |    &#39;SQL 에 대한 모든 것&#39;    |    &#39;SQL 문 그까이꺼 별 거 없죠?&#39;
2    |    &#39;ORM 은 뭔데?&#39;    |    &#39;ORM 도 해야되는데 큰일 났어요&#39;
3    |    &#39;NO-SQL 은 뭘까?&#39;    |    &#39;NO! SQL 아닙니다. Not Only SQL 입니다.&#39;
4    |    &#39;JDBC 는?&#39;    |    &#39;Java Database Connectivity 랍니다.&#39;
5    |    &#39;MVC Pattern 이란?&#39;    |    &#39;Model, View, Controller라는 디자인 패턴이죠&#39;</code></pre>
<pre><code class="language-sql">select id, reply_content from Reply where post_id = 4;</code></pre>
<p>4번 게시글의 댓글 중 댓글 번호와 댓글 내용만 알고 싶다면 위 처럼 select 문에 열의 이름을 적고 <strong>where 문에 조건을 입력</strong>한다면 아래처럼 검색되어 결과가 나올 것이다.</p>
<pre><code class="language-sql">id    |    post_content
5    |    &#39;지금까지 JDBC 와 JPA 를 혼동해서 알고 있었습니다...&#39;
6    |    &#39;이게 끝인가요?&#39;
</code></pre>
<h4 id="update">Update</h4>
<pre><code class="language-sql">update Reply set reply_content = &#39;패턴에 대해 더 공부해봐야겠습니다!&#39; where id = 2;</code></pre>
<p>2번 댓글을 수정하는 SQL 문이다. 테이블을 선택한 후 <strong>set 이후에 수정하려는 attribute 의 값</strong>을 넣고 <strong>where 절의 조건</strong>을 통해 2번 댓글을 선택해주었다. 점점 SQL 문에 자신감이 생기는 자신을 발견할 수 있을 것이다.</p>
<h4 id="delete">Delete</h4>
<pre><code class="language-sql">delete from Post where id = 5;</code></pre>
<p>삭제는 더 쉽다. <strong>delete from 으로 테이블을 선택</strong>하고 <strong>where 에 조건</strong>을 통해 5번 게시글을 삭제하였다. 이때 기억해야할 것이 <strong>Cascade 옵션</strong>이다. 우리는 댓글 테이블을 만들 때 Cascade 옵션을 주었기 때문에 5번 게시글이 삭제될 때 5번 게시글에 해당하는 댓글도 함께 삭제될 것이다. 따라서 2번 댓글도 함께 삭제된다.</p>
<p>생각보다 그렇게 어렵진 않다. SQL 문 또한 다른 프로그래밍 언어와 마찬가지로 많이 연습해보고 눈에 익히는게 가장 좋은 것 같다. 아래의 SQL Tutorial 사이트에서 연습할 수 있으니 이것 저것 많이 사용해보자 특히나 <strong>where 절을 통해 다양한 조건</strong>을 줄 수 있으니 <em><strong>이를 꼭! 연습해보고 숙달시키도록 하자.</strong></em></p>
<blockquote>
<p><strong>SQL로 DB에 데이터를 추가(insert)하고 수정(update)하고 삭제(delete)하는 방법</strong> : <a href="https://www.youtube.com/watch?v=mgnd5JWeCK4&amp;list=WL&amp;index=22">https://www.youtube.com/watch?v=mgnd5JWeCK4&amp;list=WL&amp;index=22</a>
<strong>SQL로 데이터 조회하기</strong> : <a href="https://www.youtube.com/watch?v=dTBwgWMUguE&amp;list=WL&amp;index=6">https://www.youtube.com/watch?v=dTBwgWMUguE&amp;list=WL&amp;index=6</a>
<strong>SQL Tutorial</strong> : <a href="https://www.w3schools.com/sql/default.asp">https://www.w3schools.com/sql/default.asp</a></p>
</blockquote>
<h2 id="1-3-no-sql-은-뭐야">1-3) NO-SQL 은 뭐야?</h2>
<p><strong>NO-SQL</strong> 이란 NO SQL 이 아니라 <strong>Not Only SQL 의 약자</strong>로, <strong>SQL 이 아닌 모든 DBMS</strong> 를 말한다. 사실 SQL 과 NO-SQL 이 아니라 RDBMS 와 NO-SQL 을 비교하는 게 의미가 있을 것 같다.</p>
<p>간단하게나마 짚고 넘어가면, <strong>RDBMS 는 관계형으로 데이터를 저장</strong>하지만 <strong>NO-SQL 은</strong> MongoDB 와 같은 Document Model, CassandraDB 와 같은 Key-Value Model, Neo4j 와 같은 Graph Model 처럼 <strong>다양한 방식으로 데이터를 저장</strong>한다.</p>
<p>어쨌든 여기서 다룰 것은 RDBMS 와 NO-SQL 을 비교하고자 하는 것이 아니기 때문에 자세한 내용은 아래의 유튜브를 보고 공부해보도록 하자.</p>
<blockquote>
<p><strong>RDB vs NoSQL</strong> : <a href="https://www.youtube.com/watch?v=4PxBaojUnU4&amp;list=WL&amp;index=12">https://www.youtube.com/watch?v=4PxBaojUnU4&amp;list=WL&amp;index=12</a>
<strong>SQL vs NoSQL 5분컷 설명</strong> : <a href="https://www.youtube.com/watch?v=Q_9cFgzZr8Q">https://www.youtube.com/watch?v=Q_9cFgzZr8Q</a></p>
</blockquote>
<h1 id="2-orm">2) ORM</h1>
<h2 id="2-1-orm-이란">2-1) ORM 이란?</h2>
<p><strong>ORM 이란</strong> Object Relation Mapping 의 약자로, <strong>객체와 DB의 테이블을 Mapping</strong> 시켜 <strong>RDB 테이블을 객체지향적으로 사용하게 해주는 기술</strong>이다. 위에서 봤듯이 <strong>RDB</strong> 는 <strong>데이터를 정교화해서 잘 보관</strong>하는 것이 목표라면, <strong>Java</strong> 는 필드 메소드를 <strong>캡슐화해서 사용되는 것을 지향</strong>하기 때문이다. 이것을 <strong>패러다임의 불일치</strong>라고 하는데, <strong>이를 해결해주는 것이 ORM</strong> 이며 대표적으로 JPA 와 그의 구현체인 Hibernate 가 있다.</p>
<p>JPA 를 알아보기 이전에 JDBC 와 SQL Mapper 부터 살펴보도록 하자.</p>
<h2 id="2-2-jdbc-와-sql-mapper">2-2) JDBC 와 SQL Mapper</h2>
<h3 id="jdbc-란">JDBC 란?</h3>
<p><strong>JDBC</strong> 란 Java Database Connectivity 의 약자로, <strong>자바에서 데이터베이스에 접속</strong>할 수 있도록 하는 자바 API 이다. 이름에서 보다시피 자바를 이용해 다양한 DB 에 접근할 수 있도록 도와주는 기술이다.</p>
<p>만약에 MySQL, OracleDB, PostgreSQL 을 모두 사용하고 있는 프로젝트가 있다고 해보자. DB 에 접근하려면 DB Connection 을 얻어 SQL 을 전달 및 실행한 후 DB Connection 을 다시 닫아줘야 한다. <strong>JDBC 가 없다면, 각 DB 에 맞는 코드를 작성해야하는 상황</strong>이 생길 것이다. 지금 사용하고 있는 DB 가 세 개나 되다보니 저장할 데이터에 따라 각 DB 에 맞는 Connection 을 연결해서 SQL 을 전달하고 Connection 을 닫아주도록 코드를 작성해야될 것이다. 더해서 DB 를 다른 DB로 변경하거나 DB 를 추가하는 일이 생긴다면 서버에 개발된 DB 코드도 함께 변경하거나 추가해야되는 상황이 된다.</p>
<p>JDBC 는 각 DB 에 종속되지 않도록 일관된 언어로 개발을 진행할 수 있다.</p>
<h4 id="jdbc-예시">JDBC 예시</h4>
<p>아래의 코드를 보면 String 으로 SQL 문을 작성한 후 Connection, PrepareStatement, ResultSet 객체를 선언해주고 try catch 문을 사용해 DB 에 Member 를 삽입하고 검색하는 것을 확인할 수 있다.</p>
<pre><code class="language-java">    @Override
    public Member save(Member member) {
        String sql = &quot;insert into member(name) values(?)&quot;;
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql,
                    Statement.RETURN_GENERATED_KEYS);
            pstmt.setString(1, member.getName());
            pstmt.executeUpdate();
            rs = pstmt.getGeneratedKeys();
            if (rs.next()) {
                member.setId(rs.getLong(1));
            } else {
                throw new SQLException(&quot;id 조회 실패&quot;);
            }
            return member;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs); }
    }

    @Override
    public Optional&lt;Member&gt; findById(Long id) {
        String sql = &quot;select * from member where id = ?&quot;;
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setLong(1, id);
            rs = pstmt.executeQuery();
            if(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong(&quot;id&quot;));
                member.setName(rs.getString(&quot;name&quot;));
                return Optional.of(member);
            } else {
                return Optional.empty();
            }
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }</code></pre>
<p>코드를 보면 알겠지만 SQL 문을 잘못짰더라도 컴파일 시점에 이를 알 방법이 없다. 특히 데이터 접근 관련 코드가 중복되는 경우가 많고 핵심 관심사 외에 부가적인 코드가 더 길어진다는 한계가 있다.</p>
<h3 id="sql-mapper">SQL Mapper</h3>
<h4 id="sql-mapper-란">SQL Mapper 란?</h4>
<p><strong>SQL Mapper</strong> 란 <strong>SQL 문과 객체를 매핑</strong>하는 기술이다. MyBatis 가 대표적인 예이다.</p>
<h4 id="mybatis-예시">MyBatis 예시</h4>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt;
&lt;!DOCTYPE mapper
  PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot;&gt;
&lt;mapper namespace = &quot;org.mybatis.example.UserMapper&quot;&gt;
  &lt;select id = &quot;selectUser&quot; resultType = &quot;User&quot;&gt;
    select * from user where id = #{id}
  &lt;/select&gt;
&lt;/mapper&gt;</code></pre>
<p>위와 같은 식으로 xml 에서 SQL 을 작성한 후</p>
<pre><code class="language-java">public interface UserMapper {
    User selectUser(Long id);
}</code></pre>
<p>정의해놓은 메소드로 SQL 문에서 받아온 데이터를 객체화할 수 있다.</p>
<p>그나마 JDBC 보다는 편해졌지만 여전히 SQL 문을 직접 작성해야된다는 단점이 존재한다. <del>그리고 xml 코드가 보기 싫다...</del></p>
<h2 id="2-3-jpa">2-3) JPA</h2>
<p><strong>JPA</strong> 란 Java Persistence API 의 약자로, <strong>Java 의 대표적인 ORM 기술</strong>이다. JPA 는 <strong>인터페이스 모음</strong>으로, 사용 시 <strong>JPA Repository 를 extends 받아서 사용</strong>해야한다.</p>
<p>JPA 를 사용하기 전 콘솔 창에 SQL 문을 볼 수 있도록 설정부터 해보자.</p>
<h3 id="spring-설정-해보기">Spring 설정 해보기</h3>
<p>이미 알고 있겠지만, Spring 에서 설정을 조금 바꿔준다면 JPA 가 작동할 때 Query 가 작동하는 모습을 볼 수 있다.</p>
<p>application.properties 파일에 아래의 문장을 추가해주자. </p>
<pre><code class="language-java">spring.jpa.show-sql=true</code></pre>
<p>그 후에 RDBMS 에 CRUD 가 일어난다면 아래의 사진과 같이 select, from, insert 등과 같이 어떤 Query 문이 어떻게 작성되었는지 확인할 수 있게 된다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/95079711-9124-4cf7-b76c-deb172c81dc3/image.png" alt=""></p>
<h3 id="jpa-맛보기">JPA 맛보기</h3>
<h4 id="entity-만들어보기">Entity 만들어보기</h4>
<p><strong>Entity 객체</strong>는 <strong>RDB 테이블의 하나의 튜플</strong>이 될 것이다. Entity 객체 하나가 생성되어 Repository 에 저장되면 그것이 RDB 테이블에 하나의 행이 된다. SQL 문을 작성할 때의 예시를 가져와 Post 와 Comment 객체를 만들어봤다.</p>
<pre><code class="language-java">@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Post {

    @Id
    @Column(name = &quot;post_id&quot;)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String postContent;

}</code></pre>
<pre><code class="language-java">@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Reply {

    @Id
    @Column(name = &quot;comment_id&quot;)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = fetchType.Lazy)
    @JoinColumn(name = &quot;post_id&quot;)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Post post;

    @Column(nullable = false)
    private String replyContent;

}</code></pre>
<p>실제 RDB 에 입력될 데이터를 Entity Class 에 정의해줬다. Entity 의 각 필드가 RDB 의 열이  된다. @Id 로 PK 를 정의해주었고, @Column 을 달아서 열임을 인식시킴과 동시에 열의 이름 또한 커스텀 하였다. 특히 @GeneratedValue 를 사용해 id 가 어떻게 생성될 것인지 옵션을 지정해주었다.</p>
<p>Comment Entity 에는 ManyToOne 으로 연관관계를 매핑해주었으며, Post 객체가 열로 삽입되는 것을 확인할 수 있다. 또한 @Column 에 nullable 옵션을 주어 null 값이 입력되지 않도록 하였다. 여기서는 @ManyToOne을 사용해 관계를 맺어주었으므로 @OneToMany 와 다르게 cascade 옵션을 주지 않고 @OnDelete 어노테이션을 사용했다. 관련 내용은 아래의 포스팅을 참고하자.</p>
<p><del>또 Cascade 와 Column, GeneratedValue 에는 다양한 옵션들이 있는데 이거는 따로 찾아보도록 하자.</del></p>
<blockquote>
<p><strong>Cascade vs @Delete</strong> : <a href="https://gilssang97.tistory.com/71">https://gilssang97.tistory.com/71</a></p>
</blockquote>
<h4 id="repository-만들어보기">Repository 만들어보기</h4>
<pre><code class="language-java">public interface PostRepository extends JpaRepository&lt;Post, Long&gt; {

}</code></pre>
<pre><code class="language-java">public interface ReplyRepository extends JpaRepository&lt;Reply, Long&gt; {

    List&lt;Reply&gt; findAllByPostOrderByIdDesc(Post post)
    Long countByPost(Post post)
    Boolean existByPost(Post post)
}</code></pre>
<p>너무 간단해서 할 말이 없다. PostRepository 에서는 JpaRepository 가 주는 기본 Method 를 사용하기 위해 아무것도 입력하지 않았지만, ReplyRepository 에 구현해준 것처럼 <strong>각종 메소드 들을 구현해 입맛에 맞게 사용</strong>할 수 있다.</p>
<p>위에 구현한 메소드 들은 예시를 위해 간단한 메소드를 사용하였지만 아래의 포스팅에 있는 것처럼 복잡한 메소드 들도 사용이 가능하다.</p>
<p>그래도 위의 예시를 조금 더 설명해보자면, ReplyRepository 에는 FK 로 검색하기 위해 Post 객체를 넣어준 것을 확인할 수 있다. Post 객체를 넣으면 그에 해당하는 모든 Reply 객체를 가져올 수도, 몇 개인지 셀 수도, 있는지 없는지 true / false 로 확인할 수도 있도록 하였다.</p>
<p>상세 예시를 들면, 2번 게시글의 모든 댓글을 알고 싶다면 PostRepository 에서 2번 게시글에 해당하는 Post 객체를 먼저 찾고 해당 객체를 ReplyRepository 의 메소드에 파라미터로 넘기면 List, Long, Boolean 의 타입에 맞게 응답될 것이다.</p>
<blockquote>
<p><strong>[Spring JPA] Query Method</strong> : <a href="https://velog.io/@seongwon97/Spring-Boot-Query-Method">https://velog.io/@seongwon97/Spring-Boot-Query-Method</a></p>
</blockquote>
<h4 id="service-에-구현하기">Service 에 구현하기</h4>
<pre><code class="language-java">
@Service
@RequiredArgsConstructor
public class PostService {

    private final PostRepository postRepository;
    private final ReplyRepository replyRepository;

    @Transactional
    public ReponseEntity&lt;?&gt; createPost(PostRequestDto postRequestDto) {

        Post post = Post.builder()
            .title(postRequestDto.getTitle())
            .postContent(postRequestDto.getPostContent())
            .build();

        postRepository.save(post); //게시글 저장

        return ResponseEntity.ok(&quot;게시글 작성 완료!&quot;);
    }

    public ResponseEntity&lt;?&gt; getPostList() {

        List&lt;Post&gt; postList = postRepository.findAll(); //리스트 조회
        List&lt;PostResponseDto&gt; postResponseDtoList = new ArrayList&lt;&gt;();

        for (Post post : postList){
            postResponseDtoList.add(
                PostResponseDto.builder()
                    .postId(post.getId())
                    .title(post.getTitle())
                    .replyOrNot(replyRepository.existByPost(post)) //boolean Response
                    .countReply(replyRepository.countByPost(post)) //Long Response
                    .build();
        }

        return ResponseEntity.ok(postResponseDtoList);
    }

       public ResponseEntity&lt;?&gt; getPost(Long postId) {

        Post post = postRepository.findById(postId).orElseThrow(
            () -&gt; new NullPointerException(&quot;게시글이 존재하지 않습니다.&quot;)
        ); //PK 로 단건 조회

        PostResponseDto postResponseDto = PostResponseDto.builder()
            .postId(post.getId())
            .title(post.getTitle())
            .postContent(post.getPostContent())
            .replyResponseDtoList(replyRepository.findAllByPostOrderByIdDesc(post).stream()
                .map(r -&gt; ReplyResponseDto.builder()
                        .replyContent(r.getReplyContent())
                        .build())
                .collect(Collectors.toList())) //댓글 List Stream 하여 Response
            .build();

        return ResponseEntity.ok(postReponseDto);
    }

    @Transactional
    public ReponseEntity&lt;?&gt; deletePost(Long postId) {

        postRepository.deleteById(postId); //PK 로 삭제

        return ResponseEntity.ok(&quot;게시글 삭제 완료!&quot;);
    }
}</code></pre>
<p>실제 프로젝트라면 RequestDto 의 NPE 를 방지하기 위해 더 메소드를 길게 짜겠지만 예시이기 때문에 간단히 짜보았다. Service에 @RequiredArgsConstuctor 를 사용해 PostRepository 와 ReplyRepository 를 주입시키고 Post 에는 기본 메소드를 사용해 PK 값으로 조회, 삭제를 진행하였다. 또한 기본 메소드의 save 에 객체를 넣어 튜플을 생성해주도록 했다. findAll 은 모든 post 를 불러오는 것인데, 이를 List 로 저장하여 Response 해주었다.</p>
<p>잘 보면 Post를 리스트 조회한 후 ReplyRepository 의 existBy 메소드와 countBy 메소드를 사용해 해당 게시글에 댓글이 존재하는지와 댓글이 몇개 존재하는지를 함께 Response 해주었다. 그리고 게시글 단건 조회 시 builder 패턴 내에 findAll 메소드를 사용해 이를 Stream 으로 List 화 하여 Response 해주었다.</p>
<p>아주 간단한 JPA 의 예시 코드를 작성해보았는데 <strong>JDBC 와 MyBatis 에 비해 훨씬 객체 지향적이고 코드가 간결</strong>해진 것을 볼 수 있을 것이다. 또한 간단한 메소드로 구현하여 <strong>코딩 및 컴파일 시에 에러</strong>가 바로바로 보여 사전에 <strong>에러를 차단할 가능성이 매우 높아졌다.</strong> (현직자에 따르면 이게 정말 최대의 장점이라고 한다. 서비스 오픈했는데 쿼리문에서 오타로 제대로 작동하지 않는 것을 생각하면 식은땀이 절로 날 것이다...)</p>
<p>그렇다고 JPA 가 만능은 아니다. <strong>프로젝트의 규모가 커지고 복잡해지면 속도가 저하</strong>될 수 있고 <strong>복잡한 쿼리문은 사용할 수 없다.</strong> 물론 이는 @Query 를 사용해 <strong>네이티브 쿼리</strong>를 사용하거나 <strong>QueryDSL 을 사용하면 이 문제를 해결</strong>할 수 있다. 이에 대해서는 언젠가 또 포스팅을 남기도록 하겠다.</p>
<h1 id="3-mvc">3) MVC</h1>
<h2 id="3-1-mvc-란">3-1) MVC 란?</h2>
<p><strong>MVC</strong> 란 Model-View-Controller 의 약자이며, <strong>어플리케이션을 구성하는 요소를 역할에 따라 세 가지 모듈로 나누어 구분</strong>한 패턴이다. 이를 사용하는 이유는 <strong>모듈의 역할을 나누어 각각 맡은 바에 집중</strong>하여 <strong>유지보수, 확장성, 유연성을 증가</strong>시키기 위함이다.</p>
<h4 id="model">Model</h4>
<p><strong>어플리케이션의 데이터</strong>이며, <strong>모든 데이터 정보를 가공</strong>하여 가지고 있는 컴포넌트이다. 사용자가 이용하려는 <strong>모든 데이터를 가지고 있어야</strong> 하며, <strong>View 또는 Controller에 대해 어떠한 정보도 알 수 없어야</strong> 한다. 위의 JPA 예시로 본다면, Entity 와 DB, 그리고 Service 가 이에 해당한다고 볼 수 있다. </p>
<h4 id="view">View</h4>
<p><strong>시각적인 요소를 지칭</strong>하는 용어이다. <strong>Model 이 갖고 있는 데이터를 저장하면 안되</strong>고, <strong>Model 이나 Controller 에 대한 정보를 알면 안되</strong>며 <strong>단순히 표시</strong>를 해주는 역할만 가지고 있다. 위의 예시에 프론트엔드 개발이 이에 해당한다고 볼 수 있다.</p>
<h4 id="controller">Controller</h4>
<p><strong>Model 과 View 를 연결해주는 역할</strong>을 한다. 그러기 위해 <strong>Model 또는 View 의 정보를 알아야 한다.</strong> 위의 예시에서는 Controller 를 짜보지 않았지만 해당하는 Contoller 를 만들어보도록 하자.</p>
<pre><code class="language-java">@RestController //Json 형식으로 Response
@RequiredArgsConstructor //생성자 주입을 위한 Annotation
@RequestMapping(&quot;/api/post&quot;) //PostController 공통 URI
public class PostController {

    private final PostService postService;

    @PostMapping //게시글 작성
    public ResponseEntity&lt;?&gt; createPost(@RequestBody PostRequestDto postRequestDto) {
        return postService.createPost(postRequestDto);
    }

    @GetMapping //게시글 리스트 조회
    public ResponseEntity&lt;?&gt; getPostList() {
        return postService.getPostList(postRequestDto);
    }

    @GetMapping(&quot;/{postId}&quot;) //게시글 단건 조회
    public ResponseEntity&lt;?&gt; getPost(@PathVariable Long postId) {
        return postService.getPost(postId);
    }

    @DeleteMapping(&quot;/{postId}&quot;) //게시글 삭제
    public ResponseEntity&lt;?&gt; deletePost(@PathVariable Long postId) {
        return postService.deletePost(postId);
    }
}</code></pre>
<p>현재 서비스에 게시글 작성, 리스트 조회, 단건 조회, 삭제 기능이 있다고 가정했을 때의 Controller 를 작성해보았다. View 에 해당하는 프론트엔드에 Response 해주기 위해 각 기능별 URI 와 HttpMethod 에 맞도록 Annotation 을 달아주었다. Create 에는 PostMapping, Read 에는 GetMapping, Delete 에는 DeleteMapping 을 사용한 것을 볼 수 있다. 그렇게 View 에서 원하는 URI 에 접속하면 postService 의 메소드에서 각 정보를 CRUD 할 수 있도록 Response 하고 있다.</p>
<p>위에서 설명했듯이 View 와 Model 을 연결하는 역할을 하고 있는 것이다. 로컬 환경에서 개발 중이라면 <a href="http://localhost:8080/api/post">http://localhost:8080/api/post</a> 로 Post 요청을 보내면 게시글 작성(물론 Dto 양식에 맞게 보내야겠지만) 을 할 수 있고, Get 요청을 보내면 게시글 리스트 조회를 할 수 있을 것이다. 게시글 단건 조회나 게시글 삭제에는 게시글을 특정할 수 있도록 해야하는데, 이를 URI 로 받기 위해 @PathVariable 을 사용하였다. 예를 들어 2번 게시글을 읽거나 삭제하고 싶다면 <a href="http://localhost:8080/api/post/2">http://localhost:8080/api/post/2</a> 로 Get 이나 Delete 요청을 보내면 된다.</p>
<p>@PathVariable 외에 Controller 에서 파라미터를 받는 방법은 아래의 포스팅을 참고하도록 하자.</p>
<blockquote>
<p><strong>스프링 controller에서 파라미터를 받는 다양한 방법</strong> : <a href="https://velog.io/@shson/%EC%8A%A4%ED%94%84%EB%A7%81-controller%EC%97%90%EC%84%9C-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%EB%A5%BC-%EB%B0%9B%EB%8A%94-%EB%8B%A4%EC%96%91%ED%95%9C-%EB%B0%A9%EB%B2%95-RequestParam-RequestBody-PathVariable">https://velog.io/@shson/%EC%8A%A4%ED%94%84%EB%A7%81-controller%EC%97%90%EC%84%9C-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%EB%A5%BC-%EB%B0%9B%EB%8A%94-%EB%8B%A4%EC%96%91%ED%95%9C-%EB%B0%A9%EB%B2%95-RequestParam-RequestBody-PathVariable</a></p>
</blockquote>
<blockquote>
<p>참고 : 
SQL : <a href="https://ko.wikipedia.org/wiki/SQL">https://ko.wikipedia.org/wiki/SQL</a>
[DB] ORM이란 : <a href="https://gmlwjd9405.github.io/2019/02/01/orm.html">https://gmlwjd9405.github.io/2019/02/01/orm.html</a>
[데이터베이스] ORM이란? : <a href="https://hanamon.kr/orm%EC%9D%B4%EB%9E%80-nodejs-lib-sequelize-%EC%86%8C%EA%B0%9C/">https://hanamon.kr/orm%EC%9D%B4%EB%9E%80-nodejs-lib-sequelize-%EC%86%8C%EA%B0%9C/</a>
[DB] ORM (Object Relational Mapping) 사용 이유, 장단점 : <a href="https://eun-jeong.tistory.com/31">https://eun-jeong.tistory.com/31</a>
[Spring Boot] ORM과 JDBC, JPA : <a href="https://programming-workspace.tistory.com/58">https://programming-workspace.tistory.com/58</a>
[10분 테코톡] 범고래, 소주캉의 JDBC, SQL Mapper, ORM : <a href="https://www.youtube.com/watch?v=NFK9qLWpujY&amp;list=WL&amp;index=1">https://www.youtube.com/watch?v=NFK9qLWpujY&amp;list=WL&amp;index=1</a>
SQL의 개념과 SQL로 데이터베이스를 정의하는 법 : <a href="https://www.youtube.com/watch?v=c8WNbcxkRhY&amp;list=WL&amp;index=30">https://www.youtube.com/watch?v=c8WNbcxkRhY&amp;list=WL&amp;index=30</a>
백엔드에서 사용되는 데이터베이스(database) 기본 개념 : <a href="https://www.youtube.com/watch?v=aL0XXc1yGPs&amp;list=WL&amp;index=17">https://www.youtube.com/watch?v=aL0XXc1yGPs&amp;list=WL&amp;index=17</a>
[Java] JDBC를 사용한 데이터베이스 연동 : <a href="https://devlog-wjdrbs96.tistory.com/139">https://devlog-wjdrbs96.tistory.com/139</a>
MyBatis와 JPA의 차이, MyBatis보다 JPA를 사용해야 하는 이유 : <a href="https://mangkyu.tistory.com/20">https://mangkyu.tistory.com/20</a>
[Spring JPA] JPA 란? : <a href="https://dbjh.tistory.com/77">https://dbjh.tistory.com/77</a>
JPA VS JDBC : <a href="https://sowon-dev.github.io/2021/03/22/210323jpaVSjdbc/">https://sowon-dev.github.io/2021/03/22/210323jpaVSjdbc/</a>
Spring MVC 동작 구조 : <a href="https://ss-o.tistory.com/160">https://ss-o.tistory.com/160</a>
Controller, Service, Repository 가 무엇일까? : <a href="https://velog.io/@jybin96/Controller-Service-Repository-%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C">https://velog.io/@jybin96/Controller-Service-Repository-%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</a>
스프링 controller에서 파라미터를 받는 다양한 방법 : <a href="https://velog.io/@shson/%EC%8A%A4%ED%94%84%EB%A7%81-controller%EC%97%90%EC%84%9C-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%EB%A5%BC-%EB%B0%9B%EB%8A%94-%EB%8B%A4%EC%96%91%ED%95%9C-%EB%B0%A9%EB%B2%95-RequestParam-RequestBody-PathVariable">https://velog.io/@shson/%EC%8A%A4%ED%94%84%EB%A7%81-controller%EC%97%90%EC%84%9C-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%EB%A5%BC-%EB%B0%9B%EB%8A%94-%EB%8B%A4%EC%96%91%ED%95%9C-%EB%B0%A9%EB%B2%95-RequestParam-RequestBody-PathVariable</a>
[Spring JPA] Query Method : <a href="https://velog.io/@seongwon97/Spring-Boot-Query-Method">https://velog.io/@seongwon97/Spring-Boot-Query-Method</a>
RDB vs NoSQL : <a href="https://www.youtube.com/watch?v=4PxBaojUnU4&amp;list=WL&amp;index=12">https://www.youtube.com/watch?v=4PxBaojUnU4&amp;list=WL&amp;index=12</a>
<a href="https://www.youtube.com/watch?v=Q_9cFgzZr8Q">https://www.youtube.com/watch?v=Q_9cFgzZr8Q</a>
SQL로 DB에 데이터를 추가(insert)하고 수정(update)하고 삭제(delete)하는 방법 : <a href="https://www.youtube.com/watch?v=mgnd5JWeCK4&amp;list=WL&amp;index=22">https://www.youtube.com/watch?v=mgnd5JWeCK4&amp;list=WL&amp;index=22</a>
SQL로 데이터 조회하기 : <a href="https://www.youtube.com/watch?v=dTBwgWMUguE&amp;list=WL&amp;index=6">https://www.youtube.com/watch?v=dTBwgWMUguE&amp;list=WL&amp;index=6</a>
SQL Tutorial : <a href="https://www.w3schools.com/sql/default.asp">https://www.w3schools.com/sql/default.asp</a>
MYSQL 데이터 타입 : <a href="https://blog.martinwork.co.kr/mysql/2020/01/17/mysql-data-type.html">https://blog.martinwork.co.kr/mysql/2020/01/17/mysql-data-type.html</a>
MySQL 데이터 타입 Java 데이터 타입 비교 : <a href="https://goldfishhead.tistory.com/2">https://goldfishhead.tistory.com/2</a>
SQL의 개념과 SQL로 데이터베이스를 정의하는 법 : <a href="https://www.youtube.com/watch?v=c8WNbcxkRhY&amp;list=WL&amp;index=31">https://www.youtube.com/watch?v=c8WNbcxkRhY&amp;list=WL&amp;index=31</a>
연관관계 매핑 기초 : <a href="https://catsbi.oopy.io/ed9236a0-6521-471d-8a0d-b852147b5980">https://catsbi.oopy.io/ed9236a0-6521-471d-8a0d-b852147b5980</a>
JPA 연관 관계 한방에 정리 : <a href="https://jeong-pro.tistory.com/231">https://jeong-pro.tistory.com/231</a>
[DB] 📈 데이터 모델링 개념 &amp; ERD 다이어그램 그리기 💯 총정리 : <a href="https://inpa.tistory.com/entry/DB-%F0%9F%93%9A-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AA%A8%EB%8D%B8%EB%A7%81-1N-%EA%B4%80%EA%B3%84-%F0%9F%93%88-ERD-%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8">https://inpa.tistory.com/entry/DB-%F0%9F%93%9A-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AA%A8%EB%8D%B8%EB%A7%81-1N-%EA%B4%80%EA%B3%84-%F0%9F%93%88-ERD-%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8</a>
백엔드에서 사용되는 데이터베이스(database) 기본 개념 : <a href="https://www.youtube.com/watch?v=aL0XXc1yGPs&amp;list=WL&amp;index=18">https://www.youtube.com/watch?v=aL0XXc1yGPs&amp;list=WL&amp;index=18</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해99 3주차 WIL - IOC / DI / Bean]]></title>
            <link>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-2%EC%A3%BC%EC%B0%A8-WIL-exoe9yq9</link>
            <guid>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-2%EC%A3%BC%EC%B0%A8-WIL-exoe9yq9</guid>
            <pubDate>Sun, 09 Oct 2022 07:00:41 GMT</pubDate>
            <description><![CDATA[<p>솔직히 이번 포스팅을 어느 정도 수준까지 할까 굉장히 고민이 많았다. 그러나 어차피 공부하고 포스팅해두면 내꺼가 된다는 생각으로 부족하지만 최선을 다해서 작성해봤다!</p>
<h1 id="1-ioc--di">1) IOC / DI</h1>
<h2 id="1-1-ioc-란">1-1) IOC 란?</h2>
<h3 id="ioc-의-뜻">IOC 의 뜻</h3>
<p><strong>IOC 란</strong> Inversion Of Control 의 약자로, <strong>&#39;제어의 역전&#39;</strong> 이라는 뜻이다. 쉽게 말해 프로그래머가 <strong>직접 제어(혹은 관리) 하던 것</strong>을 <strong>외부에서 제어하도록 만든다</strong>는 뜻을 갖고 있다.</p>
<h3 id="ioc-를-왜-하는데">IOC 를 왜 하는데?</h3>
<p>다른 포스팅들을 보니 &#39;IOC 의 장점 때문에 사용한다&#39;, &#39;DIP 원칙 (의존성 역전의 원칙 - Dependency Inversion Priniple) 때문에 사용한다&#39; 라는 내용을 봤다. 그러나 <strong>IOC 의 장점과 DIP 가 주는 &#39;가치&#39;</strong> 가 무엇인지에 대해서는 명확한 것을 알 수 없었다.</p>
<p>결국 모든 포스팅의 내용을 종합해보니 <strong>&#39;변경에 유연한 코드를 짜기 위해서 IOC 를 사용한다.&#39;</strong> 라고 답을 내릴 수 있었고, 실제 코드로 보았을 때도 그런 것을 확인할 수 있었다.</p>
<blockquote>
<p>*<em>IOC 의 장점 : *</em></p>
</blockquote>
<ul>
<li>객체 간 결합도를 낮춘다.</li>
<li>유연한 코드 작성 가능</li>
<li>가독성 증진</li>
<li>코드 중복 방지</li>
<li>유지 보수 용이</li>
</ul>
<blockquote>
<p><strong>DIP(의존성 역전의 원칙, Dependency Inversion Principle)</strong> - 상위 레벨의 모듈은 절대 하위 레벨 모듈에 의존하지 않는다. 둘 다 추상화에 의존해야 한다.</p>
</blockquote>
<h3 id="어떤-제어를-역전시키는데">어떤 제어를 역전시키는데?</h3>
<p><strong>객체 생명주기나 흐름을 역전</strong>시키는 것이다. 자세한 것은 아래의 코드로 나타내보겠다.</p>
<pre><code class="language-java">public class A {
    private B b;

    public A() {
        this.b = new B();
    }
}</code></pre>
<p>라는 코드가 있다고 해보자. 이 코드의 <strong>클래스 A 는 B 라는 객체를 필드</strong>로 갖고 있는데, 생성자에서 어떠한 매개변수도 받지 않기 때문에 B 객체를 직접 생성해 필드를 초기화하고 있다.</p>
<pre><code class="language-java">public class A {
    private B b;

    public A(B b) {
        this.b = b;
    }
}</code></pre>
<p>코드를 이렇게 바꾸면 <strong>클래스 A 는 B 객체를 매개변수로 받아 이를 필드로 초기화</strong>하고 있다. 이럼으로써 <strong>A 클래스는 B의 변화에 훨씬 유연하게 대처</strong>할 수 있게 되고, <strong>B 또한 상속, 구체화 등을 통해 다형성 있는 코드</strong>를 짤 수 있게 되었다.</p>
<p>갑자기 이러한 말이 생각난다. <del>김춘수 선생님은 알고보니 프로그래머 출신이었을지도 모른다!</del></p>
<blockquote>
<p><strong>&quot;내가 그의 이름을 불러 주었을 때 그는 나에게로 와서 꽃이 되었다.&quot;</strong> - 김춘수, &lt;꽃&gt; - </p>
</blockquote>
<h2 id="1-2-di-란">1-2) DI 란?</h2>
<h3 id="di-의-뜻">DI 의 뜻</h3>
<p><strong>DI 란</strong> Dependency Injection 의 약자로, <strong>&#39;의존성 주입&#39;</strong> 이라는 뜻이다. 이는 IOC 와 연관이 있긴 하지만, 명백히 IOC 와는 다른 개념이며, <strong>IOC 를 구현하는 하나의 디자인 패턴</strong> 중 하나이다. 사실 위에서 코드로 나타낸 것이 DI 를 아주 간단히 표현해본 것이라고 할 수 있다.</p>
<h3 id="di-예시">DI 예시</h3>
<p>이를 간단한 예시로 조금 더 구체적으로 설명해보도록 하겠다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/694bc480-c8f4-49c4-9c15-33daf9aba36a/image.png" alt=""></p>
<p>혹시 이 게임을 알고 있는가...? 좋은 피자 위대한 피자라는 게임인데 <del>진상</del> 손님들의 <del>개같고</del> 다양한 요구 사항에 맞춰 피자를 만드는 <del>인성 파탄용</del> 타이쿤류 게임이다. 위의 손님이 말한 것처럼 페퍼로니 없는 페퍼로니 피자를 만든다 생각하고 코드를 짜보자.</p>
<pre><code class="language-java">public class PepperoniPizza{
    private OriginalDough originalDough;
    private ItalianPepperoni italianPepperoni;
    private MozzarellaCheese mozzarellaCheese;
    private KoreanBeef koreanbeef;

    public PepperoniPizza(){
        this.originalDough = new OriginalDough();
        this.italianPepperoni = new ItalianPepperoni();
        this.mozzarellaCheese = new MozzarellaCheese();
        this.koreanBeef = new KoreanBeef();
    }

    public void deletePepperoni(){
        this.italianPepperoni = null;
    }

    public ItalianPepperoni getItalianPepperoni(){
        return italianPepperoni;
    }

    public static void main(String[] args) {
        PepperoniPizza pepperoniPizza = new PepperoniPizza();
        System.out.println(pepperoniPizza.getItalianPepperoni());

        pepperoniPizza.deletePepperoni();
        System.out.println(pepperoniPizza.getItalianPepperoni());
    }
}</code></pre>
<p><strong>전혀 DI 가 되지 않아 IOC 가 되지 않은 코드의 모습</strong>이다. 필드를 보니 페퍼로니 객체에는 오리지날 도우, 이탈리안 페퍼로니, 모짜렐라 치즈, 한우가 들어갔다는 것을 알 수 있다. 그러나 위의 손님과 같은 요구사항을 들어주기 위해 main 메소드에서 <strong>정상적인 페퍼로니 피자 객체</strong>를 만들어준 후 <strong>deletePepperoni() 메소드를 통해 이탈리안 페퍼로니를 null 로</strong> 만드는 것을 볼 수 있다. </p>
<p>물론 동작하는 데에는 크게 지장이 있진 않지만, 어딘가 <strong>다형성이 무너져 있으며</strong>, <strong>불필요한 메소드가 들어가 있는 것</strong>으로 보인다. </p>
<p>이게 실제 세상이었다면 이탈리안 페퍼로니로 피자를 만들고 빼는 <strong>불필요한 시간과 금전적 손실</strong>을 입게 되었을 것이다.</p>
<pre><code class="language-java">public class Pizza {
    private Dough dough;
    private Pepperoni pepperoni;
    private Cheese cheese;
    private Meat meat;

    public Pizza(Dough dough, Pepperoni pepperoni, Cheese cheese, Meat meat){
        this.dough = dough;
        this.pepperoni = pepperoni;
        this.cheese = cheese;
        this.meat = meat;
    }

    public Pepperoni getPepperoni() {
        return pepperoni;
    }

    public Cheese getCheese() {
        return cheese;
    }

    public static void main(String[] args) {
        Pizza pizza = new Pizza(new OriginalDough(), null, null, new KoreanBeef());

        System.out.println(pizza.getPepperoni());
        System.out.println(pizza.getCheese());
    }
}</code></pre>
<p>코드를 이렇게 바꾸면 <strong>생성자를 통해</strong> 도우, 페퍼로니, 치즈, 고기를 선택하고 넣을 수 있게 된다. 진상 손님이 페퍼로니와 치즈까지 빼달라고 하면 <strong>null 로 어떠한 값을 넣지 않아도 되며</strong>, Dough 를 <strong>extend / implement</strong> 받은 new OriginalDough() 객체와 Meat 를 extend / implement 받은 new KoreanBeef() 객체 마저 넣으며 *<em>아주 다양한 피자를 만들 수 *</em>있게 된다.</p>
<p>이렇듯 <strong>생성자에 객체를 넣어준 것으로 의존 관계를 역전</strong>시킴으로서 <strong>유연하게 대처</strong>할 수 있게 되었다. 이처럼 <strong>객체 외부</strong>인 생성자에서 <strong>의존성을 주입</strong>시켜준 것이라고 하여 의존성 주입이라 말하며, 이것이 DI 패턴 중 하나이다.</p>
<h2 id="1-3-생성자-주입을-사용해야하는-이유">1-3) 생성자 주입을 사용해야하는 이유</h2>
<h3 id="다양한-di-의-방법">다양한 DI 의 방법</h3>
<p>DI 패턴이냐, Spring 의 DI 냐로 조금 나뉘는 것 같은데 어차피 필자도 Java, Spring 스택이기 때문에 Spring 을 기반으로 설명하도록 하겠다. <del>※ DI 패턴에는 생성자 주입, Setter 주입, 인터페이스 주입으로 나뉘는 것 같고, Spring 의 DI 는 필드 주입, Setter 주입, 생성자 주입으로 나뉘는 것 같다.</del></p>
<h4 id="필드-주입">필드 주입</h4>
<p>가장 간단하지만 <strong>가장 추천되지 않는 방법</strong>이다. 주입할 필드 위에 @Autowired 어노테이션을 붙이는 방법이다. @Autowired 어노테이션을 붙이면, 스프링이 자동으로 의존성을 주입해준다.</p>
<pre><code class="language-java">@Controller
public class MemberController {

    @Autowired
    private MemberService memberService;
}</code></pre>
<p>필드 주입이 추천되지 않는 이유는 이것이 갖는 <strong>치명적인 단점</strong> 때문이다. 필드 주입을 하게 되면 외부 접근이 불가능하다. 해당 필드를 초기화하는 생성자도, 필드에 값을 넣어주는 setter 도 없기 때문에 <strong>필드에 값을 주입해줄 방법이 없다.</strong> </p>
<h4 id="setter-주입">Setter 주입</h4>
<p><strong>주입받는 객체가 변경될 가능성</strong>이 있는 경우에 사용한다고 한다. 그러나 실제로 <strong>변경이 필요한 경우는 극히 드물다.</strong> 그리고 Setter 주입은 주입되지 않은 의존성을 호출할 경우 <strong>NPE (Nullpoint Exception) 이 발생할 수 있기 때문에</strong> 역시나 <strong>추천되는 방법은 아니다.</strong></p>
<pre><code class="language-java">@Controller
public class MemberController {

    private MemberService memberService;

    @Autowired
    public void setMemberService(MemberService memberService){
        this.memberService = memberService;
    }
}</code></pre>
<h4 id="생성자-주입">생성자 주입</h4>
<p><strong>Spring 에서 공식적으로 추천하는 방법</strong>이고, 어떤 블로그 글을 봐도 하나 같이 입을 모아 추천하는 방법이 생성자 주입이다. 물론 나도 위의 코드에서 생성자 주입을 사용했다.</p>
<pre><code class="language-java">@Controller
public class MemberController {

    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService){
        this.memberService = memberService;
    }
}</code></pre>
<p>왜 생성자 주입을 사용해야 하는지 아래의 포스팅에서 자세히 설명하도록 하겠다.</p>
<h3 id="생성자-주입을-사용해야하는-이유">생성자 주입을 사용해야하는 이유</h3>
<blockquote>
<ul>
<li>객체의 불변성 확보</li>
</ul>
</blockquote>
<ul>
<li>테스트 코드 작성 용이</li>
<li>final 키워드 작성 및 Lombok 과의 결합</li>
<li>스프링에 비침투적인 코드 작성</li>
<li>순환 참조 에러 방지</li>
</ul>
<p>생성자 주입을 사용할 경우, <strong>의존성 주입이 최초 빈 생성 시 1회만 호출됨을 보장</strong>할 수 있다. (애초에 생성자는 1회만 호출되기 때문) 그렇기 때문에 변경의 가능성을 배제하고 <strong>불변성이 보장</strong>된다.</p>
<p>생성자 주입을 사용하면 컴파일 시점에 객체를 주입받아 <strong>테스트 코드를 작성</strong>할 수 있으며, 주입하는 객체가 누락된 경우 컴파일 시점에 오류를 발견할 수 있다.</p>
<p>또 필드 객체에 <strong>final 키워드를 써서 런타임 시 불변성을 보장</strong>할 뿐 아니라 <strong>Lombok 과 결합</strong>해 코드를 간결하게 짤 수 있다.</p>
<pre><code class="language-java">@Controller
@RequiredArgsConstructor //final 이 붙은 필드를 매개변수로 하는 생성자를 자동으로 생성해준다.
public class MemberController {

    private final MemberService memberService;

    //우리 눈에는 보이지 않지만, 코드 상 생략되었을 뿐 생성자가 있는 것과 같다!
}</code></pre>
<p>사실 스프링을 사용하면서 스프링에 비침투적인 코드를 짤 수 있을까 싶긴 해서 이게 장점인지는 모르겠지만...! 어쨌든 우리가 사용하는 <strong>프레임워크는 언제든 바뀔 수 있기 때문</strong>에 장점이라고(?) 하는 것 같다.</p>
<pre><code class="language-java">import org.springframework.beans.factory.annotation.Autowired;
// 스프링 의존성이 import 되어 스프링에 의존도가 높아진다!

@Controller
public class MemberController {

    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService){
        this.memberService = memberService;
    }
}</code></pre>
<p>마지막으론 <del>프로그래머의 실수로</del> <strong>순환참조가 이뤄졌을 때 컴파일 시점에 오류</strong>를 띄워준다는 것이 장점이라고 한다!</p>
<p>이 다섯 가지 이유 중에서 가장 그럴듯한 이유는 객체의 불변성 확보와 테스트 코드 작성 용이, Lombok 과 결합 이 세 가지인 것 같다.</p>
<p>특히 필드 주입 혹은 Setter 주입은 객체 생성시점에 스프링 빈이 없는 경우 null 인 상태로 존재하게 되는데, 생성자 주입 방식과 final 키워드를 사용해 <strong>무조건 의존 관계가 주입되도록 강제</strong>함으로써 <strong>NPE 를 방지</strong>할 수 있다.</p>
<p>테스트 코드 작성이 용이하다는 것은 워낙에 TDD 가 중요하고 실무에서 많이 쓰기 때문이다. <del>물론 많이 써보진 못했지만...</del> 그리고 <strong>Lombok 의 ArgsConstructor 는 정말 편하다.</strong> 생각보다 개발을 하면서 의존성을 많이 주입해 줘야 할 때도 있고, 필드를 바꿔줘야 할 때도 많은데, 그때마다 생성자를 바꿔야 하는 게 얼마나 피곤한 일인지 모른다.</p>
<h1 id="2-bean">2) Bean</h1>
<h2 id="2-1-bean-이란">2-1) Bean 이란?</h2>
<h3 id="bean-의-뜻">Bean 의 뜻</h3>
<p><strong>Bean</strong> 이란 <strong>Spring IOC Container 에서 관리되는 객체</strong>를 말한다. 우리가 특정 객체를 <strong>Bean 으로 등록</strong>함으로써 <strong>Spring IOC Container 가</strong> Bean 의 생명주기 관리, 관계설정 등의 <strong>제어 작업을 총괄</strong>한다.</p>
<pre><code class="language-java">@Controller //Controller Bean 등록 Annotation
@RequiredArgsConstructor //생성자 주입으로 DI
public class MemberController {

    private final MemberService memberService;
}

@Service //Service Bean 등록 Annotation
@RequiredArgsConstructor 
public class MemberService {

    private final MemberRepository memberRepository;
}</code></pre>
<p>위의 코드의 <strong>어노테이션</strong>을 달아주는 것만으로도 MemberController 와 MemberService 의 <strong>Bean 등록과 DI</strong> 가 끝났다. 아주 간단하지 않은가? Bean 등록 방법은 아래에서 다시 알아보도록 하자.</p>
<h3 id="bean-과-싱글톤">Bean 과 싱글톤</h3>
<p><strong>Bean은 싱글톤으로 관리</strong>된다. 스프링에 <strong>여러 번 빈을 요청</strong>하더라도 <strong>매번 동일한 객체</strong>를 돌려준다는 뜻이다. <strong>많은 양의 트래픽에 대처하기 위해</strong>서는 싱글톤으로 관리하는 것이 더 좋기 때문이다. </p>
<p>예를 들어 요청 1번에 4개의 객체가 생성되고 1초에 100 번 요청이 온다고 하면 1초에 400개의 새로운 객체가 생성되어야 한다는 뜻이 된다. 그렇기 때문에 빈을 싱글톤으로 관리하여 <strong>1개의 요청이 왔을 때 여러 스레드가 빈을 공유해 처리</strong>하도록 한다.</p>
<h2 id="2-2-bean-등록하는-방법">2-2) Bean 등록하는 방법</h2>
<p>Bean 을 등록하는 방법은 <strong>수동으로 등록하는 @Bean, @Configuration</strong> 을 사용하는 방법과 <strong>Component Scan 으로 자동으로 등록하는 @Component</strong> 를 사용하는 방법이 있다.</p>
<h3 id="bean-configuration">@Bean, @Configuration</h3>
<pre><code class="language-java">@Configuration
public class QueryDslConfiguration {

    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory(){
        return new JPAQueryFactory(entityManager);
    }
}</code></pre>
<p>항해 실전 프로젝트를 하며 실제로 사용했던 예시를 가져와봤다. 설정 클래스에 @Configuration 어노테이션을 달아주고 메소드에 Bean 을 달아주어 수동으로 Bean 을 등록한 모습이다.</p>
<p>스프링 컨테이너는 <strong>@Configuration 이 붙어있는 클래스를 Bean 으로 등록</strong>해두고 해당 클래스를 파싱하여 <strong>@Bean 이 있는 메소드를 찾아서 빈을 생성</strong>해준다.</p>
<p>수동으로 Bean 을 등록해줘야 하는 상황은 아래와 같다. <del>고 한다.</del> 위의 예에서 Bean 을 수동으로 등록해준 이유는 Spring 외부 프레임워크인 QueryDSL 을 사용하기 위함이다.</p>
<blockquote>
<ul>
<li>개발자가 직접 제어가 불가능한 라이브러리를 활용할 때</li>
</ul>
</blockquote>
<ul>
<li>어플리케이션 전범위적으로 사용되는 클래스를 등록할 때</li>
<li>다형성을 활용하여 여러 구현체를 등록해주어야 할 때</li>
</ul>
<p>이 방법을 쓸 때는 @Configuration 안에 @Bean 을 사용해야 싱글톤이 보장된다고 하는데, 그 이유는 아래의 포스팅에서 살펴보도록 하자.</p>
<blockquote>
<p><strong>@Configuration 안에 @Bean을 사용해야 하는 이유 :</strong> <a href="https://mangkyu.tistory.com/234">https://mangkyu.tistory.com/234</a></p>
</blockquote>
<h3 id="component">@Component</h3>
<p>스프링에서는 <strong>Component Scan</strong> 을 사용해 <strong>@Component 어노테이션이 있는 클래스</strong>들을 찾아 <strong>자동으로 Bean 등록</strong>을 해준다. <strong>@Controller, @Service, @Repository, @Configuration</strong> 등이 그 예시이며, 어노테이션을 잘 살펴보면 아래의 코드와 같이 <strong>@Component 어노테이션이 숨어</strong>있다.</p>
<pre><code class="language-java">@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // @Configuration 안에 @Component이 숨어 있다.
public @interface Configuration {

}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // @Controller 안에 @Component이 숨어 있다.
public @interface Controller {

}</code></pre>
<p>그랬기 때문에 아래의 코드에서 MemberService 와 MemberController 를 Bean 으로 등록하고, DI 를 사용할 수 있었다.</p>
<pre><code class="language-java">@Controller //Controller Bean 등록 Annotation
@RequiredArgsConstructor //생성자 주입으로 DI
public class MemberController {

    private final MemberService memberService;
}

@Service //Service Bean 등록 Annotation
@RequiredArgsConstructor 
public class MemberService {

    private final MemberRepository memberRepository;
}</code></pre>
<p>위에서 살펴본 것처럼 <strong>수동으로 Bean 을 등록해줘야 할 때를 제외</strong>하곤 스프링에선 <strong>자동 빈 등록 방식을 권장</strong>한다.</p>
<h3 id="autowired-는-뭐였더라">@Autowired 는 뭐였더라?</h3>
<p><strong>@Autowired</strong> 는 <strong>DI 를 시켜주는 어노테이션</strong>이므로 Bean 을 등록시켜주는 어노테이션은 아니다. 그래서 필드 주입, Setter 주입, 생성자 주입 등에 사용하는 어노테이션이다. 다만, 생성자가 하나일 경우 @Autowired 를 생략할 수 있기 때문에 쓰지 않는 경우도 많다.</p>
<p>추가적으로 Bean 으로 등록되지 않은 객체에 @Autowired 로 DI 시키려는 경우 에러가 발생할 수 있다. 또한 같은 타입의 Bean 이 여러 개일 경우 @Qualifier 를 사용해 빈을 찾도록 하거나 @Primary 를 사용해 Bean 의 우선순위를 부여할 수 있는 기능도 있지만 여기서는 다루지 않도록 하겠다. 궁금한 독자를 위해 아래의 포스팅을 남기도록 하겠다.</p>
<blockquote>
<p><strong>@Autowired 빈 탐색 전략과 @Qualifier와 @Primary :</strong> <a href="https://mangkyu.tistory.com/148">https://mangkyu.tistory.com/148</a>
<strong>@Autowired 란 무엇인가? :</strong> <a href="https://devlog-wjdrbs96.tistory.com/166">https://devlog-wjdrbs96.tistory.com/166</a></p>
</blockquote>
<h2 id="2-3-bean-의-생명주기">2-3) Bean 의 생명주기</h2>
<p>일반적으로 IOC Container 에서 Spring 이 자체적으로 Bean 을 관리하며 생명주기(생성 및 소멸) 도 관리한다. Bean 의 생명주기는 아래와 같다.</p>
<blockquote>
<p><strong>스프링 컨테이너 생성 → 스프링 빈 생성 → 의존관계 주입 →  초기화 콜백 → 사용 → 소멸 전 콜백 → 스프링 종료</strong></p>
</blockquote>
<p>필요하지 않은 경우도 있겠으나, 예를 들어 애플리케이션이 시작될 때, 미리 DB 서버를 연결시켜두거나 네트워크 소켓처럼 애플리케이션 시작 시점에 미리 연결하고, 애플리케이션이 종료될 때 안전하게 종료해줘야 할 때 콜백 메소드를 사용해 Bean 의 초기화 및 소멸 시점을 알려줘야 한다.</p>
<p>Spring 은 의존관계 주입 후에 초기화 콜백을 사용하는데, 이때 아래의 방법을 이용하면 초기화 시점을 알려줄 수 있다. 또한 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 주어 안전하게 종료 작업을 진행할 수 있다.</p>
<p>이처럼 Bean 초기화 및 소멸 시점의 생명주기를 관리하는 방법에는 세 가지 방법이 있지만, 여기서는 가장 추천되는 방법인 @PostConstruct 와 @PreDestroy 에 대해서만 알아보고자 한다. 다른 방법에 대한 내용은 아래의 포스팅을 참고하도록 하자.</p>
<blockquote>
<p><strong>SPRING - 빈 생명 주기</strong> : <a href="https://imspear.tistory.com/170">https://imspear.tistory.com/170</a></p>
</blockquote>
<pre><code class="language-java">@Service
@RequiredArgsConstructor
public class S3UploadService {

    private final AmazonS3 s3Client;

    @Value(&quot;${cloud.aws.credentials.access-key}&quot;)
    private String accessKey;

    @Value(&quot;${cloud.aws.credentials.secret-key}&quot;)
    private String secretKey;

    @Value(&quot;${cloud.aws.s3.bucket}&quot;)
    private String bucket;

    @Value(&quot;${cloud.aws.region.static}&quot;)
    private String region;

    @PostConstruct
    public AmazonS3Client amazonS3Client() {
        BasicAWSCredentials awsCredential = new BasicAWSCredentials(accessKey, secretKey);
        return (AmazonS3Client) AmazonS3ClientBuilder.standard()
                .withRegion(region)
                .withCredentials(new AWSStaticCredentialsProvider(awsCredential))
                .build();
    }</code></pre>
<p>이번에도 항해 실전 프로젝트를 하며 실제로 사용했던 예시를 가져와봤다. AWS S3 에 이미지를 업로드 시키는 서비스를 만들기 위한 클래스이다. AmzonS3 객체를 필드로 두고, 해당 객체에 AccessKey, SecretKey, Region 등을 넣어 S3UploadService 가 사용되기 전에 초기화되도록 설정하였다. 실제로 위의 방법은 AWS 측에서도 권장하는 방법이다. 추가로 NoSQL 인 CassandraDB 에 DB 연결 후 테스트 데이터를 삽입하는 방법도 아래에 있으니 참고하면 좋을 듯 하다.</p>
<blockquote>
<p><strong>AWS 기반 Spring Boot 애플리케이션 개발 시작하기</strong> : <a href="https://aws.amazon.com/ko/blogs/korea/getting-started-with-spring-boot-on-aws/">https://aws.amazon.com/ko/blogs/korea/getting-started-with-spring-boot-on-aws/</a>
<strong>Connecting to a NoSQL Database with Spring Boot</strong> : <a href="https://www.baeldung.com/spring-boot-nosql-database">https://www.baeldung.com/spring-boot-nosql-database</a></p>
</blockquote>
<pre><code class="language-java">@TestConfiguration
public class TestRedisConfiguration {

    private RedisServer redisServer;

    public TestRedisConfiguration(RedisProperties redisProperties) {
        this.redisServer = new RedisServer(redisProperties.getRedisPort());
    }

    @PostConstruct
    public void postConstruct() {
        redisServer.start();
    }

    @PreDestroy
    public void preDestroy() {
        redisServer.stop();
    }
}</code></pre>
<p>위의 코드는 RedisServer 를 테스트하기 위한 코드인데, 실제 Redis 서버를 중지하지 않고 테스트를 하기 위해 짜놓은 코드이다. 이런 식으로 @PostConstruct 나 @Predestroy 를 사용할 수 있다.</p>
<blockquote>
<p><strong>Embedded Redis Server with Spring Boot Test</strong> : <a href="https://www.baeldung.com/spring-embedded-redis">https://www.baeldung.com/spring-embedded-redis</a></p>
</blockquote>
<p>@PostConstuct 나 @PreDestroy 는 아래의 장점을 갖고 있지만 외부 라이브러리를 적용하지 못한다. 따라서, 그럴 때는 @Bean 의 initMethod 와 destroyMethod 옵션을 사용해보자.</p>
<blockquote>
<p><strong>@PostConstruct 와 @PreDestroy 의 장점</strong> : </p>
</blockquote>
<ul>
<li>어노테이션 하나로 관리 가능</li>
<li>컴포넌트 스캔에 적용이 용이함</li>
</ul>
<blockquote>
<p><strong>외부 라이브러리 사용 시 @Bean 사용법</strong> : 
</br></p>
</blockquote>
<pre><code class="language-java">@Configuration
static class LifeCycleConfig{
    @Bean(initMethod = &quot;init&quot;, destroyMethod = &quot;close&quot;)
    public NetworkClient networkClient(){
        NetworkClient networkClient = new NetworkClient();
        networkClient.setUrl(&quot;&lt;http://hello-spring.dev&gt;&quot;);
        return networkClient;
    }
}</code></pre>
<blockquote>
<p><strong>참고 자료</strong> : <a href="https://isoomni.tistory.com/entry/TISPRING-IOC-DI-%EC%A0%95%EC%9D%98-%EC%9E%A5%EC%A0%90">https://isoomni.tistory.com/entry/TISPRING-IOC-DI-%EC%A0%95%EC%9D%98-%EC%9E%A5%EC%A0%90</a>
<a href="https://velog.io/@gillog/Spring-DIDependency-Injection">https://velog.io/@gillog/Spring-DIDependency-Injection</a>
<a href="https://velog.io/@ohzzi/Spring-DIIoC-IoC-DI-%EA%B7%B8%EA%B2%8C-%EB%AD%94%EB%8D%B0">https://velog.io/@ohzzi/Spring-DIIoC-IoC-DI-%EA%B7%B8%EA%B2%8C-%EB%AD%94%EB%8D%B0</a>
<a href="https://velog.io/@ohzzi/Spring-DIIoC-%EC%8A%A4%ED%94%84%EB%A7%81%EC%9D%98-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85">https://velog.io/@ohzzi/Spring-DIIoC-%EC%8A%A4%ED%94%84%EB%A7%81%EC%9D%98-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85</a>
<a href="https://velog.io/@ohzzi/Spring-DIIoC-%EC%8A%A4%ED%94%84%EB%A7%81%EC%9D%98-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-2-DIIoC-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%99%80-%EC%9A%B0%EC%84%A0%EC%88%9C%EC%9C%84">https://velog.io/@ohzzi/Spring-DIIoC-%EC%8A%A4%ED%94%84%EB%A7%81%EC%9D%98-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-2-DIIoC-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%99%80-%EC%9A%B0%EC%84%A0%EC%88%9C%EC%9C%84</a>
<a href="https://biggwang.github.io/2019/08/31/Spring/IoC,%20DI%EB%9E%80%20%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C/">https://biggwang.github.io/2019/08/31/Spring/IoC,%20DI%EB%9E%80%20%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C/</a>
<a href="https://catsbi.oopy.io/c7f85a3d-fe55-40b5-85af-7ec0b99b27c3#c7f85a3d-fe55-40b5-85af-7ec0b99b27c3">https://catsbi.oopy.io/c7f85a3d-fe55-40b5-85af-7ec0b99b27c3#c7f85a3d-fe55-40b5-85af-7ec0b99b27c3</a>
<a href="https://mangkyu.tistory.com/150?category=761302">https://mangkyu.tistory.com/150?category=761302</a>
<a href="https://mangkyu.tistory.com/125">https://mangkyu.tistory.com/125</a>
<a href="https://mangkyu.tistory.com/151">https://mangkyu.tistory.com/151</a>
<a href="https://mangkyu.tistory.com/75">https://mangkyu.tistory.com/75</a>
<a href="https://mangkyu.tistory.com/148">https://mangkyu.tistory.com/148</a>
<a href="https://www.youtube.com/watch?v=8lp_nHicYd4&amp;list=WL&amp;index=67">https://www.youtube.com/watch?v=8lp_nHicYd4&amp;list=WL&amp;index=67</a>
<a href="https://devlog-wjdrbs96.tistory.com/166">https://devlog-wjdrbs96.tistory.com/166</a>
<a href="https://devlog-wjdrbs96.tistory.com/321">https://devlog-wjdrbs96.tistory.com/321</a>
<a href="https://imspear.tistory.com/170">https://imspear.tistory.com/170</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해99 2주차 WIL - 객체지향이란? JVM이란?]]></title>
            <link>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-2%EC%A3%BC%EC%B0%A8-WIL</link>
            <guid>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-2%EC%A3%BC%EC%B0%A8-WIL</guid>
            <pubDate>Sun, 02 Oct 2022 06:23:57 GMT</pubDate>
            <description><![CDATA[<h1 id="1-객체지향-프로그래밍이란">1) 객체지향 프로그래밍이란?</h1>
<h2 id="1-1-객체지향-프로그래밍이-뭐야">1-1) 객체지향 프로그래밍이 뭐야?</h2>
<p><del>사실 얼마 전에 객체지향의 사실과 오해라는 책도 읽었지만,</del> 객체지향을 한 마디로 정의하기란 정말 쉽지 않은 것 같다. 그나마 표현해보자면 <strong>프로그래밍 패러다임</strong> 중 하나로, <strong>데이터를 추상화</strong>시켜 <strong>속성과 행위를 가진 객체</strong>를 만들고 각 <strong>객체들의 역할과 책임</strong>에 따라 <strong>서로 협력하는 관계</strong>를 만드는 프로그래밍 정도로 말해볼 수 있겠다. <del>그냥 저만의 미천한 생각입니다...〒▽〒</del></p>
<h2 id="1-2-객체지향의-특징">1-2) 객체지향의 특징</h2>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/9cdb5cf0-ae39-4ffb-b42a-37735c491cbb/image.png" alt=""></p>
<blockquote>
<p>사진 출처 : <a href="https://www.reddit.com/r/ProgrammerHumor/comments/418x95/theory_vs_reality/">https://www.reddit.com/r/ProgrammerHumor/comments/418x95/theory_vs_reality/</a></p>
</blockquote>
<p>위의 사진이 정말 객체 지향의 특징을 잘 설명해주는 사진인 것 같아서 가져와봤다. <strong>객체지향의 특징</strong>은 <del>캡상추다!</del> <strong>캡슐화, 상속성, 추상화, 다형성</strong>이다.</p>
<h3 id="캡슐화">캡슐화</h3>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/fea8fa72-e1f7-48a1-8a1c-a0e60f41095b/image.png" alt=""></p>
<blockquote>
<p>사진 출처 : <a href="https://radait.tistory.com/5">https://radait.tistory.com/5</a></p>
</blockquote>
<p>의학의 발달로 다들 캡슐형 알약은 한 번쯤 섭취해봤을 것이다. <del>사실 호이포이 캡슐이나 몬스터볼로 개드립을 치고 싶었는데...</del> 캡슐형 알약에는 큰 장점이 있다. 한꺼번에 먹기 어려운 <strong>여러가지의 약을 일정한 배합 비율로 한 번에 먹게</strong> 해줄 수 있다는 것이다. 환자는 <strong>내부 성분과 배합 비율을 신경쓰지 않고 그저 캡슐을 먹기만 하면 된다.</strong></p>
<p>따라서 <strong>객체의 캡슐화</strong>란, <strong>변수와 함수를 하나의 클래스</strong>로 묶고 <strong>외부에서 쉽게 접근할 수 없도록 하는 것</strong>이다. 이러한 캡슐화는 private, protected 와 같은 <strong>접근제어자로 구현</strong> 가능하며, 이를 통해 <strong>높은 응집도와 낮은 결합도</strong>를 가질 수 있다. 응집도와 결합도라는 말이 약간 어려울 수 있는데, 객체 각각은 독립적으로 작용할 수 있도록 응집도가 강해질 수 있고, 다른 한 곳의 변화가 다른 곳에 영향을 미치지 않도록 결합도는 낮아질 수 있다는 뜻이다. <del>더 어려워졌는데...?</del></p>
<p>응집도와 결합도에 대한 더 자세한 내용은 아래의 포스팅을 참고하기 바란다.</p>
<blockquote>
<p><strong>객체지향 캡슐화, 응집도와 결합도</strong> : <a href="https://mangkyu.tistory.com/195">https://mangkyu.tistory.com/195</a></p>
</blockquote>
<h3 id="캡슐화-예시">캡슐화 예시</h3>
<p>필자가 이렇게 코드를 해괴망측하게 짤 수 있다는 사실에 놀랐다... <del>이것이 재능...?</del> 결국 필자의 바람대로 포켓몬 세상을 예시로 들어 코드를 짜보았다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/467d6de1-187d-482a-a7d7-23b50031e581/image.png" alt=""></p>
<p>MonsterBall Class 와 Pikachu 객체의 <strong>필드를 모두 public</strong> 으로 주었더니, capturePikachu() 메소드를 실행할 때 <strong>ballName 을 pikachu.leg + ball 을 주어 4ball</strong> 이라는 값이 나오고 분명 잡기 전엔 <strong>10k였던 Pikachu의 볼트</strong>가 MonsterBall 로 잡은 후에는 <strong>4k로 변압(?)</strong> 되었다!</p>
<p>접근제어자로 <strong>캡슐화를 하지 않았더니</strong> 각 객체의 독립성이 떨어져 <strong>응집도가 약해지고</strong> 괴상한 메소드로 인해 <strong>결합도 또한 높아져버린 것을 확인</strong>할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/68aecf54-4947-49f3-927b-abd198ce102a/image.png" alt="">
그래서 MonsterBall Class 와 Pikachu 객체의 <strong>필드를 모두 private</strong> 으로 바꾸어주었더니 똑똑한 IntelliJ 가 에러를 모두 잡아주었다. private 접근 제어자로 인해 <strong>MonsterBall 객체에서 Pikachu 객체에 접근할 수 없다</strong>는 것이다! 이로써 MonsterBall 객체는 Pikachu 객체를 필드로 갖고 있지만 Pikachu <strong>객체의 속성을 정의하는 등 과도한 간섭을 하지 않을 수</strong> 있으며 MosterBall 객체와 Pikachu 객체는 <strong>상호 독립성을 유지</strong>할 수 있게 되었다.</p>
<blockquote>
<p><strong>수정된 코드는 조금만 더 밑으로 내려보시면 나옵니다!</strong></p>
</blockquote>
<h3 id="상속성">상속성</h3>
<p><del>캡상추다 때문에 순서가 뭔가 어그러진 것 같다...</del> 상속성은 다형성을 표현하기 위한 하나의 방법으로, <strong>부모 클래스의 성질</strong>을 <strong>자식 클래스에게 물려주는 개념</strong>으로 말할 수 있다. 상속성을 통해 기존 코드를 재활용 함으로써 코드의 생산성을 높여준다. 그러나 상속은 HAS-A 관계(한 객체가 다른 객체에 속하는 구성관계)가 아닌, IS-A 관계 (자식 클래스가 부모 클래스의 포함관계) 일때와 행동 호환성이 만족하는 경우가 아니면 부작용이 더 많을 수 있으므로 유의해서 사용해야 한다.</p>
<p>이에 대한 포스팅은 아래를 참고하기 바란다.</p>
<blockquote>
<p><strong>코드의 재사용, 상속보다 합성을 사용해야 하는 이유</strong> : <a href="https://mangkyu.tistory.com/199?category=761303">https://mangkyu.tistory.com/199?category=761303</a></p>
</blockquote>
<h3 id="상속성-예시">상속성 예시</h3>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/422fdb84-dd34-445e-8209-d8fd84b8695d/image.png" alt=""></p>
<p>포켓몬 세상을 예시로 다시 코드를 짜보았다. <del>ppt 에서 그 피카츄 좀 빼!</del> Pikachu 와 Raichu 모두 전기를 발사할 수 있다는 <strong>행동이 호환</strong>되고, 모든 라이츄는 피카츄로부터 파생되기 때문에 <strong>IS-A 관계가 성립</strong>될 수 있기 때문에 상속을 사용했다.</p>
<p>Pikachu 객체를 잡을 수 있는 MonsterBall 은 Pikachu 객체를 상속받은 Raichu 객체를 잡을 수 있게된다. 그러나 <strong>Pikachu 객체의 필드를 private</strong> 으로 주었기 때문에 이를 상속받은 Raichu 의 voltage <strong>필드는 여전히 10k 로 고정</strong>된다는 한계가 있다. 그럼에도 불구하고 전기 공격을 하였을 땐 <del>조물주의 장난으로</del> <strong>Override 로 메소드를 재정의</strong>해 100k 볼트를 발사할 수 있게 되었다.</p>
<h3 id="추상화">추상화</h3>
<p>추상화란 객체들의 <strong>공통 특징(기능, 속성)을 추출해서 정의</strong>하는 것을 말한다. 추상화의 방법으로는 <strong>추상 클래스, 인터페이스</strong>를 이용해 공통 특징을 정의한 후 각 클래스에스 구체화해 사용한다. 밑에서 다형성까지 설명한 후에 예시를 한 번에 보도록 하겠다. <del>기왕 시작한 뇌절...... 끝을 보자......</del></p>
<p>추상 클래스와 인터페이스에 대한 내용은 아래의 포스팅에 자세히 나와있다.</p>
<blockquote>
<p><strong>추상화, 인터페이스, 다형성, 쓰레드, 형변환</strong> : <a href="https://velog.io/@alicesykim95/OOP-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A52-%EC%B6%94%EC%83%81%ED%99%94-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EB%8B%A4%ED%98%95%EC%84%B1-%EC%93%B0%EB%A0%88%EB%93%9C-%ED%98%95-%EB%B3%80%ED%99%98#-%EC%B6%94%EC%83%81-%ED%81%B4%EB%9E%98%EC%8A%A4abstract-class">https://velog.io/@alicesykim95/OOP-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A52-%EC%B6%94%EC%83%81%ED%99%94-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EB%8B%A4%ED%98%95%EC%84%B1-%EC%93%B0%EB%A0%88%EB%93%9C-%ED%98%95-%EB%B3%80%ED%99%98#-%EC%B6%94%EC%83%81-%ED%81%B4%EB%9E%98%EC%8A%A4abstract-class</a></p>
</blockquote>
<h3 id="다형성">다형성</h3>
<p>다형성이란 <strong>하나의 변수 또는 함수가 명령</strong>을 받았을 때, <strong>상황에 따라 서로 다른 방식으로 동작</strong>하는 것을 말한다. 동일한 명령을 <strong>각자 연결된 객체에 의존해서 해석</strong>하는 것으로, <strong>오버라이딩이나 오버로딩으로 구현</strong> 가능하다.</p>
<p>다형성에 대한 내용은 아래의 포스팅에 자세히 나와있다.</p>
<blockquote>
<p><strong>객체지향 프로그래밍이란?</strong> : <a href="https://velog.io/@khy226/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-OOP-%EC%9D%B4%EB%9E%80#4-%EB%8B%A4%ED%98%95%EC%84%B1-polymorphism">https://velog.io/@khy226/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-OOP-%EC%9D%B4%EB%9E%80#4-%EB%8B%A4%ED%98%95%EC%84%B1-polymorphism</a></p>
</blockquote>
<h3 id="추상화-및-다형성-예시">추상화 및 다형성 예시</h3>
<p>우선 몬스터볼에 다양한 포켓몬을 넣을 수 있게 Pokemon 객체를 <strong>추상클래스</strong>로 만들고 이를 MonsterBall의 필드로 선언하였다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/f69b7ce3-a8dc-44d2-84d5-1fbbde5e0a3e/image.png" alt=""></p>
<p>모든 포켓몬의 <strong>공통</strong>인 이름과 꼬리의 길이(?) <del>그냥 그렇다 치자...</del> 를 <strong>필드로 선언</strong>했고, <strong>어떤 포켓몬이 됐든</strong> 그 이름과 꼬리의 길이 알 수 있도록 getter <strong>Method 또한 선언</strong>해주었다. 포켓몬의 공격 <strong>기능을 정의하기 위해</strong> Attack <strong>인터페이스</strong> 또한 만들었다. 이로써 포켓몬 객체를 만들기 위한 추상화 설계는 얼추 마무리 되었다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/af92867e-28c2-474d-ba81-be1ad62dd56f/image.png" alt=""></p>
<p>이렇게 만들어진 추상 클래스 Pokemon 과 인터페이스를 Pichu 에 상속 및 구현하여 Pichu 클래스에 생명을 불어 넣어줬다. 다만 Pichu 의 <strong>고유 특성</strong>이라고 할 수 있는 voltage 는 <strong>따로 필드로 선언</strong>해 <strong>생성자에서 그 값을 줄 수 있도록</strong> 하였다. 또한 Pichu 는 <strong>최하위 포켓몬</strong>이므로 <strong>attack Method 만 구체화</strong>하고, 나머지 tailPress와 thunderStorm Method 는 구체화하지 않았다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/1e67d992-177f-4015-a9dd-299d8a8f20d1/image.png" alt=""></p>
<p>Pikachu 객체는 Pichu 를 상속받고, Raichu 객체는 Pikachu 객체를 상속받아 그 속성을 이어받되, 각각 tailPress Method 와 thunderStorm Method 를 구체화해 <strong>각 객체 별로 다른 기능이 구현되도록</strong> 하였다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/24d26e21-e034-4851-9f3f-7bb8c17e5e6d/image.png" alt=""></p>
<p><del>이거 보여주려고 어그로 끌었다...</del> MonsterBall 객체를 만들고, Pichu 객체를 생성해 Attack Interface 의 <strong>모든 Method 를 Override</strong> 했다. 그러나 Pichu 객체에서는 <strong>attack() 만 재정의</strong>했기 때문에 나머지 Method 에서는 null 이 출력되었다.</p>
<p>그 이후에 Pichu 객체를 Pikachu 객체로 타입 변환하였다. Pichu 객체가 Pikachu <strong>객체의 상위 클래스</strong>이기 때문에 <strong>형변환</strong>에 아무런 문제가 없고, Pichu 객체에서 <strong>implement 받았던 모든 Method 들도 정상 작동</strong>한다. 그러나, Pikachu 객체에서는 <strong>tailPress() 만 재정의</strong>하고, thunderStorm() 을 재정의 하지 않았기 때문에 null 이 출력되었다.</p>
<p>Pikachu 객체를 Raichu 로 변환해도 위의 상황과 같다. 다만 상속을 이어오며 <strong>모든 Method 를 재정의</strong>했기 때문에 모든 메소드에서 입력된 값이 출력된다.</p>
<p>그 이후에 Raichu 객체를 MonsterBall 객체의 getPokemon() 으로 납치해왔다. Monsterball 안에 있는 Pokemon 객체는 움직일 수 없으므로(추상클래스이므로), 이름을 보거나 꼬리의 길이만 확인할 수 있다.</p>
<p>마지막으로 몬스터볼에서 포켓몬을 꺼냈다. 재밌게도 포켓몬 세상에서는 힘과 속성은 그대로지만 외형은 귀여운 피츄나 피카츄의 모습으로 꺼낼 수 있다. 따라서 <strong>Pichu 객체로 형변환</strong>을 해 가져온 후 Method 를 실행시켜보니 <strong>외형은 Pichu</strong> 이지만 <strong>Raichu로 재정의된 필드값</strong>을 갖고 <strong>재정의된 Method 대로 출력</strong>되는 것을 볼 수 있다.</p>
<p>MonsterBall 객체에서 Pokemon 이라는 추상 클래스를 사용한 덕분에 캡슐화가 가능했고(MonsterBall 객체에서 Pokemon 의 구체 클래스 타입은 알 수 있으나 객체를 직접 불러오는 등의 행위가 불가능하다.), 이를 상속시켜 하위 클래스들에서 구체화시킬 수 있었다. 이를 통해 형변환으로 다형성이 구현 가능했다. 또한 Interface 의 Method 를 각 객체에서 재정의 함으로써 다형성을 갖출 수 있었다.</p>
<p>이 정도면 캡슐화, 상속성, 추상화와 다형성이 골고루 짜인 아주 적절한 예시가 되지 않았을까 싶다. <del>진심으로 바래본다... 여기까지 공부하고 자료찾고 포켓몬 세상 다시 공부하고... 코드짜고 블로그 적고 6시간은 걸린 것 같다...</del></p>
<h2 id="1-3-객체지향의-장단점">1-3) 객체지향의 장단점</h2>
<h3 id="객체지향의-장점">객체지향의 장점</h3>
<ul>
<li><p>코드 재사용이 용이함</p>
<p>모듈화된 객체, 그리고 상속을 통해 코드의 재사용을 높일 수 있다.</p>
</li>
<li><p>생산성이 향상됨</p>
<p>독립적인 객체를 사용함으로써 개발의 생산성을 향상시킬 수 있다. 이미 생성된 클래스를 상속받거나, 객체를 재사용, 부분 수정 등 적은 노력으로 높은 효율을 만들어낸다.</p>
</li>
<li><p>자연적인 모델링 가능</p>
<p>현실세계에서 사용하는 개념을 대입하여, 생각한 것을 그대로 구현할 수 있다.</p>
</li>
<li><p>유지보수가 쉬움</p>
<p>프로그램 추가, 수정 시 캡슐화를 통해 주변에 미치는 영향을 제한할 수 있다.</p>
</li>
</ul>
<h3 id="객체지향의-단점">객체지향의 단점</h3>
<ul>
<li><p>실행 속도가 느림</p>
<p>객체 지향 프로그래밍은 캡슐화와 격리 구조 때문에 절차 지향 프로그래밍에 비해 실행 속도가 느리다.</p>
</li>
<li><p>상대적으로 큰 용량이 필요하다</p>
<p>객체 단위로 프로그램을 많이 만들다보면, 불필요한 정보들이 들어가 이것이 프로그램의 용량을 증가시킬 수 있다.</p>
</li>
<li><p>개발 속도가 느림</p>
<p>객체가 처리하려는 것에 대해 정확하게 파악해야 하므로, 설계 단계에서 많은 시간과 노력이 필요하다.</p>
</li>
</ul>
<blockquote>
<p> 출처 : <a href="https://velog.io/@khy226/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-OOP-%EC%9D%B4%EB%9E%80#%EC%9E%A5%EC%A0%90">https://velog.io/@khy226/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-OOP-%EC%9D%B4%EB%9E%80#%EC%9E%A5%EC%A0%90</a></p>
</blockquote>
<h2 id="1-4-solid-원칙">1-4) SOLID 원칙</h2>
<p><del>사실 객체지향 프로그래밍의 꽃인 SOLID 원칙 또한 쓰고 싶지만... 어그로 끄느라 너무 많은 힘을 썼나봅니다... 다음 포스팅으로 넘기겠습니다... 언젠가 저의 링크를 거는 날이 오겠죠?ಥ_ಥ</del></p>
<blockquote>
<p><strong>객체지향 설계 5원칙 - SOLID 란 무엇일까?</strong> : <a href="https://devlog-wjdrbs96.tistory.com/380">https://devlog-wjdrbs96.tistory.com/380</a></p>
</blockquote>
<h1 id="2-jvm이란">2) JVM이란?</h1>
<p><del>JVM도 이거 잘못파면 엄청 내용이 깊어질 것 같아 이번 포스팅에서는 혼자 공부하는 Java 라는 책의 일부를 참고하여 간단히 다음 포스팅에 더 자세히 쓰겠습니다...!! 읍!!! 읍!!!!!!</del></p>
<p>JVM 은 Java Virtual Machine 의 약자로, 자바 프로그램 실행환경을 만들어 주는 소프트웨어입니다. 자바 코드를 컴파일하여 바이트 코드로 만들면 이 코드가 자바 가상 머신 환경에서 실행됩니다. JVM 을 사용하면 하나의 바이트코드로 각자의 플랫폼에 설치되어 있는 JVM이 운영체제에 맞는 실행 파일로 바꿔 어떤 운영체제에서도 동작하도록 할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해99 1주차 WIL - JWT 란? API 란?]]></title>
            <link>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-1%EC%A3%BC%EC%B0%A8-WIL</link>
            <guid>https://velog.io/@ming_gry/%ED%95%AD%ED%95%B499-1%EC%A3%BC%EC%B0%A8-WIL</guid>
            <pubDate>Sun, 25 Sep 2022 14:15:28 GMT</pubDate>
            <description><![CDATA[<h1 id="1-jwt-란">1) JWT 란?</h1>
<h2 id="1-1-jwt-가-뭐야">1-1) JWT 가 뭐야?</h2>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/98936b57-11c2-47f4-b905-da1a34c8f617/image.png" alt=""></p>
<blockquote>
<p>사진 출처 : <a href="https://jwt.io/">https://jwt.io/</a></p>
</blockquote>
<p>위의 사진 출처인 JWT 공식 사이트<del>(맞나...?)</del> 에는 아래와 같은 문장으로 JWT 를 설명한다.</p>
<blockquote>
<p>JWT 는 당사자 간에 정보를 JSON 개체로 안전하게 전송하기 위한 간결하고 독립적인 방법을 정의하는 개방형 표준 (RFC 7519) 입니다. 이 정보는 디지털로 서명되어 있으므로 확인하고 신뢰할 수 있습니다.</br>
출처 : <a href="https://jwt.io/introduction">https://jwt.io/introduction</a></p>
</blockquote>
<p>처음 듣는 사람은 이게 대체 뭔 소리인가... 싶을 것이다. 이를 쉽게 풀어보면 <strong>당사자 = 클라이언트 &amp; 서버</strong> 가 <strong>JSON 방식으로 사용자의 정보를 전송</strong>하기 위한 <strong>인터넷 표준</strong> 정도라고 이해하면 될 것 같다.</p>
<h2 id="1-2-jwt-의-특징은">1-2) JWT 의 특징은?</h2>
<p>가장 큰 특징은 <strong>Stateless</strong> 하다는 것이다. 아주 간단히 말해서 <strong>서버에서 클라이언트의 정보를 저장하고 있지 않다.</strong> 반대로 <strong>서버에서 클라이언트의 정보를 저장</strong>하고 있으면 <strong>Stateful</strong> 하다고 할 수 있다. Stateless 의 반대 격인 <strong>Stateful 한 방식은 Session 방식</strong>이며, <strong>JWT 와 Session 의 차이</strong>는 <strong>&#39;Stateless 이냐 Stateful 이냐의 차이&#39;</strong> 로 말할 수 있다.</p>
<p>Stateless 와 Stateful 에 대한 개념은 조금 더 깊이 들어가면 범위가 조금 확장되긴 하지만 초보자의 수준에서는 <strong>서버에서 클라이언트의 정보를 저장하고 있느냐 아니냐의 차이</strong>로 이해해도 무방하다고 생각한다. 그래도 궁금한 사람을 위해 아래의 url 을 남겨놓는다.</p>
<p>그리고 <strong>JWT 와 Session 의 차이</strong>는 코린이의 인기스타 니꼬쌤과 다른 블로그의 글이 너무 잘 정리되어 있어 추가한다.</p>
<blockquote>
<p><strong>Stateful vs Stateless</strong> : <a href="https://5equal0.tistory.com/entry/StatefulStateless-Stateful-vs-Stateless-%EC%84%9C%EB%B9%84%EC%8A%A4%EC%99%80-HTTP-%EB%B0%8F-REST">https://5equal0.tistory.com/entry/StatefulStateless-Stateful-vs-Stateless-%EC%84%9C%EB%B9%84%EC%8A%A4%EC%99%80-HTTP-%EB%B0%8F-REST</a></br>
<strong>Session vs Token vs Cookie - 니꼬쌤</strong> : <a href="https://www.youtube.com/watch?v=tosLBcAX1vk">https://www.youtube.com/watch?v=tosLBcAX1vk</a>
<strong>Cookie &amp; Session vs JWT - 블로그</strong> : <a href="https://tecoble.techcourse.co.kr/post/2021-05-22-cookie-session-jwt/">https://tecoble.techcourse.co.kr/post/2021-05-22-cookie-session-jwt/</a></p>
</blockquote>
<h2 id="1-3-jwt-의-구조">1-3) JWT 의 구조</h2>
<p>이 정도면 JWT 가 어떤 것인지 어디서, 왜 사용하는지 Session 과의 차이는 어떤 것인지 감이 조금 왔을 것이다. 이제 JWT 공식 사이트를 함께 보며 <strong>JWT 가 도대체 어떻게 생겨먹은 녀석</strong>인지 <strong>머리 / 가슴 / 배(?)</strong> <del>근데 진짜 머리 가슴 배 같긴 해... 진짜야...</del> 로 <strong>분해해보며 확인</strong>해보자.</p>
<blockquote>
<p>JWT 공식 사이트 : <a href="https://jwt.io/">https://jwt.io/</a></p>
</blockquote>
<p><del>포토샵 하느라 좀 귀찮긴 했는데 그래도 만들어봤다.</del> 공식 사이트에 들어가자 마자 스크롤을 조금 아래로 내리면 이런 화면이 나온다. <strong>이것만 알면</strong> JWT 가 어떻게 생겨먹은 녀석인지 머리 / 가슴 / 배로 <del>조질 수</del> 알 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/9e89c43e-06d1-412d-af7b-0a0df368630a/image.png" alt=""></p>
<blockquote>
<p>JWT 는 헤더, 페이로드, 시그니처로 나뉘며, 왼쪽 <strong>빨간색 글씨가 헤더</strong>, 왼쪽 <strong>보라색 글씨가 페이로드</strong>, 왼쪽 <strong>하늘색 글씨가 시그니처</strong>로 인코딩 된다. 반대로 <strong>인코딩된 JWT 를 디코딩</strong>하면 오른쪽의 헤더, 페이로드, 시그니처를 확인할 수 있다.</p>
</blockquote>
<p>머리 / 가슴 / 배를 확실히 <del>조지기</del> 분해해보기 전에 1-1) 에서 설명한 개념을 다시 한 번 확인해보자.</p>
<blockquote>
<p>JWT 는 당사자 간에 정보를 JSON 개체로 안전하게 전송하기 위한 간결하고 독립적인 방법을 정의하는 개방형 표준 (RFC 7519) 입니다. 이 정보는 <strong>디지털로 서명되어 있으므로</strong> 확인하고 신뢰할 수 있습니다.</br>
출처 : <a href="https://jwt.io/introduction">https://jwt.io/introduction</a></p>
</blockquote>
<p>아까는 미처 보지 못했던 디지털로 서명되어 있다는 말이 나온다! 사진에 보면 <strong>JWT에 사용할 알고리즘</strong>에 HS256 이 선택되어 있다. 쉽게 말해 JWT는 <strong>다른 사람이 위조하지 못하게</strong> HS256 이라는 <strong>암호화 알고리즘</strong>에 의해 <strong>시그니처 부분이 암호화 되어</strong> 있다. 다시 말해 우리가 현실의 세계에서 <strong>나를 인증하기 위해 사인</strong>을 하듯이! <strong>JWT에도</strong> 나 임을 인증하기 위해 <strong>암호화 알고리즘으로 사인</strong>을 남기는 것이다!! 이에 대한 정확한 설명은 아래에서 추가로 하도록 하겠다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/d28d405c-6cea-4ef1-88ef-4ad382d83225/image.png" alt=""></p>
<blockquote>
<p>사진 출처 : <a href="https://ko.wikipedia.org/wiki/%ED%8C%8C%EC%9D%BC:%EA%B9%80%EC%9A%A9%ED%83%9D_%EC%8B%9C%EC%9D%B8_%EC%82%AC%EC%9D%B8.JPG">https://ko.wikipedia.org/wiki/%ED%8C%8C%EC%9D%BC:%EA%B9%80%EC%9A%A9%ED%83%9D_%EC%8B%9C%EC%9D%B8_%EC%82%AC%EC%9D%B8.JPG</a></p>
</blockquote>
<h3 id="헤더">헤더</h3>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/dbfa81cb-9d14-4441-be89-f667a7f8c0e9/image.png" alt=""></p>
<p>헤더는 그 하위에 <strong>alg - 시그니처에 어떠한 암호화 알고리즘이 쓰였는지</strong>(여기서는 HS256이 적용되었다.) 와 <strong>typ - JWT</strong> 가  <strong>Base64 로 인코딩</strong> 되어 있다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/d6b29d7f-877b-4c16-837c-4b2e6e5b0e79/image.png" alt=""></p>
<p><strong>Base64</strong> 는 <strong>암호화 알고리즘은 아니다.</strong> 그러므로 <strong>누구나</strong> 쉽게 <strong>Base64 로 인코딩 / 디코딩</strong>할 수 있다! 아래의 페이로드 부분에서 더 자세히 다루도록 하고, Base64 관련된 포스팅은 아래에 있으니 들어가서 확인해보도록 하자.</p>
<blockquote>
<p><strong>Base64 란 무엇일까?</strong> : <a href="https://devuna.tistory.com/41">https://devuna.tistory.com/41</a> </br>
<strong>Base64 인코딩이란 무엇일까?</strong> : <a href="https://wookkl.tistory.com/22">https://wookkl.tistory.com/22</a></p>
</blockquote>
<h3 id="페이로드">페이로드</h3>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/4a014530-69d2-4037-88cd-80147bdc1896/image.png" alt=""></p>
<p><strong>페이로드</strong>는 <strong>클레임</strong>이라 불리우는 <strong>토큰의 정보들이 담겨져 있는 곳</strong>이다. <strong>iss(발급자), exp(만료시간), sub(주제), aud(대상) 등</strong>이 들어가며 기타 RFC7519 기준에 부합하는 클레임을 넣을 수 있다. 또한 IANA JSON 웹 토큰 레지스트리에 정의된 것처럼 <strong>원하는 정보를 넣을 수도 있다.</strong> 여기에는 해당하지 않지만, 클라이언트와의 합의 하에 원하는 정보를 넣을 수도 있다!</p>
<p>어쨌든 <strong>페이로드에는 토큰의 정보가 담긴다</strong>는 점이며, 서버와 클라이언트에서 <strong>필요한 정보들을 커스텀해서 넣을 수도 있다</strong>는 점이다!</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/2da7c148-d9f8-49fe-a6d5-0ed3fe475858/image.png" alt=""></p>
<p>실제로 필자의 프로젝트에서도 오른쪽과 같이 sub 에는 email, auth 에 권한, memberId 에 서버에서 관리하고 있는 member 번호, exp 에는 유효기간을 정의하여 사용했다. 여기서 memberId 는 클라이언트에서 JWT 를 파싱하여 로그인한 유저의 정보를 알기 위해 넣은 개인 클레임이라고 할 수 있겠다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/6f744924-8912-4153-a842-b6fe717b9d38/image.png" alt=""></p>
<p><strong>JWT 의 헤더와 페이로드</strong>에서 <strong>가장 중요한 부분</strong>은 이 곳이다.  <strong>페이로드</strong>는 헤더와 마찬가지로 <strong>Base64 로 인코딩</strong>되어 있다. 그러므로 <strong>JWT 가</strong> 어떤 방법으로든 <strong>유출되면</strong> 유출된 JWT 는 <strong>Base64로 아주 손쉽게 디코딩</strong>할 수 있다. <del>아주 친절하게도 JWT 공식 사이트에 JWT 를 넣어도 디코딩할 수 있다!</del></p>
<p>그러므로 공식 사이트에서도 밝혔듯이 <strong>JWT 페이로드와 헤더</strong>에는 <strong>!절대!</strong> 유저의 이름이나 생년월일 등과 같이 <strong>민감할 수 있는 정보</strong>와 비밀 번호 등 <strong>비밀 정보를 입력해서는 안 된다.</strong></p>
<h3 id="시그니처">시그니처</h3>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/ba6d988f-2f4c-41f6-876b-1b2bfb96e89b/image.png" alt=""></p>
<p>공식 사이트에서 언급하듯, 시그니처 부분은 <strong>Base64로 인코딩된 헤더와 페이로드</strong>, 그리고 서버에서 지정한 <strong>비밀키</strong>를 암호화 알고리즘으로 <strong>암호화하는 것</strong>을 말한다. 보통 SHA256, SHA512와 같이 <strong>단방향 암호화 방식</strong>을 사용해 <strong>암호화는 가능하지만 복호화는 불가능하도록</strong> 만든다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/976472dd-d65b-4345-b320-1387aaca6c94/image.png" alt=""></p>
<p>시그니처 부분에 인코딩된 헤더, 페이로드, 비밀키를 함께 넣는 이유는 Salting 때문이지 않을까 싶다. <del>정확하진 않고 나의 추측이다...ㅎ......</del> Salting 은 암호화 방식의 하나로, 레인보우 테이블과 브루트포스를 방지하도록 하기 위함이다. 이 부분은 본 포스팅에서 다루기 어려울 것 같아 아래의 참조로 남겨두겠다. <del>언젠가 기회가 되면 포스팅해보자!</del></p>
<blockquote>
<p><strong>[Programming] 암호화 알고리즘 종류와 분류</strong> : <a href="https://velog.io/@inyong_pang/Programming-%EC%95%94%ED%98%B8%ED%99%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A2%85%EB%A5%98%EC%99%80-%EB%B6%84%EB%A5%98">https://velog.io/@inyong_pang/Programming-%EC%95%94%ED%98%B8%ED%99%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A2%85%EB%A5%98%EC%99%80-%EB%B6%84%EB%A5%98</a> </br>
<strong>패스워드의 암호화와 저장</strong> : <a href="https://st-lab.tistory.com/100">https://st-lab.tistory.com/100</a></p>
</blockquote>
<h1 id="2-api-란">2) API 란?</h1>
<p>API 란 Application Programming Interface 라는 용어로, 어떤 응용프로그램에서 <strong>데이터를 주고 받기 위한 방법</strong>을 의미한다. 어떤 특정 사이트에서 특정 데이터를 공유할 경우 <strong>어떠한 방식으로 정보를 요청</strong>해야 하는지, 그리고 <strong>어떠한 데이터를 제공받을 수 있을지에 대한 규격들을</strong> API 라고 한다.</p>
<blockquote>
<p>출처 : <a href="https://steemit.com/kr/@yahweh87/it-api">https://steemit.com/kr/@yahweh87/it-api</a></p>
</blockquote>
<p>그렇기 때문에 <strong>사이트에 따라</strong> API 의 규격 및 방식은 <strong>모두 제각각</strong>이다. Open API, Kakao  API, Naver API, Google API 들이 모두 다르다. 뿐만 아니라 우리가 개발한 사이트의 API 도 위의 API 들과 다르다!</p>
<p>사실 좀 어렵게 얘기해서 그렇지 <strong>API 는</strong> 어떻게 데이터를 주고 받을 것인가에 대한 <strong>약속이다.</strong> 개발 프로젝트를 진행한다면, <strong>프론트엔드와 백엔드의 약속</strong>이라고 할 수 있는 것이다. 그렇기 때문에 <strong>기능별로</strong> 어떻게 API 를 주고받을 것인지 <strong>잘 정의하는 것이 중요</strong>하다.</p>
<p>프론트엔드던 백엔드던 이 <strong>API 가 잘 지켜지지 않으면</strong> 원하는 데이터를 주고받을 수 없을 뿐더러 <strong>신나는 에러 파티</strong>가 펼쳐질 것이다. </p>
<p>진짜다... CORS 문제를 해결했다면, <strong>대부분의 프론트엔드 및 백엔드 연결 에러는 여기에서 발생</strong>한다. 특히나 백엔드 입장에서 PostMan으로 테스트 해봤을 때는 아주 잘되는데 프론트엔드에서는 안된다면 여기에서 <strong>타입이나 오타 등의 문제가 발생했을 가능성</strong>이 있다.</p>
<p>그리고 API 는 프론트엔드와 백엔드의 약속이기 때문에 <strong>API 가 변경되어야 한다면</strong> 바꾼 뒤에 일방적으로 통보하지 말고 <strong>미리 협의 후에 API 를 다시 정하는 습관</strong>을 들이도록 하자.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/f8bd7256-8ef7-4eb2-a724-445f0ccd09e4/image.png" alt=""></p>
<p>실제로 필자가 프로젝트에서 사용했던 <strong>API 명세서</strong> 중 로그인 부분을 가져와봤다. 위에서 보는 것과 같이 API 명세서에 Method, URL, Request, Response, Error Message, 참고 사항 등을 넣어 프론트엔드와 백엔드 모두 API 명세서를 함께 정의한 뒤 이를 보고 개발을 진행했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹개발이 뭐야? [프론트엔드, 백엔드]]]></title>
            <link>https://velog.io/@ming_gry/%EC%9B%B9%EA%B0%9C%EB%B0%9C%EC%9D%B4-%EB%AD%90%EC%95%BC</link>
            <guid>https://velog.io/@ming_gry/%EC%9B%B9%EA%B0%9C%EB%B0%9C%EC%9D%B4-%EB%AD%90%EC%95%BC</guid>
            <pubDate>Fri, 23 Sep 2022 15:09:17 GMT</pubDate>
            <description><![CDATA[<h1 id="1-웹개발이-뭐야">1. 웹개발이 뭐야?</h1>
<h2 id="1-1-웹개발">1-1) 웹개발?</h2>
<p>개발의 종류에는 프로그램, 게임, 임베디드, 어플리케이션 등 여러가지가 있다.
<strong>웹개발</strong>은 당연하게도 말 그대로 <strong>웹사이트를 개발</strong>하는 것이라 할 수 있겠다.
웹개발의 가장 큰 축은 프론트엔드와 백엔드로 나눌 수 있겠다. (물론 DevOps 도 있다지만 이에 대해 잘 모르므로 넘어가도록 하자^ㅡ^;;;)</p>
<h2 id="1-2-프론트엔드">1-2) 프론트엔드?</h2>
<p>영어로 풀어쓰면 <strong>&quot;Front-End&quot;</strong> 이다. 이를 글자 그대로 해석하면 <strong>&quot;앞단&quot;</strong> 정도로 해석할 수 있겠다.
이를 웹개발에 적용해보면 우리가 어떠한 사이트를 볼 때 <strong>눈에 보여지는 부분</strong>을 말한다.
아래의 사진을 보면 좌측 상단에는 velog 로고, 우측 상단에는 새 글 작성과 내 사진, 그 아래로 포스팅 등이 눈에 보인다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/90678d02-64cd-4740-a968-10d3e998c574/image.PNG" alt=""></p>
<p>이렇게 <strong>웹사이트의 눈에 보이는 모양을 잡아가는 것이 프론트엔드다!</strong> 라고 할 수 있다.</p>
<h2 id="1-3-그럼-백엔드는-백엔드는-프론트와-반대">1-3) 그럼 백엔드는? 백엔드는 프론트와 반대?</h2>
<p>Front의 반대 말은? 맞다. Back 이다. 프론트엔드는 앞단이었으니, 백엔드는 <strong>뒷단</strong>이라고 볼 수 있다. 그렇다고 <strong>프론트엔드의 반대말은 백엔드일까?</strong> 솔직히 코린이라 정확히는 모르겠지만, 어느 정도 코딩을 해보고 느낀 바로는 <strong>절대 아니다.</strong></p>
<p>동전을 한 번 잘 봐보자. 동전의 앞면 또는 뒷면을 보면 반대면의 모습은 볼 수 없지만! <strong>옆쪽에서 보면 보면 동전의 앞과 뒤는 서로 정확히 붙어있다!</strong> (진짜라구...)</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/c9af382e-9ee8-42a1-8759-c2f5f244bdad/image.jpg" alt=""></p>
<p>프론트엔드와 백엔드도 마찬가지이다. <strong>서로 담당하는 부분이 다를 뿐</strong>이지 프론트엔드 없는 백엔드도, 백엔드 없는 프론트엔드도 없다. <strong>두 부분이 서로 유기적으로 작동해야</strong> 우리가 보는 웹사이트가 <strong>&quot;정상적으로&quot; 작동</strong>할 수 있다.</p>
<p>서론이 길었으나, 쉽게 말해 <strong>백엔드는 서버</strong>이다. 웹사이트(클라이언트)에서 넘어온 데이터를 서버에 저장하고 다시 웹사이트로 보내주는 부분을 말한다.</p>
<p>우리가 사용하는 서비스나 게임이 죽었을 경우 <strong>&quot;서버가 죽었다&quot;</strong> 라고 얘기하는 그 서버가 맞다...</p>
<p><strong>프론트와 백엔드의 기본 소양</strong>이라고 한다면 물론 <strong>코딩을 잘해야 한다!</strong></p>
<p>그 외에 프론트엔드에서는 UX/UI 와 같은 디자인적인 측면도 함께 고려를 해야한다면, 서버는 <strong>서비스 아키텍처나 그 환경</strong> 등에도 함께 신경을 써야한다.</p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/80385b1e-f717-4c35-acfe-703dbb3520bd/image.png" alt=""></p>
<blockquote>
<p>사진 출처 : <a href="https://nginxstore.com/blog/microservices/%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98msa-%ED%8C%A8%ED%84%B4%EC%9D%98-%EC%9D%B4%ED%95%B4/">https://nginxstore.com/blog/microservices/%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98msa-%ED%8C%A8%ED%84%B4%EC%9D%98-%EC%9D%B4%ED%95%B4/</a></p>
</blockquote>
<h2 id="1-4-그래서-너의-스택은-뭔데">1-4) 그래서 너의 스택은 뭔데?</h2>
<p>난 <del>킹갓</del> <strong>백엔드</strong>를 선택했다.</p>
<p><strong>백엔드를 선택한 이유</strong>는 간단하다. <strong>나의 경험을 가장 잘 살릴 수 있는 분야</strong>라고 생각했기 때문이다. 이 부분은 <strong>다음 포스팅</strong>에서 써보도록 하겠다.</p>
<p>어쨌든 정말 부족하고 모자란 실력이지만! 앞으로의 <strong>항해99 WIL 시리즈 포스팅</strong>에서 내가 배웠던, 내가 아는 내용을 최대한 정리해보고자 한다...!</p>
<p><strong>자 그럼 가보자고!!</strong></p>
<p><img src="https://velog.velcdn.com/images/ming_gry/post/2926d1ee-ade6-45d0-9b14-2aed94ecda14/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>