<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Archives-2020 on</title><link>https://taetaetae.github.io/tags/archives-2020/</link><description>Recent content in Archives-2020 on</description><generator>Hugo</generator><language>en</language><lastBuildDate>Thu, 31 Dec 2020 13:31:01 +0900</lastBuildDate><atom:link href="https://taetaetae.github.io/tags/archives-2020/index.xml" rel="self" type="application/rss+xml"/><item><title>‘중니어 개발자’의 2020 회고</title><link>https://taetaetae.github.io/posts/review-2020/</link><pubDate>Thu, 31 Dec 2020 13:31:01 +0900</pubDate><guid>https://taetaetae.github.io/posts/review-2020/</guid><description>&lt;p>﻿　그 어느 때보다도 정신없이 달려온 2020년. 하고 싶은 것도 많았고 큰 꿈을 꾸기도 했지만 현실의 벽 앞에 크게 좌절도 해보기도 하고. 갑작스러운 세상의 변화에 적응하랴 정신적으로 육체적으로 너무 많이 힘들었던 올해. 돌아보면 참 후회가 되지만 한편으론 시련과 좌절 속에서 여러 가지를 배웠던 그런 한 해를 보낸 것 같다.&lt;/p>
&lt;p>　﻿필자는 내년이 되면 이제 어느덧 개발자 생활을 한 지 9년 차가 된다. 보통 &lt;code>주니어&lt;/code>라 함은 단순하게 이제 막 취업한 신입 또는 3~5년 차를 말하고 &lt;code>시니어&lt;/code>는 연봉이 X 원을 넘거나 n 연차를 넘을 경우를 말하는 것 같다. 물론 각 회사마다 이 둘을 정의하는 기준이 다르겠지만. 그런데 필자는 주니어도 시니어도 아닌 그 사이에서 애매~한 연차. &lt;code>중니어&lt;/code>. 과연 나는 무엇을 해야 할까? 무엇을 해야 연차에 맞는 역할(?)이라고 할 수 있을까? 그리고 그건 누구에게 배워야 하고 누가 가르쳐 주기나 할까?&lt;/p>
&lt;p>﻿　매년 회고를 써왔다. 그럼에 연말이 되어서 연례행사처럼 작성하는 게 아닌 나에게 정말 필요한 방향으로 회고를 작성하려 한다. 단순하게 이런저런 일들이 있었고 &amp;lsquo;어쩔 수 없었네~&amp;rsquo; 읊조리는 &lt;code>무의미한 회고&lt;/code>보다 현실적으로 나 자신을 위해 변화해야 할 게 있으면 굵고 길게 계획을 세워보는 방향으로 해보고 싶다.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://taetaetae.github.io/2019/12/29/review-2019/" target="_blank" rel="noopener noreffer ">2019 회고&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://taetaetae.github.io/2018/12/31/review-2018/" target="_blank" rel="noopener noreffer ">2018 회고&lt;/a>&lt;/li>
&lt;li>2017년엔 왜 없지..?&lt;/li>
&lt;li>&lt;a href="https://taetaetae.github.io/2016/12/31/adieu-2016/" target="_blank" rel="noopener noreffer ">2016 회고&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="등장-코로나-19">등장, 코로나-19&lt;/h2>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/review-2020/corona19.jpg" title="/images/review-2020/corona19.jpg" data-thumbnail="/images/review-2020/corona19.jpg" data-sub-html="&lt;h2>나가지 말라면 나가지 마! 밥 먹지 마! 모이지 마! 출처 : salihgonenli&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/review-2020/corona19.jpg"
 data-srcset="https://taetaetae.github.io/images/review-2020/corona19.jpg, https://taetaetae.github.io/images/review-2020/corona19.jpg 1.5x, https://taetaetae.github.io/images/review-2020/corona19.jpg 2x"
 data-sizes="auto"
 alt="/images/review-2020/corona19.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">나가지 말라면 나가지 &lt;strong>마!&lt;/strong> 밥 먹지 &lt;strong>마!&lt;/strong> 모이지 &lt;strong>마!&lt;/strong> &lt;br>출처 : &lt;a href="https://www.instagram.com/p/B55nDnDAHS_/" target="_blank" rel="noopener noreffer ">salihgonenli&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>﻿　세상이 변했다. 작년까지만 해도 미세먼지가 심하면 마스크를 쓰고 나가곤 했지만 코로나-19라는 전염병이 전 세계에 퍼지며 이제는 마스크 없이 살 수 없는 세상이 되었다. 늘 사무실에 나가 팀 동료분들과 이야기를 하며 밥도 먹고 회의도 하며 업무를 진행했지만 재택근무를 한지 어느덧 반년이 훌쩍 지났다.&lt;/p>
&lt;p>﻿　처음엔 집에서 편하게 일을 할 수 있어서 좋았다. 그러나 IT 회사에 근무하고 있지만 아직도 버벅거리고 어색한 화상회의와 더딘 업무 진행으로 인해 점점 시간이 지날수록 답답함은 극을 달했다. 출/퇴근 시간 등 업무이외에 필요한 시간이 사라지며 오히려 업무에 집중하는 시간은 많아졌다. 그에 반해 피로도는 집중한 업무시간에 비례하며 늘어났기에 나무늘보처럼 늘어지는 시간들 또한 많았던 것 같다. 지나고 보면 그러한 시간들을 잘 계획하고 움직였더라면 뭐라도 배우거나 달성했을 시간들인 것 같아서 약간 아쉬움이 남는다. 내년엔 계획하는 시간의 비중을 좀 더 늘리는 것으로.&lt;/p>
&lt;p>﻿　아무쪼록 코로나-19 바이러스가 없어지고 다시 예전으로 돌아갔으면 좋겠다. 그에 마스크 잘 쓰고 손 잘 씻고 사람 많이 모이는 곳은 피해야 하는 건 우리가 &lt;del>할 수 있는&lt;/del> 해야 할 가장 큰일이겠지.&lt;/p>
&lt;h2 id="회사생활">회사생활&lt;/h2>
&lt;h3 id="서비스-전면-개편">서비스 전면 개편&lt;/h3>
&lt;p>　﻿팀에 투입한 이후 가장 큰 규모로 서비스 전면 개편을 진행하였다. 거의 올해 내내 했다고 봐도 무방할 정도. 업무의 양도 많았고 스펙 또한 복잡하였지만 가장 크게 배울 수 있었던 부분은 모놀리틱 서비스에서 마이크로 서비스로의 아키텍처 변화를 시도했다는 점. 그리고 일반적인 Request - Response 식의 1차원적인 흐름에서 &lt;code>이벤트&lt;/code>라는 행위를 기준으로 모든 프로세스가 영향을 받는 구조를 적용하며 고민했다는 점에서 여러 가지 인사이트를 얻을 수 있었다. 아무래도 &lt;code>중니어&lt;/code>다 보니 주어진 기능을 개발만 하는 것보단 좀 더 높은 곳의 설계 관점에서 고민하는 연습을 하려고 했던 것 같은데 아직 부족한 것 같다.&lt;/p>
&lt;p>﻿　올해도 개발 문화를 개선하려는 노력도 하였다. CI를 재설치하고 다양한 개선을 통해 빌드 속도를 몇 배로 늘리기도 하였고, 단순/반복적인 업무들은 각종 봇들을 개발하여 업무 생산성을 올리기도 하였다. Sentry를 서버 레벨에 적용하여 무분별하게 발생하는 에러들을 그룹핑하여 우선순위에 따라 에러를 해결할 수 있는 구조를 만들기도 하였고, 소나큐브와 jacoco를 적용하여 코드 커버리지를 도식화하며 현재 모듈의 상태를 보여주기도 해보았다. 개발 문화의 개선은 겉으로 드러나진 않지만 잠재적인 문제들을 해결하는 데 가장 큰 효과를 준다고 믿기 때문에.&lt;/p></description></item><item><title>애자일 아버지의 고함과 호통 (리뷰：Clean Agile - Back to Basics)</title><link>https://taetaetae.github.io/posts/review-the-book-clean-agile/</link><pubDate>Sun, 27 Dec 2020 17:06:19 +0900</pubDate><guid>https://taetaetae.github.io/posts/review-the-book-clean-agile/</guid><description>&lt;p>﻿　&amp;lsquo;애자일&amp;rsquo; 이라고 하면 무엇이 떠오르는가? 잘은 모르지만 막연하게 생각을 해보면, 매일 오전 스크럼을 하고 진행 현황을 가시화하며 프로젝트를 성공적으로 이끄는 일종의 &amp;lsquo;프로세스&amp;rsquo;로 알고 있다. 좋다는 것도 들었고 도입을 하려 하지만 뭔지 모르게 잘 안되는 그것. 현업에 들어오면서 &amp;lsquo;애자일&amp;rsquo; 도입의 성공/실패에 대한 이야기를 가끔씩 건너건너 들어만 본 수준이다. 이제는 주니어도 시니어도 아닌 &lt;code>중니어&lt;/code>가 되어보니 알고리즘이나 패턴, 신기술도 중요하지만 팀과 프로젝트 전반의 건강하고 성공적인 진행을 위해서는 이러한 활동들이 중요하구나 하며 요즘 &lt;del>(올해)&lt;/del> &lt;strong>뼈!저!리!게!&lt;/strong> 느끼는 중이다.&lt;/p>
&lt;p>﻿　마침 크리스마스 연휴를 앞두고 이 시국에 나가지도 못하는데 뭘 해야 하나 고민하고 있던 찰나 &lt;del>운명처럼&lt;/del> &lt;a href="https://book.naver.com/bookdb/book_detail.nhn?bid=17524418" target="_blank" rel="noopener noreffer ">클린 애자일, 저자 로버트 C. 마틴&lt;/a>이라는 책 추천을 받는다. 보통 필자는 읽고 싶은 책을 고를 때 중요하게 생각하는 두 가지가 있는데 표지와 추천인(혹은 리뷰어)의 대한 신뢰. 둘 다 너무 좋았기에 바로 인터넷 주문을 하였지만 그새를 못 참고 근처 서점에 들러 책을 사 온다.﻿&lt;/p>
&lt;h2 id="갑분돈키호테">갑.분.돈(키호테)&lt;/h2>
&lt;p>　&lt;code>풍차나 폭포를 공격해본 모든 프로그래머에게&lt;/code>&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/review-the-book-clean-agile/donkihote.jpg" title="/images/review-the-book-clean-agile/donkihote.jpg" data-thumbnail="/images/review-the-book-clean-agile/donkihote.jpg" data-sub-html="&lt;h2>풍차를 괴물로 보고 달려들었던 돈키호테&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/review-the-book-clean-agile/donkihote.jpg"
 data-srcset="https://taetaetae.github.io/images/review-the-book-clean-agile/donkihote.jpg, https://taetaetae.github.io/images/review-the-book-clean-agile/donkihote.jpg 1.5x, https://taetaetae.github.io/images/review-the-book-clean-agile/donkihote.jpg 2x"
 data-sizes="auto"
 alt="/images/review-the-book-clean-agile/donkihote.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">풍차를 괴물로 보고 달려들었던 돈키호테&lt;/figcaption>
 &lt;/figure>
&lt;p>﻿　호기롭게 첫 장을 넘기는데 강렬하게 다가오는 문구. 옮긴이에 따르면 세르벤테스의 소설 &amp;lsquo;돈키호테&amp;rsquo;에서 주인공 돈키호테가 풍차를 공격하는 모습에서 온 표현이라 한다. 대부분 헛되고 무모한 싸움을 하는 사람들을 빗대어 이야기하며 바보 혹은 현실 부적응자로 갈음하는 표현으로 사용된다. 러시아 작가 이반 투르게네프는 햄릿을 사랑하기는 힘들지만 돈키호테는 사랑하지 않기가 힘들다는 &lt;a href="https://m.blog.naver.com/leesr2006/220703629450" target="_blank" rel="noopener noreffer ">이야기&lt;/a>를 했다고 한다. 아마 저자는 고민보다는 행동을 중요하게 생각했던 돈키호테를 빗대어 현실에 안주하지 않고 건강한 개발 문화를 개선하려는 모든 프로그래머에게 조언과 박수를 보내려 했던 건 아닐까 싶다.﻿&lt;/p>
&lt;h2 id="책의-구성">책의 구성&lt;/h2>
&lt;p>﻿　페이지 수(230p)가 많지 않아서 가볍게 읽을 수 있겠다 싶었지만 다소 작은 글씨들로 구성되어 있어서 책을 잘 안읽었던 필자에겐 약간 부담으로 다가왔다. 하지만 내용들이 너~무 공감이 되어 마치 필자의 2020년을 오래전에 예견하고 미리 써둔것 같은 느낌을 받았을 정도라 아침 5시에 일어나 저녁 11시가 되어서야 다 읽을 수 있었다. 처음 들어본 용어나 이해가 잘 안되는 개념들도 있어 다음날 노트북을 옆에 두고 찾아가며 다시 읽기도 하였다. (그만큼 제대로 읽어보고 싶었다.)&lt;/p>
&lt;p>﻿　책 초반부터 저자는 이 책을 &amp;lsquo;선언&amp;rsquo;이나 &amp;lsquo;정의&amp;rsquo; 가 아닌 애자일에 대한 &amp;lsquo;경험&amp;rsquo;을 토대로 오해를 바로잡는다라고 이야기하고 있다. 2001년 2월, 애자일 선언이 &lt;a href="https://agilemanifesto.org/iso/ko/manifesto.html" target="_blank" rel="noopener noreffer ">발표&lt;/a>가 되었고 내년이면 20년이 돼가는 시점에 여러 가지로 변형된 &amp;lsquo;애자일 방법론&amp;rsquo;이 나왔지만 애자일의 기준을 다시 소개하며 본질을 흐려선 안된다고 이야기한다. (책이 부 제목이 Back to Bascis인 것을 보면 &amp;hellip;)﻿&lt;/p>
&lt;p>﻿　흥미진진한 책 내용 중에 아직까지도 머릿속에 남아있던 애자일과 자주 비교되는 &amp;lsquo;폭포수 모델&amp;rsquo;로 프로젝트를 진행한 부분을 필자가 이해한 대로 적어보려 한다. (너무나도 끔찍하게 공감되기에&amp;hellip;)&lt;/p>
&lt;div class="details admonition quote open">
 &lt;div class="details-summary admonition-title">
 &lt;i class="icon fas fa-quote-right fa-fw" aria-hidden="true">&lt;/i>폭포수 모델로 프로젝트를 진행한 사례&lt;i class="details-icon fas fa-angle-right fa-fw" aria-hidden="true">&lt;/i>
 &lt;/div>
 &lt;div class="details-content">
 &lt;div class="admonition-content">&lt;p>﻿프로젝트 관리자가 마감기한을 확인하고 회의를 진행한다. 지금은 1월이고 출시가 10월이니 각 일정은 거꾸로 계산하여 개발은 QA 기간 고려 9월에 종료, 설계는 7월에, 분석은 늦어도 4월까진 하는 걸로 &amp;lsquo;못 박는다.&amp;rsquo; (네&amp;hellip;?)&lt;/p>
&lt;p>그렇게 여유롭게 시간을 보내다 4월이 되어 분석 단계가 끝난다. 왜? 4월이 됐으니까. 또 시간이 흘러 7월이 되자 기적이 발생한다. 설계 종료. 왜? 7월이 됐으니까. 그 후 남은 2개월 동안 개발자들은 엄청난 압박과 급증하는 야근과 함께 하나둘씩 팀을 떠나고 그만두기 시작한다. QA에서 확인한 버그가 셀 수 없이 쏟아져 나온다. (소름 1)&lt;/p>
&lt;p>하지만 10월에 출시하기로 했으니 버그나 에러가 터져 나오지만 출시를 하고. 프로젝트는 실패로 돌아간다. 그리고 회고를 하고. 다음번엔 제대로 해야지! 하며 다짐한다. (소름 2)&lt;/p>
&lt;p>저자는 이것을 따라잡을 수 없는 프로세스 인플레이션(Runaway Process Inflation)이라고 부른다. 우리는 될 리가 없는 일을 계속하려고 한다. 그것도 아주 많이.&lt;/p></description></item><item><title>Swagger와 Spring Restdocs의 우아한 조합 (by OpenAPI Spec)</title><link>https://taetaetae.github.io/posts/a-combination-of-swagger-and-spring-restdocs/</link><pubDate>Tue, 22 Dec 2020 10:41:40 +0900</pubDate><guid>https://taetaetae.github.io/posts/a-combination-of-swagger-and-spring-restdocs/</guid><description>&lt;p>　﻿MSA 환경에서의 API 문서화는 어떤 식으로 구성하는 걸까? 예컨대, 모듈이 10개 있다고 하면 각 모듈마다 API 문서가 만들어질 테고 API 문서를 클라이언트에 제공하기 위해서 각각의 (10개의) URL를 전달해야 할 텐데 이게 과연 효율적일까? 물론 기능별로 URL이 분리된다는 장점이 있고 굳이 모아보자면 각 API 문서를 다시 한번 크롤링 하여 검색할 수 있도록 제공하는 것도 하나의 방법이 될 수 있다. 하지만 이러한 방법들은 요구 사항을 위한 별도의 작업을 하게 되니 일을 위한 일이 되는 것 같아 뭔가 아쉬웠다. 좋은 방법이 없을까?&lt;/p>
&lt;h2 id="고민의-시작">고민의 시작&lt;/h2>
&lt;p>　한창 궁금증이 머릿속에서 지워지지 않았을때 &lt;a href="https://www.facebook.com/groups/springkorea/permalink/2731936460251299/" target="_blank" rel="noopener noreffer ">Spring 한국 스프링 사용자 모임 페이스북 그룹&lt;/a>에 문의도 해가며 방법을 찾아가고 있었다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-1.png" title="/images/a-combination-of-swagger-and-spring-restdocs/line-1.png" data-thumbnail="/images/a-combination-of-swagger-and-spring-restdocs/line-1.png" data-sub-html="&lt;h2>﻿닉네임이나 프로필 사진은 그들의 개인 정보를 위해 임의로 지정하였다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-1.png"
 data-srcset="https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-1.png, https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-1.png 1.5x, https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-1.png 2x"
 data-sizes="auto"
 alt="/images/a-combination-of-swagger-and-spring-restdocs/line-1.png" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">﻿닉네임이나 프로필 사진은 그들의 개인 정보를 위해 임의로 지정하였다.&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿필자와 함께 개발자의 인생을 시작한 멋진 친구들에게 정확히 올해 6월 초에 고민을 털어놓으며 좋은 방법이 없을지에 대한 논의를 했던 적이 있다. 그런데 친구 중 한 명이 잊고 있었던 그 이슈에 대해서 다시 꺼내며 URL 하나를 던져준다. 참 고마운 친구들.&lt;/p>
&lt;blockquote>
&lt;p>Shout out 34. &lt;a href="http://asuraiv.tistory.com/" target="_blank" rel="noopener noreffer ">asuraiv&lt;/a>, &lt;a href="https://black9p.github.io/" target="_blank" rel="noopener noreffer ">black9p&lt;/a>&lt;/p>&lt;/blockquote>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-2.png" title="/images/a-combination-of-swagger-and-spring-restdocs/line-2.png" data-thumbnail="/images/a-combination-of-swagger-and-spring-restdocs/line-2.png" data-sub-html="&lt;h2>언 반년이 지났으나 필자도 잊고 있었던 이슈를 그는 기억하고 있었다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-2.png"
 data-srcset="https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-2.png, https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-2.png 1.5x, https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-2.png 2x"
 data-sizes="auto"
 alt="/images/a-combination-of-swagger-and-spring-restdocs/line-2.png" width="40%" />
 &lt;/a>&lt;figcaption class="image-caption">언 반년이 지났으나 필자도 잊고 있었던 이슈를 그는 기억하고 있었다.&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿올해 NHN FORWARD에서 진행했던 세션 중에서 &lt;a href="https://forward.nhn.com/session/3" target="_blank" rel="noopener noreffer ">MSA 환경에서 API 문서 관리하기: 생성부터 배포까지&lt;/a>라는 제목의 내용이었고, 정확하게 필자가 고민했던 부분을 콕! 집어서 해결해 준 사례였다. 역시 세상엔 엄청난 고수들이 내가 고민했던 부분들을 이미(혹은 이후에라도) 고민하고 해결한 경우가 많다는 것을 느끼고 공유의 힘이 이렇게도 대단하구나 하며 놀라움을 금치 못하였다.﻿&lt;/p>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/qguXHW0s8RY?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video">&lt;/iframe>
 &lt;/div>

&lt;p>　﻿이번 포스팅에서는 OpenAPI Spec 을 활용하여 Spring Restdocs로 만들어지는 문서를 Swagger UI에서 보는 흐름을 실제로 구현해 보고자 한다. 즉, Swagger 나 Spring Restdocs 뭐로 만들든 간에 OpenAPI Spec에 맞춰서만 만든다면 한곳에서 볼 수 있겠다는 희망이 보였다. 며칠 전 작성한 &lt;a href="https://taetaetae.github.io/posts/openapi-and-swagger-ui-in-spring-boot/" rel="">OpenAPI 와 Swagger-ui 포스팅&lt;/a>을 본 독자들은 지금의 포스팅을 작성하기 위한 밑거름이었다는 사실을 눈치챘을 수도 있을 것 같다.&lt;/p>
&lt;blockquote>
&lt;p>﻿좋은 내용을 공유해 주신 (저의 고민을 완벽하게 해결해 주신) NHN FORWARD 발표자분께 이 포스팅을 빌어 감사의 인사를 보냅니다. :) 당장 팀 내에도 적용해봐야겠어요!!&lt;/p>&lt;/blockquote>
&lt;h2 id="spring-restdocs에서-openapi-spec-추출">﻿Spring Restdocs에서 OpenAPI Spec 추출&lt;/h2>
&lt;p>　﻿누가 또 친절하게 오픈소스로 만들어놨다. &lt;a href="https://github.com/ePages-de/restdocs-api-spec" target="_blank" rel="noopener noreffer ">https://github.com/ePages-de/restdocs-api-spec&lt;/a> 에서 관련 내용을 확인할 수가 있는데 해당 링크에서는 gradle 버전이고 &lt;a href="https://github.com/BerkleyTechnologyServices/restdocs-spec" target="_blank" rel="noopener noreffer ">https://github.com/BerkleyTechnologyServices/restdocs-spec&lt;/a> 는 maven 버전이라고 한다. 마침 필자의 Github에 Maven 버전으로 SpringRestdocs를 세팅해둔 &lt;a href="https://github.com/taetaetae/SpringRestDocs-In-SpringBoot" target="_blank" rel="noopener noreffer ">Repository&lt;/a> 가 있어서 이를 활용해보고자 한다.﻿&lt;/p>
&lt;h3 id="pomxml-추가">pom.xml 추가&lt;/h3>
&lt;p>　﻿관련 dependency를 추가하자. jcenter라고 bintray.com 에서 운영되는 Maven Repository에 올려진 오픈소스이니 repository 도 추가해 주자.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;properties&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;restdocs-api-spec.version&amp;gt;&lt;/span>0.10.0&lt;span class="nt">&amp;lt;/restdocs-api-spec.version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;restdocs-spec.version&amp;gt;&lt;/span>0.19&lt;span class="nt">&amp;lt;/restdocs-spec.version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/properties&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;repositories&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;repository&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;id&amp;gt;&lt;/span>jcenter&lt;span class="nt">&amp;lt;/id&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;url&amp;gt;&lt;/span>https://jcenter.bintray.com&lt;span class="nt">&amp;lt;/url&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/repository&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/repositories&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>com.epages&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>restdocs-api-spec&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>${restdocs-api-spec.version}&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;scope&amp;gt;&lt;/span>test&lt;span class="nt">&amp;lt;/scope&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>com.epages&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>restdocs-api-spec-mockmvc&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>${restdocs-api-spec.version}&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;scope&amp;gt;&lt;/span>test&lt;span class="nt">&amp;lt;/scope&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>﻿위의 dependency에서 제공해 주는 모듈로 테스트의 SpringRestdocs를 만들었다면 OpenAPI Spec 을 만들어 주는 plugin 또한 추가해 주자&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;pluginRepositories&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;pluginRepository&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;id&amp;gt;&lt;/span>jcenter&lt;span class="nt">&amp;lt;/id&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;url&amp;gt;&lt;/span>https://jcenter.bintray.com&lt;span class="nt">&amp;lt;/url&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/pluginRepository&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/pluginRepositories&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;plugin&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>com.github.berkleytechnologyservices.restdocs-spec&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>restdocs-spec-maven-plugin&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>${restdocs-spec.version}&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;executions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;execution&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;goals&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">				&lt;span class="nt">&amp;lt;goal&amp;gt;&lt;/span>generate&lt;span class="nt">&amp;lt;/goal&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;/goals&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;configuration&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">				&lt;span class="nt">&amp;lt;specification&amp;gt;&lt;/span>OPENAPI_V3&lt;span class="nt">&amp;lt;/specification&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">				&lt;span class="nt">&amp;lt;format&amp;gt;&lt;/span>JSON&lt;span class="nt">&amp;lt;/format&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">				&lt;span class="nt">&amp;lt;outputDirectory&amp;gt;&lt;/span>${project.build.directory}/classes/static/docs&lt;span class="nt">&amp;lt;/outputDirectory&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;/configuration&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;/execution&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/executions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/plugin&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>﻿위 plugin 설정을 보면 format 을 JSON으로 한 것을 볼 수 있는데 YAML로도 만들 수 있다. 자세한 사용방법은 위에서 명시한 링크를 참고해보는 게 좋을 것 같다.&lt;/p></description></item><item><title>OpenAPI 와 Swagger-ui 적용하기</title><link>https://taetaetae.github.io/posts/openapi-and-swagger-ui-in-spring-boot/</link><pubDate>Sun, 20 Dec 2020 11:42:40 +0900</pubDate><guid>https://taetaetae.github.io/posts/openapi-and-swagger-ui-in-spring-boot/</guid><description>&lt;p>　﻿API를 개발하고 사용방법에 대한 명세를 작성하는 방법은 여러 가지가 있다. 가장 심플하게 개발 코드와는 별도로 직접 수기로 작성하여 파일 혹은 문서 링크를 전달하는 방법이 있다. 하지만 개발 코드와 별도로 직접 작성을 한다는 점에서 오타/실수가 발생할 수 있고 최신화가 안되는 여러 가지 문제가 발생한다. 그에 등장한 API 문서화 자동화 툴의 양대 산맥인 SpringRestDocs 와 Swagger.&lt;/p>
&lt;p>　﻿과거 &lt;a href="https://taetaetae.github.io/2020/03/08/spring-rest-docs-in-spring-boot/" rel="">SpringRestDocs 에 대한 포스팅&lt;/a>을 했기에 이번엔 Swagger에 대한 사용방법에 대해 정리해보고자 한다. 이 둘의 장단점은 너무 뚜렷하기에 API문서를 제공하는 상황에 따라 적절하게 선택하여 사용할 수 있었으면 좋겠다.&lt;/p>
&lt;h2 id="springboot에-swagger-적용">﻿SpringBoot에 Swagger 적용&lt;/h2>
&lt;p>　기본 SpringBoot 가 셋팅되어 있다는 가정하에 Swagger 관련 dependency를 추가해주자. 아참, 이제부터의 프로젝트 셋팅은 Gradle로 하려한다. (물론 Maven으로 해도 무방하지만&amp;hellip;)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gradle" data-lang="gradle">&lt;span class="line">&lt;span class="cl">&lt;span class="n">dependencies&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">implementation&lt;/span> &lt;span class="s2">&amp;#34;io.springfox:springfox-boot-starter:3.0.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>　﻿이후 JavaConfig 을 아래와 같이 설정하는데 아래 내용은 아주 기본 세팅이니 자세한 내용은 &lt;a href="https://springfox.github.io/springfox/docs/current/#springfox-spring-mvc-and-spring-boot" target="_blank" rel="noopener noreffer ">공식 도큐문서&lt;/a>를 참고해 보면 좋을 것 같다. (물론 샘플 프로젝트를 만들며 필요할 것 같은 내용은 아래에서 설명하겠다.)﻿&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@EnableSwagger2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Configuration&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">SwaggerConfig&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Bean&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Docket&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">api&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Docket&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DocumentationType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">SWAGGER_2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">select&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">apis&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">RequestHandlerSelectors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">any&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">paths&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">PathSelectors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">any&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">build&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>　﻿테스트할 컨트롤러를 아래처럼 심플하게 작성하고(사칙연산&amp;hellip;) 실행을 시킨 후 &lt;code>/swagger-ui/&lt;/code>에 접속을 해보면 swagger 관련 javaConfig 하나만 추가했는데 문서가 만들어진 것을 확인할 수 있다. (http method는 편의상 다양하게 작성했으니 왜 DELETE 인가라는 의문은 접어두자.)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@RestController&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">SampleController&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@GetMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;/addition&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">addition&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num2&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@PostMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;/subtraction&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">subtraction&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num2&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@PutMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;/multiplication&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">multiplication&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num2&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@DeleteMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;/division&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">division&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num2&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/default-config.png" title="/images/openapi-and-swagger-ui-in-spring-boot/default-config.png" data-thumbnail="/images/openapi-and-swagger-ui-in-spring-boot/default-config.png" data-sub-html="&lt;h2>기본 셋팅만 했는데 이런 화면이 나타났다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/default-config.png"
 data-srcset="https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/default-config.png, https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/default-config.png 1.5x, https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/default-config.png 2x"
 data-sizes="auto"
 alt="/images/openapi-and-swagger-ui-in-spring-boot/default-config.png" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">기본 셋팅만 했는데 이런 화면이 나타났다.&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿위에서 했던 설정들 중 몇 가지만 좀 더 자세히 살펴보자.&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>설정&lt;/th>
 &lt;th>설명&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Docket&lt;/td>
 &lt;td>﻿Springfox 프레임 워크의 기본 인터페이스가 될 빌더로 구성을 위한 여러 가지 기본값과 편리한 방법을 제공하고 있다. 이후 &lt;code>select()&lt;/code>로 ApiSelectorBuilder를 반환받아 사용할 수 있도록 해준다.&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>apis&lt;/td>
 &lt;td>﻿어떤 위치에 있는 API들을 가져올 것인가에 대한 정의. &lt;code>RequestHandlerSelectors.any()&lt;/code>이라고 했으니 SpringBoot에서 기본으로 제공하는 &lt;code>basic-error-controller&lt;/code> 도 API 문서로 만들어진 것을 확인할 수 있다. 특정 패키지만 적용하기 위해서는 &lt;code>RequestHandlerSelectors.basePackage(&amp;quot;com.taetaetae.swagger.api&amp;quot;)&lt;/code> 와 같은 형식으로 지정하면 해당 패키지 하위에 있는 Controller를 기준으로 문서를 만들어 준다﻿.&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>paths&lt;/td>
 &lt;td>﻿이름에서도 눈치를 챌 수 있듯이 특정 path만 필터링해서 문서를 만들어 준다.&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>useDefaultResponseMessages&lt;/td>
 &lt;td>﻿기본 http 응답 코드를 사용해야 하는지를 나타내는 플래그&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>﻿이외에도 security 나 공통으로 사용되는 파라미터 등 다양한 옵션을 설정할 수 있으니 가능하면 상황에 맞게 설정을 변경해 보는 것도 좋을 것 같다. 다른 설정들을 추가시켜서 좀 더 친절하게 만들어 보면 아래처럼 만들 수 있고 해당 코드는 &lt;a href="https://github.com/taetaetae/Swagger-In-SpringBoot" target="_blank" rel="noopener noreffer ">Github&lt;/a>에서 확인 가능하다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/change-config.png" title="/images/openapi-and-swagger-ui-in-spring-boot/change-config.png" data-thumbnail="/images/openapi-and-swagger-ui-in-spring-boot/change-config.png" data-sub-html="&lt;h2>API 문서화는 최대한 친절하게!!&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/change-config.png"
 data-srcset="https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/change-config.png, https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/change-config.png 1.5x, https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/change-config.png 2x"
 data-sizes="auto"
 alt="/images/openapi-and-swagger-ui-in-spring-boot/change-config.png" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">API 문서화는 최대한 친절하게!!&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="openapi">OpenAPI&lt;/h2>
&lt;p>　﻿Swagger 공식 홈페이지를 이리저리 둘러보면 OpenAPI라는 내용이 많이 나온다. 그렇다면 OpenAPI는 무엇일까? 문서에 나와있는 내용을 직역해보면 Swagger 사양으로 알려져 있으며 RESTful 웹 서비스를 설명, 생성, 소비 및 시각화하기 위한 기계 판독 가능 인터페이스 파일에 대한 사양이라고 한다. 즉, API 자체를 설명하는 인터페이스 스펙이라고 이해를 해볼 수 있다. 위에서 만들어졌던 Swagger를 보면 http://localhost:8080/v2/api-docs?group=Test API 라고 나와있는데 이를 클릭해보면 아래와 같이 json 형태로 보인다. 다시 보면 우리가 이제까지 Swagger으로 만든 API 문서를 설명하는 인터페이스 스펙으로 이해할 수 있다. (꽤 길어서 덧셈 API를 제외하고 지웠다.) 그렇다면 이 JSON 파일을 어떻게 활용할 수 있을까?﻿&lt;/p></description></item><item><title>Jenkins Job을 병렬로 실행해서 속도를 개선해보자. (by. Pipeline)</title><link>https://taetaetae.github.io/posts/jenkins-job-parallel-processing-by-pipeline/</link><pubDate>Sun, 06 Dec 2020 20:19:47 +0900</pubDate><guid>https://taetaetae.github.io/posts/jenkins-job-parallel-processing-by-pipeline/</guid><description>&lt;p>﻿　관리하는 URL이 &lt;code>200&lt;/code>응답을 주고 있는지 모니터링을 한다고 가정해보자. 다양한 방법이 생각나겠지만 가장 처음으로 떠오른 건 단연 &lt;code>Jenkins&lt;/code>. 간단하게 사용할 언어에 맞춰 &lt;code>Execute Script&lt;/code>를 작성하고 스케줄링을 걸어 놓으면 큰 수고 없이 모니터링을 구성할 수 있게 된다. 아래는 python script로 작성해 보았다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;http://모니터링url&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">status_code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">status_code&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답 실패 :&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">, status : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">status_code&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="n">exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>﻿　하지만 모니터링을 해야 하는 URL이 1개에서 여러 개로 늘어난다면 어떻게 될까? 단순하게 작성한 Script를 아래처럼 약간 수정하면 되긴 하지만 URL마다 응답속도가 다를 경우 순차적으로 실행하다 보니 실행 속도는 느릴 수밖에 없다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">urls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s2">&amp;#34;http://모니터링url-1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s2">&amp;#34;http://모니터링url-2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s2">&amp;#34;http://모니터링url-3&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">url&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">urls&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="n">status_code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="k">if&lt;/span> &lt;span class="n">status_code&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답 실패 :&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">, status : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">status_code&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="n">exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>﻿
　이러한 경우, 빠른 속도를 보장하기 위해서는 병렬로 실행을 해야 한다는 건 누구나 다 알지만 그렇다고 Thread를 사용하기엔 벌써부터 덜컥 부담이 된다. 그렇다고 Job을 URL 개수만큼 늘리기에는 배보다 배꼽이 더 커버리고&amp;hellip; 그러다 발견한 기능이 바로 Jenkins Pipeline!&lt;/p>
&lt;p>　이번 포스팅에서는 Jenkins Job을 동시에 여러 번 사용해야 하는 경우를 Pipeline을 통해서 개선한 내용에 대하여 공유해보려 한다. Jenkins Pipeline에 대해 들어만 봤는데 이번에 실제로 사용해보니 생각보다 쉽게 개선할 수 있었고 옵션들을 상황에 맞게 조합을 잘 한다면 상당히 활용성이 높아 보이는 기능인 것 같다.&lt;/p>
&lt;h2 id="기존상황">기존상황&lt;/h2>
&lt;p>　테﻿스트를 위해 임의로 느린 응답을 생성하도록 URL을 구성하고 위에서 이야기했던 것처럼 Job 하나에 아주 심플하게 Python script를 작성하고 실행해보도록 하자. 임의로 느린 응답은 &lt;a href="http://slowwly.robertomurray.co.uk/" target="_blank" rel="noopener noreffer ">http://slowwly.robertomurray.co.uk/&lt;/a> 에서 제공하는 기능을 활용하였다.﻿&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">urls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/0/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/100/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/200/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/500/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/1000/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/2000/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/5000/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/10000/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/20000/url/https://www.naver.com/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">url&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">urls&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="n">status_code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="k">if&lt;/span> &lt;span class="n">status_code&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답 실패 :&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">, status : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">status_code&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="n">exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답성공 : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그래서 실행해보면 50초가 소요되었다. 자, 이제 개선을 해보자!&lt;/p>
&lt;h2 id="개선을-해보자">개선을 해보자&lt;/h2>
&lt;p>　﻿전체적인 개선의 흐름은 하나의 Job에 모니터링하고자 하는 url을 파라미터로 받아서 처리할 수 있도록 설정하고, 이를 Jenkins Pipeline 을 통해 여러 URL을 동시에 모니터링하게 구성하는 것이다. 그러면 두 개의 Job(파라미터로 받아 모니터링하는 Job, Jenkins Pipeline Job) 만으로 보다 빠르고 효율적인 구성을 할 수 있을 것으로 상상을 하고.&lt;/p>
&lt;h3 id="job을-범용적으로-jenkins-paramters-활용">Job을 범용적으로 (Jenkins paramters 활용)&lt;/h3>
&lt;p>　﻿위에서 샘플로 작성하였던 Python script는 url 이 늘어날수록 Job 안에 script를 수정해야 한다. 그렇게 해도 무방하지만 이번 개선의 목표는 하나의 Job을 Pipeline 이 병렬로 컨트롤하도록 설정해야 했기 때문에 Jenkins Job에 파라미터를 받을 수 있도록 아래처럼 Jenkins Job 설정에 파라미터를 설정하고 Python script 또한 수정해 주자.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg" title="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg" data-thumbnail="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg" data-sub-html="&lt;h2>﻿Job &amp;gt; 구성 &amp;gt; 이 빌드는 매개변수가 있습니다&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg"
 data-srcset="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg 1.5x, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg 2x"
 data-sizes="auto"
 alt="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">﻿Job &amp;gt; 구성 &amp;gt; &lt;code>이 빌드는 매개변수가 있습니다&lt;/code>&lt;/figcaption>
 &lt;/figure>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">environ&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;url&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">status_code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">status_code&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답 실패 :&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">, status : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">status_code&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답성공 : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="병렬-실행을-위한-jenkins-설정">병렬 실행을 위한 Jenkins 설정&lt;/h3>
&lt;p>　﻿Jenkins Job 을 생성하면 기본적으로 Job마다의 대기열(Queue)이 있어 Job이 실행 중이라면 시작된 시간 순서대로 기다렸다가 앞선 Job이 종료가 되면 이어서 실행되는 구조이다. 하지만 우리는 Job을 병렬로 실행해야 했기에 Job 설정 중 &lt;code>필요한 경우 concurrent 빌드 실행&lt;/code> 옵션을 켜줘서 기다리지 않고 병렬로 실행될 수 있도록 해준다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg" title="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg" data-thumbnail="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg" data-sub-html="&lt;h2>﻿Job &amp;gt; 구성 &amp;gt; 필요한 경우 concurrent 빌드 실행&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg"
 data-srcset="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg 1.5x, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg 2x"
 data-sizes="auto"
 alt="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">﻿Job &amp;gt; 구성 &amp;gt; &lt;code>필요한 경우 concurrent 빌드 실행&lt;/code>&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿또한 Jenkins Job 자체는 병렬로 실행되도록 설정되었다 해도 기본적으로 Jenkins 자체의 대기열은 한정되어 있기 때문에 적당히 늘려줘서 여러 개의 Job이 대기 열 없이 동시에 실행될 수 있도록 해준다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg" title="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg" data-thumbnail="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg" data-sub-html="&lt;h2>﻿Jenkins &amp;gt; Jenkins 관리 &amp;gt; 시스템 설정 &amp;gt; of executors&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg"
 data-srcset="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg 1.5x, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg 2x"
 data-sizes="auto"
 alt="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">﻿Jenkins &amp;gt; Jenkins 관리 &amp;gt; 시스템 설정 &amp;gt; &lt;code>of executors&lt;/code>&lt;/figcaption>
 &lt;/figure>
&lt;h3 id="jenkins-pipeline">Jenkins Pipeline&lt;/h3>
&lt;p>　﻿Job 을 Pipeline으로 만들고 Pipeline scirpt를 작성하는데 눈여겨봐야 할 옵션들을 짚고 넘어가고자 한다.&lt;/p></description></item><item><title>기술블로그 개편기 (by HUGO)</title><link>https://taetaetae.github.io/posts/blog-reorganization-by-hugo/</link><pubDate>Sun, 29 Nov 2020 18:12:15 +0900</pubDate><guid>https://taetaetae.github.io/posts/blog-reorganization-by-hugo/</guid><description>&lt;p>　웹서비스 개발자라면 나만의 블로그쯤은 있어야지 하며 기술 블로그를 시작한 지도 어느덧 4년이 되었다. 처음엔 그저 새로 알게 된 기술이나 삽질하며 경험한 것들 중에 핵심만을 적어놓는 수준이었다. (지금 다시 보면 뭔가 오글거리는 건 기분 탓이겠지&amp;hellip;) 그렇게 계속 글을 써오면서 글쓰기라는 것에 관심을 갖게 되고 내 글이 누군가에게 도움이 될 거라는 기대에 조금이라도 글을 잘 써보고자 단순 기록 용이 아닌 하나의 &amp;lsquo;글&amp;rsquo;을 쓰려고 노력해 온 것 같다.&lt;/p>
&lt;p>　일주일에 한 개는 써야지. 한 달에 한 개는 써야지. 하며 자꾸 나 자신과의 타협을 하다가 최근에는 회사에서 운영하는 서비스 개편 때문에 정신없이 바쁘다는 핑계로 &amp;lsquo;블로그&amp;rsquo;에 &amp;lsquo;ㅂ&amp;rsquo;자도 생각하지 못하게 된다. 무엇이 문제일까?라는 생각은 결국 내 기술 블로그도 회사 서비스처럼 &amp;lsquo;개편&amp;rsquo;을 해보자는 생각으로 도달하게 되었고 간단할 것만 같았던 기술 블로그 개편 작업은 꽤 오랫동안 + 다양한 삽질들로 작업을 하게 된다.&lt;/p>
&lt;p>　이번 포스팅에서는 기술 블로그를 개편하며 겪었던 내용들에 대해 정리해보고자 한다. 기존에 기술 블로그를 운영하시는 분들이나 이번에 새롭게 시작하시는 분들께 도움이 될 거라 기대한다. 더불어 서비스 &amp;lsquo;출시&amp;rsquo; 가 아닌 개편&amp;rsquo;이라는 과정 속에서 느끼게 되었던 인사이트도 간략하게 작성해볼까 한다.&lt;/p>
&lt;h2 id="기술블로그-플랫폼-선택">기술블로그 플랫폼 선택&lt;/h2>
&lt;p>　처음 블로그를 쓰기 시작했을 때 포털서비스의 글쓰기 플랫폼을 사용하지 않은 이유는 단 하나다. &amp;lsquo;글쓰기&amp;rsquo; 뿐만 아니라 개발자이기에 웹사이트(블로그)를 내 입맛에 맞게 커스터마이징 하기 위해서. 그 이유로 &lt;a href="https://hexo.io/" target="_blank" rel="noopener noreffer ">hexo&lt;/a> 라는 프레임워크에 github의 호스팅을 사용하여 운영을 해왔다. 그렇게 블로그를 운영해오면서 느꼈던 불편했던 부분들과 개편을 하며 기대하는 부분들을 정리하면 아래와 같다.&lt;/p>
&lt;ul>
&lt;li>테마(UI)가 이뻐야 하고 기능들이 많으면 좋겠다.&lt;/li>
&lt;li>기술 블로그인 만큼 코드가 많이 삽입되니 코드 표현 또한 이뻐야 한다.&lt;/li>
&lt;li>테마 또는 프레임워크의 커뮤니티가 활발해야 한다.&lt;/li>
&lt;li>페이지 생성 또는 만들어진 웹페이지의 성능이 좋아야 한다.&lt;/li>
&lt;li>글을 작성하고 배포하는 과정이 심플하고 깔끔해야 한다.
﻿&lt;/li>
&lt;/ul>
&lt;p>　위와 같은 이유를 기반으로 검색을 해보다 SSG(쓱 쇼핑몰 아님, Static site generators)를 깔끔하게 정리해 놓은 &lt;a href="https://jamstack.org/generators/" target="_blank" rel="noopener noreffer ">사이트&lt;/a>를 발견한다. 정말 다양한 플랫폼들을 살펴보며 필자에게 맞는 게 어떤 건지 고민하다 결국 &lt;a href="https://gohugo.io/" target="_blank" rel="noopener noreffer ">hugo&lt;/a> 를 선택하게 된다. hugo를 선택한 이유는 go라는 언어를 사용한다는 것과 (간접적으로라도 다른 언어를 경험해보고 싶어서 + go 언어가 빠르다는 소리를 어디선가 들어서) &lt;a href="https://themes.gohugo.io/" target="_blank" rel="noopener noreffer ">테마들&lt;/a>이 너무 다양했기 때문이다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/blog-reorganization-by-hugo/hugo_homepage.jpg" title="/images/blog-reorganization-by-hugo/hugo_homepage.jpg" data-thumbnail="/images/blog-reorganization-by-hugo/hugo_homepage.jpg" data-sub-html="&lt;h2>﻿아주 대놓고 빠르다고 하니&amp;hellip; 쓰고 싶어진다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/blog-reorganization-by-hugo/hugo_homepage.jpg"
 data-srcset="https://taetaetae.github.io/images/blog-reorganization-by-hugo/hugo_homepage.jpg, https://taetaetae.github.io/images/blog-reorganization-by-hugo/hugo_homepage.jpg 1.5x, https://taetaetae.github.io/images/blog-reorganization-by-hugo/hugo_homepage.jpg 2x"
 data-sizes="auto"
 alt="/images/blog-reorganization-by-hugo/hugo_homepage.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">﻿아주 대놓고 빠르다고 하니&amp;hellip; 쓰고 싶어진다.&lt;/figcaption>
 &lt;/figure>
&lt;p>　결국 hugo에 &lt;a href="https://hugo-ranking-trend.com/" target="_blank" rel="noopener noreffer ">hugo-ranking-trend&lt;/a>라는 사이트에서 상위에 랭크가 되어있고 기술 블로그 성격에 적합할 것 같은 &lt;a href="https://github.com/dillonzq/LoveIt" target="_blank" rel="noopener noreffer ">LoveIt&lt;/a>이라는 테마를 사용하기로 결정하였다. 자 그럼 시작해볼까?!&lt;/p>
&lt;h2 id="hugo-는-어떻게-쓰는거야">hugo 는 어떻게 쓰는거야?&lt;/h2>
&lt;p>﻿　대부분의 오픈소스는 hello world 혹은 quick start 같이 처음 접하는 사람들을 위한 도큐먼트가 있기 마련. hugo도 마찬가지로 &lt;a href="https://gohugo.io/getting-started/quick-start/" target="_blank" rel="noopener noreffer ">quick-start&lt;/a>가 있었고 이를 천천히 따라 하면 생각보다 쉽게 초기 세팅을 할 수 있었&amp;hellip; 을꺼라 기대했지만 약간 초기 설정 과정이 어려워서 남겨 두고자 한다.&lt;/p>
&lt;blockquote>
&lt;p>참고로 필자는 윈도 10 환경에서 구성하였다. mac이라면 더 쉽게 설정할 수 있는 것 같은데 이 부분은 OS의 차이에서 생겨나는 어쩔 수 없는 약간의 장벽이라 생각한다. 이쁜 테마와 새로운 환경을 사용할 수 있다는 기대감으로 꾹 참아본다.&lt;/p>&lt;/blockquote>
&lt;h3 id="기본설정">기본설정&lt;/h3>
&lt;p>　﻿git이 설치되어 있다는 가정하에 우선 hugo는 go 언어기반으로 돌아가기에 우선 go를 설치해야 한다. &lt;a href="https://golang.org/dl/" target="_blank" rel="noopener noreffer ">다운로드&lt;/a>페이지에서 환경에 맞는 설치 파일을 다운로드하고 설치를 해준다. 다음으로 패키지 관리자인 chocolatey 또한 설치가 필요하다. &lt;a href="https://chocolatey.org/install" target="_blank" rel="noopener noreffer ">공식 홈페이지&lt;/a>페이지에서 나와있는 순서대로 진행하면 설치 완료. 필자는 여기서 진행이 잘 안됐었는데, &amp;lsquo;관리자 권한&amp;rsquo;으로 PowerShell 을 실행시켜야지만 성공을 할 수 있었다.﻿&lt;/p>
&lt;p>﻿　위 설정이 완료되었으면 드디어 hugo를 설치해 주고 초기화를 해준 뒤 샘플로 글 하나를 만들고 서버를 띄우면 끝.&lt;/p></description></item><item><title>그런 개발자로 괜찮은가 - '로그 &amp; 모니터링' 편</title><link>https://taetaetae.github.io/2020/10/04/a-good-developer-in-terms-of-log-and-monitoring/</link><pubDate>Sun, 04 Oct 2020 15:39:15 +0000</pubDate><guid>https://taetaetae.github.io/2020/10/04/a-good-developer-in-terms-of-log-and-monitoring/</guid><description>&lt;p>　캐릭터를 육성하며 게임하는 경우를 생각해 보자. 더 좋은 아이템을 얻거나 퀘스트를 달성하기 위해 당신은 다양한 방법을 통해 캐릭터를 성장시킨다. 사냥을 하다 체력이 떨어지게 되면 물약을 먹고, &lt;!--more -->캐릭터의 능력 중 부족한 부분이 있으면 훈련을 더 하거나 그에 맞는 아이템을 장착하게 된다. 이렇게 캐릭터의 &amp;lsquo;상태&amp;rsquo;를 적절한 UI를 통해 사용자에게 알려주기 때문에 &amp;lsquo;확인&amp;rsquo;이 가능하고 &amp;lsquo;대응&amp;rsquo;이 가능하게 된다.&lt;/p>
&lt;p>　우리가 만드는 애플리케이션 또한 위에서 이야기 한 게임상의 캐릭터가 아닐까 싶다. 복잡한 스펙을 다양한 테스트 케이스를 만들며 로직 동작에는 이상이 없음을 확인했다면 그걸로 만족할 수 있을까? 개발자의 &amp;lsquo;레벨&amp;rsquo;은 이 부분에서 차이가 난다고 생각한다. 운영환경에 출시한 애플리케이션에 에러가 나는지, 트래픽이 얼마나 들어오고 있고 트래픽의 유형은 또 어떠한지, 요청에 대한 응답속도는 어떻고 서버의 시스템 지표에는 문제가 없는지 등등. 애플리케이션의 유형에 따라 다양하겠지만 적절한 로그를 이용하여 애플리케이션의 &amp;lsquo;상태&amp;rsquo;를 확인하고 문제가 있다면 &amp;lsquo;대응&amp;rsquo;하는 게 꼭 필요하다고 생각한다.&lt;/p>
&lt;p>　이번 포스팅에서는 크게 로깅과 모니터링에 대해 알아보고자 한다. 이를 통해 애플리케이션의 &amp;lsquo;개발&amp;rsquo;에만 집중하고 있던 관점을 보다 더 높은 곳에서 바라보며 애플리케이션의 &amp;lsquo;운영&amp;rsquo; 측면에서도 고민해 보는 기회가 되었으면 한다.&lt;/p>
&lt;blockquote>
&lt;p>필자는 서버 개발자이다 보니 글의 내용이 다소 서버 개발자의 시선에서 작성하게 되었다. 하지만 &amp;lsquo;개발자&amp;rsquo;라면 유형만 다르지 대부분 비슷하기 때문에 크게 다르지 않다고 생각한다.&lt;/p>&lt;/blockquote>
&lt;h2 id="로그는-어떤걸-어떻게-남겨야-할까">로그는 어떤걸, 어떻게 남겨야 할까?&lt;/h2>
&lt;p>　﻿로그가 왜 필요한지에 대한 내용은 다루지 않겠다. (굳이 말하지 않아도 그만큼 중요하다는 표현이 더 어울릴 수도 있겠다.) 그렇다면 우선 어떤 로그를 남겨야 할까?&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/a-good-developer-in-terms-of-Log-and-Monitoring/talk.jpg" title="/images/a-good-developer-in-terms-of-Log-and-Monitoring/talk.jpg" data-thumbnail="/images/a-good-developer-in-terms-of-Log-and-Monitoring/talk.jpg" data-sub-html="&lt;h2>필자가 꿈나무 시절때 나누었던 조직장님과의 대화 내용&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/a-good-developer-in-terms-of-Log-and-Monitoring/talk.jpg"
 data-srcset="https://taetaetae.github.io/images/a-good-developer-in-terms-of-Log-and-Monitoring/talk.jpg, https://taetaetae.github.io/images/a-good-developer-in-terms-of-Log-and-Monitoring/talk.jpg 1.5x, https://taetaetae.github.io/images/a-good-developer-in-terms-of-Log-and-Monitoring/talk.jpg 2x"
 data-sizes="auto"
 alt="/images/a-good-developer-in-terms-of-Log-and-Monitoring/talk.jpg" width="40%" />
 &lt;/a>&lt;figcaption class="image-caption">필자가 꿈나무 시절때 나누었던 조직장님과의 대화 내용&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿아직까지도 기억에 남아있는 예전 조직 장님과의 대화. 일단 로그는 최대한 많이 (과하게) 남겨야 한다고 생각한다. 그다음 불필요한 로그들은 제거하거나 레벨을 낮추는 등 상황에 맞도록 커스터마이징이 필요하다. 경험을 해보면 알겠지만 운영환경에 애플리케이션을 배포하고 서비스를 운영하다 보면 개발 환경에서 만나기 어렵거나 경험해보지 못한 상황이 발생하곤 한다. 이럴 때 상황에 맞는 로그들이 있다면 미리 남겨둔 로그를 통해 더 효과적으로 상황을 파악할 수 있다. 트래픽의 정보(request url, parameter, UA, remote ip 등)를 남겨서 외부에서 호출하는 형태를 분석하는데 활용할 수도 있고, 애플리케이션에서 외부로 호출을 하고 난 뒤에 받는 응답에 대해서 로그를 남겨두면 외부 통신의 오류를 파악하는 데 도움이 될 수 있다. 어떤 로그를 남겨야 하는가에 대한 고민은 운영하는 애플리케이션이 어떤 행동을 하는가에 관점을 두고 고민해보면 좀 더 쉽게 찾을 수 있을 것이라 생각한다.&lt;/p>
&lt;p>　로그를 남기는 방법 또한 다양하다. 시스템 로컬에 파일로 남기거나 특정 로그 서버를 설정하여 여러 대의 서버 로그를 한곳에서 볼 수도 있다. 다만 로그를 &amp;lsquo;남기는&amp;rsquo; 것 또한 하나의 비용에 포함되기 때문에 애플리케이션의 기능에 최대한 영향이 가지 않도록 최대한 빠른 시간 내에 처리가 되도록 해야 한다. (혹은 비동기로 남기거나 등)&lt;/p>
&lt;p>　로그를 남기는 이유 중 가장 큰 이유는 &amp;lsquo;나중에 보기 위해서&amp;rsquo;이다. 그만큼 한번 로그를 남길 때에도 보기 좋게 남겨야 한다. 예컨대, 아래에 적어놓은 로그 방식의 경우 작은 차이지만 나중에 볼 때 꽤 큰 차이를 유발한다.
﻿&lt;/p>
&lt;ul>
&lt;li>안좋은 예&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">catch&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Exception&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">){&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 어떤 상황이지..?&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>보다 조금 더 좋은 예&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">catch&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Exception&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">){&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;url : &amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;, parameter : &amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">parameter&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;, remote ip : &amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">remoteIp&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 로그는 가급적 자세하게 !&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="로그가-가져다-주는-또-다른-세상">로그가 가져다 주는 또 다른 세상&lt;/h2>
&lt;p>　로그는 또 다른 데이터가 될 수 있다. 글 목록을 보여주는 웹페이지가 있다고 가정해보자. 이때 사용자들이 어떤 글을 더 많이 읽는지 &amp;lsquo;클릭 지표&amp;rsquo;에 대한 로그를 남겨 둔다면 &amp;lsquo;인기글&amp;rsquo; 같은 또 다른 웹 페이지가 나올 수 있을 것 같다. 만약 그 페이지가 회원만 읽을 수 있는 페이지라면 &amp;lsquo;20대 남성이 월요일 오후에 많이 읽는 글&amp;rsquo; 같이 회원의 정보를 조합하여 새로운 데이터를 만들 수 있게 된다. 이러한 데이터들은 보다 더 좋은 서비스를 할 수 있게 도와줄 수 있는 밑거름이 되고 그 바탕은 로그라는 걸 명심하자.&lt;/p></description></item><item><title>빌드/테스트는 내가 해줄게. 너는 코딩에 집중해 (by GitHub Pull Request Builder)</title><link>https://taetaetae.github.io/2020/09/07/github-pullrequest-build/</link><pubDate>Mon, 07 Sep 2020 10:09:56 +0000</pubDate><guid>https://taetaetae.github.io/2020/09/07/github-pullrequest-build/</guid><description>&lt;p>　git 은 분산 버전 관리 시스템 중 가장 잘 알려져 있다고 해도 과언이 아닐 정도로 대부분의 시스템에서 사용되고 있는 것 같다. 이를 웹서비스에서 보다 편하게 사용할 수 있도록 한 시스템이 Github. &lt;!--more -->Github 을 사용하는 이유 중에 가장 큰 이유를 하나만 이야기해보자면 바로 온라인상에서 코드 리뷰를 할 수 있는 pullRequest라는 기능 때문이 아닐까 조심스럽게 생각을 해본다.&lt;/p>
&lt;p>　pullRequest는 work branch에서 작업한 내용을 base branch로 merge 전 꼭 코드 리뷰가 아니더라도 작업한 내용에 대해서 다양한 검사를 자동화할 수 있는 강력한 기능들이 많다. 이러한 자동화는 CI(지속적 통합) 관점에서 매우 중요한데 코드에 대해 체크해야 할 부분들(빌드, 테스트, 정적 분석 등)을 &amp;ldquo;알아서&amp;rdquo; 해준다면 작업자는 오롯이 비즈니스 로직 개발에 대해서만 신경 쓸 수 있으니 생산성 절약 측면에서 엄청난 효과를 볼 수 있다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-pullrequest-build/car.gif" title="/images/github-pullrequest-build/car.gif" data-thumbnail="/images/github-pullrequest-build/car.gif" data-sub-html="&lt;h2>내가 하는일에만 집중할 수 있게! 출처 : https://www.clien.net/service/board/park/10453442&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-pullrequest-build/car.gif"
 data-srcset="https://taetaetae.github.io/images/github-pullrequest-build/car.gif, https://taetaetae.github.io/images/github-pullrequest-build/car.gif 1.5x, https://taetaetae.github.io/images/github-pullrequest-build/car.gif 2x"
 data-sizes="auto"
 alt="/images/github-pullrequest-build/car.gif" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">내가 하는일에만 집중할 수 있게! &lt;br> 출처 : &lt;a href="https://www.clien.net/service/board/park/10453442" target="_blank" rel="noopener noreffer ">https://www.clien.net/service/board/park/10453442&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>이번 포스팅에서는 그중에서도 아주 간단한 설정만으로 work branch의 빌드 상태를 검사해 볼 수 있는 Jenkins의 Github Pull Request Builder를 설치 및 활용해 보고자 한다.&lt;/p>
&lt;blockquote>
&lt;p>사실 최근 팀에서 CI 서버를 이전해야 했었다. 머릿속에서는 어떻게 하면 되겠지 싶었지만 막상 해보려니 Jenkins 버전업도 되었고 뭐부터 해야 할지 허둥대는 필자가 부끄러웠다. 이참에 정리를 해보며 다시 한번 리마인드 하는 시간을 가져보고자 한다. (이래서 기억보다 기록이 중요하다.)&lt;/p>&lt;/blockquote>
&lt;h2 id="준비물">준비물&lt;/h2>
&lt;p>　전체적인 흐름은 아래 그림처럼 흘러가기 때문에 당연히 서버에 Jenkins 가 설치되어 있어야 한다. Jenkins 설치는 필자의 포스팅(&lt;a href="https://taetaetae.github.io/2018/12/02/jenkins-install/" target="_blank" rel="noopener noreffer ">Jenkins 설치 치트키&lt;/a>)를 참고해 보는 것도 좋을 것 같다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-pullrequest-build/programmer-github-jenkins.jpg" title="/images/github-pullrequest-build/programmer-github-jenkins.jpg" data-thumbnail="/images/github-pullrequest-build/programmer-github-jenkins.jpg" data-sub-html="&lt;h2>전체적인 흐름&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-pullrequest-build/programmer-github-jenkins.jpg"
 data-srcset="https://taetaetae.github.io/images/github-pullrequest-build/programmer-github-jenkins.jpg, https://taetaetae.github.io/images/github-pullrequest-build/programmer-github-jenkins.jpg 1.5x, https://taetaetae.github.io/images/github-pullrequest-build/programmer-github-jenkins.jpg 2x"
 data-sizes="auto"
 alt="/images/github-pullrequest-build/programmer-github-jenkins.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">전체적인 흐름&lt;/figcaption>
 &lt;/figure>
&lt;p>　참고로 필자는 GitHub Enterprise 버전에서 사용했는데 일반 Github에서도 동일한 방법으로 사용 가능하다.&lt;/p>
&lt;h3 id="github과-jenkins의-연동을-위한-2가지-설정">Github과 Jenkins의 연동을 위한 2가지 설정&lt;/h3>
&lt;p>　Github 과 Jenkins 가 통신이 되도록 설정해 줘야 한다. 그래야 Github의 코드를 받아서 Jenkins 가 빌드를 하고 그 빌드 결과를 다시 Github에 리포트가 가능해지기 때문이다. 먼저 첫 번째로 ssh 설정으로 Github의 코드를 가져오도록 ssh 설정을 해두자. ssh 설정하는 방법은 필자의 포스팅(&lt;a href="https://taetaetae.github.io/2018/02/08/github-with-jenkins/" target="_blank" rel="noopener noreffer ">Github과 Jenkins 연동하기&lt;/a>)편을 확인해보면 될 것 같다.&lt;/p>
&lt;p>　그다음으로 아래에서 이야기할 &lt;code>GitHub Pull Request Builder&lt;/code>라는 Jenkins plugin 이 빌드가 끝난 뒤에 결과를 리포팅 해줄 수 있는 인증 토큰을 발급받아두자. Github &amp;gt; Settings &amp;gt; Developer settings &amp;gt; Personal access tokens 화면에서 키를 생성하고 만들어진 키를 저장해 둔다. (이 키는 보안에 유의해야 하고, 화면 경고(?)에서도 볼 수 있듯이 키는 생성 시 한 번밖에 볼 수 없기 때문에 미리 저장해 둬야 한다.)&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-pullrequest-build/github-access-token.jpg" title="/images/github-pullrequest-build/github-access-token.jpg" data-thumbnail="/images/github-pullrequest-build/github-access-token.jpg" data-sub-html="&lt;h2>인증토큰을 미리 받아두자.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-pullrequest-build/github-access-token.jpg"
 data-srcset="https://taetaetae.github.io/images/github-pullrequest-build/github-access-token.jpg, https://taetaetae.github.io/images/github-pullrequest-build/github-access-token.jpg 1.5x, https://taetaetae.github.io/images/github-pullrequest-build/github-access-token.jpg 2x"
 data-sizes="auto"
 alt="/images/github-pullrequest-build/github-access-token.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">인증토큰을 미리 받아두자.&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="jenkins-설정">Jenkins 설정&lt;/h2>
&lt;p>　Jenkins &amp;gt; 관리 &amp;gt; pluginManager에 들어가 &lt;code>GitHub Pull Request Builder&lt;/code>를 검색 후 설치해 준다. 그러고 나서 Jenkins &amp;gt; 관리 &amp;gt; 환경설정에 들어가 보면 아래와 같이 &lt;code>GitHub Pull Request Builder&lt;/code> 항목이 생긴 것을 확인할 수 있고 위에서 설정한 인증토큰을 아래처럼 등록 후 저장을 한다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-pullrequest-build/add-github-access-token.jpg" title="/images/github-pullrequest-build/add-github-access-token.jpg" data-thumbnail="/images/github-pullrequest-build/add-github-access-token.jpg" data-sub-html="&lt;h2>credentials 을 위에서 발급받은 인증토큰으로 등록해준다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-pullrequest-build/add-github-access-token.jpg"
 data-srcset="https://taetaetae.github.io/images/github-pullrequest-build/add-github-access-token.jpg, https://taetaetae.github.io/images/github-pullrequest-build/add-github-access-token.jpg 1.5x, https://taetaetae.github.io/images/github-pullrequest-build/add-github-access-token.jpg 2x"
 data-sizes="auto"
 alt="/images/github-pullrequest-build/add-github-access-token.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">credentials 을 위에서 발급받은 인증토큰으로 등록해준다.&lt;/figcaption>
 &lt;/figure>
&lt;p>　Jenkins job을 하나 만들고 pullRequest 가 발생했을 때 자동으로 실행될 수 있도록 설정을 해준다. 먼저 General 탭에 &lt;code>Github project&lt;/code>에 Github url 을 적어주고&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-pullrequest-build/jenkins-general.jpg" title="/images/github-pullrequest-build/jenkins-general.jpg" data-thumbnail="/images/github-pullrequest-build/jenkins-general.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-pullrequest-build/jenkins-general.jpg"
 data-srcset="https://taetaetae.github.io/images/github-pullrequest-build/jenkins-general.jpg, https://taetaetae.github.io/images/github-pullrequest-build/jenkins-general.jpg 1.5x, https://taetaetae.github.io/images/github-pullrequest-build/jenkins-general.jpg 2x"
 data-sizes="auto"
 alt="/images/github-pullrequest-build/jenkins-general.jpg" width="50%" />
 &lt;/a>
&lt;p>　소스 코드 관리 탭에서 ssh 주소를 적고 위에서 미리 설정한 ssh 키로 credentials 값을 넣어준다. 전에도 이야기했지만 이 부분에서 오류가 발생하면 빨간색 글씨로 오류 내용이 나오고 아래 화면처럼 오류가 없다면 아무것도 안 나온다. Refspec 에 &lt;code>+refs/pull/*:refs/remotes/origin/pr/*&lt;/code> 라고 적어주고 브랜치 설정은 파라미터로 받아와서 pullRequest를 발생시킨 브랜치를 빌드 할 수 있도록 &lt;code>${sha1}&lt;/code> 라고 적어주자.&lt;/p></description></item><item><title>벌써 2년 (feat. 토이프로젝트 회고,가치,수입)</title><link>https://taetaetae.github.io/2020/07/12/toy-projects-second-year-review/</link><pubDate>Sun, 12 Jul 2020 19:55:02 +0000</pubDate><guid>https://taetaetae.github.io/2020/07/12/toy-projects-second-year-review/</guid><description>&lt;p>　정확히 2018년 07월 12일 필자의 첫 토이 프로젝트인 ‘기술 블로그 구독 서비스’를 오픈하게 된다. 얼마나 많이 구독(가입) 하겠어 하는 생각이 부끄러울 만큼 6개월이 지나 구독자 수는 1,000명을 넘기고 1년이 지나 2,000명.&lt;!--more --> 어느덧 달력을 보니 오늘이 정확하게 토이 프로젝트를 서비스한지 벌써 2년이 되는 날. 구독자 수는 어느덧 3,000명을 넘어선다. 뭔가 뿌듯하면서도 서비스를 좀 더 디벨롭 하지 못한 필자 자신을 돌아보니 괜히 마음이 무거워지고.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/toy-projects-second-year-review/dog.jpg" title="/images/toy-projects-second-year-review/dog.jpg" data-thumbnail="/images/toy-projects-second-year-review/dog.jpg" data-sub-html="&lt;h2>뭔가 해야하는데&amp;hellip; 괜히 눈치만 보이네&amp;hellip;출처 : http://egloos.zum.com/nievess/v/657827&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/toy-projects-second-year-review/dog.jpg"
 data-srcset="https://taetaetae.github.io/images/toy-projects-second-year-review/dog.jpg, https://taetaetae.github.io/images/toy-projects-second-year-review/dog.jpg 1.5x, https://taetaetae.github.io/images/toy-projects-second-year-review/dog.jpg 2x"
 data-sizes="auto"
 alt="/images/toy-projects-second-year-review/dog.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">뭔가 해야하는데&amp;hellip; 괜히 눈치만 보이네&amp;hellip;&lt;br>출처 : &lt;a href="http://egloos.zum.com/nievess/v/657827" target="_blank" rel="noopener noreffer ">http://egloos.zum.com/nievess/v/657827&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>　지난 2년 동안을 돌이켜보며 서비스를 어떻게 운영해 왔는지, 그리고 토이 프로젝트가 필자에게 어떤 영향을 주었는지 되돌아보며 셀프 리뷰를 해 보고자 한다.&lt;/p>
&lt;h2 id="서비스-자체-평가">서비스 자체 평가&lt;/h2>
&lt;h3 id="심플한-기능">심플한 기능&lt;/h3>
&lt;p>　말 그대로 토이 프로젝트이기 때문에 기능 또한 아주 간단하다. &lt;a href="https://github.com/sarojaba/awesome-devblog" target="_blank" rel="noopener noreffer ">awesome-devblog&lt;/a>에서 제공하는 개인/단체 블로그들의 포스팅을 조회하여 어제 작성된 글들만 모아 발송한다. 거기에 주간 많이 클릭된 포스팅을 모아서 한 번 더 발송하는 기능까지. 추가적인 기능을 더 디벨롭 해야 하는데 아이디어가 없어서 인지 디벨롭 할 힘이 안 나서 인지 유지만 하고 있는 상태다.&lt;/p>
&lt;h3 id="서비스에-없어서는-안될-로깅logging">서비스에 없어서는 안될 &amp;lsquo;로깅(Logging)&amp;rsquo;&lt;/h3>
&lt;p>　형식을 막론하고 컴퓨터로 돌아가는 모든 &amp;lsquo;프로그램&amp;rsquo;은 상황에 따라 미리 만들어 놓은 로직에 따라 움직이는 로봇에 불과하다. 물론 요즘에는 머신러닝이나 AI 같은 기술들로 컴퓨터가 스스로 학습하는 경우도 있지만 그 또한 미리 코딩을 통해 만들어진 부분들. 그렇기 때문에 2년이 지난 지금 이제까지 서비스가 어떻게 돌아갔는지를 확인하기 위해서는 사전에 준비해야 할 것이 있다. 그것은 바로 &amp;lsquo;로깅&amp;rsquo;. 서비스 투입 전부터 프론트부터 백엔드까지 다양한 로깅을 해서인지 2년이 지난 지금, 기록된 로그로 다양한 서비스 지표를 확인해 볼 수 있음에 다행이라 생각한다.&lt;/p>
&lt;h3 id="각종-지표">각종 지표&lt;/h3>
&lt;p>　먼저 봐야 할 지표는 당연히 가입/해지 추이. 드라마틱 한 선형 그래프는 아니지만 당연히(?) 해지 보다 가입이 더 많고 시간이 지날수록 어느 정도 꾸준하게 가입자가 들어오는 것을 보면 어떻게 알고 가입을 하러 오는지 신기할 따름이다. 하지만 마냥 신기해하지만 말고 해지하는 원인을 분석해야 할 필요가 있어 보인다. 아마도 수집하는 블로그들 중 간혹 개발과 관련되지 않는 글들이 종종 수집되어서 그런 것 같기도 하다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/toy-projects-second-year-review/regist.jpg" title="/images/toy-projects-second-year-review/regist.jpg" data-thumbnail="/images/toy-projects-second-year-review/regist.jpg" data-sub-html="&lt;h2>가입/해지 트랜드&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/toy-projects-second-year-review/regist.jpg"
 data-srcset="https://taetaetae.github.io/images/toy-projects-second-year-review/regist.jpg, https://taetaetae.github.io/images/toy-projects-second-year-review/regist.jpg 1.5x, https://taetaetae.github.io/images/toy-projects-second-year-review/regist.jpg 2x"
 data-sizes="auto"
 alt="/images/toy-projects-second-year-review/regist.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">가입/해지 트랜드&lt;/figcaption>
 &lt;/figure>
&lt;p>　다음으로는 클릭수. 눈치가 빠른 분들은 이미 알고 있겠지만 이메일에서 클릭 시 서버에서 각종 로깅을 하고 넘어가게 된다. 그러다 보니 클릭 성향(?)에 대해 집계도 가능한데 아래 지표를 보면 오전 일과를 시작하면서 메일로 종합된 기술 블로그 들을 읽기 시작하고 그중에서 특히 월요일 - 10시가 가장 많은 클릭수가 집계되었다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/toy-projects-second-year-review/click.jpg" title="/images/toy-projects-second-year-review/click.jpg" data-thumbnail="/images/toy-projects-second-year-review/click.jpg" data-sub-html="&lt;h2>클릭수 트랜드 | 시간&amp;#43;요일 별 클릭수 트랜드 | 시간&amp;#43;요일 별 클릭수 히트맵&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/toy-projects-second-year-review/click.jpg"
 data-srcset="https://taetaetae.github.io/images/toy-projects-second-year-review/click.jpg, https://taetaetae.github.io/images/toy-projects-second-year-review/click.jpg 1.5x, https://taetaetae.github.io/images/toy-projects-second-year-review/click.jpg 2x"
 data-sizes="auto"
 alt="/images/toy-projects-second-year-review/click.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">클릭수 트랜드 | 시간+요일 별 클릭수 트랜드 | 시간+요일 별 클릭수 히트맵&lt;/figcaption>
 &lt;/figure>
&lt;p>　이 포스팅을 작성하고 있는 지금까지 약 19,000여 개의 포스팅을 수집하고 발행하였는데 그중에서 가장 인기 있었던 포스팅 TOP 30 은 다음과 같다. 아무래도 단체 블로그의 포스팅을 메일 상단에 위치하고 노란색으로 테두리를 표시해서인지 대부분의 글들이 단체 블로그의 포스팅인 것을 알 수 있다.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="http://woowabros.github.io/techcourse/2019/12/05/techcourse-openrecruitingday.html" target="_blank" rel="noopener noreffer ">이 회사, 이 세상 쿨함이 아니다&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://woowabros.github.io/experience/2019/11/12/bravo-baemin.html" target="_blank" rel="noopener noreffer ">대놓고 자랑하는 글&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://engineering.linecorp.com/ko/blog/2020-line-sw-developer-recruit-coding-test/" target="_blank" rel="noopener noreffer ">LINE 신입 SW 개발자 코딩 테스트, 이렇게 만들어졌습니다&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://woowabros.github.io/techcourse/2020/06/24/techcourse-level2-retrospection.html" target="_blank" rel="noopener noreffer ">우테코에서 찾은 나만의 효과적인 공부법&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://engineering.linecorp.com/ko/blog/things-i-prepared-to-be-a-line-server-developer/" target="_blank" rel="noopener noreffer ">LINE 서버 개발자가 되기까지 내가 준비한 것들&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://blog.weirdx.io/post/61620" target="_blank" rel="noopener noreffer ">연봉을 높이는 가장 쉬운 방법은?&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://1ilsang.blog.me/221984558663" target="_blank" rel="noopener noreffer ">학교에서 알려주지 않는 17가지 실무 개발 기술 리뷰&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://woowabros.github.io/experience/2020/05/14/anomaly_alarm.html" target="_blank" rel="noopener noreffer ">간단하게 만드는 이상한 알람&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://woowabros.github.io/experience/2020/05/13/birth-of-team-culture.html" target="_blank" rel="noopener noreffer ">팀 문화의 탄생&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://engineering.linecorp.com/ko/blog/how-liners-keep-productive-while-working-at-home/" target="_blank" rel="noopener noreffer ">LINE에서 전 직원이 재택 근무하면서 생산성을 유지하는 방법&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://engineering.linecorp.com/ko/blog/flutter-pros-and-cons/" target="_blank" rel="noopener noreffer ">Flutter, 왜 선택하지 못했나&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tech.peoplefund.co.kr/2020/02/18/code-rather-than-comment.html" target="_blank" rel="noopener noreffer ">주석 달 시간에 프로그래밍을 제대로 하기&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://woowabros.github.io/techcourse/2020/04/10/techcourse-level1.html" target="_blank" rel="noopener noreffer ">우아한테크코스 : 새로운 시작&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://blog.weirdx.io/post/62410" target="_blank" rel="noopener noreffer ">기획자는 필요없다.&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://d2.naver.com/helloworld/2564557" target="_blank" rel="noopener noreffer ">간단하게 구축해 보는 JavaScript 개발 환경&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://woowabros.github.io/woowabros/2019/09/04/techcourse-level2-retrospection.html" target="_blank" rel="noopener noreffer ">우아한테크코스 : 나만의 항로 찾기&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://woowabros.github.io/techcourse/2020/06/05/techcourse-javable.html" target="_blank" rel="noopener noreffer ">코드리뷰 모음 서비스를 소개합니다.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://d2.naver.com/news/4699566" target="_blank" rel="noopener noreffer ">NAVER Tech Talk: Android 밋업(2018년 11월, 2019년 11월)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://d2.naver.com/helloworld/4007447" target="_blank" rel="noopener noreffer ">2019년과 이후 JavaScript의 동향 - JavaScript(ECMAScript)&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://engineering.vcnc.co.kr/2020/01/introduce-tada-web-frontend/" target="_blank" rel="noopener noreffer ">타다 웹 프론트엔드의 모든 것&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tech.peoplefund.co.kr/2020/02/20/development-and-office-culture.html" target="_blank" rel="noopener noreffer ">개발 문화, 사무실 문화&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://taetaetae.github.io/2020/06/28/a-good-developer-in-terms-of-self-development/" target="_blank" rel="noopener noreffer ">그런 개발자로 괜찮은가 - &lt;code>자기개발&lt;/code> 편&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://woowabros.github.io/tools/2019/10/02/clean-architecture-experience.html" target="_blank" rel="noopener noreffer ">주니어 개발자의 클린 아키텍처 맛보기&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://woowabros.github.io/experience/2020/06/19/chat-app.html" target="_blank" rel="noopener noreffer ">우리도 채팅있으면 좋을 것 같아요.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://engineering.linecorp.com/ko/blog/code-readability-vol1/" target="_blank" rel="noopener noreffer ">코드 가독성에 대해 – 1. 도입과 원칙&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://woowabros.github.io/experience/2019/12/19/ruby_database.html" target="_blank" rel="noopener noreffer ">메인 데이터베이스 IDC 탈출 성공기&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://engineering.linecorp.com/ko/blog/code-readability-vol3/" target="_blank" rel="noopener noreffer ">코드 가독성에 대해 – 3. 상태와 절차&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://taetaetae.github.io/2020/06/21/a-good-developer-in-terms-of-culture/" target="_blank" rel="noopener noreffer ">그런 개발자로 괜찮은가 - &lt;code>문화&lt;/code> 편&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.imaso.co.kr/archives/5297" target="_blank" rel="noopener noreffer ">[2019. 9. 3] 카페24 개발자 세미나&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://jybaek.tistory.com/854" target="_blank" rel="noopener noreffer ">진짜 카카오 티스토리 서비스 개판이다&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>그렇다면 수익은 얼마나 있었을까? 음?! 이 서비스의 수익이 있다고?? 우선 아래 차트를 보자.&lt;/p></description></item><item><title>그런 개발자로 괜찮은가 - '자기계발' 편</title><link>https://taetaetae.github.io/2020/06/28/a-good-developer-in-terms-of-self-development/</link><pubDate>Sun, 28 Jun 2020 20:51:03 +0000</pubDate><guid>https://taetaetae.github.io/2020/06/28/a-good-developer-in-terms-of-self-development/</guid><description>&lt;p>　학창 시절엔 &amp;lsquo;선생님&amp;rsquo;께서 정해놓으신 커리큘럼에 따라가기만 하면 큰 문제 없이 지식을 학습할 수 있었다. 거기에 주기적으로 치르는 시험을 통해 &amp;lsquo;점수&amp;rsquo;라는 평가 기준으로 얼마나 잘 성장했나를 검사하기도 한다. &lt;!--more -->졸업 후 어렵게 어렵게 취업에 성공을 하여 &amp;lsquo;신입 개발자&amp;rsquo;라는 배지를 달고 회사에 첫 출근. 그렇게 n 년이 지난 지금과 라떼 시절(?)을 비교해 보며 과연 &amp;lsquo;학습&amp;rsquo;에 대한 열정 그래프가 아직도 우상향 중인가? 하는 질문엔 일단 단전부터 올라오는 깊은 한숨과 함게 이상하게도 앞이 캄캄해진다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/a-good-developer-in-terms-of-self-development/latte.jpg" title="/images/a-good-developer-in-terms-of-self-development/latte.jpg" data-thumbnail="/images/a-good-developer-in-terms-of-self-development/latte.jpg" data-sub-html="&lt;h2>우리는 모두 라떼 시절을 가지고 있다. 출처 : https://www.dogdrip.net/212294087&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/a-good-developer-in-terms-of-self-development/latte.jpg"
 data-srcset="https://taetaetae.github.io/images/a-good-developer-in-terms-of-self-development/latte.jpg, https://taetaetae.github.io/images/a-good-developer-in-terms-of-self-development/latte.jpg 1.5x, https://taetaetae.github.io/images/a-good-developer-in-terms-of-self-development/latte.jpg 2x"
 data-sizes="auto"
 alt="/images/a-good-developer-in-terms-of-self-development/latte.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">우리는 모두 라떼 시절을 가지고 있다. &lt;br>출처 : &lt;a href="https://www.dogdrip.net/212294087" target="_blank" rel="noopener noreffer ">https://www.dogdrip.net/212294087&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>　배워야 할게 너무 많다. 아니 그보다 배운 것을 이제 활용해야지 싶으면 또 새로운 기술이 등장한다. 그렇게 매너리즘에 빠지고. 거기다 회사일이 바쁘다는 핑계로 자기계발을 멈추다 보면 남들보다 뒤처진다는 생각에 괜히 자괴감이 들어 우울해 지곤 한다. (코로나 블루 때문만은 아니겠지&amp;hellip;) 그 가운데 회사에는 정말 좋은 선배님들도 많고 멘토-멘티 관계를 잘 활용하면 충분히, 잘, 올바른 길로 성장할 수 있을 것이라 생각한다. 하지만 그렇게 누군가에게 &amp;lsquo;의존&amp;rsquo;만 하다 그 대상이 없어진다든지 심지어 그런 대상조차 없을 경우에는 어떻게 해야 할까? 점점 기술은 발전하고 배워야 할 것들은 홍수처럼 넘쳐흐르고 있는 가운데 &amp;lsquo;회사원&amp;rsquo;에서 나아가 &amp;lsquo;개발자&amp;rsquo;로써 성장을 하기 위해서는 어떠한 방법이 있을까?&lt;/p>
&lt;p>　이번 포스팅에서는 개발자로 살아가면서 성장하기 위한즉, 자기계발의 &amp;lsquo;방법&amp;rsquo;에 대해 이야기해보려 한다. 이것이 정답이다 하는 은 탄환을 소개하려는 것은 아니다. 특히 개발자로서의 생을 마감(?) 할 때까지는 계속 배워야 하는 숙명과도 같은 직업이기에 첫 단추를 잘 끼워서 갑작스러운 기술의 변화에 일희일비 하지 않고 스펀지처럼 무엇이든 흡수하는. 말랑말랑한 정신을 갖기 위함이라고나 할까.&lt;/p>
&lt;h2 id="블로그">블로그&lt;/h2>
&lt;p>　개발자가 글도 써야 하나?라는 질문에는 필자가 예전에 정리해둔 &lt;a href="https://taetaetae.github.io/2019/10/27/a-reason-for-writing/" target="_blank" rel="noopener noreffer ">개발하기 바쁜데 글까지 쓰라고? (글쓰는 개발자가 되자.)&lt;/a>라는 글을 참고해봐도 좋을 것 같다. 해당 포스팅에서 수차례 강조하였지만 그만큼 개발자에게는 특히나 글쓰기가 중요하고 필요하다. 글을 꼭 &amp;lsquo;잘&amp;rsquo;써야 한다는 부담을 가질 필요는 없다. (필자도 그렇게 잘 쓰는 편은 아니다&amp;hellip;) 다만 무언가를 기록하고 정리하고 자신만의 기준에 맞추어 재 정리하는 습관을 기르다 보면 이러한 생각들이 개발을 할 때에도 도움이 상당히 되었기 때문이다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/a-good-developer-in-terms-of-self-development/exception.gif" title="/images/a-good-developer-in-terms-of-self-development/exception.gif" data-thumbnail="/images/a-good-developer-in-terms-of-self-development/exception.gif" data-sub-html="&lt;h2>개발을 하다보면 꼼꼼하게 체크해야할 예외가 너무 많다. 출처 : https://gfycat.com/ko/menacingeducatedatlasmoth&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/a-good-developer-in-terms-of-self-development/exception.gif"
 data-srcset="https://taetaetae.github.io/images/a-good-developer-in-terms-of-self-development/exception.gif, https://taetaetae.github.io/images/a-good-developer-in-terms-of-self-development/exception.gif 1.5x, https://taetaetae.github.io/images/a-good-developer-in-terms-of-self-development/exception.gif 2x"
 data-sizes="auto"
 alt="/images/a-good-developer-in-terms-of-self-development/exception.gif" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">개발을 하다보면 꼼꼼하게 체크해야할 예외가 너무 많다. &lt;br>출처 : &lt;a href="https://gfycat.com/ko/menacingeducatedatlasmoth" target="_blank" rel="noopener noreffer ">https://gfycat.com/ko/menacingeducatedatlasmoth&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>　복잡한 구조가 필요로 하는 개발을 해야 한다고 가정해보자. 연동하는 시스템도 많고 정말 다양한 요구 사항을 하나의 시스템에서 구현을 해야 할 경우 보통 개발을 하기에 앞서 &amp;lsquo;설계&amp;rsquo;라는 단계를 거치기 마련이다. 그때 글쓰기를 했을 때의 습관(스킬?)을 적용해 보면 요구 사항들 중에 중요한 feature 기준으로 정리를 하게 되고, 각 이해관계자들에게 정리한 부분을 공유하며 예외 상황을 보다 빠르게 확인할 수도 있다. 심지어 코드 레벨에서도 지난밤에 야식으로 먹은 라면 면발처럼 꼬여있는 부분들을 보다 개발하기 편하고 유지 보수가 용이하게 구조를 변경하는 &amp;lsquo;정리&amp;rsquo;의 습관 또한 글쓰기를 통해서 수련을 할 수 있다. 이러한 &amp;lsquo;꼼꼼함&amp;rsquo;을 기르는 데에는 글쓰기만 한 게 없다고 생각한다.&lt;/p>
&lt;p>　우리는 다양한 개발 언어로 코딩을 하곤 한다. 왜 &lt;a href="http://www.yes24.com/Product/Goods/6692314" target="_blank" rel="noopener noreffer ">읽기좋은 코드가 좋은 코드&lt;/a>라는 책이 있듯이 결국 코딩 또한 커뮤니케이션이 일종이라 생각한다. 내가 생각하는 로직을 개발 언어로 코딩을 해야 하는 상황이면, 결국 내가 생각하는 로직이 명료하고 정리가 잘 된 상태에서야 코드 또한 소위 &amp;lsquo;읽기 좋은 코드&amp;rsquo;가 되지 않을까 싶다.&lt;/p>
&lt;p>　블로그를 시작할 때 어디서부터 시작해야 하나 막막하다면, 오늘의 배운 내용 (개발자들 사이에서 유행처럼 번지고 있는 &lt;a href="https://github.com/milooy/TIL" target="_blank" rel="noopener noreffer ">TIL&lt;/a>에 대해서 정리해 보는 것부터 추천한다. 경력이 1년 차여도 10년 차여도 개발을 하다 보면 새로운 것을 발견하기 마련이다. 그렇게 조금씩 적절한 블로그 플랫폼에 정리를 해 나가다 보면 어느새 자신만의 개발 히스토리가 만들어지고, 나아가 글쓰기가 전해주는 긍정적인 효과를 만끽하리라 자부한다.&lt;/p></description></item><item><title>그런 개발자로 괜찮은가 - '문화' 편</title><link>https://taetaetae.github.io/2020/06/21/a-good-developer-in-terms-of-culture/</link><pubDate>Sun, 21 Jun 2020 17:22:09 +0000</pubDate><guid>https://taetaetae.github.io/2020/06/21/a-good-developer-in-terms-of-culture/</guid><description>&lt;p>　한동안 글을 쓰지 않았다. 글을 쓰지 않은 것일까 쓰지 못한 것일까. 이런저런 이유로 번아웃 늪에 빠져버려 아무것도 하기 싫어서라는 핑계가 어울릴 수도 있겠다만. &lt;!--more -->요즘 들어 더욱더 무기력함이 극도로 뿜뿜대는 가운데 문득, 개발자로써 얼마나 잘 지내왔는가 뒤를 돌아보고 싶었다. 앞만 보고 달리는 것보다 내 생각과 내 호흡을 점검하는 것 또한 중요하다고 생각했기에 당분간은 더 나은 개발자가 되기 위한 여러 가지 주제로 글을 써보려 한다.
이름하여 &lt;code>그런 개발자로 괜찮은가 XX 편&lt;/code>&lt;/p>
&lt;blockquote>
&lt;p>어디까지나 필자의 생각에 대해 적는 것일 뿐 내용이 잘못되었을 수도 있다. 즉, 정답이 아니라는 이야기. 필자의 이러한 포스팅으로 이 글을 읽는 여러분들도 자신만의 가치관을 정립해보는 기회가 되고 나아가 모두가 더 나은 개발자로 한걸음 올라서는 아름다운 세상을 꿈꾸는 마음으로 작은 날갯짓을 해본다.&lt;/p>&lt;/blockquote>
&lt;p>　개발자로 살아가는 데 있어 가장 중요한 게 무엇일까? 물론 개발할 수 있는 기술이 가장 중요하겠지만 몇 년 전부터 기술의 발전이 급변하는 세상 속에서 과연 기술만이 중요할까? 기술만 잘 알고 있으면 복잡하게 꼬인 스파게티 면 같은 문제 많은 코드를 술술 풀어헤치고, 언제 어디서든 개발자로써 행복한 삶을 영유할 수 있을까?&lt;/p>
&lt;p>　여러 가지 중요한 요소들 중 가장 첫 번째로 떠오르는 키워드는 바로 문화(Culture)가 아닐까 싶다. 그럼 왜 문화가 개발자에게 중요하고 어떤 식으로 문화를 만들어 가는 게 좋을지에 대해 정리해보고자 한다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/a-good-developer-in-terms-of-culture/dailymetting.jpg" title="/images/a-good-developer-in-terms-of-culture/dailymetting.jpg" data-thumbnail="/images/a-good-developer-in-terms-of-culture/dailymetting.jpg" data-sub-html="&lt;h2>각 팀에 맞는 문화는 모두를 성장시킬 수 있다. 출처 : https://steemkr.com/kr-dev/@dreamisnowhere/5squ7b&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/a-good-developer-in-terms-of-culture/dailymetting.jpg"
 data-srcset="https://taetaetae.github.io/images/a-good-developer-in-terms-of-culture/dailymetting.jpg, https://taetaetae.github.io/images/a-good-developer-in-terms-of-culture/dailymetting.jpg 1.5x, https://taetaetae.github.io/images/a-good-developer-in-terms-of-culture/dailymetting.jpg 2x"
 data-sizes="auto"
 alt="/images/a-good-developer-in-terms-of-culture/dailymetting.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">각 팀에 맞는 문화는 모두를 성장시킬 수 있다. &lt;br>출처 : &lt;a href="https://steemkr.com/kr-dev/@dreamisnowhere/5squ7b" target="_blank" rel="noopener noreffer ">https://steemkr.com/kr-dev/@dreamisnowhere/5squ7b&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>　개발자라는 직업을 가지고 있는 분들 중에 프리랜서나 1인 스타트업을 운영하는 분들은 제외하고. 대부분의 사람들은 여러 명과 함께 공동의 목표를 달성하기 위한 &amp;ldquo;팀&amp;quot;이라는 단위에 소속되어 개발을 하고 있다. 야근을 매일 밥 먹듯이 하는 조직도 있을 테고 이른바 워라벨을 잘 지키며 듣기만 해도 반가운 소리인 &amp;ldquo;칼퇴&amp;quot;를 밥 먹듯이 하는 조직도 있을 테고. 여기서 말하고자 함은 이러한 야근 vs 칼퇴처럼 &amp;ldquo;근무 시간의 양&amp;quot;에 대해 이야기하려는 건 아니다. 회사, 더 깊게는 팀 내에서 어떤 문화 안에서 개발자로 살아가고 있는지에 대해 이야기하려 한다.&lt;/p>
&lt;h2 id="코드리뷰">코드리뷰&lt;/h2>
&lt;p>　팀에 속해서 개발을 하다 보면 같은 코드를 동시에 작업하곤 한다. 그래서 형상관리 도구 (요즘 git 을 안 쓰는 곳이 없을 정도&amp;hellip;)를 사용해서 동시에 개발을 진행해도 전혀 무리가 없을 정도인데 결국 작업한 결과물을 한 곳으로 병합 (merge) 해야 하는 시점이 오기 마련이고 그때엔 (온라인/오프라인) 코드 리뷰를 하게 된다. 어떠한 사연으로 코드 리뷰 없이 빨리 merge 해야 하는 건 이해되지만 가급적 한 명 이상의 리뷰어가 승인을 한 뒤에 merge 가 돼야 한다고 생각한다. (pullRequest를 단순 merge 용으로 사용하는 건 정말 잘못된 방법 중 하나) 중복된 코드를 만들었거나 작업자가 예상하지 못한 부분들을 릴리스 전에 서로 이야기해보면서 버그를 수정하거나 팀 컨벤션, 설계/구조를 더 효율적으로 가져갈 수 있는 절호의 찬스.&lt;/p>
&lt;p>　여기서 중요한 포인트는 리뷰를 받는 &amp;lsquo;리뷰이&amp;rsquo; 와 리뷰를 해주는 &amp;lsquo;리뷰어&amp;rsquo;들의 문화적인 측면에서 생각을 해볼 필요가 있다.&lt;/p>
&lt;ul>
&lt;li>리뷰이(Reviewee)
&lt;ul>
&lt;li>리뷰어의 소중한 시간을 할애해서 자신의 코드가 이상이 없는지에 대한 &amp;lsquo;도움&amp;rsquo;을 요청하는 것이기 때문에 최대한 설명을 잘 적어서 리뷰하는 데 도움을 줄 수 있어야 한다.&lt;/li>
&lt;li>작업을 하다 보면 한 번에 몰아서 코드 리뷰를 받는 경우가 대부분이지만 개발 생산성 측면과 코드 리뷰 시간을 줄이는 측면에서는 최대한 작은 단위로 리뷰를 요청해야 한다.&lt;/li>
&lt;li>리뷰가 진행이 되지 못하여 다음 작업 또한 진행을 못하는 경우가 생기는 것을 방지하기 위해 최대한 코드 리뷰 받는 부분과 의존성이 없도록 작업이 돼야 하며 그도 아니라면 정중하게 리뷰어에게 &amp;lsquo;부탁&amp;rsquo;을 해야 한다. (요청 이 아니라는 점!)&lt;/li>
&lt;li>리뷰어의 리뷰는 &amp;ldquo;지적&amp;rdquo; 이 아니라 &amp;ldquo;함께 작업하는 코드에 대한 조언&amp;quot;으로 받아들여야 한다. 혹 리뷰 내용이 자신의 생각과 다르다면 토론을 통해 합의점을 찾도록 해야 한다. (무조건 리뷰어가 리뷰했다고 기계적으로 고치는 것 또한 잘못된 부분)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>리뷰어(Reviewer)
&lt;ul>
&lt;li>리뷰이는 당신의 한마디를 간절하게 기다리고 있을 수도 있으니 최대한 정성껏 리뷰를 해주자.&lt;/li>
&lt;li>온라인 코드 리뷰를 하게 된다면 &amp;ldquo;텍스트&amp;quot;라는 제한적인 상황에서 최대한 유연한 멘트로 코드 리뷰를 해야 한다. (자칫 잘못하다간 서로 오해가 있을 수 있으니&amp;hellip;)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>코드 리뷰 관련된 문화에 대한 내용들은 잠깐만 검색해봐도 훌륭한 포스팅들이 나오니 참고해봐도 좋을 것 같다.
&lt;a href="https://engineering.linecorp.com/ko/blog/effective-codereview/" target="_blank" rel="noopener noreffer ">Line | 효과적인 코드리뷰를 위해서&lt;/a>
&lt;a href="https://tech.kakao.com/2016/02/04/code-review/" target="_blank" rel="noopener noreffer ">Kakao | 코드 리뷰, 어디까지 해봤니?&lt;/a>
&lt;a href="https://tosslab.github.io/codereview/2015/12/18/%EC%BD%94%EB%93%9C%EB%A6%AC%EB%B7%B0-%EC%9D%B4%EB%A0%87%EA%B2%8C-%ED%95%98%EA%B3%A0-%EC%9E%88%EB%8B%A4.html" target="_blank" rel="noopener noreffer ">Jandi | 코드리뷰, 이렇게 하고 있습니다.&lt;/a>&lt;/p></description></item><item><title>스프링 부트에 필터를 '조심해서' 사용하는 두 가지 방법</title><link>https://taetaetae.github.io/2020/04/06/spring-boot-filter/</link><pubDate>Mon, 06 Apr 2020 23:59:36 +0000</pubDate><guid>https://taetaetae.github.io/2020/04/06/spring-boot-filter/</guid><description>&lt;p>웹 어플리케이션에서 필터를 사용하면 중복으로 처리되는 내용을 한곳에서 처리할 수 있다거나 서비스의 다양한 니즈를 충족시키기에 안성맞춤인 장치인것 같다. 필터란 무엇인가 에 대한 내용은 워낙에 다른 블로그나 공식 도큐먼트에서 자세하게 그리고 다양하게 설명하고 있기에 기본 개념에 대해서는 설명하지 않도록 하려 한다. &lt;!--more -->
이번 포스팅에서는 스프링 부트를 사용하면서 어노테이션이라는 간편함에 취해(?) &amp;ldquo;돌격 앞으로, 닥공&amp;rdquo; 의 자세로 개발을 하려했던 필자를 보고 &amp;ldquo;반성&amp;quot;의 자세로 필터를 등록하는 방법에 대해 명확하게 정리를 하고자 한다. 마지막으로는 아주 간단하면서도 엄청나게 위험한 필터 설정 사례에 대해서도 짚고 넘어가보자.
그냥 넘어가면 아쉬우니, 한번이라도 &amp;lsquo;spring&amp;rsquo; 이라는 framework 를 접해본 사람이라면 봤을법한 그림을 첨부하는것으로 필터란 무엇인가 에 대한 설명을 대신하는게 좋겠다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-boot-filter/spring-request-lifecycle.jpg" title="/images/spring-boot-filter/spring-request-lifecycle.jpg" data-thumbnail="/images/spring-boot-filter/spring-request-lifecycle.jpg" data-sub-html="&lt;h2>출처 : https://justforchangesake.wordpress.com/2014/05/07/spring-mvc-request-life-cycle/&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-boot-filter/spring-request-lifecycle.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-boot-filter/spring-request-lifecycle.jpg, https://taetaetae.github.io/images/spring-boot-filter/spring-request-lifecycle.jpg 1.5x, https://taetaetae.github.io/images/spring-boot-filter/spring-request-lifecycle.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-boot-filter/spring-request-lifecycle.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">출처 : &lt;a href="https://justforchangesake.wordpress.com/2014/05/07/spring-mvc-request-life-cycle/" target="_blank" rel="noopener noreffer ">https://justforchangesake.wordpress.com/2014/05/07/spring-mvc-request-life-cycle/&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>방법을 설명하기 전에 동일하게 사용될 필터와 컨트롤러 코드를 보면 다음과 같다.&lt;/p>
&lt;ul>
&lt;li>필터&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@Slf4j&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">MyFilter&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">implements&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Filter&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">init&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">FilterConfig&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">filterConfig&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">throws&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ServletException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;init MyFilter&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">doFilter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ServletRequest&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">servletRequest&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ServletResponse&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">servletResponse&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">FilterChain&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">filterChain&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="kd">throws&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">IOException&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ServletException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;doFilter MyFilter, uri : {}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="n">HttpServletRequest&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">servletRequest&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="na">getRequestURI&lt;/span>&lt;span class="p">());&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">filterChain&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">doFilter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">servletRequest&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">servletResponse&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">destroy&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;destroy MyFilter&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>테스트 할 컨트롤러&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@Slf4j&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@RestController&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">SampleController&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@GetMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/test&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">test&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;test&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@GetMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/filtered/test&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">filteredTest&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;filtered&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="방법-1--filterregistrationbean">방법 1 : FilterRegistrationBean&lt;/h2>
&lt;p>아주 간단하게, 일반 url 하나와 필터에 적용할 url 두개를 만들고 설정하려 한다. FilterRegistrationBean 을 이용해서 위에서 만들었던 필터를 아래처럼 등록해보자.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@SpringBootApplication&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Method1Application&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">static&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">main&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">[]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">SpringApplication&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Method1Application&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Bean&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">FilterRegistrationBean&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">setFilterRegistration&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">FilterRegistrationBean&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">filterRegistrationBean&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">FilterRegistrationBean&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MyFilter&lt;/span>&lt;span class="p">());&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="c1">// filterRegistrationBean.setUrlPatterns(Collections.singletonList(&amp;#34;/filtered/*&amp;#34;)); // list 를 받는 메소드&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">filterRegistrationBean&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">addUrlPatterns&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/filtered/*&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// string 여러개를 가변인자로 받는 메소드&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">filterRegistrationBean&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>위 주석에도 적었지만 filterRegistrationBean 의 &amp;ldquo;setUrlPatterns&amp;rdquo; 와 &amp;ldquo;addUrlPatterns&amp;rdquo; 의 차이는 별거 없다. list 자체를 받을건지 아니면 가변인자로 계속 추가 할것인지. 이렇게 되면 &amp;ldquo;/filtered/&amp;ldquo;으로 &amp;ldquo;시작&amp;quot;하는 패턴의 url의 요청이 오게 되면 등록한 필터를 통과하게 된다.&lt;/p>
&lt;ul>
&lt;li>실행 : 필터 생성&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl"> /&lt;span class="se">\\&lt;/span> / ___&lt;span class="s1">&amp;#39;_ __ _ _(_)_ __ __ _ \ \ \ \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">( ( )\___ | &amp;#39;&lt;/span>_ &lt;span class="p">|&lt;/span> &lt;span class="s1">&amp;#39;_| | &amp;#39;&lt;/span>_ &lt;span class="se">\/&lt;/span> _&lt;span class="sb">`&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="se">\ \ \ \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="se">\\&lt;/span>/ ___&lt;span class="o">)&lt;/span>&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>_&lt;span class="o">)&lt;/span>&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="o">(&lt;/span>_&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="o">)&lt;/span> &lt;span class="o">)&lt;/span> &lt;span class="o">)&lt;/span> &lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39; |____| .__|_| |_|_| |_\__, | / / / /
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> =========|_|==============|___/=/_/_/_/
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> :: Spring Boot :: (v2.2.6.RELEASE)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:01.225 INFO 14672 --- [ main] c.t.s.method1.Method1Application : No active profile set, falling back to default profiles: default
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.153 INFO 14672 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.168 INFO 14672 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.168 INFO 14672 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.33]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.361 INFO 14672 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.362 DEBUG 14672 --- [ main] o.s.web.context.ContextLoader : Published root WebApplicationContext as ServletContext attribute with name [org.springframework.web.context.WebApplicationContext.ROOT]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.362 INFO 14672 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1082 ms
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.391 DEBUG 14672 --- [ main] o.s.b.w.s.ServletContextInitializerBeans : Mapping filters: filterRegistrationBean urls=[/filtered/*] order=2147483647, characterEncodingFilter urls=[/*] order=-2147483648, formContentFilter urls=[/*] order=-9900, requestContextFilter urls=[/*] order=-105
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.391 DEBUG 14672 --- [ main] o.s.b.w.s.ServletContextInitializerBeans : Mapping servlets: dispatcherServlet urls=[/]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.409 DEBUG 14672 --- [ main] o.s.b.w.s.f.OrderedRequestContextFilter : Filter &amp;#39;&lt;/span>requestContextFilter&lt;span class="s1">&amp;#39; configured for use
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.409 DEBUG 14672 --- [ main] s.b.w.s.f.OrderedCharacterEncodingFilter : Filter &amp;#39;&lt;/span>characterEncodingFilter&lt;span class="s1">&amp;#39; configured for use
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">// 필터가 생성 되었다!
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.410 INFO 14672 --- [ main] c.t.springbootfilter.method1.MyFilter : init MyFilter
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.410 DEBUG 14672 --- [ main] o.s.b.w.s.f.OrderedFormContentFilter : Filter &amp;#39;&lt;/span>formContentFilter&lt;span class="s1">&amp;#39; configured for use
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.544 INFO 14672 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService &amp;#39;&lt;/span>applicationTaskExecutor&lt;span class="err">&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>일반 url&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:27.526 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-1&lt;span class="o">]&lt;/span> o.s.web.servlet.DispatcherServlet : GET &lt;span class="s2">&amp;#34;/test&amp;#34;&lt;/span>, &lt;span class="nv">parameters&lt;/span>&lt;span class="o">={}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:27.528 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-1&lt;span class="o">]&lt;/span> s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.taetaetae.springbootfilter.method1.SampleController#test&lt;span class="o">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:27.548 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-1&lt;span class="o">]&lt;/span> m.m.a.RequestResponseBodyMethodProcessor : Using &lt;span class="s1">&amp;#39;text/html&amp;#39;&lt;/span>, given &lt;span class="o">[&lt;/span>text/html, application/xhtml+xml, image/webp, image/apng, application/xml&lt;span class="p">;&lt;/span>&lt;span class="nv">q&lt;/span>&lt;span class="o">=&lt;/span>0.9, application/signed-exchange&lt;span class="p">;&lt;/span>&lt;span class="nv">v&lt;/span>&lt;span class="o">=&lt;/span>b3&lt;span class="p">;&lt;/span>&lt;span class="nv">q&lt;/span>&lt;span class="o">=&lt;/span>0.9, */*&lt;span class="p">;&lt;/span>&lt;span class="nv">q&lt;/span>&lt;span class="o">=&lt;/span>0.8&lt;span class="o">]&lt;/span> and supported &lt;span class="o">[&lt;/span>text/plain, */*, text/plain, */*, application/json, application/*+json, application/json, application/*+json&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:27.548 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-1&lt;span class="o">]&lt;/span> m.m.a.RequestResponseBodyMethodProcessor : Writing &lt;span class="o">[&lt;/span>&lt;span class="s2">&amp;#34;test&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:27.555 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-1&lt;span class="o">]&lt;/span> o.s.web.servlet.DispatcherServlet : Completed &lt;span class="m">200&lt;/span> OK
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>필터링 url&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">// 필터에 들어온것 확인!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:37.455 INFO &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-2&lt;span class="o">]&lt;/span> c.t.springbootfilter.method1.MyFilter : doFilter MyFilter, uri : /filtered/test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:37.456 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-2&lt;span class="o">]&lt;/span> o.s.web.servlet.DispatcherServlet : GET &lt;span class="s2">&amp;#34;/filtered/test&amp;#34;&lt;/span>, &lt;span class="nv">parameters&lt;/span>&lt;span class="o">={}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:37.456 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-2&lt;span class="o">]&lt;/span> s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.taetaetae.springbootfilter.method1.SampleController#filteredTest&lt;span class="o">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:37.457 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-2&lt;span class="o">]&lt;/span> m.m.a.RequestResponseBodyMethodProcessor : Using &lt;span class="s1">&amp;#39;text/html&amp;#39;&lt;/span>, given &lt;span class="o">[&lt;/span>text/html, application/xhtml+xml, image/webp, image/apng, application/xml&lt;span class="p">;&lt;/span>&lt;span class="nv">q&lt;/span>&lt;span class="o">=&lt;/span>0.9, application/signed-exchange&lt;span class="p">;&lt;/span>&lt;span class="nv">v&lt;/span>&lt;span class="o">=&lt;/span>b3&lt;span class="p">;&lt;/span>&lt;span class="nv">q&lt;/span>&lt;span class="o">=&lt;/span>0.9, */*&lt;span class="p">;&lt;/span>&lt;span class="nv">q&lt;/span>&lt;span class="o">=&lt;/span>0.8&lt;span class="o">]&lt;/span> and supported &lt;span class="o">[&lt;/span>text/plain, */*, text/plain, */*, application/json, application/*+json, application/json, application/*+json&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:37.457 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-2&lt;span class="o">]&lt;/span> m.m.a.RequestResponseBodyMethodProcessor : Writing &lt;span class="o">[&lt;/span>&lt;span class="s2">&amp;#34;filtered&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:37.459 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-2&lt;span class="o">]&lt;/span> o.s.web.servlet.DispatcherServlet : Completed &lt;span class="m">200&lt;/span> OK
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="방법-2--webfilter--servletcomponentscan">방법 2 : @WebFilter + @ServletComponentScan&lt;/h2>
&lt;p>@ServletComponentScan 어노테이션을 @Configuration 어노테이션이 설정되어 있는곳에 걸어준 다음 위에서 설정한 필터에 @WebFilter 어노테이션을 설정해주면 아주 간단하게 끝이 난다.&lt;/p></description></item><item><title>조금 더 괜찮은 Rest Template 2부 - Circuit-breaker</title><link>https://taetaetae.github.io/2020/03/29/better-rest-template-2-netflix-hystrix/</link><pubDate>Sun, 29 Mar 2020 23:09:16 +0000</pubDate><guid>https://taetaetae.github.io/2020/03/29/better-rest-template-2-netflix-hystrix/</guid><description>&lt;p>&lt;a href="https://taetaetae.github.io/2020/03/22/better-rest-template-1-retryable/" rel="">지난 포스팅&lt;/a>에서는 Retryable 를 활용해서 간헐적인 네트워크 오류를 &amp;ldquo;재시도&amp;quot;를 함으로써 아주 간단하면서도 강력하게 해결할 수 있는 방법에 대해 알아보았다. 실제로 필자가 운영하는 서비스 에서도 Retryable 를 이용하기 전과 후를 비교해보면 간헐적인 네트워크 오류의 빈도수가 확실히 줄어든것을 확인할 수 있었다. &lt;!--more -->&lt;/p>
&lt;p>이렇게 &amp;ldquo;재시도&amp;quot;를 해서 요청했을때 성공 응답을 받을 경우엔 문제가 안되지만 네트워크 오류가 아닌 실제로 호출을 받는 해당 서버에서 문제가 발생했다면 어떨까? 예컨대, 해당 서버에서 DB를 조회하는 API를 호출한다고 가정했을때 DB 자체에서 어떠한 오류가 난다면. 이런 경우는 단순히 &amp;ldquo;재시도&amp;quot;로 해결할 수 없는 문제다.&lt;/p>
&lt;p>물론 Retryable 의 Recover 어노테이션을 활용했기 때문에 클라이언트 즉, 사용자에게는 오류응답이 발생을 안했겠지만 호출 받는 서버 자체에서의 에러가 발생하는데 이런식의 재시도를 계속 시도한다면 호출 받는 서버 입장에서는 이 &amp;ldquo;재시도&amp;rdquo; request 또한 &amp;ldquo;부하&amp;rdquo; 로 받게 되고 결국 2차, 3차 장애가 이어질 수 밖에 없다.&lt;/p>
&lt;p>기존 한덩어리로 관리되던 Monolithic Architecture 에서는 자체적으로 관리하기 때문에 이러한 에러 컨트롤 또한 자체적으로 관리를 할 수 있지만, 모듈이 모듈을 호출하게 되는 Microservice Architecture 로 바뀌다보니 이런 &amp;ldquo;연쇄 장애(?)&amp;rdquo; 같은 현상이 발생하게 되는 경우가 있다. 호출을 받는 서버의 상태가 이상하면 (에러응답이 지정한 임계치를 벗어나는 수준으로 맞춰서 발생한다면) 적절하게 호출을 하지 않고 (2차 장애를 내지 않도록 호출 자체를 하지 않고) 어느정도 기다리다 클라이언트에게는 에러응답이 아닌 미리 정해둔 응답을 내려주고, 에러가 복구되면 다시 호출하도록 하는 &amp;ldquo;무언가&amp;rdquo; 가 필요하지 않을까?&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/better-rest-template-2-netflix-hystrix/domino.gif" title="/images/better-rest-template-2-netflix-hystrix/domino.gif" data-thumbnail="/images/better-rest-template-2-netflix-hystrix/domino.gif" data-sub-html="&lt;h2>연쇄 장애. 제발 멈춰&amp;hellip; 출처 : http://dpg.danawa.com/mobile/community/view?boardSeq=175&amp;listSeq=4066389&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/better-rest-template-2-netflix-hystrix/domino.gif"
 data-srcset="https://taetaetae.github.io/images/better-rest-template-2-netflix-hystrix/domino.gif, https://taetaetae.github.io/images/better-rest-template-2-netflix-hystrix/domino.gif 1.5x, https://taetaetae.github.io/images/better-rest-template-2-netflix-hystrix/domino.gif 2x"
 data-sizes="auto"
 alt="/images/better-rest-template-2-netflix-hystrix/domino.gif" width="30%" />
 &lt;/a>&lt;figcaption class="image-caption">연쇄 장애. 제발 멈춰&amp;hellip; &lt;br> 출처 : &lt;a href="http://dpg.danawa.com/mobile/community/view?boardSeq=175&amp;amp;listSeq=4066389" target="_blank" rel="noopener noreffer ">http://dpg.danawa.com/mobile/community/view?boardSeq=175&amp;listSeq=4066389&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>지난 포스팅에 이어 이번 포스팅 에서는 그 &amp;ldquo;무언가&amp;rdquo;. 즉, Circuit-breaker 에 대해 알아보고 직접 구현 및 테스트 하면서 돌아가는 원리에 대해 이해 해보고자 한다. 막상 개념은 머릿속에 있지만 직접 구현해보지 않으면 내것이 아니기에, 직접 구현하고 설정값들을 바꿔가면서 언젠가 필요한 순간에 꺼내서 사용할 수 있는 나만의 &amp;ldquo;무기&amp;rdquo; 를 만들어 보고자 한다.&lt;/p>
&lt;h2 id="circuit-breaker-">Circuit breaker ?&lt;/h2>
&lt;p>(한국 발음으로) 서킷브레이커를 검색해보면 주식시장 관련된 내용이 꽤 나온다. (앗, 잠깐 눈물좀&amp;hellip;) &lt;a href="http://bitly.kr/kSm6Y" target="_blank" rel="noopener noreffer ">서킷 브레이커&lt;/a>. 이 용어는 다양한 곳에서 사용되는데 &amp;ldquo;회로 차단기&amp;rdquo; 라고도 검색이 된다. 해당 내용을 발췌해보면 다음과 같다.&lt;/p>
&lt;blockquote>
&lt;p>회로 차단기는 전기 회로에서 과부하가 걸리거나 단락으로 인한 피해를 막기 위해 자동으로 회로를 정지시키는 장치이다. 과부하 차단기와 누전 차단기로 나뉜다. 퓨즈와 다른 점은, 차단기는 어느 정도 시간이 지난 뒤, 원래의 기능이 동작하도록 복귀된다.&lt;/p>&lt;/blockquote>
&lt;p>여기서 가장 중요한 문장은 &amp;ldquo;피해를 막기 위해 자동으로 회로를 정지시키는&amp;rdquo;, &amp;ldquo;어느정도 시간이 지난뒤 원래의 기능이 동작하도록 복귀된다&amp;rdquo; 이 부분이 가장 중요한 것 같다. 시스템 구성이 점점 Microservice Architecture 로 바뀌어 가는 시점에서 이러한 &amp;ldquo;서킷브레이커&amp;quot;는 자동으로 모듈간의 호출 에러를 감지하고 위에서 말한 &amp;ldquo;연쇄 장애&amp;quot;를 사전에 막을 수 있는 아주 중요한 기능이라 생각된다.&lt;/p>
&lt;p>&amp;ldquo;circuit breaker spring&amp;rdquo; 이라는 키워드로 검색해보면 이러한 고민을 이미 Netflix 라는 회사에서 &lt;a href="https://github.com/Netflix/Hystrix" target="_blank" rel="noopener noreffer ">Hystrix&lt;/a> 라는 이름으로 개발이 된것을 알 수 있다. 이 core 모듈을 Spring 에서 한번 더 감싸서 Spring Boot 에서 사용하기 좋게 spring-cloud-starter-netflix-hystrix 라는 이름으로 만들어 둔 것이 있는데 이것을 활용해 보기로 하자.&lt;/p>
&lt;h2 id="구현">구현&lt;/h2>
&lt;p>늘 그랬듯이 SpringBoot 프로젝트를 만들고 테스트할 Controller 를 만들어 주자. 원래대로라면 호출을 하는 모듈과 호출을 받는 모듈, 2개의 모듈을 만들어서 테스트 해야 하지만 편의를 위해 하나의 모듈에서 두개의 Controller 을 만들고 테스트 해보는 것으로 하자.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@RestController&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">MainController&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">final&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MainService&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mainService&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@GetMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;index&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">index&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">){&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mainService&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">MainController&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MainService&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mainService&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">mainService&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mainService&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Slf4j&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Service&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">MainService&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RestTemplate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">restTemplate&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">restTemplate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getForObject&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;http://localhost:8080/target?key=&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">MainService&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">RestTemplate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">restTemplate&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">restTemplate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">restTemplate&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Slf4j&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@RestController&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">TargetContoller&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@GetMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/target&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">target&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;input key : {}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="n">StringUtils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">equals&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;taetaetae&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="k">throw&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RuntimeException&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Invalid key&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;target&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>사실 설명할 부분도 없긴 하지만 그래도 적어보면, &lt;code>/index&lt;/code>라는 주소와 &lt;code>key&lt;/code>라는 파라미터로 요청을 하면 &lt;code>/target&lt;/code>이라는 컨트롤러가 받아서 &lt;code>key&lt;/code> 값에 따라 정상 응답을 줄지 아니면 에러를 응답하는 코드이다. 목표로 하는건, 파라미터를 일부러 잘못줘서 에러를 발생하는데 Hystrix를 적용해서 설정한 기준에 따라 응답은 미리 정해둔 응답을, &lt;code>TargetContoller&lt;/code>에서는 잠시동안 요청을 받지 않다가 조금있다가 다시 호출하면 요청을 받는 시나리오로 작성해 보려 한다.
우선 필요한 dependency를 추가해준다.&lt;/p></description></item><item><title>매니저는 정말 개발자의 무덤일까? (리뷰 - 개발자 7년차, 매니저 1일차)</title><link>https://taetaetae.github.io/2020/03/26/7-years-of-development-1st-day-of-manager/</link><pubDate>Thu, 26 Mar 2020 23:37:17 +0000</pubDate><guid>https://taetaetae.github.io/2020/03/26/7-years-of-development-1st-day-of-manager/</guid><description>&lt;p>개발자로서의 커리어는 정말 다양하지만 필자가 보고 들은 경험을 아주 일반화 시켜 정리해 보자면 다음과 같다.&lt;/p>
&lt;p>처음엔 전공/비전공을 불문하고 신입으로 개발을 시작하여 다양한 개발 경험을 하게 된다. &lt;!--more -->사수에게 혼나기도 해보고 또는 혼내줄 사수가 없어 혼자 끙끙 밤도 새보고, 다크서클과 거북목을 겸비한 이른바 &amp;ldquo;삽질&amp;quot;을 하며 고통의 시절을 보내고 나면 어느덧 승진(진급)을 하며 일정 규모의 &amp;ldquo;팀장(혹은 관리자)&amp;ldquo;이 된다. 그게 자의든 타의든. 개발자는 다소 &amp;ldquo;기술&amp;quot;이라는 특수성을 가지고 있지만 어느 직군이든 간에 이러한 커리어 패스의 흐름은 매우 비슷하게 흘러가는 것 같다. 적어도 필자가 보고 들은 것만 보면 말이다. (예외 케이스는 항상 있지만&amp;hellip;)&lt;/p>
&lt;p>하루는 팀장님과의 면담 중에 &amp;ldquo;이제는 마냥 눈앞에 있는 개발만 할 것이 아니다. 기술을 좀 더 깊게 들여다보는 자리와 사람을 관리하며 주어진 과제를 진행하는 자리, 둘 중 선택해야 하는 시기가 온 것 같다. 더 높고 더 멀리, 그리고 더 넓게 볼 줄 알아야 한다.&amp;ldquo;라는 말씀을 듣게 된다. 어느덧 &amp;ldquo;그 시점&amp;quot;이 다가온 것이다. 개인적으로 필자는 팀장님이 말씀하신 두 가지 중 전자에 좀 더 가깝게 다가가고 싶다. 그만큼 오래오래 &amp;ldquo;실무 개발&amp;quot;을 하고 싶고, 또 그만큼 개발이 재밌기 때문이다. 아직도 눈앞의 문제를 해결하기 위해 개발하며 시간 가는 줄 모를 만큼 밤을 새우는 게 재미있는 걸 보면&amp;hellip;&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/7-years-of-development-1st-day-of-manager/chiken.jpg" title="/images/7-years-of-development-1st-day-of-manager/chiken.jpg" data-thumbnail="/images/7-years-of-development-1st-day-of-manager/chiken.jpg" data-sub-html="&lt;h2>요리하는 걸 좋아하지만 이상하게 치킨집은 하고 싶지 않다. 출처 : https://catapult.tistory.com/entry/%EC%B9%98%ED%82%A8%EC%A7%91%EC%9D%B4%EB%82%98-%EC%B0%A8%EB%A0%A4%EC%95%BC%EC%A7%80&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/7-years-of-development-1st-day-of-manager/chiken.jpg"
 data-srcset="https://taetaetae.github.io/images/7-years-of-development-1st-day-of-manager/chiken.jpg, https://taetaetae.github.io/images/7-years-of-development-1st-day-of-manager/chiken.jpg 1.5x, https://taetaetae.github.io/images/7-years-of-development-1st-day-of-manager/chiken.jpg 2x"
 data-sizes="auto"
 alt="/images/7-years-of-development-1st-day-of-manager/chiken.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">요리하는 걸 좋아하지만 이상하게 치킨집은 하고 싶지 않다. &lt;br>출처 : &lt;a href="https://catapult.tistory.com/entry/%EC%B9%98%ED%82%A8%EC%A7%91%EC%9D%B4%EB%82%98-%EC%B0%A8%EB%A0%A4%EC%95%BC%EC%A7%80" target="_blank" rel="noopener noreffer ">https://catapult.tistory.com/entry/%EC%B9%98%ED%82%A8%EC%A7%91%EC%9D%B4%EB%82%98-%EC%B0%A8%EB%A0%A4%EC%95%BC%EC%A7%80&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>어느 날 SNS 피드에 개발 관련된 소식들을 받아보다가 &lt;a href="https://book.naver.com/bookdb/book_detail.nhn?bid=16240506" target="_blank" rel="noopener noreffer ">개발 7년차. 매니저 1일차&lt;/a>라는 제목의 책을 보게 된다. 뭐야, 이거 내 이야기 아니야? 하며 귀신에 홀린 듯 사서 읽어보려는 찰나, 마침 한빛미디어 에서 주최하는 &lt;a href="http://www.hanbit.co.kr/event/current/current_event_view.html?hbe_idx=127" target="_blank" rel="noopener noreffer ">나는 리뷰어다&lt;/a> 라는 이벤트를 발견하게 된다. 결국 리뷰어에 당첨이 되고 운 좋게 해당 책을 받아볼 수 있었다. (이 책을 읽게 해준 한빛미디어 측에게 이 글로나마 감사의 인사를 전하고 싶다.)&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/7-years-of-development-1st-day-of-manager/book.jpg" title="/images/7-years-of-development-1st-day-of-manager/book.jpg" data-thumbnail="/images/7-years-of-development-1st-day-of-manager/book.jpg" data-sub-html="&lt;h2>필자의 SNS를 장식했던 &amp;lsquo;개발 7년차, 매니저 1일차&amp;rsquo;&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/7-years-of-development-1st-day-of-manager/book.jpg"
 data-srcset="https://taetaetae.github.io/images/7-years-of-development-1st-day-of-manager/book.jpg, https://taetaetae.github.io/images/7-years-of-development-1st-day-of-manager/book.jpg 1.5x, https://taetaetae.github.io/images/7-years-of-development-1st-day-of-manager/book.jpg 2x"
 data-sizes="auto"
 alt="/images/7-years-of-development-1st-day-of-manager/book.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">필자의 SNS를 장식했던 &amp;lsquo;개발 7년차, 매니저 1일차&amp;rsquo;&lt;/figcaption>
 &lt;/figure>
&lt;p>이번 포스팅에서는 우선 책에 대한 리뷰를 간단히 적어보고 거기에 필자의 생각을 조금 더 얹어보고 싶다. 필자를 두고 만들어진 책 같아서 아직도 책 표지만 봐도 신기하고 설렌다. 일단 책 표지나 제목이 맘에 든 건 감출 수 없는 사실이다.&lt;/p>
&lt;h2 id="신입-혹은-주니어-개발자가-읽어봐도-좋을-책">신입 혹은 주니어 개발자가 읽어봐도 좋을 책.&lt;/h2>
&lt;p>제목만 보면 이제 갓 팀장 혹은 매니저를 하게 되는 사람에게만 해당되는 책으로 보인다. 표지 상단에 &amp;ldquo;개발만 해왔던 내가, 어느 날 갑자기 &amp;lsquo;팀&amp;rsquo;을 맡았다!&amp;rdquo; 적혀있기도 했으니까. 하지만 책을 읽다 보면 꼭 그렇지마는 않다. 멘토링을 할 때엔 멘토와 멘티 각자의 위치에서 어떤 자세로 서로를 맞이해야 하는 방법에 대해서도 알려주기도 하고 무작정 눈앞에 있는 기능 개발만을 하며 안갯속을 걷는 주니어 개발자가 미리 미래를 경험해보는 좋은 사례를 들어 알려주고 있기 때문이다.&lt;/p>
&lt;p>꼭 누군가 혹은 무언가를 &amp;ldquo;관리&amp;quot;하는 입장이 아닌 &amp;ldquo;팀&amp;quot;이라는 공동체 사회, 특히 개발 팀에서 팀원들과 협력하는 방법론을 살펴보고 있고, 경력이 낮으면 안 보이는 부분들까지 마치 멀리 있는 것을 대신 망원경으로 보여주는 느낌이 들었다. 앞부분에는 &amp;ldquo;이 책을 읽는 방법&amp;quot;이라며 상황별로 읽는 챕터를 가이드 해주고 있지만 사실 어느 하나 중요하지 않을 내용이 없어서 처음부터 무언가에 홀린 듯 읽을 수밖에 없었고 선배님이 앞서 지나간 길을 올바르게 지나갈 수 있도록 가이드 해주는 느낌으로 중간중간 사례가 있어서 현업에 있어서 그런지 좀 더 쉽게 읽힐 수 있었다.&lt;/p>
&lt;h2 id="다-읽고서야-알아차린-번역서라는-사실">다 읽고서야 알아차린 번역서(?)라는 사실.&lt;/h2>
&lt;p>어떠한 XX 기술 서적에서는 Method를 &amp;lsquo;방법&amp;rsquo;, Overriding 을 &amp;lsquo;과적&amp;rsquo;이라고 번역한 책들이 있는가 반면, 이 책은 읽는 내내 국내 어떤 분이 쓰신 거라 생각하고 읽어내려 갔지만 다 읽고 보니 외국에 어느 CTO가 쓴 책을 옮겨서 다시 써진 책이었다. 그만큼 전혀 특유의 번역 느낌(?)은 없었고 오히려 한국 문화에 맞춰 다시 써진 건 아닐까 싶을 정도로 너무 술술 잘 읽혔다.&lt;/p></description></item><item><title>조금 더 괜찮은 Rest Template 1부 - Retryable</title><link>https://taetaetae.github.io/2020/03/22/better-rest-template-1-retryable/</link><pubDate>Sun, 22 Mar 2020 15:30:35 +0000</pubDate><guid>https://taetaetae.github.io/2020/03/22/better-rest-template-1-retryable/</guid><description>&lt;p>웹 어플리케이션을 만들면서 꼭 한번 쯤 만나게 되는 &amp;ldquo;RestTemplate&amp;rdquo;. 접근 가능한 외부 HTTP URL(보통 API)을 호출하는 방법중에 하나로 springframework 에서 제공해주는 모듈이다. 특히 큰 한덩어리로 관리되던 Monolithic Architecture 에서 요청을 하고(client) 응답을 주는(server) &lt;!--more -->즉, Endpoint가 작은 단위로 분리되는 Microservice Architecture 로 바뀌면서 각 서비스간 호출방식이 HTTP 일 경우 자주 사용되곤 하는 것 같다. (webClient 등 다른 여러 호출 방법들이 있다.)
만약, 요청을 하는 클라이언트 입장에서 응답을 주는 서버의 상태가 불안정 하다고 가정했을때, 어떤식으로 처리해야 할까? 예컨대, 요청 10번에 한번은 어떠한 이슈로 응답이 지연되거나 서버에러가 발생한다고 하면 클라이언트를 사용하는 사용자 입장에서는 간헐적인 오류응답에 답답함을 호소할 수도 있다. 그럼 잠시 눈을 감고 생각해보자.
가볍게 생각하면 아래처럼 아주 간단하게 &amp;ldquo;예외처리&amp;quot;를 이용할 수도 있다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="c1">// http call&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">catch&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Exception&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">){&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="c1">// 서버에러가 아닌 약속된 에러응답을 리턴&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>하지만 이것도 정답이 아닐수 있는게, &amp;ldquo;간헐적인 오류&amp;quot;로 인해 사용자는 오류화면을 봐야하기 때문에 클라이언트에 대한 신뢰를 저버릴 수밖에 없다. 그럼 어떻게 해야할까? 여러가지 해결방법이 있겠지만 간단하면서도 강력하다고 생각되는 방법이 바로 &amp;ldquo;재시도&amp;rdquo; 라고 생각한다. 클라이언트를 사용하는 사용자가 눈치 못챌만큼 빠르게 재시도를 한다면 에러가 나도 다시한번 호출해서 성공할 수 있는 가능성이 높기 때문이다. (그치만 근본적인 원인은 해결해야&amp;hellip;)&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/better-rest-template-1-retryable/retry.png" title="/images/better-rest-template-1-retryable/retry.png" data-thumbnail="/images/better-rest-template-1-retryable/retry.png" data-sub-html="&lt;h2>실제로 조금있다 해보면 되는 경우가 많으니 안될때는 조금 (천천히) 시도해보자. 출처 : http://www.segye.com/newsView/20200302504384&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/better-rest-template-1-retryable/retry.png"
 data-srcset="https://taetaetae.github.io/images/better-rest-template-1-retryable/retry.png, https://taetaetae.github.io/images/better-rest-template-1-retryable/retry.png 1.5x, https://taetaetae.github.io/images/better-rest-template-1-retryable/retry.png 2x"
 data-sizes="auto"
 alt="/images/better-rest-template-1-retryable/retry.png" width="40%" />
 &lt;/a>&lt;figcaption class="image-caption">실제로 조금있다 해보면 되는 경우가 많으니 안될때는 조금 (천천히) 시도해보자. &lt;br>출처 : &lt;a href="http://www.segye.com/newsView/20200302504384" target="_blank" rel="noopener noreffer ">http://www.segye.com/newsView/20200302504384&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>이번 포스팅에서는 RestTemplate 를 이용할때 &amp;ldquo;재시도&amp;rdquo; 할 수 있는 방법에 대해 알아보고자 한다. 아주 간단할지 모르지만 노력에 비해 효과가 상당하다고 생각하기 때문에 정리해 두고 싶었다.&lt;/p>
&lt;h2 id="spring-retry">Spring Retry&lt;/h2>
&lt;p>&lt;a href="https://github.com/spring-projects/spring-retry" target="_blank" rel="noopener noreffer ">공식 Github&lt;/a>에 소개를 빌리자면, Spring 어플리케이션에 대한 재시도 지원을 제공한다고 한다. 위에서 이야기 했던 &amp;ldquo;RestTemplate&amp;quot;과는 사실 무관하고, 이를 활용해서 재시도 하는 &amp;ldquo;RetryRestTemplate&amp;quot;를 구현해보려 하는것이다. 우선 이 &amp;ldquo;Spring-Retry&amp;quot;의 예제를 보면 아주 심플하게 사용할 수 있다. 우선 pom에 구현에 필요한 dependency 를 추가하고 아래 코드를 보자.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.retry&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-retry&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-starter-aop&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@Configuration&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@EnableRetry&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Application&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nd">@Bean&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Service&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">service&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Service&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Service&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Service&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nd">@Retryable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">RemoteAccessException&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">service&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// ... do something&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nd">@Recover&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">recover&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">RemoteAccessException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// ... panic&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol>
&lt;li>@EnableRetry 어노테이션을 @Configuration을 지정한 클래스 중 하나에 추가한다.&lt;/li>
&lt;li>재시도 하려는 메소드에 @Retryable 어노테이션을 지정해준다.&lt;/li>
&lt;li>재시도가 완료되는 시점에서 실행하고 싶을때 선언하는 어노테이션, @Retryable 동일한 클래스에서 선언되어야 하고 return type 은 @Retryable을 지정한 메소드와 동일해야 한다.&lt;/li>
&lt;/ol>
&lt;h2 id="retry-rest-template">Retry Rest Template&lt;/h2>
&lt;p>이렇게 springframework 에서 제공해주는 spring-retry 를 이용해서 이번 포스팅의 목표인 재시도를 하는 Retry Rest Template 를 구성해보자. 우선, RestTemplate 를 Bean 으로 등록하고, 위에서 이야기 한 어노테이션들로 구성해보자.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@EnableRetry&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Configuration&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">RetryableRestTemplateConfiguration&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Bean&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RestTemplate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">retryableRestTemplate&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">SimpleClientHttpRequestFactory&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">clientHttpRequestFactory&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">SimpleClientHttpRequestFactory&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">clientHttpRequestFactory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">setReadTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">2000&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">clientHttpRequestFactory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">setConnectTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">500&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">RestTemplate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">restTemplate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RestTemplate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">clientHttpRequestFactory&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="nd">@Retryable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RestClientException&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">maxAttempts&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">3&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">backoff&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nd">@Backoff&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">delay&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">1000&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ResponseEntity&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">exchange&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">URI&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">HttpMethod&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">method&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">HttpEntity&lt;/span>&lt;span class="o">&amp;lt;?&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">requestEntity&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Class&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">responseType&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="kd">throws&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RestClientException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">super&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">exchange&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">method&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">requestEntity&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">responseType&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w"> 
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="nd">@Recover&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ResponseEntity&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">exchangeRecover&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">RestClientException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ResponseEntity&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">badRequest&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="na">body&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;bad request T.T&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="p">};&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">restTemplate&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol>
&lt;li>SimpleClientHttpRequestFactory 를 만들고 각 타임아웃을 설정해준 다음 RestTemplate 파라미터로 넘겨준다.&lt;/li>
&lt;li>사용하는 곳에서 exchange 메소드를 이용할 것이므로 해당 메소드를 오버라이드 해준다. 먼저 해당 메소드에서 &amp;ldquo;RestClientException&amp;quot;이 발생할 경우 Retry 로직을 수행한다고 정해주고, 최대 시도는 3번, backoff 설정중 delay를 1000ms(1초)로 지정해서 재시도가 진행되도록 해준다.&lt;/li>
&lt;li>2 에서 지정한 재시도가 끝나면 (재시도를 전부 다 하면) 해당 메소드를 수행하게 되어있고, 임의로 응답에 지정한 문구를 넘겨준다.&lt;/li>
&lt;/ol>
&lt;p>이렇게 하고 실제로 사용하는 로직에서 일부러 잘못된 URL을 호출해 보도록 하자. 그리고서 로그를 자세히 보도록 application.properties 에 &amp;ldquo;debug=true&amp;rdquo; 설정을 해준다.&lt;/p></description></item><item><title>SpringRestDocs를 SpringBoot에 적용하기</title><link>https://taetaetae.github.io/2020/03/08/spring-rest-docs-in-spring-boot/</link><pubDate>Sun, 08 Mar 2020 23:16:59 +0000</pubDate><guid>https://taetaetae.github.io/2020/03/08/spring-rest-docs-in-spring-boot/</guid><description>&lt;p>API를 개발하고 제공하기 위해서는 그에 해당하는 API 명세를 작성해서 사용하는 곳에 전달하게 된다. 어떤 URL에 어떤 파라미터를 사용해서 어떻게 요청을 하면 어떤 결과를 응답으로 내려주는지에 대한 관련 정보들. 이러한 &amp;ldquo;API 문서&amp;rdquo; 를 제공하는 방식은 상황에 따라 다양한 방법으로 사용되곤 한다. &lt;!--more -->&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-rest-docs-in-spring-boot/api-document.jpg" title="/images/spring-rest-docs-in-spring-boot/api-document.jpg" data-thumbnail="/images/spring-rest-docs-in-spring-boot/api-document.jpg" data-sub-html="&lt;h2>API 코드와 해당 문서의 동기화가 자동으로 되어야 조금 편해질것 같다는 생각이 들었다. 출처 : https://dribbble.com/shots/3386291-API-Documentation&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-rest-docs-in-spring-boot/api-document.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-rest-docs-in-spring-boot/api-document.jpg, https://taetaetae.github.io/images/spring-rest-docs-in-spring-boot/api-document.jpg 1.5x, https://taetaetae.github.io/images/spring-rest-docs-in-spring-boot/api-document.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-rest-docs-in-spring-boot/api-document.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">API 코드와 해당 문서의 동기화가 자동으로 되어야 조금 편해질것 같다는 생각이 들었다. &lt;br>출처 : &lt;a href="https://dribbble.com/shots/3386291-API-Documentation" target="_blank" rel="noopener noreffer ">https://dribbble.com/shots/3386291-API-Documentation&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>필자는 주로 &amp;ldquo;위키&amp;rdquo;(또는 일반 문서)를 활용해서 전달하곤 했었는데 API의 형태가 달라질 때마다 해당 위키를 수정해야만 하는 번거로움이 있었다. API 수정하면 위키도 수정하고. 깜박하고 위키 수정을 안하게 될 경우 왜 API 명세가 다르냐는 문의가&amp;hellip; 그러다 알게된 Spring Rest Docs. (아무리 좋은 기술, 좋은 툴 이라 해도 실제로 본인이 필요로 하고 사용을 해야하는 이유가 생길때 비로소 빛을 발하는것 같은 느낌이다.)&lt;/p>
&lt;blockquote>
&lt;p>이 포스팅에서는 swegger 와 비교하는 내용은 제외할까 한다. 워낙 유명한 두 양대 산맥(?)이라 검색해보면 각각의 장단점이 자세히 나와있기에&amp;hellip;&lt;/p>&lt;/blockquote>
&lt;p>최근 들어 TestCode 의 중요성을 절실하게 느끼고 있었고, TestCode 를 작성하면 자연스럽게 문서를 만들어 주는 부분이 가장 매력적이라고 생각이 들었다. 이를 반대로 생각하면, TestCode 가 실패할 경우 빌드 자체가 안되기에 어쩔수 없이 TestCode를 성공시켜야만 하고, 자연스럽게 정상적인(최신화 된) API 문서가 만들어지게 된다.&lt;/p>
&lt;p>이번 포스팅에서는 다음과 같은 목표를 두고 실무에서 언제든지 활용이 가능한 약간의 &amp;ldquo;가이드&amp;rdquo; 같은 내용으로 작성해 보고자 한다.&lt;/p>
&lt;ul>
&lt;li>Spring Boot 최신 버전에서 Spring Rest Docs 를 설정한다.&lt;/li>
&lt;li>임의의 API 를 만들고 그에 따른 TestCase 를 작성한다.&lt;/li>
&lt;li>Spring.profile 에 따라 Spring Rest Docs Url 을 접근 가능/불가능 할 수 있게 한다.&lt;/li>
&lt;/ul>
&lt;p>물론 필자의 방법이 다를수도 있지만, 이러한 방법을 토대로 보다 더 우아하고 아름다운 방법을 알아갈수 있지 않을까 하는 기대로.&lt;/p>
&lt;h2 id="spring-boot-에-spring-rest-docs-셋팅하고-testcase-작성하기">Spring Boot 에 Spring Rest Docs 셋팅하고 TestCase 작성하기&lt;/h2>
&lt;p>우선 Spring Boot 프로젝트를 만든다. &lt;a href="https://start.spring.io/" target="_blank" rel="noopener noreffer ">https://start.spring.io/&lt;/a> 에서 만들어도 되고 IDE 에서 제공하는 툴로 만들어도 되고. 만드는 방식은 무방하다. 그 다음 필요한 dependency 를 추가해 준다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.restdocs&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-restdocs-mockmvc&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;scope&amp;gt;&lt;/span>test&lt;span class="nt">&amp;lt;/scope&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>임의로 API를 작성하고&lt;/p>
&lt;ul>
&lt;li>모델&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@Getter&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Setter&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Book&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">author&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>컨트롤러&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@RestController&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">BookController&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@GetMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/book/{id}&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Book&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getABook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nd">@PathVariable&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">Book&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">book&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Book&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">book&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">setId&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">book&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">setTitle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;spring rest docs in spring boot&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">book&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">setAuthor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;taetaetae&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">book&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>해당 컨트롤러에 대한 TestCase 를 작성하자.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@WebMvcTest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BookController&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@AutoConfigureRestDocs&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// (1)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">BookControllerTest&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Autowired&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MockMvc&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mockMvc&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// (2)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">test_책을_조회하면_null이_아닌_객체를_리턴한다&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">throws&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Exception&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">mockMvc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">perform&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/book/{id}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">accept&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MediaType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">APPLICATION_JSON&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">andDo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MockMvcResultHandlers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">print&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">andExpect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MockMvcResultMatchers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">status&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="na">isOk&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">andDo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">document&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;book&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// (3)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="n">pathParameters&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">					&lt;/span>&lt;span class="n">parameterWithName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="na">description&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;book unique id&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// (4)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="n">responseFields&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">					&lt;/span>&lt;span class="n">fieldWithPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="na">description&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;book unique id&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">					&lt;/span>&lt;span class="n">fieldWithPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;title&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="na">description&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;title&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">					&lt;/span>&lt;span class="n">fieldWithPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;author&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="na">description&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;author&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">andExpect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">jsonPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;$.id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">is&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">notNullValue&lt;/span>&lt;span class="p">())))&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// (5)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">andExpect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">jsonPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;$.title&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">is&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">notNullValue&lt;/span>&lt;span class="p">())))&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">andExpect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">jsonPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;$.author&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">is&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">notNullValue&lt;/span>&lt;span class="p">())));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>(1) Spring Boot 에서는 해당 어노테이션으로 여러줄에 걸쳐 설정해야 할 Spring Rest Docs 관련 설정을 아주 간단하게 해결할 수 있게 된다. (&lt;a href="https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-rest-docs" target="_blank" rel="noopener noreffer ">참고&lt;/a>)&lt;/p>
&lt;p>(2) &lt;a href="https://docs.spring.io/spring-restdocs/docs/2.0.4.RELEASE/reference/html5/#getting-started-sample-applications" target="_blank" rel="noopener noreffer ">공식 도큐먼트&lt;/a> 에서는 4가지 방식을 말하고 있는데 이 포스팅 에서는 &amp;ldquo;MockMvc&amp;rdquo; 을 사용하고자 한다.&lt;/p>
&lt;p>(3) &amp;ldquo;book&amp;rdquo; 이라는 identifier 를 지정하면 해당 TestCase 가 수행될때 snippets 가 생성되는데 해당 identifier 묶음으로 생성이 된다.&lt;/p></description></item><item><title>Jupyter 설치하고 원격접속까지 (for 파.알.못)</title><link>https://taetaetae.github.io/2020/02/09/jupyter-install/</link><pubDate>Sun, 09 Feb 2020 20:06:15 +0000</pubDate><guid>https://taetaetae.github.io/2020/02/09/jupyter-install/</guid><description>&lt;p>파이썬이라는 언어는 다른 프로그래밍 언어들에 비해 쉽고 직관적이라 그런지 프로그래밍을 처음 시작하는 사람들에게 더욱이 주목을 받고 있는것 같다. 정말 다양한 모듈들이 많아 여러분야에서 활용되고 있고 &lt;!--more -->특히 언제부터인가 핫! 해진 분야(?)라 해도 과언이 아닐정도인 &amp;ldquo;머신러닝&amp;rdquo; 분야에서도 다양하게 사용되고 있는것 같다.&lt;/p>
&lt;p>마침 필자가 속해 있는 팀 내에 머신러닝 스터디가 시작이 되었고, 그에 파이썬을 이용하여 스터디를 해야하는 상황. 하지만 스터디를 하는 팀원 절반 이상이 파이썬을 이용한 개발 경험이 없었고, 서로 배운것을 공유를 하면서 스터디를 하면 더 좋겠다는 생각이 들때 즈음. 언제 어디선가 봤던것이 머릿속을 스쳐 지나간다. 그건 바로 Jupyter(이하 주피터).&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jupyter-install/jupyter_logo.jpg" title="/images/jupyter-install/jupyter_logo.jpg" data-thumbnail="/images/jupyter-install/jupyter_logo.jpg" data-sub-html="&lt;h2>출처 : https://jupyter.org/&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jupyter-install/jupyter_logo.jpg"
 data-srcset="https://taetaetae.github.io/images/jupyter-install/jupyter_logo.jpg, https://taetaetae.github.io/images/jupyter-install/jupyter_logo.jpg 1.5x, https://taetaetae.github.io/images/jupyter-install/jupyter_logo.jpg 2x"
 data-sizes="auto"
 alt="/images/jupyter-install/jupyter_logo.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">출처 : &lt;a href="https://jupyter.org/" target="_blank" rel="noopener noreffer ">https://jupyter.org/&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>&lt;a href="https://jupyter.org/" target="_blank" rel="noopener noreffer ">주피터&lt;/a>는 수십 개의 프로그래밍 언어에서 대화 형 컴퓨팅을위한 오픈 소스 소프트웨어, 오픈 표준 및 서비스를 개발하기 위한 툴이라고 한다. 이 포스트를 작성하기 전까지만 해도 &amp;ldquo;주피터 == 파이썬 웹 개발툴&amp;rdquo; 이라고만 알고있었는데 좀더 찾아보니 &lt;a href="https://github.com/jupyter/jupyter/wiki/Jupyter-kernels" target="_blank" rel="noopener noreffer ">다양한 언어를 지원&lt;/a>하는것 같다.&lt;/p>
&lt;p>그럼 이러한 주피터를 특정 서버에 설치하고 로컬에 파이썬을 설치하지 않아도 원격으로 파이썬 코딩을 해보면 좀더 스터디에 도움이 되지 않을까 하는 마음이 들었다. 또한 학교에서 운동장에 잔디를 깔아서 맘껏 뛰놀수 있게 하는 느낌으로 팀원들을 위해 설치를 해두고 원격으로 접속할 수 있게 해두면 모두가 편하고 쉽게 파이썬에 대해 경험을 해볼 수 있지 않을까 하는 마음으로 주피터를 설치를 해 보고자 한다.&lt;/p>
&lt;p>본 포스팅의 목표는 다음과 같다.&lt;/p>
&lt;ul>
&lt;li>환경 : CentOS 7.4 64Bit, python 2.7 (기본)&lt;/li>
&lt;li>목표
&lt;ul>
&lt;li>anaconda 를 활용하여 시스템 기본 파이썬을 건드리지 않는 가상환경을 구축한다.&lt;/li>
&lt;li>주피터를 설치하고 원격으로 접속할 수 있도록 설정한다.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>여기까지 보면 필자가 엄청나게 파이썬에 대해 잘 아는것처럼 보일수도 있어 미리 말하지만 필자는 찐 자바 개발자이면서 파이썬 개발 수준은 기본적인 스크립트를 작성하는 정도이다. 그러니 이 포스트를 읽고 있는 필자같은 파알못(?) 분들도 충분히 설치가 가능하다. (최대한 따라할수 있을 정도의 치트키 수준으로 작성 하고자 한다.)&lt;/p>
&lt;h2 id="아나콘다-설치-덤으로-설치되는-주피터">아나콘다 설치 (덤으로 설치되는 주피터)&lt;/h2>
&lt;p>우선 아나콘다를 설치하자. 아나콘다는 Anaconda(이전: Continuum Analytics)라는 곳에서 만든 파이썬 배포판으로, 수백 개의 파이썬 패키지를 포함하고 있다고 한다. 즉, 아나콘다를 설치하고 만들어진 가상환경에서 파이썬 개발을 하면 다양한 모듈이 이미 설치되어 있기 때문에 편리하다는 이야기.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jupyter-install/ananconda.jpg" title="/images/jupyter-install/ananconda.jpg" data-thumbnail="/images/jupyter-install/ananconda.jpg" data-sub-html="&lt;h2>출처 : https://www.anaconda.com/&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jupyter-install/ananconda.jpg"
 data-srcset="https://taetaetae.github.io/images/jupyter-install/ananconda.jpg, https://taetaetae.github.io/images/jupyter-install/ananconda.jpg 1.5x, https://taetaetae.github.io/images/jupyter-install/ananconda.jpg 2x"
 data-sizes="auto"
 alt="/images/jupyter-install/ananconda.jpg" width="40%" />
 &lt;/a>&lt;figcaption class="image-caption">출처 : &lt;a href="https://www.anaconda.com/" target="_blank" rel="noopener noreffer ">https://www.anaconda.com/&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>더불어 시스템에 기본으로 설치되어 있는 파이썬을 건드리면 여러 복잡한 문제가 발생할 수 있기에. 아나콘다를 활용하여 파이썬 3을 사용하는 가상환경을 만들어 보자.
설치는 아주 간단하다. 아나콘다 설치파일을 다운받고 이를 실행하면 끝.
(user 레벨이 root 면 sudo 명령어를 생략해도 된다.)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ wget https://repo.anaconda.com/archive/Anaconda3-2019.10-Linux-x86_64.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo bash Anaconda3-2019.10-Linux-x86_64.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Welcome to Anaconda3 2019.10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">In order to &lt;span class="k">continue&lt;/span> the installation process, please review the license
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">agreement.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Please, press ENTER to &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt;&amp;gt;&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Anaconda End User License &lt;span class="nv">Agreement&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copyright 2015, Anaconda, Inc.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">~~~ 중략 ~~~
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Do you accept the license terms? &lt;span class="o">[&lt;/span>yes&lt;span class="p">|&lt;/span>no&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>no&lt;span class="o">]&lt;/span> &amp;gt;&amp;gt;&amp;gt; yes &lt;span class="c1"># yes!!&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Anaconda3 will now be installed into this location:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/root/anaconda3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - Press ENTER to confirm the location
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - Press CTRL-C to abort the installation
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - Or specify a different location below
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>/root/anaconda3&lt;span class="o">]&lt;/span> &amp;gt;&amp;gt;&amp;gt; /home/anaconda3 &lt;span class="c1"># 설치될 경로를 설정해주고 기본 설정값에 설치하려면 그냥 엔터&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">~~~뭐가 엄청 설치된다. 물 한잔 먹고 오자.~~~
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">installation finished.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Do you wish the installer to initialize Anaconda3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">by running conda init? &lt;span class="o">[&lt;/span>yes&lt;span class="p">|&lt;/span>no&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>no&lt;span class="o">]&lt;/span> &amp;gt;&amp;gt;&amp;gt; yes &lt;span class="c1"># yes!!&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 되면 설치는 끝. 환경변수를 설정해서 기본 파이썬 환경을 아나콘다에 의해 설정되도록 맞춰주자.&lt;/p></description></item><item><title>스프링 부트로 멀티모듈 셋팅하기</title><link>https://taetaetae.github.io/2020/01/19/spring-boot-maven-multi-module/</link><pubDate>Sun, 19 Jan 2020 10:29:03 +0000</pubDate><guid>https://taetaetae.github.io/2020/01/19/spring-boot-maven-multi-module/</guid><description>&lt;p>서비스를 처음 만들기 시작할때면 각 직군별로 생각하는 포인트가 다양하다. 설계, 기획, 디자인, 개발. 여기서 개발은 프로젝트 셋팅을 어떻게 해야하지? 하는 고민을 하기 마련이다. 아주 간단하게 하나의 모듈로 모든 기능을 담당하도록 만들 수 있지만 기능별로 모듈을 나눠서 셋팅하는게 관리측면에서 장점이라 생각한다.&lt;!--more -->&lt;/p>
&lt;p>예를 들어보자. 도서관의 들어온 책 정보를 외부에 제공하는 &amp;ldquo;API&amp;rdquo;, 주기적으로 책 정보를 업데이트 하는 &amp;ldquo;Batch&amp;rdquo;. 이렇게 크게 두가지의 모듈이 있어야 한다고 가정했을때 어떤식으로 모듈을 설계할 수 있을까?&lt;/p>
&lt;p>이번 포스팅에서는 스프링 부트와 메이븐을 활용해서 하나의 프로젝트(컴포넌트)에서 여러 모듈을 관리할 수 있는 &lt;a href="https://spring.io/guides/gs/multi-module/" target="_blank" rel="noopener noreffer ">Spring Multi Module&lt;/a>을 셋팅하는 방법에 대해 알아보고자 한다. 필자도 셋팅하기 전에는 &amp;ldquo;그냥 하면 되는거 아니야?&amp;ldquo;라며 우습게 보다 아주 사소한 부분들에서 엄청난 삽질을 해서 그런지 꼭 포스팅으로 남겨놔야 겠다고 다짐했고 이렇게 정리를 할 수 있게 되어서 다행이라 생각한다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-boot-maven-multi-module/team_structure.png" title="/images/spring-boot-maven-multi-module/team_structure.png" data-thumbnail="/images/spring-boot-maven-multi-module/team_structure.png" data-sub-html="&lt;h2>어쩌면 우리가 있는 팀도 멀티모듈이 아닐까? 출처 : https://bcho.tistory.com/813&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-boot-maven-multi-module/team_structure.png"
 data-srcset="https://taetaetae.github.io/images/spring-boot-maven-multi-module/team_structure.png, https://taetaetae.github.io/images/spring-boot-maven-multi-module/team_structure.png 1.5x, https://taetaetae.github.io/images/spring-boot-maven-multi-module/team_structure.png 2x"
 data-sizes="auto"
 alt="/images/spring-boot-maven-multi-module/team_structure.png" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">어쩌면 우리가 있는 팀도 멀티모듈이 아닐까? &lt;br>출처 : &lt;a href="https://bcho.tistory.com/813" target="_blank" rel="noopener noreffer ">https://bcho.tistory.com/813&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="왜-멀티모듈로-셋팅할까">왜 멀티모듈로 셋팅할까?&lt;/h2>
&lt;p>위에서 예시로 이야기 한것처럼 현재 우리가 셋팅해야할 모듈은 크게 두가지 이다.&lt;/p>
&lt;ul>
&lt;li>API : 외부에 도서관에 들어온 책 정보를 알려주는 모듈&lt;/li>
&lt;li>Batch : 주기적으로 도서관의 책 정보를 갱신하는 모듈&lt;/li>
&lt;/ul>
&lt;p>한번 생각을 해보자. 위에서 말한 모듈들 중에 동시에 사용할것만 같은 정보가 있다. &amp;ldquo;책 정보&amp;rdquo;. 각 모듈마다 &amp;ldquo;책 정보&amp;quot;를 가져오는 로직을 작성하는것 보다 한곳에서 해당로직을 구현하고 이를 여러곳에서 사용하는게 사용하는게 중복코드를 방지할수 있는 방법이란건 쉽게 알아차릴수 있다. 그렇다면 어떻게 모듈을 분리할수 있을까?&lt;/p>
&lt;p>필자의 경험으로 미루어 볼때 크게 두가지 방법이 있는것 같다.&lt;/p>
&lt;ul>
&lt;li>공통으로 사용하는 모듈을 jar로 만들고 이를 메이븐 원격 저장소에 deploy, 사용하는 모듈에서 디펜던시에 추가하여 사용&lt;/li>
&lt;li>멀티모듈로 구성하고 사용하는 모듈에서 디펜던시에 추가하여 사용&lt;/li>
&lt;/ul>
&lt;p>첫번째 방법의 가장 큰 단점은, 공통으로 사용하는 모듈이 변경될때마다 버전을 바꿔주고 (안바꿔도 되지만 사용하는 모듈에서 캐시 갱신을 해야하는 불편함이 생긴다.) 메이븐 원격 저장소에 deploy를 해줘야 한다. 그에 반해 두번째 방법은 이런과정없이 함께 빌드만 해주면 끝나고 IDE에서 개발시 한 모듈에서 동시에 수정과 사용이 가능하기 때문에 훨씬 편리하다.&lt;/p>
&lt;p>&lt;a href="https://johngrib.github.io/wiki/No-Silver-Bullet/" target="_blank" rel="noopener noreffer ">은총알은 없다&lt;/a> 라는 말처럼, 정답은 없다. 하지만 이런저런 방법들을 미리 알아두면 적시적소에 사용할 수 있는. 필자가 다른글들에서도 언급을 자주하던 &amp;ldquo;나만의 무기&amp;quot;가 되지 않을까?&lt;/p>
&lt;h2 id="멀티모듈-셋팅하기">멀티모듈 셋팅하기&lt;/h2>
&lt;p>위에서 이야기 했던 &amp;ldquo;API&amp;rdquo;, &amp;ldquo;Batch&amp;quot;와는 별도로 공통으로 사용하는 모듈인 &amp;ldquo;Core&amp;rdquo; 이렇게 총 3개의 모듈을 만들예정이다.&lt;/p>
&lt;blockquote>
&lt;p>다른 이야기지만, 공통으로 사용할 것 &amp;ldquo;같아서&amp;rdquo; 미리 공통로직을 작성하는 습관은 좋지 않는것 같다. 그러다보면 쓸데없이 공통로직이 무거워지므로 실제로 사용하면서 중복코드가 발생할때 그때 공통로직으로 리펙토링 해도 늦지 않는것 같다. (꼰데인가&amp;hellip;)&lt;/p>&lt;/blockquote>
&lt;p>구현하는 환경은 다음과 같다.&lt;/p>
&lt;ul>
&lt;li>Spring Boot 2.2.3&lt;/li>
&lt;li>Maven&lt;/li>
&lt;li>IntelliJ&lt;/li>
&lt;/ul>
&lt;p>우선 IDE의 힘을 빌려 하나의 스프링 부트 프로젝트를 생성해본다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-boot-maven-multi-module/spring_boot_init.jpg" title="/images/spring-boot-maven-multi-module/spring_boot_init.jpg" data-thumbnail="/images/spring-boot-maven-multi-module/spring_boot_init.jpg" data-sub-html="&lt;h2>다음 &amp;gt; 다음 &amp;gt; 다음&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-boot-maven-multi-module/spring_boot_init.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-boot-maven-multi-module/spring_boot_init.jpg, https://taetaetae.github.io/images/spring-boot-maven-multi-module/spring_boot_init.jpg 1.5x, https://taetaetae.github.io/images/spring-boot-maven-multi-module/spring_boot_init.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-boot-maven-multi-module/spring_boot_init.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">다음 &amp;gt; 다음 &amp;gt; 다음&lt;/figcaption>
 &lt;/figure>
&lt;p>그 다음 만든 프로젝트에서 우클릭 후 새로운 모듈을 선택. Maven 모듈을 선택하고 적당한 이름을 적어준다.
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-boot-maven-multi-module/new_module.jpg" title="/images/spring-boot-maven-multi-module/new_module.jpg" data-thumbnail="/images/spring-boot-maven-multi-module/new_module.jpg" data-sub-html="&lt;h2>다음 &amp;gt; 다음 &amp;gt; 다음 222&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-boot-maven-multi-module/new_module.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-boot-maven-multi-module/new_module.jpg, https://taetaetae.github.io/images/spring-boot-maven-multi-module/new_module.jpg 1.5x, https://taetaetae.github.io/images/spring-boot-maven-multi-module/new_module.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-boot-maven-multi-module/new_module.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">다음 &amp;gt; 다음 &amp;gt; 다음 222&lt;/figcaption>
 &lt;/figure>&lt;/p>
&lt;p>&amp;ldquo;API&amp;rdquo;, &amp;ldquo;Batch&amp;rdquo;, &amp;ldquo;Core&amp;rdquo; 라는 모듈을 추가하고 실제 모듈이 되는 &amp;ldquo;API&amp;rdquo;, &amp;ldquo;Batch&amp;quot;에 &lt;code>parent&lt;/code> 와 &lt;code>dependencies&lt;/code> 을 설정해주자. 그렇게 하고 각 Pom.xml을 보면 아래와 같다. (&amp;ldquo;API&amp;rdquo; 모듈에 대해서만 집중적으로 이야기 하려 한다. &amp;ldquo;Batch&amp;rdquo; 모듈도 동일한 형식으로 작성하기 때문.)&lt;/p>
&lt;ul>
&lt;li>최 상위 Pom.xml (library)
modules 하위에 멀티모듈로 설정한 모듈들의 이름이 들어가 있는것을 확인할 수 있다.&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;project&lt;/span> &lt;span class="na">xmlns=&lt;/span>&lt;span class="s">&amp;#34;http://maven.apache.org/POM/4.0.0&amp;#34;&lt;/span> &lt;span class="na">xmlns:xsi=&lt;/span>&lt;span class="s">&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		 &lt;span class="na">xsi:schemaLocation=&lt;/span>&lt;span class="s">&amp;#34;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;modelVersion&amp;gt;&lt;/span>4.0.0&lt;span class="nt">&amp;lt;/modelVersion&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;packaging&amp;gt;&lt;/span>pom&lt;span class="nt">&amp;lt;/packaging&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;modules&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;module&amp;gt;&lt;/span>api&lt;span class="nt">&amp;lt;/module&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;module&amp;gt;&lt;/span>core&lt;span class="nt">&amp;lt;/module&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;module&amp;gt;&lt;/span>batch&lt;span class="nt">&amp;lt;/module&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/modules&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;parent&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-starter-parent&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>2.2.3.RELEASE&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;relativePath/&amp;gt;&lt;/span> &lt;span class="c">&amp;lt;!-- lookup parent from repository --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/parent&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>com.taetaetae&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>library&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>0.0.1-SNAPSHOT&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;name&amp;gt;&lt;/span>library&lt;span class="nt">&amp;lt;/name&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;description&amp;gt;&lt;/span>Demo project for Spring Boot&lt;span class="nt">&amp;lt;/description&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;properties&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;java.version&amp;gt;&lt;/span>1.8&lt;span class="nt">&amp;lt;/java.version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/properties&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;dependencies&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-starter-web&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-starter-test&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;scope&amp;gt;&lt;/span>test&lt;span class="nt">&amp;lt;/scope&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;exclusions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">				&lt;span class="nt">&amp;lt;exclusion&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">					&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.junit.vintage&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">					&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>junit-vintage-engine&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">				&lt;span class="nt">&amp;lt;/exclusion&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;/exclusions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/dependencies&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/project&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>API Pom.xml
parent 부분이 설정되어 있는 모습을 볼수 있고, core 모듈을 사용하기 위해 dependency 에 추가를 해준다.&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;project&lt;/span> &lt;span class="na">xmlns=&lt;/span>&lt;span class="s">&amp;#34;http://maven.apache.org/POM/4.0.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		 &lt;span class="na">xmlns:xsi=&lt;/span>&lt;span class="s">&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		 &lt;span class="na">xsi:schemaLocation=&lt;/span>&lt;span class="s">&amp;#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;parent&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>library&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>com.taetaetae&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>0.0.1-SNAPSHOT&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/parent&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;modelVersion&amp;gt;&lt;/span>4.0.0&lt;span class="nt">&amp;lt;/modelVersion&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;packaging&amp;gt;&lt;/span>jar&lt;span class="nt">&amp;lt;/packaging&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>api&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;dependencies&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>com.taetaetae&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>core&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>0.0.1-SNAPSHOT&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/dependencies&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;build&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;finalName&amp;gt;&lt;/span>library-api&lt;span class="nt">&amp;lt;/finalName&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;plugins&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;plugin&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">				&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">				&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-maven-plugin&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;/plugin&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;/plugins&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/build&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/project&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>Core Pom.xml
Core 모듈은 &amp;ldquo;jar&amp;quot;로 패키징 되어 다른 곳에서 사용되어야 하기 때문에 packaging 만 설정해준다.&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;project&lt;/span> &lt;span class="na">xmlns=&lt;/span>&lt;span class="s">&amp;#34;http://maven.apache.org/POM/4.0.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		 &lt;span class="na">xmlns:xsi=&lt;/span>&lt;span class="s">&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		 &lt;span class="na">xsi:schemaLocation=&lt;/span>&lt;span class="s">&amp;#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;parent&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>library&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>com.taetaetae&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>0.0.1-SNAPSHOT&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/parent&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;modelVersion&amp;gt;&lt;/span>4.0.0&lt;span class="nt">&amp;lt;/modelVersion&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;packaging&amp;gt;&lt;/span>jar&lt;span class="nt">&amp;lt;/packaging&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>core&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>0.0.1-SNAPSHOT&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/project&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 하고서 Core 모듈에 공통으로 사용될 로직을 작성하고, API 모듈에서 이를 사용하는 로직을 작성한뒤, 빌드를 해보면 에러 없이 정상 작동을 하는 모습을 볼 수 있다.&lt;/p></description></item></channel></rss>