<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Tecoble]]></title><description><![CDATA[woowacourse code review & devlog]]></description><link>https://tecoble.techcourse.co.kr</link><generator>GatsbyJS</generator><lastBuildDate>Fri, 01 Dec 2023 07:38:52 GMT</lastBuildDate><item><title><![CDATA[다중 이미지 업로드 최적화: 병렬 스트림과 CompletableFuture]]></title><description><![CDATA[사이드 프로젝트로 sns…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-11-23-multiple-image-upload/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-11-23-multiple-image-upload/</guid><pubDate>Thu, 23 Nov 2023 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;사이드 프로젝트로 sns 서비스를 개발하며 사용자가 다수의 이미지를 업로드하는 기능을 개발하게 되었다.
웹 애플리케이션에서 다중 이미지 업로드는 일반적이며, 이를 효율적으로 처리하는 것은 사용자 경험 향상에 중요한 역할을 한다.&lt;/p&gt;
&lt;p&gt;그렇다면 서버 측에서는 여러 장의 이미지를 어떻게 효율적으로 업로드할 수 있을까?&lt;/p&gt;
&lt;p&gt;이 글에서는 구체적인 이미지 저장 방법(로컬 스토리지 저장, 클라우드 서비스 사용, DB에 바이너리 데이터로 저장 등)을 떠나, 이미지 업로드 과정을 어떻게 하면 더 효율적이고 빠르게 처리할 수 있을지에 집중해 보고자 한다.
병렬 처리와 같은 기술을 활용하여 서버 성능을 최대한 활용하고, 사용자에게 빠른 응답 시간을 제공하는 방법을 예제 코드와 함께 살펴보자.&lt;/p&gt;
&lt;h1&gt;예제 코드 소개&lt;/h1&gt;
&lt;p&gt;이번 예제에서는 &lt;code class=&quot;language-text&quot;&gt;이미지 동반 포스트 업로드 서비스&lt;/code&gt; 를 구현했다.
전체 소스코드는 &lt;a href=&quot;https://github.com/xxeol2/multi-image-upload-demo&quot;&gt;Github&lt;/a&gt;에서 확인 가능하다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[요구사항]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;한 포스트당 최대 10장의 이미지를 포함할 수 있다.&lt;/li&gt;
&lt;li&gt;업로드 실패 시, 해당 포스트에 대한 모든 이미지 파일을 저장소에서 삭제한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;클래스의 구조와 전체적인 코드의 흐름은 아래와 같다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/f6ae6a382331b2f963c08f28699dce46/c3538/%ED%81%B4%EB%9E%98%EC%8A%A4%20%EA%B5%AC%EC%A1%B0%EB%8F%84.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 42.199999999999996%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABSElEQVR42nWSiW6DMBBE+f9/bC6ScBjwiW+Yrt2mRZGy0siLtHpmZt3gQ4UYwI2AWI+S4JxDKYnVBUxyxTBLMK5p1iPljOYF2PcdSmsIKeCcw8xnXPpr1Zl0Yy2e85NgugKFsegWjcuT4T5yMOUQEwFTCMgxopxyWTBPEzSBt22DTwEhkw7nq1JK5CIhbzsSzZZ+yxuaZRwwti2G2w1L11Xo/vfbvzp8F1AmFctcO0xCY5aGLDtYH9E4Y2ClhFUKmvIpQOssrLUwzkA5VbOUlJ8JK6ZlghQCXJlq+avtcboPtTcu/mdYyhC86/tqueQonMSoGU7dGff5ARHKMtY66yki4zx8zHBk11jqwxvwvXz0NOwpv1jztN6CTQwT5TxLjQfjOD96XDuGgZsfy0dA2fRLHy/x9DxoiWr1BNHo6NkMiwKTFquP+AYNjGuR+q/gLgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;       &quot; title=&quot;&quot; src=&quot;/static/f6ae6a382331b2f963c08f28699dce46/f97d7/%ED%81%B4%EB%9E%98%EC%8A%A4%20%EA%B5%AC%EC%A1%B0%EB%8F%84.png&quot; srcset=&quot;/static/f6ae6a382331b2f963c08f28699dce46/0eb09/%ED%81%B4%EB%9E%98%EC%8A%A4%20%EA%B5%AC%EC%A1%B0%EB%8F%84.png 500w,
/static/f6ae6a382331b2f963c08f28699dce46/1263b/%ED%81%B4%EB%9E%98%EC%8A%A4%20%EA%B5%AC%EC%A1%B0%EB%8F%84.png 1000w,
/static/f6ae6a382331b2f963c08f28699dce46/f97d7/%ED%81%B4%EB%9E%98%EC%8A%A4%20%EA%B5%AC%EC%A1%B0%EB%8F%84.png 2000w,
/static/f6ae6a382331b2f963c08f28699dce46/c3538/%ED%81%B4%EB%9E%98%EC%8A%A4%20%EA%B5%AC%EC%A1%B0%EB%8F%84.png 2258w&quot; sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;PostFacadeService&lt;/code&gt;는 전체 이미지 파일 업로드를 &lt;code class=&quot;language-text&quot;&gt;ImageStorageService&lt;/code&gt;에 요청한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ImageStorageService&lt;/code&gt;는 각 이미지 파일 업로드를 &lt;code class=&quot;language-text&quot;&gt;StorageClient&lt;/code&gt;에 요청한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;이미지 업로드가 성공하면, &lt;code class=&quot;language-text&quot;&gt;PostFacadeService&lt;/code&gt;는 포스트 저장을 &lt;code class=&quot;language-text&quot;&gt;PostService&lt;/code&gt;에 요청한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 포스트에서는 주로 &lt;code class=&quot;language-text&quot;&gt;ImageStorageService&lt;/code&gt;에 집중하여 여러 이미지 업로드 방법을 살펴본다.&lt;/p&gt;
&lt;h1&gt;순차적 업로드 방식&lt;/h1&gt;
&lt;p&gt;가장 기본적인 방법은 모든 이미지를 하나씩 순차적으로 업로드하는 것이다.&lt;/p&gt;
&lt;p&gt;아래는 &lt;code class=&quot;language-text&quot;&gt;ImageStorageService&lt;/code&gt;에서 파일 업로드를 담당하는 &lt;code class=&quot;language-text&quot;&gt;uploadFiles()&lt;/code&gt; 코드이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ImageUploadResponse&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;uploadFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MultipartFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; imageFiles&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;validate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageFiles&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; fileNames &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageFiles&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;storageClient&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;upload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileNames&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;convertFileNamesToResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileNames&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        executor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;deleteFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileNames&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InternalServerException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;이미지 업로드 시 예외가 발생했습니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이미지 파일들을 순차적으로 &lt;code class=&quot;language-text&quot;&gt;StorageClient&lt;/code&gt;에 업로드 요청을 보내고, 업로드에 성공한 파일의 이름을 모아 반환한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[nio-8080-exec-1] : File Upload 시작 (871cb6aae189.png)
[nio-8080-exec-1] : File Upload 완료 (871cb6aae189.png)
[nio-8080-exec-1] : File Upload 시작 (1e17f3995ade.png)
[nio-8080-exec-1] : File Upload 완료 (1e17f3995ade.png)
[nio-8080-exec-1] : File Upload 시작 (587ab1b903d3.png)
[nio-8080-exec-1] : File Upload 완료 (587ab1b903d3.png)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위 로그에서 &lt;code class=&quot;language-text&quot;&gt;[nio-8080-exec-1]&lt;/code&gt;은 해당 작업이 진행된 스레드 이름을 나타낸다. 모든 요청이 단일 스레드에서 순차적으로 처리되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1240px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/f5fb869d29fb03758d5909ef1c72430d/09504/%EC%88%9C%EC%B0%A8%EC%A0%81%20%EC%97%85%EB%A1%9C%EB%93%9C%20%EA%B5%AC%EC%A1%B0.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 31.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABMklEQVR42m2QiU7DMBBE8/+fhpBQhShtaZo6xM4BOZzEceocD7dFCCFGGo88Xo93HazrSlEUN/Z9T5qm5HlO27YopciyDN00yERSVzW/cb1rzIC1448XVHXFNnrlLQlJVMI+fuOoQiIRsRcHInVGKkmYRpzzmGEYcM4xjpas+CBUJXFeY8d7aFBUHzSuxSyW/flAtxjqSfNy2mJmi5477+8Z/Hl1aUiL1HdkkTLhGAn0tJK1A8VndQ+8vniMQ2IVU5YlQglkKql8gUgE4fsJlSqetht2px3zPN/H9TSj4ywzjkIyuunmBdf2daPRWmMHi/BjPZyeeFUHHr0mucT0hvKzpPV16/dfVcbxonp2qmUTdzxLQ9FeCPgDt0xo19FNxrNn8vv/sPjk2S/zclc3rzf9AtBPyGP3jWYPAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;          &quot; title=&quot;&quot; src=&quot;/static/f5fb869d29fb03758d5909ef1c72430d/09504/%EC%88%9C%EC%B0%A8%EC%A0%81%20%EC%97%85%EB%A1%9C%EB%93%9C%20%EA%B5%AC%EC%A1%B0.png&quot; srcset=&quot;/static/f5fb869d29fb03758d5909ef1c72430d/0eb09/%EC%88%9C%EC%B0%A8%EC%A0%81%20%EC%97%85%EB%A1%9C%EB%93%9C%20%EA%B5%AC%EC%A1%B0.png 500w,
/static/f5fb869d29fb03758d5909ef1c72430d/1263b/%EC%88%9C%EC%B0%A8%EC%A0%81%20%EC%97%85%EB%A1%9C%EB%93%9C%20%EA%B5%AC%EC%A1%B0.png 1000w,
/static/f5fb869d29fb03758d5909ef1c72430d/09504/%EC%88%9C%EC%B0%A8%EC%A0%81%20%EC%97%85%EB%A1%9C%EB%93%9C%20%EA%B5%AC%EC%A1%B0.png 1240w&quot; sizes=&quot;(max-width: 1240px) 100vw, 1240px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;h2&gt;성능 측정&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[PostFacadeService.createPost] 실행 시간(ms): 1396
[PostFacadeService.createPost] 실행 시간(ms): 1287
[PostFacadeService.createPost] 실행 시간(ms): 1251
[PostFacadeService.createPost] 실행 시간(ms): 1383
[PostFacadeService.createPost] 실행 시간(ms): 1338&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;10장의 354KB 이미지를 업로드하는 데에 평균 1331ms가 소요되었다.&lt;/p&gt;
&lt;p&gt;다중 이미지 업로드 시, 단일 스레드를 사용하여 순차적으로 처리하는 방식은 간단하고 직관적이다.
이 방법을 사용하면, 각 이미지는 이전 이미지가 완전히 업로드된 후에 업로드된다.
그러나 이러한 단일 스레드 방식은 이미지 하나당 소요되는 시간이 누적되어 전체 업로드 시간이 길어지는 단점이 있다.
예를 들어, 이미지 하나를 업로드하는데 평균 130ms가 소요되고 10개의 이미지를 업로드한다면, 총소요 시간은 약 1300ms가 된다.&lt;/p&gt;
&lt;p&gt;이에 비해, 여러 스레드를 사용하여 이미지를 병렬로 업로드하면 처리 시간을 단축할 수 있다.
이 방식에서는 여러 이미지가 동시에 업로드되므로, 10장의 이미지를 업로드하는 데 걸리는 시간을 1331ms에서 더 단축할 수 있을 것으로 예상된다.&lt;/p&gt;
&lt;p&gt;이제 병렬 스트림을 활용한 이미지 업로드 방식에 대해 자세히 알아보자.&lt;/p&gt;
&lt;h1&gt;병렬 스트림을 활용한 업로드&lt;/h1&gt;
&lt;p&gt;Java 8부터는 컬렉션의 병렬 처리를 지원하는 “병렬 스트림(parallel stream)“이 도입되었다.
이를 활용하면, 여러 CPU 코어를 통해 작업을 효율적으로 분산시켜 더 빠르게 수행할 수 있다.&lt;/p&gt;
&lt;p&gt;이를 활용하여 이미지 업로드를 병렬적으로 처리할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ImageUploadResponse&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;uploadFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MultipartFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; imageFiles&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;validate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageFiles&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; fileNames &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageFiles&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parallel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 병렬 스트림 생성&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;storageClient&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;upload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileNames&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;convertFileNamesToResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileNames&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        executor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;deleteFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileNames&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InternalServerException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;이미지 업로드 시 예외가 발생했습니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;각 이미지 업로드 요청이 병렬 스레드에서 동시에 처리되는 것을 로그로 확인할 수 있다. 아래 로그에서 &lt;code class=&quot;language-text&quot;&gt;[onPool-worker-*]&lt;/code&gt;는 해당 작업이 진행된 스레드 이름이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[onPool-worker-6] : File Upload 시작 (871cb6aae189.png)
[onPool-worker-4] : File Upload 시작 (9af5a8a21538.png)
[onPool-worker-2] : File Upload 시작 (1e17f3995ade.png)
[onPool-worker-1] : File Upload 시작 (587ab1b903d3.png)
[onPool-worker-5] : File Upload 시작 (bdc9a80b2331.png)
[onPool-worker-7] : File Upload 시작 (faac3d412e3e.png)
[onPool-worker-3] : File Upload 시작 (85db46aad248.png)
[onPool-worker-1] : File Upload 완료 (587ab1b903d3.png)
[onPool-worker-1] : File Upload 시작 (3cf2f27aacb3.png)
[onPool-worker-5] : File Upload 완료 (bdc9a80b2331.png)
[onPool-worker-5] : File Upload 시작 (9343ba3a01d1.png)
[onPool-worker-2] : File Upload 완료 (1e17f3995ade.png)
...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 946px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/a7c886ecb60b19d012528f417b926b67/b2dbf/%EB%B3%91%EB%A0%AC%20%EC%8A%A4%ED%8A%B8%EB%A6%BC%20%EA%B5%AC%EC%A1%B0.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 76.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAACBElEQVR42p2TbXeaQBCF/f8/K6emrWlMbYiKCsibqICEdwHh6bLRJp5+yDHzZXb27Llz587dATdE13Uyl2WJZpiYrkeW5VdvBtwYXduy1E2cqMSNGzRnx+l0uh3wwu5+OOR+9ItUlFEDC3vLUTC+AqyrSl72o+R5TlmUoj7Kuq5r+bBnsdvt8H2fIAxZWVumuosfhP+PrC4W3A3veBw/YpkWk+cJ30ZDxr/HJElC0zToui6bnflK8Ol8wV40uAIsigLVXJCSsy8DDMvA8m1RlXjpljiNJVjPrB87TVMcd4O1OxAWnWQZvb6eZRGAWZax3ppUNIR1hGossH1H1Ce8fM/+IEYMAskojmNmsxnff/zEz4QkAsSLM9HA+6fzoBVbU+YKij5lbqiEQhNVV3nSJijalDRJpZZRFMnRL5pr1oYXzcGwN7Qft9w/cmwHdamy1FasrTUrfYUye0HTNDlqH700nuexXpvCeykbb8tENXhRV2Jx1fVSmrbhOZzz5Cvoscl4/8yfcCbFv4xysY1hGIxGD7jCh4m4Mv1EkHDeR+4PbddSnSrqU/3h3AgTvwP1uZenj34JSzdA+BrnUGA63jXgmxG6q/yJzbFsW3y9nbBNSHP26pe+Xlw0jNcJ27jiYeUz22Qobs5U5Nu+3jknZYMVlFhhgXU4ou8LdF+cw/JrDD+Lv55Kg//X6bEoAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;         &quot; title=&quot;&quot; src=&quot;/static/a7c886ecb60b19d012528f417b926b67/b2dbf/%EB%B3%91%EB%A0%AC%20%EC%8A%A4%ED%8A%B8%EB%A6%BC%20%EA%B5%AC%EC%A1%B0.png&quot; srcset=&quot;/static/a7c886ecb60b19d012528f417b926b67/0eb09/%EB%B3%91%EB%A0%AC%20%EC%8A%A4%ED%8A%B8%EB%A6%BC%20%EA%B5%AC%EC%A1%B0.png 500w,
/static/a7c886ecb60b19d012528f417b926b67/b2dbf/%EB%B3%91%EB%A0%AC%20%EC%8A%A4%ED%8A%B8%EB%A6%BC%20%EA%B5%AC%EC%A1%B0.png 946w&quot; sizes=&quot;(max-width: 946px) 100vw, 946px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;h2&gt;성능 측정&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[PostFacadeService.createPost] 실행 시간(ms): 256
[PostFacadeService.createPost] 실행 시간(ms): 223
[PostFacadeService.createPost] 실행 시간(ms): 213
[PostFacadeService.createPost] 실행 시간(ms): 217
[PostFacadeService.createPost] 실행 시간(ms): 238&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;병렬 스트림을 활용하니 처리 시간이 평균 1331ms에서 229ms로 약 &lt;strong&gt;5배 이상 단축&lt;/strong&gt;되었다.&lt;/p&gt;
&lt;h2&gt;병렬 스트림 사용 시 주의점&lt;/h2&gt;
&lt;p&gt;병렬 처리 중 공유 자원에 대한 동시 접근 시 문제가 발생할 수 있다.&lt;/p&gt;
&lt;h3&gt;공유 자원과 &lt;code class=&quot;language-text&quot;&gt;ConcurrentModificationException&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;본 예제에서 이미지 파일들을 병렬로 업로드하는 작업 중 실패가 발생하면, 이미 업로드된 이미지들을 삭제하려고 한다.
이 과정에서 &lt;code class=&quot;language-text&quot;&gt;ConcurrentModificationException&lt;/code&gt; 예외가 발생할 수 있다.
이는 여러 스레드가 동시에 같은 컬렉션을 수정하려 할 때 발생하는 예외이다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;uploadFiles&lt;/code&gt; 메서드에서 파일 업로드가 성공하면 해당 파일의 이름을 &lt;code class=&quot;language-text&quot;&gt;fileNames&lt;/code&gt; 리스트에 추가한다.
만약 어떤 스레드의 업로드가 실패하면, 즉시 &lt;code class=&quot;language-text&quot;&gt;deleteFiles(fileNames)&lt;/code&gt; 메서드를 호출하여 삭제 작업을 시작한다.
이때 다른 스레드가 아직 업로드를 완료하지 않아 리스트에 새로운 요소를 추가하는 상황이 발생하면 &lt;code class=&quot;language-text&quot;&gt;ConcurrentModificationException&lt;/code&gt; 예외가 발생한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Exception in thread &quot;taskExecutor-1&quot; java.util.ConcurrentModificationException
at java.base/java.util.ArrayList.forEach(ArrayList.java:1513)
at practice.s3.application.ImageStorageService.deleteFiles(ImageStorageService.java:67)
at practice.s3.application.ImageStorageService.lambda$uploadFiles$0(ImageStorageService.java:38) &amp;lt;3 internal lines&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;병렬 작업 종료 후 통합 예외 처리&lt;/h3&gt;
&lt;p&gt;이러한 문제를 방지하기 위해, &lt;strong&gt;모든 병렬 작업이 끝난 후&lt;/strong&gt;에 한꺼번에 예외를 처리하는 방식으로 코드를 개선했다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ImageUploadResponse&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;uploadFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MultipartFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; imageFiles&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;validate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageFiles&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;AtomicBoolean&lt;/span&gt; catchException &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AtomicBoolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; fileNames &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageFiles&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parallel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;uploadFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; catchException&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nonNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;handleException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;catchException&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fileNames&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;convertFileNamesToResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileNames&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;uploadFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MultipartFile&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AtomicBoolean&lt;/span&gt; catchException&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; storageClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;upload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        catchException&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AtomicBoolean&lt;/span&gt; catchException&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; fileNames&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;catchException&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        executor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;deleteFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileNames&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InternalServerException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;이미지 업로드 시 예외가 발생했습니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;여기서는 &lt;code class=&quot;language-text&quot;&gt;AtomicBoolean&lt;/code&gt;을 활용하여 예외 발생 여부를 각 스레드에서 안전하게 추적한다. 업로드 과정에서 예외가 발생하면, 해당 변수를 true로 설정하여 상태를 기록한다.&lt;/p&gt;
&lt;p&gt;모든 병렬 작업이 종료된 후, 이 &lt;code class=&quot;language-text&quot;&gt;AtomicBoolean&lt;/code&gt; 상태를 검사함으로써 예외 발생 유무를 확인한다.
이렇게 함으로써 동시성 문제를 피하면서 안정적으로 예외를 처리할 수 있게 되었다.&lt;/p&gt;
&lt;h2&gt;병렬 스트림의 제약점&lt;/h2&gt;
&lt;p&gt;병렬 스트림은 내부적으로 &lt;code class=&quot;language-text&quot;&gt;ForkJoinPool&lt;/code&gt;을 사용하여 자동으로 작업을 여러 스레드에 분배한다. 이러한 분배는 시스템의 프로세서 수를 고려하여 스레드 수를 결정한다. 본 포스트의 성능 측정은 7개의 스레드가 활용된 환경에서 이루어졌다.&lt;/p&gt;
&lt;p&gt;병렬 스트림의 스레드 수를 조절하는 것은 기술적으로 가능하나, 이 조정은 &lt;code class=&quot;language-text&quot;&gt;ForJoinPool.commonPool()&lt;/code&gt;에 대한 전역 설정을 변경하는 것이므로, 모든 병렬 스트림 작업에 영향을 미친다.&lt;/p&gt;
&lt;p&gt;예를 들어, 작업 A에 최적화하기 위해 스레드 수를 10으로 설정한다면, 이는 작업 B,C,D 등 현재 진행 중인 다른 모든 병렬 스트림 작업에도 동일하게 적용된다. 따라서 각 작업의 특성에 따라 최적의 성능을 얻기 위해 병렬 스트림의 스레드 수를 독립적으로 조절하는 것이 쉽지 않다.&lt;/p&gt;
&lt;p&gt;그렇다면, 더 세밀한 병렬 처리 제어를 원할 땐 어떻게 해야 할까?&lt;/p&gt;
&lt;h1&gt;CompletableFuture를 활용한 병렬 업로드&lt;/h1&gt;
&lt;p&gt;Java 8이 등장하면서, &lt;code class=&quot;language-text&quot;&gt;CompletableFuture&lt;/code&gt;는 비동기 프로그래밍에 큰 변화를 불러왔다. 이 클래스는 비동기 연산의 결과를 나타내는 것뿐만 아니라, 그 완료 시점과 연산의 수명 주기를 세부적으로 제어할 수 있게 해준다. 이는 단순히 결과를 기다리는 것을 넘어, 연산을 시작하고, 완료하며, 여러 작업을 결합하고, 예외를 처리하는 등의 복잡한 비동기 흐름을 명시적으로 제어할 수 있다는 의미다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ForkJoinPool&lt;/code&gt;은 큰 작업을 작은 단위로 분할하여 병렬로 처리한 다음, 그 결과를 합치는 데 초점을 맞추고 있다. 반면, &lt;code class=&quot;language-text&quot;&gt;CompletableFuture&lt;/code&gt;는 기본적으로 사용하는 &lt;code class=&quot;language-text&quot;&gt;ForkJoinPool&lt;/code&gt; 외에도, 자체적으로 지정한 executor를 통해 스레드 풀을 직접 제어할 수 있다. 이를 통해 특정 작업에 최적화된 병렬 처리가 가능해지고, 복잡한 비동기 작업 흐름을 필요에 맞게 구성할 수 있게 된다.&lt;/p&gt;
&lt;p&gt;예를 들어, 이미지 업로드 작업과 같이 I/O 집약적인 작업이 있을 때,&lt;code class=&quot;language-text&quot;&gt;CompletableFuture&lt;/code&gt;를 사용하여 특정 &lt;code class=&quot;language-text&quot;&gt;coreSize&lt;/code&gt;인 &lt;code class=&quot;language-text&quot;&gt;ThreadPoolTaskExecutor&lt;/code&gt;를 사용함으로써, 독립적인 스레드 풀을 구성하여 업로드를 병렬로 수행할 수 있다. 이는 각각의 작업이 다른 작업의 리소스 사용에 영향을 끼치지 않으면서 효율적으로 수행할 수 있도록 해준다. 이처럼 &lt;code class=&quot;language-text&quot;&gt;CompletableFuture&lt;/code&gt;는 복잡한 비동기 흐름을 잘 제어하고, 각 작업에 맞는 병렬 처리를 해야 하는 상황에서 유용한 도구이다.&lt;/p&gt;
&lt;p&gt;본 예제에서는 한 포스트에 최대 10장의 이미지를 업로드하는 상황을 가정하여, &lt;code class=&quot;language-text&quot;&gt;coreSize&lt;/code&gt;가 10인 &lt;code class=&quot;language-text&quot;&gt;ThreadPoolTaskExecutor&lt;/code&gt;를 설정해 병렬 업로드를 실행했다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AsyncConfig&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Executor&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;taskExecutor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ThreadPoolTaskExecutor&lt;/span&gt; executor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ThreadPoolTaskExecutor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        executor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setCorePoolSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        executor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; executor&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Executor&lt;/span&gt; executor&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ImageUploadResponse&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;uploadFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MultipartFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; imageFiles&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;validate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageFiles&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CompletableFuture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; futures &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageFiles&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CompletableFuture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;supplyAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; storageClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;upload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; executor&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; fileNames &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;gatherFileNamesFromFutures&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;futures&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;convertFileNamesToResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileNames&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;gatherFileNamesFromFutures&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CompletableFuture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; futures&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; fileNames &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;AtomicBoolean&lt;/span&gt; catchException &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AtomicBoolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    futures&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;future &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            fileNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;future&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CompletionException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            catchException&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;handleException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;catchException&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fileNames&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; fileNames&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AtomicBoolean&lt;/span&gt; catchException&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; fileNames&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;catchException&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        executor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;deleteFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileNames&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InternalServerException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;이미지 업로드 시 예외가 발생했습니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;uploadFiles&lt;/code&gt; 메서드는 이미지 파일들을 병렬로 업로드한다.
&lt;code class=&quot;language-text&quot;&gt;CompletableFuture.supplyAsync()&lt;/code&gt;를 사용해 병렬 작업을 시작하며 그 결과를 &lt;code class=&quot;language-text&quot;&gt;CompletableFuture&lt;/code&gt;로 반환한다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;gatherFileNamesFromFutures&lt;/code&gt; 메서드는 &lt;code class=&quot;language-text&quot;&gt;join()&lt;/code&gt; 메서드를 사용해 각 &lt;code class=&quot;language-text&quot;&gt;CompletableFuture&lt;/code&gt;의 결과를 기다린 후, 해당 결과를 &lt;code class=&quot;language-text&quot;&gt;fileNames&lt;/code&gt; 리스트에 추가한다.
&lt;code class=&quot;language-text&quot;&gt;join()&lt;/code&gt; 메서드는 &lt;code class=&quot;language-text&quot;&gt;CompletableFuture&lt;/code&gt;의 작업이 완료될 때까지 대기하고 그 결과를 반환한다.&lt;/p&gt;
&lt;p&gt;만약 &lt;code class=&quot;language-text&quot;&gt;join()&lt;/code&gt; 메서드 수행 중 예외가 발생하면 &lt;code class=&quot;language-text&quot;&gt;CompletableException&lt;/code&gt;으로 감싸져 던져진다.
위 코드에서는 &lt;code class=&quot;language-text&quot;&gt;AtomicBoolean&lt;/code&gt;을 사용하여 예외 발생 여부를 관리한다.&lt;/p&gt;
&lt;p&gt;이후의 예외 처리 전략은 병렬 스트림에서 사용된 방식과 동일하다.&lt;/p&gt;
&lt;h2&gt;성능 측정&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// CommonForkJoinPool
[PostFacadeService.createPost] 실행 시간(ms): 312
[PostFacadeService.createPost] 실행 시간(ms): 261
[PostFacadeService.createPost] 실행 시간(ms): 322
[PostFacadeService.createPost] 실행 시간(ms): 251
[PostFacadeService.createPost] 실행 시간(ms): 241

// ThreadPoolTaskExecutor (coreSize 10)
[PostFacadeService.createPost] 실행 시간(ms): 182
[PostFacadeService.createPost] 실행 시간(ms): 161
[PostFacadeService.createPost] 실행 시간(ms): 197
[PostFacadeService.createPost] 실행 시간(ms): 191
[PostFacadeService.createPost] 실행 시간(ms): 179&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;스레드풀을 별도로 지정하지 않았을 때의 평균 처리 시간은 277ms였다.
반면, 커스텀 스레드풀을 적용했을 때의 평균 처리 시간은 182ms로 감소했으며, 이는 기존 병렬 스트림의 처리 시간 229ms와 비교하여 더 짧은 시간이다.&lt;/p&gt;
&lt;p&gt;이 결과로부터 병렬 스트림과 &lt;code class=&quot;language-text&quot;&gt;CompletableFuture&lt;/code&gt;를 사용한 일반적인 처리에서 성능 차이는 크지 않지만, 특정 작업에 최적화된 executor를 사용하면 성능이 향상될 수 있다는 결론을 얻을 수 있다.
예를 들어, 대규모 파일 I/O 작업을 수행하는 경우에는 I/O 작업에 최적화된 스레드 풀 설정을 적용하여 디스크 접근과 네트워크 전송을 병렬로 처리할 수 있다. 고성능 컴퓨팅 작업에서는 CPU 사용률을 극대화할 수 있는 스레드 풀 구성으로 성능을 향상할 수 있다.&lt;/p&gt;
&lt;p&gt;이처럼 실제 사용 시나리오와 요구 사항을 고려하여 성능 테스트를 실시하고, 테스트 결과를 바탕으로 특정 작업에 최적화된 스레드 풀을 선택하는 것이 중요하다.&lt;/p&gt;
&lt;h1&gt;마무리&lt;/h1&gt;
&lt;p&gt;이 글을 통해, 병렬 처리를 통해 다중 이미지를 효율적으로 업로드하는 기법들을 알아보았다. 그러나 이러한 기법들은 이미지 업로드에만 국한되지 않는다.&lt;/p&gt;
&lt;p&gt;예를 들면, 웹 크롤링을 할 때 여러 사이트의 데이터를 동시에 수집하는 작업에서도 병렬 처리는 큰 이점을 가져다준다. 배치 작업에서도, 서로 독립적인 작업 항목들을 병렬로 처리함으로써 전체 작업 시간을 대폭 줄일 수 있다. 데이터베이스에서는 복잡한 쿼리나 대용량 데이터 처리를 병렬로 실행하여 응답 시간을 단축할 수 있다. 서버 측에서는 사용자의 다양한 요청을 동시에 처리하기 위해 요청 처리를 병렬로 수행하여, 더 빠른 서비스 제공이 가능해진다.&lt;/p&gt;
&lt;p&gt;이처럼 병렬 처리는 단순히 업로드 시간을 단축하는 것을 넘어, 다양한 분야에서 응답성과 성능을 향상하는 핵심 기술이다.&lt;/p&gt;
&lt;p&gt;본문에서는 특히 병렬 스트림과 &lt;code class=&quot;language-text&quot;&gt;CompletableFuture&lt;/code&gt;에 초점을 맞추어 알아보았는데, 이들은 각각의 사용 사례에 따라 장점이 있다.&lt;/p&gt;
&lt;p&gt;병렬 스트림은 사용이 매우 간편하다는 장점이 있으나, 고정된 스레드 풀을 사용한다는 점에서 유연성이 다소 떨어진다.
반면, &lt;code class=&quot;language-text&quot;&gt;CompletableFuture&lt;/code&gt;는 보다 복잡한 비동기 작업을 우아하게 처리할 수 있으며, 특히 커스텀 스레드 풀을 사용하여 성능을 최적화할 수 있다.&lt;/p&gt;
&lt;p&gt;이들 외에도 다양한 병렬 처리 방식이 존재한다. 상황과 요구 사항에 따라 가장 적합한 방법을 선택하고 적용하는 것이 중요하다. 따라서 다양한 방법을 시도하고, 성능 측정 결과를 바탕으로 자신에게 맞는 최적의 해결책을 찾는 것이 핵심이다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[JPA가 Fetch Join에 대한 On절을 지원하지 않는 이유]]></title><description><![CDATA[JPA가 Fetch Join에 대한 On절을 지원하지 않는 이유 집사의고민 프로젝트에서 조회 쿼리가 발생할 때 데이터가 삭제되는 상황이 발생하였습니다.
그래서 이번 글에서는 간단한 JPA의 Fetch Join…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-11-01-jpa-fetch-join/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-11-01-jpa-fetch-join/</guid><pubDate>Thu, 23 Nov 2023 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;JPA가 Fetch Join에 대한 On절을 지원하지 않는 이유&lt;/h1&gt;
&lt;hr&gt;
&lt;p&gt;집사의고민 프로젝트에서 조회 쿼리가 발생할 때 데이터가 삭제되는 상황이 발생하였습니다.
그래서 이번 글에서는 간단한 JPA의 Fetch Join 동작 원리와 유의해야 하는 부분들에 관해서 기술해보려고 합니다.&lt;/p&gt;
&lt;h3&gt;참고 ERD&lt;/h3&gt;
&lt;p&gt;아래와 같은 테이블 구조에서 발생하는 문제입니다. 참고로 테이블의 모든 컬럼을 명시하지는 않았습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 882px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7307f8fd2e96a1e548017953a4825e33/5f5e5/ERD.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 65.60000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAABzklEQVR42pVTSXLbMBDUQ6LFyiG+xEksmSYobiAA7lriVP7/lMl0Q1IOSZXLhyZQ4Eyjp2ewcG6SovBSloFomuEfIMb7maiqlvH/i8P/RZ43Yq2VeUKSl81mLQ8PG2K9Xsty+UmSJJOuu1wvLmSeZ3l8/HKPXa1Wst1ulXSURak31jbIIa9lt0/l6duO+P7jRdI0VzIjh0OjhGeqqGovRdkw5uvTM3OMKWS/N5HQalDXnzUoSPJaismspKaWXNVM85sM40XKqpNh+Clte2IsRKTGEsg/nX9L48ZIWNe9OD8RIHxNKwLESCxKzxWE1g5M6pUUMS9JoZXBsl7y3F0ValAIs7TdUcvp7qRQiRJr21HFML7RdCShSRAC3C7ByqbwwI8sxeoPEN1UNi4SggAeIiGEk65Hrjhr278gYa1y++Gsfv3S9cLyoBLETi+yrichSoaSqvKqLBDGlESWVQTiFvggEKaiNCi+gefXEkctOc5gHJsQAkcljtZS95+jhwHy34F3873LTm3A7D7vUoXhPoSRzSHh7QW8hxCO8cVoA1EJLEnUZ4xb159o1YcIbx32YaLnJkPzSo4VmodnSw8/QghYG0eEg0zfo/fYo8t/ADABycvh+vrvAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;ERD.png&quot;
        title=&quot;&quot;
        src=&quot;/static/7307f8fd2e96a1e548017953a4825e33/5f5e5/ERD.png&quot;
        srcset=&quot;/static/7307f8fd2e96a1e548017953a4825e33/0eb09/ERD.png 500w,
/static/7307f8fd2e96a1e548017953a4825e33/5f5e5/ERD.png 882w&quot;
        sizes=&quot;(max-width: 882px) 100vw, 882px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;우선 쿼리가 정상적으로 동작하는지와 어떤 쿼리가 발생하는지를 알아보기 위해 간단한 테스트 코드를 작성하는 것으로 시작해 보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@DataJpaTest&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;QueryDslTestConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Sql&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scripts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;classpath:truncate.sql&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;classpath:data.sql&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@AutoConfigureTestDatabase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;replace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AutoConfigureTestDatabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NONE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PetFoodRepositoryImplTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PetFoodQueryRepository&lt;/span&gt; petFoodQueryRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; 키워드와_브랜드_이름으로_동적_조회한다&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// given&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; keyword &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;diet&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; brand &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;베베&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// when&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PetFood&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; petFoods &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; petFoodQueryRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;searchPetFoodByDynamicValues&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;keyword&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; brand&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// then&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;assertAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;petFoods&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;extracting&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;petFood &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; petFood&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getKeyword&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;diet&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;petFoods&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;extracting&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;petFood &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; petFood&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBrand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;퓨리나&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;petFoods&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;쿼리 확인&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;Hibernate: 
&lt;span class=&quot;token comment&quot;&gt;// 1번&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;
        p1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        p1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;brand_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        p1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;image_url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        p1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;keyword_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        p1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        p1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;purchase_link 
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;
        pet_food p1_0 
    &lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt;
        brand b1_0 
            &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; b1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;p1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;brand_id 
    &lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt;
        keyword k1_0 
            &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; k1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;p1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;keyword_id 
    &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;
        k1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;? 
        &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; b1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;?
Hibernate: 
&lt;span class=&quot;token comment&quot;&gt;// 2번&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;
        k1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        k1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name 
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;
        keyword k1_0 
    &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;
        k1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;?
petFood&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getKeyword&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getName&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; 다이어트
Hibernate: 
&lt;span class=&quot;token comment&quot;&gt;// 3번&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;
        b1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        b1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;founded_year&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        b1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;has_research_center&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        b1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;has_resident_vet&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        b1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        b1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nation 
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;
        brand b1_0 
    &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;
        b1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;?
petFood&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getBrand&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getName&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; 베베
&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;총 1 + N(2)가 발생하여 3번의 쿼리가 발생했습니다.
이렇게 조회한 엔티티가 한 번, 그와 연관된 엔티티가 N번 조회되는 문제를 N+1 문제라고 합니다.
Fetch Join 문법을 사용하여 이 문제를 해결할 수 있습니다.
이제 해당 쿼리를 개선해 보겠습니다.&lt;/p&gt;
&lt;h2&gt;Fetch Join으로 개선하기&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;위 문제를 해결하기 위해 PetFoodQueryRepository의 searchPetFoodByDynamicValues 메서드를 수정해 보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Repository&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;readOnly &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PetFoodQueryRepository&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JPAQueryFactory&lt;/span&gt; queryFactory&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PetFood&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;searchPetFoodByDynamicValues&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; keywordName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; brandName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; queryFactory
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;selectFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;petFood&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;petFood&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;keyword&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; keyword&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equalsKeyword&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;keywordName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;petFood&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;brand&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; brand&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equalsBrand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;brandName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetchJoin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BooleanExpression&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;equalsKeyword&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; keywordName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;keywordName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; keyword&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;keywordName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BooleanExpression&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;equalsBrand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; brandName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;brandName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; brand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;brandName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같이 on절로 필터링을 진행하고 Fetch Join을 사용했습니다. 조인 조건은 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;키워드가 일치한 데이터만 Join&lt;/li&gt;
&lt;li&gt;브랜드가 일치한 데이터만 Join&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;하지만 테스트 코드를 실행시키면 아래와 같은 에러가 발생합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;java.lang.IllegalArgumentException: org.hibernate.query.SemanticException: with-clause not allowed on fetched associations; use filters&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;에러를 자세히 읽어보면 &lt;code class=&quot;language-text&quot;&gt;not allowed on fetched associations&lt;/code&gt;이 발생하는 것을 확인할 수 있는데,
이는 fetch 구문에서 on절을 지원하지 않는다는 뜻입니다.&lt;/p&gt;
&lt;p&gt;실제로 하이버네이트 공식 문서에는 이런 말이 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“HQL(Hibernate Query Language)에서 Fetch Join에 별칭(Alias)이 필요한 유일한 이유는 추가 컬렉션(N방향)을 재귀적으로 조인하여 가져오는 경우밖에 없다.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 말은 Fetch Join에 on절이 필요한 경우는 거의 없다시피 하지만,
역설적으로 하이버네이트 상에서는 Fetch Join을 지원하긴 한다는 뜻입니다.&lt;/p&gt;
&lt;p&gt;하지만 JPQL로 Fetch Join + on절을 사용하면 런타임 에러가 발생합니다.&lt;/p&gt;
&lt;p&gt;JPQL에서는 Fetch Join에 on절을 지원하지 않을까요?
이 이유는 생각보다 간단하지만, 많은 분들이 간과하고 있을 것 같습니다.&lt;/p&gt;
&lt;h2&gt;Fetch Join의 한계&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Fetch Join 문법은 N+1 문제를 방지하기 위해 연관관계가 맺어진 테이블의 모든 값들을 한 번에 가져옵니다.
Fetch Join은 너무나 필수적인 기능이기도 하지만 다음과 같이 명확한 한계가 존재합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fetch Join의 대상은 별칭을 줄 수 없다.&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;select절, where절, 서브쿼리 등에서 Fetch Join 대상을 사용할 수 없다.&lt;/li&gt;
&lt;li&gt;하이버네이트 자체에서는 별칭을 지원하지만, 연관된 데이터 수에 대한 무결성이 깨질 수도 있다.(아래에서 자세하게 설명)
JPQL과 querydsl에서 Fetch Join + on 문법을 지원하지 않는 이유도 위와 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;(참고) 2개 이상의 xToMany 관계에서는 모든 연관 엔티티에 Fetch Join을 적용할 수 없다.
&lt;ul&gt;
&lt;li&gt;대신 default_batch_fetch_size라는 차선책이 존재한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;(참고) 페이지네이션 사용이 불가능하다.
&lt;ul&gt;
&lt;li&gt;컬렉션(xToMany) 관계에서 Fetch Join을 사용하면 페이징 처리가 불가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;On절 대신 Where절로 사용하기&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Fetch Join에서 on절을 지원하진 않지만, where절은 사용할 수 있습니다.
다만, 잘못 사용하면 꽤나 골치 아픈 일이 일어날 수도 있기 때문에 확실히 짚고 넘어가야 합니다.&lt;/p&gt;
&lt;h3&gt;Fetch Join의 On절을 지원하지 않는 이유는 무엇일까?&lt;/h3&gt;
&lt;p&gt;위에서 하이버네이트 자체에서는 별칭을 지원하지만, 연관된 데이터 수에 대한 무결성이 깨질 수도 있다고 했습니다.
이는 JPA에서 엔티티 객체 그래프와 DB의 데이터 일관성을 맞추어 주어야 하기 때문입니다.
예를 들어 아래와 같은 데이터가 있다고 가정해 보겠습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;brand1 - petFood1&lt;/li&gt;
&lt;li&gt;brand1 - petFood2&lt;/li&gt;
&lt;li&gt;brand1 - petFood3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;만약 조인 대상(petFood)을 필터링의 주체로 사용하여 petFood3을 제외하고 조인한다면,
JPA는 다음과 같은 결과를 반환해 줄 것입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;brand1 - [petFood1, petFood2]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;얼핏 보면 잘못된 게 없습니다. 저희가 바라던 대로 데이터가 반환되었으니까요.
하지만 위와 같은 결과가 나오면 Fetch Join을 한 JPA의 입장에서 상당히 난처해집니다.&lt;/p&gt;
&lt;p&gt;Fetch Join은 단 한 번의 쿼리로 join 대상 테이블 데이터까지 한 번에 가져오고 영속성 컨텍스트에 반영합니다.
그래서 임의로 데이터를 빼고 조회하면 db에는 해당하는 데이터가 없다고 판단합니다.&lt;/p&gt;
&lt;p&gt;엔티티 값을 변경하면 db에 쉽게 반영되는 걸 알 수 있듯이, 이렇게 되면 최악의 경우에는 petFood3이 db에서 삭제될 수도 있습니다.
위 상황들을 고려해서 JPA는 이 상황을 방지하기 위해 별칭에 대한 조건절을 제공해주지 않습니다.&lt;/p&gt;
&lt;p&gt;이해를 돕기 위해 쿼리문을 직접 작성해 보겠습니다.
예를 들어 brand(1)과 연관관계가 맺어진 petFood(N)을 Fetch Join으로 조회한다고 가정해 보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 1번 쿼리&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; b &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; Brand b &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fetch&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;petFoods pf &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; pf&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;베베&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;1번 쿼리는 Fetch Join에 별칭을 사용했기 때문에 런타임에서 에러가 발생합니다. 따라서 N+1을 해결할 수 없습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 2번 쿼리&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; b &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; Brand b &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fetch&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;petFoods pf &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; pf&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;베베&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;사실 1번 쿼리와 2번 쿼리는 동일하게 동작하는 쿼리입니다.
그리고 조인과 무관하게 Brand 자체 데이터를 필터링하는 것이기 때문에,
on절보다 where절을 사용하는 게 더 적절합니다.&lt;/p&gt;
&lt;h2&gt;Fetch Join과 Where절의 함정 그리고 Outer Join&lt;/h2&gt;
&lt;p&gt;“Fetch Join은 on을 지원하지 않지만, where로 해결할 수 있다.”라고 느껴질 수도 있는데, 반은 맞고 반은 틀린 말입니다. where절도 잘못 사용하게 된다면 on절과 똑같은 문제가 발생합니다.&lt;/p&gt;
&lt;p&gt;우선 Brand와 PetFood 테이블에 대한 영속성 컨텍스트의 상황을 먼저 살펴보겠습니다.
우리가 Fetch Join을 사용할 때, 영속성 컨텍스트는 Fetch Join의 대상은 모두 존재한다고 가정합니다.
영속성 컨텍스트는 Fetch Join 실행 시 아래와 같이 베베 브랜드는 일료, 사료라는 데이터를 가지고 있죠.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/642e41392158d7fc61bd470b6f5ab3ed/de634/img_2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 49.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABj0lEQVR42n2S227jMAxEdaVkyZZkJ0YQYFH0/39yOlSaZLFF92FgWo4mh0MaYwxySqilQOu/tSwLaq0/zp+KMaL1jijyPne9IR4D6djh+gbLj9ZYWOt4QSCS4H2g/OtSiwF7ihg54VoLdj678J0yfnQ4/lhIKY2Ga4V4S7qIUoTkgtErSZeX4UGz5B28tZRBcBaO9SWRNO0DC6lKzsjrinIduF0StjVi7wn70Dg8zkskpZ2GSqNm/0bQJyGp9GMi4ThPjPuJP/cV28YWukzDbfW4XWni3oZBCdlZCGHKOsfzwAy3lZmZiZxZ1/PK2jA7j5wjW09IohfdHNDH5yczEyya2xg4jgO7dsmharbG08QZNRAk5tfuN6SoU3M0iVPWejjnH0ScrBImPjO3oNBIt8GT8kWodDoYy1CFQzqPTjqPtcrUsoT5br+zGv/N8EnIfxS2FEdhhhW9saWmGWaaBtzOd4a/GT7WhvsXBndx7/DMItSMsTkOIqBtbKOFOZRjhJmtXtQd1BV57p5Ka12bL9Jc19ERD4jyAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;img_2.png&quot;
        title=&quot;&quot;
        src=&quot;/static/642e41392158d7fc61bd470b6f5ab3ed/f97d7/img_2.png&quot;
        srcset=&quot;/static/642e41392158d7fc61bd470b6f5ab3ed/0eb09/img_2.png 500w,
/static/642e41392158d7fc61bd470b6f5ab3ed/1263b/img_2.png 1000w,
/static/642e41392158d7fc61bd470b6f5ab3ed/f97d7/img_2.png 2000w,
/static/642e41392158d7fc61bd470b6f5ab3ed/de634/img_2.png 2982w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;그럼 여기에서 아래와 같은 Left Join Fetch 쿼리를 사용하게 되면 어떤 결과가 나올까요?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; b &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; Brand b &lt;span class=&quot;token keyword&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FETCH&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;petFood pf &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; pf&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;사료&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Fetch Join의 대상에 대한 조건을 걸었을 때 영속성 컨텍스트가 기대하는 객체 그래프의 상태와는 달라진 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/bd2d97c051c563574c95ba858717a7f6/425d1/img_3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 47.599999999999994%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABsElEQVR42j1S23aEIBADAUUugrdq3W7//y/TDLp9yIFBJiRxVMgZddugrIHSGppQSmEYBszzjL7vW/05F7iug+E6xYBz3xCGHlYr2I53TE6wU4LbFmjutTQ4B2NMI/XeI6WEjiRCJk3n6FF5Xvg98+787I9xgHK6g+fhmCIJIxyJQowIITRE7oXQWnsTUukeB/y8F7wf/P6u+L4mLJ5uPC1FaZ4yOoHScDwTq0ImaoXso9Dw+1V6rGtCLQXbOrdoziNi9Xy0WWTDtDCvuTbLhrWQCKlYFzKB1GHwOLLD68okKbgukp0VP69IhSQ0tNCaw4j6unDXpkEylNWYrhF/HQfe14VkxEXH7DQOogyaIhRq76A6KpKXPe2N+9rsay0ElnZ7KjMPHst88MxiucdWqXbxWLl+7T0VNkIqEltk1xyhYy8tj6U67Ktv6zrzwcE8f7lrGe47x60WrJyOWiu+T2Y42luhBB84MooqdzbPNTZFI8dDLAtZLfZ/BrfgUKolkbtRpDa3QlsybJ1gl8pfPnJYNVVZlMneF7nOrFN8LHMONw5yYiSZ2WdzI3G/DD3+AECd3FsPfPfKAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;img_3.png&quot;
        title=&quot;&quot;
        src=&quot;/static/bd2d97c051c563574c95ba858717a7f6/f97d7/img_3.png&quot;
        srcset=&quot;/static/bd2d97c051c563574c95ba858717a7f6/0eb09/img_3.png 500w,
/static/bd2d97c051c563574c95ba858717a7f6/1263b/img_3.png 1000w,
/static/bd2d97c051c563574c95ba858717a7f6/f97d7/img_3.png 2000w,
/static/bd2d97c051c563574c95ba858717a7f6/425d1/img_3.png 2942w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이로 인해 영속성 컨텍스트는 “원래는 베베 브랜드에는 일료, 사료라는 객체가 존재했는데, 일료 객체가 제거되었다.”라는 판단을 내리게 됩니다.
우리는 실제로 일료 객체를 삭제한 적이 없는데 말이죠.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5bd262d3b7daf1aaea700eefd49d9f11/cae0a/img_4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 45.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABkElEQVR42nWSyY7bMBBE2Vwlips2e2w4mcHcghwC5P//rVKkHcwlORCkGs3H6ioppzWsCGpK2FuDNwZOCzRrSikIv+08Y7ucMCFAtIEI6+yZpgm1FhjTa89+dY8TkjVIRmPhStZiDx4tuNGgtwbTeGmeMF1PSEkQ1g37jLGwzhE8jzWAzT8v9ib1Wl11edUdYTFGRB+QqEY4SYuC++ZxVI/rGnBtAY89EOq+gH3ELlu0wuQsKlVajpKpylCF43lpFYrwo2rcisO37PBRPO7R4rY6nGd5AuOyYN93HPuG37/e8Pl9w8ftjvPxjshdaIXhY5Zjq6WrFRyNtlBhWye0TJsI7t4OoKe5pRTUkvHzx4bHNaHGhHh5hz4u0B1IG2b2CIHdHs1w+uo+iuhxHsH8y0PPy/lVN3xEc+QO7Ao70FLJPLux0kLvgqF/9v+h+BHKs0EPIBOVrjAPD9esx7iFHrYakBNHpoeeVqg3vpp7CAQU7v288Zf5m3L/ZexaYQnzl50eLgO4ElgZzMqE0/IF/AOxDsTKT0HSOAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;img_4.png&quot;
        title=&quot;&quot;
        src=&quot;/static/5bd262d3b7daf1aaea700eefd49d9f11/f97d7/img_4.png&quot;
        srcset=&quot;/static/5bd262d3b7daf1aaea700eefd49d9f11/0eb09/img_4.png 500w,
/static/5bd262d3b7daf1aaea700eefd49d9f11/1263b/img_4.png 1000w,
/static/5bd262d3b7daf1aaea700eefd49d9f11/f97d7/img_4.png 2000w,
/static/5bd262d3b7daf1aaea700eefd49d9f11/49142/img_4.png 3000w,
/static/5bd262d3b7daf1aaea700eefd49d9f11/cae0a/img_4.png 3024w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위 그림처럼 베베 브랜드의 일료 데이터가 사라진 상태이기 때문에 저 상태에서 db에 flush가 된다면 실제 데이터베이스의 데이터가 삭제됩니다.
이렇게 잘못된 where절로 해결하려고 하다가 런타임 예외로 막아둔 것 마저 무시해 버리고 db 데이터를 삭제하게 될 수도 있으니 꼭 주의해서 사용해야 합니다.&lt;/p&gt;
&lt;h3&gt;검증 - Outer Fetch Join과 Where절 필터링&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BrandRepository&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JpaRepository&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Brand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;select b from Brand b left join fetch b.petFoods pf where pf.name = :petFoodName&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Brand&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findBrandsByPetFoodName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;petFoodName&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; petFoodName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt; 
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;outer_fetch_join_test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Brand&lt;/span&gt; brand &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; brandRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findBrandsByPetFoodName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;사료&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PetFood&lt;/span&gt; petFood &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; brand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPetFoods&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;petFood.getName() = &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; petFood&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;petFood&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; 사료&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위에서 봤던 그림처럼 brand는 데이터베이스 상에선 실제로 일료와 사료를 가지고 있지만, 조회해 보면 사료라는 데이터 단 하나만 가지고 있습니다. 그럼 여기서 좀 더 나가서 해당 영속성 컨텍스트를 데이터베이스에
flush 하게 되면 어떨까요?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BrandService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BrandRepository&lt;/span&gt; brandRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getBrandByPetFoodName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; petFoodName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Brand&lt;/span&gt; brand &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; brandRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findBrandsByPetFoodName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;petFoodName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        brandRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;saveAndFlush&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;brand&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;outer_join_test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    brandRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findBrandsByPetFoodName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;사료&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Brand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; brands &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; brandRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Brand&lt;/span&gt; brand &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; brands&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PetFood&lt;/span&gt; petFood &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; brand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPetFoods&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;petFood.getName() = &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; petFood&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;결과&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;=======================
petFood.getName() = 사료
=======================
petFood.getName() = 이료
=======================
petFood.getName() = 삼료
=======================&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;저희는 분명 값을 직접적으로 update 하거나 delete 하지 않았음에도 불구하고 데이터베이스에서 다시 조회했을 때 일료 데이터가 사라졌습니다.
실제 서비스에서 쿼리가 이렇게 나간다면, 유저들이 조회 쿼리 날릴 때마다 데이터가 사라집니다.&lt;/p&gt;
&lt;p&gt;이런 문제를 방지하기 위한 몇 가지 방안이 있습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;outer Fetch Join에서 where절을 사용하지 않는다.&lt;/li&gt;
&lt;li&gt;Fetch Join 대상에 where절을 사용하지 않는다.&lt;/li&gt;
&lt;li&gt;하나의 트랜잭션이 무조건 조회만 수행하도록 한다.(DB에 Flush 될 일이 절대로 없도록)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;만약 JPA를 사용하는 중 이런 쿼리가 발생한다면 위 3가지 중 하나를 잘 고민해 보고 적용해야 할 것 같습니다.&lt;/p&gt;
&lt;h2&gt;요약&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;JPA에서는 객체의 상태와 DB의 일관성 문제 때문에 다음과 같은 기능들을 제공하지 않는다.
&lt;ul&gt;
&lt;li&gt;Fetch Join의 on절을 지원하지 않는다.&lt;/li&gt;
&lt;li&gt;Fetch Join 대상에 대해서는 별칭을 지원하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;해당 문제를 해결하기 위해 where절을 사용하여 비즈니스를 풀어낼 수 있다.
&lt;ul&gt;
&lt;li&gt;단, Fetch Join의 컬렉션 결과 다(N) 쪽을 필터링 기준으로 작성하면 안 된다.&lt;/li&gt;
&lt;li&gt;특히 Outer Join에 대해서는 더욱 조심해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fetch Join 문법은 JPA를 사용하는 개발자들에게 필수 문법인 만큼, Fetch Join의 side-effect도 간단하게 짚고 넘어가는게 좋아보입니다.&lt;/p&gt;
&lt;p&gt;읽어주셔서 감사합니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[데이터베이스 복제하기(리플리케이션) 2 - 테코와 알아보는 대규모 데이터 관리]]></title><description><![CDATA[테코의 고민 2 - 이벤트에  어떤 내용을 기록해야 해? GTID…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-11-06-zero-downtime-deployment-2/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-11-06-zero-downtime-deployment-2/</guid><pubDate>Thu, 23 Nov 2023 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;테코의 고민 2 - 이벤트에  어떤 내용을 기록해야 해?&lt;/h1&gt;
&lt;p&gt;GTID 기반의 안정적인 복제 방식을 도입한 후
잘 운영되는 카페를 보며 행복한 나날을 보내는 테코.&lt;/p&gt;
&lt;p&gt;그런데 어느 날, 데이터베이스에 저장된 회원 데이터를 전체적으로 업데이트하는 작업을 하던 도중&lt;/p&gt;
&lt;p&gt;데이터베이스의 소스 서버와 리플리카 서버 A, 리플리카 서버 B는
데이터베이스의 여유 공간이 부족하다는 경고를 차례대로 보내왔어요.&lt;/p&gt;
&lt;p&gt;데이터를 수정만 했지, 한꺼번에 대량으로 추가하는 작업은 하지 않았기 때문에 매우 의아해하던 테코는
그 원인이 바이너리 로그에 있음을 알아차리고 깊은 고민에 빠졌어요.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1420px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c4fd7d8896202a538d457b0750350d6e/11bd9/2023-11-06-replication-63.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABqElEQVR42n1S6U7CQBD2+XwgX8robw2YCAgJUaHlsFzhpuUILYVetIXPmYESCOgkszO7M/vNeQei/X6POI4RhqFwFEVyP+fzN9aZE9/tdiuS6Y4PNiyXSxiGgel0ikajgU6ng16vh263i3a7jVarhWqlgmazKXdN08S2Xq9F8r8TYEIcZTAYoFaroUKfC4UCisWifP6p11FWVagVFdVqFVpDg6IomM/nSKq8AuRH3/elhB2VxlL03Q6u58L4LmFr24iO7QmI2X5OF4DcHwEm3ngh4mNUpjkF+np9wd40Dz4RAeoGZ3EJmKTquq6UEgS+lH7/8ITPvi223dHnMJRQdG3Uh/L8SAP4I0MGDoJAJJfYnlhwguiYL+AHHkb5d8SOK3dz7cCiQcbRPyXfoqQCnYDKHwXANhPDTf+rKdvU9MlkgjHxcDiU9WnRmjRplXjCJaUsq1Onqeu6fhuQs+B9WiwWmM1mMKnxKq1ILpdDPp9HNptFOv2GVCqFTCYjq8LB2Hez2UiLToCe58FxHInGOzgejwXYsiysVisJxAPjO+vJG1fCOstzwF8XMUar0dFsZgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-63.png&quot;
        title=&quot;&quot;
        src=&quot;/static/c4fd7d8896202a538d457b0750350d6e/11bd9/2023-11-06-replication-63.png&quot;
        srcset=&quot;/static/c4fd7d8896202a538d457b0750350d6e/0eb09/2023-11-06-replication-63.png 500w,
/static/c4fd7d8896202a538d457b0750350d6e/1263b/2023-11-06-replication-63.png 1000w,
/static/c4fd7d8896202a538d457b0750350d6e/11bd9/2023-11-06-replication-63.png 1420w&quot;
        sizes=&quot;(max-width: 1420px) 100vw, 1420px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;변경된 데이터를 기록한다면 - Row 포맷&lt;/h2&gt;
&lt;p&gt;테코네 서버의 데이터베이스는 바이너리 로그의 각 이벤트를 &lt;strong&gt;Row 포맷&lt;/strong&gt;으로 기록하고 있었는데,
변경된 데이터를 그대로 바이너리 로그에 등록하는 특성상
&lt;strong&gt;매우 많은 데이터를 변경한 경우&lt;/strong&gt;
&lt;strong&gt;그 대량의 데이터들이 전부 로그에 기록되면서 데이터베이스 공간에 과부하를 줄 수 있다&lt;/strong&gt;는 문제가 있음을 알게 되었어요.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1420px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e7cbeaacf5f476c34983863e9ffc8837/11bd9/2023-11-06-replication-25.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 61.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABjklEQVR42o1T2W7CMBDkg/kO/gYkQMpTK5W2rwiEgtQEIlqOkDsk5L6mu6ZIrShtLDne2OvxzB4d3BllWeJ8PiOOY4RhKOw8zxEEAaqquncNnavhOA4Mw8But8PxeMR6vcZsNoOiKJjP58JWVRXT6VSc6bqOw+Eg7niedwvIr6dpiiRJxGRGzLIoCpTEiFf24b3vfmzz/g3gvdE0QBi4yNLo67/m7/+SL84XR8uy0Ov1YJD0vKghPT5gs9FQE1ZwPsCl86qs2wNut1t0u11omkZyS/inUMQ4STJYrgxLV9A0LRheQTkup9MJvu/Dtm2sViuRCA6+R3ue78Bz3XaAPGRZxmg0wnA4xHg8xmAwQL/fhyRJmDxN8PL8isVi8UPVn4BcZ5w5nxhxHDmmFjHlMmGbM13XdTuGFTmapimAdLrMUtU3BR+bd1GHy+VShIFrlYFbSQ4ohiZ1hxVHMKge7SiCTR1jhoF4aL/fI4qi3xlyAviQW+xarFmRIyfZGTHgyXZBzBMq7izLhM+99vsE1kKZ7muXFk0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-25.png&quot;
        title=&quot;&quot;
        src=&quot;/static/e7cbeaacf5f476c34983863e9ffc8837/11bd9/2023-11-06-replication-25.png&quot;
        srcset=&quot;/static/e7cbeaacf5f476c34983863e9ffc8837/0eb09/2023-11-06-replication-25.png 500w,
/static/e7cbeaacf5f476c34983863e9ffc8837/1263b/2023-11-06-replication-25.png 1000w,
/static/e7cbeaacf5f476c34983863e9ffc8837/11bd9/2023-11-06-replication-25.png 1420w&quot;
        sizes=&quot;(max-width: 1420px) 100vw, 1420px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1420px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f5643fcc6a0b6cd8cc5600e415a8c90e/11bd9/2023-11-06-replication-26.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 61.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABuUlEQVR42nVS2W7qQAzl5/jKSpX60Jc+9gG1gIoCglAoiCXsSwFxBcoFiTUEspzmuB0KbWrJccb2HHvsE8EP8X0fruvidDqdlWfHceT/eDyKHg4Hyf0pEX48z8N0OoVpmhiNRmg0Gmi322i1WqjX6+h0OiiXyyiVSmg2m6jVanJm/nq9xmq1ugak6LqOWCyGZDIJTdOQSCQQj8fFauk0UqkU4s9PyGazeCu+IZfLoVgsyj028wtQPZXd0qozFXya70ieG8Qd1xFV8cunXwEq+RzBP7jB3OhOZnTcP9xAe7nFYv4O3/oqFHI3cjlQAlG63S6i0SgMw4AdLCChV3D3qAX6jMmrBr80hm+7CJNImJMb5Iy22+13B19deCsT3n8Tf8kVoKKDbdvS7SVNdvs99lTrgM1uJ9vdBZY5oYAEmM/nGI/HQh3aarWKQqEgNMnn88KATCYNo27Ihnu9HpbLZTggK/X7fQwGA+EaQWl5kZwkF3mmzmYzKUgeLhYLIf0ZUG2I5GQnvMjKlUoFw+FQlESnj3GVQx9jk8lERnQG5Mw4G85EVdxsNqL0W5Yly+H/pZ/zY4yq2EHAD2xfhxSslyWtAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-26.png&quot;
        title=&quot;&quot;
        src=&quot;/static/f5643fcc6a0b6cd8cc5600e415a8c90e/11bd9/2023-11-06-replication-26.png&quot;
        srcset=&quot;/static/f5643fcc6a0b6cd8cc5600e415a8c90e/0eb09/2023-11-06-replication-26.png 500w,
/static/f5643fcc6a0b6cd8cc5600e415a8c90e/1263b/2023-11-06-replication-26.png 1000w,
/static/f5643fcc6a0b6cd8cc5600e415a8c90e/11bd9/2023-11-06-replication-26.png 1420w&quot;
        sizes=&quot;(max-width: 1420px) 100vw, 1420px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;그렇다면 변경된 데이터를 그대로 바이너리 로그에 기록하는 대신
다른 방법을 쓸 수는 없을까.
&lt;strong&gt;차라리 실행한 SQL을 기록하는 게 낫지 않을까?&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;실행한 SQL을 기록한다면 - Statement 포맷&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1420px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1567b04b0129d1a050be316bb1a09be2/11bd9/2023-11-06-replication-27.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 61.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABqklEQVR42oVT226CUBD0M/06f6KtNU1MY3wy2sZbMQXrBW8IiISroDDdXUOTWrUnGT0cjrMzO2sJN1YURYIgCBDHsezDMJTnJElu/Qwl/sjzXB748nq9FvR6PUG/38dgMEC328VwOISiKNA0DYZhYLVawff9v4TF4sqHwwG6rqNaraJWqwken57x8PAoZ41GA/V6XYqy8jRNbxMW63g8Yr/fYzKZYDqdYtB/w9dYw+enisViIeouia4SFtZt20alUoFLpOkJeGm8kj393JbIQOgFyLP8PiGTZVkme1VVUS6XMR6PpQ32zoVpmgjCGOvtO5a6SnfzXyLuWubFlti667qilAPzPR9REuGYBf+nzOl1Oh1JdDQaSe/4rNlsot1uo9VqybfyoVAvNXExn8+lp6z+j0JOl0fA8zyZN58U6XSZR6QYJS7CQfE9BqfMYCdX55DJNpsNzO0WW0rScvew6cx0HMyIaEaEy+VSUmZYlgWH3t3tYXI6YUPVrTDAjv4dNsEkxVYUwjlQKM4OBhXlwtdGp8SS2SLjZ0/2mfgSKU1BnJ6Hn3GZMBN+Aws/kZsxZxT7AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-27.png&quot;
        title=&quot;&quot;
        src=&quot;/static/1567b04b0129d1a050be316bb1a09be2/11bd9/2023-11-06-replication-27.png&quot;
        srcset=&quot;/static/1567b04b0129d1a050be316bb1a09be2/0eb09/2023-11-06-replication-27.png 500w,
/static/1567b04b0129d1a050be316bb1a09be2/1263b/2023-11-06-replication-27.png 1000w,
/static/1567b04b0129d1a050be316bb1a09be2/11bd9/2023-11-06-replication-27.png 1420w&quot;
        sizes=&quot;(max-width: 1420px) 100vw, 1420px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;각 이벤트에서 실행한 SQL 문을 바이너리 로그에 기록하는
&lt;strong&gt;Statement 기반 바이너리 로그 포맷&lt;/strong&gt;을 도입한다면
바이너리 로그의 용량이 크게 줄어들기 때문에 더 이상 용량 걱정을 하지 않아도 될 거예요.&lt;/p&gt;
&lt;p&gt;하지만 Statement 포맷을 선뜻 적용하기에는 찝찝함이 느껴집니다.&lt;/p&gt;
&lt;p&gt;SQL 문이 동일하다고 해도,
&lt;strong&gt;실행할 때마다 결과가 다르게 나타나는 몇몇 비 확정적(Non-Deterministic) 쿼리&lt;/strong&gt;들이 있다면
소스 서버와 리플리카 서버의 데이터가 다르게 기록되는 정합성 문제가 일어날 수 있기 때문이었는데요!&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1420px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8852651cf0cc0e409eec2ad7865c8a7c/11bd9/2023-11-06-replication-28.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAABtUlEQVR42nVTi67BQBT0/z8k0eCW6xWqXkEIoa5eLeGmLeo598xGxatNTs7u6e7smdnZBG7f9XqNhjifzzidTjgejyoOh4OaR8H/cV/iccLNm80Gg8EA9XodjUYDvV4PnU5H5X6/j3a7jclkotbt9/t4wPV6rTYyCNhqtdR4PB6riGrdbhfD4VD9G41G8H3/ieEdkFTCMFR0drsdnMUCy+USC8m/tg17Ple1n9kMQRDgcrkoRsyxlM1mE9+VCgzJ+XIZWjaLtP6FfKmEimEgo+sy19GS7igHO37U/g1ws91iKto4vgfb8+BKJ65Qchi3MWuOyGNNp0qmWA19WfgnYDvJe6HMCEX0UPKWNTkskEN8CdZJ15Mx6X8EpMjJZBLpTAZloV0UmiUJ3na1VoMhlAuFAnK5nKqbpomsSFIWaR5pJ+L8FC2y5UK4kbZ59eurfk+A0U/XdZUdqM9qtcJMbpU3vRXKdEFFuucBqVRK2ecV+A3QsixUq1XlN1KkB/nRHgRi8CD6jy/orUMCcTFzBBpldqlpGorF4t2n1M5xnHjKpMKbijKfE83NxQRhjd182vzpLf8DMTzZU/MzRfQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-28.png&quot;
        title=&quot;&quot;
        src=&quot;/static/8852651cf0cc0e409eec2ad7865c8a7c/11bd9/2023-11-06-replication-28.png&quot;
        srcset=&quot;/static/8852651cf0cc0e409eec2ad7865c8a7c/0eb09/2023-11-06-replication-28.png 500w,
/static/8852651cf0cc0e409eec2ad7865c8a7c/1263b/2023-11-06-replication-28.png 1000w,
/static/8852651cf0cc0e409eec2ad7865c8a7c/11bd9/2023-11-06-replication-28.png 1420w&quot;
        sizes=&quot;(max-width: 1420px) 100vw, 1420px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1420px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5ba4f9ded00b3a214eae9bb19d8dbd49/11bd9/2023-11-06-replication-29.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABgklEQVR42oVS226CUBDk/3+FB/ugbROVS2P0yYcm2tRUK6BVW0BjEURu092tENt42WTY5XDOnGF2FVDkeY7D4YAoihDHseQkSQRpmiLLsj/vnHlPiTAMhYND4QeTbTYbLBYLrNdr2LYNwzDQ7XbR6/WgaRra7TZ0XUen00Gr1YLjOLJ/Pp9LzRwVYUm63W6FeLlcotlsClGj0UCtVkOjXoeqqrJumiaCIMButxN1rLgMpSgKKZjIsixMp1OMRq90q023f8CmtfHkHRapHo/Hon42d+RShuu6Ql4R4kawM/skRnb06FZUhKKUkNLBYU/D9uUZcZLCJ09nszd47kr8DfcRBuYD9tYEZUPPEx4zu3HX78O7f5T3ssucU5oAn7xWDR2h/vR7pijOE1bNocPfZLZPDcpPNpfVhtY/PQ8rz8fhpBkXCfmXGUmeVYSikMA+8lpGSKgurnn4P8ru8ygNhkN8+T5iItmTqvxKgxSeI56ncqYY7FelmOqAvrHK0+ZdIvwBsFhMaCugHSIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-29.png&quot;
        title=&quot;&quot;
        src=&quot;/static/5ba4f9ded00b3a214eae9bb19d8dbd49/11bd9/2023-11-06-replication-29.png&quot;
        srcset=&quot;/static/5ba4f9ded00b3a214eae9bb19d8dbd49/0eb09/2023-11-06-replication-29.png 500w,
/static/5ba4f9ded00b3a214eae9bb19d8dbd49/1263b/2023-11-06-replication-29.png 1000w,
/static/5ba4f9ded00b3a214eae9bb19d8dbd49/11bd9/2023-11-06-replication-29.png 1420w&quot;
        sizes=&quot;(max-width: 1420px) 100vw, 1420px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Statement 포맷을 사용하게 된다면
&lt;strong&gt;데이터베이스의 저장 공간을 취하는 대신 데이터 정합성이 깨진다&lt;/strong&gt;는 딜레마에 빠지고 만 테코.&lt;/p&gt;
&lt;p&gt;데이터를 동일하고 안전하게 복제하면서도 바이너리 로그의 크기를 적당하게 유지할 수 있는 좋은 방법은 없을까요? 🥹&lt;/p&gt;
&lt;h2&gt;두 방법의 장점만 취할 수 있다면 - Mixed 포맷&lt;/h2&gt;
&lt;p&gt;이런 고민을 MySQL 개발자들도 당연히 했을 것!&lt;/p&gt;
&lt;p&gt;테코가 겪고 있는 딜레마를 해소해 주기 위해 MySQL에서는
&lt;strong&gt;평소에는 Statement 포맷으로 기록&lt;/strong&gt;하다가,
만약 &lt;strong&gt;Statement 포맷으로 복제했을 때 문제가 될 가능성이 있는 쿼리인 경우에는
Row 포맷으로 변환하여 기록&lt;/strong&gt;해주는 &lt;strong&gt;Mixed 포맷&lt;/strong&gt;을 제공하고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1420px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8b3757e549692178b88b99985d11c2c5/11bd9/2023-11-06-replication-30.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 53.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABUUlEQVR42oVS2Y6DMAzs/38bj61Q1YOHlqOUAoEAQQRmM1az6oF2LVk2sZlM7NngafM843w+Y7/fIwxDHI9HOfu0JElwOBxWa7TNKyBBCLjdbnE6nfBa83a9XrHb7WCtXQdclgVFUeB+v6NtW8mz2w1lWSJ/ntG6rkMcJ7i52uPxQJZlEo0x3wzJwLudJlgHYu3smi36wUJ3k8TBTJgmi1lr6SUZ+hfgMAyo61rYNE0D5W7WukWtWpS1ceALKjWiqhp0upN6VVWuR0Mp9TYSASRtNhBQOUDmbCS4MYOwGEcj3wTh5Xyudv385wvwP/t81l/2BshFcOj0y+UiEuHwZVEu+hrZpWmKPM/XAT0DNlCDXovUG53fURSJToMgkEhZEfzzBW8MqS2CehaMlBO1F8exRNbpzNfEveGQ6dSZ3xq3Po4j+r7/PWeks8ZFMF+b4Q8+F1GVrMU38QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-30.png&quot;
        title=&quot;&quot;
        src=&quot;/static/8b3757e549692178b88b99985d11c2c5/11bd9/2023-11-06-replication-30.png&quot;
        srcset=&quot;/static/8b3757e549692178b88b99985d11c2c5/0eb09/2023-11-06-replication-30.png 500w,
/static/8b3757e549692178b88b99985d11c2c5/1263b/2023-11-06-replication-30.png 1000w,
/static/8b3757e549692178b88b99985d11c2c5/11bd9/2023-11-06-replication-30.png 1420w&quot;
        sizes=&quot;(max-width: 1420px) 100vw, 1420px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1420px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1cee2fc3eeeb708dad3222dde3fc08b9/11bd9/2023-11-06-replication-31.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 85.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAABYlAAAWJQFJUiTwAAACH0lEQVR42pVU2W7aUBDN//9DH6pKVV+IhJomQjwgsTSAWEoEKZuEgSTYZrd9fb2AT+dOAiLGKM1I177bHJ+ZOeMrvFkURTg113WxXC5hGAaWiwXPV6sVD8uyjj5xv6s44IKc2+02Wq0WSqUSft/fo//0hDEB/h2PeX80GvGHhBCI2xHQcRxmMZvNMJlM2KHX66Hf72NMa43ApgQ8GAwwHA4ZVJ1pmsbRnAF6noftdsvA6tKQHB87HQZVjJvNJh6IXbfbRaNRZ+D1es0jCIJzwFOLYu9L+Uqyd4AHh8B1YD52EDo2/CCEbVvQdZ1Dk54PYesQjvvm8x+Amitw/f0rooc23DCERamwKRWqupYtYM7q2K7nCXFcCNnb7TDTTfhbC0k0vMDCbic+DjmexyjG/NM5DCk8lSdVcVU5tVbz+L6glEjpHs8uAqoLStjz+Rxj0l29XketVuN3oVBAtVolyTRYVureZrNhmZ0BHkJRos7n8wyQy+WQTqeRyWSQSqXw8+YGt7e/kM1mUSwWUS6XUalUGPQU4x1DdTidTl875uUFy9Vr/5rEWDESwqX1muZzmKbJBA59/WFRDoXxKEeS8ubKAIb5DOyDzwtbkpC1WgWetcGGGJiGjsmzgfTdHbEz4ToGHEskVj4RcCJs/Pj2BeGfFnzaU38Vz5Pw/QCOkDD0BgnbSBa2lJIlwW1FczXC/Z5bjh6JYQWhwH4vE0P+B0P3Gm9BS+dxAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-31.png&quot;
        title=&quot;&quot;
        src=&quot;/static/1cee2fc3eeeb708dad3222dde3fc08b9/11bd9/2023-11-06-replication-31.png&quot;
        srcset=&quot;/static/1cee2fc3eeeb708dad3222dde3fc08b9/0eb09/2023-11-06-replication-31.png 500w,
/static/1cee2fc3eeeb708dad3222dde3fc08b9/1263b/2023-11-06-replication-31.png 1000w,
/static/1cee2fc3eeeb708dad3222dde3fc08b9/11bd9/2023-11-06-replication-31.png 1420w&quot;
        sizes=&quot;(max-width: 1420px) 100vw, 1420px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1420px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6abb0c00b3e53de9a5aafe88c5b946b7/11bd9/2023-11-06-replication-32.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 45.599999999999994%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABLElEQVR42pVS2WrDMBDM/0M/o/0TGwK2cfMSaOP6kK/Ecg2+5UNTrchRKAmNYBhpVzvaXe0GD5aUq8KCZ9bmnmMUAmmRQ4juahuGAXVdI0kSTNP0P0EppebPmOHl9Q1ZVV99vu/j3XWx3++xrutzGYpVgvcC03ILXJYFXdfdFfsjSNkJVSoxLwqE/hdixsAUPM/TTFmWZalLprsPBed5RhRF4JyDxTGCINACYRjicDhcOVY+sqdpqrP+3Sot2Latfp2ETqeTZgoikD3Pc2RZpvdko08pVAXH4xG8rNQ5Rd/3N8FLqexcHgW4qvnb7RamacKyLBiGAcdxYNs2drudvhOEDN/5B+ahwjlBbEiZGk1MvSE0TaMfIIzjqJn8lzPtaYSGYcQ0qimQtxH6Aah+tDewSD8eAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-32.png&quot;
        title=&quot;&quot;
        src=&quot;/static/6abb0c00b3e53de9a5aafe88c5b946b7/11bd9/2023-11-06-replication-32.png&quot;
        srcset=&quot;/static/6abb0c00b3e53de9a5aafe88c5b946b7/0eb09/2023-11-06-replication-32.png 500w,
/static/6abb0c00b3e53de9a5aafe88c5b946b7/1263b/2023-11-06-replication-32.png 1000w,
/static/6abb0c00b3e53de9a5aafe88c5b946b7/11bd9/2023-11-06-replication-32.png 1420w&quot;
        sizes=&quot;(max-width: 1420px) 100vw, 1420px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Mixed 포맷을 적용함으로써
바이너리 로그의 용량과 데이터 정합성을 성공적으로 지켜낸 테코!&lt;/p&gt;
&lt;p&gt;시행착오를 겪으면서 많은 공부와 성장을 해내고 있습니다.&lt;/p&gt;
&lt;h3&gt;요약&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;MySQL 리플리케이션의 복제 데이터 포맷&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Row 기반 바이너리 로그 포맷&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Statement 기반 바이너리 로그 포맷&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mixed 포맷&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;자료
Real MySQL 8.0 2권 469p ~ 485p&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;테코의 고민 3 - 복제하는 동안 데이터에 차이가 있지 않을까&lt;/h1&gt;
&lt;p&gt;데이터의 리플리케이션을 처음 경험하는 테코는 이런 고민을 하기도 했습니다.
‘소스 서버에서 리플리카 서버로 데이터를 복제하는 데에도 시간이 걸리잖아.
&lt;strong&gt;복제하는 동안은 두 서버 사이의 데이터가 다를 텐데, 문제가 되지는 않을까&lt;/strong&gt;?’&lt;/p&gt;
&lt;h2&gt;비동기&lt;/h2&gt;
&lt;p&gt;이런 고민이 들 만도 한 것이,
테코네 카페의 데이터베이스 리플리케이션은 &lt;strong&gt;비동기 복제(Asynchronous replication)&lt;/strong&gt; 방식으로 동작하고 있었기 때문인데요!&lt;/p&gt;
&lt;p&gt;이름에서 유추할 수 있듯, 소스 서버의 이벤트가 리플리카 서버들에게 잘 전송되었는지 확인하지 않기 때문에
서버들 간 데이터에 차이가 생길 가능성이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1412px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f17e1f8303e49f2e81fc760433e917c3/fa6a4/2023-11-06-replication-33.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 90.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAABYlAAAWJQFJUiTwAAADfklEQVR42pWUa0ybZRSAK4FME4gOdwlkGpdgssRkxCULHevYBqK4iROHOgtZVBw6ZMoPgmIlQ2UYNpj8gLZcBaYwUTt+sI1uDCbObPOyXkbLRSa6FoGuk3bScis8fl8HCYzWxJOcnPecfN/znnPe9z0SfMjc3BwzMzPMzs567fT0tFc9Ho/XF9WfSO4FiRCHw4HBYMBkMqHT69HpdBiNRsxmszduENZDQ0Pe70X1D5y34k8ajYYL7e2cbm1F29bGxc5Ozp45Q/v5816/t7f3vzMUdxoZGcFqtTIsZDjsGOMvwd66c4dRpwObYO2C3h4fx+4aZ9Ru935vs9mWZLkE6Ha7mZqawiOWLsQ8PtQl9NE9K/RR7O/0DC6XazlwIeB0OvmitoZKlQqVUolaqUItrNXKcirUaqqUZZgvX8L4s0mIV1BXW8fY3w4WM7xA8SBEGRj4jaCgICQSCYEBAay4L0DwHyAkJIQAIXb/6rXUV5agLTvMmtBgkmSJ9Jv/4S7DB3DYcpMDr+zl0bBQAgNX8LAAemzdIwQHryQxNobC4hKOHj9M7f5U9sk28sYOKdf1f7KYsaTkm2O36fhSxYsbNxC1OZLYbVt46MFVxG9+gksnq2hQVXMk8wDfFhTxmXwLEWvWYtANLAdaLBZahGtSfbIRxaF0qjNSOPVmOG9tDycs7HGOZyRRc6yU0kIl51oucrlRxb4dTxK6ch3dhr7lQKdwPbqFS3zt6lW+qWqg8P1MnpeG83pcJPnyeBRpKdQrT/DJwUyO5WTTUpBJcmw0q1avR683+C/ZabdRnf8RxXl5JD8dR376y7yWEE/224fo0v5Ac1kFp9SVdHxVxasJT7EhIoqebrMP4LxjH7bwdVEehTkfsmvncyS9sB/pphjKjxTxu9HEleZaNEXv0lCiYKc0GtkmKf09/f4zdDvs5L6TheK9LEpyFaQly3lWFoNZ18uvzfVoP88lNW47WyPWI4/ZSuquPfT03PAPtA4OUHDwJa5ra0jfsxtZZBSf5nzAT+c6+eWslqbycqK3PcNeeQq7ExNIy8pgcMh6F7j4Yi8Ax26N0tXWyKD+Akeb6sguKOV7oXd/9A1yw9RHa8t3fKw5QXHXaVRXOmi69iMO9/jyl+Jr4ix+4/9HJJOTk96hIOrExIRgXXiEAeodpoKKpYiHtjArxbhHtPN673b/AlLFnokfmP+6AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-33.png&quot;
        title=&quot;&quot;
        src=&quot;/static/f17e1f8303e49f2e81fc760433e917c3/fa6a4/2023-11-06-replication-33.png&quot;
        srcset=&quot;/static/f17e1f8303e49f2e81fc760433e917c3/0eb09/2023-11-06-replication-33.png 500w,
/static/f17e1f8303e49f2e81fc760433e917c3/1263b/2023-11-06-replication-33.png 1000w,
/static/f17e1f8303e49f2e81fc760433e917c3/fa6a4/2023-11-06-replication-33.png 1412w&quot;
        sizes=&quot;(max-width: 1412px) 100vw, 1412px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1024px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b205b11be4f34b4da40a2368162dfc7b/42a19/2023-11-06-replication-34.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 129.39999999999998%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAABYlAAAWJQFJUiTwAAADq0lEQVR42pVVyVIjRxDV/0/47AmffPbRDgjkAGRgGBYJ0IoEAyG0oLX3bi29PucrukUjMYepiIyqrqp+mflyqQLSkSQJ4jhWc37NmYNzFEVKts+zOxwFbnKsViu02200Gg3c3NygVqvh/v5e7T09PaHX66HT6aDVaqHZbKJer+Py8lJ9898NYGYdx2w2w3A4VBdPT09xcXGB6+trnJycoFqtqu9yuYyzszOlmIp4P49RQG4EQQDDMGCappp1XVeiaZoSKtTmGuYium7Atm31T358AFyv1wrAdByYtoj85MratixYSkxYrgfLceHIN/cD398FJNG0Kky1xaI51AwkcQjSHaXuKNe8BSI58wMfjtz3ZF6F4UdAukPyw/Tgv3YV//7+BUGtCnO5hCmWRZ5LF/CtU8feH7/Bv6lgKoC658GR/SSNugJcyYEtBy+DAbpCdPGlh3KxhKjRFp5MLHQNgaEDjo2KUHD1/RpodJC8RWOTVhvAtVhmy8XJZIKRiDMaotdsiIIuZsYcrtCx0uaIBTCSu6Qoi2xGxg5gmOMhXC6gdV8weKxhOmwhMC0FmDhvUQ1yd/EzQHUp1ei7DiLbQuwKgD5EaBlYphaGYSAS/TpgILwthAZ3sYAnQVkrCx3lSd6bn7scvWuNBGTy4xF//3OAr3/+hVFvgMHoFc/DAfoGUybY1PynFq4kORmUfr+v6tWYjOEKwGO9ie+lb5j2BzDmUzimoVLEZ2DS/LTlX1cU+KmHG8DJfI7xdIr+6yu08QjL+Qyx5F6ycLEUDg2p2XaljJY0Bl2spHLm78SYwXA85eUGMElb0ybKkpOh5F3MwAh/gYgtSvRBH6PRCJqUJ0vSsmyMxm1YxhhBnLy7TDDWcRaUtZTeWixkIicpqDOdIJRv8rcUj95KErJeiiwQ0KAMcHtEzDXpcRRfSo9zmM6+gDHKWYIncfKhEX8K6EmU2efG4zFehVNWEGVMkT2esb29J07yefvKPwcUR3iaS7AGUuMEmUrQCEjLgjR1tscOIN15fn7Gw8OD6s6VSkW1+/Pzc/U03N3dqaeBHZxRznfrTwEZHEaSrtI6rrMOTitVqojrPOPetpU7gOzM/IGPz9HREfb29nB4eIhisYirqytl6e3t7Ybb/Iu3A+hJ/i0kIBRawOSlhZyzIHE/mykEzudwIQsAha5lbna7XZRKJRwcHOD4+FgJ1/v7+4pTnlPId75ZFDKLKHxfyWHmBtfcX0oO8ozC9babeZf/B6llwNeSd97cAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-34.png&quot;
        title=&quot;&quot;
        src=&quot;/static/b205b11be4f34b4da40a2368162dfc7b/42a19/2023-11-06-replication-34.png&quot;
        srcset=&quot;/static/b205b11be4f34b4da40a2368162dfc7b/0eb09/2023-11-06-replication-34.png 500w,
/static/b205b11be4f34b4da40a2368162dfc7b/1263b/2023-11-06-replication-34.png 1000w,
/static/b205b11be4f34b4da40a2368162dfc7b/42a19/2023-11-06-replication-34.png 1024w&quot;
        sizes=&quot;(max-width: 1024px) 100vw, 1024px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1296px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/015940dfe5683785b88f3164b8b8ae73/ca756/2023-11-06-replication-35.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABl0lEQVR42o1S207CQBDlb/0MH/wS3300GgnhJqEPCFKgEERow6VpubW0hdIbx52tVbSNMsmks7vTM+fMTA6fdjqd4LouHMeBbduwLIuf9/s9DocD/9Jb8k5Od+RBECQwyCVBGIYwTROr1QqapmGxWGA4HKLVaqHf76PT6XAfDAY8R9d1rNdrLJdLHI/HNOA5MFUkdpvNBrvdjrPZbrc8NgyDMyYFnuf9/j0GJLlkVFGSJIiiiEKhgGKxiGq1CkEQUC6XUavV+Ller/McyiUlKcBzdgROkkimoigYj8eYzWYYjUY8pjsqTL2LouhH/zIlUyJJbLfbKJVKnF0+n0elUuFxo9FAr9fjOdSWTMnnRn2aTqeQZRkTxqjPhtCVuoxdzFBVVT40GgaB/guYMtbeiPU+CnGRpQCph4kHpgpn3sWkfIf50y1C2yR89hZ95VzAME6KGCW38wDl7RlX949oXt8AQjN+zQC6SLLrWNCdHV4cA9qriP27DO+7ZjYgbTktKk0s8ZCtA5nPvo7vww8YW4pp6cO/m/kB4JSWQmOgVLAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-35.png&quot;
        title=&quot;&quot;
        src=&quot;/static/015940dfe5683785b88f3164b8b8ae73/ca756/2023-11-06-replication-35.png&quot;
        srcset=&quot;/static/015940dfe5683785b88f3164b8b8ae73/0eb09/2023-11-06-replication-35.png 500w,
/static/015940dfe5683785b88f3164b8b8ae73/1263b/2023-11-06-replication-35.png 1000w,
/static/015940dfe5683785b88f3164b8b8ae73/ca756/2023-11-06-replication-35.png 1296w&quot;
        sizes=&quot;(max-width: 1296px) 100vw, 1296px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1296px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7d2be0f024602628155a1e036810e596/ca756/2023-11-06-replication-36.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 49%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABFElEQVR42p1S2WqEUAz1/7+o77641CL4UDuigyta1Cq44R09NZmxWKwjNBCSm5ucnIRIOJBpmnC73TDPMyu9t0qxv0RaHSqu6xpd17F1XZc1jmMkSQLbtuF5Hvtkq6rCMAxomgZCiD0gBS3LgqqqMAwDmqZBlmX2KUaqKAq/ydK/rutwHAd93zMGsZa2I2ZZxgyCIEAYhvB9n2PEkvwoinC9XpGm6c8ERVFgHMc9w7XD0S6pATVb89bcrb8D/C33pPKrYhamafKIR42fAz4KxNDCVV/gfLwzWJHn/wR8sBNiRP0Z4OJc8Gq8nYKdjLyc0lJcLguvhh5l1yJfTkosO30KSHdHt0Tati3r9q7ujOZTZivgNycABQ21Vp7eAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-36.png&quot;
        title=&quot;&quot;
        src=&quot;/static/7d2be0f024602628155a1e036810e596/ca756/2023-11-06-replication-36.png&quot;
        srcset=&quot;/static/7d2be0f024602628155a1e036810e596/0eb09/2023-11-06-replication-36.png 500w,
/static/7d2be0f024602628155a1e036810e596/1263b/2023-11-06-replication-36.png 1000w,
/static/7d2be0f024602628155a1e036810e596/ca756/2023-11-06-replication-36.png 1296w&quot;
        sizes=&quot;(max-width: 1296px) 100vw, 1296px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;비동기 방식의 리플리케이션을 계속하기에는 찜찜한 테코.
‘동기 방식’ 같은 더 안전한 동기화 방법은 없을까 찾아보던 중
웃긴 이름의 동기화 방식을 알게 되었습니다.&lt;/p&gt;
&lt;h2&gt;‘반’동기&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;반동기(Semi-synchronous replication)&lt;/strong&gt;.
특이한 이름입니다. 왜 &lt;strong&gt;반&lt;/strong&gt;만 동기일까요?&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1342px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/685edacedf64b722c1161dabd49cc8f1/50bf7/2023-11-06-replication-37.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 71.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAADRklEQVR42mWT60+adxTH+y/sv9iL7d1e7G2zV8veLCFt1iUubdrdu6xu6aZW6dxQ8LIUiLYR64TKWL3GeKE0VFBugpd4YQhaHRUVI3IRgQdB8LMHrHVdT/LNOb/znN8n5zzPcy7wHzs5OSGbzZZ1dHRELpdDEIRyXFIpf3bO5/Nl/3+7cBYUCkWCwRe4XE68Xi+Tk5NMT7uZsFhwOBzMzs6Wc1arFZvNJta5sNtthEKb5UbeAJbsIL7PysoKS4tLLCzO4/a4mJlx4ZmZwx9YJbC6ht8fYFF8/nx9g/V/XpA4SL7e4Rl8N5Jnai5dLkgkEzxfi+BbCjOzHCUR3yWdipJJhjk82CGe2CMeDRELB9gN+Uklo69eWbnDQjrFsnOdh90+3JYBTOob3GsdRtPlwGGcYeGxmqHOOu7f+YxHqhrc/iCaFimVH73Dj5ffx+ccOgUWi6fAwy0vvnE18zop04M99Cpq6esbQN/cgvbbT+hukPHljUquXm9gsKMLa+8AWpWWio+/oOKDd9mYHXkdaI0KXO01s6jsZEczSEudgX7lbfrlN2m/p6NLhIz2m5A1a1EbevC3NfH15ypqvr9Lf2MFwYUnL0cuAcWv6wx4kQ/1MS6Xsdah5IrkDvb+Tsb0eiSXVayPtBJ81s6GqQ118yO+u97CpUu3MGl+ZstcSzxoPwfm8sd4HBYa5MP83j7GvFmHRCJj/I8e5noVdEqr2DA2ihL9aD23Kg1clOgZb5MSGP2JwMhNkpuuc6A4dtnGbEVCW7DvtxMYrsU/LGfTrMQn+vVnCqIehehldCl16NQGjJom9mw1hMw/kN52nAMLxdP/ZuBJhgVXjljAx/JIHatjUnbNv7LztJZ9x2/sT5ck59NrWt770ICi/gEpdzXb1tuktxwvV60MPI3/Mh5j1Hn4e1DLpqmRlFNKxFJHZKKGuK2aqL2auPMuV66ZeOvtP/nqm24yc3WEp6rIbNvfBD5+Cg/kVrw9MpaHWtmeaCI6+Qsxp4KIvZ7IVDWCuwpVywjNojo0RmLLerY998mEp89HLi27IGSIHwjs7R2SScTIptPkxMUv5ASKeYHjXJZCPsPJcUaMS+eMeKeUE8SatNhY/tXq/QsXI6uTMYGUjwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-37.png&quot;
        title=&quot;&quot;
        src=&quot;/static/685edacedf64b722c1161dabd49cc8f1/50bf7/2023-11-06-replication-37.png&quot;
        srcset=&quot;/static/685edacedf64b722c1161dabd49cc8f1/0eb09/2023-11-06-replication-37.png 500w,
/static/685edacedf64b722c1161dabd49cc8f1/1263b/2023-11-06-replication-37.png 1000w,
/static/685edacedf64b722c1161dabd49cc8f1/50bf7/2023-11-06-replication-37.png 1342w&quot;
        sizes=&quot;(max-width: 1342px) 100vw, 1342px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1014px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e799c69337ccc8512c82fec11dd746af/e9d40/2023-11-06-replication-38.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 129.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAABYlAAAWJQFJUiTwAAADyklEQVR42pVVXW/iRhTNv1+pT5X2pQ9tX6IqjaJEUT602uwqjZTV7mY3USDQQCBQvjEGjG2M8df49NxZQAaSSh3pyp6x58y55965d0cphaWlabp6Zk1G9pm17H6xHWRGHMd60bZt9Ho9dLtdGIaBWq2GVquFRqOBZrOJ5+dneJ6HKIr0nuxYAQrQeDxGtVrF3d0dSqUS7u/vUSgU9LNcLqNYLOL29ha5XE5bv9/HdDrdBpTTBEyewqzT6ei5vMsm0zQxGo0wHA7Rbrc1a1mXuQCKR8J2BSg/PTw8aHdubm6wt7eH9+8vcHp6ipOTExwdHeHs7AwXFxc4PDzE8fExrq6ukM/nUalUtFdLpjtLoYMg0M8wDGE5U0xnPnzfx4wmzOXdnky4PoMfhPDnc7w0NODYsrRWoiPhoQZ9hAMDqUoAcYXC/wiyQmL0EBh9pFGIlKxSHqRIYpkFGjBOEh0t13VhUJfd64/48vYn4LGEZGgiJkDsOojI7o/PV/j08xt+KxPcgKK2CfclmswCMCSgN/fRHwzQYar89U8Dne95RI0W3JmHQEQfjxA6Nu48H837R8AwIWGICST7o0X6aMA53bLotggrejnNBlrlEurtKgbmEAkBE0Y9mZIJN6sFm4UOP0CzgDLJJmg4dWBUqmhVv8M26oDjILZGiAkYJTGSjWROXgJc5pFSjLQ9oeAuUo+iOxbNJuBYA8ZZhv8JuFgQJyJxkQACJKZeAUwXJi5Hm4CzMEBAdwK+C0M1sVaAYhKUSCK9kCdbOLYYyql96lQnUJPmkU1CQCXANHmPaQnTJstQEdAn0Dzj4bbL/EmYCCtI4lJHMHWsVhP5b99Qq9f1vW7xmo5tB+1BExbzMF7Lw40o+3TP7XUwqFVR/PoZTreDRrGAy3fvkOP9lRRzeaDleDAHBXhT6qvSl6Osqw9vS/OxiL+ZOrmnCkasPuZzFVavC5esJReXQXlRw2AhspQsqThyI3pM7F9/38Mvv/1J15nQXEvo/lqUXwuKx8oxYM1zqIXoM2Gtk1Tp1uqo8c5KQMLRECG11fIQdBVlmszXAOesFh0WU5uAE0a7z6vnseJEtkWbwCeYw+8zAk+lpPHeRwsQsUAfEq+3gOxI+UPCQxR1VXGk50osibca02YzWwFmF5dDytmIuk54c8RMBkvKvhTh18YaQ6nK0iukDXz88AEHBwe61J+yDezv7+P8/By7u7u4vLzE9fU1np6etsDXAKV8CaC0S2mV0mtkLi102VKlgUnTklwUk3K3BjhnhIWZmPQVycdN1//P+Bed6cPs4oSauwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-38.png&quot;
        title=&quot;&quot;
        src=&quot;/static/e799c69337ccc8512c82fec11dd746af/e9d40/2023-11-06-replication-38.png&quot;
        srcset=&quot;/static/e799c69337ccc8512c82fec11dd746af/0eb09/2023-11-06-replication-38.png 500w,
/static/e799c69337ccc8512c82fec11dd746af/1263b/2023-11-06-replication-38.png 1000w,
/static/e799c69337ccc8512c82fec11dd746af/e9d40/2023-11-06-replication-38.png 1014w&quot;
        sizes=&quot;(max-width: 1014px) 100vw, 1014px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1302px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/87f4934a5cd0280a44dbbc450b900735/3b5a6/2023-11-06-replication-39.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 97.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAABYlAAAWJQFJUiTwAAADIklEQVR42oVU21LTUBTlpx3f9AOUj/BFXxTUcZhxgHJHGCjpLfQGrWl6JWnTNEnbtEmWawdSWkDtzOm55Jx11lp7n72G//zCMITjOPA8L+6jKPrn/rVkg67rOD8/x8nJCfL5PLa2tuL50dERNjY24nkqlcLFxQV2dnZwdnaG4+NjHB4eIpvNYjwe3wPKXwKqaRpUVUW9fotyuYSDgwN83dzE3t4eQXaxv7+P7e2fyChXKF5fo1qp4KZaRb/ff2T4Eu1E1WQ6hcubR5Tqui7soY2h5SEI/y577SmKZ/dRSX2HU1KAqY/IGiCa+fG38chC7eAHnEoOMMiq10NEduFshugpYCL7xvWw/m0T3oePgGkiNA3MBzw8tFC1bax/+Qz30xfeTM/EN6oQMhK8FyWHwRyBYwPdHjBi77mAy+jaQwRkEgThPRgeZUcP2bAADIKA/gwxZJtNJhj0TZj0zSerQa8L3TBhU7pPH/tMH3M8QUhWpuPipmfAoyWJwhiw0+nE6VBiZNt6C42agk5Lg9XQcLS7j1dv3yOXzqClNaC1a2jpKmY8d5ZV8frNO3R4YWJbDDilDxL68WSMYX8A19DhGRrsZhMmU6msFtGr12HRU2/iwHE7mMXKx/jdaDEb/IUFK0GJvZCIUToYAPEwEj8nHsIRPfQpbcm7JDPkrNj2clBobkSmEVmvNIKFPBQlly+BLc9jwBlZ9ZhTd3d3aDQaMClfb+potzto8ElqXKtRsmEYaNIGeVHSt9vteE2anF94KAOT/shit9vl06sjl8vhKp1G+vIShUIh7uVZFgoqTk9/4ZJzARYwAZazK5Klmtj0TVJHwOWpSa8oCqp8r3JJJpPDnZ6HPegyiAPutRb7RqPRcw+jh4xfBIjj+XweHxIrpKKEIQMTBiveLZe0lWpjWRaa9CxPuSVWkwJ7lXKV9BXOWa7KpRJqt7e4JeNrfhfWYlFSup4xlCrS4QtRqhWc5HNQSkVcFYu4UAvIEiRbLqPcaqFItnkCSgmTQK4wnPCpiX9yiySqRcDpfIY5N81FsuSYjKP7sc81/8EKn6kkbbl8/QHd4J24eJB5JgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-39.png&quot;
        title=&quot;&quot;
        src=&quot;/static/87f4934a5cd0280a44dbbc450b900735/3b5a6/2023-11-06-replication-39.png&quot;
        srcset=&quot;/static/87f4934a5cd0280a44dbbc450b900735/0eb09/2023-11-06-replication-39.png 500w,
/static/87f4934a5cd0280a44dbbc450b900735/1263b/2023-11-06-replication-39.png 1000w,
/static/87f4934a5cd0280a44dbbc450b900735/3b5a6/2023-11-06-replication-39.png 1302w&quot;
        sizes=&quot;(max-width: 1302px) 100vw, 1302px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;비동기 복제 대신 반동기 복제를 선택하면
소스 서버와 리플리카 서버 사이의 동기화를 ‘어느 정도’는 보장할 수 있겠네요!&lt;/p&gt;
&lt;p&gt;하지만 반동기 복제 또한
&lt;strong&gt;정확히 어느 시점에 이벤트를 리플리카 서버로 전송하고 응답을 받느냐&lt;/strong&gt;에 따라
&lt;strong&gt;AFTER_SYNC&lt;/strong&gt;, &lt;strong&gt;AFTER_COMMIT&lt;/strong&gt;이라는 두 종류로 나눌 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1132px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e605e23f6c01ad97c636efbf93e07b45/abe8a/2023-11-06-replication-40.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 110.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAABYlAAAWJQFJUiTwAAADIklEQVR42q1VSW/TQBjt/79xQ4gD4sIFBNdyqKo2Il2StmRpmrXN6sTxFu/LjB9vnEU0DUVCWHqasWfmfcv7vvER/vNztJ2kaQpN02CaJgaDATqdzg7tdhutVguPj4/FmsJoNCrebds+TCilRJZlcF0XpbMz/CiVUDo/x+nJCb4fH+Oc3y7KZZSJEteUEbVfOfKCMM/zHZTVJjFcLlGnV3fNJi5vqrip13F9d4dqpYLKdQVNftd1/c8hY80MISSsJMYyDKDT2wVh+D50QnMcLGhoOpkg4LuKSp15QRiGYZETNRbhhwnyWCgLe/YZRZwiT+Xroqi8dXu9Iifqubg6RevLB2A8QU6PpLGEiGMIen65XRuOAHrKuJF7HuSzHCoiIRBywaXKX9sd1D59Bm5rSB0LUl+QmAep6DcS1dXaQxeSZ6CiSpL1fEsoad0iUZ9eNuoNaL0OVvMZdFOHYTlIVw6kZUIoT5jjXO6FTDK5UXvtIS2kxFbp2LHRu7lF574EUxsiJ6GwDUjfQ8bDYuPNq4Rik7+C0HaQ2S4Se4nE1Dh3EC9NJCsPSSpYeySlkzukBwiVIPlaR3jGCtZkhqfJAgPNgG+tCSMaiah+nAh6ih3C4BDhbxXvGjaGzTrevf+IN28/Qq5sguJEYRGu3As5ifYIGQMMz8WQhTvlaC1M5m2FaX+AbqMFye8RldbYQQ7nKpowCJBw1LjfdiPW5h5hEEXwOHqsNdfgIctiLD4LOQKorj7oo1mtovXwgPF4jCeST3mZTCc1OIyoSOYhUVQWfRIKlkmkz7Ho96gwBTF06OMRfLZcnq8PSyY9jLgvCBlydlgU9VizJZ4Y6qjVRu3iCrNuD72fDVTLl7y6hqzjtSgqlYo03s+h3HjoeT7m83lRNolhQTgu28pHolTme+YHRdnsq/yibFImeDadFrm555Wlj4YUgjl0HYpjrwu76JRVUdhS/KVTVOOnzA1USxExFc3mGnLTQM6LQbKP49mMpFZhPCOgcq5IFFQvZ9nhC1YhY71lgQ/BO/EZWAnqDixqkeM+CsKY3ql7MOLmLaSU//yT+gWjIprfv+oVXgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-40.png&quot;
        title=&quot;&quot;
        src=&quot;/static/e605e23f6c01ad97c636efbf93e07b45/abe8a/2023-11-06-replication-40.png&quot;
        srcset=&quot;/static/e605e23f6c01ad97c636efbf93e07b45/0eb09/2023-11-06-replication-40.png 500w,
/static/e605e23f6c01ad97c636efbf93e07b45/1263b/2023-11-06-replication-40.png 1000w,
/static/e605e23f6c01ad97c636efbf93e07b45/abe8a/2023-11-06-replication-40.png 1132w&quot;
        sizes=&quot;(max-width: 1132px) 100vw, 1132px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1132px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2a1797a563533f84d7ffe239d82d6704/abe8a/2023-11-06-replication-41.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 110.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAABYlAAAWJQFJUiTwAAAC9ElEQVR42q1Vy24TQRDM/5+RiLggDgRF4QBcUC7kAOSG5TixLb8S2/Fj/dy1970zO0X1eJ3EJiFEYqXW7oxnaqq6q8cH+M/PwfYjyzIkSYKbmxt0Oh30+3202230ej00Gg00m010u11cX1/bNdVqFavVCkopGGN2AWXC8zzM53MLIDGZTDAejzEcDi2AgMlYfpM5OdRxHIxGI5JRu4Byymw2s6Dr9dqyqNVq3Ny17IRpqVSyrC4rFQueUk0YhoijAHm63gUUqfV63Z4um2v8rlyUUSOAAAvDcrlsvy8uK2Q4wJQKhmS3nDnIgsEuoJwmEtM0tZMmpYTcPJp0ozSMzv9eFJHaYNK11shh8PP7KVqf3wOjMfLxCHq5gEpipHGI8x+naH85AvoD5IMBjKwJKbsozKYoUilGxPytFkscX1bRODoBrupQ7gL5dALMplA8+KTVQfPDR6DTJVMqiSJKTJGTzD0gJ9zlcpM/5mnSbsJzRpgt51i4HjLPRU6WOgigc2rI9ySLMtru3occyAlbPwXzGVqlMpq1cyzGfeRkpggugBlzqAs2O4BK7QISDabwZEpGRjZ7E2jPKQALhrL5McB9huYhoLtE4IwxcKYYzhZIeIAtjL+Gks3/IhkP2ieUjrmq4NXhOxy+PSZDAq4YrPSzDM2mVbCg63vszSErHRAQ/gq3zTY6tTpluwjJ2G014bvunUVSVtnxfYRxYl1yz5CDhCesWW0/TRBLVVceDCUi8O3bYe+23rxG9ewbsgJQ6RCDwRViP7AszVOSY5o5p+8MQQVYL+bIRkPm0Ld9v61ybjSCaMle3me4LUoB6p2fIbr4RdOGll02vIX79RPUelMU/WyVBf0Bw4Q3jxjZMKfCUiqcTZxHbGM28ZQPN2vEhxuppggpiqKVXuRD6Uf7ZkS8OBUZGXaMhGYvJ8yh2CelG5T0rzAq1tte3mG49yjmTvEG0XzfhYx5zYmpdWHu/bCAcRwz93LzRrw4Isj4j054wZ/Ub4bnn7fLlZYhAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-41.png&quot;
        title=&quot;&quot;
        src=&quot;/static/2a1797a563533f84d7ffe239d82d6704/abe8a/2023-11-06-replication-41.png&quot;
        srcset=&quot;/static/2a1797a563533f84d7ffe239d82d6704/0eb09/2023-11-06-replication-41.png 500w,
/static/2a1797a563533f84d7ffe239d82d6704/1263b/2023-11-06-replication-41.png 1000w,
/static/2a1797a563533f84d7ffe239d82d6704/abe8a/2023-11-06-replication-41.png 1132w&quot;
        sizes=&quot;(max-width: 1132px) 100vw, 1132px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1112px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/198401ce16f8fa5676ca3376b828e14c/78ab6/2023-11-06-replication-42.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 80%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAABYlAAAWJQFJUiTwAAACKUlEQVR42q1UaY/aQAzd//9PWrUfqBBaCqKI+2rL1cKCKBACSYAkXCHkfLVHO5SV6HY/dJAzE9vz4uOZB/zn9cCPOI6x2+1wOp1wPB6x2WywXq+x3W7FbpqmOBu6Dm21wp58bcsSNp10K9Lx3Sug53mYz+dQFAX1eh21Wg3pdBrJZFIIn/OFArIkmXwehWoVxWefbrcrQDmgK6DjOBiPxxgMBpjNZlgul+j3+0I3fHrCaDSCQjqLPmyez1iTv0GZaJqGxWIB27ZxOBz+AL51uQTG4l8ucCjFmH5/rWEURUL4zCL0JFIXhiHVzIBtWtCpdpqmw/d9yPvyzt0IozCA8bOHvaZeL/AyKc1Wo4yzZUMzptQc636EcskEHAL4+OUzrMe0eA+jEAfXxQ+q1Yf37xB8b0Hf/4K90f8B+BzJhijyrdvDdDCktLyr7ULAtrqCb+6hrTUsVQVx/AqgXFyvgOrD+219bj8cBIGwvxohO3ChpTPz06VU+Z3lQt09U5d5Z2E773cBuZvs0Ol0UC6XMZlMUCUC53I5NJtNQXiWbDYr7KVSCXkiOXP2RZflgUnabreFUyKRwNdWC5lMBp/o/JhKIUUT02o0UCGwYrEowCuVCobDoRg9mf6LlDktnlGdZnmgqlCoOWOKYEIXpsS7EU3FjEa01+tBJTuPm2VZ91OWYQuSc0cJ3CNxiZMu148i4F3WTZL6lh0CkIvM/xQsPNO886W3rHsM+A3MMMQWplaYoQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-42.png&quot;
        title=&quot;&quot;
        src=&quot;/static/198401ce16f8fa5676ca3376b828e14c/78ab6/2023-11-06-replication-42.png&quot;
        srcset=&quot;/static/198401ce16f8fa5676ca3376b828e14c/0eb09/2023-11-06-replication-42.png 500w,
/static/198401ce16f8fa5676ca3376b828e14c/1263b/2023-11-06-replication-42.png 1000w,
/static/198401ce16f8fa5676ca3376b828e14c/78ab6/2023-11-06-replication-42.png 1112w&quot;
        sizes=&quot;(max-width: 1112px) 100vw, 1112px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;흔치 않은 장애 상황에서만 일어날 수 있는 문제이지만,
&lt;strong&gt;팬텀 리드&lt;/strong&gt;와 같은 데이터 정합성 문제가 일어날 여지가 있는 방식이 AFTER_COMMIT이군요 🤨&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1128px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e9be534136f422e3bad2385f51fb0eda/bac97/2023-11-06-replication-43.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 110.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAABYlAAAWJQFJUiTwAAADTUlEQVR42qVVXW/bRhD0//8JQd8KBAjQxoGtuHH6ktiBo9Zp4VSSbdmyJIuiaJEURYrf5N1N5s5SSsnOQ9EDBuSRx7nd2dnjHhpDKYX/O/aak7IskWUZPM/D5eUlbm9vMRqNDPT9eDzGYDAwiOMYaZo+CWKL8OzsDPv7+3h/fIzjd+/QarXM/W9HRzg8OEDr8BDtdhsnJyc4PT3FAZ+FYbiV3d7mRu/Y6XTQ63ZxNRzi5v4eXzm/6HXxz9UV/uJ9+/zcRK7X6Yhvbm4MfhihISeSuobL1F1u4iYJvCwlMjwsFnA9Fw+uCymkWS+lfEpYFAVs20ZdVY/ESQ6ZVc+KrvISsqyhGmmqXcJFEJjQN7t9+nKKzqufUNozRpoijFdQjFCUBT79+RGdly+wmjmwWBSdQcJiKn6rNoSSDyq+DOZzzMb3+KXTw9XrQ+BrFzlFF0xTEXK5xNuJhcs3b4HbIQQpJLNT/F5Spu8R6ocRF0+nU9zREt7oDv5kTK0c+IsAVbgk2QKCkQpBGgJrvQ0410F9J1TUbqOfHtUqwqTbw7DfxnJuQeoo14QVI9GkW8oxXbFFyIkmVOadRMZo6yBEtpgj91msYImKEFmOmmRCyDWPQl5LfrsbISdirYEeKy9C6jLVIELhR+Y6H9KX53/jbjhBsIwxsRwEUQyLknhBbgjVjwhTEsiIHZDEQJaYa+zYcO/HmNpT05qBJvIdPDjXiIIYsqobhDoNmpZmBNw5IntOW9AydwP02n8gYrFCy8L1xQWN7f3rSe3hilkYS1U7RaFtkBc8IQqEjgeH1bZJMmAbutYEs/41LLab63qomY029cbYZcGiFLsaboqiW4+6+aMhXr1u4edfj0x1FSHzzBRlt93KQjxD2NAwckP44yk+f/iM348+sEAL5BrLFbK8RlFK2gePoIOybCdCXaGcOgRsQYtpJtRJRZFeaVDTRhU1rlcxKjLU5mBoeFvs+LDSJ4njwGYh+v0+/Kk285KkoYGM6EttbFb70Ydi+8SgBFs+FGy9ik2+2TLn8VQ9ODw1fCjfM71czGwIZlCyeLrvoSXS3bWGaPZy40A0qHN2RJqwM9IGEtRFbqKTz2FdqL2cH+t/g/6XbPAkpf/wk/oGUymXgKsJlacAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-43.png&quot;
        title=&quot;&quot;
        src=&quot;/static/e9be534136f422e3bad2385f51fb0eda/bac97/2023-11-06-replication-43.png&quot;
        srcset=&quot;/static/e9be534136f422e3bad2385f51fb0eda/0eb09/2023-11-06-replication-43.png 500w,
/static/e9be534136f422e3bad2385f51fb0eda/1263b/2023-11-06-replication-43.png 1000w,
/static/e9be534136f422e3bad2385f51fb0eda/bac97/2023-11-06-replication-43.png 1128w&quot;
        sizes=&quot;(max-width: 1128px) 100vw, 1128px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1128px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f2b68eee5ae55cfef6d537190db7d17d/bac97/2023-11-06-replication-44.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 81.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAABYlAAAWJQFJUiTwAAACQElEQVR42pVU2XLaQBDk/7/AFVflT3iwX4QAcdk44CjmDkIyktCFzu30roEAhSvOFF3DMlJPz8wONZxZVVUoy/Lki6I4+Wvkea4g4+dWy7IMpmlis9lgOBzCMAy0Wm10uz00Gg30+32MRiPouq7OEobRUb5er6PX62G1WmE6nSKOY9RklsViAcdxsF6v1QMNXSOhAU3TFGG321Xfm80m2kabhG2VoNPpwLIsJWa5XCJJEtSOUtM0hRDi4yDU52Tyd3HwF4Ebpght28ZgMPjoh6gw72lYtx6Q7hO4TBTLZLJ3eYpZX4fFWBhH2EQRPKpKWKVg32VCRbjf75EQFQn3DN6x5MG3e+DHK4IwQOpuAc9Fxh7dtXQVE5MZfCaPOYOMyeQgTwols+/7GI/HeH5+wsvrCP5PE7vtO3Y7HwVRkrSMQnhFjnTrgSPG9Q25IDxCWmJvYLIFk7cBXH4vPQ8FFVZZqq5Mdbgq4jPCa9tTTc6pb1eviJwZEOwQrVdwWUEgieXLQnyu8ML4YBYEEGFIInqWK9hHezqB+f0eL48PKG+IuEkoDkUET12k5hiCPRNUl1NdZGiql0X+dwD/VngoIxx0kJMQScxDgNK2EGqPKHhNiuKLhBHHP9/t8Oa6mFhrLIhfy7nC/PcKGWNV/B+EGScX8ypEhMdVihwbMYcjEbw7nLyFiorl7pck/VrJR3JOsiTUQIjK95CTtGTJGRMeVZ7j+K9TkzuccAPkYsttkb6gCrlq1RnkWfCl8zt7DUn4B+1ny6PqYGvcAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-44.png&quot;
        title=&quot;&quot;
        src=&quot;/static/f2b68eee5ae55cfef6d537190db7d17d/bac97/2023-11-06-replication-44.png&quot;
        srcset=&quot;/static/f2b68eee5ae55cfef6d537190db7d17d/0eb09/2023-11-06-replication-44.png 500w,
/static/f2b68eee5ae55cfef6d537190db7d17d/1263b/2023-11-06-replication-44.png 1000w,
/static/f2b68eee5ae55cfef6d537190db7d17d/bac97/2023-11-06-replication-44.png 1128w&quot;
        sizes=&quot;(max-width: 1128px) 100vw, 1128px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;데이터의 정합성을 지키려면
AFTER_SYNC 방식의 반동기 복제를 적용하면 되겠다는 결론을 내린 테코.
반동기 복제 적용 방법이 MySQL 문서에 자세히 나와 있으니 그대로 따라 하기만 하면 되지만,
적용하기 전 마지막으로 진지하게 고민해 보기로 합니다.
&lt;strong&gt;데이터베이스의 속도를 내리면서까지 꼭 정합성을 지켜야만 할까&lt;/strong&gt;?&lt;/p&gt;
&lt;h2&gt;성능과 정합성 사이의 줄다리기&lt;/h2&gt;
&lt;p&gt;‘사용자가 소스 서버에 쓰기 작업을 하고, 리플리카 서버에서 그 데이터를 읽으려 하면 데이터가 없을 수도 있잖아!’라고 생각한다면
데이터 정합성을 위해 반동기 방식을 쓰는 게 안전해 보이지만,&lt;/p&gt;
&lt;p&gt;사실 데이터의 리플리케이션 작업은
우리의 생각보다 훨씬 짧은 시간 안에 이루어진다고 해요.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1400px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7c9efabd7872dd6d1ddbe31c5defd7a0/3643c/2023-11-06-replication-45.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.199999999999996%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABRUlEQVR42p1S226CQBD1/7+lf9B3I2mNgBIvIYhQQBQVEBC5ne5Ms0Zja21PMszsXM7OzNLDAxRFgSRJkKYpy263Y98j9OjTdd2lYLFYYDQasR4OhxgMBnxWVRX9fh+apmE8HmM2m2G9XvMF15dcCMMwxHw+x3Q65UTXdVm22y2CIGDb8zzOs22bfb7vI4oi7Pf7W0IJIq7rGnEcw7IsLJdLLjZNk23q2nEc9lEOdVZV1f3IElRkGMbXWJMJ3sXIREJnGplipBVF4XXous7TyGbuCNu2vQglNG2DqqmFFr6uvYnLHEn0bYdXswuSDofTCYfyjDTPkaVHPIPeTwHqqBaXJwcDobfCynLFvpr/E3aCkOBuVHj2G8KPDbI4kNG/E0oczwVM6wWu84pj5v9OSE+fZRkL2bnYF/06uCqN8gRlXT61w0+LjlAE1qqOPgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-45.png&quot;
        title=&quot;&quot;
        src=&quot;/static/7c9efabd7872dd6d1ddbe31c5defd7a0/3643c/2023-11-06-replication-45.png&quot;
        srcset=&quot;/static/7c9efabd7872dd6d1ddbe31c5defd7a0/0eb09/2023-11-06-replication-45.png 500w,
/static/7c9efabd7872dd6d1ddbe31c5defd7a0/1263b/2023-11-06-replication-45.png 1000w,
/static/7c9efabd7872dd6d1ddbe31c5defd7a0/3643c/2023-11-06-replication-45.png 1400w&quot;
        sizes=&quot;(max-width: 1400px) 100vw, 1400px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1406px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a46f2f6fda35fb5a865d60b55c31afa6/ace50/2023-11-06-replication-46.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB1UlEQVR42o1TWY+CYAzc//+nfDEeUeONYDwQFY2KyH3T7XQXF7L7sF8yafNRhpm2fND3KYqC4jimKIoEYRhSkiSUpillWSaxnqMWQB3erc5HWZaSgMQ0TTIMg06nE+33e+r3+9RqtajT6QiQt9ttOh6PAtQfDgchfRNWCYiv16sUqapK6/WaRqMRdbtd6vV6gsFgQMPhkBRFofP5LPWPx4MqUQ3Cum1YvVwu8nUo3e128qFKEZ59tSdksqJO8UMYBAHd73fSdV0UQsV8PqfJZCJxOp3K/Ww2o/F4LLmqamRZT3Jd5zeh7/uiYLVaCZbLpdhGXCwWcge7UKwoS9ruDnTWF+RaOkVx2iSsemDbtijEYGANEcq/npdic7vdytAwFMPQuZdHhvmedIPwdruJPU3TBLDlud57aCCE2s1mI880bc3KVcnzPG9aBhl6+Hw+6fV6yfQe3B/ELEtZQc7DMMWFzTWIeAfAbv45ZagQc7y4ZZpQXpQ88ZLCKCc/zCjmHHclE5RsUeoZfy42ltOyLJ6YSw4rtFmZ47is0mOrvE5Jweo9hk2e45ALcC3Q2MP6nwK7nucJYBvFmD76AxVB4Ms97qq6X4QgwiSrfxeNr1uot+M/5xNnnNxdLOWHbwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-46.png&quot;
        title=&quot;&quot;
        src=&quot;/static/a46f2f6fda35fb5a865d60b55c31afa6/ace50/2023-11-06-replication-46.png&quot;
        srcset=&quot;/static/a46f2f6fda35fb5a865d60b55c31afa6/0eb09/2023-11-06-replication-46.png 500w,
/static/a46f2f6fda35fb5a865d60b55c31afa6/1263b/2023-11-06-replication-46.png 1000w,
/static/a46f2f6fda35fb5a865d60b55c31afa6/ace50/2023-11-06-replication-46.png 1406w&quot;
        sizes=&quot;(max-width: 1406px) 100vw, 1406px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;테코는 자신이 운영하는 카페의 성격을 되짚어 보았을 때,
비동기 복제 방식을 계속 쓴다고 하더라도 큰 불편이 일어날 일이 없으며
오히려 반동기 복제를 사용했을 때 데이터베이스의 작업 처리 속도가 느려져서 불편해할 사람들이 더 많겠구나! 라는 결론을 내렸어요.&lt;/p&gt;
&lt;p&gt;그렇게 비동기 복제 방식을 계속 사용하기로 마음먹은 테코는
&lt;strong&gt;더 새롭고 좋은 기술이 있다고 해도
무조건 적용하기 전에 그것이 본인에게 꼭 필요한 기술인지 고민하는 것&lt;/strong&gt;이 중요하다는 깨달음을 얻었습니다. 😊&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;요약&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;MySQL 복제 동기화 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;비동기&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;반동기&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;자료
Real MySQL 8.0 2권 pp.484~493&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h1&gt;번외 - 리플리케이션 구조(토폴로지)&lt;/h1&gt;
&lt;p&gt;지금까지 리플리케이션을 설명하면서 예시로 들었던 테코네 카페 데이터베이스 설정은
소스 서버(쓰기용) + 리플리카 서버 1(읽기용) + 리플리카 서버 2(예비용) 이렇게 3대로 구성된 &lt;strong&gt;멀티 리플리카 복제 구성&lt;/strong&gt;을 하고 있었습니다.&lt;/p&gt;
&lt;p&gt;이런 멀티 리플리카 구성 외에도, 리플리케이션을 수행하는 목적에 따라 다양한 형태로 데이터베이스들을 두고 운영할 수 있어요.&lt;/p&gt;
&lt;p&gt;일반적으로 사용되는 데이터베이스 구성 방식들을 간단히 알아본 후 포스트를 마무리하겠습니다. 🐋&lt;/p&gt;
&lt;h2&gt;싱글 리플리카 복제 구성&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;소스 서버 1 + 리플리카 서버 1&lt;/strong&gt;로 구성된
가장 단순한 구성 방식을 &lt;strong&gt;싱글 리플리카 복제 구성&lt;/strong&gt;이라고 불러요!&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1326px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f0129725d88b42160682720d5dd33a5b/329d6/2023-11-06-replication-47.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 80.80000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAABYlAAAWJQFJUiTwAAABoklEQVR42p1U207DMAzl/78DAUJ8AQ8I9sCmDZBgbFyqTYh1dINRWtJLkjY92NkKLR0IZtVKkyjOOcd2tlCxPM+RJAmUUlBSIhLCzlPyOIogaS1NU7vPI7sxphoCW9UJH3YcB5PJBAMaW70uev2+9ZNOB1fX1zg/62E4HGI6nWI8HiOOY3u2KIpmwNJ4KyA0ISFhF+xaIxQRRBRDEPKfrBlwddPXtPi8/c1z8XzZBb7R/D1gBeWnthyQvmOSorW3D3jPy/1vl/8akC0MQ4xGI8SJpERopIqkEITYFP9DWFJcLBY2QZlW0Eo2kP+Lco06lZPJcswmD/AuTmmebR6QdSprre1Ncbp/AMxeYDbRsClqAPg+kP0RYVFBU9UyCAKI93C5ttq35WTyzRBmuWm02J8Qcm+6rkuMNLQ2SGVmk+K7j3i9v7Wa5XKZbf/JxctNnyE3A5bU5vO5rTt+JKTU1NvKrh/eDXC1twMqSChqQbajsYP27jbVll9LUA0h01pHLaUcJELXslqQruZpxnrUETJNfjHYy3+9QlEtxmJdz68pmw8REuSaB75jqgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-47.png&quot;
        title=&quot;&quot;
        src=&quot;/static/f0129725d88b42160682720d5dd33a5b/329d6/2023-11-06-replication-47.png&quot;
        srcset=&quot;/static/f0129725d88b42160682720d5dd33a5b/0eb09/2023-11-06-replication-47.png 500w,
/static/f0129725d88b42160682720d5dd33a5b/1263b/2023-11-06-replication-47.png 1000w,
/static/f0129725d88b42160682720d5dd33a5b/329d6/2023-11-06-replication-47.png 1326w&quot;
        sizes=&quot;(max-width: 1326px) 100vw, 1326px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1326px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/28ec26ea540c42c2d7996e3322f89141/329d6/2023-11-06-replication-48.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.199999999999996%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABgUlEQVR42qVTy27CMBDk/3+hl1566LE3KnEChAQIVTQvKsIrIZBEBCjkQWInU68hqkjoqZZGa6+d2Rmv08BtFEUh8d/RqCYsy4Jt29jtdthsNgiCAKZpwvM8rNdruK4r43Q6lZFzfk9YqvJ9H4vFAqPRCK1WS8Zms4nBYIBOpyPR7XbRbrfR7/fR6/VgGIYsGIZhXeF+v8d8PsdyuZTVHcfBarWSRSiSalJEDmiPsN1upZAoiuqEJJ0OTyYTDIdDaJoGVVWh6zpURYHyqcg17c9mM4nxeCxzjLE6ISWpUpZliOMY5/NZWqF5nCSICNE1T+tLmorcReb4I0IaeZ7LDwjXteg8TbIU/HgEzzk9h5slJnIH4Yz93eVEVCYLgegwYzm+Twl4ymCfDnh/eQI3dDDBV4hzbhTi7fUZbPxRvrs6IXU8FVbKN5nTIYGT2FNNH4UXXBWLXCJsmtoXCsu5Jyw/rqI6ihvwe+lAHNG93Fsmm3T5BGoKgVTiEWWl0KP/6geLl0tKab9tMQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-48.png&quot;
        title=&quot;&quot;
        src=&quot;/static/28ec26ea540c42c2d7996e3322f89141/329d6/2023-11-06-replication-48.png&quot;
        srcset=&quot;/static/28ec26ea540c42c2d7996e3322f89141/0eb09/2023-11-06-replication-48.png 500w,
/static/28ec26ea540c42c2d7996e3322f89141/1263b/2023-11-06-replication-48.png 1000w,
/static/28ec26ea540c42c2d7996e3322f89141/329d6/2023-11-06-replication-48.png 1326w&quot;
        sizes=&quot;(max-width: 1326px) 100vw, 1326px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1326px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/183bdc37f7aa1c2c27fa92677fd48d30/329d6/2023-11-06-replication-49.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABfklEQVR42nVT/07CQAzm2U2Mz+AD+Aho4l8kkmC2QYA5tpC4ADLGhu4HDMZ+sM9r8RCYXtJc27t+/dreNXC1yrLEfr9HlmUsUi+KgoVsKXmeX4ejIZXD4YDlcgnLstBqtaCqKnq9HrrdLhRFYZ324XAITdPQbrfZZ9s2qqqqA5LT8zyEYYjRaIRms4l+v8/gFNzpdDAYDFhM08R0OoXv+5jP50ymBihZ0orjGJPJBI7jMANKtFgsONh1XcxmMyRJwiTOwU6AknIURTAMA+PxmEum0nRdZ5+qCFvVmDW1QX97E0wtjjnHaFw3lS5Q2UEQMDNiy7ooj3q82WzEeYTPlY/tdvv/UGQWOdVjCypQ3gNNNIiFXbKPlvGxxsurAd9z6wylQdmppDAMkBcl4nWKMitgx194frhHKV5ATnfTFFaQ4ObuFsbj00X/a0PZ7Xa/jH/2tZDxuwesgtNZVuRwDBPVwpPlHQEJgHpBQnoqsv/1YKnS6vIHQPTn4g0S4DceTfhP2fLkTQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-49.png&quot;
        title=&quot;&quot;
        src=&quot;/static/183bdc37f7aa1c2c27fa92677fd48d30/329d6/2023-11-06-replication-49.png&quot;
        srcset=&quot;/static/183bdc37f7aa1c2c27fa92677fd48d30/0eb09/2023-11-06-replication-49.png 500w,
/static/183bdc37f7aa1c2c27fa92677fd48d30/1263b/2023-11-06-replication-49.png 1000w,
/static/183bdc37f7aa1c2c27fa92677fd48d30/329d6/2023-11-06-replication-49.png 1326w&quot;
        sizes=&quot;(max-width: 1326px) 100vw, 1326px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;멀티 리플리카 복제 구성&lt;/h2&gt;
&lt;p&gt;싱글 리플리카 복제 구성에서,
다른 용도의 데이터베이스가 더 필요해서 여분의 리플리카 서버를 더 두기 시작한다면
&lt;strong&gt;멀티 리플리카 복제 구성&lt;/strong&gt;이 돼요.&lt;/p&gt;
&lt;p&gt;테코의 데이터베이스도 백업 용도 외에 읽기 작업을 수행하는 데이터베이스가 필요하게 되면서
멀티 리플리카 복제를 사용하게 됐었죠!&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1326px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/30f9d48747c96de4ec1b588f0509e108/329d6/2023-11-06-replication-50.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABRElEQVR42pVT7U6DQBDk/R9Cf5gY4yP4z9ioVUORNBptpIpQS0OFAHdwfI17x0cxsbadZEOWCzN7M4uGARhjqrIsQxRFSOK4fxdTHw96znn/HEIbNq7rwrIsuIsF7kwTt+Yj9OcnPEynuNEnuB6PMdF1GIYBx3Fg27aquq7/Juwgj1lRIBICcZ6DlSV4VSGiiZIkUbUN2rYDpdoqiyAEo6kbqQ7VYYQSZUt4Zb1gdHoEfHkQ2AjtRSgnk2av1z6KskZOAS1ZDdsKAZ4iofpefsLTR0Au9iOczWbK7IL8y0XzUcwzVO1k976Pi7NzYP7RmLArlDRNUVEIoDAETcj9FcL3t95Dz/PwSqJpuzI7U+5t7zwkssuTY2DlqygK2gBZSvSQUDrwIEBK1wPjm73aFor8K+ReDbc/J+9+gaaR1/8v3Y7wB7uzWL1Fa5BqAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-50.png&quot;
        title=&quot;&quot;
        src=&quot;/static/30f9d48747c96de4ec1b588f0509e108/329d6/2023-11-06-replication-50.png&quot;
        srcset=&quot;/static/30f9d48747c96de4ec1b588f0509e108/0eb09/2023-11-06-replication-50.png 500w,
/static/30f9d48747c96de4ec1b588f0509e108/1263b/2023-11-06-replication-50.png 1000w,
/static/30f9d48747c96de4ec1b588f0509e108/329d6/2023-11-06-replication-50.png 1326w&quot;
        sizes=&quot;(max-width: 1326px) 100vw, 1326px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1326px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/31d944da4817edf84745a65efd18d273/329d6/2023-11-06-replication-51.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 49%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABR0lEQVR42nVS26qCUBD12/MffO0n6i2JXiJIsixLiCAy6SqilRql65w15yhddGDce/asPXvNGhVU2PP5xOPxKNdiX3hxVmUKP2ma4na74XQ64XK5YL1eYzqdYrlcYjabYT6fw7IsrFYrmKYpZ4vFAkEQyJ0oiuReWfB6vSIMQ/i+L4DD4YDtdivO2PM87Pd7OXddV3Dn8xm73U72LMj7eZ7/FSzaZJLsHMfBaDSCbdvCptvtYjgcYjweYzKZYDAYwDAMYclHSYhWWZBJAvv9Pjqdjqy9Xg+6rkvMwq1WSwqT/WazEbneNKwbyv1+F2ecZVnpjMmmdiiv9glknP8/omka2u226MdBSu4DX8mwALEdtur+DoXGlpvNJhqNBlRVRZIkXySU4pVPp1Hw4/H4xYI6U8Oqf1GJ41gAdIpLp2512la1+VrwB8Vj97sb4q+BAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-51.png&quot;
        title=&quot;&quot;
        src=&quot;/static/31d944da4817edf84745a65efd18d273/329d6/2023-11-06-replication-51.png&quot;
        srcset=&quot;/static/31d944da4817edf84745a65efd18d273/0eb09/2023-11-06-replication-51.png 500w,
/static/31d944da4817edf84745a65efd18d273/1263b/2023-11-06-replication-51.png 1000w,
/static/31d944da4817edf84745a65efd18d273/329d6/2023-11-06-replication-51.png 1326w&quot;
        sizes=&quot;(max-width: 1326px) 100vw, 1326px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1326px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b11b61cb48e10e2a116d7eca780a2a50/329d6/2023-11-06-replication-52.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.60000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA/UlEQVR42p1RXUuEUBDd/5j/qvfth+hDYNiTvvighCCK34reTcEIP7imnnLKLdqUpQOHuQMzZ+bMPeAL8zxvcpomjONIcatmxQE7KIoCjuPANE1YlgXbtsEY22v5Fuy6DnEcI01TikEQQFEUoizLUFUVoihC0zR4nocwDKl2GVBV1aXgYqfvewzDgCzL4LouTqdnBB+NURShLEsS8n2fBiZJQvWcc+JVltfb/L7Tz/em5b/Q8xHslaPhb5Qv9iVJOju6WnCaPjd4fGK4uVVxb+SUH493EAQBdV1vbrq74TDOeDBz+PkL5YZhQtd1uvOm4PK7TdOgbdsz14b/4B02Z7RT48p5ZwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-52.png&quot;
        title=&quot;&quot;
        src=&quot;/static/b11b61cb48e10e2a116d7eca780a2a50/329d6/2023-11-06-replication-52.png&quot;
        srcset=&quot;/static/b11b61cb48e10e2a116d7eca780a2a50/0eb09/2023-11-06-replication-52.png 500w,
/static/b11b61cb48e10e2a116d7eca780a2a50/1263b/2023-11-06-replication-52.png 1000w,
/static/b11b61cb48e10e2a116d7eca780a2a50/329d6/2023-11-06-replication-52.png 1326w&quot;
        sizes=&quot;(max-width: 1326px) 100vw, 1326px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1326px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/10c8c4794b504c20df9fc26afb5bc3e4/329d6/2023-11-06-replication-53.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAACHElEQVR42pVTbWsTQRDu7/Jz/dCPIiJWsDQtiAW1qCmtFlEqpBVFqghS8KUo1AjSaC0IprHQFkuhCXk50+Ryzcvl7uLlEtPr5XK5XPJ4uyShhV7UgWVnZnefeWZ2pg8OYlkW6vU6arUa3U3ThGEY1Ca6k/QdNVqtFgqFAhRFQSqVAsMwdMXjcUSjUbAsS21yVi6XIcsyqtVq920XsGOoqopgMIhYLIZkMolsNot0Ok114iM6ARcEgfqIzvN8b4aNRoOmRKITQEmSIIoicrkcZSZJIvL5PE2d3G02mycDdliWSqUuM9YGIHqK45DY26M+AkwCSHZpyN6zhh3RNB2/5CJ0XYVR06AeVOxACjS1AkUuoGiz1NQDtP72KVab/rfAJi6NjGHhuQcz927AMzOJh/fv4ObVs3Cd68f5/lPwvn5G7zqmfBxwCyOXx/HR+wJT04MYvnIGS0/nMP9oAk9m3Zi7dRH+T2/bpeoB2Inm/76FsfFJ8FwIm4H3cA0P4troELzvXmE/sYvIhg95LnKs9o6/TCST5bD65QOY0AZ2f6xjesqNgdMD+Lq0gCwbRjS4DUnI/Dvg4e8i9iM7SIZ3sLbqg/v6KFxDF3D39gQezHqw+PKx/eOh/wCslJH5GYbIJcAy2/AHfFheX8OibwXLn9/Y9jwE3iFlMp9kfMgizarrOm1sUk8yz6bZnuN61X5s2K1iwTAP0bCME2f5D3aYtoYaCKX1AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-53.png&quot;
        title=&quot;&quot;
        src=&quot;/static/10c8c4794b504c20df9fc26afb5bc3e4/329d6/2023-11-06-replication-53.png&quot;
        srcset=&quot;/static/10c8c4794b504c20df9fc26afb5bc3e4/0eb09/2023-11-06-replication-53.png 500w,
/static/10c8c4794b504c20df9fc26afb5bc3e4/1263b/2023-11-06-replication-53.png 1000w,
/static/10c8c4794b504c20df9fc26afb5bc3e4/329d6/2023-11-06-replication-53.png 1326w&quot;
        sizes=&quot;(max-width: 1326px) 100vw, 1326px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;체인 복제 구성&lt;/h2&gt;
&lt;p&gt;만약 소스 서버는 하나인데, 복제를 해주어야 하는 리플리카 서버가 엄청나게 많아진다면 어떻게 해야 할까요?
예를 들어, &lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001550638&quot;&gt;대규모 서비스를 지탱하는 기술&lt;/a&gt;이라는 서적에서 예시로 든 하테나라는 기업은 데이터베이스 서버만 25대였다고 해요.&lt;/p&gt;
&lt;p&gt;만약 이 기업의 리플리케이션 구성이 소스 서버 1대와 리플리카 서버 24대로 이루어졌다면,
소스 서버에서 리플리카 서버들로 이벤트를 전송하는 작업 자체가 부하로 작용할 수도 있을 거예요.&lt;/p&gt;
&lt;p&gt;이때는 &lt;strong&gt;리플리카 서버가 또다시 다른 리플리카 서버에 대한 소스 서버 역할&lt;/strong&gt;을 하는, 사슬같은 구조의 &lt;strong&gt;체인 복제 구성&lt;/strong&gt;을 생각해볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1326px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/32c5de2ab4b147895c3d739b31fa3ba1/329d6/2023-11-06-replication-54.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.800000000000004%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABkklEQVR42p1Sy07CUBDlQ4iGtTExGg1/4dLEhYlu/AQXavQDYOHerQbRxOCDhTzKQ9BoUB6CBCgYSkuixAUpbTVtj/deKQYBQ5xkOnN7Z87MnDs2dMUwDJimyXxqx1Urz8q10Y+u6xBFEZIkodVqoVKpoFQqged5FAoFlMtlFItFVKtVCILA7um/Wq3GlOapqvoDaFXqdDoMkAbQQJpMC1G/Tnx2lkQIDQENsYFmU0K73e6bzoZxxWSVu7brD5EBQNatSSsavSRVU3Gd4BBPhBFPRohyCMdC4LkrQNNg/gXIOCVAKuH1k4xCRSH+CeHzIJvFUfoRh5k0jnMZvESiQFv+Bvw9ssWBrCp4CPqh1mvWBTPhmxQ8/ij2QwX4b/Pwnfrg9XhHj2y1/U4AgrEYjKfnPsCzQAA7LjfWN3axuLQMu30Sayurfc2MHlmne2n2BW9ubWNh3omZqWk4JhyYnXPC5d6D2aWlBzh0YYcUiXJRnF9cgiO8Je/ukcrlIb6+Dby2TVEUyLLMlO4hVU37wH/lCyBfNrPghWobAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-54.png&quot;
        title=&quot;&quot;
        src=&quot;/static/32c5de2ab4b147895c3d739b31fa3ba1/329d6/2023-11-06-replication-54.png&quot;
        srcset=&quot;/static/32c5de2ab4b147895c3d739b31fa3ba1/0eb09/2023-11-06-replication-54.png 500w,
/static/32c5de2ab4b147895c3d739b31fa3ba1/1263b/2023-11-06-replication-54.png 1000w,
/static/32c5de2ab4b147895c3d739b31fa3ba1/329d6/2023-11-06-replication-54.png 1326w&quot;
        sizes=&quot;(max-width: 1326px) 100vw, 1326px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1326px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/460240d19d6d1c5fc9fec87157bca271/329d6/2023-11-06-replication-55.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 84.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAABYlAAAWJQFJUiTwAAACO0lEQVR42pVU247aMBDl//+hWrVv+1KpSKi0y8N2JQQ89CJooQt0IUACCUEkITc7tk/HXkJZukvLRNbIzsyZ27ErOCNZliFJEnieh81mY3QUReafUupZn8rxRhsPBgN0Oh30+300m000Gg1Uq1XUajXc3Nyg1Wqh2+2i1+thOBwiz/MnASrHGyGEyWg0GmFExtZ0Cne1wnK5xHw+x2w2w2QyMXpK/9I0NT5/Zeg4DhaLBeI4hu/7xnm9XiPY7WAT4Hg6gU02QbCFY9twXdfYr/bBdDv0XidWKTMrigJSSqM558gZg0MtcILArJm9IGcXrr892JR+pX5Ssj5kBHJotpJAIaHow/4stC1Ev36cm+OfDC3LQrvdNn2RtPeiAHdvr8HHI6QUvchy1H8O0Lp6Q9OLoR4zeXnKui+6F0aodF8K3NFExXgCTo6KznZEo3AXm/9nMyzLPuWWMtgSCQ2LUZaJ5yJ6uP93yS+J2memCa5Fl3z76gpY02B0MKEgTVB1qP4sYClFwQm0gB8zaksEltKUiX6MURucDaIwowouAGQsxzYkEtMweBiayQuiTULrXaOKzccPeOS3+j9Abahpdtv9jO71a2CXIM4y09dviyXCoUW8vCBDQy0C/O75uP/ao1cjN2CKmCDDLV0CflnJWnIGw0+hDNWR0p1POEP9Ux1e/T2EPCn5OdqcAuo+6QtU0pDRTWo+zOF9GYCz/WujXxf9KGhdLl425EiIGaDKDivXi0CKXCCjQGIf5DfK6CLlMvsv6wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-55.png&quot;
        title=&quot;&quot;
        src=&quot;/static/460240d19d6d1c5fc9fec87157bca271/329d6/2023-11-06-replication-55.png&quot;
        srcset=&quot;/static/460240d19d6d1c5fc9fec87157bca271/0eb09/2023-11-06-replication-55.png 500w,
/static/460240d19d6d1c5fc9fec87157bca271/1263b/2023-11-06-replication-55.png 1000w,
/static/460240d19d6d1c5fc9fec87157bca271/329d6/2023-11-06-replication-55.png 1326w&quot;
        sizes=&quot;(max-width: 1326px) 100vw, 1326px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;MySQL 서버를 전체적으로 업그레이드하거나, 장비를 일괄적으로 바꿀 때 복제 그룹 단위의 교체를 할 수 있는데요!
대략 아래와 같은 과정을 통해 교체가 이루어집니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/01186ad90ffd7b4c8498da95b6635a37/2023-11-06-replication-64.gif&quot; alt=&quot;2023-11-06-replication-64.gif&quot;&gt;&lt;/p&gt;
&lt;h2&gt;듀얼 소스 복제 구성&lt;/h2&gt;
&lt;p&gt;지금까지 알아본 복제 구성은
데이터 쓰기용 서버는 하나만 사용하고,
읽기용 서버만을 확장한다는 전제를 바탕으로 한 구성이었어요.&lt;/p&gt;
&lt;p&gt;하지만 쓰기 작업을 아주 많이 하는 서비스인 경우에는
&lt;strong&gt;쓰기용 서버를 여러 개&lt;/strong&gt; 두어야 할 수도 있습니다!&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1326px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d0f1f20e7dea03cf2c19a5fdb6860a83/329d6/2023-11-06-replication-57.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAABjUlEQVR42pVT7U7CQBDk/R9CfxlfgZBgQqIofyBpIIBpbNVAsUqB0vZ6117H3bPFQuTDTSa7d93O7e3cNlBaURQQQiCOY+OTJME2DBESBMWrIDBxQH69XmO1WmG5XBpft0adkD/OPc/8/E5+MJ3CIowcB4+DAfr9Pu7abYxGI7iua7xt238THprSOdI8R04HSa0hKU6lhFIZNMXHbI+Qq2SUCxNrIkO5l9INotkMBYpd7i7/XIW/pECWabPsujbub66Aj08U5feTFdaNKxMigectSBSJjIT5ilIsnl9Byu0OvJhQKQXLsjAejyGpdxn1TUmFMIpPXurklcPNBpKIlSQyekqh7yNwXri0/4lShybkuuzhm42H22tg4YN3Ctq/SJQq6VDJMM3hO3MU1M+fhDMVcq94GrbbLdI0NeCYEUURBHlBgkQJgWLO5ZyjhDxyM3pn3W7XTAX7TqeDZrOJVquFp17PTMdwOMRkMjG5cbwvUqOaXwafxms+mWeZPe9vSJwqrirnV6DL3tYJvwFbA/Et0Thw7QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-57.png&quot;
        title=&quot;&quot;
        src=&quot;/static/d0f1f20e7dea03cf2c19a5fdb6860a83/329d6/2023-11-06-replication-57.png&quot;
        srcset=&quot;/static/d0f1f20e7dea03cf2c19a5fdb6860a83/0eb09/2023-11-06-replication-57.png 500w,
/static/d0f1f20e7dea03cf2c19a5fdb6860a83/1263b/2023-11-06-replication-57.png 1000w,
/static/d0f1f20e7dea03cf2c19a5fdb6860a83/329d6/2023-11-06-replication-57.png 1326w&quot;
        sizes=&quot;(max-width: 1326px) 100vw, 1326px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1326px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f041b70dd04518c3da48e799f72a82c2/329d6/2023-11-06-replication-58.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABhUlEQVR42q2S2WqCQQyF+/7vIqKooOAuiPuuuOGCCnohuO+e8qX8VqrtVQcyk8lMTnKSfOif18f9ftdqtdJisVCv1zMpl8tqtVrqdrtqNBoajUYaDAYPe7vdtrdSqaROp6PJZKL5fP4FyHa9Xk1w4gMOuVxOLpdLkUhEqVRKXq9XPp9Pfr9ftVrNAlerVft7Pp91uVy+Ad8tPux2O/t8Op20XC7tvt/vH85vKTsKTlCCRqFQUKVSsUyKxaLS6bRlwxtMKBMnwJxvAQ+Hgzwej4EFg0FFo1ElEgmjGAgEFIvFlEwmdbvd/m7Kbw9kwaJGgFPHcDiseDxuAQF3u912/xWQ6AA5lFjD4dCypqv1el3NZtOo03nK0e/3XwGPx6M2m43VEf1ZqBOBeEPQsdEcp7tO8x6AGBiDfD5v4wLFUChko8LYcKe+6Nls1jJ2ToQ5JPgL5fV6bYNLdxloJJPJGFXsOI7HY81mM5PpdKrtdvuotwGSOnQRdLr9c84cB6e+z/JsB/ATjk2B0KtQMMIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-58.png&quot;
        title=&quot;&quot;
        src=&quot;/static/f041b70dd04518c3da48e799f72a82c2/329d6/2023-11-06-replication-58.png&quot;
        srcset=&quot;/static/f041b70dd04518c3da48e799f72a82c2/0eb09/2023-11-06-replication-58.png 500w,
/static/f041b70dd04518c3da48e799f72a82c2/1263b/2023-11-06-replication-58.png 1000w,
/static/f041b70dd04518c3da48e799f72a82c2/329d6/2023-11-06-replication-58.png 1326w&quot;
        sizes=&quot;(max-width: 1326px) 100vw, 1326px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;ACTIVE-PASSIVE와 ACTIVE-ACTIVE&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1326px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/fdbf5eb6c2e166b384758cf4e0553d46/329d6/2023-11-06-replication-59.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 89.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAABYlAAAWJQFJUiTwAAADd0lEQVR42p2Uy0+bRxTF+SO66777dtGsumgWrbpIpC4qpVWlquoiimiqpqEPQS1KAZFEBOISQvOAuHEg5mHzMJ+JCzgYP+NgF78hxrHNy2BjCMbgyGB+HTtQgYI3Helq5nzzzTln5s7cMo60fD7P/v7+f/1+oReRP4hjcyL29vaK+GgrOwrW19dJJhIsx+PElpZYWF4mFI0SWVjAHwwSi8WIChwIBFhdXS2Ok8lkcW1B4A3CXC7Hq1dZNjMZ4i83SGa2mBciK+k00cUFFgVxQggWiLa3t9nZ2SmuKekwlUoVFQsRjUTwuN0EfD78Xi/hcJjZ2Vl8AodCoaLDuNhJWoiVdLgjVH1i8ezMDGaTibHRUew2G5IkMW4w4HA4GB83CPIXRacFwt3d3dIOs2JybXOTlIj1rS3S2WwRF85yPjZPIrnCTmaDvEhGISE58X/+wNkxwsNPXS8iXHI6kXk8yLw+Kr0efrJauaTRUN56C9mNq6ie6LjZ9YjbD1UM6oXb6OKbWz4kVMyF+dZmp27azR8i5E8dKPxB7otzuylErruddE2qkEm9fNXxgLO19dyVdK+v3EmEI8Jhk8XMHfMEvwviSqORBucU1U/F2GrhmkiSW9dMp9dEgz8khD1I8wsnODwA/RYb1d0qHhk0XDOO8atISL3LRY04hivCYYfdRKs/QItlklqrmZ8tdqRItDThwNgEjfIW+lWt6DV/ong8QqNWom5giFqThatqFZf71dy3TQpx4do+hUZkvCRhV+g5sluN9PaqeNgpxyZ1Yx41cVfVTWdfH0rNAJ+c+4JmdQ+hiSH0w2qMS/ET7uEB0DqMNCnvYLbb6VANoNDr8fe3063VotAq0YyPUdPWzrlf6mlQKrFWXcHw+Elph3//80y40DKiN9I2aOTdi3XUVLdi/KsP7cQDfnNN8X0gzNkfmjh95js+La+nz+E6KcuvQcTjpF9nRDL6GDaY+exrGe9/eJ6L8nv86JnmG+9zLszM8fn1Hj74qIJT52sY2nhZ+unFph306J1ohp8xrRuk/HIz752+QJWsDlvARbfFxO2gj48r5Lz19hne+bKCrpXEcYeHta3gc008eHdkjdDcEvG5IO0dPVTVtiH1DZINeUj5XGTCAdTqESoqW7ihGsab3uLoXS4rlKCMKFeFcpQVbzcnylc+v8f/bf8C/ZzsYjxg4zkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-59.png&quot;
        title=&quot;&quot;
        src=&quot;/static/fdbf5eb6c2e166b384758cf4e0553d46/329d6/2023-11-06-replication-59.png&quot;
        srcset=&quot;/static/fdbf5eb6c2e166b384758cf4e0553d46/0eb09/2023-11-06-replication-59.png 500w,
/static/fdbf5eb6c2e166b384758cf4e0553d46/1263b/2023-11-06-replication-59.png 1000w,
/static/fdbf5eb6c2e166b384758cf4e0553d46/329d6/2023-11-06-replication-59.png 1326w&quot;
        sizes=&quot;(max-width: 1326px) 100vw, 1326px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1326px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8311fd600d492a8b4c03000f54029893/329d6/2023-11-06-replication-60.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 95.39999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAABYlAAAWJQFJUiTwAAACIklEQVR42p2UPW/TQBjHD4rY2RiQ+AQsTHwAEGMlvkUZWAsI8SIIZWIpWweGAEMWJCqEhAQiYGhenBLFiU2jqE3SkLhO5PglceLXP2fLiZqo2FYf6dHd2eff/e/5n49gKTzPg2WasG0bJm2tWVpWMDaP9f25y0GWH7iui8NOB/VmExVBQKlSQaFcRoXjwDBMkCzLol6vB3NjgX4Y5hSSqkLSNPT91HV06CK9Xg89UUS324VOnzmOEw+UJClYfbdUAlssgqMK+WoVjUYDPFVcpUqFGocaz6PVasUD/dqMRiMMh0PIshwoUVQNqqZjRNUOFA1tsY/pxMBkMkm25QWTwvTDoZ3VTAN3vxwGY9v1kgNnDrq0fVcd4P0fGVe3OJD7DD7syfN3iYE2nasrQ9zeroM8zmHlSQ5k/QduvBEW1McCnXDV7YaKKy+/4cLDLEiqiDPP8rj2uoamMg13cLIQElWz9a9/QZ7mQR79wtqng3kdvYianwicxc00D3KPodAcLm/+Rls1I9XFury1e4TrbwWce14AefATax/3QzNOAZw5mO/oOJui26Y1XKFKmba2UOvkLocy0pUBdXknVLlDx/3/nsFIoBN+8IoVA+D5jQIupr7jRbZ9OuBsR7cyewHw0mYZdzJFHIjKwsFPDgzbbEvD530F2tSB4Xhxf2q0y76INDfA0ciaq/K8aCjxbwz/dvHTMAyMx+Pgxjmu1A1ASBT/ACYblracymHTAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-60.png&quot;
        title=&quot;&quot;
        src=&quot;/static/8311fd600d492a8b4c03000f54029893/329d6/2023-11-06-replication-60.png&quot;
        srcset=&quot;/static/8311fd600d492a8b4c03000f54029893/0eb09/2023-11-06-replication-60.png 500w,
/static/8311fd600d492a8b4c03000f54029893/1263b/2023-11-06-replication-60.png 1000w,
/static/8311fd600d492a8b4c03000f54029893/329d6/2023-11-06-replication-60.png 1326w&quot;
        sizes=&quot;(max-width: 1326px) 100vw, 1326px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1154px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/852af6cc065329570d6744b2e98e6aeb/248ea/2023-11-06-replication-61.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 108.19999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAABYlAAAWJQFJUiTwAAACXklEQVR42q1V2Y4aMRDk/78leSAQIcQu2qy4okiIByTu+w47iCww97jSZWZZyAzHQyy1PONuV1cfthP4zyOx3+8xGo2wXC7R6XQwnU71f6/X09Lv909rw+EQjUYD3W4XrVYL4/FY76OedsRK+L4P0zThOA622y1ms5k2JPh8PteGXPtYb7fbmEwm+psz93Dv4XAAsRLndA3D0N5oTEYEHQwGGoQzhcB0slwstMOZ2F+EfP7juq4W27aPxrKZDigMl7IQoLf1b7FzhNnRNhZQKaVnbmCOyKpWq6Fer2u2dEDhNyMYjyfY7XbRopwDMg8EeHl5QbVaRbFYRKlU0nO5XNZrlUoFhUIB2WwWzWYzAnoRMjd8S6WQTqe15HI5JJPJk+TzeTw9PyOTySAldrRnns8jvAD0gwC+VMwT4TfzyQpy9jwPnlTR5b9IIHrKB1CUIRUSsrfZwFit0JBwCHbyHurdjYG19B71dHI1h3rQowiLwlDI4GIIKHutJQVj0//LLgoYjp10vGVZV4/Xn917pF3iASVXMKXjjQ1c24kYa7X42RuW5Nm9D6jkCErmUfhVQDf9RRA86KDCyExLwfWB158/0E9/lSoqqJuAYU7K6zdMX8tC5XAGqD71iyVWhYqmqx7JIYcX452gvi2sveD69RW3SKBAWsKS20TZx+I4sxHMrlQ+jCSuwvGAoZ2SomyevsORAun8tZvY16qXRo8yPDU6T4+EHmzf4W/3QtM5lvrWjX1NwZ62bAXHDmCZQYiv7j8Btwiy1WyRg3WX2CcgTwTfAl5dfAo4x53RRx+pv1KzoAcnJKciAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-61.png&quot;
        title=&quot;&quot;
        src=&quot;/static/852af6cc065329570d6744b2e98e6aeb/248ea/2023-11-06-replication-61.png&quot;
        srcset=&quot;/static/852af6cc065329570d6744b2e98e6aeb/0eb09/2023-11-06-replication-61.png 500w,
/static/852af6cc065329570d6744b2e98e6aeb/1263b/2023-11-06-replication-61.png 1000w,
/static/852af6cc065329570d6744b2e98e6aeb/248ea/2023-11-06-replication-61.png 1154w&quot;
        sizes=&quot;(max-width: 1154px) 100vw, 1154px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1154px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ec5f2c91235e04208965244fd8f0b074/248ea/2023-11-06-replication-62.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 90.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAABYlAAAWJQFJUiTwAAAD40lEQVR42n2U+0+bZRTH+QN0icaYLBp1ZvEHhcVkEeIWNh0Sg+B0NlAGDBaBRYFucyzK3AVCBhMjsMjFOa5mI2icTLY4ZAzMoEjKRTauZRTa0tIWWjpAri3tx/d5DRYW9U1OnvNczvf5fs953uPHv3xer1e2//r+b89vPViYx+ORTfhutxubzcbExAR6vV4eDQYDFotFPrPx4o0E/B69QRw2Go0MDw+jVqtpamqisbERjUZDa2srbW1taLVazGbzP8CbGC4uLsq3CxAxjo2N0d/fLwcJf3x8HJ1OJ+/Z7XZMJpPMdHJyUlYg1pxOpxy/sLCAn8vlYm5ujunpaflWESAOT01NyaOQKNatVqscKNaEL8AcDoe8L+Ln5+dZWVnxSV5eXpYPCWa9vb309PTQ3t4u+93d3fT19TE0NEhLSzOdnRo6OjrkuWC+tLTkk7zurK2tyZu6UZ0E8gf3JCAhXRREO6xlcHBIyquWrq4uCWhIkm1EOzKCRWK7KYfr1Zo0mTGaLdgkGVabJNdqwyJJNegNzNinmX84w5wwp4NZxzSOKSt2ixQj5dhkNGwGFHlMTf6I92OjOflpMilHUzisOsYRaR4RFk5h7jlqywv5ruQLakrzuFp8gYLPVOSoEvkg5C2Oxh0UtPCuA7pdqygi49geFEp4XCKK+CQUhz4kMuId9u7aQ/75z6m7nM8vVyupqCyl8KtcUmKUKPcFExEYyKl4pfTe3D6GaxLg2+/FsDUghB2hMRyIP8aVb8tITYwnfG8wxw9FU15VRn3VZWoyMggLCuSNV/wJ2bmT3QE7OB6jECA+QLfbRVi4gseeeZUt24IJTTpBww813MrO5sCbrxMVHERRRRk/X6/jWuYpMpKUFJ1OpTovg9rCM9z4JkcmxUbJUfv3s2Xryzy+bRc5JZWoG2/RVFbCJwkKMj+OpU7KYcGFXIpzTtJ3uxqt+kf0nfWYum9gH2zG61nzAa6urqI6HMNr/gG8uN2foMB9xEdGU1uaS21xJjfLc2m5WUvEu0qpKF8yM9qKeaAF24gax2gbs0aNBOjZyNDFpbyzZKoSSJMS/OQTz7M7aA9Z6WlUFWYyoL5GR/N1lIpIVEeSKSsqYLDzLjOGe9iG7+Ic10hF9mwuSlt9Nc3fX6Tm6yyefuoFnnv2JWou5VN8/jTZJ9IoyjlLRX4mfd1qTHodi3MOFqb1zOi7mDXf9wGKt7MmFeWB5o5kDfT/3oAyKoHYgwncb7/N5EA7v/10hYtZZziXns6dX5ukIv6dL9eikz+ndDy0PvBJFv+h6BKLkglfdB/x0Ndbmcfj3dTaxP6jbWtjw/0LKOilF0fKq78AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-62.png&quot;
        title=&quot;&quot;
        src=&quot;/static/ec5f2c91235e04208965244fd8f0b074/248ea/2023-11-06-replication-62.png&quot;
        srcset=&quot;/static/ec5f2c91235e04208965244fd8f0b074/0eb09/2023-11-06-replication-62.png 500w,
/static/ec5f2c91235e04208965244fd8f0b074/1263b/2023-11-06-replication-62.png 1000w,
/static/ec5f2c91235e04208965244fd8f0b074/248ea/2023-11-06-replication-62.png 1154w&quot;
        sizes=&quot;(max-width: 1154px) 100vw, 1154px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;요약&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;MySQL 리플리케이션의 복제 토폴로지&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;싱글 리플리카 복제 구성&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;멀티 리플리카 복제 구성&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;체인 복제 구성&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;듀얼 소스 복제 구성&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;자료
Real MySQL 8.0 2권 pp.494~503&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;이렇게 테코네 카페의 시행착오를 따라가 보면서
데이터베이스, 그중에서도 MySQL의 리플리케이션 작업에 대해 알아보았습니다.&lt;/p&gt;
&lt;p&gt;참고 자료로 활용한 &lt;a href=&quot;https://m.yes24.com/Goods/Detail/105536168&quot;&gt;Real MySQL 8.0 2권&lt;/a&gt;에 훨씬 자세한 내용이 기록되어 있으니 참고하시길 바라요!&lt;/p&gt;
&lt;p&gt;읽어주셔서 고마워요.
테코도 이제 행복하게 살아야 한다! 🙌🏻🙌🏻🙌🏻&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;도움을 주신 분&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://drunkenhw.github.io&quot;&gt;우아한테크코스의 멋쟁이 천재 박스터&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/choi-jjunho&quot;&gt;우아한테크코스의 멋쟁이 주노&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[데이터베이스 복제하기(리플리케이션) 1 - 테코와 알아보는 대규모 데이터 관리]]></title><description><![CDATA[일상을 공유하는 작은 카페 사이트를 직접 개발해 운영하고 있는 테코(…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-11-06-zero-downtime-deployment-1/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-11-06-zero-downtime-deployment-1/</guid><pubDate>Thu, 23 Nov 2023 11:00:00 GMT</pubDate><content:encoded>&lt;p&gt;일상을 공유하는 작은 카페 사이트를 직접 개발해 운영하고 있는 테코(1세).
사이드 프로젝트로 만든 카페에 많은 사람들이 들어와 응원의 글을 남기자 테코는 행복해해요.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1530px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2aa16d920a44af731c3a1c7891a751c7/77e4e/2023-11-06-replication-1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 53.800000000000004%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA9ElEQVR42r2Sz0rDQBDG83Y+iVfvevDUi/QB9OJJELwUfICUVkQkiHooZE9hDws5hcCG7jbZnf1MlgQTay2hxYFh9u9v55vZAEe24N+AzjnvW+t+j2BlDloXh2WojIGoHIqEA+cXoOs7UPeo2wFUSkFKiTRNIYRoD1sfDH+AfDxBvJxitjjD0+cl8lLDEm1n2MnjnIMxhiiK/BitxMZ0FiMOb/H8eoPJ2ynu2RVMDTO/ATvLsgxJkvj4s3pSEeYrIPwgvLxTfe5b6ugaNgI2tqrvV/XMYlC4v4C7utsHN96o7CkdAvdBRv1DrbXvbOdlWR4E/wKKQFt9/l7OtgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-1.png&quot;
        title=&quot;&quot;
        src=&quot;/static/2aa16d920a44af731c3a1c7891a751c7/77e4e/2023-11-06-replication-1.png&quot;
        srcset=&quot;/static/2aa16d920a44af731c3a1c7891a751c7/0eb09/2023-11-06-replication-1.png 500w,
/static/2aa16d920a44af731c3a1c7891a751c7/1263b/2023-11-06-replication-1.png 1000w,
/static/2aa16d920a44af731c3a1c7891a751c7/77e4e/2023-11-06-replication-1.png 1530w&quot;
        sizes=&quot;(max-width: 1530px) 100vw, 1530px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;그런데 어느 날!
카카오톡에 ‘귀염둥이 우테코’ 신상 이모티콘이 출시되고,
이 이모티콘이 선풍적인 인기를 끌며
테코의 카페에 유례없이 많은 사람이 유입되기 시작했어요.&lt;/p&gt;
&lt;p&gt;하지만 테코의 작은 서버와 데이터베이스로는
엄청나게 많은 사람으로 인한 트래픽을 감당하기에 너무나 버거웠고,&lt;/p&gt;
&lt;p&gt;결국 테코의 데이터베이스에 과부하가 걸려
고장 나기에 이르렀어요.&lt;/p&gt;
&lt;p&gt;데이터베이스를 뚝딱뚝딱 고쳐서 다시 가동하기까지 걸린 시간은 길어야 3시간 남짓.
하지만 그 &lt;strong&gt;3시간 동안 카페를 전혀 이용하지 못한&lt;/strong&gt; 사용자들은 불만이 가득했어요.&lt;/p&gt;
&lt;p&gt;짜증 한가득한 사람들에게 사과문을 쓰고 마음에 깊은 상처를 받은 테코는
&lt;strong&gt;다음에 또 데이터베이스 과부하가 생기더라도
카페를 내리지 않고 계속 서비스할 방법&lt;/strong&gt;을 공부해 적용하기로 합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;데이터베이스를 하나 더 두자 - 스케일 아웃&lt;/h1&gt;
&lt;p&gt;테코는 생각했어요.
하나뿐인 데이터베이스가 고장 나면
이것을 완전히 고칠 때까지 카페를 정지할 수밖에 없구나.&lt;/p&gt;
&lt;p&gt;만약 &lt;strong&gt;똑같은 데이터를 가지고 있는&lt;/strong&gt; 예비용 데이터베이스 서버를 하나 더 둘 수 있다면,
원래 쓰던 하나가 고장 나더라도
예비용 서버로 카페를 운영하고 그동안 고장 난 쪽을 수리하면 되겠구나!&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1410px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/370bfd11504cbbad518a48e04913fb4c/5b89d/2023-11-06-replication-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 45.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABPUlEQVR42o2S3U6DQBCF+/4v4EN4b+K1qZEWIQu2EJBW/kov2ibFskD5Oc6uYoBi4kkms7MMH3uGnWGktm1R1zWapvmNcd2F6B1r1gcJpWkKXdfBGIOqvsKybWiaBtM0sV6v5TOxZgbD+XwevDsAdiqKAkEQwHXfsU8iXNIT1SHiOMZ2u5URiTqMZO+fJ+yf1PcDZAR6fNng7sGS+9I2hdDB3+DE9JvTTQIbaoh2O4HA3Ahx/+zL/ar+nqWQu9/jYDn/A1ZVBZ5lyDknax+IfQ+WZcE0DNiUN56HkqxeeIYpDYCcYOHxiMVqBeY4UJgB9W2FBf2gp+USjIBz+ika1YqiIEmSmzkOgCVZ+ixLcJGvV5lzEfShnK5JQeuCMieIAIkbIRwNgHmeIyOL2Y/Nguq630Qz6uY0de/Glr8ABzW0AzlwGuIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-2.png&quot;
        title=&quot;&quot;
        src=&quot;/static/370bfd11504cbbad518a48e04913fb4c/5b89d/2023-11-06-replication-2.png&quot;
        srcset=&quot;/static/370bfd11504cbbad518a48e04913fb4c/0eb09/2023-11-06-replication-2.png 500w,
/static/370bfd11504cbbad518a48e04913fb4c/1263b/2023-11-06-replication-2.png 1000w,
/static/370bfd11504cbbad518a48e04913fb4c/5b89d/2023-11-06-replication-2.png 1410w&quot;
        sizes=&quot;(max-width: 1410px) 100vw, 1410px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1410px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/706a615ee39986008e2026dbec2c5804/5b89d/2023-11-06-replication-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.60000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABgUlEQVR42o1SC26CQBD12r1Ar9Bj9Ag1UesHFFuKv5RIFD/AKgqoIODrzFps05jqJpMZhpm3b3ZeCVeOEAK2bWM2m2EymcCyLDiOI7/Zc263211rRelvwvd9mKaJTqcDVVXRaDQuXlEU6O866vVXjEYjZFl2G3C9XkuGzIKB2u02ut0ugdTRbDYp1rCcL+F4AnEc3wYszul0Qp7n0jMTjgtG1vgTq75xqfsXME1TuK6LXRhg6oSYiojbkCTJmRH1W8KBS89yFyA3Gr0+Q+Ph6QWPz2cmyTH9YZ9niLMUB7o8vwXIh8eTnsf+1VCw0QcGFqpyH8NibLac3qyIs++YczOxhr9w7pMNs9NIMmwfui43PRwO0TMMaLRxpdWC/tZFn3J8wVXAhEDY9lRwJB/t9zAGA5QrFaiaRnJpoVKroVyrokrSGZOkQhJ2fDxiT7WFScDD4QB7tZJm0nanK4FlGMKNItgkcnuzkVv3Nj7mwRaLLVkQwKeNu1TneZ7875JnwC+GakilggQ/jgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-3.png&quot;
        title=&quot;&quot;
        src=&quot;/static/706a615ee39986008e2026dbec2c5804/5b89d/2023-11-06-replication-3.png&quot;
        srcset=&quot;/static/706a615ee39986008e2026dbec2c5804/0eb09/2023-11-06-replication-3.png 500w,
/static/706a615ee39986008e2026dbec2c5804/1263b/2023-11-06-replication-3.png 1000w,
/static/706a615ee39986008e2026dbec2c5804/5b89d/2023-11-06-replication-3.png 1410w&quot;
        sizes=&quot;(max-width: 1410px) 100vw, 1410px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;테코는 카페의 &lt;strong&gt;단일 장애점(SPOF)&lt;/strong&gt; 에 해당하는 데이터베이스의 문제점을 해결하기 위해
데이터베이스에 대한 &lt;strong&gt;스케일 아웃&lt;/strong&gt;을 도입해 보기로 했어요.&lt;/p&gt;
&lt;p&gt;오! 어떻게 이런 생각을 해냈담.
테코는 자신이 천재가 아닌가 생각했어요.&lt;/p&gt;
&lt;p&gt;실행력 강한 테코는 우선 데이터베이스 서버 2대를 추가로 구입해,
&lt;strong&gt;총 3대의 데이터베이스 서버&lt;/strong&gt;를 가진 구조를 갖추었어요.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1448px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d036955b82dbc62879b5f22b6430ef06/e7c3c/2023-11-06-replication-4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.19999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB70lEQVR42n1T227aQBTk/1/73k9okQAhLBQiWgqIgEtiYoNdAriWscOuoTY28W26uw6BBpojHe3lnB3PjmdL4JHnIvMsY0MuMmNzkWJeZNF6qh17j8mjdGzi4QcBprMZnhYLqNMpflsWDEPHZPKAqabC0KdQlHs8PD6yfQOyLMP3fZxjlAQ7z0O2XsMaDqG3WlgN7rAc3kHudHH7Y4abtorb7xq+9Rcof72BVP6CdruNer2Ofr8P27ax2WxODHkcwhAOKzy7LigrkucN1msHhG5BiAfK0t+n2PsHpJQyAILddguX9Yfs7D9Xfh/H4pu++z3iOIYud5FtKfLj/pW4AHQcF5a5xMSw8al8X2xGIdI0AXUc5H5Q9NkmiKFdELgA9JieURhgblF8ltQCL86RpDnS4I/4uzyemBzm5BXwI4bXgpEDkw9yt4vU3bxJkX905XM/pWkqMknYmCTIxDxB9MLGOBG1mK3fe/AqQ+6r1WoFXdehqio0TROeWy6XmM9/iZppmmw+F/JcZci/wIF2u50AaDQaqFQqaDFPNptNSJKEWq2GarWKTqcjDN3r9QRowB4DP3fxUvgmIUQwUxRFsOM5Ho/FejQaYTAYiJGbWZZ/wlmNsfUIKPVOgIfDAVEUCZ9xnZJXbc51/Z9Pr9X+Aj4D3NBg+FfcAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-4.png&quot;
        title=&quot;&quot;
        src=&quot;/static/d036955b82dbc62879b5f22b6430ef06/e7c3c/2023-11-06-replication-4.png&quot;
        srcset=&quot;/static/d036955b82dbc62879b5f22b6430ef06/0eb09/2023-11-06-replication-4.png 500w,
/static/d036955b82dbc62879b5f22b6430ef06/1263b/2023-11-06-replication-4.png 1000w,
/static/d036955b82dbc62879b5f22b6430ef06/e7c3c/2023-11-06-replication-4.png 1448w&quot;
        sizes=&quot;(max-width: 1448px) 100vw, 1448px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;굿! 하지만 관리가 필요한 서버의 숫자가 늘어나자
테코는 이전까지 해본 적 없던 새로운 고민을 하게 되었습니다.&lt;/p&gt;
&lt;h2&gt;여러 개의 데이터베이스가 어떻게 데이터를 똑같이 가져? - 리플리케이션 개요&lt;/h2&gt;
&lt;p&gt;테코의 서버에 데이터베이스를 더 설치해서
데이터베이스가 총 세 개가 되긴 했는데,&lt;/p&gt;
&lt;p&gt;실시간으로 데이터가 빠르게 생겼다 수정됐다 사라지는 상황에서
어떻게 다수의 데이터베이스 &lt;strong&gt;데이터를 똑같이 동기화, 즉 리플리케이션(Replication)&lt;/strong&gt; 해주느냐는 문제가 생겼습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1410px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/933435c8073ae7ebbbc317e84a15e274/5b89d/2023-11-06-replication-5.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 26%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAmklEQVR42pWPSQ7CMAxFe2tOwBE4BkfgCIhRDGLooqVCahdEbWrc4nzShE0hEuCNrW//ZzvCHyEiLhdZguqSeNGY3kwUMimlQLVGfiPkipyuiVBqDbGA+emMdDp78b4AyRoXy5WtGgxGEwzHa6eXdPcLLYDaB9iEv/gAdhuZ2V/FLepGer0u4viI63bz24Xv5pB2yFIUu30Q+AR3UIdQ8sCo+QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-5.png&quot;
        title=&quot;&quot;
        src=&quot;/static/933435c8073ae7ebbbc317e84a15e274/5b89d/2023-11-06-replication-5.png&quot;
        srcset=&quot;/static/933435c8073ae7ebbbc317e84a15e274/0eb09/2023-11-06-replication-5.png 500w,
/static/933435c8073ae7ebbbc317e84a15e274/1263b/2023-11-06-replication-5.png 1000w,
/static/933435c8073ae7ebbbc317e84a15e274/5b89d/2023-11-06-replication-5.png 1410w&quot;
        sizes=&quot;(max-width: 1410px) 100vw, 1410px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;데이터베이스의 리플리케이션 방법을 사흘 밤낮으로 조사한 테코는
자신의 서버에서 사용하고 있는 MySQL에서 리플리케이션 기능을 제공해 주고 있다는 것을 알게 되었고
이 기능을 공부해 보기로 하였어요!&lt;/p&gt;
&lt;h1&gt;MySQL의 리플리케이션&lt;/h1&gt;
&lt;p&gt;”&lt;strong&gt;MySQL 서버에서 일어나는 모든 변경 기록&lt;/strong&gt;은 &lt;strong&gt;바이너리 로그(Binary Log)&lt;/strong&gt; 라는 파일에 순서대로 기록된다.
그리고 이 바이너리 로그 안의 변경 기록 하나하나를 &lt;strong&gt;바이너리 로그 이벤트&lt;/strong&gt;, 또는 짧게 &lt;strong&gt;이벤트(Event)&lt;/strong&gt; 라고 한다.”&lt;/p&gt;
&lt;p&gt;이 문장을 읽자마자 눈을 반짝이는 테코.
그렇다면 &lt;strong&gt;운영 중인 데이터베이스에서 기록한 모든 바이너리 로그를
예비용 데이터베이스로 가져와 똑같이 적용&lt;/strong&gt;하면
간단하게 데이터 동기화를 해낼 수 있겠구나!&lt;/p&gt;
&lt;p&gt;테코는 MySQL의 데이터 복제 과정을 조금 더 자세히 들여이다보았어요.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 684px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/27d4d14370e10c7b79572b1765060f91/544f7/2023-11-06-replication-6.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 194.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAnCAYAAAAPZ2gOAAAACXBIWXMAABYlAAAWJQFJUiTwAAAE+ElEQVR42qVWSW8jRRjNT5wb/2AkTgiJXzAnhLhx4syNE3BAAgnNMJkhJLbjfY/3Jb3Y7d737sertt1xPJ7YEi091eKqV9/y6itfpWkK8amqil6vh2aziX6/n/Wr1SoajQY6nQ4qlUqGu7s71Gq1DK1WC6PRCPf39wjDMOO52hPKsoybmxvc3t5mJIKwXC7nB5RKJRQLBXy4vka73c7IxYHdbhfFYvGJEGe+JElgmiYc183GMcfxzohTX04oLBWbBfZ98dm2jR5dnq1WmOo6JpoG2bLEhk/WXmSh2BDFMS79ngg/44Vwz42izNU13VYdB4bvIzqw6rTLW3NOEgoCQbSkq4+E4tgZ+SnSjDD1XYRrDbFwTSw6IE4459DChHNi9rDd9z8h7Gsq/vzpRyTUkz+bw6MsHErF7Pdg1uswHh9hKgp0trokwWViEiYrobXJLvvPCFdhhF6zB/QesJqMITGrc2pxUL5Hl/rrsp006hhWyhhWK9C4BqaBRFuT1ER64FFGmAQBUtcibOqEkmCM4DqA5zIcPkMRcWhDMXRohoHVRoNv6tm6RKw7tjAmYaJvkBLJDjE3CXJ5OMC3b95g0mmjUSqi8OEa/757i0G9BokhUaYTqNRoQI5nFmaEtGAPMRYWqKMhfvj+Owzoau3jR5Tfv0eDV7RVuEPp/d9o3ZfwyNjuxf1ZwhyMUcR7GtJic6XCUBUE7ItxwBj63Le/xxcRxpy31BUMZrnd6+JhMMDi4QEqQ6FN6O5sCoNxPUuYEKwKtGgBacbMThcY16qYt5qYtVm2mHVruYBNi90D6bxooZCGqynYSM3tAUIBDEEO/p46J7J8Loax61MmBisPyxjHtihnJI84BxKmlxIKC3wmot+sQ2Gs2rw1fVbpLt2uU+T6zuXDavREyKylOy1mehQ6pGinzQa+ePUKC8ZNZlIUak/qdiCxL7M/5xX1PP+oOPAEcSNOIfE8mOIwIVxhCQsFkijvi72HPl9dUjRDUZl3Je4kju8y6xDSJM3r4hZp3g+EB/nogoq9VCWUfvsZ4eABWCyA+RxgmQLFjA1vAvuxZSPOHqkUYZTkCCLxDh1ZOGR83v31FmG5hVlvhNZdGfWbAppE7aaICseLmYz5YoXZQoVuBBB5cLwUtpswlMcup9tTnSCFyQUbK4RmBdvWDKAaHnQn4jiA4YSw/e1an1fYYxsfWyiC6nIR9Zud7AUHCPmmuAHWdoCxukF/IWGmaBhJK6w0VqO1DdvxYLF6WwxLTugFW8I9hDs0GsPRAl99/Q3qrT4q1TaKpRoKxRr+uSmhUKpjPJUx5bOxXC6zN/xFQuGSsOKXX3+HJOuQVQOKamIpaZAUHctHbTe34h1wnrt8TLiHcDtkjDySG0HMN1rEDBmEB2GU5lkWcjxLaHsx1I0JmZZWekNU2w9od4fo9CYYjpd0WaLCFNaIsxammXUmF47H91ivLMZKyaUjYrfeuLCcGH4Q5bflIgsVbcnsR5mLUfwEP9glLz4hm5di6HPDxg0x0y1IJv/fWB4UwvaoR08IOzlPuM+y0Nkfb6/RHc5wV2mg3R+jUGmi3OigVu+i3ujzj2kNGv9NvJiUJx0u8fr1l1n8ZMXIICQkIOTzmM2prMVG9pRe7f97eUIS2W15ju1BEQLemCASVm/bPfzwRHE494fzEuSEHiuy0JBoxXMYiYr8P77/AOmusZFN0RH5AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-6.png&quot;
        title=&quot;&quot;
        src=&quot;/static/27d4d14370e10c7b79572b1765060f91/544f7/2023-11-06-replication-6.png&quot;
        srcset=&quot;/static/27d4d14370e10c7b79572b1765060f91/0eb09/2023-11-06-replication-6.png 500w,
/static/27d4d14370e10c7b79572b1765060f91/544f7/2023-11-06-replication-6.png 684w&quot;
        sizes=&quot;(max-width: 684px) 100vw, 684px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;바이너리 로그&lt;/strong&gt; -&gt; &lt;strong&gt;바이너리 로그 덤프 스레드&lt;/strong&gt; -&gt; &lt;strong&gt;리플리케이션 I/O 스레드&lt;/strong&gt; -&gt; &lt;strong&gt;릴레이 로그&lt;/strong&gt; -&gt; &lt;strong&gt;리플리케이션 SQL 스레드&lt;/strong&gt;로 이어지는 긴 여정을 통해
두 데이터베이스가 빠르게 동기화할 수 있도록 MySQL에서 도와주고 있었어요.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;이 세 대의 서버 데이터를 동기화하기 위해서는
먼저 MySQL 리플리케이션의 중요한 전략 하나를 정해야 했습니다.&lt;/p&gt;
&lt;p&gt;바로 &lt;strong&gt;소스 서버에 있는 바이너리 로그의 이벤트 하나하나를 리플리카 서버에서 어떤 방식으로 읽어올 것인가&lt;/strong&gt;였습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1402px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/715409dd1abb12ddd9f4a8f4860b270a/23039/2023-11-06-replication-7.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB3ElEQVR42pWTW3OiQBCF/f8/YV/3MU/7HH3JxdrarNFkq0xUJIiyLAJyHeQycPYMUKWplHuhPNVD0340p2cGQIM6SiFjgdPV8Ne8jxf1/ho0/MNwPMLXz58gnBBRDQjZIGNMJXBkzBhFmz/FNkdFRacwB2ryB4p6Zdp4/nINGD+REKa/bfC6XMOyXSxWOpaagbfNDqZlwzCtVppuYGv9QsUXVBVwyIBC9sCUibys25a11RrTxymWSw0vLwuMhiOMx2M8PHzHbPaE2fQJk8kj5vM57u/ukSadVXHenIAJWz5WDRoyPe8Az/WQsDAMIgSHgAqx37sIeH/wA9b4fJ7CcVykadYCo/y8w5LAsvO4ohG+r8Pfe/SmRJAXCBklvW76GdX9uh1fP5u4uABUyY01gWOvENALJ0jhRhn8MEPM+5jFShW/SNJvWffADx3SR/XJdWuyhMgqPP9Y4Hp4g9u7bxiObmFu99juXIT8vrqv/SegenPKPaEbNqdtcMomtPUOthfj1XKQiKqt+T8gN1op0W2LXoKT9EVJoPwzMCEwJxB1B1WquI1KEktWtZFSuZreqdjIUwMfgDkXPhNh0Sk4W1+SqjmXf2S36qQIISBEii5SWYZKSvztBF861b8BOQTsNoyApxEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-7.png&quot;
        title=&quot;&quot;
        src=&quot;/static/715409dd1abb12ddd9f4a8f4860b270a/23039/2023-11-06-replication-7.png&quot;
        srcset=&quot;/static/715409dd1abb12ddd9f4a8f4860b270a/0eb09/2023-11-06-replication-7.png 500w,
/static/715409dd1abb12ddd9f4a8f4860b270a/1263b/2023-11-06-replication-7.png 1000w,
/static/715409dd1abb12ddd9f4a8f4860b270a/23039/2023-11-06-replication-7.png 1402w&quot;
        sizes=&quot;(max-width: 1402px) 100vw, 1402px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1402px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f024f98c65383054823f6ca14c83df97/23039/2023-11-06-replication-8.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 86.80000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAABYlAAAWJQFJUiTwAAAB70lEQVR42q1UyW7bMBDN//9Cjj320nNq9BLbsIPYhg3vW+FV3iVL1r5M501CRYrVogVK4GnI4fBxNuqB/vN4wCcIAprNZqRpGq3Xa1osFjSZTEQ3Ho9F9no9arVaIkejEW02GwHsVqsVxXH8QYiRJIkoj8cjbbdbOTQYDAQgBVGj0RDy4XBI+/2eDMNIz6YeYmHbtmyapknn81kIT6cTHQ4HuQBz6AAQQYdooDdNS+T1ev0gtCyLLpcL6boucxzAHJfgAgBE6hLYQnfhfZ2BPdjnQv73wZH5LjmuzdMoXxSVg2wuMC9C/G5juTda/3wl03BknSsKDFVOkCeEHYbhH/1zAp92uwb5npV6nAu5XC7TfD6XtimVSlKgvNcJFqku4AuVV3d9iNHpdISoWq0KaZYs4IPXMKIbI+F5GEVcnCPdbtZ922QVy+VSGjWKopTI8DwyOPG2tiPP0OURqM4oSkuuyopYhRLCG4D1X5++0ejLIxH3XfJm/PunV1TZ7AD9d4378EeFy2sLYZHdHSE8Qxgehwn4vs/SJ8dxKA4D8nnPZb3rurIP28+kOUIY4J02m02q1WoigUqlQrWXFyo/P0vR6vW6yOl0mua7kBADT6jb7VK73RbgD4O1+svgQuj7/X7aWjlCuJ+FCuNvUFSUX+twHR615uZ4AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-8.png&quot;
        title=&quot;&quot;
        src=&quot;/static/f024f98c65383054823f6ca14c83df97/23039/2023-11-06-replication-8.png&quot;
        srcset=&quot;/static/f024f98c65383054823f6ca14c83df97/0eb09/2023-11-06-replication-8.png 500w,
/static/f024f98c65383054823f6ca14c83df97/1263b/2023-11-06-replication-8.png 1000w,
/static/f024f98c65383054823f6ca14c83df97/23039/2023-11-06-replication-8.png 1402w&quot;
        sizes=&quot;(max-width: 1402px) 100vw, 1402px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;테코의 고민 1 - 바이너리 로그를 어떻게 식별할까&lt;/h1&gt;
&lt;h2&gt;1. 바이너리 로그 이벤트의 이름과 위치를 통해 식별하자&lt;/h2&gt;
&lt;p&gt;바이너리 로그 파일 안에는 여러 개의 이벤트가 순서대로 저장되어 있습니다.
‘그럼 &lt;strong&gt;복제하려는 파일의 이름&lt;/strong&gt;이랑, &lt;strong&gt;복제해 올 이벤트의 위치만 정확하게 알 수 있다면&lt;/strong&gt;
이걸 사용해 로그를 식별하면 되겠다!’라고 테코는 생각했어요.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1460px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ebe7766b71c158c4d45a8a58355878f1/a2906/2023-11-06-replication-9.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 87.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAABYlAAAWJQFJUiTwAAACH0lEQVR42o2UaY/iMAyG+f9/ZgRCIGAlEFr4wDHiKDClnIVS7mGhLdCmfcfO0tGsthyVrDiJ7cTO48bw4hcEwUt2sVcCRAW7d0As3AwNLMuCoihSGo0GarUa5vM5VFVFs9nEaDSSe6wfDodv39D/vxsej0domoZCoYBEIoF4PI5sNotMJoN0Oo1kMolcLoder4fhcBh9Q9u2YZqmFF3XMZ1OMZlM0Gq15C3r9brUK5UKisUiqtUqNpuNtGUxDENm9h3Q9314niflcr3CcV24QkDc1jgdQXNpI/6uuWRzuVxwJXvWOUZkyuzs73YQ1hFsws7sdD6fpfC+JxwIX7z2ykHgI5WJY/b2RhNA1fqy+FyzfD6Pk33CctnEbrV+LaDwA7x/fmKhG3Cotr9LJYzHY5TLZfSHI1jeFfrqg/b+hFd4HJDrd3apLpTamfT1aoXBYIDtdiv3ParVgUqwdWycaPSDBwH/YeoHW1EwC9JtOtgT4nmnsCPzyHi0222JTafTQbfbRYXWZgT6w04J0WH2tH5fPgDDnE6lJOBZgrpMtSzd5BeBzfztiAYeIwPyieZyiREFNWisUqtV1Q8oNH+nGirkqBpz9AhiZUYwr9fY7fdYUDPcTdkltkzbwpZ4Y1k5DgxKXZnNoC4M6IQP789PJ5jUGUuiwL0B/fRRwhox2Nxme0qP9Xt298F+Mn/6P+QfAzc2S6hzi/k/Uom6yb2AX3IyZgcB17bvAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-9.png&quot;
        title=&quot;&quot;
        src=&quot;/static/ebe7766b71c158c4d45a8a58355878f1/a2906/2023-11-06-replication-9.png&quot;
        srcset=&quot;/static/ebe7766b71c158c4d45a8a58355878f1/0eb09/2023-11-06-replication-9.png 500w,
/static/ebe7766b71c158c4d45a8a58355878f1/1263b/2023-11-06-replication-9.png 1000w,
/static/ebe7766b71c158c4d45a8a58355878f1/a2906/2023-11-06-replication-9.png 1460w&quot;
        sizes=&quot;(max-width: 1460px) 100vw, 1460px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;그 생각을 그대로 재현한
&lt;strong&gt;바이너리 로그 파일 위치 기반 리플리케이션&lt;/strong&gt; 을 적용해 두 데이터베이스의 동기화에 성공한 테코.
소스 서버에서 리플리카 서버로 데이터를 쇽쇽 복사해 주는 것을 지켜보면서 뿌듯해하고 있습니다.&lt;/p&gt;
&lt;h3&gt;위 방법의 문제점&lt;/h3&gt;
&lt;p&gt;그런데 어느 날, 또 다른 우테코 이모티콘 신상이 출시되면서
소스 서버가 또 터지고 말았어요!&lt;/p&gt;
&lt;p&gt;테코는 하얗게 타버린 기존의 소스 서버 대신
읽기 전용으로 사용하던 리플리카 서버를 새로운 소스 서버로 대체하고,
백업용으로 사용하던 리플리카 서버를 읽기 전용 서버로 대체하려고 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/adf2ad68fd8e9740b948b02949db5e4e/2023-11-06-replication-24.gif&quot; alt=&quot;2023-11-06-replication-24.gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;하지만 백업용 리플리카 서버의 상태를 보고 크게 당황한 테코.
백업용 리플리카 서버에는 소스 서버로부터의 동기화가 50분 정도 늦게 진행되어,
소스 서버가 가지고 있던 원본 데이터와 50분만큼의 차이가 있는 상태였어요.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1478px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/518bbb16e6906736c2bc8eca1d83e741/02a0d/2023-11-06-replication-10.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 58.20000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABiElEQVR42q1T0W6CMBTd/3/D9rBsf7DsxSV7MzGLkEgCGhfQIoI4gYKggMBZWwUF2duaXHrb3nvuuaflAf88Ho7HI7bbLWzbhmmaMAxD+Lquw3Ec4W82G6zXa7EmhGC1WmG5XMJ1XWE85nQ6nQHLssR+vxcHPHA0GkGSJAyHQ2iaJvzJZAJZloU/Ho+hKApUVYVlWaJQGIaoquoMyD9pmsL3fURRBEopPM8T691uBz8IENBQrOs9fh7wfWacSM2uAewdl4opDUD1b1Rl0Q1oYloaXvPbhwWTgo8v18bHyxMKdYq5SWAyDVOme53TzWsxzLIMSZKgKK5saA6mFduLEniBj4WxAGHAliIjJnqrmxZgnueYzWZCeK4TD+F6+gFFys5uu4gZ+9fPAX7e3u+6a7VcPx1+8zljmzGxi9BHYuqsQNUQieMYqjaFTcy/NexqWV5myXUweH4EFkSwrlH79LsDvA2q54i9UW/OtArCFmBXuwaQXwK3w+EgZn4xrcrsjQrrSe779X4BFEyeC+g594IAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-10.png&quot;
        title=&quot;&quot;
        src=&quot;/static/518bbb16e6906736c2bc8eca1d83e741/02a0d/2023-11-06-replication-10.png&quot;
        srcset=&quot;/static/518bbb16e6906736c2bc8eca1d83e741/0eb09/2023-11-06-replication-10.png 500w,
/static/518bbb16e6906736c2bc8eca1d83e741/1263b/2023-11-06-replication-10.png 1000w,
/static/518bbb16e6906736c2bc8eca1d83e741/02a0d/2023-11-06-replication-10.png 1478w&quot;
        sizes=&quot;(max-width: 1478px) 100vw, 1478px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1256px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a0864b49c17298d458801a16c8d6bff3/30373/2023-11-06-replication-11.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 97.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAABYlAAAWJQFJUiTwAAACCElEQVR42q1U2XLaQBDk/38k+YSUH1yucgXEDQIhDMYEGQyIS1wCAaIzPbaAchQ7JNmq0erYbfV0z04C/3kkeDkejzgcDnAcB91uF8PhEKPRCL1eT+8Zg8FA302nUyyXS8znc4zHY533+/0ZkGAcXJTP55FKpZDNZlEoFJBOp1GpVPQ5mUyiXC7DMAzYto1Go6HPxWIRi8UCETFlOJlMlAVBPc/DZrM53XOezWbKjhu5znVdZcd7ZsGsyPyU8nq9RqfT0T+22208yN9/yNwVCcjksdnEQ71+Ctu29H21WkWpVEK/34fv+2fAiO5rhNjvAmz3B2zDEFvRNpB5tdvBF63CN4mOF3tiTXldAuwOZFuC8zJA86WLlqTkuEM8S0o9kWYdbPCUvYP/1MB573lOvLedDKfeM2zLRMZIwxCTqmIMNZqIZnNh+vX+O7yb22jDrwwv01aWssmyLHWXTpdE29Vqpd88Mahu11S3KKsPAPEGGCggyyiTyahZNI4jFD3DGO0+ZVir1RSQwVqMAH8H9CkgS4Ipv2f4V4A8SgQkGE9GLOC1KVNDAtKUS0D8S8oRQ9M09fi5UjZ+sIVp3MOrlK8rG5rCBpHL5dQUPePCciHfvtzdYvDt5jpAMoxcJkM2CY65ALdbLfSkDuMMSsTpwN4Y9cEogiD4swbLVsVOcRmXDTNuHD8o7J/yuK7LS10LcQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-11.png&quot;
        title=&quot;&quot;
        src=&quot;/static/a0864b49c17298d458801a16c8d6bff3/30373/2023-11-06-replication-11.png&quot;
        srcset=&quot;/static/a0864b49c17298d458801a16c8d6bff3/0eb09/2023-11-06-replication-11.png 500w,
/static/a0864b49c17298d458801a16c8d6bff3/1263b/2023-11-06-replication-11.png 1000w,
/static/a0864b49c17298d458801a16c8d6bff3/30373/2023-11-06-replication-11.png 1256w&quot;
        sizes=&quot;(max-width: 1256px) 100vw, 1256px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;‘헉, 그래도 A 서버는 소스 서버와 같은 데이터를 가지고 있으니까,
A 서버의 바이너리 로그를 읽어서 데이터 동기화를 이어가면 되겠다!’라는 똑똑한 생각을 하는 테코.&lt;/p&gt;
&lt;p&gt;하지만 &lt;strong&gt;바이너리 로그 위치 기반 리플리케이션의 치명적인 단점&lt;/strong&gt;으로 인해
테코의 계획은 무용지물이 되었고, 결과적으로
&lt;strong&gt;과부하로 마비된 데이터베이스 서버는 1대인데&lt;/strong&gt;
&lt;strong&gt;실제로 쓸 수 없게 된 서버는 2대가 되는&lt;/strong&gt; 참혹한 상황을 맞게 되었어요.&lt;/p&gt;
&lt;p&gt;결국 2대가 분담해야 할 대량의 부하를 1대가 감당하게 되었고,
또다시 테코의 카페는 새하얀 오류 화면만을 보여주게 되었습니다.&lt;/p&gt;
&lt;p&gt;어떤 문제가 있었던 걸까요?&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1256px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7cd76d4a83318945a28e1740f4d9c53e/30373/2023-11-06-replication-12.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 73.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAABqUlEQVR42p1UTU/CQBDl//8DL15MPHjDs4kXA+FDDEEUqRJpKbSUGmi3UPr1nJnQJnwIyCSTXXa2b97Om6EEsizLxC+x3W9L+SYMQ3iehyiKsFwu4bqu/PZ9X1alFObzORaLRXE2nU6xWq22EgggAxiGgXa7jVarJV6pVNBoNFCv11Gr1YrzarUqK9/lmKZpAp6zFcAkScTX6zUGg4GAj8djmKYpzme7e77D9/lF/O3ekw9ZnihNU8RxfFZNS4eKzMbZmYXjzGDbNjGeIN4wOSZi6Zh6rjvD6LMP31OYWAbC1fL/DNnSTfZXUvb++gqZqWM07WBByl/EkGtGqFjT3mm2kcx+MDQ1WBMzp3+wTCdFyQg4V9H3FVQQbOobST/GFDtDlBR2rwvf+C6UVtTIOQs1HsH50qimE2mh4XAoQ5DHC8CcNDfH3dMj7HIZaZyQyo5MhGVZovpDr4O32xsgipEQCE+NrusyRduAmww+CfHS6ULvf241LMcDijXfP9CpPSPeGbk/n8wgIV3m2u0qyX3oUe1UoIqe3BMloEKz8zzzyg198p/nSPgX6IeK8ZedD2MAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-12.png&quot;
        title=&quot;&quot;
        src=&quot;/static/7cd76d4a83318945a28e1740f4d9c53e/30373/2023-11-06-replication-12.png&quot;
        srcset=&quot;/static/7cd76d4a83318945a28e1740f4d9c53e/0eb09/2023-11-06-replication-12.png 500w,
/static/7cd76d4a83318945a28e1740f4d9c53e/1263b/2023-11-06-replication-12.png 1000w,
/static/7cd76d4a83318945a28e1740f4d9c53e/30373/2023-11-06-replication-12.png 1256w&quot;
        sizes=&quot;(max-width: 1256px) 100vw, 1256px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1256px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/52799777b457bbebfe56c1889cb9c5a9/30373/2023-11-06-replication-13.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 83.39999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAABYlAAAWJQFJUiTwAAACiUlEQVR42p1U53LaYBDk/Z8jT5BknMy4BbANwhiMTQdThcAUiaJCkbS5/WLJMHb+WMxx0ne61d1eSeCTKwxDfPVK8C8IAmw2GziOg/V6je12C9d11bPneeqemuf7/T62Ree2bcdBxID9fh+lpydks1lkNQ1aLod0Oo1isYh8Po+cPJdKJaRvbpTt9vYWyWRS2XRdfweMbvThEKXHRwwHA1QrFdSrVdREU9qtFpr1OjrtNmpy3u10UCmX8fz8jIrYl8tlTFUiyn3n+/AOB9i7HR4JZBhoWSa6loWeONTmM7QWC3SWFtrmAiOhZrlaYT6fq5RjDiP61wK0En5M4Wkm/ExEZp4r4mEqms+vFOHNED13Hejy0apEPJlMToviS3RMOXN3h19nZ0gJN3+ur5EWfXlxgeurK9ykUirtsvCcFw6pC4UCGo0GxuPxKaBKWSLsdrvovLygXqvBGI3QbDYVf4o3sQ3koy+0CzCLSDBqFvUIMMDhsEcYhNiKwREevUhL5O6bPhw5fezboz7c+2vogwpW5hKGkN42xhhOp5hJXy63HkzXxlxId/Y75Rl1xbHmL0JN+IEP0+oLuU/IaTnhR0Ph4QErqeC/Hg1PJogNTRsHge1ib6TCvoJ85/AgadWEt0wmgzuR+/t72OJYrhbQO/8uDoFyICCnhVXt9XqYSSZrqbZ29RNuuXgKSKI5DZpMCbvfkT477/RQ+vEb4cLETPqRQBRD2oXAvDbi+y2ZxuIy9RGQkRGMIzaVrw9Z2ZEO922GCcJuoKi5Fj+LvDfq0F8n74B8mYCcW4JRLIlI8faf6rJV2L/qnaNSx8uBWyPaKBRGHX5S1c9WHf3j5cCqcRa5iiIh2Ff34V+vOQfXfgTFUAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-13.png&quot;
        title=&quot;&quot;
        src=&quot;/static/52799777b457bbebfe56c1889cb9c5a9/30373/2023-11-06-replication-13.png&quot;
        srcset=&quot;/static/52799777b457bbebfe56c1889cb9c5a9/0eb09/2023-11-06-replication-13.png 500w,
/static/52799777b457bbebfe56c1889cb9c5a9/1263b/2023-11-06-replication-13.png 1000w,
/static/52799777b457bbebfe56c1889cb9c5a9/30373/2023-11-06-replication-13.png 1256w&quot;
        sizes=&quot;(max-width: 1256px) 100vw, 1256px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1256px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f6f0020bcfc994df77d627836b38210e/30373/2023-11-06-replication-14.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 67.80000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB6ElEQVR42q1UW2+iUBD2//+DfetrUx8as8btbqJuytYLBqPR4i2oQUERxY0KiuDXmZMeIq1960mOc2Hm85sLZPDNJ8M/l8sF5/MZ2+0WlmXBtm2Yponlcilsd+ViPp8LfTabCblYLETM8XgU+SlAfqDrOlS1gVq9jmZTQ61WQ530SqWCBtkNTRP233IZv55+46XyAo18qqqKfElMAMZxDN8PYM9MBMQyfvdFxJr/OzAt+M5KJK3Xa7hzE1EYJpVxbLrkd8PwNijkHxBZZhIwdWwodz8QDoeQsX+aKoznJ1zn3gRckaY+/kT8qie+4WGPuvIPsB1IHtp4AvP+4euhyHP0fUxHI3jb/4lvSQPQu10sHCdhNCW2nVZLDFH27iZgRL0IaGpRFCWXp3g6nRDS5V5xz04h2dRD9l+DpdaGz2azwXg8Rq/XQ7/fR7vdRqfTEfaImLNkX4vY8epMJhPs9/sUy8zHHvAD13VFObyPPFVO5p1k3fM84T8cDp/KvQnIh8thAAZlprxvg8EgAeWl/giUAuSAarWKLjWfS+RlzeVyyGazKJVKKBQKUBQF+XwexWIRZVpuwzDE28OSGacAmVEQBGII3HQehFhsspmJ1OVQZNy1nQDudjt6S3wBwvLW5KTNUt6vPg5vjP8qrfxGoMsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-14.png&quot;
        title=&quot;&quot;
        src=&quot;/static/f6f0020bcfc994df77d627836b38210e/30373/2023-11-06-replication-14.png&quot;
        srcset=&quot;/static/f6f0020bcfc994df77d627836b38210e/0eb09/2023-11-06-replication-14.png 500w,
/static/f6f0020bcfc994df77d627836b38210e/1263b/2023-11-06-replication-14.png 1000w,
/static/f6f0020bcfc994df77d627836b38210e/30373/2023-11-06-replication-14.png 1256w&quot;
        sizes=&quot;(max-width: 1256px) 100vw, 1256px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1256px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a6bd43b6dd1693e95c132b6d8627db5c/30373/2023-11-06-replication-15.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52.800000000000004%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABcklEQVR42pVT2W6CUBD1//+iD/2BPjVpSUw0rS0WqShgQKVuCdoosst2OvcGGlRS05NMZu42y5m5LdQQRRFOpxOSJEGaplzXbabjOOY20+zuJVqVURQFFosFJEmCruuQZRlvvR663S7a7TYEQUCn04EoilAUBYZhYLfb8XeNDiv4vs9lf9jDDXz+aLPZYLVawbZtHI9HOI7Dq2nCmcOzaFmBwDBR5PnvWf38ct2cYXUhDPHy+AD9/o4vXc+DqqoYDAY826vgtxwm9g7PTwK+JZn2gAOVOBwOYRHH2+32Hw5LpFTmbDrlxEfUTYacHGRl+X/hikM2DlEQwHNdBKRDP0BEFMTUBD5WdB5Q00Lau8lhnmVYE0cSjc2IMpyt1xDVMT40FbplQZroUKw5Xvt9TMhuzJBFyctSnMMBo/EY70S+TFrRNPRoLpn9SaKZJszlEvPFF6YUMC7pOHPoUQfZ3LHyKjuln8CCMMko67rU95tK/gETM05uIJqwQwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-15.png&quot;
        title=&quot;&quot;
        src=&quot;/static/a6bd43b6dd1693e95c132b6d8627db5c/30373/2023-11-06-replication-15.png&quot;
        srcset=&quot;/static/a6bd43b6dd1693e95c132b6d8627db5c/0eb09/2023-11-06-replication-15.png 500w,
/static/a6bd43b6dd1693e95c132b6d8627db5c/1263b/2023-11-06-replication-15.png 1000w,
/static/a6bd43b6dd1693e95c132b6d8627db5c/30373/2023-11-06-replication-15.png 1256w&quot;
        sizes=&quot;(max-width: 1256px) 100vw, 1256px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;결국 또다시 반성문을 쓰게 된 테코.
바이너리 로그 파일 위치 기반 복제의 문제점을 몸소 깨닫게 되었네요.&lt;/p&gt;
&lt;h2&gt;2. 바이너리 로그 이름을 통일해서 사용하자&lt;/h2&gt;
&lt;p&gt;’&lt;strong&gt;특정 서버에서만 식별할 수 있는 이름과 위치 정보는 다른 서버에서 이용할 수 없다.&lt;/strong&gt;’&lt;/p&gt;
&lt;p&gt;뼈아픈 깨달음을 얻은 테코는
리플리카 서버에서 소스 서버의 이벤트를 읽어올 더 나은 방법을 생각해 내야 했어요.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1256px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2dde6517af686c2167710dee04b28bbb/30373/2023-11-06-replication-16.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB3UlEQVR42m1T2XLaQBDk/38jH+AnkhDghauK+w4gmxJIiEPcui9oz4xNLNvZqmHZ2dne7e5RBqnheR7O5zNOp5PMx+MR1+sVl8sFpmlKzrZtWXPNfr9HGIZpCGT4536/y2Kz2aDVaqHf72M0GqHRaKDX66HT6cjM+Xa7LVGv1yV2ux3SGJk0OicZlMNYr2AYBlarFXRdh6Zp2G630JZLyutYLBay/joEMIh8HPcaUbHgOM6/23yiE8WJ/I+TBAnl7+8H+Yx1Naneg+s6qRe+V+i6inK5IrTWBxPtfBYevSS43WDTAdt1ERLo1bGgVP/ANQ08qwqdKWM4HCBJ3i7OPJCZXrVaxWgwwNyx8fP3LwTjCTza35ExR59f4kEhY348PSHuDrAgaaqViuj9DZDFHRDY88sLoiiSuDFN2ouoJqa4MfU4ElliksJYr8UwRVE+ANOCMgi3gWVZYgI72+120SA3W80mmuQ6Ozse/xWAMAik/gH2rW3WdCNrWKvVUCwWRZ9cLodCoYBSqYRsNot8Pi+zqqoYjcdYkuufXH6A3Uh8l4Tnpp3P55hOpwI+HA4lWI7JZCJ7THM2m+FwOEjjfwJkENaEZ/5SfN9HQFSYxiPiOH7TlC7lB/Ca43/jFXrRObuqZqNJAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-16.png&quot;
        title=&quot;&quot;
        src=&quot;/static/2dde6517af686c2167710dee04b28bbb/30373/2023-11-06-replication-16.png&quot;
        srcset=&quot;/static/2dde6517af686c2167710dee04b28bbb/0eb09/2023-11-06-replication-16.png 500w,
/static/2dde6517af686c2167710dee04b28bbb/1263b/2023-11-06-replication-16.png 1000w,
/static/2dde6517af686c2167710dee04b28bbb/30373/2023-11-06-replication-16.png 1256w&quot;
        sizes=&quot;(max-width: 1256px) 100vw, 1256px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;놀랍게도, 5.5 버전 이후의 MySQL은 이 아이디어를 실현하여,
&lt;strong&gt;글로벌 트랜잭션 아이디(Global Transaction Identifier, GTID)&lt;/strong&gt; 를 이용한 복제 방식을 제공하고 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1256px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/14de953f6945bf003b482d3234197149/30373/2023-11-06-replication-17.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABEElEQVR42o2SW0uEUBSF/cP+IJ+FXoR8CbXxxR4McTAZsDAvyCRKkELmBcfbajw4UWqDCw6bs9l8e+91DoWZhmHAVq3VUmtFdV0jSRIEQYDj8QjP8+D7PhzHgeu6SNMUTdOsNqHmsBEgyzLk3Q73oghREMDzPCRJAsdxEM53RVGgqiqiKFpMuQCGYQhd16Hv93i2X/F0OEDTNBiGQaJt27AsC6ZpIo5j9H1/feVR/Rnc1Q2+nBe0H+84tS3Jj5MxDEMg/3m9AF465nWJm7tbVA+POE1+sSwLmqbRdd32R7mo6Xq8fWbI8vwnl2UZeaz5mpuAk6t/phhB19YlwLIsURQFxlhVFTnt5Nmw8e/9Bn4DNHOyWJ9mcxAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-17.png&quot;
        title=&quot;&quot;
        src=&quot;/static/14de953f6945bf003b482d3234197149/30373/2023-11-06-replication-17.png&quot;
        srcset=&quot;/static/14de953f6945bf003b482d3234197149/0eb09/2023-11-06-replication-17.png 500w,
/static/14de953f6945bf003b482d3234197149/1263b/2023-11-06-replication-17.png 1000w,
/static/14de953f6945bf003b482d3234197149/30373/2023-11-06-replication-17.png 1256w&quot;
        sizes=&quot;(max-width: 1256px) 100vw, 1256px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1256px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2d58e0861877d79b4299bee5aa8b05fb/30373/2023-11-06-replication-18.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 77%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAACKklEQVR42o1U2XLaQBDk//8leUgqT37CVWDu+7AFIgJhQNygEwmpPb2OVDY+Kls1jPaY3pmeXnKQkSSJsjiOOUUYhnAcB4fDAbZt43Q6Kc81z/NwPB5xPp/xNpbGkcPNCIIAs9kM4/EYrVYLnU5Hec6bzSZ0XUepVEKtVsPlcrkNR47Ivu+rGzebDXa7HdbrNbbbrZrv93tl6ZyeZ+l5ltlzn1lngK7rqg0CjUYjDAYD9Ho9lRF9t9vF43CIarWKoXhm3e/3oWmaAiI1jM9KFgaEv2uWtuJTKLler4iiSHHLNc75HSev5zn/UDJ/4jjCYjGG8dfAVPgj8YkfwBEqfOEpEgD1Lc2KE2lcFMMNXZjPusQtsVqtMvBc2h1yUiwWUalUsBBOfv/6iWO1jFD2HKEkELBQgva+h7s/P+D2OjCtFQqFAhqNhlLGB0BulqWDK/uMh7EGr/+IUPZ3kvEh8GFLlkuRz127iaA3hLFY4D6fVx3PANPaqTHTNDGfzxFIYEL+hC9m5QmPqV0uATzbUevs8mQyUXHk+lMd3o5EQMmpJ2XjXzXfjXeATHs6nWK5XMIwDHU75VEul5GX0tI1NsGyrKyBXwJSEhR5+vSoN1q9Xs9K056eVLahdJ9n0+f6KSAPMJhGUVPQ7XZbGdf6IvihiPlBlKDJE6R9myFLZXkEoBT4ZikjghdFAQR7llehS7nmYQ/Dei39HSBv4NOj5x8Du5X8B/lfNeUFr+RyuFnL5TMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-18.png&quot;
        title=&quot;&quot;
        src=&quot;/static/2d58e0861877d79b4299bee5aa8b05fb/30373/2023-11-06-replication-18.png&quot;
        srcset=&quot;/static/2d58e0861877d79b4299bee5aa8b05fb/0eb09/2023-11-06-replication-18.png 500w,
/static/2d58e0861877d79b4299bee5aa8b05fb/1263b/2023-11-06-replication-18.png 1000w,
/static/2d58e0861877d79b4299bee5aa8b05fb/30373/2023-11-06-replication-18.png 1256w&quot;
        sizes=&quot;(max-width: 1256px) 100vw, 1256px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1256px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7796f17f52e3c55f18dd9cff81de267f/30373/2023-11-06-replication-19.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 43.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABJUlEQVR42p1Ra0/CQBDs//9BfjKoGGh4WCw0pi0gMYHUaKtN6JU+7m6cu4hBIiS46WRvbx+3M3VwwvSJO43z5pxLNlLhJc8RZxnCNIVUCvjPwFpKpEJYPL4mFsPNGm+iwDvvGuYvGqjIS7QtaiXRcKtGa+tLDjJQjKH/Ju8URYGctIrtFoLnRRxjHkVINhvM4wjPiwWC6RRxGGK1XNr4KQgQs2ZXlhamX3BzOzBioe/76I5GuB+PcXV3i2vXteeO28dwNkOn10N3MMAN4773wNwI7sSDz94J4Xkelnzsh7JZviBFQ6citZ3xRE1aSfaB1Zr65Z+MYXOlbL/rWogjPR1pNGHSQB/qYnXix/uWj6n9Hz6uMZof9FoNDYwGxldV9XvwhfYFOYC2t0YJ+00AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-19.png&quot;
        title=&quot;&quot;
        src=&quot;/static/7796f17f52e3c55f18dd9cff81de267f/30373/2023-11-06-replication-19.png&quot;
        srcset=&quot;/static/7796f17f52e3c55f18dd9cff81de267f/0eb09/2023-11-06-replication-19.png 500w,
/static/7796f17f52e3c55f18dd9cff81de267f/1263b/2023-11-06-replication-19.png 1000w,
/static/7796f17f52e3c55f18dd9cff81de267f/30373/2023-11-06-replication-19.png 1256w&quot;
        sizes=&quot;(max-width: 1256px) 100vw, 1256px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;GTID 기반 복제의 효과&lt;/h3&gt;
&lt;p&gt;어느날 우테코 컬렉션이 또 출시되어
소스 데이터베이스 서버가 또 고장났어요!
과연 GTID 기반 복제를 적용한 지금은 장애에 잘 대처할 수 있을까요?&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1302px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/50a1b84381f08ceefa77dbab933ecbf7/3b5a6/2023-11-06-replication-20.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 102%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAABYlAAAWJQFJUiTwAAACQ0lEQVR42q1U2ZLaMBDk/78llefkiU0IBct9Q8FyGBNzLdjY5rDB7p0W65SXhUCqIpiSZI16ei4l8D7CMMT/GIk4YBAEat5sNhgMBhgOh+h2uxiNRphMJlgul5jNZur7drtVuqfT6QOZRARm2zbm87m6yAutVksBNZtNBUoDnCntdhuapikD1FmtVp8ZHo9HHA4HNdMq2XLd7/eh6zqm0ykMw1A6PPc8T60jiVgmuKAyLXU6HfR6PWWdTMiyVqv9YVkul9WeevV6Xe0bjYb6tlgszoC0xg3jZlkW1us1HMc9700TpuxNma2YmJYpeiulbzu2usM1ySUusxSG4s5+i613hCvGtiL74ART3HIlBEEYiBL/4e0sEzmKgec70EYVaLqBF32Ml/EYmvEbU2ExJ3vvgMLTN7jN2juB4MP9zwzlZ20MlEt5ZDNZpFMplIpF5fpasrqUcvnylIT541fk0vU6PIOdh+f5KFeqyOcFNJtFqVTCbrdTZxuJlS5l9SoG/lrYcWMsiUqlglwuh0wmg6IwJKAvpeT6vorr3U6Jtx8Bq9UqCoUCnp+fVXm44ioTY7MOg9PNVr0JGNUZ3ea83+//rZcvAelynGEUQ5XRK8m4C0iQKIbxpIBl4h8fczkavgSeDOkuARlPdsNMWtQVY5nkd9jF3NVn7yYgWdHddDqt1rbjSAftYUlhf039xCyZug8YHfL1YFLIjEy55vvHwV4ej4aYr14fd5kPBptdPQoyU/iUPZRllgMDHpd7l+O9ewn4BkLK/VcdIVUhAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-20.png&quot;
        title=&quot;&quot;
        src=&quot;/static/50a1b84381f08ceefa77dbab933ecbf7/3b5a6/2023-11-06-replication-20.png&quot;
        srcset=&quot;/static/50a1b84381f08ceefa77dbab933ecbf7/0eb09/2023-11-06-replication-20.png 500w,
/static/50a1b84381f08ceefa77dbab933ecbf7/1263b/2023-11-06-replication-20.png 1000w,
/static/50a1b84381f08ceefa77dbab933ecbf7/3b5a6/2023-11-06-replication-20.png 1302w&quot;
        sizes=&quot;(max-width: 1302px) 100vw, 1302px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1302px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/cf5f69825cd5fbbc2b0c4a4c0eda87d9/3b5a6/2023-11-06-replication-21.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 73.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAABuUlEQVR42q1UW0/CUAz2//8GX3wy+gNMSIwvPhAQXAQkogsI7O7YGLtw2TifbcMUBC8kdjs757Q9bc/Xdif4ZzopF6vVCkEQYLFYIE1T+L6POI4RhiGiKEKSJLJmndlsJjzXdUWfSSn1aTDLMliWhWazCU3T0Gq1UKvV0Gg0UK1WUa/X0el0RM77Uofluq6L450I1+u1jPl8jn6/D8dxYNs2TNOEYRgYDAbwPE/2vGbnzC+KAnmey7x35UO02ijzYEiOwrCkEgvGZjwaIZxGsB2O1hLZ9viTwW1y3zwYeg9pMicYRuQkOz5CwXTjXQtCXJ2dQpGxsdvGjPblLY6KUKk1f7CkefLQRT4JMDR0eK5dKmzpYsf4j1dWlPkyMUmcSnkxhQSB5Uci+z0p9JiPLSTGUPYFHZI6U/IKf/ys4/7VR/XJgKZbML2AjBeHDTL74vYGQaWCgjLtBxPpDotrkurvuvuA58tzUqS6LRScaYZaj2QU8Y7BEoeI2kprdzDsveyVE0d61+3isaEh37TcNpaHIyS8lqS8pEL+mknuipj7OkuRb7pjLyn8I+DBgPP8U0d8HPymZNjgOx47iqRQ1LIDAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-21.png&quot;
        title=&quot;&quot;
        src=&quot;/static/cf5f69825cd5fbbc2b0c4a4c0eda87d9/3b5a6/2023-11-06-replication-21.png&quot;
        srcset=&quot;/static/cf5f69825cd5fbbc2b0c4a4c0eda87d9/0eb09/2023-11-06-replication-21.png 500w,
/static/cf5f69825cd5fbbc2b0c4a4c0eda87d9/1263b/2023-11-06-replication-21.png 1000w,
/static/cf5f69825cd5fbbc2b0c4a4c0eda87d9/3b5a6/2023-11-06-replication-21.png 1302w&quot;
        sizes=&quot;(max-width: 1302px) 100vw, 1302px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1302px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/13608f0e0582de66ed389e23c8c55d5e/3b5a6/2023-11-06-replication-22.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 94.39999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAABYlAAAWJQFJUiTwAAACkklEQVR42p1U227aQBDl/78kD5Xapz6lUnpJIIAhwWAwlxJDCGCKr2AbbHN6ZpUQQkCNijTaWe9y5szMmS3gg7/dbvehe4XDzWazge/78DwPQRDAdV3ly7f1eq324svZcrlUfpZl5wHl0mw2Q7/fR6vVQrFYRLVaRblcRqVSQb1eh6Zpat/pdDCZTBT4SUCJJBc8YUWbz+dYLBZqlSB/6LuOg5FlwZYz21bnksFJQJsXemTWJLNGs4mmYcBot9Ht9dQqe5P+8OFBrTrvGaapmErAd4BpmiJcrTBm1B6Z6o9j6E8TtKdPMKZT/GzqqBLkWm/AeLDQI0iX3wP+J4nj0zVU3XxenTCEJeky/QWLb43HGDIDk2wl/XOdLxwDRdstfJoxGOCqVII5HOJXTYPONDWCVXQdv8nMjtbYHHV4DyiRJJhNaYx8Dz12u8sGWGGAgeeiLXuX+8Dn3kOffos1nzELJ4rOMCRinufP4O8tz2m7V9sd+EeAOYu6RpbmiLMUDgscMuVARE7fU5YgYdPOBcMBaCHNY1ije9izOUasTYc6G7ABCwo2JIiXJAho2zw7PZIQStiDFhgDq7WNRkND8aaI0s01itc3aqyQbJA9S0JkJUKeMuiYAZ8oLWs0gk1Z5eGKqeevNZTaGQY7WK2gzBErsbsyu9/vNXQ+XRAtUyxigjts1pCdt5iJ57gYO0t8/XyBjN0/AMyU4mVW63d3qNVqavx+mG3o366IlCCKI6Txat+8Leucss6PVMGXy0t4d/pbhk2OmzCTB+H29laNok82DmWU8jwlgEc/pFRWnA5hK2UIKKOlzDS/vQGUNLrdrjKTM/ryinz0HTypw7Oy+MfDe3ivEFHpLyZpiGVZhv/9/QWsN6y+1UjHXQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-22.png&quot;
        title=&quot;&quot;
        src=&quot;/static/13608f0e0582de66ed389e23c8c55d5e/3b5a6/2023-11-06-replication-22.png&quot;
        srcset=&quot;/static/13608f0e0582de66ed389e23c8c55d5e/0eb09/2023-11-06-replication-22.png 500w,
/static/13608f0e0582de66ed389e23c8c55d5e/1263b/2023-11-06-replication-22.png 1000w,
/static/13608f0e0582de66ed389e23c8c55d5e/3b5a6/2023-11-06-replication-22.png 1302w&quot;
        sizes=&quot;(max-width: 1302px) 100vw, 1302px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;와우!
바이너리 로그 파일 위치 기반 복제와 다르게
소스 서버의 GTID를 리플리카 서버에서도 이용할 수 있게 됨으로써
리플리카 서버 A로부터 리플리카 서버 B로의 데이터 동기화가 가능하게 되었어요.&lt;/p&gt;
&lt;p&gt;만세를 부르는 테코!&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1302px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7b02137e90cc7653a97922aca7bd03e0/3b5a6/2023-11-06-replication-23.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 58.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABy0lEQVR42o1T2W7aUBDNn/WX+j19qJTnvvFQKR8QJDCboAQaCGvCYhsvYGPA2KdzxtgFKlUZ6XrOvTP33Nn8AJEgCOC6bqE9z1O83W6x2+108cxxHPi+rzZi2rlOpxNyeeCHJOv1Wp2Wy6Xi1WqF+Xyu2jRNxePxWPVms8FisYBt27AsC/v9/pYwjmONIAxDRFGkeDQaYTqb4W04xMf7OyaTCfr9PgaDgZLlfoyW928I6WQYBrrdLmq1THPf673gdTDEr5c+Op0Oms0m2u02KpUKyuUyWq0W6vU6ZvIwJU3TjJA1OJ/PomPFfPF4POIsDoFtwf7dxfEQIRYf2vKVJIku3r2J8F6YwS5MFFeXC/x8/A5M5khpk4eYbnQ4wJeUmXZewyJCSpKk8IOD4jACNp6cycNmAEynIUIns7GrzUZDyYZSqlKpJKXp/SXkh2JaHr58/YGpF9OCJDvGxSz7DLA5RrWqk1AVXX5+LshuUmb9jFcTUZxqzukljdh3Ec5GQpiVgKPSkAjZEDYzJ/pvDTWiy7DWVh94evwmeWc1xB3BvRSE6VVa16J/xdsYcFz1YUTX6x9CDjMvsVPEB+le4UgtHYWMDOITPiN/AF5dlnPy91QoAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-11-06-replication-23.png&quot;
        title=&quot;&quot;
        src=&quot;/static/7b02137e90cc7653a97922aca7bd03e0/3b5a6/2023-11-06-replication-23.png&quot;
        srcset=&quot;/static/7b02137e90cc7653a97922aca7bd03e0/0eb09/2023-11-06-replication-23.png 500w,
/static/7b02137e90cc7653a97922aca7bd03e0/1263b/2023-11-06-replication-23.png 1000w,
/static/7b02137e90cc7653a97922aca7bd03e0/3b5a6/2023-11-06-replication-23.png 1302w&quot;
        sizes=&quot;(max-width: 1302px) 100vw, 1302px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;요약&lt;/h3&gt;
&lt;p&gt;MySQL 리플리케이션의 복제 타입&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;바이너리 로그 파일 위치 기반 복제&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;글로벌 트랜잭션 아이디(GTID) 기반 복제&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;자료
Real MySQL 8.0 2권 pp.434~445&lt;/p&gt;
&lt;hr&gt;</content:encoded></item><item><title><![CDATA[로컬에서도 프로덕션과 유사한 환경에서 테스트할 수 없을까? : Testcontainers 도입기]]></title><description><![CDATA[우아한테크코스 레벨3과 레벨…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-11-06-testcontainers/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-11-06-testcontainers/</guid><pubDate>Tue, 21 Nov 2023 20:00:00 GMT</pubDate><content:encoded>&lt;p&gt;우아한테크코스 레벨3과 레벨4 동안 프로젝트를 진행하면서, 로컬 테스트 환경과 실제 운영 환경이 다른 기술적 어려움을 겪었습니다.&lt;br&gt;
해당 어려움으로 비롯된 문제 상황과 단계적 해결 방안, 그리고 최종 해결 방안으로 도입한 &lt;code class=&quot;language-text&quot;&gt;Testcontainers&lt;/code&gt;에 대한 소개와 사용법을 공유하려 합니다.&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;문제 상황 : 로컬에서는 H2, 프로덕션에서는 MySQL&lt;/h3&gt;
&lt;p&gt;Spring Boot를 사용하여 프로젝트를 진행했습니다. 로컬 테스트 시에는 Spring Boot에 embedded 되어있는 H2를 사용했습니다. 개발 서버와 운영 서버에서는 MySQL을 사용했습니다. 그리고, Flyway를 통해 DB Schema 관리를 했습니다.&lt;/p&gt;
&lt;p&gt;개발을 진행하며, 사용자분들의 니즈에 맞춰 프로젝트의 요구사항들이 추가되었습니다. 또, 리팩터링을 하며 패키지를 컨텍스트별로 분리했습니다.&lt;br&gt;
이러한 과정 중에 외래키와 unique 제약조건을 해제하거나 칼럼을 변경해야 하는 상황이 있었습니다. &lt;br&gt;
이 때 H2와 MySQL의 문법이 달라서, MySQL 문법으로 작성되어있는 Flyway용 migration File 내의 sql문을 H2에서 사용하지 못했습니다. 그래서 &lt;strong&gt;H2를 사용하는 로컬 profile로는 빌드가 불가능&lt;/strong&gt;한 문제가 있었습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ Wix에서 만든 embedded MySQL 오픈소스를 사용하면 간단하게 의존성을 추가함으로써 테스트 시에 경량화된 MySQL을 사용할 수 있지만,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;벤더가 변경될 시 해당 데이터베이스의 embedded 라이브러리가 없다면 같은 문제가 발생&lt;/li&gt;
&lt;li&gt;embedded MySQL을 사용하기 위해 추가적인 소스코드 생성 필요&lt;/li&gt;
&lt;li&gt;Window 환경에서 MySQL 8.0 이상을 지원하지 않음&lt;/li&gt;
&lt;li&gt;추후에 이야기 할, 다른 리소스(AWS S3)에 대한 의존성도 해결하고 싶었음&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위와 같은 문제로 해당 방법으로는 문제를 해결하지 않았습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br&gt;
&lt;h3&gt;1차 해결 방안 : 로컬에서 Flyway를 사용하지 않는 방법&lt;/h3&gt;
&lt;p&gt;우선 팀에서 도출한 해결방안은 ’&lt;strong&gt;Flyway를 로컬에서 사용하지 말자!&lt;/strong&gt;‘였습니다.
처음에 저는 아래와 같은 이유로 반대했습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Flyway의 migration File이 제대로 작동하는지 로컬에서 확인할 수 없다.&lt;/li&gt;
&lt;li&gt;도메인에 변경이 있을 때마다 테스트를 위한 스키마를 다시 수정해야한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그러나 다른 할 일들이 많이 남았는데도 서비스 배포까지 시간이 얼마 남지 않았고(&lt;del&gt;하루 전이었던 건 안 비밀…&lt;/del&gt;), 해당 방법이 가장 간단하므로 적용하자는 의견에 동의하였습니다.&lt;br&gt;
무사히 배포를 했지만, 추후 개발을 진행하며 우려했던 상황이 발생했습니다.
매번 테이블의 변경 사항이 있을 때마다 Flyway의 migration file에 ddl도 작성하고, 로컬 테스트 용 스키마도 매번 수정하는 공수가 추가로 들어서 더욱더 개발 리소스가 많이 들었습니다. 그리고 가장 큰 문제는 배포하기 전까지 Flyway가 제대로 작동하는지 알 수 없다는 점이었습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;2차 해결 방안 : 벤더 별로 Flyway Migration File 각각 작성&lt;/h3&gt;
&lt;p&gt;Flyway를 H2와 MySQL에 동시에 사용할 수 있는 방법을 구글링하고 공식 문서를 읽어본 결과, &lt;strong&gt;각 벤더사 별로 패키지를 따로 두면&lt;/strong&gt; 각각에 맞는 패키지에서 migration file을 읽는다는 것을 알게 됐습니다. 그래서 H2용과 MySQL용 패키지를 따로 두는 방법으로 해결했습니다.&lt;br&gt;
이제 배포를 하기 전에 Flyway가 제대로 작동하는지를 H2 한정으로는 확인할 수 있게 됐습니다. 다만 아직도 H2가 아닌 MySQL 환경에서 Flyway가 제대로 작동하는지는 확신할 수 없다는 점, 그리고 ddl을 두 가지 버전으로 짜기 때문에 공수가 든다는 점이 문제로 남아있었습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;3차 해결 방안 : Docker를 띄워볼까?&lt;/h3&gt;
&lt;p&gt;&lt;del&gt;ddl을 매번 두 개씩 짜다가 지친&lt;/del&gt; 팀에서 더 좋은 방법을 협의하게 되었고, docker등을 사용하여 &lt;strong&gt;컨테이너로 MySQL 환경을 로컬에 구축&lt;/strong&gt;하자고 의견을 냈습니다. 해당 방법은 실제 운영환경과 유사한 환경에서 테스트를 진행할 수 있게 되는 장점이 있습니다. 그러나 저를 비롯한 팀원들이 docker를 거의 사용해보지 않아서, 데모데이가 얼마 남지 않은 상황에서 학습 비용을 고려해야 한다는 의견이 있었습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;최종 해결 방안 : Testcontainers를 사용하자&lt;/h3&gt;
&lt;p&gt;팀원들의 학습 비용도 줄이고(혹은 아예 없애고), 운영환경과 유사한 환경에서 테스트를 진행할 수 있는 방법을 찾기 위해 3~4일여간 구글링을 열심히 했습니다. 또, 다른 팀들의 코드와 블로그도 많이 찾아봤습니다. 그 결과, &lt;strong&gt;Testcontainers&lt;/strong&gt;라는 새로운 기술을 찾아냈습니다.&lt;/p&gt;
&lt;p&gt;공식문서와 StackOverFlow를 열심히 뒤지며 사용법을 알아냈습니다. 설정 파일에서 단 두 줄, 혹은 테스트에서 클래스 하나 정도만 추가하면 (docker가 실행되고 있다는 가정하에) &lt;strong&gt;자동으로 컨테이너를 띄워주면서&lt;/strong&gt; 테스트를 실행할 수 있었습니다. 해당 기술을 사용해서 팀원들의 학습 비용도 줄이고 더욱 실제 운영환경과 유사한 환경에서 테스트를 진행할 수 있게 되었습니다.&lt;br&gt;
이제 그 혁신적인 라이브러리인 &lt;code class=&quot;language-text&quot;&gt;Testcontainers&lt;/code&gt;를 소개하겠습니다.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;br&gt;
&lt;h1&gt;Testcontainers&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/TawF6JD.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Testcontainers의 공식 사이트에서는 다음과 같이 소개하고 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Testcontainers is a library that provides easy and lightweight APIs for bootstrapping local development and test dependencies with real services wrapped in Docker containers. Using Testcontainers, you can write tests that depend on the same services you use in production without mocks or in-memory services.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;즉, 도커 컨테이너로 래핑된 실제 서비스를 제공해서, 로컬 테스트 시에도 mocking이나 in-memory 서비스들을 사용하지 않고 운영환경에서 사용하는 실제 서비스에 종속되는 테스트를 작성할 수 있게 해주는 오픈 소스 라이브러리입니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;Testcontainers의 장점&lt;/h2&gt;
&lt;p&gt;&lt;del&gt;프로젝트의 아키텍쳐는 매우 간단해서&lt;/del&gt;, Testcontainers의 사용 효과를 극적으로 설명하기 위해 공식 사이트에서 소개하는 예시를 가져와봤습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/JdtwWLL.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;위의 도식에서 볼 수 있듯이, 현재 &lt;code class=&quot;language-text&quot;&gt;My Service&lt;/code&gt;는 데이터베이스, 다른 서비스, 이벤트/메세지 브로커, AWS S3 같은 다른 클라우드 서비스에 의존성을 가지고 있습니다. 여기서 &lt;strong&gt;다음과 같은 문제&lt;/strong&gt;들이 발생합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;테스트를 실행하기 전에 인프라가 가동 중이고, 원하는 상태로 미리 구성되어 있는지 확인해야합니다.&lt;/li&gt;
&lt;li&gt;데이터베이스, 이벤트/메세지 브로커 등 여러 사용자 또는 CI 파이프라인에 걸쳐 공유되는 리소스의 경우, 데이터와 구성의 변경 가능성으로 인해 테스트 시 멱등성을 보장할 수 없습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ 멱등성이란?&lt;br&gt;
연산을 여러 번 적용하더라도 결과가 바뀌지 않는 성질을 말합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br&gt;
&lt;p&gt;그래서 그동안 프로젝트에서도 아래와 같이 해당 리소스들을 mocking하는 방법으로 테스트를 작성했었습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FileUploaderTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
  
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Mock&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FilePath&lt;/span&gt; filePath&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
      
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Mock&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FileUrlMaker&lt;/span&gt; fileUrlMaker&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
      
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@InjectMocks&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FileUploader&lt;/span&gt; fileUploader&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
      
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; 파일의_URL을_반환한다&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token comment&quot;&gt;// given  &lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;UUID&lt;/span&gt; randomUUID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;UUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; baseUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://example.com/files/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; expectedFileUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; baseUrl &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; randomUUID &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.jpg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        &lt;span class=&quot;token class-name&quot;&gt;MultipartFile&lt;/span&gt; multipartFile &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MultipartFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        &lt;span class=&quot;token function&quot;&gt;given&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;multipartFile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContentType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;willReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IMAGE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contentType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        &lt;span class=&quot;token function&quot;&gt;given&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileUrlMaker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;willReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expectedFileUrl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
          
        &lt;span class=&quot;token comment&quot;&gt;// when  &lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fileUploader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;upload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;multipartFile&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
          
        &lt;span class=&quot;token comment&quot;&gt;// then  &lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expectedFileUrl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;프로젝트 내에서 생성된 파일이 저장소에 업로드 된 후 그 주소를 반환하는 기능에 대한 테스트입니다. 기존에는 서버에 저장을 하다가 AWS S3로 변경이 되었는데, 이 경우 모두 로컬에서 테스트할 적절한 방법이 없어서 위와 같이 관련 클래스들을 전부 mocking할 수 밖에 없었습니다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/6gN7hWR.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;그러나 위처럼 Testcontainers를 사용하면, 서비스가 의존하는 리소스들을 컨테이너로 래핑하여 제공해줍니다. 이렇게 되면 위에서 이야기했던 실제 리소스에 의존하여 테스트시 정확성이 보장되지 않는 문제들을 해결할 수 있고, 반복적인 테스트가 가능해집니다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;여기까지 읽다보면 한 가지 의문이 들 수도 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;그냥 Docker나 Docker Compose 쓰면 안 되나?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;물론 Docker와 Testcontainers 모두 컨테이너를 사용하여 리소스를 래핑하는 것은 동일합니다. 그러나 Testcontainers를 사용하면 컨테이너의 설정과 사용이 혁신적으로 편리해집니다.&lt;/p&gt;
&lt;p&gt;이를 설명하기 위해 위에서 언급한 장점들을 포함하여, &lt;strong&gt;Testcontainers를 사용하여 얻는 장점들&lt;/strong&gt;을 아래에 정리해보겠습니다.&lt;/p&gt;
&lt;h4&gt;1. 격리된 인프라 제공&lt;/h4&gt;
&lt;p&gt;통합 테스트 시에 사용될 인프라 리소스들을 미리 준비하지 않아도 됩니다. Testcontainers를 사용하면 테스트를 실행하기 전에 자동으로 해당 리소스들을 제공합니다. 각 파이프라인이 격리된 서비스 집합으로 실행되므로 여러 빌드 파이프라인이 병렬로 실행되는 경우에도 테스트 데이터 오염이 발생하지 않습니다.&lt;/p&gt;
&lt;h4&gt;2. 로컬 및 CI 환경 모두에서 일관된 테스트&lt;/h4&gt;
&lt;p&gt;IDE에서 바로 통합 테스트를 실행할 수 있어서, 변경 사항을 push하고 CI 파이프라인이 완료될 때까지 기다릴 필요가 없습니다.&lt;/p&gt;
&lt;h4&gt;3. 대기 전략을 사용한 안정적인 테스트 설정&lt;/h4&gt;
&lt;p&gt;테스트를 실행하기 전에 컨테이너를 초기화해야합니다. Testcontainers를 사용하면 컨테이너와 그 안에 있는 어플리케이션이 완전히 초기화되었는지 확인하는 몇 가지 대기 전략을 제공합니다. 또, 모듈을 사용하여 해당 전략들을 직접 구현하거나 복합 전략을 생성할 수도 있습니다.&lt;/p&gt;
&lt;h4&gt;4. 고급 네트워킹 기능&lt;/h4&gt;
&lt;p&gt;Testcontainers를 사용하면 컨테이너의 포트를 사용 가능한 임의의 포트에 매핑하여 테스트가 해당 서비스에 안정적으로 연결되도록 해줍니다. 심지어 네트워크를 생성하고 여러 컨테이너를 함께 연결하여 각 컨테이너가 서로 통신하도록 할 수도 있습니다.&lt;/p&gt;
&lt;h4&gt;5. 자동 초기화&lt;/h4&gt;
&lt;p&gt;테스트 실행이 완료된 후, 생성된 모든 리소스(컨테이너, 볼륨, 네트워크 등)를 &lt;code class=&quot;language-text&quot;&gt;Ryuk Sidecar 컨테이너&lt;/code&gt;를 사용하여 자동으로 제거합니다. 필요한 컨테이너를 시작하는 동안 생성된 리소스에 일련의 라벨을 붙이고, Ryuk은 해당 라벨을 매칭하여 자동으로 리소스 정리를 수행해줍니다. 그래서 테스트가 비정상적으로 종료되더라도 안정적으로 작동합니다.&lt;/p&gt;
&lt;p&gt;즉, Docker와 Docker Compose를 사용하여 직접 명령어를 사용하거나 서비스 종속성을 초기화하는 등의 행위를 하려면 Docker에 대한 내부 지식과 컨테이너에서 특정 기술을 실행하는 방법에 대한 지식이 필요합니다. 이러한 부분에 대한 지식이 없다면, 포트가 충돌하거나 테스트 시작 시에 컨테이너가 초기화되지 않는 문제, 또는 상호작용할 준비가 되지 않는 등의 문제가 발생할 수 있습니다.&lt;br&gt;
Testcontainers를 사용하면 &lt;strong&gt;내부에서 이러한 설정을 전부 지원&lt;/strong&gt;해주므로 개발자는 API를 통해 이러한 지식 없이도 컨테이너 기반의 테스트를 사용할 수 있게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/Atw4AD9.jpg&quot; alt=&quot;&quot;&gt;
Testcontainers가 지원하는 DB 및 인프라 리소스들 목록입니다. 웬만한 건 다 있네요!&lt;/p&gt;
&lt;p&gt;Docker나 Docker Compose에 대한 학습 비용을 절감하면서도 컨테이너 기반 테스트를 사용할 수 있는 점이 저희 팀의 문제를 깔끔하게 해결하기 때문에 Testcontainers를 사용하기로 했습니다.&lt;br&gt;
또, 프로젝트가 커져가면서 인프라 리소스가 추가될 가능성이 매우 높았기 때문에 해당 리소스에 대한 테스트를 위해서도 Testcontainers를 사용하기로 했습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Testcontainers 사용법&lt;/h2&gt;
&lt;p&gt;MySQL 컨테이너를 띄우는 방법을 예시로 사용법을 설명하겠습니다.
다른 리소스(Kafka 등)에 대한 사용법은 참고 자료에 잇는 공식 문서에 자세하게 설명이 되어있습니다. MySQL 컨테이너를 띄우는 방법과 매우 유사하므로, 어렵지 않게 사용하실 수 있습니다.&lt;/p&gt;
&lt;p&gt;우선, 의존성을 추가해줍니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;testImplementation &quot;org.testcontainers:testcontainers:1.19.1&quot;  
testImplementation &apos;org.testcontainers:junit-jupiter:1.19.1&apos;  
testImplementation &apos;org.testcontainers:mysql&apos;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;JUnit과 MySQL에 대한 모듈 의존성도 추가해줍니다. (이유는 to be continue…)&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;기본적인 사용 방법&lt;/h3&gt;
&lt;p&gt;기존에 존재하는 테스트에 대해서 MySQL 컨테이너를 띄워서 실행해보겠습니다.
우선, 기존의 테스트 코드입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@ServiceTest&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MenuGroupServiceTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
  
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MenuGroupRepository&lt;/span&gt; menuGroupRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
      
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MenuGroupService&lt;/span&gt; menuGroupService&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
      
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; 메뉴_그룹을_등록한다&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
      
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; 메뉴_그룹들을_조회한다&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token comment&quot;&gt;// ...  &lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@SuppressWarnings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;NonAsciiCharacters&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
&lt;span class=&quot;token annotation punctuation&quot;&gt;@DisplayNameGeneration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;DisplayNameGenerator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ReplaceUnderscores&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RUNTIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootTest&lt;/span&gt;  
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServiceTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;테스트를 실행하면, H2에서 해당 테스트를 진행하고 있음을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/k4qtysf.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;이제, MySQL 컨테이너를 띄우고 해당 컨테이너에서 테스트가 진행되도록 하겠습니다.&lt;br&gt;
세 가지 방법이 존재하고, 세 방법 모두 “아주”.repeat(Integer.MAX_VALUE) 간단합니다.&lt;/p&gt;
&lt;br&gt;
&lt;h4&gt;첫 번째 방법 : 컨테이너에 대한 인스턴스 생성&lt;/h4&gt;
&lt;br&gt;
테스트 클래스 내에서 `Testcontainers` 모듈이 제공하는 MySQLContainer 인스턴스를 생성합니다.  
그 후, @BeforeEach, @AfterEach 등을 활용해서 해당 컨테이너에 대한 조작을 합니다.
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@ServiceTest&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MenuGroupServiceTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MenuGroupRepository&lt;/span&gt; menuGroupRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
      
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MenuGroupService&lt;/span&gt; menuGroupService&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MySQLContainer&lt;/span&gt; mySQLContainer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MySQLContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mysql:8&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@BeforeEach&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        mySQLContainer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
      
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@AfterEach&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tearDown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        mySQLContainer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; 메뉴_그룹을_등록한다&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
      
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; 메뉴_그룹들을_조회한다&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token comment&quot;&gt;// ...  &lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;원래 Testcontainers 모듈에는 Container라는 인터페이스와 구현체인 GenericContainer가 존재합니다. MySQL에 대한 의존성을 위에서 추가해줬기 때문에 (&lt;del&gt;복선 ㄷㄷ&lt;/del&gt;) GenericContainer를 상속받은 MySQLContainer를 사용할 수 있게 되었고, 이를 통해 추가적인 설정없이 바로 MySQL 컨테이너를 띄울 수 있게 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/vHO4z6H.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/KxxHqFc.png&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;https://i.imgur.com/JkbeWaZ.png&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;https://i.imgur.com/DYZ4N2J.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;이제 테스트를 실행해보면, MySQL 컨테이너가 띄워지는 것을 확인할 수 있습니다.
(만약 Driver를 찾지 못하는 예외가 발생한다면 MySQL에 대한 의존성을 추가해주세요!)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;implementation &apos;com.mysql:mysql-connector-j&apos;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/BrRsAzK.gif&quot; alt=&quot;&quot;&gt;
gif 파일입니다! 컨테이너가 올라가고 내려가는 시간이 오래 걸려서 편집을 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/CijiL8B.png&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;https://i.imgur.com/XSfP5MZ.png&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;https://i.imgur.com/miuibAl.png&quot; alt=&quot;&quot;&gt;
글자가 너무 작아서, 아래에 설명을 적었습니다!&lt;/p&gt;
&lt;p&gt;보시듯이, &lt;code class=&quot;language-text&quot;&gt;RYUK Sidecar 컨테이너&lt;/code&gt;가 먼저 띄워지고, 첫번째 테스트에 대한 컨테이너가 띄워집니다. (Testcontainers의 장점에서 설명했습니다!)&lt;br&gt;
첫번째 테스트가 종료된 후, &lt;code class=&quot;language-text&quot;&gt;RYUK Sidecar 컨테이너&lt;/code&gt;는 그대로 있고 그 다음 테스트에 대한 컨테이너가 띄워지는 것을 볼 수 있습니다. (눈썰미가 좋으시다면, 위의 이미지의 스크롤 크기를 통해 첫번째 테스트 시에만 Ryuk 컨테이너가 실행됐음을 확인하실 수 있습니다. 저는 몰랐습니다.)&lt;/p&gt;
&lt;br&gt;
&lt;h4&gt;두 번째 방법 : JUnit5의 @ExtendWith 사용&lt;/h4&gt;
&lt;br&gt;
JUnit5의 @ExtendWith을 통해서, 테스트 인스턴스의 생명주기를 Intercept하는 Extension을 사용하는 방법도 있습니다. 설명은 거창하지만, 어노테이션 두 개만 추가하면 됩니다.
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Testcontainers&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ServiceTest&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MenuGroupServiceTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MenuGroupRepository&lt;/span&gt; menuGroupRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
      
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MenuGroupService&lt;/span&gt; menuGroupService&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Container&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MySQLContainer&lt;/span&gt; mySQLContainer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MySQLContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mysql:8&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;    
    
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; 메뉴_그룹을_등록한다&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
      
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; 메뉴_그룹들을_조회한다&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token comment&quot;&gt;// ...  &lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;@Testcontainers와 @Container 모두 위에서 추가해준 Testcontainers JUnit 모듈에서 제공하는 기능입니다. (&lt;del&gt;복선222 ㄷㄷ&lt;/del&gt;)&lt;/p&gt;
&lt;p&gt;어떻게 어노테이션 두 개만 달면 자동으로 컨테이너가 올라갈까요?&lt;br&gt;
이제, @Testcontainers에 대해 살펴보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RUNTIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ExtendWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TestcontainersExtension&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Inherited&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Testcontainers&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
    &lt;span class=&quot;token comment&quot;&gt;/**  
    * Whether tests should be disabled (rather than failing) when Docker is not available. Defaults to  
    * {@code false}.  
    * @return if the tests should be disabled when Docker is not available  
    */&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;disabledWithoutDocker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
      
    &lt;span class=&quot;token comment&quot;&gt;/**  
    * Whether containers should start in parallel. Defaults to {@code false}.  
    * @return if the containers should start in parallel  
    */&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parallel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;@ExtendWith(TestcontainersExtension.class)가 있는 것을 보니, &lt;code class=&quot;language-text&quot;&gt;TestcontainersExtension&lt;/code&gt;이라는 Extension이 테스트 인스턴스의 Life-Cycle을 intercept하는 것을 짐작할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestcontainersExtension&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BeforeEachCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BeforeAllCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AfterEachCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AfterAllCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExecutionCondition&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  

	&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
	
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;beforeAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ExtensionContext&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
      
        &lt;span class=&quot;token comment&quot;&gt;// ...  &lt;/span&gt;
        
        &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StoreAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; sharedContainersStoreAdapters &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findSharedContainers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;testClass&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
          
        &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
      
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;afterAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ExtensionContext&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
      
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;beforeEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExtensionContext&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
    
        &lt;span class=&quot;token comment&quot;&gt;// ... &lt;/span&gt;
      
        &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StoreAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; restartContainers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;collectParentTestInstances&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parallelStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flatMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findRestartContainers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Collectors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
        &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
      
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;afterEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ExtensionContext&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
      
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StoreAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findSharedContainers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; testClass&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReflectionSupport&lt;/span&gt;  
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findFields&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;testClass&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isSharedContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HierarchyTraversalMode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TOP_DOWN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;f &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getContainerInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Collectors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
      
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Predicate&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Field&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isSharedContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ModifierSupport&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isStatic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
      
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StoreAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findRestartContainers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; testInstance&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReflectionSupport&lt;/span&gt;  
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findFields&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;testInstance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isRestartContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HierarchyTraversalMode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TOP_DOWN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;f &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getContainerInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;testInstance&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
      
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Predicate&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Field&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isRestartContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ModifierSupport&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isNotStatic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
      
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Predicate&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Field&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; field &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
            &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; isAnnotatedWithContainer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AnnotationSupport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isAnnotated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;field&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Container&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        
            &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
        
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;예상대로, beforeAll과 beforeEach를 오버라이딩하면서 @Container가 붙은 정적/클래스 인스턴스로 컨테이너를 띄우고 있습니다. 그래서 @BeforeEach, @AfterEach가 붙은 setUp(), tearDown() 메서드 없이도 컨테이너가 자동으로 띄워지고, 내려집니다.&lt;/p&gt;
&lt;br&gt;
&lt;h4&gt;두 가지 방법의 공통된 장점&lt;/h4&gt;
&lt;br&gt;
우선 장점으로는, 두 방법 모두 인스턴스를 직접 생성하여 사용하므로 조금 더 정교한 customizing이 가능합니다.
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MySQLContainer&lt;/span&gt; mySQLContainer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MySQLContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mysql:8&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
	  mySQLContainer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withUsername&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;리오&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withPassword&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;짱짱&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withDatabaseName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;멋쟁이&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withConfigurationOverride&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;귀요미&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;MySQLContainer의 Super Class인 JdbcDatabaseContainer와 GenericContainer는 훨씬 더 많은 메서드를 가지고 있습니다. 이러한 메서드들을 사용해서 컨테이너를 customizing하거나 원하는 정보를 얻을 수 있습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h4&gt;두 가지 방법의 공통된 단점 및 해결방법&lt;/h4&gt;
&lt;br&gt;
두 가지 방법 모두 테스트 메서드마다 컨테이너를 새로 띄웁니다. 각 컨테이너가 올라가고 내려가는 시간이 꽤 길기 때문에, 테스트의 수가 많아진다면 전체 테스트의 수행 시간이 굉장히 오래 걸리게 됩니다.
&lt;p&gt;이를 해결하기 위해서는 &lt;strong&gt;하나의 컨테이너 인스턴스만 사용하도록&lt;/strong&gt; 하면 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WithContainerTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

	  &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MySQLContainer&lt;/span&gt; mySQLContainer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MySQLContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mysql:8&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        mySQLContainer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Testcontainers&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WithContainerTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

	  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Container&lt;/span&gt;  
	  &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MySQLContainer&lt;/span&gt; mySQLContainer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MySQLContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mysql:8&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootTest&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SomeTest&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WithContainerTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 클래스를 하나 생성한 후 MySQLContainer의 인스턴스를 static 메모리에 올리면(싱글턴 패턴이죠!), 이후 해당 클래스를 상속하는 테스트들은 하나의 MySQLContainer 인스턴스만 사용할 수 있게 되…&lt;strong&gt;지 않습니다!!! (주의!!!)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;정확히 말하면, &lt;strong&gt;두 번째 방법인 @Testcontainers를 사용하는 경우에 해당 방법이 작동하지 않습니다.&lt;/strong&gt; JUnit의 Life-Cycle 관리는 클래스 단위로 이루어지기 때문입니다. 그래서, 다른 테스트 클래스가 실행되면 또 다른 Life-Cycle이 돌기 때문에 해당 static 컨테이너를 공유하지 못하고 새로 띄우게 됩니다.&lt;br&gt;
다만, 한 클래스 내의 메서드들은 하나의 컨테이너를 공유할 수 있습니다. 위의 TextcontainersExtension의 코드에서 beforeAll() 메서드 내부에서도 findSharedContainer()로 static 인스턴스를 찾는 로직이 보이네요.&lt;br&gt;
&lt;a href=&quot;https://testcontainers.com/guides/testcontainers-container-lifecycle/&quot;&gt;공식 문서&lt;/a&gt;에서도 ‘common misconfiguration’이라고 설명하며, Extension을 이용한 방법으로는 클래스 간 컨테이너 공유가 불가능하다고 합니다.&lt;/p&gt;
&lt;p&gt;그래서, 전체 테스트에서 하나의 컨테이너만 사용하고 싶다면 처음 소개드린 대로 싱글턴 패턴을 사용해야 합니다! (&lt;strong&gt;&lt;del&gt;그런데 과연 이 방법만 있을까요? 후훗…&lt;/del&gt;&lt;/strong&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/qZsgssD.gif&quot; alt=&quot;&quot;&gt;
보시듯이 하나의 컨테이너만 띄워지고 이 컨테이너에서 모든 테스트가 실행되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;추가적으로, 하나의 컨테이너를 사용하게 되면서 테스트 격리를 해야하는 상황이 있을 수 있습니다. 기존에 했던 방식대로 테이블들을 truncate 해주시면 됩니다. 저는 개인적으로 &lt;a href=&quot;https://kong-dev.tistory.com/248&quot;&gt;이 블로그&lt;/a&gt;에서 추천하는 방식이 가장 좋았습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h4&gt;세 번째 방법 : Datasource URL 설정&lt;/h4&gt;
&lt;br&gt;
&lt;p&gt;프로젝트의 상황을 조금 더 고민해본 결과, 위의 두 방법보다도 훨씬 간단한 방법을 직접 찾아낼 수 있었습니다. 간단하기도 하고, Testcontainers의 라이브러리 사용을 위해 기존 테스트 코드가 오염되는 것이 싫으신 분들 (&lt;del&gt;혹은 코드 몇 줄 더 치기 귀찮은 분들&lt;/del&gt;)을 위한 방법일 수 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ 이 방법은 사용하시는 상황에 따라 적절하지 않은 방법일 수 있습니다.
이후에 설명하는 상황과 부합하는 경우에만 사용하세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;현재 저희 프로젝트는 테스트 시 MySQL 컨테이너 하나만 띄우면 되는 상황입니다. 그래서 &lt;strong&gt;프로파일 설정 파일의 Datasource만 띄워지는 컨테이너로 설정하면 되지 않을까?&lt;/strong&gt; 라는 생각을 했습니다.&lt;br&gt;
Spring에서는 (스코프를 재설정하지 않는 이상) &lt;strong&gt;DataSource를 빈으로 등록&lt;/strong&gt;하고, 여러 곳에서 참조할 때 재사용하는 것을 알고 있었기 때문에, 가능할 것으로 생각했습니다.&lt;/p&gt;
&lt;p&gt;찾아보니 Datasource를 다음과 같이 Testcontainers가 띄워주는 MySQL 컨테이너로 설정하는 방법이 있었습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;spring:  
	datasource:  
		driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver  
		url: jdbc:tc:mysql:8://test  
		username: root  
		password: test&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(데이터베이스 이름과 username, password는 원하는대로 설정해도 됩니다!)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;결과는…!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/ymMrd9F.gif&quot; alt=&quot;&quot;&gt;
(&lt;strong&gt;성공적이었습니다!&lt;/strong&gt; 315(+ɑ)에 달하는 커버리지 93%(Line 기준)의 테스트도 1분 안에 끝납니다!)&lt;/p&gt;
&lt;p&gt;물론 위의 두 방법처럼 테스트 격리를 위해 truncate가 필요하긴 하지만, 코드로 인스턴스를 생성할 필요없이 간단하게 Testcontainers를 사용할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;올바른 방법인지 확인해보려고 공식문서를 열심히 뒤져서 &lt;a href=&quot;https://java.testcontainers.org/modules/databases/jdbc/&quot;&gt;JDBC 모듈 관련 문서&lt;/a&gt;를 찾아냈습니다. Testcontainers에서 지원하는 기능이 맞았고, 적절한 driver만 가지고 있다면 다른 벤더에도 적용할 수 있었습니다. (설명드린 첫번쨰 방법과 세번째 방법을 동시에 해야한다고 알려주는 다른 블로그 글들이 많으니 주의해주세요!)&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;마치며&lt;/h3&gt;
&lt;p&gt;Testcontainers를 사용하여 로컬에서도 실제 운영 환경과 유사한 환경에서 테스트를 수행하는 방법에 대해서 알아봤습니다.
개인적으로는 팀의 문제를 단계별로 해결해나가며 최선의 방법을 찾아냈고, 잘못 알려져있는 사용법들을 올바른 방법으로 소개해드릴 수 있었던 뜻깊은 경험이었습니다.
긴 글 읽어주셔서 감사합니다!&lt;/p&gt;
&lt;br&gt;
&lt;h4&gt;참고 자료&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://testcontainers.com/&quot;&gt;Testcontainers 공식 사이트&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://java.testcontainers.org/&quot;&gt;Testcontainers for Java 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=eZbLAD2yUfE&quot;&gt;Testcontainers로 MariaDB 통합 테스트하기 - 최범균 님&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/riiid-teamblog-kr/testcontainer-%EB%A1%9C-%EB%A9%B1%EB%93%B1%EC%84%B1%EC%9E%88%EB%8A%94-integration-test-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-4a6287551a31&quot;&gt;TestContainer로 멱등성있는 integration test 환경 구축하기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[useLayoutEffect를 활용한 산발적 마커 렌더링 최적화]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-10-11-marker-rendering-optimization/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-10-11-marker-rendering-optimization/</guid><pubDate>Tue, 21 Nov 2023 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;마커 렌더링 최적화&lt;/h1&gt;
&lt;h2&gt;서비스 소개&lt;/h2&gt;
&lt;p&gt;마커 렌더링 최적화 설명에 앞서 잠시 저희 팀에서 진행했던 서비스에 대해 소개드리겠습니다.&lt;/p&gt;
&lt;p&gt;저희 서비스의 출발점은 &lt;code class=&quot;language-text&quot;&gt;전기차 충전의 불편함&lt;/code&gt;이었습니다. 최근 들어 보조금과 저렴한 유지비용 등의 이유로 전기차의 수요는 점점 증가하고 있습니다. 하지만, 인프라는 그에 맞게 충분히 성장하지 못했습니다. 특히 충전에 관련된 불편함이 많이 대두되고 있습니다. 저희 서비스는 충전소 이용량 통계 정보를 제공함으로써 전기차 차주분들이 자발적으로 충전소를 분산 이용하는 것을 목표로 삼았습니다. 서비스의 주요 기능은 전국 6만개의 충전소와 그 내부 24만개의 충전기 정보 실시간 제공, 각 충전소 사용량 통계 정보 제공, 충전소 정보 불일치 신고 등이 있습니다.&lt;/p&gt;
&lt;p&gt;저희는 이러한 기능을 지도를 기반으로 전달하고자 했습니다. 각 충전소의 위치를 지도상의 마커로 표현하고, 마커를 클릭할 시 해당하는 충전소의 세부 정보 및 사용량 통계를 볼 수 있도록 구성했습니다. 따라서 저희 서비스를 이용하는 사용자들이 가장 처음으로 접하는 중요 정보는 &lt;code class=&quot;language-text&quot;&gt;마커&lt;/code&gt;라고 할 수 있습니다. 프로젝트 초반에는 이렇게 중요한 마커가 산발적으로 렌더링되는 문제가 있었습니다. 저는 이를 개선하고자 했습니다.&lt;/p&gt;
&lt;p&gt;이제 제가 어떻게 산발적 마커 렌더링을 개선했는지 살펴보도록 하겠습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;1. 개요&lt;/h2&gt;
&lt;h3&gt;1-1. 마커 렌더링 방식 선택&lt;/h3&gt;
&lt;p&gt;제가 지도에 표현하고자 했던 마커는 다음과 같은 모양을 가지고 있는 마커였습니다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 120px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/16640e02dcdb11df6a633232b18b95c3/be895/2023-10-11-scent-marker.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 108.33333333333333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAABYlAAAWJQFJUiTwAAACIElEQVR42r1VTUsbURTNr2lNVdDEJIIbcdGFP6JgF/UjqBi/q1KpuOmiYBct7txZl1LEj4WRuHOhoAGDk0TRndI4zkzKTNN6es/TaByNjlJcHDi599yT9969ufGdmRb+J3zPapjTDeTOTOhmXoGcsUcbnkrhqWHBKTgoFGzk86YCOWPMUePJkMJftg3DNLCcSGL8WwKtk6sK5IwxR81dpj63me3YSKWP0PJhCZVvE6ho20Fl556C4hJjjhpq3aY+98lS2hFeR3/A37GL8NAhwv0ZhPrSCuSMMUcNte6TKkPd4Ic8TLnKm7FFvGzfRWRwH4GYhmBvGnWEGJIHJcYcNdSyhrUXHpeG/IbCHwcL8W1UtCQQGjxUhTSojaVR1aWhultDoPfalBpqWcPa4imVIcfh/K+NkS9reNGaRHgge2XWMJTB9HIO47PHqO25NBRQQy1rWJsrNdQFlmXgnXTSH00hJG/FooAY1g9k0DxxgO/rOhpHs6jp0dQTUEMta1ir33jDMoZsxCvp7se5Y3yeP1G8+Jb3Gt51ZZ6Cp2kay2Jp08TXxZ9oGM7KM2gPX/m+phDV0pSrLntpSrmxCZaMDRvkeWzKDXZEhvjiPTUF8oiXwS7705Mr+TuSct2MAjljnn56t5aDYWBlPYn3U3HURLcUyBljztNycK8v+7cDwManmQ0FcsYetb7cC5aL1ZJdSKgl+5QFW4pi99z82f5T/gELCdLhIopPZgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 10 11 scent marker&quot; title=&quot;&quot; src=&quot;/static/16640e02dcdb11df6a633232b18b95c3/be895/2023-10-11-scent-marker.png&quot; srcset=&quot;/static/16640e02dcdb11df6a633232b18b95c3/be895/2023-10-11-scent-marker.png 120w&quot; sizes=&quot;(max-width: 120px) 100vw, 120px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;디자인 유연성이나 기능 추가와 같은 확장성을 고려했을 때 위 마커를 구글 기본 마커로 구현하기보단 리액트 컴포넌트로 관리하는 것이 더 유리하다고 판단해 저는 마커를 리액트 컴포넌트로 관리해 보고자 했습니다.&lt;/p&gt;
&lt;p&gt;하지만 지도는 리액트에서 제어하는 영역이 아니었기 때문에 리액트 컴포넌트로써 마커를 제어하기 위해 조금 특별한 방식이 필요했습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;DOM API인 createElement를 통해 DOM 요소를 생성한다.&lt;/li&gt;
&lt;li&gt;생성된 DOM 요소를 마커 생성자 함수에 전달해 지도에 해당 DOM 요소를 마커로써 사용한다.&lt;/li&gt;
&lt;li&gt;리액트 API인 createRoot를 이용해 전달한 DOM 요소를 리액트 root로 만든다.&lt;/li&gt;
&lt;li&gt;root.render 호출에 원하는 마커 컴포넌트를 전달해 원하는 마커를 렌더링한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위와 같은 방식으로 저는 마커를 리액트 컴포넌트로써 제어할 수 있게 되었습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;1-2. 여러개의 마커를 제어하는 구조 설계&lt;/h3&gt;
&lt;p&gt;지도는 리액트를 기반으로 동작하지 않습니다. 그렇기 때문에 리액트의 기존 코드에 지도가 추가되면 자칫 코드 구조가 복잡하게 얽힐 가능성이 있습니다. 저는 최대한 기존에 리액트 코드를 작성하던 방식에서 크게 벗어나지 않게 지도를 제어하고자 했습니다.&lt;/p&gt;
&lt;p&gt;리스트 컴포넌트를 만든다고 했을 때 대부분 다음과 같은 코드 구조를 떠올릴 것입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Item key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; item&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;ㅣ&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;지도에 찍히는 마커를 생각해 보면 이 또한 지도에 어느 좌표에 위치한다는 것만 빼면 위 컴포넌트와 크게 다르지 않은 동작을 한다는 것을 알 수 있습니다. 그래서 제가 선택한 마커 렌더링 방식은 다음과 같습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MarkerContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// station는 충전소 정보를 배열 형태로 저장하고 있습니다.&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; stations &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useStations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;stations&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;station&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Marker key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;station&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; station&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;station&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Marker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; station &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; renderMarker &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useMarker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; unmountMarker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;renderMarker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;station&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; unmountMarker&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;간단하게 설명해 드리자면 &lt;code class=&quot;language-text&quot;&gt;MarkerContainer&lt;/code&gt;에서 서버에서 받아온 충전소 정보를 통해 &lt;code class=&quot;language-text&quot;&gt;Marker&lt;/code&gt;를 호출하고, &lt;code class=&quot;language-text&quot;&gt;Marker&lt;/code&gt;에서 구글 지도 API를 통해 마커를 렌더링하는 구조입니다.&lt;/p&gt;
&lt;p&gt;마커 컴포넌트에서 호출한 &lt;code class=&quot;language-text&quot;&gt;renderMarker&lt;/code&gt;에서는 구글 지도 API의 호출이 일어나 마커를 생성하고, 전역적으로 관리되고 있는 마커 인스턴스 배열에 생성한 마커를 추가하는 기능을 수행합니다. 또한 지도 내 불필요하게 남아있는 마커를 삭제하기 위해 &lt;code class=&quot;language-text&quot;&gt;Marker&lt;/code&gt;가 unmount 될 때 호출되는 &lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt;내 클린업 함수에 지도에서 마커를 제거하는 함수를 등록했습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;1-3. 산발적 렌더링 발생&lt;/h3&gt;
&lt;p&gt;앞서 말씀드린 방식들을 활용해 서비스를 구현하던 와중 충전소가 몰려있는 지역에서 마커가 렌더링 될 때 산발적으로 마커가 렌더링 되는 모습을 확인할 수 있었습니다.&lt;/p&gt;
&lt;img src=&quot;/98beb82b608da9cdbf8e21ad219543ed/2023-10-11-scent-0.gif&quot;&gt;
&lt;p&gt;퍼포먼스 탭을 통해 CPU 성능을 저하시킨 후 테스트해 본 결과 디바이스의 성능이 낮을수록 마커의 산발적 렌더링이 더욱 심해지는 것을 확인할 수 있었습니다. 앞서 말씀드렸듯 마커는 저희 서비스에서 가장 처음으로 접하는 중요 정보이기 때문에 이 부분은 꼭 개선이 필요한 부분이라고 판단되었습니다.&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;h2&gt;2. 문제 원인 분석&lt;/h2&gt;
&lt;h3&gt;2-1. 잦은 상태 변경&lt;/h3&gt;
&lt;p&gt;다수의 컴포넌트에서 마커 인스턴스에 접근해야 하는 상황이 생겼기 때문에 저희는 마커 인스턴스 배열을 전역 상태로써 관리하는 방식을 선택했습니다. 이 때문에 마커를 렌더링할 때 전역 상태에 생성한 마커를 추가하는 동작이 수반되어야 했습니다. 저는 이 부분이 산발적 마커 렌더링이 발생한 지점이라고 생각했습니다.&lt;/p&gt;
&lt;p&gt;앞서 이야기했듯 마커 생성은 전역 상태의 변경을 야기합니다. 이 때문에 마커를 하나 렌더링할 때마다 전역 상태의 변경이 한번 일어나게 됩니다. 잦은 상태의 변경은 잦은 리렌더링을 발생시킬 것이고, 이 지점에서 마커의 산발적 렌더링이 발생했다고 판단하게 되었습니다.&lt;/p&gt;
&lt;p&gt;이를 고쳐보고자 저는 기존의 구조를 과감히 버리고 다음과 같은 렌더링 방식을 선택하게 되었습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MarkerContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// station는 충전소 정보를 배열 형태로 저장하고 있습니다.&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; stations &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useStations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 마커 인스턴스를 다루는 커스텀 훅&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createMarkerInstances&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; getRemainedMarkers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; removeUnmountMarkers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useMarker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// ... 마커 재생성 방지 및 영역 밖 마커 제거를 고려한 마커 렌더링 과정 구현 코드&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;기존에는 &lt;code class=&quot;language-text&quot;&gt;MarkerContainer&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;Marker&lt;/code&gt;를 호출하고, &lt;code class=&quot;language-text&quot;&gt;Marker&lt;/code&gt;에서 마커 렌더링을 수행하는 방식이었습니다. 변경된 구조에서는 상태 변경을 최소화하기 위해 구글 지도 API 호출을 관리하는 커스텀 훅을 통해 &lt;code class=&quot;language-text&quot;&gt;MarkerContainer&lt;/code&gt;에서 모든 마커의 렌더링을 일괄적으로 처리하고, 모든 처리가 끝난 마커 인스턴스를 전역 상태에 한 번만 반영하는 방식을 채택했습니다.&lt;/p&gt;
&lt;p&gt;위 구조로 리팩터링을 마치고 나니 예상했던 대로 마커의 산발적인 렌더링이 사라지게 되었습니다.&lt;/p&gt;
&lt;img width=&quot;600&quot; src=&quot;/eb23df9a915908ebbb56f6a9201e9b25/2023-10-11-scent-10.gif&quot;&gt;
&lt;br&gt;
&lt;h3&gt;2-2. useEffect의 동작 원리&lt;/h3&gt;
&lt;p&gt;기존에는 마커를 한 가지 종류만 렌더링하면 됐었지만 클러스터링 기능이 추가됨에 따라 다음과 같이 여러 종류의 마커를 렌더링해야 했습니다.&lt;/p&gt;
&lt;img src=&quot;/8f04d15686141b8ded46672b5d259ea8/2023-10-11-scent-clustering.gif&quot;&gt;
&lt;p&gt;마커 렌더링 최적화를 위해 만든 훅은 한 종류의 마커에 초점이 맞춰져 있어 마커의 종류만큼 훅이 필요하게 되었습니다. 또한 훅의 복잡도가 굉장히 높아 추후 유지보수를 위해선 개선이 필요한 상황이었습니다.&lt;/p&gt;
&lt;p&gt;다른 기능의 개선이 대부분 완료된 시점에 가용할 수 있는 시간이 늘어남에 따라 유지보수성 향상을 위해 이전의 구조를 유지하되 산발적 렌더링이 발생하지 않게 하는 방법이 있을까 고민하는 시간을 가졌습니다.&lt;/p&gt;
&lt;p&gt;일단 지난 마커 렌더링 최적화 이전 시점의 커밋으로 브랜치를 돌린 후 기존에 문제라고 생각했던 상태 변경을 일으키지 않으면서 전역 상태로 관리되는 마커 인스턴스 배열을 변화시키는 방법을 적용해 봤습니다. &lt;code class=&quot;language-text&quot;&gt;(이 방법은 글 가장 하단부에 소개하도록 하겠습니다.)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;처음 생각과는 달리 전역 상태의 변경을 발생시키지 않았음에도 마커의 렌더링이 산발적으로 일어나는 모습을 확인할 수 있었습니다. 훅 분리를 통해 해결한 문제가 애초에 근본적인 문제의 해결을 했다고 볼 수 없는 상황이었습니다.&lt;/p&gt;
&lt;p&gt;문제의 정확한 원인을 파악하고자 개발자 도구의 Performance 탭에서 함수의 호출 단위로 분석을 시도해 보았습니다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1764px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/5b17afba2e25cb4db4e4ad975c7a6ad3/1888f/2023-10-11-scent-2.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 13.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAABJ0AAASdAHeZh94AAAAp0lEQVR42kWLwQ6CMBBE+WpDW4QQf8qbiSYeNJEWcNHfUKRQWsYFTDy8vM3MbFQ9Czxoj7o5oaYzm6EjquaCgiqUdAXRAZpKmMYs6LteTfqHwa0uODeIvA/wfoIPf8YxIExs7pzzS+9Gv9zj4GDtANsN6N49WsZ2Dp+XRdf2iISQiIWAYBI1W7FjSCmh1AzniUSWSuSZQJ6uuy2T8m6XxVAy5nuz/H0BfMzEMTts7sAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 10 11 scent 2&quot; title=&quot;&quot; src=&quot;/static/5b17afba2e25cb4db4e4ad975c7a6ad3/1888f/2023-10-11-scent-2.png&quot; srcset=&quot;/static/5b17afba2e25cb4db4e4ad975c7a6ad3/0eb09/2023-10-11-scent-2.png 500w,
/static/5b17afba2e25cb4db4e4ad975c7a6ad3/1263b/2023-10-11-scent-2.png 1000w,
/static/5b17afba2e25cb4db4e4ad975c7a6ad3/1888f/2023-10-11-scent-2.png 1764w&quot; sizes=&quot;(max-width: 1764px) 100vw, 1764px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;위 이미지에서 performance 탭의 프레임 이미지 하단부를 보면 함수의 호출이 노란색 선으로 표시된 모습을 확인해 볼 수 있습니다. 이 부분이 마커의 렌더링에 관여하는 함수의 호출이 아닐까 싶어 해당 부분에 어떤 함수들의 호출이 있었는지 확인해 보았습니다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 714px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/b1c706d3e87e1893affdc080502dcdd0/f91ec/2023-10-11-scent-performance.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 70%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAAC7ElEQVR42o2SyU8TcRTHJ/EgiImJJxODsiRo9FJBZElYFEpwQY3Rg/HkwRNWECREBQT0QCJxSWSJtJSte2mnhba0oG1noJS1GBER0BgCBdGD/gVf38zIZoLx8MmbN7953/m+935MXf0lBEaL4OHvwu1XUCxGn1cBF9HnUyDwvgLBmaotPPpnzshvlcMQMqOBZ6EOuWFY4KGf56Cd9UE35wcbHof928R/wxTcKQE71QlVQAfDBzvc3wfhXuPh+RNdKxycYR8cy96dWdqEOX+7FNYpDZQBA9pHzdBP96IzZEX7mBmdkxZ0jHXDOOOAI8ztiHNlMzLnCktgDXWhZVCP1mEDlIM6UcD6pR/WzxK2r2/+EuDpfADmeQ+Msy50jHdLTFjAXBAdaqEeMUM1pEPbiAk9i164Vnnxj84wLwkt+zdwUm6e8xBu6siBVupOTXVKXis4LKUZasidieYoORScCs62ijiWuQ2En6y704RsUA8bqcZI9XphKffgW3GhfYLdaFmIqiE9OqmF3iVaSNi/DcG5ZaEfduqka8pGzqQaJXXIFBSVgVvzQD1mEUWEQ0G0e8EjFmx3KdFDgtx0H1hy1z5phZLTQkU7MM44wVwuLUfwl5e2LM1ASQeCdUFUE2LFBfSSyAZLftjI5cinAVy9cR31PY1oC5rwmtNAHTSCOX2zEIYJWsY4Ky5EuDptRCsdWsila5Xu448AMUx3lOIa8TOInlE7DsmuoNrUAuO0XawVNs3sOyJHSv5FlL2oQmVTLSoaa/HgVTUqmx/jYUMNalrr8JxtwjO2ES/tzfTcjCaPEtcURYg4kIonhgb0rXKwLb4VR8REJZxFjCwb0YcTIEtMQ3JqlsiJpHScTMlEUnIG0tMzkJiUJubJpzKRkpaNjOw8HDsuQ/HT++Jo1sfE7E3IR3RiHiJic7EnLhdR8XJExuUgMjZHzCNi5dRaFvYfPYPdMfQuXvoukth1MAs1ukaYPjqgfWdD16QVvwF/QzHZqNXWUwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 10 11 scent performance&quot; title=&quot;&quot; src=&quot;/static/b1c706d3e87e1893affdc080502dcdd0/f91ec/2023-10-11-scent-performance.png&quot; srcset=&quot;/static/b1c706d3e87e1893affdc080502dcdd0/0eb09/2023-10-11-scent-performance.png 500w,
/static/b1c706d3e87e1893affdc080502dcdd0/f91ec/2023-10-11-scent-performance.png 714w&quot; sizes=&quot;(max-width: 714px) 100vw, 714px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;자세히 보니 해당 부분에서 &lt;code class=&quot;language-text&quot;&gt;CarFfeineMarker&lt;/code&gt;의 렌더링이 이루어지는 것을 확인할 수 있었습니다. 또한 가장 상단을 보면 마커 렌더링 이전에 &lt;code class=&quot;language-text&quot;&gt;performWorkUntilDeadline&lt;/code&gt;이라는 메서드의 호출이 있는 것을 확인해 볼 수 있습니다. 저는 이 메서드가 마커의 산발적 렌더링의 원인일 것이라고 생각해 어떤 역할을 하는지 자세히 알아보았습니다.&lt;/p&gt;
&lt;p&gt;자세히 알아보니 기존 코드의 문제점은 잦은 상태 변경이 아닌 &lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt; 내부에서 호출되던 &lt;code class=&quot;language-text&quot;&gt;root.render&lt;/code&gt;에 있었습니다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/b112f9941b688e4043dcc93f752bb5b9/fdad3/2023-10-11-scent-useEffect.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 33.199999999999996%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABR0lEQVR42oWRPU7EMBCF09HRcgRarkDHDTgBFaKn4QZIFBRI20KLqBCiQFtSUSA6CirQ7hKSTez4L3b8McouJWDJem/smec344L18glUD63sZVjxOqzi2kMXQff8u4q8Jo3PNEpTLhWzuuNLcNFYiVvmgqptea8MMQ1jfv5N8IcYcVDqwEJ5ZmJv3jrmOo5iCzNQtnY8c87/7VBFS5c8Ojl8HzHWE2LCOEdIic7YMVF1Bh8CtXaUDiqbSWubS5f5NBkVMoXtPVaKl0ELWqwxBCnUWosbRyeYcx4FIXA8VWxOMtuXkdeqJ/aB3euejYvM/l0UQe/QMqfKtlIkItZhRLRpGpRSI7fW4oLMhIGj+47iHLYmA88fRh5U7Fz1FGewdyOCrbRsBmlFHA45/zqbYX11+5Y4nCZOHiOlzeP3nD5FDh4Sk5fIN99qEjTexkm3AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 10 11 scent useEffect&quot; title=&quot;&quot; src=&quot;/static/b112f9941b688e4043dcc93f752bb5b9/f97d7/2023-10-11-scent-useEffect.png&quot; srcset=&quot;/static/b112f9941b688e4043dcc93f752bb5b9/0eb09/2023-10-11-scent-useEffect.png 500w,
/static/b112f9941b688e4043dcc93f752bb5b9/1263b/2023-10-11-scent-useEffect.png 1000w,
/static/b112f9941b688e4043dcc93f752bb5b9/f97d7/2023-10-11-scent-useEffect.png 2000w,
/static/b112f9941b688e4043dcc93f752bb5b9/fdad3/2023-10-11-scent-useEffect.png 2678w&quot; sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt;의 셋업함수는 리액트의 렌더, 커밋 단계를 거친 이후 브라우저에서의 렌더링이 완료된 시점에 처리됩니다.&lt;/p&gt;
&lt;br&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1690px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/cb3f36e56d84048b68f539e796bbf566/650fc/2023-10-11-scent-marker-rendering-1.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 25.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAoElEQVR42m2P7QqAIAxFff/XLPowjYJKKW1xBot+JIztuOt2dSEEsfDeS9/3EmP8Zeppml79l8nzPIvj8r5vKaXIvu8yjqNyrVW2bVOmJtCmlOS6rpdzzvoWHoZBHC6Y3HWdZjbhCF6WRUXckYl1XdU5i8ho0JpLR4PDBrbTMD6O42VcM+A8T+1xjHForANxRFC3bfvLuG+aRh992X5kDh+DEHv8tO2XcwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 10 11 scent marker rendering 1&quot; title=&quot;&quot; src=&quot;/static/cb3f36e56d84048b68f539e796bbf566/650fc/2023-10-11-scent-marker-rendering-1.png&quot; srcset=&quot;/static/cb3f36e56d84048b68f539e796bbf566/0eb09/2023-10-11-scent-marker-rendering-1.png 500w,
/static/cb3f36e56d84048b68f539e796bbf566/1263b/2023-10-11-scent-marker-rendering-1.png 1000w,
/static/cb3f36e56d84048b68f539e796bbf566/650fc/2023-10-11-scent-marker-rendering-1.png 1690w&quot; sizes=&quot;(max-width: 1690px) 100vw, 1690px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;등록된 셋업 함수들은 위와 같이 모두 한꺼번에 처리될 것 같지만 셋업 함수 내에서 리액트의 렌더링에 직접적으로 영향을 미치는 메서드 호출이 발생하게 된다면 위와 같이 동작하지 않습니다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/6ce36f40598d25873d876420fb6c0747/4a797/2023-10-11-scent-marker-rendering-2.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 18.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAzElEQVR42i2O23KDMAwF+f9vbAmhTAbfLYNxQ2i3gvRBoxntHkndzWQGKwzaP42Q2wHHS+vA1xf9Pxts5mMWgs74eTtR3d6Wi597+jnTlfbC5YqXSqw7wXpoK8cieBeQ70P5evFwchf5XcvlBBtI52xpmLRQ2k7Xto2cEiJCXQpxuuNiwhiDPCZqraQYWRcNFK35gXNWnUxSd1tX0pnPwrmrk1IwVgXvLxDHgb0kmreErztZ3tyHQFCeppFn8jwlEsabZrIecFh1zqf+AMIFMBBIyHs7AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 10 11 scent marker rendering 2&quot; title=&quot;&quot; src=&quot;/static/6ce36f40598d25873d876420fb6c0747/f97d7/2023-10-11-scent-marker-rendering-2.png&quot; srcset=&quot;/static/6ce36f40598d25873d876420fb6c0747/0eb09/2023-10-11-scent-marker-rendering-2.png 500w,
/static/6ce36f40598d25873d876420fb6c0747/1263b/2023-10-11-scent-marker-rendering-2.png 1000w,
/static/6ce36f40598d25873d876420fb6c0747/f97d7/2023-10-11-scent-marker-rendering-2.png 2000w,
/static/6ce36f40598d25873d876420fb6c0747/4a797/2023-10-11-scent-marker-rendering-2.png 2860w&quot; sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;렌더링에 영향을 미치는 메서드 호출 시 리액트의 렌더, 커밋 단계를 거쳐 브라우저의 렌더링이 진행됩니다. 이 과정에서 리액트는 처리 시간이 오래 걸려 사용자에게 60프레임의 웹 페이지를 보여줄 수 없게 될 것을 고려해 메서드 처리에 5ms의 시간제한을 걸어주게 됩니다.&lt;/p&gt;
&lt;p&gt;이것이 산발적인 마커 렌더링을 일으키는 원인이었습니다.&lt;/p&gt;
&lt;p&gt;마커를 렌더링하기 위해 &lt;code class=&quot;language-text&quot;&gt;root.render&lt;/code&gt;를 호출하게 되면 리액트의 렌더링에 직접적으로 영향을 주기 때문에 해당 메서드의 처리 시간에 5ms의 시간제한을 걸어주게 되고, 그로 인해 한 번에 마커를 렌더링하지 못하고 몇 개씩 나누어 렌더링을 진행했기 때문에 산발적 렌더링이 발생했던 것이었습니다.&lt;/p&gt;
&lt;p&gt;처음 해결했던 방식은 &lt;code class=&quot;language-text&quot;&gt;MarkerContainer&lt;/code&gt;에서 마커 렌더링을 일괄 처리하는 것을 위해 &lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt; 내부에 있던 마커 렌더링 처리 로직을 외부로 꺼내서 수행했기 때문에 산발적 렌더링을 제어할 수 있던 것이었습니다. 애초에 잦은 상태 변경이 원인이 아니었던 것이었습니다.&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;h2&gt;3. 문제 해결&lt;/h2&gt;
&lt;p&gt;결국 원점으로 돌아와서 문제를 해결하기 위해선 브라우저 렌더링 이후 시점에 트리거 되는 셋업 함수들을 브라우저 렌더링 이전 시점에 모두 실행해야 했습니다.&lt;/p&gt;
&lt;p&gt;리액트 공식 문서를 보면 이 같은 경우에 사용하는 훅이 존재합니다. 바로 &lt;code class=&quot;language-text&quot;&gt;useLayoutEffect&lt;/code&gt;입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;useLayoutEffect is a version of useEffect that fires before the browser repaints the screen.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;공식 문서상에서는 &lt;code class=&quot;language-text&quot;&gt;useLayoutEffect&lt;/code&gt;를 브라우저가 화면을 그리기 전에 실행되는 &lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt;라고 소개하고 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The code inside useLayoutEffect and all state updates scheduled from it block the browser from repainting the screen. When used excessively, this makes your app slow. When possible, prefer useEffect.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;공식 문서 하단부 주의 사항을 보면 &lt;code class=&quot;language-text&quot;&gt;useLayoutEffect&lt;/code&gt;는 모든 상태 업데이트가 완료될 때까지 브라우저의 렌더링을 막아 성능 저하의 여지가 있다는 문구가 적혀 있습니다.&lt;/p&gt;
&lt;p&gt;저는 브라우저의 렌더링 이전 시점에 모든 마커 렌더링을 마치고 브라우저가 마커를 그리도록 하는 방법을 찾고 있었으므로 위 주의 사항이 제겐 꼭 필요한 기대 동작이었습니다. 어차피 화면에 렌더링 될 마커의 개수는 무한정 늘어나지 않고, 지도의 너무 넓은 영역을 보게 되면 클러스터링 마커가 기존 마커 대신 렌더링 되므로 마커의 개수가 너무 많아져 브라우저의 렌더링을 지연시킬 문제는 발생하지 않을 것이라 판단되었습니다. 그래서 저는 최종적으로 &lt;code class=&quot;language-text&quot;&gt;useLayoutEffect&lt;/code&gt;를 사용하기로 결정하게 되었습니다.&lt;/p&gt;
&lt;p&gt;기존의 구조에서 &lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt;가 사용되었던 부분을 &lt;code class=&quot;language-text&quot;&gt;useLayoutEffect&lt;/code&gt;로 수정하고, 훅의 내부 동작에서 상태 변경 또한 트리거 하지 않도록 수정해 보니 다음과 같이 산발적 렌더링이 개선된 모습을 확인해 볼 수 있었습니다.&lt;/p&gt;
&lt;img width=&quot;600&quot; src=&quot;/eb23df9a915908ebbb56f6a9201e9b25/2023-10-11-scent-10.gif&quot;&gt;
&lt;br&gt;
&lt;br&gt;
&lt;h2&gt;4. 정리&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;산발적인 마커 렌더링 발생&lt;/li&gt;
&lt;li&gt;잦은 상태 변경이 원인이라 판단, 상태를 한 번만 변경하는 구조로 수정&lt;/li&gt;
&lt;li&gt;구조 수정에 사용한 훅의 복잡도가 너무 높음 + 다른 마커를 렌더링해야 하는 요구사항 발생&lt;/li&gt;
&lt;li&gt;유지보수에 유리한 이전 구조로 돌아가고자 코드를 보다 보니 문제의 원인이 &lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt; 동작에 있음을 인지&lt;/li&gt;
&lt;li&gt;산발적 렌더링 개선을 위해 &lt;code class=&quot;language-text&quot;&gt;useLayoutEffect&lt;/code&gt; 도입&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;
&lt;br&gt;
&lt;h2&gt;5. useSyncExternalStore를 활용한 전역 상태 관리 유틸&lt;/h2&gt;
&lt;p&gt;앞서 글 가장 하단부에서 소개하기로 했던 전역 상태의 변경을 리렌더링 없이 하는 방법에 대해 짧게 말씀드리며 글을 마치도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;일단 간단히 말씀드리자면 상태로써의 변경이 아닌 일반 자바스크립트 객체로써의 변경으로 전역 상태를 변경하는 방법을 사용했기 때문에 리렌더링이 일어나지 않으면서도 전역 상태의 변경을 수행할 수 있었습니다. 이를 이해하기 위해선 &lt;code class=&quot;language-text&quot;&gt;useSyncExternalStore&lt;/code&gt;에 대해 알아야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;useSyncExternalStore&lt;/code&gt;는 리액트 외부 환경과 리액트를 연결해 주는 훅입니다. 이를 통해 모듈 분리된 일반 자바스크립트 객체를 리액트의 상태처럼 다룰 수 있습니다. 저희 팀은 이를 이용한 전역 상태 관리 유틸을 만들어서 사용해 왔습니다. 해당 유틸의 동작 방식은 다음 이미지와 같습니다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1544px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/d6da582aedfad0dbda7c36ddbacb2852/bb3e3/2023-10-11-scent-useSyncExternalStore.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 89.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAABYlAAAWJQFJUiTwAAAC0ElEQVR42o1UuY4UMRCdvwIiYkiIEL+yQIQExAR8A1eCEEJkSCuYDQggQGJXQtrd2enpmenDffnoy35UubtHo11xtLpku1yu472yZ/jDZx0QSOC8dAilw4rmZwWw1fjrN7uscG4YZetwIoCsIWmHcaMdjrM9o385dGTovLFD2VikNaAKgcXiAut1iKZWWFOGvXU7W3fJ+RWH09dZzohEWiwShaUwfh2Z/yh5ciOlhBAZsixHnudIaZ7kFRZBiNNF4OdZNuyzpIlA3TTY9zHbz0ykKdqmhu27ncBZGC1RFrmfs64f9bIqUVXV1QwVEcCgL2OKaixKIqEYRXHppUYoShAnpHPDPvncFgbrTCGns00/OmzpQEzY8BilORSRQT/qziGvNAplEGcl7WXIpSYx/jDb81qUEhQDkd5zKOphIYRA1w2hrO2xIWaDVYDVaoUwpPlyiSSOduUppalk6efx5JAbOKsdeo5CWdREr6RyuGXyAW9UdLAgvCwHbQge2iNkUJI+oww772MPw5Okw4N5hydHEgefG/yIhiwl1fbsW4tHc437hxIvjttdU38863FwqPH4SOPp1w4ZR5gczoMW1183uPte49pLg0+LdnRoce9Djdtva9x8o/HwS70r9/n3Bjdeadx5Z3CL9tdySGLG8QSVzOz+vIgQq86Xy51VjeyfRgXOo9JjzYR0hBPPA6HwKxS+3GKEZ8YGGzU0Zj92Jz8E3JtrGplxO2aVjbgqAm0igc+wrOTkkKy31GxpEmOz3foxKDsfgK+ZoBuxJT2zu82V70FDSayLGiKJ/BnuDr6WbsowUg5GVijoNhhVIaysf774qVKaWoP1skAqG18aoYKYWsHI0t8go+nRGKv0fcgLJmmSYEyfHVbdoGvckLHPsBseDtZpOwRgHzuWJRkldK9S2k1IKrYkDFsCh/XJqBfGUcOzWOQ142g9mSnpp6v3G1VJavd99GK7AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 10 11 scent useSyncExternalStore&quot; title=&quot;&quot; src=&quot;/static/d6da582aedfad0dbda7c36ddbacb2852/bb3e3/2023-10-11-scent-useSyncExternalStore.png&quot; srcset=&quot;/static/d6da582aedfad0dbda7c36ddbacb2852/0eb09/2023-10-11-scent-useSyncExternalStore.png 500w,
/static/d6da582aedfad0dbda7c36ddbacb2852/1263b/2023-10-11-scent-useSyncExternalStore.png 1000w,
/static/d6da582aedfad0dbda7c36ddbacb2852/bb3e3/2023-10-11-scent-useSyncExternalStore.png 1544w&quot; sizes=&quot;(max-width: 1544px) 100vw, 1544px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;useSyncExternalStore&lt;/code&gt;의 인터페이스에 맞게 자바스크립트 객체에 접근하게 된다면 리액트의 상태로써 자바스크립트 객체를 사용할 수 있게 됩니다. 이러한 동작 과정에서 이미지에서 보시다시피 옵저버 패턴과 유사하게 전역 상태로 사용될 수 있게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;useSyncExternalStore&lt;/code&gt;훅을 통해 수정하는 것이 아닌 직접 수정을 가하게 된다면 리렌더링 없이도 전역 상태의 최신 값을 변경할 수 있게 됩니다. 앞서 마커 인스턴스 배열을 리렌더링 없이 수정할 때도 이와 같은 방식을 사용했습니다.&lt;/p&gt;
&lt;p&gt;※ 하지만 이는 리액트의 불변성을 해치는 행위이므로 정말 특수한 경우가 아니라면 지양하는 것이 좋습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[웹 어플리케이션의 역사]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-10-25-HISTORY-OF-WEB-APP/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-10-25-HISTORY-OF-WEB-APP/</guid><pubDate>Tue, 21 Nov 2023 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;들어가면서&lt;/h2&gt;
&lt;p&gt;백엔드 개발자라면 웹 어플리케이션이라는 개념을 자주 접하게 됩니다.
그렇지만 웹 어플리케이션이 왜 등장했는지, 어떻게 지금의 형태에 이르렀는지에 대해 답하기는 쉬운 일이 아닐 것입니다.&lt;/p&gt;
&lt;p&gt;좋은 엔지니어는 기술 그 자체에 매몰되는 것이 아니라 기술을 문제 해결을 위한 하나의 도구로써 활용합니다.
우리가 사용하는 도구가 ‘왜’ 등장했는지는 알아야 할 필요가 있습니다.&lt;/p&gt;
&lt;p&gt;따라서 이번 아티클에서는 Spring을 사용하는 백엔드 개발자의 관점에서 웹 어플리케이션의 발전 과정을 살펴보려고 합니다.
WWW의 등장부터 시간순으로 설명할 예정이니, 참고해주시면 감사하겠습니다.&lt;/p&gt;
&lt;h2&gt;WWW(World-Wide-Web)&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1280px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/266be9f277542c330e3309cf71c75c46/bbbf7/2023-10-25-HISTORY-OF-WEB-APP_001.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.60000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAA7zAAAO8wEcU5k6AAACVUlEQVR42nWS2U8TURTG+aMk4cXEF0xUHkiMJiY18CAxuDQmkJgYH0CLIWwWqUXa0jaoJYWx1Eg3u8xMZ7rRztJpp7M0XVOg2JamIiho1NsNUDG5OTN35vzm+74zt8Mdjv9vuTZioAbjqVA81ajpjUQGITlXu6GjefFETjGN5YmwXpp3hdmHcs2gTC6dUA6Ozw2MTkFOP3iO0hxoqMMwkfDFRFDBHo8KvigXYHhKSJNCiuCTi2bLC8PqvO6NQq2dUS5Y8YiPTXoZvgXXRSgO8J4w83hKfVemuidTScfUd54sDE3q1Yb3hcrn3epBtVSpliuEkEFpHosJLbhpMiJmIYd3VvduOZRXeQS9L62GRR0iTmuMMS755ehHrlRJ5AukmPEQLEywpzOzKMlqIavRbK8Wt3M0tc0l8jS9mc0Y1iw+kinufUt/qu3U9kkxCwYJxnYCO0OMyeVfXLVC687i5haJYGKEImGsUi5DFqc3RH7/+Wv/4Ovh4VGYSwFlHGQ8zuwI0GvuwAc0PDI5/1z1dmxOe//R+DOFbvrV65EJpZgt5HaqW6Xdcm3Pz4gIxWFMO3OThz7iRhu6bIFtPmp0RnPuwtWns1o3iEPzQS69Yvc6grS3wYBf9Qd8/AkrTjiDjA6y6U0OuXZFbVzXgixODPgCgnhMAKP6G8ZoHmcEYH5gWPZyyXS+p6+z+0bXpZudFyVdl/uMdhRvuz0DRsELmgeGr90aViyZevsfdF+/3SORXpFIe/uHzHAQiwowmThb2X1yJAWESIAtQoHW+rFDyNYN+o/yb1ZAR6GGCgj5AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-10-25-HISTORY-OF-WEB-APP_001.png&quot;
        title=&quot;&quot;
        src=&quot;/static/266be9f277542c330e3309cf71c75c46/bbbf7/2023-10-25-HISTORY-OF-WEB-APP_001.png&quot;
        srcset=&quot;/static/266be9f277542c330e3309cf71c75c46/0eb09/2023-10-25-HISTORY-OF-WEB-APP_001.png 500w,
/static/266be9f277542c330e3309cf71c75c46/1263b/2023-10-25-HISTORY-OF-WEB-APP_001.png 1000w,
/static/266be9f277542c330e3309cf71c75c46/bbbf7/2023-10-25-HISTORY-OF-WEB-APP_001.png 1280w&quot;
        sizes=&quot;(max-width: 1280px) 100vw, 1280px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;웹 어플리케이션을 이해하기 위해서는 웹이란 도대체 무엇인지, 왜 등장했는지를 이해할 필요가 있습니다. WWW은 도대체 어떤 이유에서 등장했을까요?&lt;/p&gt;
&lt;p&gt;1989년, 팀 버너스 리라는 사람이 CERN(유럽 입자 물리 연구소)이라는 기관에서 근무하고 있었습니다.&lt;/p&gt;
&lt;p&gt;그런데 그는 근무를 하면서 한 가지 고민에 빠지게 되었습니다.
당시 CERN에서는 핵 소립자 실험의 성과를 전 세계의 연구자들과 공유하고자 하는 니즈가 있었는데, 쉽게 정보를 공유할 방법이 없었기 때문입니다.&lt;/p&gt;
&lt;p&gt;사실 당대에 이메일이나 파일 전송과 같은 기술은 이미 존재했습니다.
그런데 수많은 연구자를 상대로 일일이 메일을 보내야 한다면 얼마나 번거로울까요?
따라서 팀 버너스 리는 보다 효율적인 정보 관리 방법을 고민하게 됩니다.&lt;/p&gt;
&lt;p&gt;1989년, 팀 버너스 리는 ‘정보 관리(Information Management)’ 라는 이름의 제안서를 CERN의 경영진에게 제출합니다.
해당 제안서에서는 하이퍼텍스트(HyperText) 등의 개념을 소개하면서 ‘정보 관리 네트워크’의 필요성을 주장했습니다.
그리고 이 제안서가 바로 WWW의 개념적인 시초입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;하이퍼텍스트(HyperText)란 웹 페이지를 다른 웹 페이지로 연결하는 링크를 말합니다.
이런 하이퍼텍스트의 등장으로 참조하고 있는 다른 페이지에 대한 열람이 쉬워졌고, 정보의 전달 효율은 크게 향상되었습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;팀 버너스 리는 개념적인 모델의 WWW를 제안했을 뿐만 아니라, 실제로 구현까지 직접 참여했습니다.
그는 구현 과정에서 웹의 핵심 기술들을 많이 고안했는데, 대표적인 기술이 바로 HTML과 HTTP입니다.
그리고 최초의 웹 서버라고 불리는 CERN Httpd도 이 시점에 등장했습니다.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;CGI&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;그런데 WWW를 사용하고자 하는 수요가 급속도로 늘어남에 따라 이슈가 하나 생기게 됩니다.
바로 동적 페이지에 대한 요구 또한 증대되었다는 것입니다.&lt;/p&gt;
&lt;p&gt;하지만 초기 웹 서버 모델은 URL에 맞춰 페이지를 반환하기만 할 뿐, 동적인 페이지를 생성할 수 있는 능력은 없었습니다.
이런 동적 페이지를 생성하기 위해서는 외부 프로그램의 도움이 필요했습니다.&lt;/p&gt;
&lt;p&gt;따라서 HTTP 요청이 들어오면 그에 걸맞은 적절한 프로그램을 수행하자는 아이디어가 부상하게 되었고, CGI라는 표준 인터페이스가 등장하게 됩니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;CGI란, Common Gateway Interface의 약자로 서버와 애플리케이션 간에 데이터를 주고받는 방식을 의미합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 909px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d5b01db0ff7676920f26070b8929671c/2f7b1/2023-10-25-HISTORY-OF-WEB-APP_002.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 63%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAABHUlEQVR42q2Sa26EMAyEuf/deoeq7Q9APAMhAULcft4Nu3S7qFI7kpUY2+OxQyZ/xBI2MW7d/ayzq3w0XrYYD4nJK/pZ3mr3VRgfyCh5r728vBoZfbgQ+iVI0Qw7zbquaqkBRL11EuPPCq2bxVgEXRWGEKRtW3Ug4p7nuYzjqL61Vu8XRVG2bdtNGy6LeO9vIxNomkbmeVajQVKJkWyMUbJhGLRZWZZqxGjmnLsRQtD3/eniIaKYwmmaVDUnAhCD/5QQJfcGiJMHOKuq2mOMDPFhZEY6Q13XqjKtBbXp5PthZAi7rtOOJLEjCIqi0EQU3Y/0HcQPjwIRC6YoLZn9pBNDxTMg6OGVz0am4ZlCcBiZAhZ7Bn6f38Yz+Wd8AsfBABJVKptjAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-10-25-HISTORY-OF-WEB-APP_002.png&quot;
        title=&quot;&quot;
        src=&quot;/static/d5b01db0ff7676920f26070b8929671c/2f7b1/2023-10-25-HISTORY-OF-WEB-APP_002.png&quot;
        srcset=&quot;/static/d5b01db0ff7676920f26070b8929671c/0eb09/2023-10-25-HISTORY-OF-WEB-APP_002.png 500w,
/static/d5b01db0ff7676920f26070b8929671c/2f7b1/2023-10-25-HISTORY-OF-WEB-APP_002.png 909w&quot;
        sizes=&quot;(max-width: 909px) 100vw, 909px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;웹 서버는 동적 페이지를 원하는 클라이언트 요청이 들어오면 CGI 인터페이스를 통해 외부 프로그램을 실행시키면서 적절한 HTTP 응답을 반환할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;예를 들어, 사용자가 장바구니 조회 요청을 보내는 경우에는 다음과 같이 동적 페이지를 구성했습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 890px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/52eff213138eca45bcda2bfa0046f1a3/f3cf6/2023-10-25-HISTORY-OF-WEB-APP_003.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 61.199999999999996%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABF0lEQVR42pVSXW+DMAzs//93e99DpSG+YSIJkASCy1lzygZpNUvGJrHP54Mb/bFt2w7xJ6fX1ihHkwuc3/C4tzOVg70s9utGn9W4N/lfA8X0vNDHl2KMCGidJ6UNH4zjSNM0kdaanHMk9877y4GA12akELYnoN+L27Ylay3VdU1d11Ge53vsGfy772lZFh6AGonzPPM5aoR5BFRKnVZCDkcDvGkaHoxYlmV0nIkxIKZgxSuNeOUDG2MMg4s0IYTY+5ahGMCGYWBAsIMkYFYUBd9J71uGkoMhmjB4XdcoxVGSE0NMBv1+/wBZllFVVewAwHoYmrIkICIczXB5h24+8duIJKeVwSJl+E3+BQgdsG7KRKtX9wL4AOc1rs8/0nlfAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-10-25-HISTORY-OF-WEB-APP_003.png&quot;
        title=&quot;&quot;
        src=&quot;/static/52eff213138eca45bcda2bfa0046f1a3/f3cf6/2023-10-25-HISTORY-OF-WEB-APP_003.png&quot;
        srcset=&quot;/static/52eff213138eca45bcda2bfa0046f1a3/0eb09/2023-10-25-HISTORY-OF-WEB-APP_003.png 500w,
/static/52eff213138eca45bcda2bfa0046f1a3/f3cf6/2023-10-25-HISTORY-OF-WEB-APP_003.png 890w&quot;
        sizes=&quot;(max-width: 890px) 100vw, 890px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;장바구니 조회 요청이 발생하면 웹 서버에 도달합니다.&lt;/li&gt;
&lt;li&gt;웹 서버는 CGI 인터페이스를 사용해 외부 프로그램에 요청 처리를 위임합니다.&lt;/li&gt;
&lt;li&gt;CGI 프로그램은 사용자의 장바구니 정보를 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이처럼 CGI의 등장 덕분에 사용자들은 정적 페이지만 사용하는 것이 아닌 동적 페이지도 접근할 수 있게 되었습니다.&lt;/p&gt;
&lt;p&gt;하지만 CGI 방식에도 몇 가지 문제점들이 존재했습니다.
우선 요청마다 프로그램을 실행시켜야 하니 서버 리소스가 많이 소모되었습니다.
또한 HTTP 요청마다 스레드가 아닌 프로세스를 할당하는 구조였기 때문에 서버의 부하가 상당했습니다.&lt;/p&gt;
&lt;p&gt;심지어 CGI 프로그램은  C, C++, Perl 등의 언어로 작성되는 경우가 많았기 때문에, 어플리케이션 확장에도 어려움을 겪었습니다.&lt;/p&gt;
&lt;p&gt;이에 Java 진영에서는 이런 문제점들을 해결할 수 있는 Servlet이라는 모델을 제시했습니다.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Servlet&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Servlet은 Java 코드를 사용해서 웹페이지를 동적으로 생성하는 기술을 의미합니다.
‘어떻게 Java 코드를 통해 동적 페이지를 생성하지?’ 라는 의문이 드실 수 있는데, 간단하게 HTTP 요청이 하나 들어오면 요청에 대응하는 클래스의 메소드를 호출하는 방식이라고 이해하셔도 됩니다.&lt;/p&gt;
&lt;p&gt;이런 Servlet 방식은 CGI 방식과 대비해 상당히 경제적인데, 프로세스가 아닌 스레드 기반으로 동작하기 때문입니다.
CGI 방식의 경우 요청마다 프로세스를 할당하지만, Servlet 방식은 스레드를 할당합니다.&lt;/p&gt;
&lt;p&gt;조금 더 구체적으로 이야기하면, Java Application을 미리 띄워두기 때문에 매번 프로세스를 할당할 필요가 없는 것입니다.
HTTP 요청이 들어오면 Java Application에게 ‘처리해줘’ 라고 의뢰를 하게 되고 Java Application은 스레드를 생성해서 요청 처리에 적합한 Servlet을 실행합니다.&lt;/p&gt;
&lt;p&gt;또한 Servlet은 Java 진영의 기술이기 때문에 순수 Java 코드로 작성됩니다.
따라서 JVM 생태계에 친화적이고, Java의 특징 중 하나인 ‘플랫폼 독립성’ 가장 잘 누릴 수 있게 됩니다.
Servlet을 한 번만 구현해두면 어느 플랫폼에서도 쉽게 재사용할 수 있습니다.
이처럼 Servlet은 앞서 설명해 드렸던 CGI의 문제점들을 극복하는 모델입니다.&lt;/p&gt;
&lt;p&gt;Servlet을 사용하는 경우의 HTTP 요청 처리 방식은 CGI 방식과 크게 다르진 않습니다.
도식화하면 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 885px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/eb1f8aeb9897888f059b50453c5132b8/540ae/2023-10-25-HISTORY-OF-WEB-APP_004.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 57.599999999999994%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABNElEQVR42pVRi26DMAzk/7+uP7CpQ2K8nwkJNAlezhAE7Yq2k6zYsXM+O5F6OIIdsSyLt9VP2olusaCvWq85OkPNjoQ23izH0Xer6V7Il8KAxOc/c8nEy9Zlbbj6vTL0kQuqxLwSWmOo7zoODPy+p7IsaZqm7e5BWuud6DeM47jnImOsJ6honmcmQRKkUkq+a9rWxwMXI2593HkBsNBI+jfObYTWWi4CnHMvKnA3DAMppZikKAqqqopPNBZC8EQBTIgH7wDCoAQ+JkCME4pZoZ9mHxmE6PQOKKzrmmtAlKYpW5Ik1DQNrwm5E2FQCAX4GNzhPKrC3fM6wm+fPgWF2AEeYh9ZlnEcxzEXYhzY8ZefT+z3RJjnOY+DnQQloQAxiK+AtydCKLvaIRT8mRAOxr3Cf/I/i9St72+Yqx0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-10-25-HISTORY-OF-WEB-APP_004.png&quot;
        title=&quot;&quot;
        src=&quot;/static/eb1f8aeb9897888f059b50453c5132b8/540ae/2023-10-25-HISTORY-OF-WEB-APP_004.png&quot;
        srcset=&quot;/static/eb1f8aeb9897888f059b50453c5132b8/0eb09/2023-10-25-HISTORY-OF-WEB-APP_004.png 500w,
/static/eb1f8aeb9897888f059b50453c5132b8/540ae/2023-10-25-HISTORY-OF-WEB-APP_004.png 885w&quot;
        sizes=&quot;(max-width: 885px) 100vw, 885px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP 요청이 발생하면 웹 서버에 도달합니다.&lt;/li&gt;
&lt;li&gt;웹 서버는 Servlet Container에게 HTTP 요청을 토스합니다.&lt;/li&gt;
&lt;li&gt;Servlet Container는 적절한 Servlet(Java 프로그램)을 선택하고 실행시킵니다.&lt;/li&gt;
&lt;li&gt;Servlet은 요청에 걸맞은 동적 페이지를 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Servlet Container란 Servlet을 관리해주면서 동적 웹페이지를 제공하기 위한 기타 작업을 수행해주는 역할을 합니다.
Servlet은 동적 페이지를 만들어내기 위한 프로그램에 불과하므로 이들을 초기화해주고 관리해주는 누군가가 필요하기 때문입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;그렇지만 서블릿 방식의 웹 어플리케이션도 문제점은 존재했습니다.
바로 자바 코드에서 HTML을 다루다보니 비즈니스 로직과 뷰의 영역 자체가 모호했다는 것입니다.&lt;/p&gt;
&lt;p&gt;다음 코드를 보면 ‘뷰의 영역이 모호하다’ 라는 말이 이해가 될 것입니다.
아래 Servlet 코드에서는 로그인 처리를 위한 코드와 렌더링을 위한 코드가 공존합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; class MyServlet extends HttpServlet {
	
	&lt;span class=&quot;token variable&quot;&gt;@Override&lt;/span&gt;
	protected void doGet&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;HttpServletRequest request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; HttpServletResponse response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; {
		String account &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getHeader&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;account&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		String password &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getHeader&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		
		&lt;span class=&quot;token comment&quot;&gt;// 비즈니스 로직&lt;/span&gt;
		validateCredential&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;account&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

		&lt;span class=&quot;token comment&quot;&gt;// 뷰를 위한 로직&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;html&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; 
		&lt;span class=&quot;token keyword&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;head&gt; &amp;lt;/head&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;body&gt; Hello world! &amp;lt;/body&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;/html&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이처럼 뷰의 변경 지점과 비즈니스 로직의 수정 지점이 같아지다 보니, 유지 보수하는 데 어려움을 겪기 일쑤였습니다.
프로그래머는 Servlet 코드를 변경하고 싶어도 디자이너가 HTML 작업을 하고 있다면 기다려야 했습니다.&lt;/p&gt;
&lt;p&gt;따라서 Java 코드와 HTML 코드의 분리를 더 명확히 하자는 요구가 생겨났고, JSP가 등장하게 됩니다.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;JSP&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;JSP는 Java Server Page의 약자로(현재는 Jakarta Server Page라고도 불립니다) HTML 코드에 Java 코드를 넣어 동적 웹페이지를 생성하는 기술을 의미합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;HTML&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;HEAD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;TITLE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;The Welcome User JSP&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;TITLE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;HEAD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;BODY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&amp;lt;% String user=request.getParameter(&quot;user&quot;); %&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;H3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Welcome &amp;lt;%= (user==null) ? &quot;&quot; : user %&gt;!&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;H3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;P&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;B&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; Today is &amp;lt;%= new java.util.Date() %&gt;. Have a nice day! :-)&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;B&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;P&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;B&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Enter name:&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;B&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;FORM&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;METHOD&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
				&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;INPUT&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;NAME&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;SIZE&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;15&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
				&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;INPUT&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;submit&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;VALUE&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Submit name&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;FORM&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;BODY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;HTML&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위 코드는 간단한 JSP 파일 예시입니다.
&amp;#x3C;% %&gt; 블록 안에 Java 코드가 포함된 것을 확인하실 수 있습니다.
&amp;#x3C;% %&gt;와 같은 식별자들을 JSP 태그라고 하는데, JSP에서는 JSP 태그를 활용해 Java 코드를 HTML에 삽입할 수 있습니다.&lt;/p&gt;
&lt;p&gt;JSP를 사용하는 경우 프로그래머는 JSP 태그로 감싸진 부분만 다루면 되고, 디자이너는 JSP 태그 이외의 HTML만 다루면 되므로 순수 Servlet을 사용하는 방법과 대비해 유지 보수성이 향상됩니다.&lt;/p&gt;
&lt;p&gt;사실 위처럼 비즈니스 로직을 직접 JSP 페이지에 작성하기보다는 재사용 가능한 컴포넌트인 Java Beans를 사용하는 경우가 많았습니다.
참고로 Java Beans란 재사용할 수 있는 Java 클래스를 의미하는데, 자세한 설명은 본문의 주제를 벗어날 것 같아 생략하도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;아래 코드는 Java Beans를 활용해 JSP 프로그래밍을 한 예시입니다.
userBean, dataBean이라는 Java Bean을 활용해 렌더링에 필요한 정보를 가져옵니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;HTML&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;HEAD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;TITLE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;The Welcome User JSP&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;TITLE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;HEAD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;BODY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;jsp:&lt;/span&gt;useBean&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;userBean&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;mybeans.UserBean&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;jsp:&lt;/span&gt;useBean&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;dateBean&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;mybeans.DateBean&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
		&amp;lt;% String user=request.getParameter(&quot;user&quot;); %&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;H3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Welcome &amp;lt;%= userBean.getName() %&gt;!&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;H3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;P&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;B&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; Today is &amp;lt;%= dateBean.getDate() %&gt;. Have a nice day! :-)&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;B&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;P&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;B&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Enter name:&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;B&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;FORM&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;METHOD&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
				&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;INPUT&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;NAME&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;SIZE&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;15&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
				&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;INPUT&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;submit&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;VALUE&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Submit name&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;FORM&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;BODY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;HTML&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이런 JSP의 경우 어떤 방식으로 사용하느냐에 따라 모델 1 구조와 모델 2 구조로 나뉘었습니다.
JSP를 통해 뷰의 구성과  제어 로직의 처리까지 모두 수행한다면 모델 1이라고 불렸고, 뷰의 구성만 담당한다면 모델 2라고 불렸습니다.&lt;/p&gt;
&lt;p&gt;앞서 본 Java Beans를 활용하는 JSP 예시 코드가 모델 1에 해당합니다.
JSP가 직접 Java Beans를 호출하는 등의 제어 로직을 담당하기 때문입니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1185px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e6cc811521593a39b5f373e45422d726/4ee41/2023-10-25-HISTORY-OF-WEB-APP_005.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 98%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC1UlEQVR42oVUi3KbMBD0//9em04f6cSOE94gxEsvYLsnjGM7malmzghZrO5293SY5xkxAsNv83VdsSwL5wsC1yXkfQt5D5f/t/WwYzAOccJNremgbQfjDJRS6Psenu/BKjh5EnTlx7Ue8VpqGOvgnYMbR3hGsHYDXJiFCw5VUGjREZibJwOtO3hTAGvBZw5jDEE9zlmFp5cUg7EY5FCtAQJaPmceGAH97PHWJjipM9TYQoZk5AhYpT9ghiSCydjp2PYE6DzHyBiq6gLIH+sd3rsUJ/0WM1yYumVJdsxQZd9Z1nssR4Z8ZJyAr+Q9oE1TNKcTuqLAwsNihtZblL5Gs7Sohhp5RqC6Ji8lETLMNucBFt57vLLkby8Z2n5kyeS264BhgGf5H6Iwejugm/ooynpR1DkS7rgxmFjqui7ohglp3cERXEq25M8Il8JxFOVG/j32Q5ZljVy2JFyU3+0i1rndfxsH/GdIZkmSoCBHMv/f+JTh4/zD1Fu23ofr2vZ/uEbMUBYnO6E3A6zbzNk0DXkmfxTLTkWMTr1hoAvclNNODUUyGCmGbdvoRct55NDTAsq2qGeFxil2gME0TdHI49hT6Qyrz9EUv9E1f6l6Ho3eEmjioYGC8AO4XWXH9tGui11S+Saqd8ufGHqeAw9gN/CQeS9P/uPe4nhE9vyMnjaLPnQ0cDMpJDRxPpXRa49DzHwuFCr28d262IZAnl1ybT0BKPsKv7I/SHRKHsI1u31Y0vJ0TJHU/R2gdNQsJdPcV2P7m5IbT4LDfYairmDXSmPihbA+HCZlO3bRcnHHIdAGrdMolwa1V8zGkXDN20ZH7jaVS6jqlUqfESictzoe9IkayVAI7myPhvee4sUwjENUWIAd7TTbFItLUfLWqfOfUWVRXi7krwHncGfOR2OL3yQcPboFLcL3+eaW3kO+31pvxaX572M/9XR6xZH2MOTwVrCvnv8AgeIYPl4befkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-10-25-HISTORY-OF-WEB-APP_005.png&quot;
        title=&quot;&quot;
        src=&quot;/static/e6cc811521593a39b5f373e45422d726/4ee41/2023-10-25-HISTORY-OF-WEB-APP_005.png&quot;
        srcset=&quot;/static/e6cc811521593a39b5f373e45422d726/0eb09/2023-10-25-HISTORY-OF-WEB-APP_005.png 500w,
/static/e6cc811521593a39b5f373e45422d726/1263b/2023-10-25-HISTORY-OF-WEB-APP_005.png 1000w,
/static/e6cc811521593a39b5f373e45422d726/4ee41/2023-10-25-HISTORY-OF-WEB-APP_005.png 1185w&quot;
        sizes=&quot;(max-width: 1185px) 100vw, 1185px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;모델 1의 경우에는 구조가 단순하여 간단한 페이지를 구성하는 데에 주로 사용되었지만, 규모가 큰 프로그램을 작성할 때에는 상당한 양의 Java 코드가 JSP 파일에 드러나게 되었습니다.
이는 시스템 규모가 크다면 JSP의 장점을 살릴 수 없었음을 의미합니다.&lt;/p&gt;
&lt;p&gt;따라서 대안으로 모델 2가 등장하게 됩니다.
모델 2의 경우는 모델 1을 보완한 것으로 제어 로직 처리 자체는 서블릿이 하며 JSP는 렌더링만 담당하게 됩니다.
이를 통해 비즈니스 로직과 뷰의 책임을 명확하게 분리할 수 있게 됩니다.&lt;/p&gt;
&lt;p&gt;코드로 나타내면 아래와 같습니다.
서블릿에서는 HTTP 요청을 받고, 적절한 제어 로직을 수행합니다.
그리고 request의 Attribute에 렌더링을 원하는 객체를 등록한 뒤, JSP 페이지로 포워딩합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;클라이언트를 거쳐 다시 HTTP 요청을 보내는 것이라면 리다이렉션, 서버 내에서만 이동하는 경우 포워딩이라고 부릅니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Servlet 코드&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyServlet&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServlet&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServletException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 비즈니스 로직 수행&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;util&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;Date&lt;/span&gt; now &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;util&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;currentDateTime&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// JSP 페이지로 리다이렉션&lt;/span&gt;
        request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRequestDispatcher&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;myView.jsp&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
					&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그리고 JSP 페이지에서는 단순히 currentDateTime 객체를 꺼내 렌더링만 수행합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JSP 코드&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;HTML&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;HEAD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;TITLE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;JSP Model 2 example&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;TITLE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;HEAD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;BODY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;H1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;current date and time:&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;H1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;P&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&amp;lt;%= request.getAttribute(&quot;currentDateTime&quot;) %&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;P&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;login form:&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;FORM&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;process.jsp&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;INPUT&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;BR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;INPUT&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;email&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;BR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;INPUT&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;submit&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;submit&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;FORM&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;BODY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;HTML&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이처럼 모델 2는 Servlet과 JSP의 관심사를 분리하여 유지 보수성을 향상시킨 모델입니다.
Servlet은 제어 로직을 담당하고, JSP는 뷰 로직을 담당합니다.
그리고 이 구조는 현재 우리에게 익숙한 MVC 패턴과 유사합니다.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;J2EE와 EJB&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1510px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0e59da12c800e5d7c28f7b3c796e4ae7/1d574/2023-10-25-HISTORY-OF-WEB-APP_006.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 42.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsTAAALEwEAmpwYAAABLElEQVR42p2RTUvDQBCG88u92aMHb4LoQRDBmx8ohZYiSJqaaFJp/UhoS0wsNmnTZLPZbLYzcWNECoJQh4Edlnl233lHKetArHLDUL5PwTBx/wnDuAm+WmvYDMbIXmkNDAdfLIgV/IwCa7MgT5DHZUGBLQpR9SgZF0EUp6MO8/SIlTnnbdOZJzSMKc0540WwTCPCShTC2IUPE/r73OsGCZOvKQnN1Se3P5m5QTyaziV2oQ17L+7epXp6Y511By3TPmwaGSVwuwWRI9RtCIe1xgp+cLzjtvE4nsp/5OVJx5Qpyave88F177w7OGrdk5Sg1oCZJfQdmN7Vcyi2H1qjd/31zbA9xw9pXsg6jCupWV4QxudJtiC06o0nSDwkvizWDEME6cyvPVe7/9PtT/Rr/RSjECDqAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-10-25-HISTORY-OF-WEB-APP_006.png&quot;
        title=&quot;&quot;
        src=&quot;/static/0e59da12c800e5d7c28f7b3c796e4ae7/1d574/2023-10-25-HISTORY-OF-WEB-APP_006.png&quot;
        srcset=&quot;/static/0e59da12c800e5d7c28f7b3c796e4ae7/0eb09/2023-10-25-HISTORY-OF-WEB-APP_006.png 500w,
/static/0e59da12c800e5d7c28f7b3c796e4ae7/1263b/2023-10-25-HISTORY-OF-WEB-APP_006.png 1000w,
/static/0e59da12c800e5d7c28f7b3c796e4ae7/1d574/2023-10-25-HISTORY-OF-WEB-APP_006.png 1510w&quot;
        sizes=&quot;(max-width: 1510px) 100vw, 1510px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;앞서서 우리는 동적 페이지를 구현하기 위한 방법으로 Servlet과 JSP를 살펴보았습니다.
사실 Servlet과 JSP는 J2EE에 포함되어 있는데, J2EE란 기업 환경의 어플리케이션을 구성하는 데 필요한 표준의 집합을 의미합니다.&lt;/p&gt;
&lt;p&gt;많은 Java 개발자들은 이 J2EE라는 표준을 따르며 웹 어플리케이션을 구축해왔습니다.
J2EE에는 JDBC, Servlet, JSP 등 JVM 진영에서 개발을 해보았다고 한다면 한 번쯤은 들어보았을만한 표준이 많이 존재합니다.
그렇지만 모든 표준이 항상 실용적이었던 건 아닙니다.&lt;/p&gt;
&lt;p&gt;J2EE에는 EJB라는 기술이 존재합니다.
EJB(Enterprise Java Beans)란 분산 애플리케이션을 지원하는 컴포넌트 객체이며, J2EE를 사용해 엔터프라이즈급 서버를 개발하는 경우 EJB로 비즈니스 로직을 처리했습니다.
그리고 이런 EJB 객체들을 관리하는 EJB 컨테이너라는 것도 존재했는데, EJB 컨테이너는 분산 트랜잭션 지원 등의 기술적인 지원을 해주었습니다.&lt;/p&gt;
&lt;p&gt;하지만 EJB로부터 기술적인 지원을 받기 위해서는 EJB 컨테이너에 종속적인 코드들을 많이 작성해야 했고, 더는 순수한 비즈니스 로직을 작성하기가 어려웠습니다.&lt;/p&gt;
&lt;p&gt;다음은 EJB를 사용하는 경우의 예시 코드입니다.
HelloBean은 단순히 이름이 입력으로 들어왔을 때 “Hello”를 붙여 출력해주는 비즈니스 객체입니다.
해당 객체는 단순한 비즈니스 로직을 수행함에도 불구하고 SessionBean이라는 인터페이스를 구현해야 합니다.
EJB와 관련된 코드가 상당 부분을 차지하고 있는 모습을 확인하실 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;javax&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ejb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HelloBean&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SessionBean&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sayHello&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; myName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EJBException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; myName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/* ------------------------------------------------------
    * Begin EJB-required methods. The following methods are called
    * by the container, and never called by client code
    * ------------------------------------------------------- */&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ejbCreate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CreateException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// when bean is created&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setSessionContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SessionContext&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

		&lt;span class=&quot;token comment&quot;&gt;// Life Cycle Methods&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ejbActivate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ejbPassivate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ejbCreate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ejbRemove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이런 형식의 웹 어플리케이션 개발은 유지 보수성에 있어 많은 문제를 불러왔고, 흔히 이 시기를 ‘추운 겨울’이라고 표현하기도 했습니다.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Spring&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;이처럼 J2EE 표준을 준수한 애플리케이션들이 너무나도 복잡해지니 로드 존슨이라는 사람이 나타나 ‘J2EE Development without EJB’ 라는 책을 출간합니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 284px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e983a7143d262848bc14ccb83de04950/18db4/2023-10-25-HISTORY-OF-WEB-APP_007.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 123.23943661971832%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAIAAAC+dZmEAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEn0lEQVR42oVRC0xTZxT+AVEWE0AmIm/aTlsexWEpfTHHBJlkE1RgBBdjdIQxJ4sZMliYSyRbQgSmoaXNpttEQggLModmDhJeGSOKQO+9vX/vbXl0TEHeBYyMNzu35bEsMfvz5dz//Od85zvnXLR316sc3D28bFh3t+Cx1323eJ8gXMQT8gIEfj4C/z18vz18X28fD0+kUavL1WpduVan1X6r02k1mnI1AB41mjI1PF7/5lp6ekpGxunLBZ+Vlhbdqausrv7+1q3vgIjW1tYWFxfHJyamp6etVuvy8vLaxlldXQV7tbg4Pv7tzs7HFsugvoeYn5/fTECQDf7Q0NDw8DDYycnJCdsZGxubm5tjWVYoFBUXX7VapyAyOjra3Nw8OzsDdYG4oTw+DtlTU1MQhvvIyAh0AaGqqqoDBw7odLru7u6ZmZnlpeWGhgbQsPfFkRcWFkD22bNn8Ao04MPl+fPnEKqoqMjKyqqvry8pKenp6Wltba2pqVlaWlpv264MmlAY1OACw8/OzkLPEKqurs7MzITHrq6u9vb2uro6hmE217FOhiEhY9x2gAa1gQ+hBw8eZGdnGwwGEG9paYGmIHlrYfbPiu3ADqAkhAcHBy0WC1TMzc0FZZIk79+/39jYCNNtym6R//1voH8Y78aNGx0dHaCmUqkKCgqamppAH7p78WLOrvFfsv3cu3evsLAQmPn5+ZAE08LktbW1bW1tmKYnJsZnubVzO0NL3B9bWV7h7MLC4pOnQ2Vlmtu3K1/8PV9ervui4Mt+c39Lc2vHo84xq7VLTwwM/jU2ZV1cXVlaW0XskU+MKTmGpIvG5EuGtDwyIbs5Kp0E93iO/uj5Sjf5zwFxf8Sco+I+MsWeZ2I+JBVnjTGZbCznIsJN1fl+3sPT+Z0fXOk6ckEfmU6d+vxh6qedJz5+dOICczKHSLv0+Ewe5XMEIxHtIDYiMUZiGoUBEO0op3zkBkkMFaygRAcpTyUOlFNeCtLpLWL7Ico5mvKKpna8STsojE4y7CinHeTYMQp7ROFtMkS7RPQW+z751afvp90DTV6U8HVLXqDljudA6X5zHo8t4vdV+v6pDejTBpnUvgM6/z4dn7nuP1jhi10iEXY9yBwNZ86J2Iz9prQwereUjRKzaUL2rBAnCtmsYOO74v4zwaaL+3C8yHQy2JwWhhNCzfEh2DkK4V0R5ly+WR305Bc/9nJg7xWBqZTXd0rEfhVk1vg/vRtIIilGcgNSYCSjURQAc1YGFhm3R7ISMSMJN0WHMioxcyiMUYUZAyNMCjGjDGXlIbRDJHaUYke7lWKnjQuSIswV5srYrMxWXkavu3YrfRnsZAmNJBhF2qzECAC1TaCXAtHblJTgOCVMJgRJRHBKjzDVEHSc2qY0OP8/EO19bPi333tv1gx8fbPbK07v9w7hnUC6xhJ2uB0m3WK3XFe4HybscI9DxtfeY2vq9ddumYt+MKIIzEFCu6joHUo7DAAXJffCWSX9SjS98w3DThXsDDH8lP5uAvea+368yzEdpbSTnOQlGQISSf9jRIAN/CSKn0TwkkheIrXvBBGSqhcl0y6qfwD5enRI0Z4cHwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-10-25-HISTORY-OF-WEB-APP_007.png&quot;
        title=&quot;&quot;
        src=&quot;/static/e983a7143d262848bc14ccb83de04950/18db4/2023-10-25-HISTORY-OF-WEB-APP_007.png&quot;
        srcset=&quot;/static/e983a7143d262848bc14ccb83de04950/18db4/2023-10-25-HISTORY-OF-WEB-APP_007.png 284w&quot;
        sizes=&quot;(max-width: 284px) 100vw, 284px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;로드 존슨은 해당 저서에서 EJB의 문제점을 제시하면서 EJB를 사용하지 않고도 충분히 고품질의 애플리케이션을 개발할 방법이 존재함을 증명했습니다.
그리고 이 저서가 바로 Spring이라는 웹 프레임워크의 시작점입니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1510px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c1a602e6b23f25c52a25c619c29ca898/1d574/2023-10-25-HISTORY-OF-WEB-APP_008.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 47.199999999999996%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA70lEQVR42mP4jwL+/fv/7z8WgCyMYDGA1GNVAgJ//yHAXwgX2UgGZP7vv78evr/+7ttLZBV///39++8PNudANf+DGLH+2rTyHb6H7m/49ffnnTcXbr05d/PN2QvPD55/dvDWm/P33119/OE2susYwPpA/K+/Pm26PuvTj3dvvz1//+3V3jsrll/sWX154t67K3beXrzr9pJVl/qPPtwM8RGKsyH677+7tuxi95UXx4Dczz/e335z4cP31z9+f//66/OPP99OP9n99NM95KBB8TNQ9Pvvr7///kYPahwAVTOqQRAf/YPG3z8sof2fAgAATiIG2Zo+rCsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023-10-25-HISTORY-OF-WEB-APP_008.png&quot;
        title=&quot;&quot;
        src=&quot;/static/c1a602e6b23f25c52a25c619c29ca898/1d574/2023-10-25-HISTORY-OF-WEB-APP_008.png&quot;
        srcset=&quot;/static/c1a602e6b23f25c52a25c619c29ca898/0eb09/2023-10-25-HISTORY-OF-WEB-APP_008.png 500w,
/static/c1a602e6b23f25c52a25c619c29ca898/1263b/2023-10-25-HISTORY-OF-WEB-APP_008.png 1000w,
/static/c1a602e6b23f25c52a25c619c29ca898/1d574/2023-10-25-HISTORY-OF-WEB-APP_008.png 1510w&quot;
        sizes=&quot;(max-width: 1510px) 100vw, 1510px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;로드 존슨이 주장하는 Spring의 핵심 가치는 ‘POJO를 통해 순수한 비즈니스 로직을 작성하자’ 입니다.
EJB를 사용하는 경우 앞서 말씀드렸듯이 특정 기술에 종속적인 코드를 작성하게 되었고, 객체 지향적이고 순수한 비즈니스 로직과는 거리가 멀어졌기 때문입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Spring is essentially a technology dedicated to enabling you to build applications using POJOs. - Rod Johnson&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Spring은 EJB의 문제점을 해결하기 위한 장치들을 여럿 가지고 있습니다.
대표적인 장치로는 제어의 역전(Inversion Of Control), 의존성 주입(Dependency Injection), 관점 지향 프로그래밍(Aspect Oriented Programming) 등이 있습니다.
이외에도 트랜잭션 추상화 같은 다양한 개발 편의 기술들을 제공하기도 합니다.&lt;/p&gt;
&lt;p&gt;이런 Spring을 사용하면 EJB 스펙을 따르지 않아도 EJB와 비슷한 효과를 낼 수 있었습니다.
하지만 여기서 주의해야 할 점은, Spring 자체가 J2EE 사양을 대체하기 위해 등장한 프레임워크는 아니라는 것입니다.
로드 존슨의 저서 이름을 보시면 아시다시피, Spring은 J2EE 사양을 보완하기 위해 등장한 프레임워크임을 알아야 합니다.&lt;/p&gt;
&lt;p&gt;현재는 Spring이 단순히 J2EE 사양을 보완할 뿐만 아니라, 독자적인 생태계를 구성하고 있습니다.
Spring MVC 뿐만 아니라 Spring Batch, Spring Security, Spring Reactive 등과 같은 많은 기술이 생겨나고 있으며 그 입지를 단단히 하고 있습니다.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;마치면서&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;WWW의 등장으로부터 Spring에 이르기까지의 역사를 훑어보았습니다.
이를 통해 시대적 배경 속에 각각의 기술들이 등장할 수밖에 없었던 이유를 확인할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;정보 공유를 위해 WWW가 탄생했으며, 동적 페이지에 대한 요구에 대응하기 위해 CGI, Servlet, JSP 등의 기술이 생겨났습니다.
또한, J2EE 사양의 한계를 극복하기 위해 웹 프레임워크인 Spring이 등장했었습니다.&lt;/p&gt;
&lt;p&gt;역사를 보시면 아시다시피, 100% 완전무결한 기술은 없습니다.
그렇기 때문에 기술의 근본을 이해하려는 노력이 중요하다고 생각합니다.&lt;/p&gt;
&lt;p&gt;이번 글을 통해 평소 웹 어플리케이션의 발전 과정에 대해 궁금하셨던 분들의 요구가 충족되었으면 하는 바람입니다.&lt;/p&gt;
&lt;p&gt;감사합니다.&lt;/p&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;프로가 되기 위한 웹기술 입문 - 위키북스&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.theserverside.com/news/1364527/Introduction-to-the-Spring-Framework&quot;&gt;Introduction to the Spring Framework - Rod Johnson&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://cds.cern.ch/record/369245/files/dd-89-001.pdf&quot;&gt;Information Management: A Proposal - Tim Berners-Lee, CERN&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/History_of_the_World_Wide_Web&quot;&gt;History of World Wide Web -Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Web_server&quot;&gt;Web Server - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTML&quot;&gt;HTML - MDN Web Docs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.oracle.com/cd/B12166_01/web/B10321_01/overview.htm&quot;&gt;Servlet Overview - Oracle&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.oracle.com/cd/B14099_19/web.1012/b14014/genlovw.htm#i1005577&quot;&gt;JSP Overview - Oracle&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://download.oracle.com/otn_hosted_doc/jdeveloper/1012/developing_mvc_applications/adf_aboutmvc2.html&quot;&gt;About the Model 2 Versus Model 1 Architecture - Oracle&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.oracle.com/javaee%2F6%2Fapi%2F%2F/javax/ejb/SessionBean.html&quot;&gt;Session Bean Interface - Oracle&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/overview.html&quot;&gt;Spring Framework Overview - Spring&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[어떤 경우에 한 방 조인 쿼리와 여러 개 쿼리 분리를 고려할 수 있을까?]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-10-09-join-query-vs-multiple-quries/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-10-09-join-query-vs-multiple-quries/</guid><pubDate>Mon, 13 Nov 2023 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;최근에 프로젝트에서 리팩토링과 동시에 쿼리 개선을 하고 있는데요. 조인 한 방 쿼리로 &lt;strong&gt;한 번에 끝내는 게 여러 번 네트워크 타는 것보다 좋을 것&lt;/strong&gt; 같아서 한 방 쿼리로 수정하고 있다가 다음과 같이 모든 쿼리가 조인이 덕지 덕지 붙어있는 코드를 보고 뭔가 이상함을 감지했습니다.&lt;/p&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; pet_food p1_0
&lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt; brand b1_0 &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; b1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;p1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;brand_id
&lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt; pet_food_primary_ingredient p2_0 &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; p1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;p2_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pet_food_id
&lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt; primary_ingredient p3_0 &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; p3_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;p2_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;primary_ingredient_id
&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; p1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; review r1_0
&lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt; pet p1_0 &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; p1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;r1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pet_id
&lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt; breed b1_0 &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; b1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;p1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;breed_id
&lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt; pet_size p2_0 &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; p2_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pet_size_id
&lt;span class=&quot;token keyword&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt; helpful_reaction h1_0 &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; r1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;h1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;review_id
&lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt; adverse_reaction a1_0 &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; r1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;a1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;review_id
&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; r1_0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;?&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;뭔가 아닌 거 같은데..?&lt;/code&gt; 물론 한 방 쿼리가 좋을 때도 있지만 항상 한 방 쿼리가 좋진 않을 것인데 &lt;strong&gt;언제 한 방 쿼리를 사용하고 언제 여러 개 쿼리를 사용&lt;/strong&gt;할 수 있을지 궁금증이 생겼습니다.
​&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;h2&gt;조인(Join)으로 한 방 쿼리 vs 쿼리 여러 개&lt;/h2&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;No Silver Bullet - 만병통치약은 없다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;어떤 성능 시나리오에서든 어느 솔루션이 더 빠른지 확인하기 위해 직접 솔루션을 테스트하고 측정해야 합니다.&lt;/strong&gt; 즉, 최선의 경우는 없는데요. 하지만, 일반적으로 어떤 상황에서 쓰면 좋을지는 통계가 있을 것입니다. 테이블 관계를 기준으로 한번 살펴봅시다.
​&lt;/p&gt;
&lt;h3&gt;일대일 관계&lt;/h3&gt;
&lt;p&gt;​
일반적으로 일대일 관계 같은 &lt;strong&gt;많은 외부 레코드를 가리키지 않는 경우&lt;/strong&gt; 다음과 같은 성능을 나타냅니다.&lt;/p&gt;
&lt;p&gt;​
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1290px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/636920497aec5a6398404e4910dc0c52/629d2/2023-10-09-join-vs-multiple-performance_graph.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABFUlEQVR42o2R227EMAhE8//fWfWhD+0mvoKB6dhJq652VyrSyITEZ4BsYMRUTMXSs3heZd0MJVfk2lBbx2bmkN7hqnDeuhPfhZ8mvs5HeskJb+8fSISaO7b9KJDWgLzDjxu8JITIMpj3fuBkn7mf8GnmBOgIdBl858uAwIxcOoQFY/tsl/B0igZLNAwVYGqMtZ94sYbtRmApisSOcgwcPpDC1pmVRkPR2XU7vtATtX8u+f4FpOMBuzosiTusc6SA8QO/fs5gXmlSaVDgqFN8LiZI1pF5PnQIXCANSA3opcHJfU43pX901Wyc0z8H+rnQCVa1lQ9xtGI04lobOytj5b3ab33mdt29A74K4V5vR1oSUfwnvgF5M8X8Pq0OWwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023 10 09 join vs multiple performance graph&quot;
        title=&quot;&quot;
        src=&quot;/static/636920497aec5a6398404e4910dc0c52/629d2/2023-10-09-join-vs-multiple-performance_graph.png&quot;
        srcset=&quot;/static/636920497aec5a6398404e4910dc0c52/0eb09/2023-10-09-join-vs-multiple-performance_graph.png 500w,
/static/636920497aec5a6398404e4910dc0c52/1263b/2023-10-09-join-vs-multiple-performance_graph.png 1000w,
/static/636920497aec5a6398404e4910dc0c52/629d2/2023-10-09-join-vs-multiple-performance_graph.png 1290w&quot;
        sizes=&quot;(max-width: 1290px) 100vw, 1290px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;​
조인이 가장 빠른데 그 이유는 여러 개 쿼리가 발생하면 데이터베이스에 대한 각 쿼리에 &lt;strong&gt;고정 비용&lt;/strong&gt;이 발생하기 때문입니다. 일대일 관계 같은 경우 중복도 발생하지 않아서 부담 없이 조인을 사용할 수 있을 것 같습니다.&lt;/p&gt;
&lt;p&gt;​
그렇다고 해서, 일대다 관계에서는 절대 조인을 쓰지 말란 것이 아닙니다. 위에서 말한 것에 핵심 포인트는 일대일 관계뿐 아니라 일대다 관계에서도 많은 외부 레코드를 가리키지 않는 경우입니다. 한 방 조인으로 결괏값 주기 vs 여러 번 통신 + 애플리케이션에서 조합의 &lt;strong&gt;트레이드오프&lt;/strong&gt;를 잘 고려해 봐야 됩니다.
​&lt;/p&gt;
&lt;h3&gt;일대다 관계&lt;/h3&gt;
&lt;p&gt;​
하지만, 참조된 레코드 중 다수가 동일할 수 있는 &lt;strong&gt;일대다 관계&lt;/strong&gt; 같은 경우 조인 시에 중복이 엄청나게 발생할 수 있으니 잘 생각해 봐야 됩니다.
​&lt;/p&gt;
&lt;p&gt;예를 들어봅시다.
​&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 670px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/05f9ff9efd6579a443ab577404f3c5d3/6bbca/2023-10-09-join-vs-multiple-entities.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 23.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA0klEQVR42jWQx66EUAxD+f9/A4QQGzZvJMqjiN6bPHMisbBubhI7cZzzPDWOo9q21bqu2rZNx3Houi5RA/d92x8QU5+m2WK4YN93Lcsip65rhWEoz/N+TZPmeTbRYRgNxH+fj5EgUIec5/9qmkZpmgoNcl3XyfF9X67rKkkSI/R9r7IslWWZ4jhWEAQ/cm7CiEGuqkpRFFkNHmIsg0sHMkLP85gFLPJno6IobAuaAblhGEycPOBM8OCbZZpIMv219N6NydyLFyBEjVu+J3g3J2boF24jfcR905RtAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023 10 09 join vs multiple entities&quot;
        title=&quot;&quot;
        src=&quot;/static/05f9ff9efd6579a443ab577404f3c5d3/6bbca/2023-10-09-join-vs-multiple-entities.png&quot;
        srcset=&quot;/static/05f9ff9efd6579a443ab577404f3c5d3/0eb09/2023-10-09-join-vs-multiple-entities.png 500w,
/static/05f9ff9efd6579a443ab577404f3c5d3/6bbca/2023-10-09-join-vs-multiple-entities.png 670w&quot;
        sizes=&quot;(max-width: 670px) 100vw, 670px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
​&lt;/p&gt;
&lt;p&gt;위와 같은 관계에서 1개의 게시물이 있고 게시물에는 2개의 댓글과 2개의 태그가 있다고 가정해 봅시다.&lt;/p&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tag&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; post
&lt;span class=&quot;token keyword&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id
&lt;span class=&quot;token keyword&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; tag &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; tag&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
​
&lt;span class=&quot;token comment&quot;&gt;-- post_id comment_id tag_id&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        1           1&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        1           2&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        2           1&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        2           2&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;p&gt;위와 같은 쿼리를 실행하면 2 * 2로 4개의 결과가 나왔습니다. 여기서 댓글과 태그가 4개로 늘어난다면 어떻게 될까요?&lt;/p&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tag&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; post
&lt;span class=&quot;token keyword&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id
&lt;span class=&quot;token keyword&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; tag &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; tag&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
​
&lt;span class=&quot;token comment&quot;&gt;-- post_id comment_id tag_id&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        1           1&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        1           2&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        1           3&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        1           4&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        2           1&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        2           2&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        2           3&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        2           4&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        3           1&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        3           2&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        3           3&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        3           4&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        4           1&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        4           2&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        4           3&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1        4           4&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;p&gt;각 행의 곱인 4 * 4 = 16개의 행이 나오게 됩니다. 그럼 만약에 각 데이터의 수가 1000개씩만 되어도 나오는 행의 수는 몇 개일까요..? 여기서 더 &lt;strong&gt;많은 테이블과 레코드들을 추가하면 수많은 중복된 데이터가 생겨날 것입니다.&lt;/strong&gt;
​
실제 테스트를 해봅시다. 실험 환경은 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mac M1 Pro 16G 512GB&lt;/li&gt;
&lt;li&gt;실험 데이터 수는 게시글 1개, 댓글 1000개, 태그 1000개&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;p&gt;먼저 조인을 이용하여 100만(1000 * 1000)개의 테이블을 생성해 결괏값 100만 개를 내어줄 때는 &lt;strong&gt;12초&lt;/strong&gt;가 걸렸습니다.
​&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1178px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/486af2325274fb1cefcf08eddda9bfcf/dc992/2023-10-09-join-vs-multiple-log1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 13.799999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAhElEQVR42lWPWQ7DIAxEOQqhbAbCJvWzau5/qSmDlEr5eDJj7BlQJQmiCEIIkJRgg0eShBgjjuN4oLXe3JozxOaId7D4iIO6ri/GGDjPE713tNZQa8WcEznnXdMKKqXsUGKMebLMWV8LxQQOc4lmNKFmAO+oaXK/RtZvvPdwzv37PLNHfnXVWAk+DfHqAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023 10 09 join vs multiple log1&quot;
        title=&quot;&quot;
        src=&quot;/static/486af2325274fb1cefcf08eddda9bfcf/dc992/2023-10-09-join-vs-multiple-log1.png&quot;
        srcset=&quot;/static/486af2325274fb1cefcf08eddda9bfcf/0eb09/2023-10-09-join-vs-multiple-log1.png 500w,
/static/486af2325274fb1cefcf08eddda9bfcf/1263b/2023-10-09-join-vs-multiple-log1.png 1000w,
/static/486af2325274fb1cefcf08eddda9bfcf/dc992/2023-10-09-join-vs-multiple-log1.png 1178w&quot;
        sizes=&quot;(max-width: 1178px) 100vw, 1178px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;​
하지만, 두 개의 쿼리(1000 + 1000)로 분리하여 결괏값 100만 개를 내어 줄 때는 고작 &lt;strong&gt;0.2초&lt;/strong&gt;밖에 안 걸린 걸 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;​
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1106px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9b5589e00f115d68ff951eddf98972d2/22593/2023-10-09-join-vs-multiple-log2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 12.200000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAACCAYAAABYBvyLAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAb0lEQVR42jWOWwrFIAxEuxMVjRrrExWE7n9dc6+WfoQzyXycXM/syDnDOQdmhmMHzx4hBGitQUSHe6SUUEpBCPF2xkCRwU0aUysskrjmGFhroZSC1hrGf08pofdXtFlrPbcY45F6788DW7bzR2stfoOAO16wQs4yAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023 10 09 join vs multiple log2&quot;
        title=&quot;&quot;
        src=&quot;/static/9b5589e00f115d68ff951eddf98972d2/22593/2023-10-09-join-vs-multiple-log2.png&quot;
        srcset=&quot;/static/9b5589e00f115d68ff951eddf98972d2/0eb09/2023-10-09-join-vs-multiple-log2.png 500w,
/static/9b5589e00f115d68ff951eddf98972d2/1263b/2023-10-09-join-vs-multiple-log2.png 1000w,
/static/9b5589e00f115d68ff951eddf98972d2/22593/2023-10-09-join-vs-multiple-log2.png 1106w&quot;
        sizes=&quot;(max-width: 1106px) 100vw, 1106px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
​
​
여러 개의 테이블을 조인하는 경우도 아래의 벤치마크를 한번 봅시다. 두 경우 모두 동일한 결과(6 x 50 x 7 x 12 x 90 = 2268000)를 얻지만 여러 개의 쿼리가 훨씬 더 빠릅니다.
​&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;5개의 조인을 사용한 단일 쿼리&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;쿼리: &lt;strong&gt;8.074508초&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;결과 크기: 2268000&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;연속 쿼리 5개&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;결합된 쿼리 시간: &lt;strong&gt;0.00262초&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;결과 크기: 165(6 + 50 + 7 + 12 + 90)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;p&gt;또한, 중복 데이터로 인해 더 많은 &lt;strong&gt;메모리&lt;/strong&gt;를 사용합니다. 적은 테이블의 수를 조인하는 경우 메모리 사용량이 적겠지만, 테이블이 점점 늘어날수록 차지하는 메모리가 기하급수적으로 늘기 때문에 잘 고려하면 좋을 것 같습니다.&lt;/p&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;h3&gt;조인이 적합한 지 여부 판단&lt;/h3&gt;
&lt;p&gt;​
조인을 사용해야 하는지는 조인이 &lt;strong&gt;적합한지&lt;/strong&gt;에 따라 판단해 볼 수도 있습니다. 위의 예시로 사용한 테이블에서 댓글과 태그는 게시글과는 관련이 있지만 서로는 관련이 없습니다. 이 경우 두 개의 별도 쿼리를 사용하는 것이 더 좋습니다. 태그와 댓글을 결합하려고 하면 둘 사이에는 직접적인 관계가 없는데도 각 테이블행의 곱만큼 가능한 모든 조합이 생성됩니다. 또한, 두 쿼리를 병렬로 수행하여 이득을 얻을 수도 있습니다.&lt;/p&gt;
&lt;p&gt;​
하지만, 여기서 만약에 다음과 같은 상황을 생각해 봅시다.&lt;/p&gt;
&lt;p&gt;​
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 697px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/665449e94bd3520f9c11844f6017733e/11663/2023-10-09-join-vs-multiple-entities-modified.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 27.200000000000003%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAyElEQVR42lWQWYqFQAxFa/8bcweiIIgoiji1cznexwn40cI1IcNJUs57r/u+dZ6n9n03exyHxaZpMr/rOhP+tm16nuef3vc14bu6rlVVldI0VVmWWtdVfd9bQVEUSpJEQRAoDEPLjeNo4OFvlPe78jy3wcuy2EKuaRrFcawsy2w6YhuKiAEEDIxc27Ym4lEU2TKA5nk2qOMHFPudS5KTsWyL5eMkoADoIccgRA3WUQCMt/w2RJzMecMwWP5rxAfMYHqAI/zruvQDh79/H32fya4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2023 10 09 join vs multiple entities modified&quot;
        title=&quot;&quot;
        src=&quot;/static/665449e94bd3520f9c11844f6017733e/11663/2023-10-09-join-vs-multiple-entities-modified.png&quot;
        srcset=&quot;/static/665449e94bd3520f9c11844f6017733e/0eb09/2023-10-09-join-vs-multiple-entities-modified.png 500w,
/static/665449e94bd3520f9c11844f6017733e/11663/2023-10-09-join-vs-multiple-entities-modified.png 697w&quot;
        sizes=&quot;(max-width: 697px) 100vw, 697px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;​
게시물에 댓글을 달고 댓글 작성자의 이름까지 원한다면? 조인을 고려해 볼 수 있습니다. 훨씬 더 자연스러운 쿼리일 뿐 아니라 데이터베이스 시스템에서 이와 같은 쿼리를 &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/optimization.html&quot;&gt;최적화&lt;/a&gt;하려고 노력하고 있다고 합니다.&lt;/p&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;h2&gt;서브쿼리(SubQuery)&lt;/h2&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;p&gt;조인 대신에 &lt;strong&gt;서브 쿼리&lt;/strong&gt;를 이용하는 방법도 있습니다. 서브 쿼리와 조인의 성능을 비교해 봅시다. 우선 MySQL &lt;strong&gt;5.5&lt;/strong&gt; 버전에서 서브 쿼리는 못쓸 정도라고 평할 정도로 제대로 수행되지 않았습니다.&lt;/p&gt;
&lt;p&gt;​
테스트 데이터&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;메인 테이블 100만 건&lt;/li&gt;
&lt;li&gt;서브 테이블1 (Index O) 1000건&lt;/li&gt;
&lt;li&gt;서브 테이블2 (Index X) 1000건&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;​
수행 시간&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MySQL 5.5 + 서브 쿼리 + No Index에서 100만 건 &amp;#x26; 1천 건 조회에 &lt;strong&gt;3분&lt;/strong&gt;이 소요&lt;/li&gt;
&lt;li&gt;MySQL 5.5 + 서브 쿼리 + Index에서 100만 건 &amp;#x26; 1천 건 조회에 &lt;strong&gt;1.8초&lt;/strong&gt; 소요&lt;/li&gt;
&lt;li&gt;MySQL 5.5 + 조인 + No Index에서 100만 건 &amp;#x26; 1천 건 조회에 &lt;strong&gt;11초&lt;/strong&gt; 소요&lt;/li&gt;
&lt;li&gt;MySQL 5.5 + 조인 + Index에서 100만 건 &amp;#x26; 1천 건 조회에 &lt;strong&gt;0.139초&lt;/strong&gt; 소요&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;​
MySQL 특정 버전(5.5이하)에서는 서브 쿼리 대신 조인이 압도적으로 빠른 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;MySQL에서는 무슨 일이 있었기에 5.5 버전에서는 서브 쿼리 성능이 좋지 않을까요?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;최적화 부재: 초기 버전의 MySQL에서는 서브 쿼리에 대한 충분한 최적화가 이루어지지 않았습니다. 그 결과 서브 쿼리는 복잡한 연산을 필요로 하는 경우에 매우 비효율적으로 동작하게 되었습니다.&lt;/li&gt;
&lt;li&gt;재귀적 처리: MySQL 5.5에서 서브 쿼리는 종종 재귀적으로 처리되었고, 이는 성능 문제를 야기했습니다. 특히, 상위 쿼리에서 서브 쿼리의 결과를 여러 번 재참조해야 할 때 이 문제가 두드러졌습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;5.6&lt;/strong&gt;에서는 &lt;strong&gt;서브 쿼리 성능 개선&lt;/strong&gt;이 많이 이루어졌습니다.
​&lt;/p&gt;
&lt;p&gt;수행 시간&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MySQL 5.6 + 서브 쿼리 + No Index에서 100만 건 &amp;#x26; 1천 건 조회에 &lt;strong&gt;19초&lt;/strong&gt; 소요&lt;/li&gt;
&lt;li&gt;MySQL 5.6 + 서브 쿼리 + Index에서 100만 건 &amp;#x26; 1천 건 조회에 &lt;strong&gt;0.18초&lt;/strong&gt; 소요&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;p&gt;3분에서 19초, 11초에서 0.18초 개선될 정도로 5.6에서는 최적화가 이루어졌지만 아직 &lt;strong&gt;모든 서브 쿼리가 다 최적화가 된 것은 아니라고 합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;​
즉, 웬만하면 &lt;strong&gt;최대한 조인을 이용&lt;/strong&gt;하고 조인을 이용하기 어렵다면 5.6 이상은 서브 쿼리를 사용합니다. 5.5 이하는 절대 사용하지 않고 차라리 쿼리를 나눠서 2번(메인 쿼리, 서브 쿼리) 실행하고 애플리케이션에서 조립하면 좋을 것 같습니다. 물론, 스칼라 서브 쿼리 캐싱 등의 최적화 기술로 때때로 서브 쿼리가 조인보다 더 효율적일 때도 있습니다.&lt;/p&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;p&gt;사실 위의 내용은 어느 정도 통계에 기반에서 이렇다 할 뿐이지 자신의 상황에서는 전혀 다르게 동작(고려해야 할 변수가 많기 때문에)할 수도 있습니다. 그러므로 &lt;strong&gt;참고만 하고&lt;/strong&gt; EXPLAIN 같은 실행 계획이나 &lt;strong&gt;직접 테스트하여 측정해 사용&lt;/strong&gt;하도록 합시다.&lt;/p&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Don’t guess, measure!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;​&lt;/h2&gt;
&lt;p&gt;참고:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/1067016/join-queries-vs-multiple-queries&quot;&gt;JOIN queries vs multiple queries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jojoldu.tistory.com/520&quot;&gt;MySQL where in (서브쿼리) vs 조인 조회 성능 비교 (5.5 vs 5.6)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[레이어드 아키텍처에서 이미지 업로드하기]]></title><description><![CDATA[[Android] 레이어드 아키텍처에서 이미지 업로드하기 배경 저는 현재 ‘나아가’라는 우테코 프로젝트와 ‘ZOOC…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-10-17-image-upload/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-10-17-image-upload/</guid><pubDate>Mon, 13 Nov 2023 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;[Android] 레이어드 아키텍처에서 이미지 업로드하기&lt;/h1&gt;
&lt;h2&gt;배경&lt;/h2&gt;
&lt;p&gt;저는 현재 ‘나아가’라는 우테코 프로젝트와 ‘ZOOC’이라는 사이드 프로젝트에서 안드로이드 개발을 담당하고 있습니다. 두 프로젝트에서 모두 이미지 업로드를 위해 &lt;code class=&quot;language-text&quot;&gt;multipart/form-data&lt;/code&gt;를 사용하고 있습니다.&lt;/p&gt;
&lt;p&gt;프로젝트 동안 다른 팀원들의 이미지 업로드 기능 구현을 리뷰하면서 새로운 방식이 떠올랐습니다. 떠오른 것을 깊이 고민하고 비교하며 더 좋은 방안을 생각했고 그 결과를 팀에 제안했습니다. 이 글에서 해당 내용에 대해 나누려고 하며 단순한 이미지 업로드 기능 구현 방법뿐만 아니라, 아키텍처 관점에서 해당 구현을 어디에서 하면 좋을지가 그 내용입니다.&lt;/p&gt;
&lt;p&gt;만약 이미 이미지 업로드 기능 구현 경험이 있고 아키텍처 관점만이 궁금하시다면 &lt;a href=&quot;#%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B5%AC%EC%A1%B0%EC%99%80-%EA%B5%AC%ED%98%84-%EC%98%88%EC%8B%9C&quot;&gt;프로젝트 구조와 구현 예시&lt;/a&gt;부터 보셔도 좋습니다.&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;HTTP multipart/form-data란?&lt;/h2&gt;
&lt;p&gt;안드로이드 개발을 하다 보면 이미지 업로드 기능을 종종 마주치게 됩니다. 이미지 파일을 서버에 전송하는 방법은 여러 가지가 있지만, 그중에서 말씀드릴 내용은 &lt;code class=&quot;language-text&quot;&gt;multipart/form-data&lt;/code&gt;를 이용하는 것입니다.&lt;/p&gt;
&lt;p&gt;안드로이드 개발을 하다 보면, TCP/IP 기반의 HTTP 프로토콜 통신 과정에서 JSON 데이터를 주로 주고받습니다. 클라이언트와 백엔드가 서로에게 필요한 데이터를 JSON 타입으로 주고받는 것이죠. 이때 주고받을 데이터의 타입을 (정확히는 MIME 타입을) HTTP 헤더에 &lt;code class=&quot;language-text&quot;&gt;Content-type&lt;/code&gt;으로 명시하게 되는데, JSON의 경우 &lt;code class=&quot;language-text&quot;&gt;Content-type&lt;/code&gt;이  &lt;code class=&quot;language-text&quot;&gt;application/json&lt;/code&gt;이 됩니다. 사진이나 mp3 같은 “파일”들의 &lt;code class=&quot;language-text&quot;&gt;Content-type&lt;/code&gt;은 어떻게 될까요? jpg나 jpeg 이미지 파일은 &lt;code class=&quot;language-text&quot;&gt;image/jpeg&lt;/code&gt;, mp3 파일은 &lt;code class=&quot;language-text&quot;&gt;audio/mpeg&lt;/code&gt; 형식으로 통신할 수 있습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;파일 종류&lt;/th&gt;
&lt;th&gt;Content-type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;jpg, jpeg&lt;/td&gt;
&lt;td&gt;image/jpeg&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mp3&lt;/td&gt;
&lt;td&gt;audio/mpeg&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;json&lt;/td&gt;
&lt;td&gt;application/json&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;그렇다면 &lt;code class=&quot;language-text&quot;&gt;multipart/form-data&lt;/code&gt;는 무엇일까요?&lt;/em&gt;&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 360px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/882424b622a22102989bc316d7f11a40/53918/2023-10-17-image-upload-6.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 177.77777777777777%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAkCAYAAACJ8xqgAAAACXBIWXMAAAsTAAALEwEAmpwYAAACHUlEQVR42u2WyU7bUBSG8yhdVio8Q7d9jAqJrihFVKqE6CrtE6CyKR2EUBdtl7BD0E2lLoqKFCCErDIoiTPPo+37k/8mNnYG+2LY0Sv98p3O53Psc65uCMA+Rs1E8GbZ7oc4GDZ0Op07yTAMCSZQcKJWq6FerwcSbckgi0C022250Gg0PNVqtdBsNqfmaUsGmwSSrgLMZrPI5/MzgWMPvYEMpVKpyKemaYjFYojH43IcCFitVqVH5XJZhppOp6WXgT2kd/Qsl8shlUpJ8KxP4Qtknx7RO4ZIRaNRJBKJYEDnRoZtpdTkt7s1UFVTQNU89AK68rDX690ZSIYNxKigZT1OivMqslrI7xgRQijL9VOKxSJKpZLMNT45Zp+5yBTyEsPudrs3QBZ9MpmUymQysiIssTJUThvXT3F+M13Xp8b9ft9X4/NwBKQR30AxfGefoQwGA1+5gHwDQ2XILDeGaalQKMh1poWXCLWBHMwLxQ80Fzi5gaGqwpSAt9V/4IMDqtarl8iwgSaEp3jSCZ89JhznITcbwpwpXRjom7rUYNift084gbupYyyfbuNV5BNeRnZsrUQ+Yv3sCzaie3hzsYvX519d6xRtaEuGBRRLpx/w6PAFFn+t4cnxqtTCUI+PVvD09ybWzj7j+b8tPPsTttct0Ya2ZFjXOfNAO8Hby294f/UT765+uBSOfUd4/Jxco2hD2wPtr33hvNcr8TUPcrXxu8omjQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 10 17 image upload 6&quot; title=&quot;&quot; src=&quot;/static/882424b622a22102989bc316d7f11a40/53918/2023-10-17-image-upload-6.png&quot; srcset=&quot;/static/882424b622a22102989bc316d7f11a40/53918/2023-10-17-image-upload-6.png 360w&quot; sizes=&quot;(max-width: 360px) 100vw, 360px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;위의 화면처럼 생긴 일기를 작성하는 기능을 예시로 글을 진행하겠습니다. 해당 일기에는 사진, 제목, 내용 이렇게 세 가지 정보가 들어갑니다. 일기를 “생성”해야 하므로 서버로 POST 요청을 보내야 합니다. POST 요청을 보내기 위해 HTTP 요청의 본문인 body에 사진, 제목, 내용을 함께 담으려고 하는데 잠깐..! &lt;code class=&quot;language-text&quot;&gt;Content-type&lt;/code&gt;을 무엇으로 지정해야 할까요?&lt;/p&gt;
&lt;p&gt;이렇게 &lt;strong&gt;여러 타입의 데이터를 한 번에 전송하고 싶을 때&lt;/strong&gt; 우리는 &lt;code class=&quot;language-text&quot;&gt;multipart/form-data&lt;/code&gt; 형식을 활용합니다. &lt;code class=&quot;language-text&quot;&gt;multipart&lt;/code&gt;라는 말 그대로 바디에 해당하는 데이터가 여러 부분(타입)으로 이루어져 있다고 생각하시면 됩니다. 이럴 때 우리는 &lt;code class=&quot;language-text&quot;&gt;Content-type&lt;/code&gt;으로 &lt;code class=&quot;language-text&quot;&gt;multipart/form-data&lt;/code&gt;를 사용하게 됩니다.&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;이미지 가져오기&lt;/h2&gt;
&lt;p&gt;지금까지 &lt;code class=&quot;language-text&quot;&gt;multipart/form-data&lt;/code&gt;에 대해 간략하게 알아봤습니다. 이제 이미지 업로드 과정을 살펴볼 차례인데요, 이미지 파일을 서버에 전송하려면 우선 이미지가 있어야겠죠? 제가 진행 중인 두 프로젝트에서는 다른 방식으로 이미지를 가져옵니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;디바이스에서 저장되어 있는 이미지를 가져오는 것&lt;/li&gt;
&lt;li&gt;사진을 바로 촬영해서 가져오는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이미지를 가져오기 전에 먼저 알아야 할 개념이 있습니다. 안드로이드에서 이미지 정보를 가져올 때 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt; 객체로 가져오게 되는데, Uri에 대해 잠시 설명하고 넘어가겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%86%B5%ED%95%A9_%EC%9E%90%EC%9B%90_%EC%8B%9D%EB%B3%84%EC%9E%90&quot;&gt;URI&lt;/a&gt;는 ‘Uniform Resource Identifier’로 한글로 하면 ‘통합 자원 식별자’입니다. 즉, 자원(리소스)를 식별하는 고유한 아이디라고 볼 수 있습니다. URI의 개념 자체는 웹에서 출발했지만 인터넷상의 자원에 국한되지는 않습니다.&lt;/p&gt;
&lt;p&gt;안드로이드에서는 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;라는 클래스가 존재하며 &lt;code class=&quot;language-text&quot;&gt;android.net&lt;/code&gt; 패키지에 있습니다. 이는 안드로이드 프레임워크 내의 자원에 대한 식별자이며 이를 통해 사진, 음성 파일 등의 디바이스 데이터에 접근할 수 있습니다. 지금부터 글에서 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;라 함은 방금 설명한 안드로이드 프레임워크의 클래스를 지칭하는 것으로 하겠습니다.&lt;/p&gt;
&lt;h3&gt;디바이스에서 저장되어 있는 이미지 가져오기&lt;/h3&gt;
&lt;p&gt;존재하는 이미지를 가져올 때는 &lt;code class=&quot;language-text&quot;&gt;ActivityResultContracts.PickVisualMedia()&lt;/code&gt; 를 사용하고 있습니다. (&lt;a href=&quot;https://developer.android.com/training/data-storage/shared/photopicker?hl=ko&quot;&gt;공식문서&lt;/a&gt;)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; launcher &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;registerForActivityResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ActivityResultContracts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;PickVisualMedia&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; uri&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Uri&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
	uri&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// uri 사용하기 }                                                                  &lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                                                                                        
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;selectImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    launcher&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;PickVisualMediaRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ActivityResultContracts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PickVisualMedia&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ImageOnly&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같이 &lt;code class=&quot;language-text&quot;&gt;launcher&lt;/code&gt; 를 선언하고 &lt;code class=&quot;language-text&quot;&gt;launch&lt;/code&gt; 함수를 호출하면 아래 사진과 같이 이미지를 가져올 수 있는 다이얼로그가 나옵니다. &lt;em&gt;(Android 13 / One UI 5.1)&lt;/em&gt;&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1078px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/3473e22b482c4b0712dcf460faca39f3/fe9f0/2023-10-17-image-upload-1.jpg&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 214.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wgARCAArABQDASIAAhEBAxEB/8QAHgAAAgEDBQAAAAAAAAAAAAAAAAoGAQcJAgMEBQj/xAAYAQEAAwEAAAAAAAAAAAAAAAAAAQMEBf/aAAwDAQACEAMQAAAB8PbMz0EWJADF9VS+dpva4E7iV/LXZ4uwgvuZ6zoU/wD/xAAdEAACAgMBAQEAAAAAAAAAAAADBQIEAQYHABAX/9oACAEBAAEFAvRhmPyKF7OA0T0uCqWteRujaNgY+haLKP6Jz+XqdHVhq7oFYqtivSiRjsxGikdjNqTWlfYW3vIOdkm35XoIJL+XaKWv/8QAJBEAAgADBwUAAAAAAAAAAAAAARECEjEAAyEiQVFhEEJTcaL/2gAIAQMBAT8B6S1LQEvaUGtTBWo2KysI2y+S7+uOLGSB3cAlg1EMIALrgCAN1vrVm7heBK9C3//EACQRAAIBAwQABwAAAAAAAAAAAAECAwQREgATMVEQISIjMlJi/9oACAECAQE/AfBmjVoadkxnmY4WaMSOAMrCKWZHf6+xDIQ1srDLRhqbnGjqSL+R2uR38tPkKlJ5ZTWTwMXpppowkirIq+ljnMVuoAbFiLDg2Gkr6oqCWw/KbRUdWLQA8d6//8QALhAAAgEDAgQEBAcAAAAAAAAAAQIDBAURABIGEyEiFCMxUTZBYnEQMjM0coHR/9oACAEBAAY/AtfqSP8Az2+/0qv2+34CVLJeXjZOYsiWm4sjIRuDqy0xDKV6grkEemspYb96A91kui9D6HupB/vuNBJ7Xc4HI3BZrfWxMVyRuCvAp25BGfTII+WlMPGXDTENIoig4osaZEY+qrKYC923KlFPfj00GPHnDq9u593E9k8pQQAWPPHRiQu4ZGWwcEjXxxwg3y+I7Qf6/d64XnFzSpvCV1xe/UElC/hFoZUqDSinrBUP44yxhebTCjpHBd1MsjbNSRzUqUqxyxsKiop5w703iU51PMHpIMSw+T2RSpzV5bvyGj3N56Uyl98kRmNXGzwPLIYWCtH0QJiNer/k6yOcto089OqW+ShNPFUQW6lptrU1IVBkIWnmndljwMZ38zrKzBtQl0kqahKJaxVe1AxGlSopDyjmuYtQxRxb5FwVjZpGErdVBqq0XOqldV2PFbw8aQ9eXCmKtNiRA7VjIyFx65zps8PbVFB2xw3W+QQLttkca7aeC5Rwjt9o+r+YfM7tWieDh6KGWHguCJGirbonYrQY3Ba0LIfd5AzsCwZiGbPm2PncpjFGZbpeZSkQCssatJcGIjTcdiZ2oO1QFAGv/8QAHhABAQEAAwACAwAAAAAAAAAAAREhADFBUWEQcYH/2gAIAQEAAT8hneu3fS/H68t+7zucEiU7X1H8fC7+Bs0oVqkoeSFonMISGm7AM08QHhw4YWoAolHEqpUCgdN28iUEkCAeNJFNR0Ox3iKM81uDStHulSd2fd94rqcuAfQh2tVzdlJWrHQiw1y4Ol5Erhq/Az7mWh1AoH1NcwvbodF0PFCx8FeLXWMLmeqhkSoCl7aVRlBemWlm89f2jrJ84hdQLgENS0bGWGdcFD//2gAMAwEAAgADAAAAECTBgsTf/8QAHBEBAQABBQEAAAAAAAAAAAAAAREhABAxQZFR/9oACAEDAQE/EI/HzZApISIk6EsGZVZKoW8AYwlHayaZ4pQikUHZkHEQrOCsFyVSrSxGohwUHtX1df/EABoRAQEBAQEBAQAAAAAAAAAAAAERITEAEFH/2gAIAQIBAT8Qj+fOgNdTrLGFUWnQMAqAGDnDoWSmDvMTp/QMjQ6QAg9RSTkPYGEwsY5U33//xAAaEAEBAQEBAQEAAAAAAAAAAAABEQAhMUEQ/9oACAEBAAE/EFIIeAQihaIfUOvhzMKasj6ifHcLfBQ/AlbUYmNoYtlJ5hpDUgXQW5gnmasM8tZEcyp7hhtSUEDFx+HmvqCDZ+ZOKBCQZWPgdFjoG6QMe2eFfsV2OpZ9hGZ5Iw9f3FmyhpXMfbe0URV1Vr5ybIrgVqVZfBJDMsFTsIA/gQdbSyeT+3vbjaowHnalrithaqQndY5rt1lRBJjFVFidP//Z&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 10 17 image upload 1&quot; title=&quot;&quot; src=&quot;/static/3473e22b482c4b0712dcf460faca39f3/fe9f0/2023-10-17-image-upload-1.jpg&quot; srcset=&quot;/static/3473e22b482c4b0712dcf460faca39f3/953fe/2023-10-17-image-upload-1.jpg 500w,
/static/3473e22b482c4b0712dcf460faca39f3/0a251/2023-10-17-image-upload-1.jpg 1000w,
/static/3473e22b482c4b0712dcf460faca39f3/fe9f0/2023-10-17-image-upload-1.jpg 1078w&quot; sizes=&quot;(max-width: 1078px) 100vw, 1078px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;사진을 선택하면 &lt;code class=&quot;language-text&quot;&gt;launcher&lt;/code&gt;의 콜백에서 해당 사진의 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;를 받을 수 있습니다.&lt;/p&gt;
&lt;h3&gt;사진을 바로 촬영해서 가져오기&lt;/h3&gt;
&lt;p&gt;앞선 방식이 디바이스에 이미 존재하는 사진을 가져온다면, 이번 방식은 사진을 직접 촬영하고 바로 가져옵니다. 다양한 방식이 존재하겠지만 해당 프로젝트에선 &lt;code class=&quot;language-text&quot;&gt;ActivityResultContracts.TakePicture()&lt;/code&gt;를 사용합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; imageUri&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Uri&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; launcher &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;registerForActivityResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ActivityResultContracts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;TakePicture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; success&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Boolean
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;success&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// TODO: uri 사용하기&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;startCamera&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    imageUri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createImageUri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getOrElse&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// TODO: 오류 처리&lt;/span&gt;
       	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    launcher&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageUri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 매개변수로 전달한 Uri로 사진이 저장됩니다.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// uri 생성하기&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createImageUri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Result&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Uri&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; contentValues &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getContentValues&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; uri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; contentResolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            MediaStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Images&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Media&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EXTERNAL_CONTENT_URI&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            contentValues&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;failure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// TODO: 원하시는 Throwable을 담으시면 됩니다!)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getContentValues&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ContentValues &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ContentValues&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// TODO: 원하시는 칼럼을 추가하시면 됩니다!&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;권한과 관련한 코드 및 설명은 생략합니다&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ActivityResultContracts.TakePicture()&lt;/code&gt; 를 사용하게 되면 콜백 함수는 성공 여부를 반환하게 되고, &lt;code class=&quot;language-text&quot;&gt;launcher.launch()&lt;/code&gt; 함수에 우리가 직접 생성한 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;를 담고 해당 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;로 사진이 저장됩니다. 사진을 저장하지 않고 보여주고 싶다면 &lt;code class=&quot;language-text&quot;&gt;ActivityResultContracts.TakePicturePreview()&lt;/code&gt; 도 존재하지만, 다음의 설명처럼 화질이 저하된 비트맵을 콜백으로 받아보실 수 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An ActivityResultContract to take small a picture preview, returning it as a Bitmap.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;프로젝트에 사용된 사진을 가져오는 두 가지 방법에 대해 알아보았습니다. 위 방법보다 좋은 방법들이 있을 수 있습니다. 다만 사진을 가져오는 것 자체가 글의 주제가 아니므로 넘어가도록 하겠습니다. 어찌 됐든 우리는 두 방식에서 모두 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;를 얻었고 이제 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;를 이용해서 이미지를 서버로 보내야 합니다!&lt;/p&gt;
&lt;p&gt;두 프로젝트 모두 서버 통신에서 &lt;code class=&quot;language-text&quot;&gt;Retrofit&lt;/code&gt; 라이브러리를 사용 중입니다. 그래서 최종적으로 이미지 및 함께 전송할 데이터들을 &lt;code class=&quot;language-text&quot;&gt;Retrofit&lt;/code&gt;에서 지원하는 &lt;code class=&quot;language-text&quot;&gt;RequestBody&lt;/code&gt; 혹은  &lt;code class=&quot;language-text&quot;&gt;MultipartBody.Part&lt;/code&gt; 타입으로 만들어야 합니다.&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;Uri가 Part가 되는 과정&lt;/h2&gt;
&lt;p&gt;지금까지 보내고자 하는 이미지의 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;를 얻었습니다. 이제 이미지를 보내기만 하면 됩니다! 어떻게 하면 될까요? &lt;code class=&quot;language-text&quot;&gt;imageUri.toPart()&lt;/code&gt; 하면 우리가 원하는 형태가 나올까요? 아쉽게도 그런 함수는 없습니다. 그럼 어떤 과정을 거쳐야 할지 지금부터 알아보겠습니다!&lt;/p&gt;
&lt;p&gt;우선 최종적으로 서버에 어떤 형태로 데이터를 전송할지 보고 시작하면 좋을 것 같습니다. 그래서 API 명세서와 &lt;code class=&quot;language-text&quot;&gt;Retrofit&lt;/code&gt;을 통해 명세대로 작성한 서비스 인터페이스를 먼저 보도록 하죠!&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 913px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/f2d9b2666b464b67c7b5b1efcc38f8ea/200fa/2023-10-17-image-upload-7.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 64.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABJ0AAASdAHeZh94AAABMElEQVR42q1S7W6DMAzs+78lmwaohZIPkpCQ5GaHUlFA/bHOkhVj7HNy54uUAk1To+tukFLiU7torfD9VaGqKggC/xjQWYumrjFNDv9hBGjQXVtMzsA7CzH08JNF8K44x6dO9XEOR8AQPAYC0UrC+4kA71AUSzqNGZFSRIzzqfO/A+A8zwVAkSAcM6fDfYAQApboeGc55yMgJ1NKZdoaxxgfucRdS+PO85rfA/LNmvqnrM31dkXbtgvQX0Vh3rQWcHYsbo1eBHqQz7Ex6kUgjrUSGMfXfM6JAT012YV84jDF5dw6Cxaobv3mGq2Y677k07z0MAVFFGPMKR+rsVBct7XJORJuOOQLYNd3z8XOG8JX50ZFN+LBK7+OXsVbsK8tKvOz3wnBQ7kmhPCyMmcq/wKV0v7ne5KacgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 10 17 image upload 7&quot; title=&quot;&quot; src=&quot;/static/f2d9b2666b464b67c7b5b1efcc38f8ea/200fa/2023-10-17-image-upload-7.png&quot; srcset=&quot;/static/f2d9b2666b464b67c7b5b1efcc38f8ea/0eb09/2023-10-17-image-upload-7.png 500w,
/static/f2d9b2666b464b67c7b5b1efcc38f8ea/200fa/2023-10-17-image-upload-7.png 913w&quot; sizes=&quot;(max-width: 913px) 100vw, 913px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 898px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/56f2452e904d52fa2dca4d3e68776cdc/3f4de/2023-10-17-image-upload-8.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 63.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABJ0AAASdAHeZh94AAABC0lEQVR42q2S62rDMAyF/f4vONY/HbS0W5f4Gl8Sxz6TPWa6JF2hq+BgkJQPSTls9/qCw/GAt/0eWmuUyDnj0WBdd0Hf9+C8h/ce/w12pOmeMVkDXj5O8GHAODpI0cFag2nyCMHeVUpxDZSSQ0lBDR7GKAhaXSlB0BHzHP9UzmkNTJSsxZTpnRFjrO+j67P38xnpCbdrQME7up/HdEPODavc6B2MVoh0lut82YqF4DDHqRaXKnmtJIL/3VOAvP8kqCRQaPkKvOc9Z+3KnymlarWi5bHYYEz7CUtVoLMk13xaVPoVTX7t359+JgRvE2wBrd0AkhOM0WStadFfbxhqw60oloqL+veU2998ART9AD6NqQImAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 10 17 image upload 8&quot; title=&quot;&quot; src=&quot;/static/56f2452e904d52fa2dca4d3e68776cdc/3f4de/2023-10-17-image-upload-8.png&quot; srcset=&quot;/static/56f2452e904d52fa2dca4d3e68776cdc/0eb09/2023-10-17-image-upload-8.png 500w,
/static/56f2452e904d52fa2dca4d3e68776cdc/3f4de/2023-10-17-image-upload-8.png 898w&quot; sizes=&quot;(max-width: 898px) 100vw, 898px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; DiaryService &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation builtin&quot;&gt;@Multipart&lt;/span&gt;
    &lt;span class=&quot;token annotation builtin&quot;&gt;@POST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;diary&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postDiary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token annotation builtin&quot;&gt;@Part&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RequestBody&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token annotation builtin&quot;&gt;@Part&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RequestBody&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token annotation builtin&quot;&gt;@Part&lt;/span&gt; photo&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MultipartBody&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Part&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Response&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;DiaryDto&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;API 명세서와 코드를 비교해서 보시면 &lt;code class=&quot;language-text&quot;&gt;postDiary()&lt;/code&gt;의 인자에 명세서의 Request Body가 들어가게 되고, 명세서의 Response Body가 &lt;code class=&quot;language-text&quot;&gt;DiaryDto&lt;/code&gt;가 됩니다. 그런데 코드를 살펴보면 &lt;code class=&quot;language-text&quot;&gt;title&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;content&lt;/code&gt;는 키 값을 갖는데 왜 &lt;code class=&quot;language-text&quot;&gt;photo&lt;/code&gt;는 키 값이 없지? 라고 생각하실 수 있습니다. 이 부분은 뒤에서 &lt;code class=&quot;language-text&quot;&gt;MultipartBody.Part&lt;/code&gt; 타입에 대해 이야기할 때 알아볼 예정입니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;postDiary()&lt;/code&gt;를 보시면 &lt;code class=&quot;language-text&quot;&gt;@Multipart&lt;/code&gt; 어노테이션이 있습니다. 이 어노테이션을 달아주는 것은 해당 함수의 &lt;code class=&quot;language-text&quot;&gt;Content-type&lt;/code&gt;이 &lt;code class=&quot;language-text&quot;&gt;multipart/form-data&lt;/code&gt; 라고 명시하는 것입니다. 그럼 &lt;code class=&quot;language-text&quot;&gt;Retrofit&lt;/code&gt;이 내부적으로 어노테이션을 통해 &lt;code class=&quot;language-text&quot;&gt;Content-type&lt;/code&gt; 을 지정해 줍니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Part&lt;/code&gt; 어노테이션으로 각 &lt;code class=&quot;language-text&quot;&gt;part&lt;/code&gt;를 명시해 주고, 여러 &lt;code class=&quot;language-text&quot;&gt;part&lt;/code&gt;가 모여 하나의 &lt;code class=&quot;language-text&quot;&gt;multipart/form-data&lt;/code&gt; 형식을 이룹니다. 초반에 &lt;code class=&quot;language-text&quot;&gt;multipart/form-data&lt;/code&gt;에 관해 설명했던 내용이 코드로 보이는 부분입니다. 파일은 &lt;code class=&quot;language-text&quot;&gt;MultipartBody.Part&lt;/code&gt;로, &lt;code class=&quot;language-text&quot;&gt;title&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;content&lt;/code&gt;와 같은 문자열은  &lt;code class=&quot;language-text&quot;&gt;RequestBody&lt;/code&gt;로 보내고 있습니다.&lt;/p&gt;
&lt;p&gt;그럼 &lt;code class=&quot;language-text&quot;&gt;MultipartBody.Part&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;RequestBody&lt;/code&gt; 객체를 생성하려면 어떻게 해야 할까요? 아래 코드를 보시면 &lt;code class=&quot;language-text&quot;&gt;MultipartBody.Part&lt;/code&gt;를 만드는 팩토리 메서드들이 존재합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Part &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token annotation builtin&quot;&gt;@get:JvmName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;headers&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; headers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Headers&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token annotation builtin&quot;&gt;@get:JvmName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; body&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RequestBody
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;companion&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token annotation builtin&quot;&gt;@JvmStatic&lt;/span&gt;
      	&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;body&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RequestBody&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Part &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

      	&lt;span class=&quot;token annotation builtin&quot;&gt;@JvmStatic&lt;/span&gt;
      	&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;headers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Headers&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; body&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RequestBody&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Part &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
        	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Part&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        
        &lt;span class=&quot;token annotation builtin&quot;&gt;@JvmStatic&lt;/span&gt;
      	&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createFormData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Part &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
          	&lt;span class=&quot;token function&quot;&gt;createFormData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toRequestBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

      	&lt;span class=&quot;token annotation builtin&quot;&gt;@JvmStatic&lt;/span&gt;
      	&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createFormData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filename&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; body&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RequestBody&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Part &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; disposition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; buildString &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          		&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;form-data; name=&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          		&lt;span class=&quot;token function&quot;&gt;appendQuotedString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

          		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filename &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            		&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;; filename=&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            		&lt;span class=&quot;token function&quot;&gt;appendQuotedString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filename&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        	&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; headers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addUnsafeNonAscii&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Content-Disposition&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; disposition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            	&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;총 네 개의 팩토리 메서드가 존재하는데요, 저희 대신 많은 일을 해주는 마지막 메서드를 사용할 예정입니다. 마지막 메서드를 호출하기 위해 필요한 인자들을 봅시다. &lt;code class=&quot;language-text&quot;&gt;String&lt;/code&gt; 타입의 name과 filename, 그리고 &lt;code class=&quot;language-text&quot;&gt;RequestBody&lt;/code&gt; 형태의 body가 필요합니다.&lt;/p&gt;
&lt;p&gt;우선 name과 filename에 대해 알아봅시다. 여기서 name은 키 값입니다. 업로드된 데이터에서 백엔드가 원하는 값을 꺼내려면 키 값이 있어야겠죠? 앞에서 명세서를 볼 때 언급했던 &lt;code class=&quot;language-text&quot;&gt;photo&lt;/code&gt;에 대한 키 값입니다. 백엔드와 합의하신 키 값을 적어 주시면 됩니다. 여기서는 “imageFile”이 됩니다. filename은 말 그대로 파일명입니다. 전송하실 파일의 이름을 적어 주시면 됩니다.&lt;/p&gt;
&lt;p&gt;이제 &lt;code class=&quot;language-text&quot;&gt;RequestBody&lt;/code&gt;를 생성하는 방법에 대해 알아봅시다. &lt;code class=&quot;language-text&quot;&gt;RequestBody&lt;/code&gt;는 추상 클래스이며 이를 구현하기 위해서는 두 개의 추상 메서드를 필수적으로 구현해야 합니다. 동반 객체를 보시면 아래와 같이 확장 함수로 &lt;code class=&quot;language-text&quot;&gt;RequestBody&lt;/code&gt;를 생성해 주는 메서드들이 구현되어 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toRequestBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contentType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MediaType&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RequestBody &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; ByteString&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toRequestBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contentType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MediaType&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RequestBody &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; ByteArray&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toRequestBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contentType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MediaType&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  offset&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  byteCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; size&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RequestBody &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; File&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asRequestBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contentType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MediaType&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RequestBody &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;쭉 훑어보시면 다른 타입의 객체들을 &lt;code class=&quot;language-text&quot;&gt;RequestBody&lt;/code&gt;로 변환해 주는 함수들입니다. 이 중에 뭘 사용해야 할까요? 우선은 &lt;code class=&quot;language-text&quot;&gt;File&lt;/code&gt;에서 변환하는 마지막 메서드를 사용해 보겠습니다. 그렇다면 이제 남은 건 &lt;code class=&quot;language-text&quot;&gt;File&lt;/code&gt; 객체를 만드는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;File&lt;/code&gt; 객체의 생성자들을 살펴보다 보면 아래와 같은 생성자를 발견할 수 있습니다!&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URI&lt;/span&gt; uri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그럼 이제 &lt;code class=&quot;language-text&quot;&gt;File&lt;/code&gt;을 만들 수 있겠군요! 하지만 이것은 틀렸습니다. 이미지로부터 얻어온 것은 &lt;code class=&quot;language-text&quot;&gt;android.net&lt;/code&gt; 패키지의 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;이고 위에 보이는 것은 &lt;code class=&quot;language-text&quot;&gt;java.net&lt;/code&gt; 패키지의 &lt;code class=&quot;language-text&quot;&gt;URI&lt;/code&gt;입니다. 헷갈리지 않도록 주의합시다! 다른 생성자를 둘러보면 아래와 같이 &lt;code class=&quot;language-text&quot;&gt;pathname&lt;/code&gt;을 받는 생성자가 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; pathname&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;여기서 &lt;code class=&quot;language-text&quot;&gt;pathname&lt;/code&gt;이란 파일이 위치하는 절대 경로를 의미합니다. 우리에게 좀 더 익숙한 예시를 들자면 &lt;code class=&quot;language-text&quot;&gt;C:\Program Files\Android\Android Studio&lt;/code&gt;과 같은 것을 말합니다. 우리가 갖고 있던 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;는 파일의 절대 경로가 아닌 안드로이드 프레임워크의 식별자이므로 &lt;code class=&quot;language-text&quot;&gt;pathname&lt;/code&gt;은 새로 얻어 와야 합니다.&lt;/p&gt;
&lt;p&gt;그럼 우리가 가진 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;로 &lt;code class=&quot;language-text&quot;&gt;pathname&lt;/code&gt;을 가져와야 합니다. &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt; 객체를 살펴보면 &lt;code class=&quot;language-text&quot;&gt;getPath()&lt;/code&gt; 함수가 존재합니다. 하지만 해당 함수는 우리가 원하는 파일 경로를 주지 않습니다. (단순히 현재 식별자(URI)에서 path 부분만 떼어서 반환합니다) 그럼 파일 경로를 어떻게 가져올 수 있을까요? 프로젝트의 코드를 첨부합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; Uri&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFilePath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; projection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;arrayOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;MediaStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Images&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Media&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DATA&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; cursor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; contentResolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; projection&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    cursor&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;moveToFirst&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; columnIndex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getColumnIndexOrThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;MediaStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Images&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Media&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DATA&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;columnIndex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;contentResolver&lt;/code&gt;를 통해서 &lt;code class=&quot;language-text&quot;&gt;MediaStore.Images.Media.DATA&lt;/code&gt;라는 칼럼을 읽어오면 파일 경로를 알 수 있습니다. &lt;code class=&quot;language-text&quot;&gt;uri&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;uri.getPath()&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;uri.getFilePath()&lt;/code&gt; 에 대해 로그를 찍어보면 다음과 같이 나옵니다. (예시로 제 프로젝트에서 로그를 찍어보았습니다)&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 970px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/880b061c12272f0b573dddbcc3d0dd8e/adb12/2023-10-17-image-upload-2.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 7.199999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAABCAYAAADeko4lAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAP0lEQVR42i2I2w6AMAjF/P9fVS4ujkGGOSPTh6ZNDx03uAt0NJAJ+IdMP3fZn4yrGedz1Vf4dEQGYsZuL+ebWB4VTQOPRxeAAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 10 17 image upload 2&quot; title=&quot;&quot; src=&quot;/static/880b061c12272f0b573dddbcc3d0dd8e/adb12/2023-10-17-image-upload-2.png&quot; srcset=&quot;/static/880b061c12272f0b573dddbcc3d0dd8e/0eb09/2023-10-17-image-upload-2.png 500w,
/static/880b061c12272f0b573dddbcc3d0dd8e/adb12/2023-10-17-image-upload-2.png 970w&quot; sizes=&quot;(max-width: 970px) 100vw, 970px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;첫 줄을 보시면 확실히 URI의 형태임을 알 수 있습니다. content가 scheme, media가 authority, picker부터가 path입니다. &lt;code class=&quot;language-text&quot;&gt;uri.getPath()&lt;/code&gt;는 URI에서 path 부분만 잘라서 반환합니다. &lt;code class=&quot;language-text&quot;&gt;uri.getFilePath&lt;/code&gt;는 URI 형태가 아닌 파일의 절대 경로를 반환하고 있습니다. 그러므로 저희가 사용할 것은 마지막 줄입니다. 파일 절대 경로를 알았으니 이제 &lt;code class=&quot;language-text&quot;&gt;File&lt;/code&gt; 객체를 만들 수 있습니다! &lt;code class=&quot;language-text&quot;&gt;val file = File(uri.getFilePath())&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;지금까지 살펴본 과정을 정리하면 이렇습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;File&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;RequestBody&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;MutlipartBody.Part&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;드디어 우리가 처음 받아왔던 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;MultipartBody.Part&lt;/code&gt; 형식이 되었습니다. 이제 이미지를 업로드 하는 데는 문제가 없겠죠?&lt;/p&gt;
&lt;p&gt;이제 진행했던 프로젝트에서 위의 일련의 과정을 어디서 어떻게 수행했는지를 알아봅시다!&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;프로젝트 구조와 구현 예시&lt;/h2&gt;
&lt;p&gt;일기 기능을 구현하면서 안드로이드에서 &lt;code class=&quot;language-text&quot;&gt;multipart/form-data&lt;/code&gt; 형태로 이미지를 업로드하는 방법을 알아보았습니다. 글의 제목이 ‘계층화 아키텍처’에서 이미지 업로드인 만큼, 앞으로 설명할 내용의 기반이 되는 프로젝트의 아키텍처 구조에 대해 먼저 말씀드리겠습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UI 디자인 패턴으로 MVVM 패턴을 사용하였습니다.&lt;/li&gt;
&lt;li&gt;Presentation, Domain, Data 이렇게 세 개의 Layer가 존재합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MVVM 패턴을 사용하며 ViewModel은 &lt;code class=&quot;language-text&quot;&gt;AAC ViewModel&lt;/code&gt;로 구현하고 있습니다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1266px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/53dd24f801e5c879019030ecb69f2a92/03d4c/2023-10-17-image-upload-9.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 24.200000000000003%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA/klEQVR42p2OwUrEQBBE82dePcuKCN5VvOtfeBJEXFH/wIP/4Uk02WBmdyXJxqTXTGaymZCZsscgePBkw6OorqlmglXxMYjFcqDPeljX8hspR378b/7KqJZ91/dIkiQO0Bv0SgJw+O9YHq95ns+DhzUwzR3ucuCWuc6AtRnQGYNp5nCVAve8v2S94czwT3x+kY473ztfWPuogcIfPHsHDmKHwwQ4ZvZjIO0s2k2HSeiwx/5EAJMZsBs5bDrD+YDtF2AnAo64s/Vs7WkBVMVKBMta02vZUFQpClnjqiHZKNJa05wamrEPq1G917olqRS9Vf6t4h5r2ZSZbLUQ4ukL4qVvzsmjOgQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 10 17 image upload 9&quot; title=&quot;&quot; src=&quot;/static/53dd24f801e5c879019030ecb69f2a92/03d4c/2023-10-17-image-upload-9.png&quot; srcset=&quot;/static/53dd24f801e5c879019030ecb69f2a92/0eb09/2023-10-17-image-upload-9.png 500w,
/static/53dd24f801e5c879019030ecb69f2a92/1263b/2023-10-17-image-upload-9.png 1000w,
/static/53dd24f801e5c879019030ecb69f2a92/03d4c/2023-10-17-image-upload-9.png 1266w&quot; sizes=&quot;(max-width: 1266px) 100vw, 1266px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;외부로부터 받아오는 데이터는 Data - Domain - Presentation Layer 방향으로 흐릅니다. 그러나 Domain Layer에 Repository 인터페이스를 두고, Data Layer에서 이를 구현하면서 의존성을 역전시킵니다. 그러므로 Domain Layer는 Presentation과 Data Layer를 모르고,  Presentation과 Data Layer는 Domain Layer에 의존하게 됩니다. 완전히 클린 아키텍처라고 할 순 없지만 큰 틀을 따른다고 보시면 됩니다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1318px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/69676582928a00ebeda4949b22237811/90694/2023-10-17-image-upload-10.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 46%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAACNElEQVR42kVSPY/TQBTMz6JBAgoaICBSQIM4ncSJkgaJhhIJCQo6REmBBMeBgBMU6JqjQIAQUQI5x1+cc0nu4o1jrz9ie+3dHZ6Tgic/afxmNW80uy3bccTQsuTAMKRhWrXtuPA8j5umeZVzfkwYhmHUtm3Xo6MxFpwH37r9K1me88l0uuIcx6mjKAKdD1sBYzJmM4SzY3Dm6yxgiAKWWq7bEUL4i8UC3W5X93o9nfEQlRDhn16vrZRKRJHjeDrVk8lEKymR53ncMsJMPmHAU1/jmQ/9mPCvuEoT1+rkpfBBJZXSgMbzAHg4kuLOUOw/GKlqO8Cq1IoHsmwZt36yVG7ZwF1X4Z6r9Cbhz/Mqrb3/ggprwfsecMuE3jJVvjGEfjRuWE2CciW4XJLgKMpks+k109hh0C8IW0mVliToxsJ/FQHv5tBvqXcID3kRxX+N9jCtk5cLoJlv+0q/4YDHSfAHy+TZ38C1A42btPVUH/jAqhS+1dljwj99ADRubhjQZwbk3i8izIz2R1Yn5+h/kxxfP1D6kgl8ZyQYppk8qYFxqamhTyTAyyodW1aHF8KnSHFUQDccxQyel+HcNtqHyzr5RK52A+j3c6X3UmAS57yVZZlcJaH1OhCqqqpSiwSbW17HvpqvcyrI4cho7wZ1cp5c3XahN2ylLzrA18YhPYukKMt8mdNXFFleliUt8QeDweU4jj3iqtWcmrCIeDx1+/0Lh1E+o0iKL6zI9puei9Kbhyf/AGVlhBvdZaJ2AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 10 17 image upload 10&quot; title=&quot;&quot; src=&quot;/static/69676582928a00ebeda4949b22237811/90694/2023-10-17-image-upload-10.png&quot; srcset=&quot;/static/69676582928a00ebeda4949b22237811/0eb09/2023-10-17-image-upload-10.png 500w,
/static/69676582928a00ebeda4949b22237811/1263b/2023-10-17-image-upload-10.png 1000w,
/static/69676582928a00ebeda4949b22237811/90694/2023-10-17-image-upload-10.png 1318w&quot; sizes=&quot;(max-width: 1318px) 100vw, 1318px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;지금부터는 방금 설명한 프로젝트 구조로 위에서 봤던 일기 기능을 구현해 보겠습니다. (오류 처리 로직은 제외했습니다)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DiaryVieModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; application&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Application&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; diaryRepository&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DiaryRepository&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ViewModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; imageUri&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Uri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Uri&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Empty
    
    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;writeDiary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        viewModelScope&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            diaryRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postDiary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                title &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                image &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; imageUri&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFilePath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
	        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; Uri&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFilePath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; projection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;arrayOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;MediaStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Images&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Media&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DATA&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; cursor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; application&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;applicationContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;contentResolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; projection&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        cursor&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;moveToFirst&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; columnIndex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getColumnIndexOrThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;MediaStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Images&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Media&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DATA&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;columnIndex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;제가 프로젝트에서 처음 마주쳤던 ViewModel 코드입니다. 보시면 ViewModel에서 &lt;code class=&quot;language-text&quot;&gt;Application&lt;/code&gt;을 받아 &lt;code class=&quot;language-text&quot;&gt;applicationContext&lt;/code&gt;를 사용하고 있습니다. &lt;code class=&quot;language-text&quot;&gt;ContentResolver&lt;/code&gt;를 사용하려면 &lt;code class=&quot;language-text&quot;&gt;Context&lt;/code&gt;가 필수적이기 때문입니다. 또 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt; 객체를 &lt;code class=&quot;language-text&quot;&gt;imageUri&lt;/code&gt;라는 하나의 상태로 들고 있었습니다.&lt;/p&gt;
&lt;p&gt;하지만 ViewModel에서 &lt;code class=&quot;language-text&quot;&gt;Context&lt;/code&gt;와 같은 안드로이드 의존성이 있는 객체를 참조하는 것은 그리 권장되지 않습니다. MVVM 안티패턴이라고도 볼 수 있습니다. 그 이유는 &lt;strong&gt;ViewModel에서 안드로이드 의존성을 가지면 유닛 테스트가 어려운 구조가 되기 때문&lt;/strong&gt;입니다. 물론 &lt;code class=&quot;language-text&quot;&gt;Roboletric&lt;/code&gt;과 같은 라이브러리를 이용해 테스트해 볼 수도 있지만, 이는 유닛 테스트에 비해 훨씬 무거워 많은 시간이 소요됩니다.&lt;/p&gt;
&lt;p&gt;이번에는 ViewModel에 주입 받은 Repository의 구현체를 보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DefaultDiaryRepository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; service&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; diaryService&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DiaryRepository &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postDiary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        image&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Result&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Unit&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; titleRequestBody &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toRequestBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text/plain&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toMediaTypeOrNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; titlePart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; MultipartBody&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Part&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createFormData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requestBody&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; contentRequestbody &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toRequestBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text/plain&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toMediaTypeOrNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; contentPart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; MultipartBody&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Part&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createFormData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requestBody&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
       
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; imageRequestBody &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asRequestBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;image/jpeg&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toMediaTypeOrNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; imagePart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; MultipartBody&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Part&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createFormData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;imageFile&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requestBody&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postDiary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;titlePart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; contentPart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; imagePart&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;ViewModel에서 받은 파일 경로를 통해 &lt;code class=&quot;language-text&quot;&gt;File&lt;/code&gt; 객체를 만들고 &lt;code class=&quot;language-text&quot;&gt;RequestBody&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;MultipartBody.Part&lt;/code&gt; 객체를 만듭니다. 여기엔 크게 문제 될 만한 부분이 보이진 않습니다.&lt;/p&gt;
&lt;p&gt;이제 위 코드들을 어떻게 개선할 수 있을지 이야기해 보겠습니다!&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;구현의 적절한 위치 찾기&lt;/h2&gt;
&lt;p&gt;이제 본격적으로 ‘계층화 아키텍처’에서 이미지 업로드하기라는 제목에 가까운 내용이 등장합니다. 글의 핵심 내용으로 앞서 보았던 과정들을 &lt;strong&gt;어디서&lt;/strong&gt; 수행할지 알아봅시다!&lt;/p&gt;
&lt;p&gt;우선 앞서 말했던 문제를 해결해 보겠습니다. ViewModel에서 안드로이드 의존성을 분리합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;context.contentResolver&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;contentResolver&lt;/code&gt;를 먼저 보겠습니다. &lt;code class=&quot;language-text&quot;&gt;contentResolver&lt;/code&gt;가 필요한 행동은 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;로부터 파일 경로를 가져오는 것입니다.  이 행동을 다른 곳으로 옮기기 위해 연관된 행동을 한번 살펴보겠습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Activity가 ViewModel에게 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;를 넘겨준다.&lt;/li&gt;
&lt;li&gt;ViewModel이 파일 경로를 Repository에게 넘겨준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;연관된 행동들을 보니 &lt;code class=&quot;language-text&quot;&gt;contentResolver&lt;/code&gt;가 필요한 행동을 넘길 수 있는 곳이 보입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Activity에서 처리하여 파일 절대 경로 혹은 &lt;code class=&quot;language-text&quot;&gt;File&lt;/code&gt; 객체를 ViewModel에게 넘긴다. (&lt;code class=&quot;language-text&quot;&gt;File&lt;/code&gt;은 안드로이드 의존성이 없는 자바 객체입니다)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;를 Repository에 넘기고 Repository에서 파일 경로를 가져오도록 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Activity에서 처리하기&lt;/h3&gt;
&lt;p&gt;Activity는 그 자체로 &lt;code class=&quot;language-text&quot;&gt;Context&lt;/code&gt;이므로 &lt;code class=&quot;language-text&quot;&gt;contentResolver&lt;/code&gt;를 사용할 수 있습니다. ViewModel에 존재하던 &lt;code class=&quot;language-text&quot;&gt;getFilePath&lt;/code&gt; 함수의 로직을 (혹은 &lt;code class=&quot;language-text&quot;&gt;File&lt;/code&gt; 객체로의 변환까지) Activity로 옮기게 되면 아래와 같이 안드로이드 의존성으로부터 한결 깔끔해진 ViewModel을 만나볼 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DiaryVieModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; diaryRepository&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DiaryRepository&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ViewModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;lateinit&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; imageFilePath&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;lateinit&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; imageFile&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; File
    
    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;writeDiaryByPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        viewModelScope&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            diaryRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postDiaryByPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                title &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                imagePath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; imageFilePath
	        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;writeDiaryByFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        viewModelScope&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            diaryRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postDiaryByFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                title &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                imageFile &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; imageFile
	        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DefaultDiaryRepository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; service&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DiaryService&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DiaryRepository &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postDiaryByPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        imagePath&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Result&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Unit&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
       
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; imageRequestBody &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asRequestBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;image/jpeg&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toMediaTypeOrNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; imagePart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; MultipartBody&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Part&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createFormData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;imageFile&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requestBody&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postDiary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;titlePart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; contentPart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; imagePart&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postDiaryByFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        imageFile&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; File&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Result&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Unit&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
       
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; imageRequestBody &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; imageFile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asRequestBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;image/jpeg&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toMediaTypeOrNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; imagePart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; MultipartBody&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Part&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createFormData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;imageFile&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requestBody&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postDiary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;titlePart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; contentPart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; imagePart&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 앞선 ViewModel보다 훨씬 테스트에 용이한 ViewModel이 되었습니다. &lt;code class=&quot;language-text&quot;&gt;getFilePath&lt;/code&gt; 함수의 로직을 옮기면서 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;도 자연스럽게 사라졌습니다. 그럼 이제 완성인 걸까요? 한 가지만 더 생각해 봅시다.&lt;/p&gt;
&lt;p&gt;우리는 객체지향 프로그래밍을 할 때, 객체에 어떤 책임과 역할을 부여할지 고민합니다. 그럼 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;로부터 파일 경로 혹은 &lt;code class=&quot;language-text&quot;&gt;File&lt;/code&gt; 객체를 가져오는 행동은 과연 Activity의 책임일까요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;사람마다 생각이 다르겠지만 저는 아니라고 생각합니다. 제가 정의한 Presentation Layer는 뷰와 뷰의 상태에 관련된 일을 해야 합니다. 그런 의미에서 Activity의 역할은 사진을 선택하는 것에서 끝났습니다.&lt;/p&gt;
&lt;p&gt;그럼 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;로부터 파일 절대 경로 혹은 &lt;code class=&quot;language-text&quot;&gt;File&lt;/code&gt; 객체를 가져오는 행동은 어디서 수행해야 할까요? 앞서 두 가지 선택지가 있었던 것이 기억나시나요? 그 두 번째 선택지를 보겠습니다.&lt;/p&gt;
&lt;h3&gt;Repository에서 처리하기 (Data Layer에서 처리하기)&lt;/h3&gt;
&lt;p&gt;Repository에서 수행하게 된다면 원래의 ViewModel에서 &lt;code class=&quot;language-text&quot;&gt;getFilePath&lt;/code&gt; 함수만 Repository로 넘어가게 됩니다.&lt;/p&gt;
&lt;p&gt;여기서 두 가지 문제가 생깁니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ViewModel에 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;는 그대로 남아있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Domain Layer에서 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt; 타입을 참조해야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 부분을 해결하기 위해 Activity에서 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;toString&lt;/code&gt; 함수를 사용해 &lt;code class=&quot;language-text&quot;&gt;String&lt;/code&gt; 타입으로 변환합니다. (ex. &lt;code class=&quot;language-text&quot;&gt;viewModel.setImage(uri.toString())&lt;/code&gt;)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DiaryVieModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; diaryRepository&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DiaryRepository&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ViewModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;lateinit&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; imageUri&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String
    
    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wirteDiary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        viewModelScope&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            diaryRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postDiaryByPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                title &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                imageUri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; imageUri
	        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DefaultDiaryRepository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; contentResolver&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ContentResolver&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; service&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DiaryService&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DiaryRepository &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postDiary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        imageUri&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Result&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Unit&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
       
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; uri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Uri&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageUri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFilePath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; imageRequestBody &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asRequestBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;image/jpeg&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toMediaTypeOrNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; imagePart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; MultipartBody&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Part&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createFormData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;imageFile&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requestBody&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postDiary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;titlePart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; contentPart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; imagePart&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; Uri&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFilePath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;로부터 파일 경로와 &lt;code class=&quot;language-text&quot;&gt;File&lt;/code&gt; 객체를 가져오는 행동을 Repository에서 수행하였습니다. 이 행동을 수행하기 위해선 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;라는 경로와 &lt;code class=&quot;language-text&quot;&gt;contentResolver&lt;/code&gt;를 통해 디바이스의 데이터에 접근해야 합니다. 이런 행동은 분명 Data Layer의 책임이라고 생각해 Repository로 옮겨보았습니다. &lt;em&gt;(DataSource가 있다면 DataSource의 역할이라고 생각합니다.  DataSource를 데이터의 출처라고 생각하며 &lt;code class=&quot;language-text&quot;&gt;contentResolver&lt;/code&gt;를 통해 디바이스의 데이터를 가져왔기 때문입니다. 그러나 현재 프로젝트에선 DataSource가 따로 존재하지 않아 Repository에서 수행하였습니다)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Repository에서 &lt;code class=&quot;language-text&quot;&gt;ContentResolver&lt;/code&gt;를 참조하는 부분에서 고개를 갸우뚱하시는 분들이 계실 수도 있습니다. Repository는 “저장소”이고 (DataSource가 있다면) DataSource는 저장소에서 필요한 데이터의 출처입니다. &lt;code class=&quot;language-text&quot;&gt;ContentResolver&lt;/code&gt;를 사용하여 디바이스의 데이터를 접근하는 행위는 Repository와 DataSource 즉, Data Layer의 역할이라고 볼 수 있습니다. 그러므로 Data Layer의 어디선가 &lt;code class=&quot;language-text&quot;&gt;ContentResolver&lt;/code&gt;를 참조하는 것은 생소할 수 있지만 이상하지 않다고 말씀드릴 수 있습니다.&lt;/p&gt;
&lt;p&gt;여전히 낯설다면 &lt;code class=&quot;language-text&quot;&gt;Room&lt;/code&gt;과 같다고 생각해 보시면 좋습니다. &lt;code class=&quot;language-text&quot;&gt;Room&lt;/code&gt;은 안드로이드 프레임워크에서 제공하는 ORM으로 앱에서 로컬 데이터베이스가 필요할 때 많이 사용합니다. DataSource에서 &lt;code class=&quot;language-text&quot;&gt;Room&lt;/code&gt;을 사용하는 것이 생소하지 않다면 &lt;code class=&quot;language-text&quot;&gt;ContentResolver&lt;/code&gt; 또한 마찬가지입니다. &lt;code class=&quot;language-text&quot;&gt;Room&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;ContentResolver&lt;/code&gt; 모두 디바이스에 데이터를 읽고 쓰는 역할을 하기 때문입니다. (저장할 위치와 저장할 내용이 다릅니다)&lt;/p&gt;
&lt;p&gt;하지만 이런 구조에서도 한 가지 문제가 존재합니다. Activity에서 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;를 &lt;code class=&quot;language-text&quot;&gt;String&lt;/code&gt;으로 변환하고 ViewModel에서 Domain Layer의 &lt;code class=&quot;language-text&quot;&gt;DiaryRepository&lt;/code&gt; 인터페이스의 &lt;code class=&quot;language-text&quot;&gt;postDiary&lt;/code&gt; 함수가 사용됩니다. 그리고 &lt;code class=&quot;language-text&quot;&gt;DiaryRepository&lt;/code&gt;의 구현체인 Data Layer의 &lt;code class=&quot;language-text&quot;&gt;DefaultDiaryRepository&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;String&lt;/code&gt;을 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;로 파싱하게 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; DiaryRepository &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postDiary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        imageUri&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Result&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Unit&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; 
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그 과정에서 Domain Layer에서 &lt;code class=&quot;language-text&quot;&gt;postDiary&lt;/code&gt; 함수의 &lt;code class=&quot;language-text&quot;&gt;imageUri&lt;/code&gt;라는 매개변수가 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;를 &lt;code class=&quot;language-text&quot;&gt;String&lt;/code&gt;으로 파싱한 것이라는 사실을 알고 있어야 합니다&lt;/strong&gt;. 이렇게 되면서 Domain이 간접적으로나마 안드로이드에 대해 알고 있다고 말할 수도 있을 것입니다. 더해서 프로젝트를 처음 보는 개발자가 해당 인자에 &lt;code class=&quot;language-text&quot;&gt;Uri.toString()&lt;/code&gt;을 담고, Repository에서 &lt;code class=&quot;language-text&quot;&gt;Uri.parse(imageUri)&lt;/code&gt;를 수행해야 하는지 모를 수 있습니다.&lt;/p&gt;
&lt;p&gt;결과적으로 말씀드린 두 가지 방식 모두 한 가지의 문제점을 안고 있습니다. 현재 두 프로젝트는 제가 ViewModel의 리팩터링을 제안한 뒤로 변화해 현재는 각각 다른 모습을 띠고 있습니다. 두 프로젝트가 각각 다른 방식을 채택하면서 두 방식 모두 사용되고 있는 것이죠.&lt;/p&gt;
&lt;p&gt;이렇듯 정답은 없으며 이 글을 읽는 여러분이 더 적합하다고 판단되는 방식으로 진행하시기를 바랍니다! 또한 더 좋은 방식이 있다면 추천해 주시면 감사합니다!&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;추가 내용&lt;/h2&gt;
&lt;p&gt;이제 글에서 하고자 하는 주된 이야기는 끝났습니다. 하지만 몇 가지만 추가로 얘기해 보고자 합니다. 글을 예전에 구성되었던 프로젝트의 코드에 맞춰서 진행했는데, 몇 가지 개선할 수 있는 부분이 있었으나 글을 진행하느라 넘어갔습니다.&lt;/p&gt;
&lt;h3&gt;MediaStore.Images.Media.DATA&lt;/h3&gt;
&lt;p&gt;우리가 파일 경로를 가져오기 위해 사용했던 이 컬럼은 사실 &lt;strong&gt;Deprecated&lt;/strong&gt; 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.android.com/reference/android/provider/MediaStore.MediaColumns#DATA&quot;&gt;공식문서&lt;/a&gt;&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 903px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/6db8c07b93e3bc8d13973b4f6aa171df/8728b/2023-10-17-image-upload-3.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 18.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAa0lEQVR42p2MUQvEIAyD/f//9EC0dnS1rZg5n/ZwsOMCHyElTRI58a/GGBCRTbjvW4qIr+X5wi0zQ84ZRA21VqgqUlmBGoP4QKEDnyLLGdoN3fyVUzvMA3XtNGak56Pehe57zGP8RDx8zokLai06PdgBi2QAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 10 17 image upload 3&quot; title=&quot;&quot; src=&quot;/static/6db8c07b93e3bc8d13973b4f6aa171df/8728b/2023-10-17-image-upload-3.png&quot; srcset=&quot;/static/6db8c07b93e3bc8d13973b4f6aa171df/0eb09/2023-10-17-image-upload-3.png 500w,
/static/6db8c07b93e3bc8d13973b4f6aa171df/8728b/2023-10-17-image-upload-3.png 903w&quot; sizes=&quot;(max-width: 903px) 100vw, 903px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;저도 글을 작성하면서 찾아보다가 이 부분에 대해 알게 되었습니다. 일반적으로 Deprecated 된 것들은 IDE에서 취소선을 그려 주는데 프로젝트에서 작성한 코드에서는 취소선을 볼 수 없었기 때문입니다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 693px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/0262a93ac7082932da3bca8beac719fb/ee2da/2023-10-17-image-upload-4.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 18.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAqUlEQVR42o2PyQ7CMBBD+//fh4R6aVFJ6JK1dEtCEpMGegEOWLLGM57LK4RQkHqEuk+w1iGEAO89Qoz40q/bhwrnFjDO0DQ16uqMsjyBkgqS3yBFB84oeMr7VLIHG0i2kl3q23RPHacYhcCqNYoYDKwz4GrFqCW6tkkPHYaepkxArpe8W7Nkm3WC2ab3PmPLeYYzKx52Q/HCjNkxIfmEvOPu6IfjH6gH8hM1vjWv5KhxcgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 10 17 image upload 4&quot; title=&quot;&quot; src=&quot;/static/0262a93ac7082932da3bca8beac719fb/ee2da/2023-10-17-image-upload-4.png&quot; srcset=&quot;/static/0262a93ac7082932da3bca8beac719fb/0eb09/2023-10-17-image-upload-4.png 500w,
/static/0262a93ac7082932da3bca8beac719fb/ee2da/2023-10-17-image-upload-4.png 693w&quot; sizes=&quot;(max-width: 693px) 100vw, 693px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 387px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/ea64e68e60bae929e3248210a9177921/0082d/2023-10-17-image-upload-5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 42.11886304909561%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABUklEQVR42nWRjXKbMBCEef/3a5vOxHbKr7GMkAAJkBBfD2c8aZN2NTt3QtLecZullDgwz56+71FKMfuZbU1sMZGE27qx7ztbeo9R4sG0p8e3P5E9Ba01lGVJXuTY1qB+avpiYKxHunOPGz3DMnBSr5y6nF99SWFqjDOkLX0IHhUPJKlkjcUIP+NxZX/m78ku6+jwuf/S4TAMVFVN3dQ47ehOBn2xDOVEWAO901x0QW5KatsSY+RfyGIw0t6Ivudczt+5vH2jywvUS0kv4qO5UulXzuoHd5PjfIuflRTRMl/7hVmMTlr3eN/T3RvaNmceDcFPbJuXM4cWEevvhODkX4NwfcR9X2RU/i9mHy7PIqa4icthDiw2sLqI7xbitImbSUxxTKvDh1mKGMn9/2fopkkEb1RNxXCzjFd5rDwml/w2sYSV69A+2NhGnD6jPzl8CP4G5XNrYaC7lR4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 10 17 image upload 5&quot; title=&quot;&quot; src=&quot;/static/ea64e68e60bae929e3248210a9177921/0082d/2023-10-17-image-upload-5.png&quot; srcset=&quot;/static/ea64e68e60bae929e3248210a9177921/0082d/2023-10-17-image-upload-5.png 387w&quot; sizes=&quot;(max-width: 387px) 100vw, 387px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;다음은 공식문서의 내용입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From Android 11 onwards, this column is read-only for apps that target &lt;code class=&quot;language-text&quot;&gt;R&lt;/code&gt; and higher. On those devices, when creating or updating a uri, this column’s value is not accepted. Instead, to update the filesystem location of a file, use the values of the &lt;code class=&quot;language-text&quot;&gt;DISPLAY_NAME&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;RELATIVE_PATH&lt;/code&gt; columns.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Android 11부터, R 버전 이상을 대상으로 하는 앱의 경우 이 칼럼은 읽기 전용입니다. 이러한 디바이스에서는 uri를 생성하거나 업데이트할 때 이 칼럼의 값은 허용되지 않습니다. 대신 파일의 파일 시스템 위치를 업데이트하려면 DISPLAY_NAME과 RELATIVE_PATH 칼럼의 값을 사용하십시오.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Deprecated 된 것을 계속 사용할 수는 없으므로 새로운 방식을 알아봅시다. 다음 예시 코드처럼 캐시 파일을 통해 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt; 객체로 &lt;code class=&quot;language-text&quot;&gt;File&lt;/code&gt; 객체를 생성할 수 있습니다. (&lt;a href=&quot;https://developer.android.com/training/data-storage/app-specific?hl=ko#internal-create-cache&quot;&gt;캐시 파일&lt;/a&gt;, &lt;a href=&quot;https://developer.android.com/training/data-storage/shared/media?hl=ko#open-file-stream&quot;&gt;파일 스트림&lt;/a&gt;) 임시로 사용할 캐시 파일을 생성하고 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;로 &lt;code class=&quot;language-text&quot;&gt;Bitmap&lt;/code&gt;을 만들어 저장합니다. 그리고 해당 파일을 &lt;code class=&quot;language-text&quot;&gt;MutlipartBody.Part&lt;/code&gt;로 변환해서 전송하면 됩니다.  &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;만 알아도 &lt;code class=&quot;language-text&quot;&gt;Bitmap&lt;/code&gt;은 생성할 수 있으므로 절대 경로 없이 업로드할 파일을 생성할 수 있습니다!&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;makeImageFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Uri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; File &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 파일 스트림으로 uri로 접근해 비트맵을 디코딩&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; bitmap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; contentResolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;openInputStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        BitmapFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decodeStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;it&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 캐시 파일 생성&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; tempFile &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; File&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createTempFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;파일명&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.jpeg&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cacheDir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?:&lt;/span&gt; FILE_EMPTY

    &lt;span class=&quot;token comment&quot;&gt;// 파일 스트림을 통해 파일에 비트맵 저장&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;FileOutputStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tempFile&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        bitmap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;compress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Bitmap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CompressFormat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;JPEG&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; tempFile
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;File을 거치지 않고 RequestBody 만들기&lt;/h3&gt;
&lt;p&gt;글에서 내내 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;File&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;RequestBody&lt;/code&gt; 의 과정을 거쳤습니다. 그러나 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;RequestBody&lt;/code&gt;의 과정도 가능합니다. 직접 &lt;code class=&quot;language-text&quot;&gt;RequestBody&lt;/code&gt;를 구현하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;RequestBody&lt;/code&gt;를 상속받으면 두 개의 추상 함수를 구현해야 합니다. 즉, 이것만 구현하면 &lt;code class=&quot;language-text&quot;&gt;RequestBody&lt;/code&gt;를 만들 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; RequestBodyImpl &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;RequestBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;contentType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MediaType&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;TODO&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Not yet implemented&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;writeTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sink&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; BufferedSink&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;TODO&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Not yet implemented&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;바로 코드를 보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ContentUriRequestBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    context&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Context&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; uri&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Uri&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;RequestBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; contentResolver &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;contentResolver

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; fileName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; size &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        contentResolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            uri&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;arrayOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;MediaStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Images&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Media&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SIZE&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; MediaStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Images&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Media&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DISPLAY_NAME&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cursor &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cursor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;moveToFirst&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                size &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cursor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cursor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getColumnIndexOrThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;MediaStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Images&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Media&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SIZE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                fileName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
                    cursor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cursor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getColumnIndexOrThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;MediaStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Images&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Media&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DISPLAY_NAME&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getFileName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fileName

    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;contentLength&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Long &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; size

    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;contentType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MediaType&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
        contentResolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toMediaTypeOrNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;writeTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sink&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; BufferedSink&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        contentResolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;openInputStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; source &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
            sink&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;source&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이런 식으로 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;로 &lt;code class=&quot;language-text&quot;&gt;RequestBody&lt;/code&gt;를 바로 구현하면 파일 경로도, &lt;code class=&quot;language-text&quot;&gt;File&lt;/code&gt; 객체도 만들 필요가 없습니다. 콘텐츠 길이, 미디어 타입을 넘기고 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;로 &lt;code class=&quot;language-text&quot;&gt;InputStream&lt;/code&gt;열어 파일을 가져옵니다. 두 프로젝트 중 Repository에서 처리하는 방법을 선택한 프로젝트 ‘ZOOC’의 경우 위의 &lt;code class=&quot;language-text&quot;&gt;ContentUriRequestBody&lt;/code&gt; 객체를 다음과 같이 사용하고 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DefaultDiaryRepository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; context&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Context&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; service&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DiaryService&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DiaryRepository &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postDiary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        imageUri&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Result&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Unit&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
       
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; uri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Uri&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageUri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; imageRequestBody &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ContentUriRequestBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; imageUri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; imagePart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; MultipartBody&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Part&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createFormData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;imageFile&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requestBody&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postDiary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;titlePart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; contentPart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; imagePart&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;프로젝트의 구조가 &lt;code class=&quot;language-text&quot;&gt;ContentUriRequestBody&lt;/code&gt; 객체를 사용할 수 있는 상태라면 사용해 보는 것을 추천 드립니다!&lt;/p&gt;
&lt;h2&gt;조금 더 개선된 구조&lt;/h2&gt;
&lt;p&gt;앞서 말씀드렸던 “Repository에서 처리하기 (Data Layer에서 처리하기)“에서 생각을 조금 더한 구조를 보여드리려고 합니다.&lt;/p&gt;
&lt;p&gt;우선 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;를 &lt;code class=&quot;language-text&quot;&gt;String&lt;/code&gt;으로 변환해서 옮기는 것에서 오는 혼동을 방지하기 위해 래핑 객체를 만들었습니다. (래핑 객체를 만들고 팀 내에서 약속하면 혼동을 방지할 수 있다고 생각합니다) 또 파일이나 &lt;code class=&quot;language-text&quot;&gt;Uri&lt;/code&gt;를 생성하는 역할을 분리하여 &lt;code class=&quot;language-text&quot;&gt;ContentRepository&lt;/code&gt;를 만들고 &lt;code class=&quot;language-text&quot;&gt;DiaryRepository&lt;/code&gt;에서 사용합니다. 또 &lt;code class=&quot;language-text&quot;&gt;RequestBody&lt;/code&gt;나 &lt;code class=&quot;language-text&quot;&gt;MultipartBody.Part&lt;/code&gt;로 변환하여 서버에 전송하는 행동은 &lt;code class=&quot;language-text&quot;&gt;RemoteDiaryDataSource&lt;/code&gt;로 옮겼습니다. &lt;code class=&quot;language-text&quot;&gt;ContentRepository&lt;/code&gt;는 다른 Repository에서도 사용 가능하게 되면서 재사용성도 증가했습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TecobleUri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DiaryRepository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postDiary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        imageUri&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; TecobleUri&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; ContentRepository &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; TecobleUri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; File
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DefaultDiaryRepository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; contentRepository&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ContentRepository&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; remoteDiaryDataSource&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RemoteDiaryDataSource&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DiaryRepository &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postDiary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        imageUri&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; TecobleUri&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Result&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Unit&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
       
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; imageFile &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; contentRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageUri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        remoteDiaryDataSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postDiary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; imageFile&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h2&gt;글을 마치며&lt;/h2&gt;
&lt;p&gt;이렇게 &lt;code class=&quot;language-text&quot;&gt;multipart/form-data&lt;/code&gt;를 통한 이미지 전송 방법과 그 과정들이 어디에서 일어나는 것이 좋을지에 대해 이야기했습니다. 각각의 장단점이 있으니 더 적합하다고 생각하시는 방식을 사용해 주시면 될 것 같습니다. 저는 개인적으로 Data Layer에서 수행해 주는 후자가 더 적합하다고 생각합니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ContentResolver&lt;/code&gt;가 하는 모든 행동이 Presentation Layer가 아닌 Data Layer에 어울린다고 생각하기 때문입니다. 또한 Activity에서 작성한다고 한다면 사진을 다루는 여러 Activity에서 중복 코드가 발생할 것입니다. (물론 다른 객체로 빼볼 수도 있습니다) 이 모든 것들은 제 개인적인 견해일 뿐 다른 의견도 궁금하고 존중합니다! 읽어 주셔서 감사합니다!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[공간 데이터 개념부터 적용까지]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-10-04-spatial-data/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-10-04-spatial-data/</guid><pubDate>Tue, 24 Oct 2023 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;들어가며&lt;/h1&gt;
&lt;p&gt;성수동에 위치한 힙하고 트렌디한 카페를 쉽게 찾을 수 있는 &lt;a href=&quot;https://yozm.cafe&quot;&gt;요즘카페&lt;/a&gt; 서비스에 지도 기능을 추가하기로 했다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 982px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5a8449a9ca2afdadbd779574ef6c6ce6/9e029/map_example.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 159.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAgCAYAAAASYli2AAAACXBIWXMAABYlAAAWJQFJUiTwAAAGO0lEQVR42lVV628bdRC8v7uf+ID4gFClgoBKRaoCLZQWSvoA0oS0ISFN0iRN4uZlx/b5eWff2ffwvX1Ohtl1UkSkky9399udndmdNS6DAKVpYlqtoqzXMT0/x6XvQ/+urq5/riB3V9MpyosLlLaFfGcH2fIy8rdvka+vI1tawmw4hDHrdpFvbKDY3ESxv498dRUzy/pfwI+BswzFux2U1XOkTx4j+vprpI8eIVlYQHTnjgIyZrNLlGWJKa/ykvezmd5PpyVmvM/SFOEkRv1oG2HgoShn/33LRHleIOM1Y8KCzw2z20ePKFvtNnq9HuIk+Xhvtkw06g297zRrDBigkLIZ1B2NmShCymBxmiPhOXlmWAMH3W4P47GPKIr50QRhGKJv2fD8ACM+HwxHmBDlhO8nDDLiQXswRO2izvdjZFmuweW8YdsD9GwHG8vPUdnbYsZSXw7dkGhmWk4UJwjCCdI0Y3AHHQJIklSfuww4iaLrbxjQo6JjL8DFyRHazTpRRej3B1h79RJB4CNldkGUUpA4jqHfjz0NUD8/wyGFDMMICZP98fg+jKKYauaCIgiaWLnwsbm8CLNpEm2iV8BDQRDikkJIaWk21W9WVhcRlQURZ6geH8CImckPJvpxms0DDh0HAQ95XkgkKUauoyh9P4RHdAmVv6jVFMjZsAEvCeGS01dPf4Thj0dY/OGuluG5lpLuMGC1WqMwljb02u+PcbC9jm6riZVfF+C4I1QqFW0t4btvUYe+he3Xv8No16vomXVcFgHi5kv2Xa6tEJEvQS28xXGkiaRtQs9Fq9WCbdusJiPKVAVqcIKyPIexu76KMAfWjwP02jaCoY2QysVsHzmQMIFQEkUJZAgEkWm2tKVCKu9R0CFH7sX3X8F1XRiOM1bluk6M83pHUYxdDyt7Dlbee0hDXw+GDJAXnAqqPXRcdoLFCgJNEDBhy2zixcO7onKp45SwrF6/ry0gH1huhM5ggonnEbWLaGCjY3bw9rCtgYUCj+9kEG46oH72AcbGq2c4O9rDJckX9SJVN0DEAyVLXFtZwtriAyS9v7G98gzb66/VKIQKGTnpEkEqlzPyYJwe7sHutcCzOD4+hsXJ8Twf925/irPjCszGBcxqBSetEUbeBEWR8fuuNvHy0wecqLGCkHM1WqARkXghXdR02Q7CSeh7WPr5HgZWj+Vx/EjLFd1FysryKcVrYGXxezROjzRQTnWL8gqbf72AIWVKgzpUSLibCCeEL00uh4six9FZF3uVE1jOgEbSV8OQcmX8omtRhPs4CmG4hOzwcvmRtMOY5UoCnRz+CrcX5yf4eeFLvFl+pv0nDiOiJNpWGfuyQ8ujUbMS43B/B9sbb9RpbKo5pJvcOIugbTebKsLuP6t4eOcTdSBJJug+HOyi32kxgUfaAj1nyJidvN/C8ftt5bJLM5WX4nHOYIAXD74hh8KbifrpIZW/gs92iZlw489fUKMhlCzDZ5+KDxgygwI75CSIWpJdGllGz2f54sQXpxW8efkEjAXxTwEhJiyGKxrImRvujZCofN//GEReiPdpQD4XRO7QojXtz7lmJ7TJl6g74Rl1a/6a3JziUoaI4LOR1d4loww8FRb15OOhM8Tp2Tl/Rzpu8wnx55Vcrwup0B2NlC5DpkLI7POfER+a9RqadA4JLOU1abLHJ6dzZaNIy7oJ2O70YNG6pBoxEvFOQwwzp3JLvyzgaHcTB1tvsLfxF9W8ZLCmCiRlCuE3ri6ViH922JMSOCBKWXKitiE7V9yj2WggIGfSDpPrDSaCCJqIyLSN+ExQpLo2U/1OLjkXJ3NxjMN3W9qYNteprMrg2uP0cDQfx+BGUe4R4VEuASFt5nEtCNf7W5uwhMPnD76dc8IXsp/nuyOAzUC1ZgsNKtomT41WF38++xWP7t3G66Xnunol8bz/Mqy9/Ak9rghjwOkYi+eRK0HqUE1pg/x6AzrDATodjlUJPLn7GSo76+rW0h1SviAfDFwC8ZQWQ+eR9Q8ZeMCyJaO0gXIUFzg53MX9L27htHLAw4miEi8UhWUoJPB3n9/C9t+r6lSGcCZOobvBH8EeOZBdLZkliSz+D7v/4OxwBwWbXNeB9KJQ1LOU++rRO93Ry7/9iH8BIPVKpAGMNDEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;지도 기능 예시&quot;
        title=&quot;&quot;
        src=&quot;/static/5a8449a9ca2afdadbd779574ef6c6ce6/9e029/map_example.png&quot;
        srcset=&quot;/static/5a8449a9ca2afdadbd779574ef6c6ce6/0eb09/map_example.png 500w,
/static/5a8449a9ca2afdadbd779574ef6c6ce6/9e029/map_example.png 982w&quot;
        sizes=&quot;(max-width: 982px) 100vw, 982px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;기능 개발을 위해 공부하던 중 공간 데이터에 대해 알게 되었다..&lt;/p&gt;
&lt;p&gt;공간 데이터는 좌표계를 통한 지리적인 위치 데이터를 의미한다.
좌표계라는 단어가 굉장히 생소하게 느껴지지만 한 번쯤은 들어봤을 위도와 경도 또한 가장 일반적으로 사용되는 좌표계 중 하나다.
좌표계는 지구 상의 특정 위치를 정확하게 나타내기 위해 사용되는 시스템으로, 각각의 목적에 따라 여러 종류가 존재한다.
이 글에서는 위도/경도 좌표계를 사용하는 공간 데이터를 다루고 있다.&lt;/p&gt;
&lt;p&gt;공간 데이터에 대해 학습하고 적용하는 과정에서 수많은 시행착오를 거쳤다.
처음 접해본 개념인데다가, 레퍼런스도 여기저기 흩어져 있어 여간 애를 먹은 게 아니었다.
한 번에 정리된 글이 있으면 좋을 것 같아, 학습한 내용을 공유하고자 한다.&lt;/p&gt;
&lt;h1&gt;1. MySQL의 공간 데이터&lt;/h1&gt;
&lt;h2&gt;1-1. 공간 데이터 타입&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1390px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/89dbdb4036cfb12c8dc59529ae646613/55991/spatial_types.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 46%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABLklEQVR42m2RS6uDQAyF/f//qdzuuuuiFYsLsRZFpNZnfbRaU75AxMINDMkkmXOSM87z+ZT3+y2v10uPxfiu66SqKpnnWe/jOKpvmkZc15Usy2RZljVPvzMMg5hRaNtWthaGoT6E5PP5aA4S8vf7Xa7Xq4JiYP0ATtOkD7dGvSxLSdN0BcQnSSJxHKvfDvQDaCtuDUkej4d6zKap61pJWPVfQFityJRMhVZ2pw9C24B+YnIAWd1Bs77vVXhYsdvtJufzeV2H/H7/J8fjUfWkn3WDIJDD4SCn00n1BMuxjzBG++XL5SKe5ylgnuey2+0kiiIFoR8Q3/d1qqIoVJb1U1iNR+gHMA2sDCt1CJgMIOshBwh9HHIKiNiw0EzMOhAQk2s3OcA4bEWd2MjJoeEXBxKz4qB/L4gAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;공간 데이터 타입&quot;
        title=&quot;&quot;
        src=&quot;/static/89dbdb4036cfb12c8dc59529ae646613/55991/spatial_types.png&quot;
        srcset=&quot;/static/89dbdb4036cfb12c8dc59529ae646613/0eb09/spatial_types.png 500w,
/static/89dbdb4036cfb12c8dc59529ae646613/1263b/spatial_types.png 1000w,
/static/89dbdb4036cfb12c8dc59529ae646613/55991/spatial_types.png 1390w&quot;
        sizes=&quot;(max-width: 1390px) 100vw, 1390px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;MySQL에서 제공하는 공간 데이터의 종류는 총 7가지이다.
단일 타입으로는 &lt;code class=&quot;language-text&quot;&gt;Point&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;LineString&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;Polygon&lt;/code&gt; 세 가지가 있고,
나머지 타입들은 이 세 가지 타입의 조합이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Point&lt;/strong&gt; : 좌표 공간의 한 지점&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LineString&lt;/strong&gt; : 다수의 Point를 연결해 주는 선분&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Polygon&lt;/strong&gt; : 다수의 선분이 연결되어 닫혀있는 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1362px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f252e97699bfa058be35fc7f30bc0eee/7046d/spatial_types_cli.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 33.800000000000004%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABcElEQVR42jVS13LCQAzkZ0JvxoBtbGxwoxkMoYSSAIGZJJP/f9+sNJOHm5NWZfekK0znW9hODKPjY+CmSCdrWPQ7ZoBubwTPnyJJ1xhHS8Y2GI0XmLEmjFeI05x4hmbLRacboGeFKARMaLU9NJoDBVb5UZsKVifmehMk9OUW8nS60SMN5I6TXMkk1rciFBbZHpVqH8VSlyqHOLzdMCcmDV9KJpNIsj6hzVit4WgspuJypY+W4aFWt0nsqErJKQiDFJcqPRimj5zF88UOjpsoZvLZq/VRk8sk/ldZrVtotAYolruaJwRtgw3Plyc22wuqNRsGgf3hho/rl86o2XbhDBJVKGSVmoVsedCmVdqiSpuRSGKq8Hb/wen8wIyqbBYfaV/v33g8f3We3nCC4+mTS/KpoofX3TvyzUnH1KTCEhUKLr6hT+amRE3IwQ6DGSLaUbLCKMzgB3P07UhtmaXJRQTjDC5JZBTyC+R3iDJZkmB/SQ7zjGRiJwEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;공간 데이터 타입 MySQL&quot;
        title=&quot;&quot;
        src=&quot;/static/f252e97699bfa058be35fc7f30bc0eee/7046d/spatial_types_cli.png&quot;
        srcset=&quot;/static/f252e97699bfa058be35fc7f30bc0eee/0eb09/spatial_types_cli.png 500w,
/static/f252e97699bfa058be35fc7f30bc0eee/1263b/spatial_types_cli.png 1000w,
/static/f252e97699bfa058be35fc7f30bc0eee/7046d/spatial_types_cli.png 1362w&quot;
        sizes=&quot;(max-width: 1362px) 100vw, 1362px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;각각의 컬럼에는 당연하게도 알맞은 데이터 타입만 입력할 수 있지만,
&lt;code class=&quot;language-text&quot;&gt;geometry&lt;/code&gt; 타입에는 &lt;strong&gt;모든 공간 데이터 타입의 입력이 가능&lt;/strong&gt;하다.&lt;/p&gt;
&lt;h2&gt;1-2. 공간 함수&lt;/h2&gt;
&lt;p&gt;공간 데이터들은 &lt;strong&gt;공간 함수&lt;/strong&gt;를 통해 활용할 수 있다.
공간 함수에 대해선 &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/spatial-function-reference.html&quot;&gt;공식 문서&lt;/a&gt; 에 잘 소개되어 있으니, 여기서는 대표적으로 사용 되는 몇 가지의 함수만 확인하려 한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;함수명&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ST_GeomFromText(WKT*[, SRID]*)&lt;/td&gt;
&lt;td&gt;WKT와 SRID를 통해 geometry를 생성한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ST_Buffer(&lt;em&gt;(Multi)&lt;/em&gt; Point, Radius)&lt;/td&gt;
&lt;td&gt;Point(또는 MultiPoint)로 부터 Radius를 반지름으로 갖는 원을 그린다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ST_Contains(geom(a), geom(b))&lt;/td&gt;
&lt;td&gt;b가 a에 포함되어 있으면 1을 반환, 아니면 0을 반환.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ST_Within(geom(a), geom(b))&lt;/td&gt;
&lt;td&gt;a가 b에 포함되어 있으면 1을 반환, 아니면 0을 반환.(ST_contains와 인자가 반대)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ST_Distance(geom(a), geom(b))&lt;/td&gt;
&lt;td&gt;a와 b 사이의 거리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;WKT&lt;/strong&gt;(&lt;strong&gt;W&lt;/strong&gt;ell-&lt;strong&gt;K&lt;/strong&gt;nown-&lt;strong&gt;T&lt;/strong&gt;ext)는 공간 데이터를 텍스트 형식으로 표현하는 방법 중 하나이다.
WKT는 기하학적인 객체를 설명하기 위해 사용되며, 다양한 유형의 공간 개체를 나타낼 수 있다.&lt;br&gt;
&lt;em&gt;예) POINT(80 110) , LINESTRING(0 0, 10 10) , POLYGON(0 0, 10 10, 20 20, 0 0)&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ST_&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ST_&lt;/code&gt; 라는 prefix가 붙어있는데, &lt;code class=&quot;language-text&quot;&gt;Spatial&lt;/code&gt;의 약어이다.
공간 함수라는 것을 명시적으로 보여주고, 다른 함수들과 네이밍이 겹치지 않게끔 하는 역할을 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1-3. SRID&lt;/h2&gt;
&lt;p&gt;공간 데이터를 설명하며 빼놓을 수 없는 개념 중 하나인,
SRID(&lt;strong&gt;S&lt;/strong&gt;patial &lt;strong&gt;R&lt;/strong&gt;eference &lt;strong&gt;Id&lt;/strong&gt;entifier)는 데이터의 좌표계를 구분할 수 있는 식별 코드이다.&lt;br&gt;
가장 흔하게 사용되는 값은 &lt;code class=&quot;language-text&quot;&gt;4326&lt;/code&gt;으로 위도(Latitude)와 경도(Longitude)를 사용하여 위치를 표현하는 &lt;code class=&quot;language-text&quot;&gt;WGS84&lt;/code&gt; 좌표계를 의미한다.
이는 GPS 및 다양한 GIS 솔루션에서 널리 사용된다고 한다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;MySQL&lt;/code&gt;에 입력되는 데이터에도 이 SRID 값을 지정해 줄 수가 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;mysql&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt; sridTest&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;-------+--------+------+-----+---------+----------------+&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Field &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Type&lt;/span&gt;   &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;Null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Default&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Extra          &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;-------+--------+------+-----+---------+----------------+&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; id    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;NO&lt;/span&gt;   &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; PRI &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;auto_increment&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; YES  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;     &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;                &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;-------+--------+------+-----+---------+----------------+&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.00&lt;/span&gt; sec&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

mysql&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; sridTest &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ST_PointFromText&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;POINT(10 20)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
Query OK&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt; affected &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.01&lt;/span&gt; sec&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

mysql&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; sridTest &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ST_PointFromText&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;POINT(10 20)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
Query OK&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt; affected &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.00&lt;/span&gt; sec&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

mysql&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ST_SRID&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; SRID &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; sridTest&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----+------+&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; SRID &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----+------+&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;    &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----+------+&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.00&lt;/span&gt; sec&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위의 그림에서 첫 번째 데이터는 SRID 값을 지정해 주지 않아 기본값인 0으로 입력이 되었다.
문제는 &lt;code class=&quot;language-text&quot;&gt;MySQL&lt;/code&gt;에는 한 번 입력된 데이터의 SRID 값을 수정할 수 있는 방법이 없다.&lt;br&gt;
즉, 해당 데이터를 지우고 다시 입력하거나, &lt;code class=&quot;language-text&quot;&gt;UPDATE&lt;/code&gt; 쿼리를 사용해야 한다.&lt;/p&gt;
&lt;p&gt;한두 개의 데이터야 직접 수정하면 된다지만, 잘못 작성된 쿼리로 입력된 데이터가 100개, 1000개, 10000개가 된다면,
이를 수정하는 것은 제법 귀찮은 일이 될 것이 분명하다.&lt;br&gt;
귀찮은 일의 발생을 막기 위해 처음부터 값을 잘 입력하는 것이 매우 중요한데,
Table의 컬럼속성에 SRID 값을 설정해 준다면, 이러한 실수를 미리 방지할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; sridTest2 &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
	id &lt;span class=&quot;token keyword&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;auto_increment&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt; SRID &lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이런 식으로 Table 생성 시 SRID 값을 지정해 주거나,&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; sridTest2 &lt;span class=&quot;token keyword&quot;&gt;MODIFY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COLUMN&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt; SRID &lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;테이블명&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;MODIFY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COLUMN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;컬럼명&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;컬럼유형&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; SRID &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;SRID 코드번호&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ALTER TABLE&lt;/code&gt;을 통해 SRID 값을 변경해 줄 수 있다.&lt;br&gt;
&lt;em&gt;물론, 두 번째 방법은 하나의 데이터라도 컬럼에 지정하려는 SRID값과 일치하지 않는다면 에러가 발생한다.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;위와 같은 방법으로 컬럼에 직접 SRID 값을 지정해 주었다면, 데이터 삽입 시 다른 SRID 값을 입력하려 하면 에러가 발생한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;mysql&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; sridTest2 &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ST_PointFromText&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;POINT(10 20)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
ERROR &lt;span class=&quot;token number&quot;&gt;3643&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;HY000&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;: The SRID &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; the &lt;span class=&quot;token keyword&quot;&gt;geometry&lt;/span&gt; does &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt; the SRID &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; the &lt;span class=&quot;token keyword&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;point&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; 
The SRID &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; the &lt;span class=&quot;token keyword&quot;&gt;geometry&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; but the SRID &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; the &lt;span class=&quot;token keyword&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4326.&lt;/span&gt; 
Consider changing the SRID &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; the &lt;span class=&quot;token keyword&quot;&gt;geometry&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;or&lt;/span&gt; the SRID property &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; the &lt;span class=&quot;token keyword&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;컬럼에 지정한 SRID 값과 일치하는 값을 입력해주면 정상적으로 데이터가 삽입된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;mysql&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; sridTest2 &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ST_PointFromText&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;POINT(10 20)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
Query OK&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt; affected &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.01&lt;/span&gt; sec&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

mysql&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       ST_X&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; latitude&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       ST_Y&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; longitude&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       ST_SRID&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; SRID
       &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; sridTest2&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----+----------+-----------+------+&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; latitude &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; longitude &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; SRID &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----+----------+-----------+------+&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;       &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;        &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----+----------+-----------+------+&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.00&lt;/span&gt; sec&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;2. 공간 인덱스&lt;/h1&gt;
&lt;p&gt;데이터베이스에서 데이터를 빠르게 검색하고 조회 성능을 향상하기 위해 인덱스를 사용한다.
공간 데이터도 마찬가지로 조회 성능 향상을 위해 인덱스를 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;공간 인덱스를 거는 방법은 매우 쉽다.
일반적으로 인덱스를 거는 것처럼 명령어 한 줄 이면 해결된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; SPATIAL &lt;span class=&quot;token keyword&quot;&gt;INDEX&lt;/span&gt; idx_point &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; sridTest2&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; SPATIAL &lt;span class=&quot;token keyword&quot;&gt;INDEX&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;인덱스 명&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;테이블 명&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;컬럼 명&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;mysql&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SHOW&lt;/span&gt; INDEXES &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; sridTest2&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;-----------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Table&lt;/span&gt;     &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Non_unique &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Key_name  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Seq_in_index &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Column_name &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Collation &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Cardinality &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Sub_part &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Packed &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;Null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Index_type &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Comment&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Index_comment &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Visible &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Expression &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;-----------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; sridTest2 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;          &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt;   &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;            &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; id          &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; A         &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;           &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;     &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;   &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;      &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BTREE&lt;/span&gt;      &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;         &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;               &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; YES     &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;       &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; sridTest2 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;          &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; idx_point &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;            &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;       &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; A         &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;           &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;       &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;   &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;      &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; SPATIAL    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;         &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;               &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; YES     &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;       &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;-----------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.00&lt;/span&gt; sec&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;생성된 인덱스를 확인해 보면 &lt;code class=&quot;language-text&quot;&gt;Index_type&lt;/code&gt;이 &lt;code class=&quot;language-text&quot;&gt;SPATIAL&lt;/code&gt;로 생성된 것을 확인해 볼 수 있다.
이대로도 충분히 사용은 가능하지만 그래도 이왕이면, 공간 인덱스의 원리를 얕게나마 이해해 보자.&lt;/p&gt;
&lt;h2&gt;2-1. MBR&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;MySQL&lt;/code&gt;의 공간 인덱스는 MBR의 포함관계를 바탕으로 트리 구조를 생성하여 이용하기에, MBR에 대한 이해가 필수적이다.&lt;/p&gt;
&lt;p&gt;이름이 거창해서 그렇지 그냥 사각형을 그리는 방법이다.
MBR(&lt;strong&gt;M&lt;/strong&gt;inimum &lt;strong&gt;B&lt;/strong&gt;ounding &lt;strong&gt;R&lt;/strong&gt;ectangle)의 약어로 &lt;strong&gt;최소 경계 사각형&lt;/strong&gt;을 의미한다.
글로 하는 설명보다는 그림을 보면 단박에 이해가 된다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 934px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/cd03e2c796015e48f09f1c80b5d1c091/e7956/point_line_poly.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 58.599999999999994%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAwElEQVR42p2SiQqFIBBF/f/PlAhazBbb1JzHHTCCXmUJgsyMZ+4sYp5nenNCCLd+UZblYxD8TzE78I0iYwxBwC3wKvPRbq2lPM/5ruv6TuGxvG3bqKoqklLSOI5JLbksuW1bBmmtbwcSbRF6Ak7TxKCiKMh7nzQItAHxEHEC9n1PcZWepouEqAB/AIMY8WXf4ENPAVuWhW11XXMC8WXnhmGgLMt4+lGpUup/D1MPIF3XscqmafidtNgpYCy8c46BP/80tNC29NelAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;공간 데이터들&quot;
        title=&quot;&quot;
        src=&quot;/static/cd03e2c796015e48f09f1c80b5d1c091/e7956/point_line_poly.png&quot;
        srcset=&quot;/static/cd03e2c796015e48f09f1c80b5d1c091/0eb09/point_line_poly.png 500w,
/static/cd03e2c796015e48f09f1c80b5d1c091/e7956/point_line_poly.png 934w&quot;
        sizes=&quot;(max-width: 934px) 100vw, 934px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위 같은 도형들(점, 선분, 다각형)이 있을 때 각각을 감싸는 최소크기의 사각형을 그리면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 934px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/53523970bf5bfbbc7c78424572ae1352/e7956/point_line_poly_mbr.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 58.599999999999994%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABH0lEQVR42p1Ti5KCMAzs/3+liCJieR0iUCBrFurcoRyOZiZDW5pNspuawlpMJoJ3xivvbhnXtsuTcXxxGYZ5rdY7tw242DGw6zSqn1wYzLWW1mviaL+HjaIPAQmE39ZEwWyaIlCwqqoAJlmhh/foL4DCiryVZYkgCJAqoHgQWQGUzQrVG20vPBwQn8/oefbXCOjvif8ygWuaiV/zLMjleES426Gt6zk7RWHVj2ByTCE9t/xX5TniMESZ2iUgg5vrdW7pGejhVNsrXd9uE7/sqMgyON2b1XnD9rw5rT5TIIo0+nGySYJBgc2aWv8P9vyv1McQx/FUGa3TijM+EO3A4BtTgEG5rIocuVaWnE740ZZJx1eAMnouKQqnQikYvUh35aeyvgdeZGQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;공간 데이터들 MBR&quot;
        title=&quot;&quot;
        src=&quot;/static/53523970bf5bfbbc7c78424572ae1352/e7956/point_line_poly_mbr.png&quot;
        srcset=&quot;/static/53523970bf5bfbbc7c78424572ae1352/0eb09/point_line_poly_mbr.png 500w,
/static/53523970bf5bfbbc7c78424572ae1352/e7956/point_line_poly_mbr.png 934w&quot;
        sizes=&quot;(max-width: 934px) 100vw, 934px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;여기서 보이는 빨간색 사각형이 각각의 도형에 대한 MBR이다.&lt;/p&gt;
&lt;h2&gt;2-2. R-Tree&lt;/h2&gt;
&lt;p&gt;인덱스가 &lt;code class=&quot;language-text&quot;&gt;B-Tree&lt;/code&gt;를 이용하는 것과 마찬가지로, 공간 인덱스는 &lt;code class=&quot;language-text&quot;&gt;R-Tree&lt;/code&gt;라는 트리 형태의 자료구조를 활용한다.
&lt;code class=&quot;language-text&quot;&gt;R-Tree&lt;/code&gt;는 위에서 언급하였듯, MBR의 포함 관계를 트리로 표현한 것이다.
삽입 및 삭제 과정에서도 &lt;code class=&quot;language-text&quot;&gt;B-Tree&lt;/code&gt;와 마찬가지로 동적으로 재구성되어 &lt;code class=&quot;language-text&quot;&gt;leaf node&lt;/code&gt;들의 균형을 유지한다.
또한, 탐색 속도를 향상하기 위해 자식 노드들의 적정 개수를 유지하며, 삽입 과정에서 MBR의 크기가 작게 확장되는 분기를 선택한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;균형유지&lt;/strong&gt;&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;root node&lt;/code&gt;부터 모든 &lt;code class=&quot;language-text&quot;&gt;leaf node&lt;/code&gt;까지의 거리가 동일하게 유지된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;R-Tree의 네이밍 유래&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;R&lt;/strong&gt;ectangle(사각형) 과 B-Tree의 Tree를 섞어서 만들어진 네이밍이라고 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;우선은 MBR의 포함관계가 정확히 무엇을 의미하는지 그림을 통해 알아보자.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1299px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/bc7948c45247047f810d302d3f14004b/9dba0/seongsu_map.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60.199999999999996%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACg0lEQVR42h2TWW/cRhCE+f//SoA8BEGAfXHg5MVBLCeCY0XSand5zcW5L+4qQFAu7EODAw5RXf11cdDBQYkjjJVIKWNZFpynCxal4EtHrFe4WOFKhnhrOP98hT7tyNcrVhPudzZXxNaxBY3BOounl7/xx/MB5+UvODViGs/IMeHWKvYcYa2D0QvUaKA/XKEOO2qpKCxdF8hNomSFlA0GJc+IRiFYgWX6wvMbvDcQeoYMT4jbK7J3WNmkFIP1t4j5VwcbIlzyEF3emyd7Qtt3CroVhWLWeezRw8wTip6QisCWRhzlA77OvxPBM8RyhF0dWu7obce2a4zWoBNHlt9Q2hWDlp8h5n/vLFpKiJbs1AXZjXS20PGCb5dP+Dwe8GoeodqKTn69d5zzRKcdNc3UOKJ0Cl6OB7SoyCFh0hnaJQjj0TeB6hR6EOhcmBxf75xMmxC5vNmvkE0jcdydz+w25FIwyPUrvF5RLd04A7M58gso0aBSLDEBmVi8XJiEEybxyCkodAsIPd25VjejZ0fBhGFzhEvB7AQ/lNgYl8zNGzFhPv2DFgyap1s6vrgvKGlFYoPAzVY6cjxHPZItG+eAYVWaF5GZSnDbRvsZ0nGkVVLM0aVGlM9scsLypBGWjW4kvOWISaE5Rqx4bIY5psthnGek4OFTx07YITVWhfENlZDdRpFHi+2TxfMPFadfCh0HRqQzSheYPy3S2SMHLpOTDdNKdsyd55Z7a2isynKhcIQCTaZeMRZvBeqBzj9k+OmK238jwgO3++M75E8d5mOFkhxZyBdoyyzWhn2/0Wm7u3WBInxn2Uj6jOv/N/R3/oqd7kNj9l4ZlTPExw5xeId7sfdf9zvmyn/PNRIA+wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;성수 지도&quot;
        title=&quot;&quot;
        src=&quot;/static/bc7948c45247047f810d302d3f14004b/9dba0/seongsu_map.png&quot;
        srcset=&quot;/static/bc7948c45247047f810d302d3f14004b/0eb09/seongsu_map.png 500w,
/static/bc7948c45247047f810d302d3f14004b/1263b/seongsu_map.png 1000w,
/static/bc7948c45247047f810d302d3f14004b/9dba0/seongsu_map.png 1299w&quot;
        sizes=&quot;(max-width: 1299px) 100vw, 1299px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위의 그림과 같이 성수역 쪽에 몇 개의 공간 데이터를 입력했다고 가정해보자.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1794px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/21704bbb2dd4acac507918d6a9b590c7/d4913/without_map_seongsu.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 61.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAAB2UlEQVR42oVT227UMBDd//+kqiCQqOAFxANFgiLo3ppsYie+xHcnh7F3u61aWkYajSdOzsyZM1k1TYNpmqCUenCpIJU8e8m1UdjftPj9/oD9bQM+MAzDgHEYIaWE1hp932PFOUexZVnwop2u1M5AvAH4pwAhBfKc0dkGKaV6X4BXjLGazPNcQUucU8bkJ7TTHsKMsMbCTQ6j4jhcWbTvDJSWSIjofIvoY8UQQjzqsIDGiBwCLNG2zsEtFhJECwwGGjZYsB8S/WaAtQQKAekFUnjU4T1gMUMV5pzrWYvji0taEFKATCP60KJzDWKIMHaiQj289wjUxDPA8wxPUdHz0oXzjj7w8MaDkRD7cVPp9OZQu44+vQJIXmIkungiUo4ZYxjAXX+clx2hrarzzSdW/+ywREczLNQj0bm3GBPuwpZA9IP4Z1LLK5RPNtMqeNqtTKobY2DChNuvTRUkJF9p54WKuoyUI6XzUeWna1O9nOdjgRADhmuH6RoYLhaIqwV6NEhLRP9dob104F9opXYaBeu/i13mUwDkH4/hm0P3kcB7D+oL+sZDXtI8L4ju5wTec6w2mw3GcUQBfskZZ+CSgQlWlS6/XNd12N5t8OvDDj/f7rBer7HdbfEXCvifHeLpwdUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;데이터와 MBR&quot;
        title=&quot;&quot;
        src=&quot;/static/21704bbb2dd4acac507918d6a9b590c7/d4913/without_map_seongsu.png&quot;
        srcset=&quot;/static/21704bbb2dd4acac507918d6a9b590c7/0eb09/without_map_seongsu.png 500w,
/static/21704bbb2dd4acac507918d6a9b590c7/1263b/without_map_seongsu.png 1000w,
/static/21704bbb2dd4acac507918d6a9b590c7/d4913/without_map_seongsu.png 1794w&quot;
        sizes=&quot;(max-width: 1794px) 100vw, 1794px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;지도를 걷어 내고, 각각의 데이터들에 MBR을 그려보면 위와 같은 그림이 나온다. 그리고 이렇게 생성된 MBR들을 포함하는 MBR을 그리다 보면, 다음과 같은 그림이 나온다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 979px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/dc7891cbf26d2ebe3f6245bf1576c566/1d5a8/MBRS.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 61.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACbElEQVR42m2T627bOBBG8/4Ps1j01xbFojWabBM7cGzHlzi+xbIsybrfSMmU7bNTuUHRoiSIIQTq8JuZjzeO42DMmfn0ltdRB8t6Zjx+4qk/YDYdMBlLfBnS/XzP3V/33P/3leGwx2D4yKjfZXtY8uyPCfKAKIi4icKQ78PZDjFF0u6VunBquI7LNaRvivQjRL2aJI1omhPbzQK0IWwi9EVR5iU34Q/g3pmitG5BfqI5KJdMp1S6blecxxxuNf6dpihzKmq221coGwIToc6KIit+AifjEZ6XEIcptqdJq4qclOQSy88yjxXhvCTcp2i5OCXDs3aSTvNnhZPhA2kStjk6bz6Jl4NAK1ET5R5eauElFlVWkkcxYeURvNpcohJf++jzb8DNbibAnCbIsF88csdF+QHuIqKwQ+zFks16xinS7O0VeRZxGG5orBCr+42iiClLLU2JfjTFm1FbW9RsReyUApNaHQpyV1HLJaHv4Ls2JlR4tkW89wiXu2v9twsyqbFSLTBqP7q1K+mkWL0FZabwpVZVadDFkUpS2SYrEjl7qgznurlGY65Ad0MuClX5C9DD6ILSLSiqC1mYUR/PJIkoNIq3gcd+7XE81e35ykhjlOIk8816EYXRVWH4nrIoPGpphKh8N6CqaqmT2GYqPvwXgo7s85q6qVkPUnZfDNESRqMJcXL4VeFegKYqBJi/e5m6Pom3GsQV6JVARycx/5nmYkhsQ9mBoge9+zlxKp3+rtCyxApFxYP9wNgesJ51Wa6eWC37zOf99ok9T3s8z2S99JhM++3Texzc8enDHZ1/unzq/I21W+MfAv4HwL6FULJTevgAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;MBRS&quot;
        title=&quot;&quot;
        src=&quot;/static/dc7891cbf26d2ebe3f6245bf1576c566/1d5a8/MBRS.png&quot;
        srcset=&quot;/static/dc7891cbf26d2ebe3f6245bf1576c566/0eb09/MBRS.png 500w,
/static/dc7891cbf26d2ebe3f6245bf1576c566/1d5a8/MBRS.png 979w&quot;
        sizes=&quot;(max-width: 979px) 100vw, 979px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;그리고 이 MBR들의 포함관계를 Tree 형태로 표현한 것이 R-Tree이다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1319px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1def62040e1119bcc511f3a15c27671b/9eec3/RTree_1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 57.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB/klEQVR42m1S/U/aUBTl//9xbotmyZgi6EQbBvKpi9vMNAU3MicClbWw8tEWsF8QKC09e+/Rsjm8yU3exz3nnvPui3Q6HUynU5imCcuynqRhGpjP5jj6sY+v0iXcuQfD0DfqaNq2DVmWEVEUBTR838dz4cwdxPi3uKh/hO+SOmzWhVhVVf8SLpfL1QXJmetC1TSIogj5t4yJPYHxaIC6oWej8Rhzz2O1FEOxG4Q+JaQLw4CZTmPYbsP7TzlduyTVeh1TjgMmE4Z5VqEfdJkRIi0ahUeI140CJWHNot+HurMDh7j4F/uEkMbjeARRkjAkZK1Wizy2EUpDKFTXx/hFbA/JEEVRYvswGOFgsCKsCUsUPunkPVy2tycLZM5MKEPGGNT4yJ6P4LoLtve8Bbi8CUleKVQUQtgPCK8qBrhcOPElHII5TnfRlqdrBfx3C/kLfT1EGnvJLn7eW2w9UALL1E6tdoebmzL7jzQ0Iv/6+opN1nEcdtbryngQmush6LqOcpmHIAisQWB5gM9fLpHN5lEsnqFUOkel8g3HJxwymVOkUmnc3t6h1+shnjhAInGIZrMJTRvi8P0RcrkCUh8yKBRL5ExDpNFo4IRLYS8Wx378AMkkh1NC/m43hlgsQTJOlPKoVqt4sfUKWy9fg+fLxNE9trffsAYUG43usj/6B7TTKtDW2GpJAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;R-Tree&quot;
        title=&quot;&quot;
        src=&quot;/static/1def62040e1119bcc511f3a15c27671b/9eec3/RTree_1.png&quot;
        srcset=&quot;/static/1def62040e1119bcc511f3a15c27671b/0eb09/RTree_1.png 500w,
/static/1def62040e1119bcc511f3a15c27671b/1263b/RTree_1.png 1000w,
/static/1def62040e1119bcc511f3a15c27671b/9eec3/RTree_1.png 1319w&quot;
        sizes=&quot;(max-width: 1319px) 100vw, 1319px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;최상위 MBR인 &lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;B&lt;/code&gt;가 R-Tree의 루트 노드에 저장되는 정보이며,
차상위 MBR인 &lt;code class=&quot;language-text&quot;&gt;C,F,D,E&lt;/code&gt;브랜치 노드가 된다.
그리고 각각의 데이터의 MBR이 리프노드에 저장된다.&lt;/p&gt;
&lt;h3&gt;2-2-1. Insert&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1400px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/02f96a429946eaff1d9782e6f64d3ae0/3643c/insert_X_MBR.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACb0lEQVR42l2TW2+bQBCF8///S1/60qpSVLWp0iapU7tOYnyJ7Rh7uRhYlssCBvN14vSmzAgxAvZwZs6ZM89T1IeW+eSSxc9PuO4d93dDRsMhzmTEw/2I6ewnNx+/8eXNJVeXF4zH3xmPBtwNrlnvV8ziOb7xCP2QsziKeA5/M6bN0lNtLbTtqaTvX+7ZkyV537O/KslyQ9t3uMsZfdVRHi261aRJyln0G1ApB1uWdG1PqC0qU6RWyzNLbWu0Sdh9ylFfcmxVcpDcrKYcy5biWJK0CTrW/wCd+ztClchfDK6qMbUlJ0WTUD1nUxE4KbEy1HUl7zLC1YajbbG9MOxeMXRkLmn8Uq9nAVlokJPURYbOQpnRBpWsqbMCE8eYLiJ2XJo4J6+zf4B/ZrjZTsl0TrdPcR2fwvOw4Z7dNMIK8810zno1oY1KEW5Gkcb4gwV2syf8dk1cBqSp+U+UYMph51I8zDB+SRFWlGGJ3pW0icVTT0SBEkBLIN+lao9+3J3Oxq4rgCHmBCj0nyOofJo8x71ekKc54S6lfh64aWhEmFU0k5ZielG/F4EIROEXAxDvFLF9DdgEtI2llKEXVU+W5BxE8UharA6W9SDAX0c0xxqOYpVcU5qaTuyjnlZErxn6dcBBlEVr/kQjht/eluSjnuQcoouDzE6sJaluDNG5dPAAq+9zwtzD6P8AVe2dGKLTv4CtMEz2DfW+I5916NGB2rTS6pHMsWQfoPworjhfEha+MMw422632MJy690yUWPWzg3L5ZDl4w8eFz8YyxpOpnLNZRUXQx6coazegJvBBZdvv3L1/oqLz+/Y+Gt8L+AXFhyFqlhbUboAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;MBR X 삽입&quot;
        title=&quot;&quot;
        src=&quot;/static/02f96a429946eaff1d9782e6f64d3ae0/3643c/insert_X_MBR.png&quot;
        srcset=&quot;/static/02f96a429946eaff1d9782e6f64d3ae0/0eb09/insert_X_MBR.png 500w,
/static/02f96a429946eaff1d9782e6f64d3ae0/1263b/insert_X_MBR.png 1000w,
/static/02f96a429946eaff1d9782e6f64d3ae0/3643c/insert_X_MBR.png 1400w&quot;
        sizes=&quot;(max-width: 1400px) 100vw, 1400px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;새로운 데이터 &lt;code class=&quot;language-text&quot;&gt;X&lt;/code&gt; 가 추가되었다.
데이터가 추가되는 경우, MBR이 적게 확장되는 브랜치 노드를 따라 데이터가 삽입된다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1103px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3191ee78505fc9a71acda39c912ba0bc/890d8/insert_X_tree.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACCUlEQVR42m2SDU8aQRCG+bf9L22TJk0T06a1ampSQ421UanVINgAFimC9YuaAseXx33B3aHl4Linu4ecGjvJZGd2Zt95392NdTodLMvCNM0HbhgGpmGiGRqpShKl26Rv9TBMXdSNR/0SQ1VVYt1uF2lBEBCZiGe5feMwd/AU1a7e9d1rvX9WAkeAk8kkLEj3fB+z10Op11EaTVzLAKdDYNUIBhoTfxhizvrl2UeAYVEGgwFOOo1aqzEcjR4y98fg9SGzA9XqIzUPAW+neK0WxsICvm3fDQplinUyHTqIrxDkUtxK+z9geF+2RU3IVIXcaq3KwLFnt3R7dkxDqaHqmkBVCeyGGOIzExECquoUUGkHfNp08aUsYY47YnXrBtudAo7F9pcdj1/nbkTguHzNYXEc5fL1I8DLqs3aphIV/w4nxNf/YFrDKTvBYmv3imZnGPVkCzrJRFlEU1BTfLWYruti+piTk2OOinna7XaYK0qdwuEBlcoFI/E4nudxdlrmqtOaKnAcCj/znJYO0C8LgoH8tzoxTdMoHpWZf7/Eh+UV1tc3SCS+8frNWxaXlvks8rOzc1KpNM+ev2B+fhHXdYnHV3kn4tW1DV6+muM6/oR+cY2YZFQqHbO3lxaeIpvNkc8fkkzuhXkuJ1n+Fj0lvm5vs7//nZb4CXJfDslksuzsJmlnPqJe/OAfs94uoBd9uDgAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;tree X 삽입&quot;
        title=&quot;&quot;
        src=&quot;/static/3191ee78505fc9a71acda39c912ba0bc/890d8/insert_X_tree.png&quot;
        srcset=&quot;/static/3191ee78505fc9a71acda39c912ba0bc/0eb09/insert_X_tree.png 500w,
/static/3191ee78505fc9a71acda39c912ba0bc/1263b/insert_X_tree.png 1000w,
/static/3191ee78505fc9a71acda39c912ba0bc/890d8/insert_X_tree.png 1103w&quot;
        sizes=&quot;(max-width: 1103px) 100vw, 1103px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;X&lt;/code&gt; 가 추가된 MBR 그림을 다시 보면, &lt;code class=&quot;language-text&quot;&gt;A MBR&lt;/code&gt;에는 &lt;code class=&quot;language-text&quot;&gt;X&lt;/code&gt;가 포함되어 있지 않아&lt;code class=&quot;language-text&quot;&gt;A MBR&lt;/code&gt;의 크기가 확장되어야 한다.&lt;br&gt;
그러나 &lt;code class=&quot;language-text&quot;&gt;B MBR&lt;/code&gt;에는 이미 &lt;code class=&quot;language-text&quot;&gt;X&lt;/code&gt; 가 포함되어 있으므로 크기의 확장이 필요하지 않다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;D MBR&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;E MBR&lt;/code&gt;의 경우에도 마찬가지의 방식으로 결정되어 위와 같이 데이터가 추가된다.&lt;/p&gt;
&lt;h3&gt;2-2-2. Split&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1635px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8aed5da0340a31b019b12518740e471e/06640/insert_Y_MBR.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 61.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACbUlEQVR42m2TW3PbNhCF8/9/S1/ap0zraTL1uGliWbYpS7JkKRHF+50ESJAETX6FJad1ZgLODkhgcXjO7sE7z3Pp+oHtwyVf7y/xjkvWi3usW4vt2uJxaebNgtmHL3z65TPXV1csrRsTtyznM5zowDJZkoqUOIp5l6YpLyOw7xlqcXpvmonxmfOYzpOwFeJ3KGY9VVUyjuAcnkzyQDKktFOLKMULYHI64PorGqV41hNR3hA0PpUqUaqlbToKkRNeKqK/G+pGYlY52luoB2Kd0I6KqqgMYHJm+LCwiMOSPClxA0XZmT9ivqcMRYPqFOlGkgUVqjX75omOzk8YJmeGy7vPlHl20ujsY6pYQt/TNzVFnRBJh6hy6ESNyHNyHZM/eUxZQ6yit5LPDG13jTQLY2wYrgOE6yGDCO8xoTrEHB+3fN0teU7USWqRhITzHdpOOFx9NGUoEUK+aUq0Rrs2arNDRAqVtMioQRj5bSxIYp8k9NCGUegdyf2YbO+ezjr2BilL5AkwewVsfbqiwL3eIYuaxC3oTH1a2dOaRKfcmZKkjHpkMg0i3jO9OsBxttT2NULWbwD7kEFJpC8QaqLMJF03UpSGoVbYtxH+twg9aibjKdUUNKLDwLO3V4ZEYFjW/0v2uxBt7EBZfreesVFPeNdSLyaKCwg/GPC6Qz9rU7+a+GKgWcPq5oGySn+U7PUBWr0AVv8BvjAU+UAfg3yayOYaLUeGSaN2GvGHybmE9cWKSmRnho5rrGCMOwtmbLwHjts5h8MC+5vFfmexXC14fHqNvQlzDS1rzvzuHz79aq7i+xv+/Os3/MAxjUv4F+YKhZyWexmlAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;MBR Y 삽입&quot;
        title=&quot;&quot;
        src=&quot;/static/8aed5da0340a31b019b12518740e471e/06640/insert_Y_MBR.png&quot;
        srcset=&quot;/static/8aed5da0340a31b019b12518740e471e/0eb09/insert_Y_MBR.png 500w,
/static/8aed5da0340a31b019b12518740e471e/1263b/insert_Y_MBR.png 1000w,
/static/8aed5da0340a31b019b12518740e471e/06640/insert_Y_MBR.png 1635w&quot;
        sizes=&quot;(max-width: 1635px) 100vw, 1635px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이번에는 어떤 MBR에도 속하지 않는 &lt;code class=&quot;language-text&quot;&gt;Y&lt;/code&gt;데이터가 추가되었다.&lt;br&gt;
위의 &lt;code class=&quot;language-text&quot;&gt;Insert&lt;/code&gt; 에서 설명했듯이 가장 적은 확장이 일어나는 MBR을 따라 데이터가 삽입된다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1605px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ed779e311acf062cacdca1409308d79c/3b19a/insert_Y_tree.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABt0lEQVR42nWRW2/aQBBG+c39IX3pc9WqElJF24QABkMfACtBgEi4CAi0BQkwdxtbgZib8anXoY6r0pFGnpmdOfPtOjSdTjFNE8MwfF+tVp5v1huy3e+01LYbW25N/6sv6IIxm80ILZdLhDmOQ9BEftgfidbC/JyWL/YEe4VpmvYKPJ1O3oHjfnX3YDQaMZlMsJ4t2JmcTBXHHOHsnl76Ai5m/wE656JdLLLO5Tj5m52zCnehiL+God/3qn+UXVQobH88MKpUmFSrHI5HDxaErq0tCyULasev/Rc412ya7THbw57tfsdDfYpmvA71Blseu2OETtxrq705TxaBpWfgYvECzBYslOKzD8jkN1SbWz//fLOh27f9PBLV+fG4OL//BYXtdotarYZt2xzd61YqZXq9X14sFHQ7HW9AmGVZlIt3DEsxnFnd+1E+UNd1CoUS8biEJKXIK7dISZmr6yiJRIpGo0mz2eLDx0/EE5IHu7q+IRZL8CWWoRx5C+k3UHqHNh8TUlUVRUAkGVnOkMsryOmMB0/Jaer1Bvf3D0Qi37z6YDgkmRS9MjEpzV0ijJF1Ycp7xoM+vwExA+YXPhc+0AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;tree Y 삽입&quot;
        title=&quot;&quot;
        src=&quot;/static/ed779e311acf062cacdca1409308d79c/3b19a/insert_Y_tree.png&quot;
        srcset=&quot;/static/ed779e311acf062cacdca1409308d79c/0eb09/insert_Y_tree.png 500w,
/static/ed779e311acf062cacdca1409308d79c/1263b/insert_Y_tree.png 1000w,
/static/ed779e311acf062cacdca1409308d79c/3b19a/insert_Y_tree.png 1605w&quot;
        sizes=&quot;(max-width: 1605px) 100vw, 1605px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;그런데 만약 트리의 최대 노드 개수(M)가 4이고, 최소 노드 개수(m)가 2인 경우라면, &lt;code class=&quot;language-text&quot;&gt;E&lt;/code&gt; 노드에서 &lt;code class=&quot;language-text&quot;&gt;OverFlow&lt;/code&gt;가 발생했기에 트리의 분할이 일어나야 한다.&lt;/p&gt;
&lt;p&gt;분할의 동작 순서는 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;분할이 필요한 노드(&lt;code class=&quot;language-text&quot;&gt;E&lt;/code&gt;) 의 자식 노드(&lt;code class=&quot;language-text&quot;&gt;O, P, Q, X, Y&lt;/code&gt;) 중 &lt;code class=&quot;language-text&quot;&gt;분할 알고리즘&lt;/code&gt;에 따라 두 개의 노드가 선택된다.&lt;/li&gt;
&lt;li&gt;기준이 아닌 나머지 노드들은 &lt;code class=&quot;language-text&quot;&gt;분할 알고리즘&lt;/code&gt;에 따라 1에서 선택된 두 개의 노드 중의 하나를 선택한다.&lt;/li&gt;
&lt;li&gt;2의 결과를 통해 노드를 분할한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;분할 기준은 다양하지만, 대부분 &lt;code class=&quot;language-text&quot;&gt;Quadratic Split&lt;/code&gt; 방식이 일반적으로 사용된다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;R-Tree 분할 알고리즘&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Plane Sweep&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&gt;   단순 추가 순으로 일정 비율을 새로운 노드에 삽입한다.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Linear Split&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&gt;   한 축 기준으로 가장 멀리 있는 두 노드를 A, B로 하고 나머지 노드는 두 노드에 추가할 때 증가하는 MBR이 적은 곳으로 삽입한다.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Quadratic Split&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&gt;  한 MBR에 속하면 낭비되는 공간이 가장 큰 두 노드를 A, B로 하고, 나머지 노드는 두 노드에 추가할 때 증가하는 MBR의 넓이가 적은 순서대로 찾아 삽입한다.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Exponential Split&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&gt;  모든 분할 경우에 대해 계산한 후 공간상 가장 효율적인 분할을 선택한다.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Quadratic Split&lt;/code&gt;방식에 따라, &lt;code class=&quot;language-text&quot;&gt;P&lt;/code&gt; 와 &lt;code class=&quot;language-text&quot;&gt;Y&lt;/code&gt; 를 A, B로 지정하고 MBR을 확장해 나가면 다음과 같이 트리가 분할된다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1337px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/bfaa2c1309bdf2db16bc0bc9fe4a1a36/ac2ab/split_tree.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 53.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACBklEQVR42nVSbW/SUBTm5/on/Al+UeNLiPEdrYubEydjGWYbjCaSLFpxmd2y9RX6QqFdC4WW9vHcCyWOzJM8lHPuOc95zrm3ZNs2HMe5Adux4TouDEtHWz6C5Vir+HpuAcuy4LouSrjF8jznX9PX8VC8h2ASLOLI8T9jNYy4VDgFZlkGbzSCrmkwTIN3Nk2T+0OKs/N/8wtkFF8RIl/2jiJEjQYGqopZmq6U8kbks3hYrwPjMc/Pl+S3KmQ2ITU9QUBCStaVM5sHAS7KZVjyOeIk5YRLPWuEBNsZQqWxwjjGlaKQPyq2w389L4CqanCpmfSri16vB13XaSV9pGlyk9DxMgjVGabTRfFgmOHTboLrkA+GoZ/jxcYEYTjn5yxXOmNnKU7PAlQ2Y4wnGd2yvSR0A1RrP4kw4QXX4RT1/R/w/Yj70TjBQesSSboYsSnquFT9xUtwZ3izeYowmsEb0LMJwxCi2IbYPoQkSTSGgU6nQ7EmQURMK5DlPzg63Oe37dMeW60GjS0hdjXYJ5+hdrbQbh5AUTSU5PMLvBc+4uWrCr7W9vBlZxfl569RefcB29Ud9Pp9CMIG7j94hGbzGN3ubzx9UsZ27Rv8ky2gegfzvbt4++wxjsXvKBmGQcwKX7hKz4ItWqPLUTWV+30iNEiZQv+ZQuazXFZn9w045hVBgcZzLfwFQ/suvsnFSzAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;split Tree&quot;
        title=&quot;&quot;
        src=&quot;/static/bfaa2c1309bdf2db16bc0bc9fe4a1a36/ac2ab/split_tree.png&quot;
        srcset=&quot;/static/bfaa2c1309bdf2db16bc0bc9fe4a1a36/0eb09/split_tree.png 500w,
/static/bfaa2c1309bdf2db16bc0bc9fe4a1a36/1263b/split_tree.png 1000w,
/static/bfaa2c1309bdf2db16bc0bc9fe4a1a36/ac2ab/split_tree.png 1337w&quot;
        sizes=&quot;(max-width: 1337px) 100vw, 1337px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1726px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9d466e1fde6cc9e69e236b6d0aadfb0d/dd200/split_mbr.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACcElEQVR42l2Ta1PbOBSG+///THdm2w/dnWlhW0ppoOXqJCQ42Am2hRXfbcmX+NkDocNu5dFIY0mPXr3nnDdRFNL3I4v5Mc7536y8n1xeTTj9MeHamXB5fcrN7Izjgy8cvv2Ho6NPnF+d8PPiG+dnX1nGt7jJHdfxFaEKeaN1zFML/Utstp9X9UjXPU8Zx/2YeTXxhx2PpzVFmWN6y3o5Z7D7japVKK1egUE4paxKhh500hBVIbmRg7WhbVrSPOHhoER9qTGmwcrnuzMG8wsY/R84c66JAk2+zVkHhtwaSnKyMZWjBmMtkZOThCVW1jJSHlf+q8LuN4Vz8SrZPj7P10tFHmVQN5giQ2cBQXJPEK/Yyb8iiclEUTr16LIcugFVb1DbZ6B+hvjrGek2YxcXrKeKMowwscZzNNVDwmaxxHWn9FuD1kus0aQ3Lp1SokBg60tUHL4Co2iO8VfY6YI8LKgfG2pVkW0qum0ldvio4IFi0xBuPJQXo2Zr2q5nlCyJohlKBQLcvgCtKKpzvIlLlkhEn7xqBuqypZVnusmMp8vFPto0oRK1VgJorWSEHbg//fN3oKK1JY2oq5oduc7p5GatJapdg/dDsbmLKUwr0IFSvLWVxFrWdmPH/d2FqAxenxwK0JpSEi7lJQNlc0d4IYG5geKTpNNhTxKLFabHP87RHwfSi44iNKzuHaLwPwoDG74Ac361Ydix1Z34OBJPR4KJKNODJPVAvjA0B1IEclHj9ty5zl6hv/ZpiobJZsK5f8bC+cZ8MWF+u+/T2Xdu5ic4t9IXJ9wupRyl9L5+P+DzH585eif95JD3f33A81f8CzWUgkVyMtqVAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;split MBR&quot;
        title=&quot;&quot;
        src=&quot;/static/9d466e1fde6cc9e69e236b6d0aadfb0d/dd200/split_mbr.png&quot;
        srcset=&quot;/static/9d466e1fde6cc9e69e236b6d0aadfb0d/0eb09/split_mbr.png 500w,
/static/9d466e1fde6cc9e69e236b6d0aadfb0d/1263b/split_mbr.png 1000w,
/static/9d466e1fde6cc9e69e236b6d0aadfb0d/dd200/split_mbr.png 1726w&quot;
        sizes=&quot;(max-width: 1726px) 100vw, 1726px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;2-2-3. Search&lt;/h3&gt;
&lt;p&gt;검색의 경우도 마찬가지로 검색하려는 공간 데이터가 포함된 MBR들을 따라서 검색한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1229px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f7eabc5918c6d802c4ceae00629d8920/2324c/search_mbr.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACfUlEQVR42m2TSXPbRhSE9f//Sw455OCKY1ol0TJFiQpFSCpxAUkQ+74MVoL48kTKlYunqmuAeZhGd8+bK89z6Y4n1qsJ788jtsa/LF4emD098PL6gKbJ/PbAz++33P51w+T+mvliIvU7Fs/36PGaTbxhGS9xfIerKAr5GM5+TpfF52elBvojlzFcpmRdEn+BcF6RFgl103LQV3A6nevRMcIObK7C8EJo2a9UdcWpl01Jjadc8iqjruoz4jTCHdX4k4qyUqhBCeES+k/CLhRC63/Ct5cFvpuShBm21xDLpoKUdEhoENK6IXhTRI78pKmIhgh/a3JW8DuFr9qUTFRc7IckspGmoalyEuXh5yZ+ZnJUJUUu9cYjWQqhuOI4EMm7HdofGV5ILG1NoSt6N8N6F7uWTeUFGC8huSHz6h1d0Eclnv9OmbjEizX4Hug7In+N7RpCGAqhDtnIovvi0Vy7ZHZFFdYoT5E6FXWQE0o+jmlTOCWubRDsPYKNzTCI5Y/cIx3b2gmhHsDTQHYfcrzLyP6wKfUM35GTzNsLsgIz3RC5EY060QY+ar9HDlryhLbqODx/FYWmEG4lw5kQTiP6qaL9p6S0ezIhaeqeJBOFXYXx5GNsfKr2KOsdubRYW3a0fU0vKg9bTTpl/2l5B/mPmH5cwCIXG5feU2WLO29Rz9KHX8H8VlNkLYWsG2NFOOpJFx2F32EcVlhny5+HYkeOWMkhzX/1Mm3XS+Ylwa7G0Wr0WyUR9Ki6JdY60r/l85FgdWKzXWJ/KHTl6h3bnnm8QAvmbNdTKT6eoe8e2e5nbA8zdqbAEhgztNcJk8cbxn+OmXwbM57Ktbwb43om/wHLFIG6fkJKjAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;search MBR&quot;
        title=&quot;&quot;
        src=&quot;/static/f7eabc5918c6d802c4ceae00629d8920/2324c/search_mbr.png&quot;
        srcset=&quot;/static/f7eabc5918c6d802c4ceae00629d8920/0eb09/search_mbr.png 500w,
/static/f7eabc5918c6d802c4ceae00629d8920/1263b/search_mbr.png 1000w,
/static/f7eabc5918c6d802c4ceae00629d8920/2324c/search_mbr.png 1229w&quot;
        sizes=&quot;(max-width: 1229px) 100vw, 1229px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위의 그림에서 분홍색 원안에 포함되어있는 모든 데이터를 반환하는 경우를 가정해보자.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1462px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4a59a9528dd647d0f0fa1f50c4f4dd33/4ab23/search_tree.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 53.400000000000006%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB+0lEQVR42nVTDW/SUBTl//oD/B8zMTPGJQ7DXNkkAnORLftwMsRMyMCYKRstfX3pF18tLQVKe7xt7bYavcl5vffc2/vuPcnLmaYJwzAy0A0dI3OEO36Lg24ZXOf4V91jRPl+v48cyMIwxGNLY91WIbTfwFt5CY9s3d/1siwnDYMgiMkwDBCsyXcJNsEhLAkzgpXwUf6hPkEURzYYDB4mjO8Yj4DTOmB52VnSDSYOcHhM+en9ZCkyE6aEzRgGRQHedJJtlF648MELRcjfuyTDEv7K/8/KFMtMh6woVOiTuBI0fZqOF+c9m44Z4Mw9XHd/0M8cCtVHTeZzL7vynRxAqK6wXodxA64FKB/5cNwkNkcBtovr+1io+rjqRL5PQzh4tePCossYk5KGTBnisNbG6s8K44mL2vE32LYbx/4C6LRMLJZJ/rKp4NftMPZH1hL5vQ7GUw+cM+RMc4hG4xL1+jk6nS6twNBsfiHuM3F1zBwHuqyi1WhClERYlo2LixO02i24JoPeKkO52sfX+ilx18hFR2FHwHa+gFrtBJXKB2xtvcausI9SqQJV1bBb3MPGs+c4O/uEm5ufeLH5Eu8qRxi2D4DqE+DjU7zPb+JtsYRcJKwoiiSoDEmSYpEjcdOvqqokPo9znHNomhbzjCkwVHpBXIRB4ApDr9fDb+uyL+O3kpflAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;search Tree&quot;
        title=&quot;&quot;
        src=&quot;/static/4a59a9528dd647d0f0fa1f50c4f4dd33/4ab23/search_tree.png&quot;
        srcset=&quot;/static/4a59a9528dd647d0f0fa1f50c4f4dd33/0eb09/search_tree.png 500w,
/static/4a59a9528dd647d0f0fa1f50c4f4dd33/1263b/search_tree.png 1000w,
/static/4a59a9528dd647d0f0fa1f50c4f4dd33/4ab23/search_tree.png 1462w&quot;
        sizes=&quot;(max-width: 1462px) 100vw, 1462px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;분홍색 원에 &lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt; MBR 과 &lt;code class=&quot;language-text&quot;&gt;B&lt;/code&gt; MBR이 모두 걸쳐 있기 때문에, &lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt; 와 &lt;code class=&quot;language-text&quot;&gt;B&lt;/code&gt; 모두 확인을 해본다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt; 의 자식 노드들 중에는 분홍색 원의 범위에 포함되는 MBR이 없으므로 &lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt; 브랜치의 검색은 종료된다.&lt;br&gt;
반면, &lt;code class=&quot;language-text&quot;&gt;B&lt;/code&gt; 의 자식 노드들 중에는 &lt;code class=&quot;language-text&quot;&gt;D&lt;/code&gt; 가 범위에 포함되어 있다.&lt;br&gt;
그리고 &lt;code class=&quot;language-text&quot;&gt;D&lt;/code&gt;의 자식 노드 중 &lt;code class=&quot;language-text&quot;&gt;M&lt;/code&gt;이 범위에 포함되기에 &lt;code class=&quot;language-text&quot;&gt;M&lt;/code&gt;의 데이터를 반환한다.&lt;/p&gt;
&lt;h2&gt;2-3. 인덱스 적용 안 되는 경우&lt;/h2&gt;
&lt;p&gt;어느정도 공간 인덱스를 이해한 것 같으니, 실행계획을 통해 인덱스가 잘 적용되는지 확인해보자.&lt;br&gt;
인덱스가 정상적으로 등록되어 있지만, 몇몇 경우에 등록된 인덱스가 적용되지 않는 경우가 있다.&lt;br&gt;
만약, 인덱스 등록은 정상적으로 이뤄졌으나 실행계획을 확인했을 때 인덱스가 적용되지 않는다면, 아래 항목들을 확인해보자.&lt;/p&gt;
&lt;h3&gt;2-3-1. 공간 함수 오용&lt;/h3&gt;
&lt;p&gt;대표적인 공간 데이터의 사용 예시는 &lt;strong&gt;기준점으로부터 반경 nKM 내에 있는 데이터를 검색&lt;/strong&gt;하는 경우이다.&lt;br&gt;
공간함수를 사용해서 예시의 결과를 반환하는 쿼리를 작성해 보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; example &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; ST_DISTANCE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ST_GeomFromText&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;POINT(10 20)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ST_DISTANCE&lt;/code&gt; 함수를 활용해서 &lt;code class=&quot;language-text&quot;&gt;point&lt;/code&gt; 와 기준점(10, 20)의 거리가 5KM 이하인 모든 데이터를 가져오는 쿼리이다.&lt;br&gt;
하지만 이 쿼리의 실행 계획을 확인해 보면 &lt;code class=&quot;language-text&quot;&gt;type&lt;/code&gt;이 &lt;code class=&quot;language-text&quot;&gt;ALL&lt;/code&gt;인 것을 확인할 수 있는데, 이는 곧, &lt;strong&gt;인덱스가 적용되지 않았다&lt;/strong&gt;는 의미이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;mysql&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXPLAIN&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; example &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; ST_DISTANCE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ST_GeomFromText&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;POINT(10 20)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; select_type &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt;   &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; partitions &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; possible_keys &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt;  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; key_len &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ref  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; filtered &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Extra       &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SIMPLE&lt;/span&gt;      &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; example &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;       &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ALL&lt;/span&gt;  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;          &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;    &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;   &lt;span class=&quot;token number&quot;&gt;100.00&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Using&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; warning &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.01&lt;/span&gt; sec&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;R-Tree&lt;/code&gt;는 &lt;strong&gt;MBR들의 포함관계&lt;/strong&gt;를 이용하여 만들어진 트리라고 했다.&lt;br&gt;
하지만 위의 &lt;code class=&quot;language-text&quot;&gt;ST_DISTANCE&lt;/code&gt;를 사용한 쿼리는 포함관계를 확인하는 것이 아니라, 모든 데이터와 기준점과의 거리를 계산하여 반환하는 쿼리이다.&lt;/p&gt;
&lt;p&gt;이러한 케이스는 &lt;code class=&quot;language-text&quot;&gt;ST_DISTANCE&lt;/code&gt; 대신 &lt;code class=&quot;language-text&quot;&gt;ST_CONTAINS&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;ST_BUFFER&lt;/code&gt;를 활용하면 쉽게 해결할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; example &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; ST_CONTAINS&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ST_BUFFER&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ST_GeomFromText&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;POINT(10 20)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ST_BUFFER&lt;/code&gt; 는 첫 번째 인자를 중심점으로 반지름이 두 번째 인자인 원을 그리는 함수이고,&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;ST_CONTAINS&lt;/code&gt; 는 첫 번째 인자가 두 번째 인자를 포함하는지 여부를 확인하는 함수이다.&lt;br&gt;
즉, 위의 쿼리는 &lt;code class=&quot;language-text&quot;&gt;POINT(10 20)&lt;/code&gt;을 중심으로 반경 5KM 내에 있는 데이터를 조회하는 쿼리이다.&lt;/p&gt;
&lt;p&gt;수정한 쿼리는 공간 데이터 간의 포함관계를 이용하므로, 인덱스가 잘 적용되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;mysql&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXPLAIN&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; example &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; ST_CONTAINS&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ST_BUFFER&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ST_GeomFromText&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;POINT(10 20)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----+-------------+-----------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; select_type &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt;     &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; partitions &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt;  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; possible_keys &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt;       &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; key_len &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ref  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; filtered &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Extra       &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----+-------------+-----------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SIMPLE&lt;/span&gt;      &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; sridTest2 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;       &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; range &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; idx_point     &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; idx_point &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;34&lt;/span&gt;      &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;    &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;   &lt;span class=&quot;token number&quot;&gt;100.00&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Using&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----+-------------+-----------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; warning &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.00&lt;/span&gt; sec&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2-3-2. SRID 미 적용&lt;/h3&gt;
&lt;p&gt;두 번째 이유는 어딘가에 SRID가 적용되지 않았거나 서로 상이한 값을 사용했기 때문이다.
다음 세 군데에 적용한 SRID 값이 모두 일치해야 인덱스가 정상적으로 동작한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;테이블의 컬럼 속성&lt;/li&gt;
&lt;li&gt;검색 쿼리의 SRID&lt;/li&gt;
&lt;li&gt;삽입된 데이터&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;1-3 에서 얘기했듯, 테이블의 컬럼 속성에 SRID 값을 적용할 수 있다.
이 부분에 SRID 값이 적용되지 않았다면, 인덱스는 적용되지 않는다.&lt;/p&gt;
&lt;p&gt;또한 검색 쿼리에 실수로 SRID를 미적용한 경우 역시 인덱스가 적용되지 않는다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; example &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; ST_CONTAINS&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ST_BUFFER&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ST_GeomFromText&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;POINT(10 20)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위에서 사용한 쿼리를 예로 들어보자면, &lt;code class=&quot;language-text&quot;&gt;ST_GeomFromText&lt;/code&gt; 함수에 두 번째 인자로 SRID 값이 빠져있다.
이런 경우에는 SRID값이 default인 0으로 지정되는데, 이때 point 데이터의 SRID값이 0이 아니라면 인덱스는 적용되지 않는다.
포함관계를 이용한 검색의 경우에는 SRID 값이 일치해야 인덱스가 적용된다.&lt;/p&gt;
&lt;p&gt;마지막으로 삽입된 데이터들이 같은 SRID 값을 갖고있는 지 확인해 봐야 한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;mysql&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DISTINCT&lt;/span&gt; ST_SRID&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; example&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----------------+&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ST_SRID&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----------------+&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;              &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;           &lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;----------------+&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.00&lt;/span&gt; sec&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위 쿼리를 실행했을 때 만약 예시와 같이 row가 두 개 이상 나온다면 데이터들의 SRID값이 통일되지 않았다는 뜻이다.
안타깝게도 &lt;code class=&quot;language-text&quot;&gt;MySQL&lt;/code&gt;에서는 데이터의 SRID 값을 일괄적으로 변경할 수 있는 방법이 없다.&lt;br&gt;
일일이 &lt;code class=&quot;language-text&quot;&gt;UPDATE&lt;/code&gt;문을 작성하거나, &lt;code class=&quot;language-text&quot;&gt;DELETE&lt;/code&gt; 후 새롭게 &lt;code class=&quot;language-text&quot;&gt;INSERT&lt;/code&gt;를 해줘야 한다.&lt;/p&gt;
&lt;p&gt;위 쿼리가 2개 이상의 row를 반환한다면, &lt;code class=&quot;language-text&quot;&gt;아.. 오늘은 야근이구나..&lt;/code&gt; 라고 생각하면 된다.&lt;/p&gt;
&lt;p&gt;하지만 다행히도, 1번의 내용인 컬럼 속성에 SRID 값이 설정되었다면, 나머지 2번 3번의 경우는 신경 쓰지 않아도 된다.
&lt;code class=&quot;language-text&quot;&gt;MySQL&lt;/code&gt;은 기본적으로 &lt;code class=&quot;language-text&quot;&gt;STRICT SQL MODE&lt;/code&gt;가 활성화되어있기에 상이한 SRID 값의 삽입과 포함관계를 이용한 검색에 에러가 발생하기 때문이다.
하지만 항상 만약이란 건 존재하기에 인덱스가 적용되지 않는다면, 꼭 한 번 확인을 해보자.&lt;/p&gt;
&lt;h1&gt;3. JPA로 공간 데이터 사용하기&lt;/h1&gt;
&lt;p&gt;공간 데이터와 충분히 친해진 것 같은 기분이 든다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;hibernate 5.0&lt;/code&gt; 부터 &lt;code class=&quot;language-text&quot;&gt;Hibernate ORM&lt;/code&gt;애 정식 라이브러리가 된 &lt;code class=&quot;language-text&quot;&gt;Hibernate-spatial&lt;/code&gt;을 이용하여 JPA로 공간 데이터의 입/출력을 다뤄보자.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.jboss.org/hibernate/orm/6.3/userguide/html_single/Hibernate_User_Guide.html#spatial&quot;&gt;Hibernate 공식문서&lt;/a&gt;와 &lt;a href=&quot;https://www.baeldung.com/hibernate-spatial&quot;&gt;Baeldung&lt;/a&gt;을 함께 참고하면 좋다.&lt;/p&gt;
&lt;h2&gt;3-1. 설정&lt;/h2&gt;
&lt;p&gt;공간 데이터를 사용하기 위해서는 의존성을 하나 추가해야 한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;implementation &lt;span class=&quot;token string&quot;&gt;&apos;org.hibernate.orm:hibernate-spatial:{hibernate vesion}&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;hibernate-spatial&lt;/code&gt;의 버전은 현재 프로젝트에 적용된 &lt;code class=&quot;language-text&quot;&gt;hibernate&lt;/code&gt;의 버전과 동일하게 작성해 주면 된다.&lt;br&gt;
만약 사용하는 &lt;code class=&quot;language-text&quot;&gt;hibernate&lt;/code&gt;의 버전이 6.0 이전의 버전이라면, &lt;code class=&quot;language-text&quot;&gt;Dialect&lt;/code&gt; 설정을 따로 해주어야 한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;spring&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jpa&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;database&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;platform &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hibernate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;spatial&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dialect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mysql&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MySQL8SpatialDialect&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;hibernate 6.0&lt;/code&gt; 부터는 spatial 관련 &lt;code class=&quot;language-text&quot;&gt;Dialect&lt;/code&gt;는 모두 &lt;code class=&quot;language-text&quot;&gt;Deprecated&lt;/code&gt;이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Hibernate-spatial&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Point&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;LineString&lt;/code&gt; 등의 데이터타입들은 &lt;code class=&quot;language-text&quot;&gt;java&lt;/code&gt; 표준 라이브러리도 아니고 &lt;code class=&quot;language-text&quot;&gt;JDBC&lt;/code&gt;의 사양도 아니다.&lt;br&gt;
그렇기에 공간 데이터를 사용하기 위해서는 &lt;code class=&quot;language-text&quot;&gt;JTS&lt;/code&gt;라는 외부 라이브러리를 이용해서 공간 데이터를 사용해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;hinernate-spatial&lt;/code&gt;는 내부에 &lt;code class=&quot;language-text&quot;&gt;JTS&lt;/code&gt; 와 &lt;code class=&quot;language-text&quot;&gt;geolatte-geom&lt;/code&gt; 을 포함하고 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;3-2.  JTS 공간 데이터 다루기&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;hibernate spatial&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;JTS&lt;/code&gt;를 사용하여 공간 데이터에 대한 타입을 지원한다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;Point&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;LineString&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;Polygon&lt;/code&gt; 등의 공간 데이터를 사용하기 위해서는 &lt;code class=&quot;language-text&quot;&gt;JTS&lt;/code&gt;에서 제공하는 데이터 타입을 &lt;code class=&quot;language-text&quot;&gt;import&lt;/code&gt;해야 한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;locationtech&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;geom&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Point&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;example&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Coordinate&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
  
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt;  
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Point&lt;/span&gt; point&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;가장 흔하게 사용되는 &lt;code class=&quot;language-text&quot;&gt;Point&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;Polygon&lt;/code&gt;을 생성해보면, 다른 타입에 대한 사용은 금새 익힐 수 있다.&lt;/p&gt;
&lt;p&gt;공간 데이터를 생성하는 방법에는 크게 두가지가 있다.&lt;br&gt;
첫 번째는 &lt;code class=&quot;language-text&quot;&gt;GeometryFactory&lt;/code&gt; 를 사용하는 것이고,&lt;br&gt;
두 번째는 &lt;code class=&quot;language-text&quot;&gt;WKTReader&lt;/code&gt;를 사용하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;code class=&quot;language-text&quot;&gt;WKT&lt;/code&gt;에 대한 설명은 1-2를 참고&lt;/em&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createPointWithFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
    &lt;span class=&quot;token comment&quot;&gt;//given  &lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GeometryFactory&lt;/span&gt; geometryFactory &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GeometryFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Coordinate&lt;/span&gt; coordinate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Coordinate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;180&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
    &lt;span class=&quot;token comment&quot;&gt;//when  &lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Point&lt;/span&gt; point &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; geometryFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createPoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;coordinate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    point&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSRID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// SRID 설정  &lt;/span&gt;
  
    &lt;span class=&quot;token comment&quot;&gt;//then  &lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;point&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;180&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;point&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  

&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createPointWithWKT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ParseException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
    &lt;span class=&quot;token comment&quot;&gt;//given  &lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WKTReader&lt;/span&gt; wktReader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WKTReader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Geometry&lt;/span&gt; read &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wktReader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;POINT(180 90)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
    &lt;span class=&quot;token comment&quot;&gt;//when  &lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Point&lt;/span&gt; point &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; read&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    point&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSRID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
    &lt;span class=&quot;token comment&quot;&gt;//then  &lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;point&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;180&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;point&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;WKTReader&lt;/code&gt;를 사용하는 방식은 &lt;code class=&quot;language-text&quot;&gt;WKT&lt;/code&gt; 를 읽어서 공간 데이터를 생성하는데, &lt;code class=&quot;language-text&quot;&gt;WKT&lt;/code&gt;는 모든 공간 데이터 타입의 입력이 가능하다. 그렇기 때문에 &lt;code class=&quot;language-text&quot;&gt;read()&lt;/code&gt;의 반환 값이 추상클래스인 &lt;code class=&quot;language-text&quot;&gt;Geometry&lt;/code&gt;타입을 반환하기에 다운캐스팅이 필요하다.&lt;br&gt;
또한, &lt;code class=&quot;language-text&quot;&gt;WKT&lt;/code&gt;를 파싱하는 과정에서 발생할 수 있는 &lt;code class=&quot;language-text&quot;&gt;ParseException&lt;/code&gt;도 처리해줘야 하기에 사용하기에 번거롭기 때문에 개인적으로는 &lt;code class=&quot;language-text&quot;&gt;GeometryFactory&lt;/code&gt;를 사용하는 방식을 선호한다.&lt;/p&gt;
&lt;p&gt;어떠한 방식을 사용하든 꼭 한 가지 주의해야 할 점이 있다.&lt;br&gt;
SRID 값이 4326이라는 가정하에, &lt;code class=&quot;language-text&quot;&gt;MySQL&lt;/code&gt; 에서 사용하는 &lt;code class=&quot;language-text&quot;&gt;WKT&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;X&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;Y&lt;/code&gt; 값은 각각 위도와 경도이다.&lt;br&gt;
&lt;em&gt;예시) POINT(위도 경도)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;그런데 &lt;code class=&quot;language-text&quot;&gt;new Coordinate()&lt;/code&gt;의 인자와 &lt;code class=&quot;language-text&quot;&gt;WKTReader&lt;/code&gt;가 읽는 &lt;code class=&quot;language-text&quot;&gt;WKT&lt;/code&gt; 모두 경도, 위도 순이다.&lt;br&gt;
&lt;em&gt;예시) &lt;code class=&quot;language-text&quot;&gt;new Coordinate(경도, 위도)&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;wktReader.read(&quot;POINT(경도 위도)&quot;)&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;SRID 값에 따라 &lt;code class=&quot;language-text&quot;&gt;X, Y&lt;/code&gt;가 각각 의미하는 값이 달라질 수 있기에 이런 차이가 발생한 듯하다.&lt;/p&gt;
&lt;p&gt;그런데 데이터의 삽입과정과 일반적인 조회에서는 &lt;code class=&quot;language-text&quot;&gt;X, Y&lt;/code&gt;에 그 어떤 값이 들어가도 예외가 발생하지 않는다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;selectWithSpatialException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;//given&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Point&lt;/span&gt; point &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; geometryFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createPoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Coordinate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    point&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSRID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CoordinateEntity&lt;/span&gt; coordinateEntity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; repository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CoordinateEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;point&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;//when&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CoordinateEntity&lt;/span&gt; saved &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; repository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;coordinateEntity&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;//then&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertSoftly&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;softAssertions &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;saved&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;saved&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;110&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;예외는 MySQL에서 &lt;code class=&quot;language-text&quot;&gt;ST_X&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;ST_Y&lt;/code&gt;를 통해 point의 값을 위도 경도로 변환하려 할 때 발생한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;
        SELECT co.id AS id, st_x(co.point) AS latitude, st_y(co.point) AS longitude
        FROM example AS co
        WHERE co.id = :id
        &quot;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CoordinateDto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findCoordinatesById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;공간함수인 &lt;code class=&quot;language-text&quot;&gt;ST_X&lt;/code&gt; 와 &lt;code class=&quot;language-text&quot;&gt;ST_Y&lt;/code&gt; 를 이용해 위도, 경도 값을 변환하여 반환받는 쿼리이다.&lt;br&gt;
&lt;em&gt;JPA에서 사용 가능한 공간함수에 대한 설명은 3-3에서 조금 더 자세히 다룬다.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;위 메서드를 실행시켜보면, 위도 범위를 벗어났다는 예외가 발생한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Caused by: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: 
A parameter of function st_x contains a geometry with latitude 110.000000, 
which is out of range. It must be within [-90.000000, 90.000000].
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:104)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:916)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeQuery(ClientPreparedStatement.java:972)
	at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52)
	at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java)
	at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.executeQuery(DeferredResultSetAccess.java:217)
	... 111 more&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;데이터를 삽입하는 순간에는 문제가 발생하지 않지만, 공간함수를 사용한 조회에서는 예외가 발생할 수 있다는 것은 문제사항에 대한 인지를 늦추게 된다. 이를 방지 하기 위해 공간 데이터를 사용하는 엔티티를 생성하는 시점에 위도 경도 범위에 따른 검증 절차를 추가해주는 것이 바람직하다고 생각한다.&lt;/p&gt;
&lt;p&gt;다음은 &lt;code class=&quot;language-text&quot;&gt;Polygon&lt;/code&gt; 을 생성하는 코드이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createPolygonWithFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
    &lt;span class=&quot;token comment&quot;&gt;//given  &lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GeometryFactory&lt;/span&gt; geometryFactory &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GeometryFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Coordinate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; coordinates &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Coordinate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Coordinate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  
            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Coordinate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  
            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Coordinate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  
            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Coordinate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  
            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Coordinate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    
    &lt;span class=&quot;token comment&quot;&gt;//when  &lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Polygon&lt;/span&gt; polygon &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; geometryFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createPolygon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;coordinates&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    polygon&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSRID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// SRID 설정  &lt;/span&gt;
  
    &lt;span class=&quot;token comment&quot;&gt;//then  &lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;polygon&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCoordinates&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
  
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createPolygonWithWKT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ParseException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
    &lt;span class=&quot;token comment&quot;&gt;//given  &lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WKTReader&lt;/span&gt; wktReader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WKTReader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Geometry&lt;/span&gt; read &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wktReader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
    &lt;span class=&quot;token comment&quot;&gt;//when  &lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Polygon&lt;/span&gt; polygon &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Polygon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; read&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    polygon&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSRID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
    &lt;span class=&quot;token comment&quot;&gt;//then  &lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;polygon&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCoordinates&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Polygon&lt;/code&gt;을 생성할 때는 주의해야 할 점들이 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;마지막 좌표는 항상 시작점 이어야 한다.
&lt;code class=&quot;language-text&quot;&gt;Polygon&lt;/code&gt;은 다각형의 모양인데, 마지막 좌표가 시작점이 아니라면 닫혀있는 형태가 아니게 된다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;구멍 뚫린 &lt;code class=&quot;language-text&quot;&gt;Polygon이 생성 가능하다.&lt;/code&gt;
&lt;code class=&quot;language-text&quot;&gt;Polygon&lt;/code&gt; 내부에 구멍이 뚫려 튜브 또는 도넛 형태의 &lt;code class=&quot;language-text&quot;&gt;Polygon&lt;/code&gt;을 생성 할 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;GeometryFactory&lt;/code&gt; 에서는 구멍을 위해 두 번째 인자로 &lt;code class=&quot;language-text&quot;&gt;LinearRing[]&lt;/code&gt; 을 받는다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Polygon&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createPolygon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LinearRing&lt;/span&gt; shell&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LinearRing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; holes&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Polygon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shell&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; holes&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위에 내가 사용한 예시처럼 &lt;code class=&quot;language-text&quot;&gt;Coordinate[]&lt;/code&gt; 하나만 인자로 사용하게 되면 구멍이 없는 &lt;code class=&quot;language-text&quot;&gt;Polygon&lt;/code&gt;이 생성된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Polygon&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createPolygon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Coordinate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; shell&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createPolygon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createLinearRing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shell&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Polygon&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createPolygon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LinearRing&lt;/span&gt; shell&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createPolygon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shell&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;WKT&lt;/code&gt;에서는 &lt;code class=&quot;language-text&quot;&gt;POLYGON((0 0, 10 0, 10 10, 0 10, 0 0), (2 2, 2 4, 4 4, 4 2))&lt;/code&gt;
이런 방식으로 구멍이 뚫린 &lt;code class=&quot;language-text&quot;&gt;Polygon&lt;/code&gt;을 생성할 수 있다.&lt;/p&gt;
&lt;p&gt;위에 내가 사용한 예시처럼 하나의 인자만 넣어준다면, 구멍이 없는 &lt;code class=&quot;language-text&quot;&gt;Polygon&lt;/code&gt;이 생성된다.&lt;/p&gt;
&lt;h2&gt;3-3. 공간함수를 사용한 조회&lt;/h2&gt;
&lt;p&gt;기본적인 &lt;code class=&quot;language-text&quot;&gt;CRUD&lt;/code&gt;는 여타 다른 데이터와 동일하게 &lt;code class=&quot;language-text&quot;&gt;DataJpa&lt;/code&gt;가 마법처럼 해결해 준다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;example&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Coordinate&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
  
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt;  
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Point&lt;/span&gt; point&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CoordinateRepo&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JpaRepository&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CoordinateEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;우리가 궁금한 건&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; example &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; ST_CONTAINS&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ST_BUFFER&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ST_GeomFromText&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;POINT(10 20)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이런 쿼리를 적용하는 방법이다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#spatial-configuration-dependency&quot;&gt;hibernate 공식 문서&lt;/a&gt;&lt;code class=&quot;language-text&quot;&gt;Table 5. Hibernate Spatial dialect function support&lt;/code&gt; 항목을 참고하면 공간 함수 별로 지원 여부가 표시되어 있는데, 다른 DB는 모르겠지만 &lt;code class=&quot;language-text&quot;&gt;MySQL&lt;/code&gt;은 5.6 이전 버전으로 표가 만들어져 있다.&lt;/p&gt;
&lt;p&gt;표를 믿지 말고, &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/spatial-function-reference.html&quot;&gt;MySQL 공식 문서&lt;/a&gt;와 비교를 해가며 사용해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;hibernate&lt;/code&gt;에서 지원하고 &lt;code class=&quot;language-text&quot;&gt;MySQL&lt;/code&gt;도 지원하는 공간 함수라면, &lt;code class=&quot;language-text&quot;&gt;JPQL&lt;/code&gt;이 사용 가능하다.&lt;/p&gt;
&lt;p&gt;위에서 얘기한 예시를 이용해 기준점으로부터 반경 5Km 내에 위치한 데이터들을 가져오는 기능을 만들어 보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;  
        SELECT co  
        FROM example AS co  
        WHERE st_contains(st_buffer(:center, :radius), co.point)  
        &quot;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CoordinateEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findAllWithInCircleArea&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;center&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Point&lt;/span&gt; center&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  
                                               &lt;span class=&quot;token annotation punctuation&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;radius&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; radius&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;MySQL&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;ST_COTAINS&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;ST_BUFFER&lt;/code&gt;는  &lt;code class=&quot;language-text&quot;&gt;hibernate&lt;/code&gt;에서 지원을 해주기 때문에 &lt;code class=&quot;language-text&quot;&gt;JPQL&lt;/code&gt;로 사용이 가능하다.
기준점과 반지름을 인자로 받아 쿼리를 생성한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findAllWithInCircle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
    &lt;span class=&quot;token comment&quot;&gt;//given  &lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Point&lt;/span&gt; point &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; geometryFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createPoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Coordinate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    point&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSRID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CoordinateEntity&lt;/span&gt; ce1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; repository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CoordinateEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;point&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Point&lt;/span&gt; point2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; geometryFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createPoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Coordinate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    point2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSRID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    repository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CoordinateEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;point2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
    &lt;span class=&quot;token comment&quot;&gt;//when  &lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CoordinateEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; allContainArea &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; repository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAllWithInCircleArea&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;point&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
    &lt;span class=&quot;token comment&quot;&gt;//then  &lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertSoftly&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;softAssertions &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;allContainArea&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;allContainArea&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ce1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;allContainArea&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;allContainArea&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;테스트를 실행시켜 정상적으로 동작하는 것을 확인 할 수 있다.&lt;/p&gt;
&lt;h1&gt;나오며&lt;/h1&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;MySQL&lt;/code&gt; 에서의 공간 데이터, 공간 함수, 공간 인덱스 그리고 &lt;code class=&quot;language-text&quot;&gt;DataJpa&lt;/code&gt;에서의 적용까지 알아보았다.&lt;/p&gt;
&lt;p&gt;물론 공간 데이터와 관련된 복잡한 기능개발을 위해서는 부족한 글일 수 있다.
그래도 이 글이 낯설기만 했던 공간 데이터와 한 걸음 더 가까워질 수 있는 기회가 되면 좋겠다.&lt;/p&gt;
&lt;hr&gt;
&lt;h4&gt;참고 자료&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;8.4 R-Tree 인덱스 | Real MySQL 8.0&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/spatial-types.html&quot;&gt;11.4 Spatial Data Types | MySQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/spatial-analysis-functions.html&quot;&gt;12.16 Spatial Analysis Functions | MySQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#spatial&quot;&gt;19. Spatial | Hibernate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/hibernate-spatial&quot;&gt;Introduction to Hibernate Spatial | Baeldung&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[JPA Cascade는 무엇이고 언제 사용해야 할까?]]></title><description><![CDATA[들어가면서 JPA를 사용한 개발을 진행하면서 가장 많이 들었던 고민 중 하나는  였습니다. ‘Cascade…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-08-14-JPA-Cascade/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-08-14-JPA-Cascade/</guid><pubDate>Mon, 16 Oct 2023 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;들어가면서&lt;/h2&gt;
&lt;p&gt;JPA를 사용한 개발을 진행하면서 가장 많이 들었던 고민 중 하나는 &lt;code class=&quot;language-text&quot;&gt;JPA Cascade를 언제 사용해야 하는가&lt;/code&gt; 였습니다. &lt;strong&gt;‘Cascade는 위험하니 조심해서 사용해야 한다!’&lt;/strong&gt; 와 같은 이야기도 많이 들었을뿐더러, 언제 어떻게 사용해야 할지 명확한 가이드라인을 찾기도 어려웠기 때문입니다. 따라서 이번 아티클에서 JPA Cascade에 대한 개념을 알아보고, 어떤 부분에서 위험하다고 일컬어지는 것인지까지 확인해 보도록 하겠습니다.&lt;/p&gt;
&lt;h2&gt;JPA Cascade란?&lt;/h2&gt;
&lt;p&gt;어떤 JPA 엔티티는 다른 엔티티의 존재에 깊게 연관되어 있기도 합니다. 가장 대표적인 예시로는 ‘댓글’과 ‘게시물’의 관계가 있습니다. ‘댓글’ 엔티티는 ‘게시물’ 엔티티가 없다면 존재 의의가 없기 때문입니다.&lt;/p&gt;
&lt;p&gt;게시판 어플리케이션을 만들어 본다고 가정해 봅시다. 게시물을 삭제하는 비즈니스 로직은 어떻게 작성할 수 있을까요?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// PostService.java&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;deletePost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; postId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; post &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; postRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;postId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; comments &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getComments&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    commentRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;deleteAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    postRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같이 비즈니스 로직을 작성할 수 있을 것 같습니다. 그런데 여기서 몇 가지 의문점이 생깁니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;게시물을 삭제할 때 댓글을 삭제하는 로직을 번거롭게 항상 작성해 줘야 할까?&lt;/li&gt;
&lt;li&gt;실수로 댓글 삭제 로직을 빼먹을 수도 있지 않을까?&lt;/li&gt;
&lt;li&gt;보다 좋은 방법이 없을까?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이런 문제점들을 해결하기 위해 JPA Cascade가 등장했습니다. JPA Cascade를 활용하면 ‘어떤 엔티티와 다른 엔티티가 밀접한 연관성이 있을 때’에 대한 관리가 매우 수월해집니다. 즉, A라는 엔티티에 어떤 작업을 수행했을 때, 그 작업이 연관된 B라는 엔티티도 이루어져야 한다면 JPA Cascade를 유용하게 사용할 수 있다는 의미입니다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1264px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/0fa31508247e8d3f056612a63cc302d1/f6c2e/2023-08-14-JPA_01.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 76.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAABt0lEQVR42p1U12pCQRT061VULIhdxAcVERHEf7DggwULigr23hG7E+aEmxhyE2IOHHZhd+dO2bsavFiPx0P6er2iVCohm80in88jl8vhcDhA8x9A1vl8ht/vh9FohNVqhcFgwHw+fw3wdDphPB5jsVig2WwiFAohHo8jlUrB4/H8DKjIem7WbrfDer2WOeX5fD5hZrFYhOlsNvsEVA79Vvv9XkBZ9/td2PZ6PfT7fRkvl8t3hjS7Xq+jVquhWq2i0WiIX2qA3W4XrVYLnU5HRu7TcIEyRqMRjsejHLLZbCKF7XA4ZG21WgkLBZCSvV4vtFqtyNXr9e+SicoDg8FAejgcIhAIIJFISAeDQVmfTCbCgqwI3m63EYlEEIvFJBSXy/UeiprxZrNZvsomWyUIrm02G5lTjdvtFmbcr9PpMJ1Ov3tIY8vlMorFIgqFAiqViqqHt9tNGNNn+s3mtdK8cpkJuN1uPwApkUnTDo4MVBWQQT33sx0KIFnzHiqh/OtPoW9kslwuRW44HEY0GkUymYTT6fx6sf9a9JjSKJ8MTSYT7Ha7MFQN5a9FDxlcOp1GJpORV4evzRtLrFRpYxbLWQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 14 JPA 01&quot; title=&quot;&quot; src=&quot;/static/0fa31508247e8d3f056612a63cc302d1/f6c2e/2023-08-14-JPA_01.png&quot; srcset=&quot;/static/0fa31508247e8d3f056612a63cc302d1/0eb09/2023-08-14-JPA_01.png 500w,
/static/0fa31508247e8d3f056612a63cc302d1/1263b/2023-08-14-JPA_01.png 1000w,
/static/0fa31508247e8d3f056612a63cc302d1/f6c2e/2023-08-14-JPA_01.png 1264w&quot; sizes=&quot;(max-width: 1264px) 100vw, 1264px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;blockquote&gt;
&lt;p&gt;Cascade는 ‘폭포수가 흐르다’ 라는 사전적 의미를 갖고 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;JPA Cascade Types&lt;/h2&gt;
&lt;p&gt;JPA에서는 총 6개의 Cascade Type을 지원하는데, 예제 코드와 함께 하나씩 살펴보겠습니다.&lt;/p&gt;
&lt;p&gt;예제 코드에서 사용하는 엔티티는 앞서 설명드렸던 ‘게시물(Post)‘과 ‘댓글(Comment)‘이며, 실습 환경은 다음과 같습니다. Post 엔티티의 물음표로 표시된 부분(CascadeType)을 바꿔가면서 실습을 진행할 예정입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Post 엔티티&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@OneToMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mappedBy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cascade &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; comments &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 생성자 생략&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;addComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; comment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        comments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Comment 엔티티&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ManyToOne&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@JoinColumn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;post_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;1. &lt;strong&gt;PERSIST&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;먼저 PERSIST 옵션을 살펴보도록 하겠습니다. 이는 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 옵션입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; post &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; comment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

entityManger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;persist&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// post, comment 둘 다 영속화&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2. &lt;strong&gt;MERGE&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;엔티티 상태를 병합할 때 연관된 엔티티도 함께 병합하는 옵션입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; post &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;this is post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; comment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;this is comment&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;persist&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;persist&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 영속성 컨텍스트로부터 분리&lt;/span&gt;

post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setTitle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;this is changed post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
comment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;this is changed comment&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;merge&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// post, comment 모두 변경사항 반영&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;3. &lt;strong&gt;REMOVE&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;엔티티를 제거할 때 연관된 엔티티들도 함께 제거합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; post &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; comment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;persist&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;persist&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// post, comment 모두 삭제&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;4. &lt;strong&gt;REFRESH&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;엔티티를 새로고침할 때, 연관된 엔티티들도 함께 새로고침합니다. 여기서 ‘새로고침’ 한다는 말의 의미는 데이터베이스로부터 실제 레코드의 값을 즉시 로딩해 덮어씌운다는 의미입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; post &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;this is post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; comment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;this is comment&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;persist&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;persist&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 데이터베이스에 반영&lt;/span&gt;

post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setTitle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;this is changed post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
comment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;this is changed comment&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;refresh&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 데이터베이스로부터 post, comment의 원본 값 즉시 로딩&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTitle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// this is post&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// this is comment&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;5. &lt;strong&gt;DETACH&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;엔티티를 영속성 컨텍스트로부터 분리하면 연관된 엔티티들도 분리됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; post &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;this is post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; comment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;this is comment&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;persist&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;persist&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;detach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// post, comment 모두 영속성 컨텍스트로부터 분리됨&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;6. &lt;strong&gt;ALL&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;위에서 언급한 모든 Cascade Type들이 적용됩니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;JPA의 구현체인 Hibernate의 경우에는 추가로 3개의 Cascade Types(REPLICATE, SAVE_UPDATE, LOCK)을 지원하는데, 이번 아티클에서는 다루지 않겠습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;JPA Cascade가 위험한 이유&lt;/h2&gt;
&lt;p&gt;보시다시피 Cascade의 개념 자체는 어렵지 않습니다. 단순히 EntityManager의 어떤 작업이 수행되면 해당하는 작업을 연관된 엔티티들에 전파하겠다는 의미입니다. 그렇다면 ‘Cascade는 위험하다’라고 일컬어지는 이유는 무엇일까요?&lt;/p&gt;
&lt;p&gt;연관관계 매핑과 함께 Cascade를 잘못 사용하게 되면 의도치 못한 결과를 불러올 수 있기 때문입니다.&lt;/p&gt;
&lt;h3&gt;1. 참조 무결성 제약조건 위반 가능성&lt;/h3&gt;
&lt;p&gt;CascadeType.REMOVE 혹은 CascadeType.ALL 옵션을 사용하면 엔티티 삭제 시 연관된 엔티티들이 전부 삭제가 되기 때문에 의도치 않게 참조 무결성 제약조건을 위반할 수도 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;참조 무결성 제약조건이란, 관계형 데이터베이스(RDB)에서 릴레이션(relation)은 참조할 수 없는 외래 키(foreign key)를 가져서는 안 된다는 조건을 의미합니다.&lt;/p&gt;
&lt;p&gt;이를 위반하는 경우 데이터의 모순이 발생하게 됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;예를 들어 아래와 같이 Comment와 Post가 다대일로 매핑이 된 상태이고 Comment 엔티티의 필드 post에 CascadeType.REMOVE를 지정한 상태라고 가정해 보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Comment.java&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ManyToOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cascade &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CascadeType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;REMOVE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@JoinColumn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;post_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...생략&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그렇다면 다음과 같은 문제가 발생할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;remove_bad_case&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; post &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; comment1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; comment2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    comment1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    comment2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    postRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    commentRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    commentRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    commentRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 관련된 엔티티(post)도 삭제&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;assertThatThrownBy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isInstanceOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PersistenceException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// comment2 가 참조하는 post 엔티티가 삭제되었으므로 외래키 관련 예외 발생&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 만약 외래키 제약조건이 없다면 예외가 발생하지 않음&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;comment1을 삭제했을 뿐이지만, 연관된 엔티티인 post까지 삭제되었고 기존에 post를 참조하고 있던 comment2는 참조하고 있는 값이 사라져 참조 무결성 제약조건 위반 예외가 발생합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;flush 시점에 참조 무결성 제약조건 위반 예외가 발생하는 이유는 ‘영속성 컨텍스트’에서는 외래키 제약조건이 존재하지 않기 때문입니다. 데이터베이스에 직접 쿼리를 날리고 나서야 예외가 발생하게 됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;실제 운영 환경에서는 개발 편의성을 위해 외래키 제약조건을 사용하지 않는 경우도 많은데, 그런 경우에는 예외도 발생하지 않아서 데이터 정합성에 문제가 생길 가능성이 큽니다.&lt;/p&gt;
&lt;h3&gt;2. 양방향 연관관계 매핑 시 충돌 가능성&lt;/h3&gt;
&lt;p&gt;양방향 연관관계 매핑을 하는 경우에도 유의해야 합니다. Comment, Post가 다대일 관계로 구성되어 있고 연관관계의 주인은 Comment라고 가정하겠습니다. 이 경우 아래와 같이 Post의 comments 필드에 CascadeType.PERSIST를 지정하는 경우 문제가 생길 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Post.java&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@OneToMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mappedBy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cascade &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CascadeType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PERSIST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; comments &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;테스트 코드를 통해 문제 상황을 보여드리면 다음과 같습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bidirectional_bad_case&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; post &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; comment1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; comment2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    commentRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    postRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;commentRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;existsById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isTrue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이상하게 &lt;code class=&quot;language-text&quot;&gt;commentRepository.delete(comment1)&lt;/code&gt; 을 호출했음에도 삭제가 되지 않는 모습을 확인할 수 있습니다. &lt;code class=&quot;language-text&quot;&gt;delete&lt;/code&gt; 를 호출해 comment1이 삭제된 상태에서 post를 &lt;code class=&quot;language-text&quot;&gt;save&lt;/code&gt; 하니 다시 comment1 값이 복원된 것입니다.&lt;/p&gt;
&lt;p&gt;이처럼 영속화에 대한 관리 지점이 두 곳이면 데이터값을 예측할 수 없는 문제가 발생합니다. &lt;strong&gt;‘영속성 전이(cascade)는 관리하는 부모가 단 하나일 때 사용해야 한다’&lt;/strong&gt; 라는 주장이 나온 배경도 비슷한 맥락입니다.&lt;/p&gt;
&lt;p&gt;이를 해결하기 위해서는 다음과 같이 post에서도 comment를 제거하도록 편의 메소드를 구현해야 합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Post.java&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;deleteComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; comment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    comments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bidirectional_good_case&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; post &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; comment1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; comment2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;deleteComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    commentRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    postRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;commentRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;existsById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;comment1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isFalse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이는 비단 CascadeType.PERSIST만의 문제는 아니며, 다른 CascadeType을 사용하면서 양방향 연관관계 매핑을 적용하는 경우에는 항상 염두에 둬야 하는 부분입니다.&lt;/p&gt;
&lt;h2&gt;언제 사용해야 할까?&lt;/h2&gt;
&lt;p&gt;JPA Cascade는 엔티티 간 관계가 명확할 때 사용하는 것을 권장합니다. 단순히 편해서, 비즈니스 로직이 깔끔해진다고 해서 JPA Cascade를 사용하는 것은 추후 큰 문제를 야기할 수도 있습니다. 특히 Cascade 오용으로 인한 문제가 발생하는 경우, 앞서 설명한 예시처럼 예외가 발생하지 않는 경우도 있기 때문에 발견되지 못한 채로 운영 환경에 오랫동안 잠식할 확률이 높습니다.&lt;/p&gt;
&lt;p&gt;‘게시물’과 ‘댓글’의 관계처럼 부모 - 자식 구조가 명확하다면 Cascade를 사용하는 것은 괜찮은 선택입니다. 하지만 하나의 자식에 여러 부모가 대응되는 경우(‘수강 중인 수업’과 ‘학생’의 관계 등)에는 사용하지 않는 것이 좋습니다. 여러 부모가 대응되는 순간 예상하지 못한 충돌 문제가 발생할 확률이 높아지고, 이로 인해 비즈니스 로직을 작성하는 개발자는 항상 JPA Cascade의 부수효과를 경계해야 하기 때문입니다.&lt;/p&gt;
&lt;p&gt;JPA Cascade는 개발 편의성을 높일 수 있는 기술입니다. 하지만 이를 오용하는 경우 데이터 정합성을 깨뜨릴 수 있을뿐더러 최악의 경우에는 데이터 손실까지 발생하게 됩니다. 따라서 JPA Cascade에 대한 정확한 개념과 발생할 수 있는 문제상황들을 먼저 익힌 뒤 유의해서 사용하시면 좋을 것 같습니다.&lt;/p&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.baeldung.com/jpa-cascade-types&quot;&gt;https://www.baeldung.com/jpa-cascade-types&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;자바 ORM 표준 JPA 프로그래밍 - 김영한&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://terms.tta.or.kr/dictionary/dictionaryView.do?subject=%EC%B0%B8%EC%A1%B0%20%EB%AC%B4%EA%B2%B0%EC%84%B1&quot;&gt;정보통신 용어사전 - 참조 무결성 제약조건&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[전송 제어 프로토콜이란?]]></title><description><![CDATA[OSI 7계층과 전송제어 프로토콜 데이터 통신은 여러 단계로 진행되는데, OSI 참조 모델은 그 과정을 7개 계층으로 나눈다.
1계층(물리 계층)은 ‘케이블이 연결된 상대에 대한 전기·기계적 신호 전달’ 제어를 담당한다.…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-08-10-WHAT-IS-TCP/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-08-10-WHAT-IS-TCP/</guid><pubDate>Wed, 11 Oct 2023 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;OSI 7계층과 전송제어 프로토콜&lt;/h2&gt;
&lt;p&gt;데이터 통신은 여러 단계로 진행되는데, OSI 참조 모델은 그 과정을 7개 계층으로 나눈다.
1계층(물리 계층)은 ‘케이블이 연결된 상대에 대한 전기·기계적 신호 전달’ 제어를 담당한다.
2계층(데이터링크 계층)은 네트워크 내부에서 데이터를 주고받는 방식을, 3계층(네트워크 계층)은 네트워크 간에 데이터 송수신 방식을 제어한다.
즉, 하나의 컴퓨터에서 다른 컴퓨터로 데이터를 주고받는 방식을 결정하는 일이 1계층부터 3계층의 역할이다.&lt;/p&gt;
&lt;p&gt;5계층은(세션 계층) 두 통신장치 간 연결과 통신 시작 및 종료 시점을 결정하며,
6계층(표현 계층) 애플리케이션 목적에 맞는 데이터 형식으로 변환한다.
7계층(응용 계층)은 실제 네트워크 서비스를 사용자에게 제공하는 역할을 담당한다.
간단히 정리하자면 1계층부터 3계층은 데이터 전송, 4계층부터 7계층은 전송된 데이터를 애플리케이션 목적에 맞게 처리하는 과정을 제어한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1005px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/05a6ada90ec7fc419499d57961340ff2/3d463/2023-08-10-What_IS_TCP_001.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 82.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACWUlEQVR42l2U2W7bMBBF9dN968e0zj+0AfqUCuhbbRhe4hWO932J94USe8/EdO0IGFAihzNn7pCK2u32r7e3t5+y52CaM6vVas+VSsXGcrlsY1j7ZD+63e5LPp//Ejnn/OFwSI/HI6PZdru1sdPp+GKx6BuNhi+VSr7ZbNr8fr9/MPk7r0eBn6LRaMSLE1nS6/USBUkWi0UynU6T8XicLJdLG5lbrVbJbDZLtCeZTCY3m8/n58vlQuLv0W6389VqNS0UCl4bvBxI5hXQq1yPI1SMPPgruQdkOByaac0ItTdjhKJKpYEXoTlvNhuz9XptAUTl39/f7R05WAvj1ULJmYhNokuz2awXuld5N0L0O51ORng+n20+SRI2+larZcklBdP/CSlRdCl0/X7fnMkODYboJCJxaEIgxe6bokozEciBkGxBQ97Rle5jgZAK6vW6yYNMkKqKR8Kg4T1h0OgzYdDt3ido+ECYy+UeCAnC2UNDCEOX8YcwaMjZfOgyHRRZCh1HAFKagE5sJqDOotGEwx90xK7fFlAyfBCKJNW1MUK6y+2hdF01IyZo0JB1yNCQIwdhKNkIEVkOaXBihIZzBxkJKZ9vaNASC+eUy/CgIdgiSXX5bZHNlAYhNhgMrLQ0TS0BSaEMXWZdgdxV9w9CbbyIzGnRyclxrlS+UwIz3hXUQaKgTmROxLdR6ycCqg/f7G9DU6ChJCh5KIfs17/RrcvcFCSBloZCf3dTniJ19o+Owe/X19dYAWLhxwoQq6xY1LE2x9oci87m+VagWJtjlW1+mn+R/dX313/UQfEVSP5VaQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;이미지1&quot;
        title=&quot;&quot;
        src=&quot;/static/05a6ada90ec7fc419499d57961340ff2/3d463/2023-08-10-What_IS_TCP_001.png&quot;
        srcset=&quot;/static/05a6ada90ec7fc419499d57961340ff2/0eb09/2023-08-10-What_IS_TCP_001.png 500w,
/static/05a6ada90ec7fc419499d57961340ff2/1263b/2023-08-10-What_IS_TCP_001.png 1000w,
/static/05a6ada90ec7fc419499d57961340ff2/3d463/2023-08-10-What_IS_TCP_001.png 1005w&quot;
        sizes=&quot;(max-width: 1005px) 100vw, 1005px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;그렇다면 4계층의 역할은 무엇일까?
3계층 이하에서는 ‘데이터 전달’만 제어할 뿐, 신뢰할 수 있는 데이터 전달을 보장하지 못한다.
데이터를 수신할 대상이 존재하지 않거나, 전송 중인 데이터가 분실·손상되는 문제를 다루지 않는다.
따라서 데이터를 안전하게 전송하기 위하여 전달되기 전과, 전달된 후의 데이터에 대한 처리를 4계층(전송계층)이 제어한다.
이러한 처리를 담당하는 프로토콜이 전송제어프로토콜(TCP, Transmission Control Protocol)이다.
그렇다면 TCP는 안전한 데이터 전송을 위해 구체적으로 어떤 역할을 담당할까?&lt;/p&gt;
&lt;h2&gt;TCP 헤더와 포트 번호&lt;/h2&gt;
&lt;p&gt;물건이 상자에 포장되어 택배로 보내지듯이, 데이터도 송신에 필요한 제어 정보와 함께 전송된다.
데이터의 앞부분에 위치하는 제어 정보를 헤더(Header)라고 한다.
데이터가 안전하게 전달되도록 제어하는 역할을 TCP 헤더가 담당한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 973px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/41994cedc0631aba7a71a32eed169a9f/08a5e/2023-08-10-What_IS_TCP_002.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 76.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAC9klEQVR42q1US08TURQeBFpjYmvFTV0YF2iU2ESDJKgLQiTahIUIKdFWEl24aGwxDI1gXFRZuTVEEgwL/wMr7NO2lIa+bKCSKYR3CaVUKG2g7cx4PGd0fAR3epMvZ3LPud/9znfnXqalpeVoU1NTHUGr1Z7R6XSa0dHRWo/HU0MAgEOQc3+pqWJwVCNOINQILeIY8y8jEok0p9PpweXlZcvs7KwlFos9DQQCrIylpSV2fn6exRy7vr7ORqNRdmpqSoJck0gk+tbW1gbHx8fPMbhgCKVCoVCo4CQgOaysrABuAJiDSqUCBwcHsLu7S2WQyWQAiSXINdlsVqAcbtbJoCI7ksHW1tY+gpeBRfzm5iaPHfChUIj3+Xx8OByW5iiPxPxv9aV8Pg9Op/MuKXxF7IIgVEhNqVSCcrkMPM9DLpeTFJCSxcVFwNYlpZSTQfXYgaRwenq6k8FTekkLZmZmSvF4XMS2xbm5OREXi8lkUkSFYjAYlL7RDhE3EdESKc9xnIiKRVQnifF6vZ0MEg3RLqikQv6trq6SF4BGA3lKni0sLEie0aBa8pjUplIpQELy+ZdCLO5FD7M4mcTIIRGHaiimisUih/Pczs4OhyQc5lPb29scnrgEqsNuaP7z3t5eGk/8FqPX65VtbW1qjCoC/tRqlmVP2e32k2ajUWNub9cYKZrNmicdHXWDiNcDA2rMq6xWq4qiyWRSGQwGNV6SGua/D7ouuMsRGQ6Hw+RxuZ47/ZP9sbFhW+5hq83lctlcH/22DdbEFjt0LzKGZj2tNTQ0KOrr65WNjY21qLD6D1L5G0/dF8dD8YejQuL9CBQe34bJgB+8k0HIPOsRoPsKlB/ceIulp48rFOcxXkVcRCh+F/qTcGJiwhcKBsEZCAqRd28g/+gmuF3Or06fDzb6jAJ0XYbCvWsjdPfVSuVZjDrEhR/vwGEP3W53l9vtYD/4/ZZPY8PWLz2tVpfDYXV4fNZ0733L/p1L/YXu661Sy98fFhlEVvUNGXGjEu0tIvQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;이미지2&quot;
        title=&quot;&quot;
        src=&quot;/static/41994cedc0631aba7a71a32eed169a9f/08a5e/2023-08-10-What_IS_TCP_002.png&quot;
        srcset=&quot;/static/41994cedc0631aba7a71a32eed169a9f/0eb09/2023-08-10-What_IS_TCP_002.png 500w,
/static/41994cedc0631aba7a71a32eed169a9f/08a5e/2023-08-10-What_IS_TCP_002.png 973w&quot;
        sizes=&quot;(max-width: 973px) 100vw, 973px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위 사진은 TCP 헤더가 제어하는 정보를 나타낸다.
우선 TCP 헤더에는 송신처와 수신처의 포트 번호가 포함된다.
앞서 편의상 컴퓨터 간에 데이터를 주고받는다고 설명했지만, 실제로 데이터를 주고받는 주체는 애플리케이션이다.
컴퓨터 내 존재하는 다양한 애플리케이션 중 데이터 송수신이 필요한 애플리케이션을 구분하기 위해 포트 번호를 지정한다.&lt;/p&gt;
&lt;h2&gt;세그먼트 분할과 시퀀스 번호&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 706px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/84ea24605d301bf5b3acfa892b430715/190d7/2023-08-10-What_IS_TCP_003.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 40%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABoElEQVR42mWROWsCYRCG1yRIWFFCQrIR0giLmI0RUqTySrN/IEGw8EAEO3+CWNgIighW1iIWgheCRyVYWCgq2HgQXLBQUtuIxWTmIwZjBh7e2Zl3Zgc+jsOIRqNnx1BNr9fzGo1G0Gq1Nzqd7pqU5/l7wul0XgCA6mRGxf2EyuVynXP/g0eMyAMiISLygjwj6lMz/YQlbrdb8Hq974jR4/GYfT7fExEIBCQiFAqZCOoFg8FHyg+eYy/2P/x+/yUny7IGt5tEUbw1m82CwWAQSE+xWCx31DvmuG+1Wp8kSVJzkUjkdTKZLGezmTKfz5kuFgul2+0q+XxeqVarSrlcZlQqFfZ94FDH/BO9X7jLziUSCcdut4PNZgPj8Rh6vR5st1tYr9fQ6XRgOBxCv99n2mq1oFgsQqFQAFwEg8GA9Uaj0Z40k8nI9MKO5XIJiqIAXgbT6RRWqxWZoFQqQbvdhmazyajX61Cr1QAvBbyK1RqNBnn2pPF4XObC4bAul8vZcdhG4ADTbDZri8VitmQy+Yd0Ov3LoZZKpazofcNHufoGWX0Yx0mNokAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;이미지3&quot;
        title=&quot;&quot;
        src=&quot;/static/84ea24605d301bf5b3acfa892b430715/190d7/2023-08-10-What_IS_TCP_003.png&quot;
        srcset=&quot;/static/84ea24605d301bf5b3acfa892b430715/0eb09/2023-08-10-What_IS_TCP_003.png 500w,
/static/84ea24605d301bf5b3acfa892b430715/190d7/2023-08-10-What_IS_TCP_003.png 706w&quot;
        sizes=&quot;(max-width: 706px) 100vw, 706px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;데이터와 헤더가 통합된 상태를 프로토콜 데이터 유닛(PDU, Protocol Data Unit)이라고 한다.
7계층부터 5계층까지 거쳐 전송된 데이터(메시지)에 4계층의 전송 제어 헤더를 결합한 것이 4계층 PDU 즉, 세그먼트이다.&lt;/p&gt;
&lt;p&gt;하나의 데이터는 여러 개의 세그먼트로 분할되어 전송된다.
각각의 세그먼트에 번호를 부여하는데 이것을 ‘시퀀스 번호’라고 한다.
여러 개로 분할 전송된 데이터가 최종적으로 순서에 맞게 결합하여 수신되는 만큼 시퀀스 번호는 중요한 의미를 가진다.&lt;/p&gt;
&lt;h2&gt;커넥션과 3-Way Handshake&lt;/h2&gt;
&lt;p&gt;수신처 IP주소로 데이터를 전송했지만, 실제 수신처가 존재하지 않을 경우 데이터 전송에 실패할 것이다.
또 수신처가 존재하지만, 데이터를 받을 준비가 되지 않은 경우도 마찬가지다.
본격적으로 데이터를 주고받기 전, 송신자와 수신자 간 데이터 전송 가능 여부를 확인한다면 데이터의 전송을 보장할 수 있다.
이처럼 송·수신자 간에 데이터를 확실하게 주고받을 수 있는 가상 경로를 ‘커넥션’이라고 한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 941px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6086219051f2a0d8c981074d1f48d573/b4b29/2023-08-10-What_IS_TCP_004.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 43.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABXklEQVR42rXSzU7CQBAA4AFrbaW0tJXy05pKBUoQRCItYIQFkVBPyoGDXjhK4kNw8T1M8ObBEzHxxKPVKS6midHEg5N8mbSZ7czuFgAghjQURyKSkIIS1Bb8MVSURzYyaS4CsJhZzMAB+BGAefQzf4sI9b/B0Uk3HaPOzatKrp8S53dLmUyW6eH4XSO3qz3Pe9sJ1QE9Hjn8Lke3usnBOXKiWmnsSoUTScrXRbXeWFNqLsOIDl0s0vpDZFHM5oNfk2btuD15rOnFoZTr3usFd6wb/ZltXz5Y1f7swCZTszRbtDWrpVWxXgitNelOoYnO0AU65RXeMMqiwjDgCipbAhZsVmDLMXn7CFWC7PvzKG/welCP+nR9C7FAu3TQFTLWvZJJQc5222q64aRSzWbKHPUy+6OOZnkDnjec0FQZNEI9+rv9FH7keLiIkemKCy6BTF6E4Nn1nkVCVsxv1/sBfREy+PIcq2MAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;이미지4&quot;
        title=&quot;&quot;
        src=&quot;/static/6086219051f2a0d8c981074d1f48d573/b4b29/2023-08-10-What_IS_TCP_004.png&quot;
        srcset=&quot;/static/6086219051f2a0d8c981074d1f48d573/0eb09/2023-08-10-What_IS_TCP_004.png 500w,
/static/6086219051f2a0d8c981074d1f48d573/b4b29/2023-08-10-What_IS_TCP_004.png 941w&quot;
        sizes=&quot;(max-width: 941px) 100vw, 941px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;커넥션을 확보하는 방법으로 쓰리웨이 핸드쉐이크(3-Way Handshake)가 있다.
위 그림처럼 애플리케이션 A는 B에 데이터 송신 허가를 요청(SYN)한다.
B는 요청에 대한 허가(ACK)를 A에 알린다.
이로써 A는 B에 대한 송신로를 확보한다.&lt;/p&gt;
&lt;p&gt;하지만 아직 반대의 송신로(B → A)는 확보되지 못한 상황이다.
따라서 B는 A에 허가 사실(ACK)을 전달함과 동시에 데이터 송신 허가 요청(SYN)을 함께 보낸다.
A가 B의 요청을 수락(ACK)함으로써 양방향 커넥션이 확립된다.
연결 설정에 사용되는 제어 비트인 SYN과, 데이터 수신 성공을 의미하는 ACK 같은 정보는 TCP 헤더의 플래그에 담긴다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 920px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b08081847e4df496d5caf86bf469610d/d8a90/2023-08-10-What_IS_TCP_005.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 42.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABWElEQVR42rXSzU7CQBAA4AElpWkX6C+lTRvaEuTHUqBCNWhUigQSQBMkJsabvgfP4Ut49+TZxHfS2bhGQrh4cJIv3TQzs9udAgCoyEJ5pCMNGchEEtqDPwYtrqEy8lDlW+YQIGvjkgP4TP3aGSn475AR/7NbFD1nkpsXpXX1KpxfvxWS5F2hz9HqIw/rdXqrltaVNl+4qIp89tn7guDpmh2Oi151qDtBYjeOFnazMy/VwjExyJTmoMzGNdVZH3ow4Daa06EU6EBklxv5sXIhu3zfPc6P/FNpUu7LY29gTFmutHUyjm0AIeqiAEWI0MlbLTluT5yGP7CbrUU5DmZON1pYcXBpHrAG9K/osboOarOBQhM9ohWbOORyfkXSe/dErU9lI15q1smt7gwfVPPsjif2HFPSbLI0f4meUINdw87gRLGkEmIqeBAJcC0UPV0UKxpkJYc13BlfB+YqeEApCuwAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;이미지5&quot;
        title=&quot;&quot;
        src=&quot;/static/b08081847e4df496d5caf86bf469610d/d8a90/2023-08-10-What_IS_TCP_005.png&quot;
        srcset=&quot;/static/b08081847e4df496d5caf86bf469610d/0eb09/2023-08-10-What_IS_TCP_005.png 500w,
/static/b08081847e4df496d5caf86bf469610d/d8a90/2023-08-10-What_IS_TCP_005.png 920w&quot;
        sizes=&quot;(max-width: 920px) 100vw, 920px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;TCP 연결을 초기화하는 3-Way Handshake와는 반대로 연결을 종료하는 절차를 4-Way Handshake라고 한다.
애플리케이션 A는 B에 연결을 종료하겠다는 의미로 FIN 플래그를 전송한다.
B는 FIN 플래그에 대 확인 메시지 ACK를 보낸다.
B는 일정 시간 대기 후 연결 종료 준비를 마치면 연결 종료 요청을 위해 FIN 플래그를 A에 보낸다.
마지막으로 A가 확인 응답 메시지 ACK를 보내면서 연결은 종료된다.&lt;/p&gt;
&lt;h2&gt;확인 응답과 에러 복구&lt;/h2&gt;
&lt;p&gt;수신처에서 데이터를 받았다는 응답을 보내준다면 송신처는 분실 여부를 확인할 수 있다.
이처럼 세그먼트를 수신하면, 응답 정보를 송신처에 전달하는데 이를 ‘확인응답’이라고 한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 884px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/848c2846a20f2ce44bd2b6249bb7267b/2b3a9/2023-08-10-What_IS_TCP_006.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 37.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABFUlEQVR42rXRbUvDMBAH8Oum0bkx7JTCahfWbmtnKhUtU+kKIj68E0XfDEVFRET8KIIoDmWfdv6jNwl7Kx78CEl6l2tCRKRAQgeasEB/jDo40IAVmDH2LKKx9TN+mw5ryv+EBy6PNq/pLgvJ4bOzcTRsb+2+eenBq1RKCazPGt0s8V/VOX9RLxagBEUIYA5q0BLCjcp2sFapSFW2GzG2fKynoAvPg895JW7Cn3Qp+NSIN/RDbfJc67JViI0iIecJbkzP6Qye4JSTdVSTY+nvPXbT/fuwl9+FSX7bVv2Hzrq3U63xN0Uufs75J5OCDp++/HurY7JuPrebg2EvvvzIoutR1roa9eXFSx4M3jPXeGEzX98nfQFxDB/hcFVJxgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;이미지6&quot;
        title=&quot;&quot;
        src=&quot;/static/848c2846a20f2ce44bd2b6249bb7267b/2b3a9/2023-08-10-What_IS_TCP_006.png&quot;
        srcset=&quot;/static/848c2846a20f2ce44bd2b6249bb7267b/0eb09/2023-08-10-What_IS_TCP_006.png 500w,
/static/848c2846a20f2ce44bd2b6249bb7267b/2b3a9/2023-08-10-What_IS_TCP_006.png 884w&quot;
        sizes=&quot;(max-width: 884px) 100vw, 884px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;TCP 헤더에 시퀀스 번호와 확인응답 번호가 포함되는데, 앞서 설명했듯이 시퀀스 번호는 세그먼트의 순서를 나타낸다.
확인응답 번호는 다음에 받고자 하는 데이터의 시퀀스 번호를 의미한다.
수신처는 세그먼트의 시퀀스 번호를 통해 해당 데이터의 순서를 파악하고, 다음에 받을 시퀀스 번호를 확인응답 번호에 명시하여 응답한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 899px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c34d562d3cc225fbdc1ce6ea4c5f86f4/f7121/2023-08-10-What_IS_TCP_007.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 37.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABQklEQVR42mNgYGBQAGJ5IJYBYlEg5mCgEIAMk4IaKEGigYxYMCawr9/PErrqP3N9/X8WIGYCif0PDWX+H6rF9n9VKDMhW+SAWBrqQpBLGbSM8+SUNeO0VQzStdjl4xWmWmqbTjORN5ygI6BXq8ZnmmbMwArVK4mkTxaIhRjQvKgMxExC0mYy3NzSeoKCSjoMbPIaHcbqIWGqmnYMbGoavLzKlvLy9jA9KkheZ4HqB9siCMT8QGwAEtAwydMRlfWwkVXysZGU87Xml3QyEpXzshKTd7GQUvbzUzJO44caZAjVC8JiQKwNEvQC4iIgDgdidZjNLCwMFqxcDAasrAz6XFwMhiCaAYQZGIyAmA2qTg2qrxiIvWFBxgZ1HReS13m03ZRlXbJ1de0ytTXtk7VUbBJVlVyAbHE9cW60OOCC6mcH8QDWHC6DE/teRwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;이미지7&quot;
        title=&quot;&quot;
        src=&quot;/static/c34d562d3cc225fbdc1ce6ea4c5f86f4/f7121/2023-08-10-What_IS_TCP_007.png&quot;
        srcset=&quot;/static/c34d562d3cc225fbdc1ce6ea4c5f86f4/0eb09/2023-08-10-What_IS_TCP_007.png 500w,
/static/c34d562d3cc225fbdc1ce6ea4c5f86f4/f7121/2023-08-10-What_IS_TCP_007.png 899w&quot;
        sizes=&quot;(max-width: 899px) 100vw, 899px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;데이터가 전송 도중에 분실되는 경우가 발생할 수 있다.
세그먼트 송신 후 일정 기간 확인 응답이 돌아오지 않는다면 송신처는 해당 세그먼트를 재전송한다.
송신처가 확인 응답을 기다리는 시간을 RTO(Retransmission Timeout)라고 한다.
RTO의 초깃값은 1초로 설정되는데, 실제 확인응답이 돌아오는 시간을 반영하여 조정될 수 있다.&lt;/p&gt;
&lt;h2&gt;윈도우 제어&lt;/h2&gt;
&lt;p&gt;하나의 세그먼트를 송신하고 확인 응답을 보내는 과정이 시간 비용면에서 비효율적일 수 있다.
한 개의 세그먼트에 관한 확인 응답을 받기까지 다음 세그먼트를 보내지 못하고 기다려야 하기 때문이다.
따라서 복수의 세그먼트를 한 번에 전송하고 확인응답을 받는 방식을 통해 시간 효율을 높일 수 있다.&lt;/p&gt;
&lt;p&gt;복수의 세그먼트를 정확하고 효율적으로 송신하기 위해 ‘윈도우 제어’를 사용한다.
‘윈도우 제어’는 송신자와 수신자 간 데이터 전송 속도를 조절하는 ‘흐름 제어(Flow Control)’의 한 형태다.
복수의 세그먼트를 안정적으로 처리하기 위해 수신한 데이터를 일시적으로 보관하는 버퍼(Buffer)가 필요하다.
수신처의 버퍼 용량 이상으로 데이터가 송신된다면, 데이터를 처리할 수 없어서 파기하는 ‘오버플로’가 발생할 수 있다.
따라서 데이터 수신처는 어느 정도 버퍼량을 가졌는지 상대방에게 알려주게 된다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1058px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8ca92b2169a29cefe087731220c9a118/e5cc9/2023-08-10-What_IS_TCP_008.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 57.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACS0lEQVR42mNgYGDQB2IFIFYHYlUg5mDADxjRMAZgBmImJJqRgUIgDcQiUCwONRgfEAViCah6kF5udAUgb7JADVIEYk48XgUBJSDmgbKlgFgMTZ5BD4g9gdgRGo4YLqyHBAUTlCsDxDZA7A3EpkDMh2Kgvn2/AK+YroWAqIE+VAKkQAhICPEzMAgA2WxQcSafemOu5NluQmr2khoSqgJ2WqHyErnbPNhDey2EInuMRaInmvEx2HgvFYwp/s/tl/Sa19g4jZULEj4y0PCU1BNhUwuS5nZMURbUvlJvIbQwVltzd56x3O1qE9mtKRpq9bkqfPZTtHhCO4z5Y7r1uBksQlcJWbjNFjL2WSriHLBOGBTYghwMssHi3Npd+lIGperCjiUqom65ukIyU6bY8xQXG8oXFBhLZhZpy1ZVGUtG15vxebQaiAZ264n59+sLgA00DVgoDDFwofBsQwnNei1Rm1ZtUeuJeiKGVapCZolKgjbtumJKxwothHZn6iqtyTKUB7lyW5q2MlCMM/5+PEfaJmOu+Hp7DgZjn5lcQEM5JX02cZW7lPPvtFEwL1EWs6zQEDFu0hXTLQMaWK8t7FCvLixlWawnZhihqGcRo6yiEySpoR8vr6DtJiVrFaVqYBgkIY+RLlaFMjC/89GS+x+jJ/Y/Wlfmf6i+9P94fYH///+DY7BVR0y8WENQB+gDlWwlQd1WHUkNLS4uWJoUxjDwjLEx6w0PNb0T9ioy1/3UeZHT4H8QBlr4f1Uo8/96Bqb/9fVM/9OMWf+j5SwARtKDSsOHJScAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;이미지8&quot;
        title=&quot;&quot;
        src=&quot;/static/8ca92b2169a29cefe087731220c9a118/e5cc9/2023-08-10-What_IS_TCP_008.png&quot;
        srcset=&quot;/static/8ca92b2169a29cefe087731220c9a118/0eb09/2023-08-10-What_IS_TCP_008.png 500w,
/static/8ca92b2169a29cefe087731220c9a118/1263b/2023-08-10-What_IS_TCP_008.png 1000w,
/static/8ca92b2169a29cefe087731220c9a118/e5cc9/2023-08-10-What_IS_TCP_008.png 1058w&quot;
        sizes=&quot;(max-width: 1058px) 100vw, 1058px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위 그림에서 송신처는 한 번에 3개의 세그먼트를 전송한다.
하나의 세그먼트 데이터양이 1000 옥텟이고, 수신처의 기본 버퍼량이 3000 옥텟이라고 가정하자.
수신처는 세그먼트를 버퍼에 임시 보관하며, 각 세그먼트를 저장할 때마다 버퍼량이 1000 옥텟씩 감소한다.
3개의 세그먼트 모두가 버퍼에 저장되면 버퍼량은 0이되고 데이터 처리가 실행되면서 버퍼량은 다시 3000 옥텟으로 복구된다.&lt;/p&gt;
&lt;p&gt;송신처가 보낸 각각의 세그먼트에 대응하여 수신처는 확인응답 번호와 윈도우 사이즈 정보를 TCP 헤더에 포함하여 전송한다.
앞서 설명했듯이 확인응답 번호는 다음 순서로 받고자 하는 데이터 시퀀스 번호를 의미한다.
데이터 통신에서 윈도우(Window)는 한 번에 전송하거나 수신할 수 있은 데이터 프레임를 나타내는 개념이며,
윈도우 사이즈는 해당 데이터 프레임의 수를 의미한다.
위 그림에서 수신처가 송신처에 마지막으로 보낸 확인응답 번호는 3001이며 윈도우 사이즈는 3000 옥텟이다.
이는 송신처가 시퀀스 번호가 3001인 데이터부터 세그먼트(데이터양 1000 옥텟) 3개를 한 번에 보내도 좋다는 의미다.&lt;br&gt;
이처럼 윈도우 사이즈로 버퍼량을 전달하여 수용 가능한 데이터를 송수신하는 과정을 윈도우 제어라고 한다.&lt;/p&gt;
&lt;h2&gt;TCP의 단점과 UDP&lt;/h2&gt;
&lt;p&gt;앞서 설명한 TCP의 역할인 3-Way Handshake, 에러 복구, 흐름 제어 방식은 데이터 전송을 확실하게 보장한다는 장점이 있다.
하지만 안정적인 데이터 전송을 위한 확인 요청과 응답 과정에서 시간이 소요된다는 단점이 있다.
UDP를 통하여 이러한 단점을 보완할 수 있다.&lt;/p&gt;
&lt;p&gt;UDP(User Datagram Protocol)는 TCP와 마찬가지로 전송 계층에 속하는 프로토콜이다.
UDP는 앞서 설명한 TCP의 ‘3-Way Handshake’와 같은 연결 설정 과정을 생략한다.
게다가 ‘확인응답’ 과정을 거치지 않기 때문에 데이터 도착 여부를 검증하지 않는다.
이와 같은 제어 과정이 생략되는 만큼 신속한 데이터 전송이 가능하다.
이처럼 UDP는 데이터 전송의 신뢰성보다는 신속성이 중요한 애플리케이션에서 주로 사용된다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1015px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d242a0687968317678392966e843b4f2/4d2c8/2023-08-10-What_IS_TCP_009.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 80.19999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsTAAALEwEAmpwYAAACyUlEQVR42rVTS0tbURC+VpqmRIpUWrtKKQS7imv7S7qyi8K1NQtdhK5duWoD8ZJFs3AhdFEkpV7IRgQTJA8VH2iIJD4TzVsTH7E5JLk3p99Mq9AuKz3wcefcMzPnm2/mKIqidNlstn6r1Wq3WCwD9HU4HA+UOy5KYAMeAT10yT9nmpiY6M3n819rtdr3zc3NwNra2reVlZW5WCymLy0t6ScnJ3q9XtcPDg70arWqb29v68vLywzyCYfDc0dHR0H4fZmenn6ieL3e/h9YEuv8/LxzcXEhCbAlEsjLy0vZbDZlpVKR5HZ9fX17Tjg9Pe1QLP5XA4HAc2VycrL/+Pi4Dobk2IaTAdug79nZmQG2RjQaNcDWACOjXC7zOS5jwKYYCZaVmZkZOzPM5XJMkm6jxCiRmRDDvb09ub+/L3d3d2U6nZZ0LoSQjUaDv/DtmKZJsdXZ2Vm74na7n4JBDVq0FhcXGxsbGyIej4uFhQWxuroqwExEIhEBbQXKFggU0JrPwFiATAOVtA8PD/N+v9/+h4ZYnVKpJNEAZkQMOh2WSCKI7VarJdEACZkkkpC27HB1dfVLQ4/H8zCZTL6FBu/hMILuvQPjEWinYq8imLG1taVmMhkVEqhgrKIKBpirhUJhZGdn543P5+tR/sfqGh4e7hsaGnIMDg4OOJ3Ol+Pj471gbCWgGsaNffM/FApZ//YBbh+EFXj8G31A910YdmMcPmAGpzAeH6Hnp/X1dS+6qEEjDdpoaJSWSCQ0dFRDszTopkFrBvmkUqkpNPLz/Pz8C0p4H8OaoE5BfBPCy2w2S4PK84cx4e5S99vtNu/Jh0BdpjnFBJjkg0a+4oRw5oTFYtGkJHjbnJDGh4b8JiE9QbJBgM/Il54mlkmXoTJOeA/Ga9w4hrJGUaoLw+1CWS6yUY4LyXmPcl1I5MII8RkBMfRvFFKNBYPBZz8BsRT7ZWLhOFYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;이미지9&quot;
        title=&quot;&quot;
        src=&quot;/static/d242a0687968317678392966e843b4f2/4d2c8/2023-08-10-What_IS_TCP_009.png&quot;
        srcset=&quot;/static/d242a0687968317678392966e843b4f2/0eb09/2023-08-10-What_IS_TCP_009.png 500w,
/static/d242a0687968317678392966e843b4f2/1263b/2023-08-10-What_IS_TCP_009.png 1000w,
/static/d242a0687968317678392966e843b4f2/4d2c8/2023-08-10-What_IS_TCP_009.png 1015w&quot;
        sizes=&quot;(max-width: 1015px) 100vw, 1015px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위 그림은 UDP 헤더를 나타낸다. TCP 헤더에 포함됐던 확인 응답 번호, 시퀀스 번호, 윈도우 사이즈, 제어 비트, 등이 빠져 있음을 알 수 있다. 데이터의 확실한 전송을 보장하기 위한 절차가 UDP에는 생략됐다는 사실을 UDP 헤더를 통해서도 알 수 있다.&lt;/p&gt;
&lt;p&gt;이처럼 ‘빠른 전송’이 필요한 경우 UDP를, ‘정확한 전송’이 필요한 경우에는 TCP를 사용할 수 있다. 각각의 장단점을 제대로 파악하여 서비스 환경에 맞는 프로토콜을 선택할 필요가 있을 것이다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Transmission_Control_Protocol&quot;&gt;위키피디아&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.ktword.co.kr/test/view/view.php?m_temp1=1889&quot;&gt;정보통신 기술 용어 해설&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.tutorialspoint.com/tcp-3-way-handshake-process&quot;&gt;3-Way Handshake&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://accedian.com/blog/tcp-receive-window-everything-need-know/&quot;&gt;tcp window&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;‘성공과 실패를 결정하는 1%의 네트워크 원리’ / Tsutomu Tone / 성안당&lt;/li&gt;
&lt;li&gt;‘하루 3분 네트워크 교실’/ 아미노 에이지 / 영진닷컴&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[무한스크롤 기능에서 동시성 이슈 해결하기]]></title><description><![CDATA[안녕하세요. 우아한테크코스 5기 BE…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-08-16-ObjectOptimisticLockingFailureException/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-08-16-ObjectOptimisticLockingFailureException/</guid><pubDate>Wed, 11 Oct 2023 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕하세요. 우아한테크코스 5기 BE 연어입니다.
팀 프로젝트에서 열심히 &lt;code class=&quot;language-text&quot;&gt;요즘카페&lt;/code&gt;라는 서비스를 만들고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://yozm.cafe&quot;&gt;요즘카페&lt;/a&gt;는 스크롤 내리면서 성수동의 카페들을 하나씩 볼 수 있는 &lt;strong&gt;숏폼&lt;/strong&gt; 형식의 서비스입니다.
최근에 무한 스크롤 기능을 추가하면서 예상치 못한 동시성 문제를 맞닥뜨렸는데요.&lt;/p&gt;
&lt;p&gt;이 글에서는 &lt;code class=&quot;language-text&quot;&gt;ObjectOptimisticLockingFailureException&lt;/code&gt;이 왜 발생했고, 관련 개념들과 해결 방법에 대해서 다뤄보고자 합니다.&lt;/p&gt;
&lt;p&gt;목차는 아래의 순서와 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;문제 발생&lt;/li&gt;
&lt;li&gt;배경 설명&lt;/li&gt;
&lt;li&gt;Entity Manager&lt;/li&gt;
&lt;li&gt;DB Isolation Level&lt;/li&gt;
&lt;li&gt;문제 원인&lt;/li&gt;
&lt;li&gt;해결 방안&lt;/li&gt;
&lt;li&gt;정리&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;문제 발생&lt;/h2&gt;
&lt;p&gt;평소처럼 선릉 캠퍼스에 도착해서 로그를 보고 있었습니다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1858px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/1504dbefe3529499a56daf55b8800916/bf7a7/2023-08-16-object-optimistic-locking-failure-exception.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 16%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAjUlEQVR42j1OWw6CMBDsAcCK2AcPQYo0Coh4/8ONu1vixyTzyu4o328o6xcKH3EyD4G2E3nPv+YsKwMuFXuj5NK/jpKfXUROPKeOasMXpp1hmhn2tsL3b/CT6r7DdcQJrlups5C3UWcRXQ8f6TJnvwm7aCXHjoNc4lCbSVbwZ215QdJpYVrEKA4I91E6P3aJT8UmL5ntAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 16 object optimistic locking failure exception&quot; title=&quot;&quot; src=&quot;/static/1504dbefe3529499a56daf55b8800916/bf7a7/2023-08-16-object-optimistic-locking-failure-exception.png&quot; srcset=&quot;/static/1504dbefe3529499a56daf55b8800916/0eb09/2023-08-16-object-optimistic-locking-failure-exception.png 500w,
/static/1504dbefe3529499a56daf55b8800916/1263b/2023-08-16-object-optimistic-locking-failure-exception.png 1000w,
/static/1504dbefe3529499a56daf55b8800916/bf7a7/2023-08-16-object-optimistic-locking-failure-exception.png 1858w&quot; sizes=&quot;(max-width: 1858px) 100vw, 1858px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;그런데 처음 보는 예외가 발생해 있었고, &lt;strong&gt;어떤 행동을 하면 발생하는지&lt;/strong&gt; 알기 위해 이런저런 행동을 해봤습니다.&lt;/p&gt;
&lt;h3&gt;기대했던 동작&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;회원이 스크롤을 엄청 빠르게 내려도&lt;/li&gt;
&lt;li&gt;로그에는 예외가 발생하지 않고&lt;/li&gt;
&lt;li&gt;새 카페가 중복 없이 나온다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;문제가 발생하는 행동&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;회원이 스크롤을 엄청 빠르게 내리면&lt;/li&gt;
&lt;li&gt;로그에는 위와 같이 &lt;code class=&quot;language-text&quot;&gt;ObjectOptimisticLockingFailureException&lt;/code&gt;이 발생해 있고&lt;/li&gt;
&lt;li&gt;스크롤을 내려도 계속 똑같은 카페 화면이 보인다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;왜 문제가 발생했을까요…?&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;배경 설명&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://yozm.cafe&quot;&gt;요즘카페&lt;/a&gt;에서는 회원에게 &lt;code class=&quot;language-text&quot;&gt;아직 보여주지 않은 카페&lt;/code&gt;들을 우선으로 보여주는 로직이 존재합니다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 886px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/85b3661a3c4f181b6965df6359c134f2/6f18b/2023-08-16-unviewed-cafe-erd.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 55.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB1ElEQVR42m1Sa3OTUBDlz5gCSQjPAEmJYAwOpKF5TM1ImqRq+pjWjlNHxw/634+7S4no9MNh773cPXv23FXUjgut60FtO3D8GEGUwqbIcMNEohMkaBt9uufS/wm8wfj5/ximdwrhIHBUeKETYUuzUCxKfLx5wu76G/a3P/Dp/ifuvv7GfH2AYQZE6uPq8IgP+wfsbr7j+vEXknRBYmzhYC5FFWYHJ7qNPlUejWcYxjnWmwMm+QpxOsdpkqPd86UTXvP5RfkZEd3lHO7uqFCnj2GF6NkDdEVFn6q5mBYrGHaAV6p5TOC7Ldrb1Oa7rEBLt0QZK+9wQSGkg4iqJpMC8dszhKNU4oC8Et8a/tReda2AioV4k55L7mg8xYDyuLBSXzyhDXtR77lFL4yhy2N4Fei8Q114wWuKfuVbA2rtodaoXr26K57a/UhUssd1UYPUme6Q2v1b/J9XVl9oySA/Gayw5wylRQarMr0ILiu0fPn3vxilNlt/PmSUu1uUV19wsb3HanOH7eFJsKdReb97wJrAI5UvLsW3Zv6RsCZjXybZEtlsjWxeIitKTOcbnC0vBTmtp0Q0W27pIc+hNSZAe6llBg95BVPGpkJPIp/Xe7XhY93yH75SbaMnRXObAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 16 unviewed cafe erd&quot; title=&quot;&quot; src=&quot;/static/85b3661a3c4f181b6965df6359c134f2/6f18b/2023-08-16-unviewed-cafe-erd.png&quot; srcset=&quot;/static/85b3661a3c4f181b6965df6359c134f2/0eb09/2023-08-16-unviewed-cafe-erd.png 500w,
/static/85b3661a3c4f181b6965df6359c134f2/6f18b/2023-08-16-unviewed-cafe-erd.png 886w&quot; sizes=&quot;(max-width: 886px) 100vw, 886px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;회원마다 아직 보지 않은 카페 목록 &lt;code class=&quot;language-text&quot;&gt;UnViewedCafe&lt;/code&gt;를 가지고 있습니다.
이는 &lt;code class=&quot;language-text&quot;&gt;Member:Cafe = N:M&lt;/code&gt; 인 다대다 관계를 중간에서 &lt;code class=&quot;language-text&quot;&gt;1:N, M:1&lt;/code&gt; 로 해결해 주는 테이블입니다.&lt;/p&gt;
&lt;p&gt;회원이 스크롤을 내릴 때마다 새로운 카페들을 보여주게 되는데 이때 아래의 두 API가 필요합니다.&lt;/p&gt;
&lt;h4&gt;A. 회원이 아직 보지 않은 카페 목록을 조회하는 API&lt;/h4&gt;
&lt;p&gt;회원이 다음 카페를 보기 위해 스크롤을 내리면, 아직 보지 않은 카페들 중 랜덤으로 &lt;strong&gt;5&lt;/strong&gt;개를 응답으로 보내줍니다.
해당 회원의 &lt;code class=&quot;language-text&quot;&gt;UnViewedCafe&lt;/code&gt; 테이블에서 5개를 조회해 와서 그대로 리턴하는 방식입니다.&lt;/p&gt;
&lt;h4&gt;B. 회원이 해당 카페를 봤다는 처리를 하기 위한 API&lt;/h4&gt;
&lt;p&gt;회원이 다음 카페를 보기 위해 스크롤을 내리면, 해당 카페 ID를 서버로 보내서 &lt;code class=&quot;language-text&quot;&gt;UnViewedCafe&lt;/code&gt; 테이블에서 제거합니다
&lt;code class=&quot;language-text&quot;&gt;UnViewedCafe&lt;/code&gt; 테이블에서 (memberId, cafeId) 조합의 데이터를 삭제하는 방식입니다.
또한 해당 회원의 &lt;code class=&quot;language-text&quot;&gt;UnViewedCafe&lt;/code&gt;가 0개가 됐을 때는 전체 카페들을 가져와서 다시 채워줍니다. 따라서 무한 스크롤이 가능해지게 됩니다.&lt;/p&gt;
&lt;p&gt;위의 두 API를 가지고 &lt;strong&gt;프론트엔드&lt;/strong&gt;에서는 아래와 같은 방식으로 동작합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;카페 목록에서 스크롤을 내려 추가로 볼 수 있는 남은 카페가 3개 이하&lt;/strong&gt;일 때마다 &lt;strong&gt;A&lt;/strong&gt;를 요청합니다.&lt;/li&gt;
&lt;li&gt;스크롤을 내려 다음 카페를 볼 때마다 &lt;strong&gt;B&lt;/strong&gt;를 요청합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;대체 왜 &lt;code class=&quot;language-text&quot;&gt;ObjectOptimisticLockingFailureException&lt;/code&gt;이 발생했고, 스크롤을 내려도 똑같은 카페 화면만 보였을까요?
이유를 알기 위해서 먼저 &lt;code class=&quot;language-text&quot;&gt;JPA의 Entity Manager&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;DB Isolation Level&lt;/code&gt;에 대해 알아야 합니다.
아래에서 하나씩 간단히 살펴보겠습니다.&lt;/p&gt;
&lt;h2&gt;Entity Manager&lt;/h2&gt;
&lt;p&gt;JPA를 쓰신다면 다들 들어보셨을 용어입니다.&lt;/p&gt;
&lt;p&gt;Spring Data JPA를 사용하신다면 보통 아래의 방법들로 DB와 소통할 것입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JPA Repository Interface&lt;/li&gt;
&lt;li&gt;JPQL&lt;/li&gt;
&lt;li&gt;Native Query&lt;/li&gt;
&lt;li&gt;Entity Manager&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위의 방법들은 공통점이 있습니다.
Spring Container에서 관리되는 &lt;code class=&quot;language-text&quot;&gt;Entity Manager&lt;/code&gt;를 통해 쿼리를 수행한다는 점인데요.&lt;/p&gt;
&lt;p&gt;그럼 이제부터 이 &lt;code class=&quot;language-text&quot;&gt;Entity Manager&lt;/code&gt;의 특징에 대해서 하나씩 살펴봅시다.&lt;/p&gt;
&lt;h4&gt;&lt;code class=&quot;language-text&quot;&gt;Entity Manager&lt;/code&gt;는 항상 트랜잭션 내에서 동작합니다.&lt;/h4&gt;
&lt;p&gt;보통 서비스 레이어에서 &lt;code class=&quot;language-text&quot;&gt;@Transactional&lt;/code&gt;으로 메서드를 하나의 작업 단위로 관리하는데요.
이때 내부적으로 하나의 &lt;code class=&quot;language-text&quot;&gt;Entity Manager&lt;/code&gt;가 할당되고 이 &lt;code class=&quot;language-text&quot;&gt;Entity Manager&lt;/code&gt;가 영속성 컨텍스트, 실제 DB와 소통하게 됩니다.&lt;/p&gt;
&lt;h4&gt;&lt;code class=&quot;language-text&quot;&gt;Entity Manager&lt;/code&gt;는 싱글톤이 아닙니다.&lt;/h4&gt;
&lt;p&gt;기본적으로 멀티 스레드 방식으로 동작하는 스프링을 사용할 때 여러 요청을 동시에 처리합니다.
이때 실제 DB와 소통하는 &lt;code class=&quot;language-text&quot;&gt;Entity Manager&lt;/code&gt;를 싱글톤으로 쓴다면 여러 동시성 문제가 발생할 수 있겠죠?&lt;/p&gt;
&lt;p&gt;따라서 Spring에서는 DB와 연결을 관리하는 객체인 &lt;code class=&quot;language-text&quot;&gt;Entity Manager Factory&lt;/code&gt;를 싱글톤으로 관리합니다.
그리고 &lt;code class=&quot;language-text&quot;&gt;Entity Manager&lt;/code&gt;가 필요할 때마다 &lt;code class=&quot;language-text&quot;&gt;Entity Manager Factory&lt;/code&gt;가 생성하는 방식으로 다수의 &lt;code class=&quot;language-text&quot;&gt;Entity Manager&lt;/code&gt; 인스턴스를 관리합니다.&lt;/p&gt;
&lt;h4&gt;&lt;code class=&quot;language-text&quot;&gt;Entity Manager&lt;/code&gt;는 자신만의 1차 캐시를 관리합니다.&lt;/h4&gt;
&lt;p&gt;JPA를 쓸 때 변경 감지를 사용하시나요?
&lt;strong&gt;쓰기 트랜잭션&lt;/strong&gt; 범위 내에서 &lt;strong&gt;영속 상태의 인스턴스&lt;/strong&gt;는 스냅샷이 관리됩니다.
&lt;code class=&quot;language-text&quot;&gt;Entity Manager&lt;/code&gt;는 이 스냅샷을 관리하다가 최종적으로 트랜잭션이 커밋될 때 DB로 Flush 해줍니다.&lt;/p&gt;
&lt;h4&gt;정리해 보면,&lt;/h4&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Entity Manager Factory&lt;/code&gt;에서 관리하는 &lt;code class=&quot;language-text&quot;&gt;Entity Manager&lt;/code&gt;는 트랜잭션별로 생성되고 소멸합니다.
또한 1차 캐시를 각각 생성해서 관리합니다.
따라서 한 엔티티를 여러 스레드에서 동시에 수정해도 자신들만의 캐시에서 관리하기 때문에 서로 간섭이 없습니다.&lt;/p&gt;
&lt;p&gt;그렇다면 동시에 아무리 요청이 많이 들어와도 동시성 이슈가 없지 않나요?&lt;/p&gt;
&lt;h4&gt;문제는 1차 캐시에서 DB로 Flush될 때 발생합니다.&lt;/h4&gt;
&lt;p&gt;Spring Data JPA의 기본 구현체인 Hibernate는 Flush를 한 이후 실제 DB에서 영향 받은 데이터의 수를 리턴 받습니다.&lt;/p&gt;
&lt;p&gt;이때, 기존 상태에서 예상되는 &lt;strong&gt;변경할 데이터의 수&lt;/strong&gt;와 &lt;strong&gt;실제 변경된 데이터의 수&lt;/strong&gt;가 다르면 &lt;code class=&quot;language-text&quot;&gt;StaleObjectStateException&lt;/code&gt;이 Hibernate에서 발생합니다.
그리고 위 런타임 예외가 JPA 레벨로 올라오면, &lt;strong&gt;더 일반적인 예외인 &lt;code class=&quot;language-text&quot;&gt;ObjectOptimisitcLockingFailureException&lt;/code&gt;으로 Wrapping&lt;/strong&gt; 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;동시에 여러 요청이 동일한 데이터를 변경하려고 할 때&lt;/strong&gt;, Application 레벨에서는 문제가 없지만 DB로 Flush될 때 발생하게 되겠죠?&lt;/p&gt;
&lt;h2&gt;DB Isolation Level&lt;/h2&gt;
&lt;p&gt;데이터베이스에는 **격리 수준(Isolation Level)**이라는 개념이 존재합니다.
이는 트랜잭션의 작업이 다른 트랜잭션의 작업과 어떤 관계를 맺는지 정의합니다.&lt;/p&gt;
&lt;p&gt;간단히 살펴보면 크게 4가지 단계가 있습니다. (더 자세한 설명은 &lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2022-11-07-mysql-isolation/&quot;&gt;트랜잭션 격리 수준&lt;/a&gt;에서 보실 수 있습니다.)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Uncommitted Read&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;다른 트랜잭션에서 현재 수정 중인 내용도 볼 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Committed Read&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;다른 트랜잭션에서 커밋을 하면, 그 내용도 볼 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repeatable Read&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;한 번 읽어온 정보에 대해서는, 다른 트랜잭션에서 수정하더라도 이전에 읽었던 상태로 보입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Serializable&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;읽어온 정보에 대해서 현재 트랜잭션이 끝날 때까지, 다른 트랜잭션에서는 읽기조차 불가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;저희는 어떤 격리 수준을 사용하고 있을까요?
이를 위해 저희가 사용하는 MySQL에 대해서도 간단하게 살펴봅시다.&lt;/p&gt;
&lt;p&gt;MySQL은 &lt;strong&gt;MySQL 엔진&lt;/strong&gt;과 &lt;strong&gt;스토리지 엔진&lt;/strong&gt;으로 구현돼 있습니다.
간단히 &lt;strong&gt;MySQL 엔진은 핵심 본체&lt;/strong&gt;이고, &lt;strong&gt;스토리지 엔진은 주로 읽기, 쓰기 등의 작업을 처리하는 일꾼&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;스토리지 엔진은 여러 종류가 있고, MySQL에서도 여러 가지를 기본으로 제공하는데요.
&lt;strong&gt;테이블 별로 어떤 스토리지 엔진&lt;/strong&gt;을 사용할지 설정할 수 있습니다.
&lt;strong&gt;MySQL에서는 기본적으로&lt;/strong&gt; 테이블을 만들 때 스토리지 엔진을 지정하지 않으면, &lt;strong&gt;InnoDB 스토리지 엔진&lt;/strong&gt;을 사용하게 됩니다.&lt;/p&gt;
&lt;p&gt;그리고 &lt;strong&gt;격리 수준은 각 트랜잭션별로 설정&lt;/strong&gt;할 수 있습니다.
&lt;strong&gt;InnoDB에서는&lt;/strong&gt; 트랜잭션을 수행할 때 격리 수준을 따로 지정하지 않으면, &lt;strong&gt;Repeatable Read&lt;/strong&gt; 수준을 사용하게 됩니다.&lt;/p&gt;
&lt;p&gt;결론은 &lt;strong&gt;Repeatable Read&lt;/strong&gt; 특성상 &lt;strong&gt;최초 조회한 시점 이후&lt;/strong&gt;로 어떤 수정이 발생해도 똑같은 결과만 보이게 되겠죠?&lt;/p&gt;
&lt;h2&gt;드디어 살펴보는 문제 원인&lt;/h2&gt;
&lt;p&gt;여기까지 먼 길 오시느라 고생하셨습니다!&lt;/p&gt;
&lt;p&gt;다시 문제를 리마인드 해봅시다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;회원이 스크롤을 엄청 빠르게 내리면
&lt;strong&gt;ObjectOptimisticLockingFailureException&lt;/strong&gt; 예외가 발생해 있고
스크롤을 내려도 &lt;strong&gt;계속 똑같은 카페 화면&lt;/strong&gt;이 보인다&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;먼저 &lt;strong&gt;프론트엔드&lt;/strong&gt;에서는 아래와 같은 방식으로 동작합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;회원이 스크롤을 내려서 다음 카페를 조회할 때마다 서버에게 해당 카페를 봤다는 요청을 합니다.&lt;/li&gt;
&lt;li&gt;스크롤을 내려 추가로 볼 수 있는 남은 카페가 3개 이하일 때마다 서버에게 다음 카페 5개를 요청합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;그리고 서버의 &lt;code class=&quot;language-text&quot;&gt;UnViewedCafe&lt;/code&gt; 테이블에 해당 회원의 남은 카페가 3개일 경우를 생각해 봅시다.&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;스크롤을 내릴 때, 추가로 볼 수 있는 남은 카페 목록이 3개 이하라면 다음 카페 조회 요청을 보냅니다.
&lt;ul&gt;
&lt;li&gt;이때 스크롤 속도가 빠르다면 남은 카페 목록이 3개, 2개, 1개일 때 다음 카페 요청을 동시에 3번 보내게 됩니다.&lt;/li&gt;
&lt;li&gt;또한 스크롤을 내리면서 조회한 카페를 봤다는 요청을 동시에 3번 보냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위의 상황에서&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서버는 봤다는 요청이 들어올 때마다 해당 카페를 회원의 &lt;code class=&quot;language-text&quot;&gt;UnViewedCafe&lt;/code&gt; 테이블에서 제거합니다.
&lt;ul&gt;
&lt;li&gt;마지막 1개의 카페가 제거되어 0개가 됐을 때, 새로 전체 카페 목록을 회원의 &lt;code class=&quot;language-text&quot;&gt;UnViewedCafe&lt;/code&gt;에 채웁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;서버는 다음 카페 조회 요청이 들어올 때, 남아 있는 카페들을 보냅니다.
&lt;ul&gt;
&lt;li&gt;봤다는 요청이 처리되는 시점에 따라 동일한 3개를 3번 보냈을 수도 있고 3개, 2개, 2개를 보냈을 수도 있고 2개, 2개, 1개를 보냈을 수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;위의 행동들이 어떠한 순서로 처리될지는 매번 다릅니다.&lt;/h4&gt;
&lt;p&gt;결론적으로 발생하는 문제는 아래와 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;회원이 스크롤을 움직일 때마다 동일한 카페에 대해서 봤다는 요청이 동시에 발생할 수 있습니다.
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;ObjectOptimisticLockingFailureException&lt;/code&gt;이 발생합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스크롤을 내려도 새로 카페가 채워지지 않은 시점이라면 2개, 1개의 카페만 계속해서 보일 수 있습니다.
&lt;ul&gt;
&lt;li&gt;서버에서 회원의 &lt;code class=&quot;language-text&quot;&gt;UnViewedCafe&lt;/code&gt; 목록에 새 카페가 채워질 때까지(해당 &lt;strong&gt;트랜잭션이 커밋되기 전&lt;/strong&gt;까지) 계속해서 동일한 카페들만 응답하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;해결 방안&lt;/h2&gt;
&lt;p&gt;문제를 해결하기 위한 두 선택지가 있었습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Serializable 격리 수준 사용하기&lt;/li&gt;
&lt;li&gt;조회 요청 때 &lt;code class=&quot;language-text&quot;&gt;UnViewedCafe&lt;/code&gt;에서 다음 카페들을 제거하고 응답하기&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;1. Serializable 격리 수준 사용하기&lt;/h4&gt;
&lt;p&gt;Serializable 격리 수준을 사용한다면, 제일 먼저 도착한 첫 요청만 처리되고 나머지 요청은 기다렸다가 처리될 수 있습니다.
하지만 이는 매우 비효율적인 방안입니다.&lt;/p&gt;
&lt;p&gt;한 가지 극단적 예시를 보자면 아래와 같습니다.
카페를 제거하려고 할 때, 해당 카페를 조회하고 있는 회원이 있거나 새로 카페 목록이 채워지는 회원이 존재하면 평생 제거가 불가능합니다.&lt;/p&gt;
&lt;h4&gt;2. 조회 요청 때 &lt;code class=&quot;language-text&quot;&gt;UnViewedCafe&lt;/code&gt;에서 다음 카페들을 제거하고 응답하기&lt;/h4&gt;
&lt;p&gt;이 글을 보시는 여러분은 아래와 같은 의문이 들었을 수 있을 것 같습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;왜 저런 식으로 카페를 봤다는 처리를 하지…?
그냥 조회 요청 때 &lt;code class=&quot;language-text&quot;&gt;UnViewedCafe&lt;/code&gt;에서 제거하면서 응답하면 되지 않았나?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이렇게 구현했다면,
조회 요청이 동시에 들어와도 가장 먼저 도착한 요청이 남은 카페 목록을 제거하고 응답받았을 것입니다.
그리고 다른 요청들은 예외가 발생하고 500으로 응답받게 됩니다.
사용자 입장에서는 첫 요청의 응답으로 카페들을 잘 받았기 때문에 예외가 발생해도 지장이 없습니다.&lt;/p&gt;
&lt;p&gt;하지만 사용 도중에 브라우저를 종료하면 아직 보지 않은 카페는 &lt;code class=&quot;language-text&quot;&gt;UnviewedCafe&lt;/code&gt;가 새로 채워질 때까지 볼 수 없습니다.
이러한 상황이 반복되면 불행하게도 평생 못 보는 카페가 생길 수도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;무한 스크롤&lt;/code&gt; 기능을 구현할 때 이 방식을 먼저 고려했으나 회원이 특정 카페를 못 보게 되는 경험은 막아야 한다고 생각했기 때문에 선택하지 않았습니다.&lt;/p&gt;
&lt;h4&gt;선택과 근거&lt;/h4&gt;
&lt;p&gt;각 해결 방안의 트레이드 오프를 고려해 본 결과, 2번 방안(조회 요청 때 &lt;code class=&quot;language-text&quot;&gt;UnViewedCafe&lt;/code&gt;에서 다음 카페들을 제거하고 응답하기)을 선택하게 되었습니다.&lt;/p&gt;
&lt;p&gt;회원이 특정 카페를 평생 보지 못할 수도 있다는 단점이 존재하나, 스크롤을 내릴 때 계속 같은 카페만 보이는 상황이 더 문제 된다고 판단했습니다. 회원 스스로는 특정 카페를 보지 못했다는 사실을 인지하기 어렵지만, 같은 카페만 보이는 경험은 사용자가 인지할 수 있는 단점이기에 사용자 이탈률로 직결됩니다.&lt;/p&gt;
&lt;p&gt;따라서 2번 방안인 &lt;strong&gt;조회 요청 때 &lt;code class=&quot;language-text&quot;&gt;UnViewedCafe&lt;/code&gt;에서 다음 카페들을 제거하고 응답하는 방식&lt;/strong&gt;으로 로직을 개선했습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;봤다는 요청 API를 제거하고 조회 요청 때 삭제하고 응답하도록 변경했습니다.
&lt;ul&gt;
&lt;li&gt;더 이상 중복된 카페 화면이 나오지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;남은 &lt;code class=&quot;language-text&quot;&gt;UnViewedCafe&lt;/code&gt;가 0개일 때 새로 채우지 않고, 20개 이하일 때 채웁니다.
&lt;ul&gt;
&lt;li&gt;카페를 다시 채울 때까지 기다려야 하는 경우가 줄어듭니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;새로 카페를 채울 때 &lt;code class=&quot;language-text&quot;&gt;UnViewedCafe&lt;/code&gt;에 남아 있는 카페는 중복해서 추가하지 않습니다.
&lt;ul&gt;
&lt;li&gt;새로 채웠을 때 기존 리스트와 중복된 카페가 연속으로 보일 수 있는 상황이 발생하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;하지만 &lt;code class=&quot;language-text&quot;&gt;StaleObjectStateException&lt;/code&gt;에 대해 깔끔하게 해결하지 못한 상황입니다.
여전히 조회 요청이 동시에 온다면 이후 트랜잭션들은 예외가 발생하고 롤백 됩니다.
&lt;code class=&quot;language-text&quot;&gt;UnViewedCafe&lt;/code&gt; 테이블의 동일한 데이터를 동시에 제거하려고 했기 때문입니다.&lt;/p&gt;
&lt;h4&gt;위의 남은 문제를 해결하려면 어떤 방법이 있을까요?&lt;/h4&gt;
&lt;p&gt;DB의 처리 속도를 개선해서 문제 발생 확률을 줄일 수도 있고, 격리 수준을 조정하거나 비관적 락, 낙관적 락 등을 사용할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;하지만 동시성 문제를 해결하는 방법은 간단하지 않습니다.
제시한 방법들을 사용해도 완벽히 해결하지 못할 수도 있고, 다른 문제들이 발생할 수 있습니다.&lt;/p&gt;
&lt;p&gt;저희는 회원의 경험을 최우선시하는 방향으로 아래의 대안을 생각해 봤습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;엔티티에 버전을 매기는 낙관적 락을 사용해서 충돌을 감지합니다.&lt;/li&gt;
&lt;li&gt;충돌이 감지되면 해당 트랜잭션을 재시도 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이제 &lt;code class=&quot;language-text&quot;&gt;StaleObjectStateException&lt;/code&gt;이 발생해도 재시도하므로 롤백 문제를 해결할 수 있게 됐군요.
회원 입장에서는 요청의 재시도 여부와 관계없이 항상 카페를 조회할 수 있습니다. 👍&lt;/p&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;지금까지 무한 스크롤 기능에서 발생한 동시성 이슈를 살펴봤습니다.
DB에서 동일한 데이터를 제거하려고 할 때 &lt;code class=&quot;language-text&quot;&gt;StaleObjectStateException&lt;/code&gt;이 발생하는 것이 원인이었습니다.&lt;/p&gt;
&lt;p&gt;서버 측에서 이를 해결하기 위해 아래의 방법들을 살펴봤습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Serializable&lt;/code&gt; 격리 수준 사용&lt;/li&gt;
&lt;li&gt;비즈니스 로직 변경&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;2번을 선택해서 예외가 발생하는 확률을 훨씬 줄이고 성능은 그대로 유지할 수 있었습니다.
그러나 근본적인 문제인 &lt;code class=&quot;language-text&quot;&gt;StaleObjectStateExcpetion&lt;/code&gt;은 해결되지 않았고, 추가로 낙관적 잠금을 사용하게 됐습니다.&lt;/p&gt;
&lt;p&gt;위의 방법이 모든 상황에서 정답은 아닙니다.&lt;/p&gt;
&lt;p&gt;각자가 Trade-Off 사이에서 고민한 선택이 결과적으로도 최고의 선택이었기를 바랍니다.
지금까지 긴 글 읽어주셔서 정말 감사합니다😃
다 같이 힘내서 더 성장해 보아요🤍&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;백은빈, 이성욱 지음, Real MySQL 8.0&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javaee/7/tutorial/persistence-intro003.htm#BNBQW&quot;&gt;Managing Entities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.digitalocean.com/community/tutorials/hibernate-caching-first-level-cache&quot;&gt;Hibernate Caching&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[낙관적 업데이트로 사용자 경험을 개선해보자!]]></title><description><![CDATA[들어가며: 낙관적 업데이트, 왜 필요할까? 장바구니에 상품을 추가했는데, 왜 화면에는 바로 반영되지 않을까? 낙관적 업데이트의 필요성을 처음 느낀 순간은,  기능을 만들 때였습니다. ‘장바구니 상품 추가’를 위해 POST…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-08-15-how-to-improve-ux-with-optimistic-update/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-08-15-how-to-improve-ux-with-optimistic-update/</guid><pubDate>Thu, 05 Oct 2023 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;들어가며: 낙관적 업데이트, 왜 필요할까?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;장바구니에 상품을 추가했는데, 왜 화면에는 바로 반영되지 않을까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;낙관적 업데이트의 필요성을 처음 느낀 순간은, &lt;code class=&quot;language-text&quot;&gt;장바구니에 상품 추가하기&lt;/code&gt; 기능을 만들 때였습니다.&lt;/p&gt;
&lt;img width=&quot;294px&quot; height=&quot;381px&quot; src=&quot;/15656231a61159a4a143976d70e95530/2023-08-15-image1-without-optimistic.gif&quot;&gt;
&lt;p&gt;‘장바구니 상품 추가’를 위해 POST 요청을 보내고, 이에 대한 &lt;strong&gt;응답이 오기까지 3초의 지연 시간&lt;/strong&gt;이 있다고 해봅시다. 그리고 개발자 네로 씨는 이 응답을 기다렸다가, 응답에 따라 상태를 업데이트하여 UI를 변경하려고 합니다.&lt;/p&gt;
&lt;p&gt;이러한 로직이라면, 화면을 보고 있는 사용자들은 위 그림처럼 3초를 기다렸다가, 3초 뒤에야 해당 상품이 장바구니에 담긴 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;사용자들은 이러한 기다림에 대해 불편함을 느꼈고, 네로 씨는 &lt;code class=&quot;language-text&quot;&gt;서버와의 통신과 상관없이, 화면(또는 UI) 먼저 바꿔줄 수는 없을까?&lt;/code&gt; 라는 생각을 하게 됩니다.&lt;/p&gt;
&lt;img width=&quot;302px&quot; height=&quot;381px&quot; src=&quot;/3ca7cf80334548259b6ffe75cfd481c9/2023-08-15-image2-with-optimistic.gif&quot;&gt;
&lt;p&gt;따라서 네로 씨는 서버의 응답이 오기 전에 상태를 먼저 바꿔서, 업데이트된 상태를 화면에 반영해 주기로 했습니다.&lt;/p&gt;
&lt;p&gt;사용자들은 이제 위 그림처럼, 장바구니 아이콘을 누른 순간 즉시 ‘아 장바구니에 상품이 추가되었네!’ 라고 느끼게 됩니다.&lt;/p&gt;
&lt;p&gt;바로 ‘낙관적 업데이트’를 통해 지연 시간에 대한 불편을 해소한 것이죠!&lt;/p&gt;
&lt;h2&gt;낙관적 업데이트는 무엇인가?&lt;/h2&gt;
&lt;h3&gt;낙관적 업데이트가 등장하기 이전에는…&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;낙관적 업데이트(Optimistic Update)&lt;/code&gt;는 웹 애플리케이션에서 &lt;strong&gt;사용자 경험을 향상&lt;/strong&gt;시키기 위해 사용되는 개념입니다. 주로 네트워크 요청이나 데이터 업데이트와 관련하여 사용됩니다.&lt;/p&gt;
&lt;p&gt;프론트엔드의 일반적인 상태 업데이트 방식은 다음과 같습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;사용자가 어떤 동작(예: 게시글 작성, 댓글 수정, 북마크)을 수행합니다.&lt;/li&gt;
&lt;li&gt;서버에 해당 변경 사항(mutation)을 요청하고 응답을 기다립니다.&lt;/li&gt;
&lt;li&gt;서버의 응답에 따라 UI를 업데이트합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;하지만 이 접근 방식은, 장바구니 예시처럼 사용자가 서버의 응답을 기다려야 하므로 네트워크 지연이나 서버 응답 속도가 느릴 경우에는 사용자 경험이 저하될 수 있습니다.&lt;/p&gt;
&lt;h3&gt;사용자 경험 향상을 위한 방법, 낙관적 업데이트&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;낙관적 업데이트&lt;/code&gt;는 이러한 문제를 해결하기 위한 방법으로, &lt;strong&gt;사용자의 동작에 대한 응답을 기다리지 않고 미리 UI를 업데이트하는 것&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;사용자가 어떤 동작을 수행했을 때, 서버 요청을 보내기 전에 먼저 UI를 업데이트합니다. 서버 응답 성공 시 새로운(업데이트된) 상태를 화면에 그리고, 실패 시에는 오류 메시지를 보여주고 기존의 상태를 유지합니다.&lt;/p&gt;
&lt;p&gt;그 결과, 사용자는 동작에 대한 빠른 피드백을 받으므로 애플리케이션이 더 빠르게 반응한다고 느끼게 됩니다.&lt;/p&gt;
&lt;p&gt;혹자는 아래와 같은 의문을 가질 수도 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;요청에 대한 응답이 빠르게 오도록, 서버의 성능을 높이면 되지 않나요? 🤔&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;서버의 응답이 아무리 빠르더라도, &lt;strong&gt;사용자가 어떤 네트워크 환경에 처해 있느냐&lt;/strong&gt;에 따라 애플리케이션의 응답 속도가 달라집니다. 예를 들어 지하철의 공공 와이파이로 접속한 경우, 어떨 때는 네트워크가 불안정할 수도 있죠.&lt;/p&gt;
&lt;p&gt;따라서 서버의 응답에만 의존하여 상태(또는 UI)를 업데이트하는 것이 아니라, &lt;code class=&quot;language-text&quot;&gt;낙관적 업데이트&lt;/code&gt; 와 같은 로직을 통해 각기 다른 사용자들의 환경에 대비하는 것이 좀 더 바람직해 보입니다.&lt;/p&gt;
&lt;h3&gt;커뮤니티/SNS 서비스 속 낙관적 업데이트&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://votogether.com&quot;&gt;VoTogether(보투게더)&lt;/a&gt;라는 투표 중심의 커뮤니티 서비스를 만들면서, &lt;strong&gt;투표 선택지 수정 기능과 댓글 수정&lt;/strong&gt; &lt;strong&gt;기능&lt;/strong&gt;을 구현해야 했습니다.&lt;/p&gt;
&lt;p&gt;그런데 투표 선택지 수정 및 댓글 수정 API 요청을 한 뒤, 이에 대한 응답이 성공적으로 오기 전까지는 사용자가 바라보는 UI가 즉각적으로 수정되지 않았습니다.&lt;/p&gt;
&lt;p&gt;사용자는 UI가 업데이트되기를 몇 초간 기다려야 하는데, 이러한 기다림은 사용자 경험을 저하할 수 있다고 생각했습니다.&lt;/p&gt;
&lt;p&gt;VoTogether 팀은 서버 데이터로 UI를 업데이트하는 경우가 많았기 때문에 &lt;code class=&quot;language-text&quot;&gt;TanStack Query&lt;/code&gt;나 &lt;code class=&quot;language-text&quot;&gt;swr&lt;/code&gt; 같은 서버 상태 관리 라이브러리를 도입하였고, &lt;code class=&quot;language-text&quot;&gt;TanStack Query&lt;/code&gt;가 제공하는 메서드 중 &lt;code class=&quot;language-text&quot;&gt;useMutation&lt;/code&gt;(onMutate, onError, onSettled)을 이용하여 ‘낙관적 업데이트’를 구현하기로 하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;TanStack Query&lt;/code&gt;를 사용하지 않고도 낙관적 업데이트를 구현할 수는 있지만, VoTogether 팀은 서버 상태에 대한 관리를 &lt;code class=&quot;language-text&quot;&gt;TanStack Query&lt;/code&gt;를 이용하고 있기에 &lt;a href=&quot;https://tanstack.com/query/v4/docs/react/guides/optimistic-updates&quot;&gt;공식 문서&lt;/a&gt;의 설명을 참고하여 낙관적 업데이트를 구현해 보았습니다!&lt;/p&gt;
&lt;h2&gt;상태 관리 라이브러리 없이 구현하기&lt;/h2&gt;
&lt;p&gt;먼저 &lt;code class=&quot;language-text&quot;&gt;TanStack Query&lt;/code&gt; 를 사용하지 않고, react의 &lt;code class=&quot;language-text&quot;&gt;setState(useState)&lt;/code&gt;으로만 투표 선택지 수정 기능을 구현해 봅시다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;투표 선택지 컴포넌트(VoteOptionList.tsx, styled-components 코드는 생략됨)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;tsx&quot;&gt;&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;react&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Props&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  selectedOption&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;onChange&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;option&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;updateSelectedOptionApi&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newOption&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/api/updateSelectedOption&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      method&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;PUT&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      headers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string-property property&quot;&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;application/json&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      body&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; newOption &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ok&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;투표 선택지 수정 오류 발생&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;투표한 선택지는 : &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;newOption&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;API error:&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;투표 선택지 수정 중 오류가 발생했어요&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;VoteOptionList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; selectedOption&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onChange &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Props&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleOptionClick&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;option&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;onChange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;option&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 선택한 선택지 바로 반영&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateSelectedOptionApi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;option&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 선택한 선택지 수정 API 요청 호출&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;투표 선택지 수정 오류:&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;onChange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selectedOption&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 업데이트 실패 시 선택지 원래대로 돌리기&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;VoteListContainer&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;option&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;VoteItem&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleOptionClick&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;option&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;OptionRadio&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;radio&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;voteOption&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;option&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;checked&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;selectedOption &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; option&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;onChange&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleOptionClick&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;option&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;OptionLabel&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;option&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;OptionLabel&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;VoteItem&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;VoteListContainer&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;App.tsx (VoteOptionList 컴포넌트의 상위 요소)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;tsx&quot;&gt;&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;react&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; VoteOptionList &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./VoteOptionList&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; options &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;취업하기&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;창업하기&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;대학원 진학하기&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;selectedOption&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setSelectedOption&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleOptionChange&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;option&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setSelectedOption&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;option&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 선택한 선택지를 수정&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;진로가 고민됩니다🤔🥺&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;VoteOptionList&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;selectedOption&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;selectedOption&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;onChange&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;handleOptionChange&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;투표한 선택지: &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;selectedOption&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; App&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위의 코드로 구현한 결과물은 아래와 같습니다.&lt;/p&gt;
&lt;img width=&quot;612px&quot; height=&quot;426px&quot; src=&quot;/e31351fbd92f8c94020f4013b958142e/2023-08-15-image3-without-query.gif&quot;&gt;
&lt;p&gt;처음에는 ‘취업하기’라는 선택지였고, ‘창업하기’라는 선택지로 먼저 낙관적 업데이트를 진행합니다. 그 후 API 요청을 보내고, 응답을 기다리다가 오류가 발생하여 원래의 선택지(’취업하기’)로 되돌리는 모습입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;tsx&quot;&gt;&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleOptionClick&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;option&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;onChange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;option&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 선택한 선택지 바로 반영&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateSelectedOptionApi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;option&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 선택한 선택지 수정 API 요청 호출&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;투표 선택지 수정 오류:&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;onChange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selectedOption&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 업데이트 실패 시 선택지 원래대로 돌리기&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;자세한 코드는 &lt;a href=&quot;https://codesandbox.io/p/sandbox/xenodochial-hertz-pmllqg?file=%2Fsrc%2FApp.tsx%3A7%2C35&quot;&gt;링크&lt;/a&gt;에서 참고할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;TanStack Query로 구현하기&lt;/h2&gt;
&lt;p&gt;이제 &lt;code class=&quot;language-text&quot;&gt;TanStack Query&lt;/code&gt;를 이용하여 선택지 수정 기능에 대한 낙관적 업데이트를 구현해 봅시다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;useEditVote.ts&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;tsx&quot;&gt;&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useMutation&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useQueryClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@tanstack/react-query&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 선택지 수정 API 요청에 쓰이는 fetch 메서드&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateSelectedOptionApi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newOption&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/api/updateSelectedOption&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    method&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;PUT&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    headers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token string-property property&quot;&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    body&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; newOption &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ok&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Failed to update selected option&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 선택지 수정 쿼리&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;useEditVote&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;postId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; queryClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useQueryClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; queryKey &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;posts&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; postId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;option&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; mutate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isLoading&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isError&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useMutation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newOption&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateSelectedOptionApi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newOption&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function-variable function&quot;&gt;onMutate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newOption&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 선택지 데이터에 대한 모든 퀴리요청을 취소하여 이전 서버 데이터가 낙관적 업데이트를 덮어쓰지 않도록 함 -&gt; refetch 취소시킴&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; queryClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cancelQueries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;queryKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; prevOption &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; queryClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getQueryData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;queryKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 기존 선택지 데이터의 snapshot&lt;/span&gt;
        queryClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setQueryData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;queryKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; newOption&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 새로운 선택지 데이터로 낙관적 업데이트 실시&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; prevOption &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// context를 return, context 예시에는 이전 스냅샷, 새로운 값(또는 롤백하는 함수)이 있음&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token function-variable function&quot;&gt;onError&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 캐시를 저장된 값으로 롤백&lt;/span&gt;
        queryClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setQueryData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;queryKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; context&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;prevOption&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;선택지 수정에 실패했습니다. 다시 시도해주세요.&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token function-variable function&quot;&gt;onSuccess&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 쿼리 함수의 성공하면 -&gt; 기존 선택지 데이터 무효화&lt;/span&gt;
        queryClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invalidateQueries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;queryKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; mutate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isLoading&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isError&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;VoteOptionList.tsx&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;tsx&quot;&gt;&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;VoteOptionList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  options&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  selectedOption&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  onChange&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Props&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; postIdExample &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1234&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; mutate &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useEditVote&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;postIdExample&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleOptionClick&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;option&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;onChange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;option&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;mutate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;option&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// .. 중략&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;자세한 코드는 &lt;a href=&quot;https://codesandbox.io/p/sandbox/intelligent-gates-f999p3?file=%2Fsrc%2FVoteOptionList.tsx%3A26%2C1&quot;&gt;링크&lt;/a&gt;에서 참고할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;TanStack Query&lt;/code&gt;의 &lt;a href=&quot;https://tanstack.com/query/v4/docs/react/guides/optimistic-updates&quot;&gt;공식 문서&lt;/a&gt;는 어떠한 매커니즘으로 낙관적 업데이트를 구현하는지 아래와 같이 설명하고 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;낙관적으로 상태를 업데이트한 후 변경 작업을 수행할 때, 변경 작업이 실패할 가능성이 있습니다. 이러한 실패 케이스 대부분에서는 낙관적인 쿼리를 다시 가져와서 실제 서버 상태로 되돌릴 수 있습니다. 그러나 어떤 상황에서는 다시 가져오기가 올바르게 작동하지 않을 수 있으며, 변경 작업 오류가 다시 가져오기가 불가능한 종류의 서버 문제를 나타낼 수 있습니다. 이 경우에는 대신 업데이트를 롤백할 수 있습니다.
이를 위해 &lt;code class=&quot;language-text&quot;&gt;useMutation&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;onMutate&lt;/code&gt; 핸들러 옵션을 사용하여 나중에 오류 및 &lt;code class=&quot;language-text&quot;&gt;onSettled&lt;/code&gt; 핸들러로 전달될 값을 반환할 수 있습니다. 대부분의 경우 롤백 함수를 전달하는 것이 가장 유용합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이러한 업데이트 및 롤백에 대한 자세한 코드는, 이 &lt;a href=&quot;https://tanstack.com/query/v4/docs/react/guides/optimistic-updates#updating-a-list-of-todos-when-adding-a-new-todo&quot;&gt;예시 코드&lt;/a&gt;에서 확인하실 수 있습니다.&lt;/p&gt;
&lt;h3&gt;서버의 응답이 성공한 경우&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;tsx&quot;&gt;&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt; &lt;span class=&quot;token function-variable function&quot;&gt;onSuccess&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 쿼리 함수의 성공하면 -&gt; 기존 선택지 데이터 무효화&lt;/span&gt;
      queryClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invalidateQueries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;queryKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;서버의 응답이 실패한 경우&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;tsx&quot;&gt;&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onError&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 캐시를 저장된 값으로 롤백&lt;/span&gt;
      queryClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setQueryData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;queryKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; context&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;prevOption&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;선택지 수정에 실패했습니다. 다시 시도해주세요.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        error
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;TanStack Query&lt;/code&gt;에서 진행되는 낙관적 업데이트는 흐름도는 아래와 같습니다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 757px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/537c881a4e0fe50884ff1f7e4d237ef5/70668/2023-08-15-image5-optimistic-update-flow.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABtElEQVR42oWT3U7bQBCFef/XQWrLHSnlrlIFSaUikwaUNCa24984Xtu783V245ZSIVjpaO3x7plzZsZnvLHc0OP6DhmN7gbb99hji4jofkSsAen1oJ4b/Jmes7cIZRjojcUMYO2URC+Jc6DJokj4+k1YLIQs1ZhVQmuUWS/6y26CTGjKkvlNzOfZih93aza/1tR5QbpLaKs93xdwfj5ycWGpCnsidEpYVxXtoaFpakVDmee0SpbEMW1TUVUFpb7nRUGn3904YrujqofbW0d0L0F1sFxlKVdXM75cX3M5u+TDx0+q5o7dZkunSfD2xPuXybIJMe9MxAWisNvxRDhqcWNV8rB6YLNZs1bkqjBPU0zbYlXRWNeMfp/gl2/Ki7g+B0Jv2dvZ53vKqqQoT/YOijLL/ir7t/NM9l40MMTMidA3ILRRayMBQ7C0225pVkvy+Q1Noo0wHb2q7roOowldnmH3KTZ5wqYJzvixkVNB/4df3eFA/PhItJizvI9Yrn4G1YWWpNaSaGux6sxlOyXO3p/DMEJ/BpDnOWSy96rl9wh98a1a9CUIaJ//FKdxXzKP8G4MvwGk/VNCivUXBQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 15 image5 optimistic update flow&quot; title=&quot;&quot; src=&quot;/static/537c881a4e0fe50884ff1f7e4d237ef5/70668/2023-08-15-image5-optimistic-update-flow.png&quot; srcset=&quot;/static/537c881a4e0fe50884ff1f7e4d237ef5/0eb09/2023-08-15-image5-optimistic-update-flow.png 500w,
/static/537c881a4e0fe50884ff1f7e4d237ef5/70668/2023-08-15-image5-optimistic-update-flow.png 757w&quot; sizes=&quot;(max-width: 757px) 100vw, 757px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;h2&gt;낙관적 업데이트는 언제 필요할까?&lt;/h2&gt;
&lt;h3&gt;낙관적 업데이트가 필요한 경우&lt;/h3&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 835px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/4471825db9e550637299c77e515c8400/98650/2023-08-15-image6-optimistic-updated-example.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 30%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABW0lEQVR42k2RzXLaQBCEef93yTVVITEYgzFCIEVgEUqOLYP+kVZiJUGOX2aVSw5du10729PTM1Ltnbq7o9obSVmTK01W9ORVT1ZdhbekzY3L9Sa8+fd+6QRSU2uSS4PSotEajTujSkghyMqW6cLFPQS8TDJ+blIWtod3+ODxTXOMGuZLD/s1YDXLsZ4S7P2Rx6VLXNSU0rDSN0aJ6nkKNY5d8DxO2R3fmVs7Dr/PTJ+3BGHC8rNl41dYk4QXUxvKfbvnYbHFl7qV48sfTxy3jIxN1f2RDj2W+4vd4Z11rHGDBnsqDZwL376ceBO+yTscv8Z+yNl8XlmHV1YnTdEadz2VGdkIGqvOPmA8s4YR598z1l7JMmx4lXMxjrFmKbMfZoITq7WP9aEEmsnXs9QnQ47qf8FIcohyNSwhjmURZUckuZjlpOIsTgVJS6auw/JMVAZxpDmd9SBmBP8CRBSzMJGld2oAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 15 image6 optimistic updated example&quot; title=&quot;&quot; src=&quot;/static/4471825db9e550637299c77e515c8400/98650/2023-08-15-image6-optimistic-updated-example.png&quot; srcset=&quot;/static/4471825db9e550637299c77e515c8400/0eb09/2023-08-15-image6-optimistic-updated-example.png 500w,
/static/4471825db9e550637299c77e515c8400/98650/2023-08-15-image6-optimistic-updated-example.png 835w&quot; sizes=&quot;(max-width: 835px) 100vw, 835px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;위 이미지의 &lt;strong&gt;메시지 전송, 좋아요, 즐겨찾기, 투두리스트 체크, 장바구니 담기, 게시글 비공개, 파일 업로드, 북마크&lt;/strong&gt; 등은 모두, 낙관적 업데이트가 필요한 기능들입니다.&lt;/p&gt;
&lt;p&gt;이러한 기능들의 공통점은, 모두 &lt;strong&gt;기존의 데이터에 업데이트(mutate)를 일으킨다&lt;/strong&gt;는 점입니다. 사용자의 동작에 따라 발생한 데이터 업데이트는, 지연 시간(latency) 없이 즉각적인 피드백을 화면에 띄워야 사용자 만족도를 높일 수 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;낙관적 업데이트가 굳이 필요한가요? 사용자가 기다리게 하는 것 역시 사용자 경험이 될 수 있지 않나요?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;위와 같은 궁금증이 들 수도 있습니다. 사실 지연 시간 또한 사용자 경험이 될 수 있습니다. 웹 애플리케이션 개념이 등장한 지 얼마 되지 않은 시절에는, 사용자들은 기다림이 문제가 된다는 것을 인지조차 하지 않았습니다.&lt;/p&gt;
&lt;p&gt;그러나 세월이 흘러 지금처럼 매일 새로운 콘텐츠가 쏟아져 나오고, 데이터를 업데이트하는 기능들이 다양해지면서 피드백 없는 기다림은 불편 사항이 될 수밖에 없었죠.&lt;/p&gt;
&lt;h3&gt;낙관적 업데이트를 지양해야 하는 경우&lt;/h3&gt;
&lt;p&gt;물론 낙관적 업데이트가 모든 상태의 업데이트에 필요한 것은 아닙니다. 낙관적 업데이트가 필요하지 않거나, 심지어 사용을 지양해야 하는 경우도 존재합니다.&lt;/p&gt;
&lt;p&gt;예를 들어 &lt;strong&gt;장바구니에 상품을 담고 결제를 진행&lt;/strong&gt;한다고 해봅시다.&lt;/p&gt;
&lt;p&gt;사용자가 결제 버튼을 클릭하면 어떻게 될까요? 일반적으로 결제 요청이 서버로 전송되고, ‘로딩 중’ 같은 문구나 로딩 스피너가 보일 것입니다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 249px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/8b5be119c499d6d848f229597f69e1df/14a1f/2023-08-15-image7-baemin-payment-loading.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 83.93574297188754%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAABdUlEQVR42q1Uy2rCQBT139y5cqu/4tKV/kkVXYgg4qZRBF1kp1iL4qI02TTPeSbRns5MW0EpmFQDh5u5MzmcM/felDgVeCRKfyUpYQr0AuQismKEhBBIKZEkyTkKIcAYw+l0AuccLDch0R9yrNdr2LaN/X6PzeYF2+0WrutiNBrBcRwkIgEj/DYhjRmOxxM6nSfUajW0Wi1UKhU0m03M53PU63VMrSk+jzBncxN2u11Uq1X0+300Gg202230ej2Uy2UMBgPoh0T0NqGxwQV2ux1msxnCMEQQBDgcDsaqzrmOC8lzWtaI49gUJssyQ6ZBKUUURUjTVBWE56+yYAm8Dw+TyQSWZWG5XGKxWGA8Hpv1cDjEarVCKrP8CklMDKnv+wiDEHEUK5Uqqrzv+Qj8oFgf/ioVTBpQtZb63eSl2StMqO2wc6MzvHqempBvi4Xu8BqSCdiqoZ/f3iH/O8vX8GI1v0qVtvsQQknlj9UHKbz793UP4Rem1QXc3sQkBgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 15 image7 baemin payment loading&quot; title=&quot;&quot; src=&quot;/static/8b5be119c499d6d848f229597f69e1df/14a1f/2023-08-15-image7-baemin-payment-loading.png&quot; srcset=&quot;/static/8b5be119c499d6d848f229597f69e1df/14a1f/2023-08-15-image7-baemin-payment-loading.png 249w&quot; sizes=&quot;(max-width: 249px) 100vw, 249px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;만약 결제 기능에 낙관적 업데이트를 적용한다면, 결제 응답과 상관없이 장바구니의 상품을 먼저 제거하는 등 UI 먼저 업데이트하게 됩니다. 그 결과는 사용자는 결제했음에도 장바구니에 아직 상품들이 남아 있는 것을 발견합니다.&lt;/p&gt;
&lt;p&gt;장바구니의 상품들 상태는 서버의 데이터를 GET 요청하여 보여주는데, 아직 서버의 데이터는 완전하게 업데이트를 끝내지 못했기 때문입니다.&lt;/p&gt;
&lt;p&gt;이렇듯 서버 데이터와 화면의 상태 간 일관성이 중요한 경우, 낙관적 업데이트를 지양해야 합니다. 결제나 계정 정보 관리 등의 기능들은 일정 시간의 지연 시간이 필연적이라는 사실을, 많은 사용자가 그동안의 사용자 경험으로 인지하고 있는 부분입니다. 따라서 이러한 상황에는 낙관적 업데이트를 하여 사용자 경험을 저하하기보다는, 중요한 작업의 신뢰성을 유지하기 위해 ‘로딩 중’처럼 서버 응답을 기다리도록 하는 것이 안전합니다.&lt;/p&gt;
&lt;h2&gt;마치며&lt;/h2&gt;
&lt;p&gt;낙관적 업데이트는 사용자 경험을 개선하고 더 빠른 애플리케이션 응답을 제공하기 위한 좋은 전략 중 하나입니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;TanStack Query&lt;/code&gt;는 낙관적 업데이트 기능을 제공하고 있고, 이러한 기능을 라이브러리 없이 직접 구현해 보는 것도 좋은 학습이 될 것이라 생각합니다!&lt;/p&gt;
&lt;p&gt;나아가 낙관적 업데이트는 데이터의 종류를 고려해 보고 신중하게 구현해야 합니다. 만약 꼭 필요한 경우라면, 서버 응답 실패의 경우에도 오류 메시지를 사용자에게 보여주는 등 적절한 대처가 필요합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://tanstack.com/query/v4/docs/react/guides/optimistic-updates&quot;&gt;https://tanstack.com/query/v4/docs/react/guides/optimistic-updates&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ilxanlar.medium.com/better-ux-with-optimistic-ui-b72665e1afdf&quot;&gt;https://ilxanlar.medium.com/better-ux-with-optimistic-ui-b72665e1afdf&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://uxplanet.org/faster-user-experience-optimistic-ui-41b151feaa28&quot;&gt;https://uxplanet.org/faster-user-experience-optimistic-ui-41b151feaa28&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://uncertainty.oopy.io/optimistic-ui-with-react-query&quot;&gt;https://uncertainty.oopy.io/optimistic-ui-with-react-query&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[AWS 로그인부터 시작하는 인프라 구축하기 1편]]></title><description><![CDATA[AWS 로그인부터 시작하는 인프라 구축하기 1편 : AWS 로그인 / EC2 / VPC 이해하기 0. 들어가기 AWS를 처음 접한다면, 아래의 사진(인스턴스 생성 설정)에 나온 이야기들은 별나라 이야기 같을 것입니다.

 IAM 사용자, EC…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-09-22-AWS-EC2-(1)/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-09-22-AWS-EC2-(1)/</guid><pubDate>Wed, 04 Oct 2023 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;AWS 로그인부터 시작하는 인프라 구축하기 1편 :&lt;/h1&gt;
&lt;h2&gt;AWS 로그인 / EC2 / VPC 이해하기&lt;/h2&gt;
&lt;br&gt;
&lt;h2&gt;0. 들어가기&lt;/h2&gt;
&lt;p&gt;AWS를 처음 접한다면, 아래의 사진(인스턴스 생성 설정)에 나온 이야기들은 별나라 이야기 같을 것입니다.
&lt;/br&gt;&lt;/br&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 664px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/447d0692c992fc74bfa3b14a75a59e54/5bd9c/2023-08-11-AWS-EC2-setting.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 86.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAABYlAAAWJQFJUiTwAAACaUlEQVR42o1Ta28SURTc//+9v8DoB1PSRKwJjVgTraJCKBa2lLJP9v1gCeVVoB3n3LB8aKuyydl77s3ZuXPmzGpnZ2eoVCo4Pj5Gs9nEyckJarUaGo0GLi4ucHR0hGq1itVqBXkeHx/xr0czDAO9Xg/D4RBJmmLkebBsG2EYwmPearWg93Qi4aBHk9d6vVYM7u/vsVwssFwu1V7WkpGsT/OX2GoLAqRkFgQBRgx9MMBvXYflumhfdVFMpy+2+re9JqwE0A98XMYpvvoRGkGMSlfH2ysd3SRDmiQYj8fIWCdSRFGEKI4RM54xFEApchwHfXeEjmGhazvoOmTI3AojxARICCohIFmWqZB8Pp9jNpuhKAolnSYvadeyLMUkYVGepciY52SU57n6eDKZKAAZlOxLmeRMauRc5NNEfClQGpKhfOj5vmLss0hCbj/EMqrlEjDdsSkIKCDDKMZ3y0HbC/DL9XBFfTcPD8+m/HTiClAELzXKmYcckJlmOO3fojYwUL2+wSnDLSaYU/MS9IEXPI39lAVU2lSGpmVcP8Bn08Y5o87hvGm28ap1ieswRsQLg90gXdaKfjZ/BulUAYqwYgMZSkS9vrDV+i2Z9fp43ergB1u+k07mND2HuN1uVWw2GxXCTFY5U4DlUOIkRsfz8Y5AlU4X77k2bDKY7Mx9yK8ngBOyEruk4wKfTId6DfDhZog6211ttgeD7QGlZdM0Vdsh2zZMi3sLjuuoixx6VDo4yDblUKKdQXOy7dCPLcvGT8NEk2ub4NO7u8MYim2EofhPseHEvo0CnHMwtcEQHw0bzSDae/B/gH8AsMoE9axXKgEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 setting&quot; title=&quot;&quot; src=&quot;/static/447d0692c992fc74bfa3b14a75a59e54/5bd9c/2023-08-11-AWS-EC2-setting.png&quot; srcset=&quot;/static/447d0692c992fc74bfa3b14a75a59e54/0eb09/2023-08-11-AWS-EC2-setting.png 500w,
/static/447d0692c992fc74bfa3b14a75a59e54/5bd9c/2023-08-11-AWS-EC2-setting.png 664w&quot; sizes=&quot;(max-width: 664px) 100vw, 664px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;IAM 사용자, EC2 인스턴스 생성 시 설정하는 VPC, 서브넷, 보안그룹 등 온통 낯선 단어들에 한숨부터 나왔을지도요. 일단 필요하니 블로그에 잘 설명된 대로 EC2 만드는 법을 따라해보지만 문득, 마치 알 수 없는 언어로 된 설명서를 읽는 기분이 들지 않았나요?&lt;/p&gt;
&lt;p&gt;IAM 로그인 부터, EC2 인스턴스, 인스턴스 생성에 필요한 VPC, 서브넷과 보안그룹에 대한 개념 정의, 생성된 EC2 인스턴스가 어떻게 인터넷과 통신하는 지까지. 이해에 앞서 단순히 블로그를 보며 따라했던 것들을 하나하나 되짚어보며 알아보도록 합시다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;1. AWS 로그인 이해하기 : 루트사용자? vs IAM사용자?
2. EC2 이해하기 : AMI와 인스턴스 유형은 뭘까?
3. VPC 이해하기 : 가상 사설 네트워크(VPC) 생성에 필요한 Region과 IPv4 CIDR블록이 뭘까?
4. 서브넷 이해하기 : 서브넷 생성에 필요한 가용영역(AZ)와 CDIR 블록이 뭘까?
5. 보안그룹 이해하기 : EC2 인스턴스의 인바운드 아웃바운드 규칙이 뭐지?
6. EC2 생성 완료하기 : public IP 활성화 vs 비활성화?
7. 라우팅 테이블과 인터넷 게이트웨이 이해하기 : 가상 네트워크 속에 들어있는 EC2 인스턴스와 실제 인터넷은 어떻게 통신하는 걸까?&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;/br&gt;&lt;/br&gt;&lt;/p&gt;
&lt;h2&gt;1. AWS 로그인 이해하기 : 루트사용자? vs IAM사용자?&lt;/h2&gt;
&lt;p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 674px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/a14e94054678f10146193201896e9505/84f54/2023-08-11-AWS-EC2-login.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 105.60000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAYAAABG1c6oAAAACXBIWXMAABYlAAAWJQFJUiTwAAAC9klEQVR42p1U207bQBC1RNX2i/rUD6rgK6r+QatW/Qx6kYAHqiKIKJCQKiE3E8fOzY4dO7GdxHf7dHbtQKhACFY6PrMz69m57K4wn8+xt7+PWq2Gw8NfYHM2sizDc4YQRSGmxhTz2RzmdIo4jpk3PHcIYQJ4BD8FVoRFlGFBPm1SzFYJZmTkvIaXbsibuhRhkkFYRQmOFeCkqaPeuUZTHuHP3wbGswVWFKgTAW5cINlgglPoWQCmn8GhIIRFmEK0gEbfwmW9hctGB6WLKgYTCwPdhGo5GBozDIw5ZI10hg1FM7k8Mh309RnGlgtjGWMRUoQstSXtZthLVK7aaMtDtAY6arKB84aMS1HFeVNGTzXR6g3RUcYQBxrEvkqOLVwPJ+iNDehueOvQ8ADV9tDsjSHRj9LUR00N0DYi4hCiGWNOqTu0sR3nzNK1o5wZDI9SDihlP0phUoh2kPGGMGXOrOARbC/mPF3QaaAopouoQLiBXBfEFGGWpojCAMuFC8s04To29IkG3/OKg5Bt8ENyfm7TNIGQJNTuMIQkSSiVSmg0Gjg+PoGmaYiiCEmScDs7n2zO5LDQs4gCyjCIKSjykxIEZmDDth1cd3sYjUaQZZlzp9PBZDLhm+m6DkVR0O120e8rdAlMKIaP7mSF9ngJe0kFzchhHCc87PkqRt9YcTl9wk25SZz+YcEJKdUwz/8ur8HmbOFjYCVha7lD3/e5wqNGsBoFQXADZn/SXXZdF+VKmdfn6OgIoiji7OwMp6enaDabvJZPeX0EtnDdwXVX46KbXFd0mOGxtJkvSjnDjN7A/mCA0VglHlK3JQxHYy4znapqMC2LmgUk9LkPcZLXWuB9yujuIM45Swo5KuaFDcnGgX4YgkWvxG59hW91j7Bm7878Z8PDjyvvP9stdgnfaa1D76Pwu2Vh610Fr3aqHC8LrOcvtqt4876Mtx8usLVdvXcd49c7FZR7NgR15uHjXhef9qUH8eWgi68H4oP2j4TPBxKmToB/eBEjW7A1uggAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 login&quot; title=&quot;&quot; src=&quot;/static/a14e94054678f10146193201896e9505/84f54/2023-08-11-AWS-EC2-login.png&quot; srcset=&quot;/static/a14e94054678f10146193201896e9505/0eb09/2023-08-11-AWS-EC2-login.png 500w,
/static/a14e94054678f10146193201896e9505/84f54/2023-08-11-AWS-EC2-login.png 674w&quot; sizes=&quot;(max-width: 674px) 100vw, 674px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 648px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/5219b8affa891f7133044c595a9db053/244f0/2023-08-11-AWS-EC2-iam-login.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 116.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAYAAAALHW+jAAAACXBIWXMAABYlAAAWJQFJUiTwAAACV0lEQVR42q2Uy27TQBSGZ8OCyzPwKN3xJEhICFj1BZBYsClILJBYsqyglbKLBOKiBIIdOW6Mk7ipYze2c3HSXJzUie3Y8c/YbaCBtKlLR/p8PLfj/8wZH6JpGjiOQ61WQ6vVAsuyyOfzEEURqqrG44qixNTrdaxrJHKWTqfBMEy8IZVKIZPJIJfLQRAE8Dz/20qStN4hLtHCMDyxa4gdHhxUIRT3UBJ/olwSIZVLqFTKUGoymg0De3yBzongCxxc11mvsN7sQDFMKLqJmt6G2uhiX21A1tp0vANJMeK+WD1E/9jDyJnDmgYr8fwQxBz70KjTqmpAkg/RPhogoF9ygxDuHJhRPPruUzvx5rAvwBz5IM3eFAdcAcUcC6VuoNO3qANgSh9Tuugsziy8kNihQzf7p0qoYthU1sAOMJwkYzA5DbndatKk8DB0DaJQRL/fW8ps0ka63S5kenl7vR69xAoGg0E8EQQB5vN5IiIRJHpZumvUXpVIBPE8D77vx19YqPovh4vYk4Z3bshRcdB1HdfVSFRFZFm+PoWLpJy3IPEZLhJyduKq5xr5Iq7rwrbt+B6apgnLsmIb9WezWfKQx+NjmJ0uKtI+GDYP3WiALwq0hEmYTJ2TWhcrp4rXEF62wCbK8ruChecfh3jxeYitT39j4dVXC2++jfA6O6JrrJW8/EJ9fBii3HJA7m2JIA843H5cwM1Hf7hFufGQx91NFhtPs9h4xtIxbmnNgjtPCiD383ibbYFkpS62czp2WQM7zL+8Z3Ts/NCwy2gr5yOivdvf6Q9yZOMXdQjCzhPLg5sAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 iam login&quot; title=&quot;&quot; src=&quot;/static/5219b8affa891f7133044c595a9db053/244f0/2023-08-11-AWS-EC2-iam-login.png&quot; srcset=&quot;/static/5219b8affa891f7133044c595a9db053/0eb09/2023-08-11-AWS-EC2-iam-login.png 500w,
/static/5219b8affa891f7133044c595a9db053/244f0/2023-08-11-AWS-EC2-iam-login.png 648w&quot; sizes=&quot;(max-width: 648px) 100vw, 648px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;루트 사용자는, 처음 AWS계정을 생성하고, 단일 로그인을 통해 모든 AWS 서비스 및 리소스에 대한 완전한 접근 권한을 가진 사용자를 말합니다. 한편 IAM(Identity and Access Management) 사용자는 &lt;strong&gt;이러한 루트사용자 계정의 리소스를 관리하고 사용할 수 있는 권한을 가진 “가상의 사용자”&lt;/strong&gt; 를 말합니다.&lt;/p&gt;
&lt;p&gt;좀 더 자세히 말하자면, &lt;u&gt;루트사용자가 IAM 사용자를 생성 하면, IAM 사용자가 사용할 ID와 비밀번호가 만들어집니다.&lt;/u&gt; 루트 사용자 계정에 접근 할 가상의 사용자를 만드는 것이죠. 이때, 루트 사용자는 IAM사용자가 어디까지 접근할 수 있는지 적절하게 권한을 부여할 수 있습니다.&lt;/p&gt;
&lt;p&gt;IAM 사용자들은 부여받은 권한 정도에 따라, 인스턴스를 생성하거나 종료하고, 데이터베이스를 관리하고, 스토리지를 사용하고, 네트워크를 설정하는 등 다양한 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;p&gt;즉, &lt;strong&gt;루트 사용자의 계정의 리소스를 권한에 맞게 이용하는 “가상의 신분”을 IAM 사용자&lt;/strong&gt;라고 할 수 있습니다.&lt;/p&gt;
&lt;br&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;지금까지 가이드&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;루트 사용자의 리소스에 접근할 수 있는 가상의 신분으로(IAM) 로그인 했습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;2. EC2 이해하기 : AMI와 인스턴스 유형은 뭘까?&lt;/h2&gt;
&lt;p&gt;IAM 사용자라는 가상의 신분으로 루트 사용자의 계정에 접속했습니다. 루트 사용자의 EC2 서비스를 이용해봅시다.&lt;/p&gt;
&lt;p&gt;EC2 서비스를 사용하는 것은, 우리가 PC방에서 돈을 내고 컴퓨터를 이용하듯 사용할 만큼 AWS PC방의 컴퓨터를 빌리는 것과 같습니다. &lt;br/&gt;대신 &lt;strong&gt;우리가 빌리는 컴퓨터는 가상 컴퓨터로 제공되고, 각각의 컴퓨터는 인스턴스로 부르기로 한 것&lt;/strong&gt;이죠.&lt;/p&gt;
&lt;p&gt;PC방은 대체로 게임을 하러가는데, 게임에는 윈도우 기반의 고사양의 컴퓨터가 필요하죠?&lt;/p&gt;
&lt;p&gt;우리도 필요한 목적에 따라 가상의 컴퓨터인 EC2 인스턴스의 운영체제와 사양을 설정해야 합니다.&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h3&gt;첫째, &lt;strong&gt;애플리케이션 및 OS 이미지(Amazon Machine Image)&lt;/strong&gt; 를 설정해야 한다.&lt;/h3&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1496px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/49dddd9be03a982b48d084da791498cf/6ee55/2023-08-11-AWS-EC2-os.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAAB+UlEQVR42n1TOY7cMBDU/xM/wKFjf8OAMzuwo93ZOUUdpKiDlChqOOVqzo6vBZZAgZdYXdXdKkJY0WqDdY3ohwHzPMNMAc/NhGEOeG/cbrc3KMK6wvsZ87LAzwucc1DaYq9alK3F5Dzh8r3ceQZ03ud9SokkiesZ1+t2JxSy4/GMcZpIzMdDj85HHPWAxgVoO6IzXSaxdDCOE0zXwRqLRIJEpWWjMfolqy5CCDjsd1hmKqNVNXj0PuBkRuieJJ3CtkX4YcRqDNUEDH7C191PrDHiwsA7ZTLcsqKIcUV0GmuYoUePth+ocMVTqVH1POs1bXq4vofd76GZ74lqS57LCLQ+Uvk0jrgxBYW1tGiHXBhLK7ptEeKGKz/eEi1lJOboerfIdeT9Ld2L8H+RivP5gt3uBYfDMc9PxOl0hioVyrJEqVQuhHTDQjXbtr0GSHltmN+6adCKEKav0ExuqWpUdYvTuYSqG+6rfHZksKppWTCHiXCsuCj9u02EVBRHzosQWmtzNLG2bbSVrlnBA4+Hj70Q/ov0G3JPhSbLFnSdhQQQ5NZgfqUI93uD5tXWe6OoaVEePuTLLNYkj9KjAsmdqAFrMCxsevOMS39i0ba3hFVV42V/4G/X5+hCFtlfokZUaa2zwkzI8UN9x8cvH/D52ycscb5XF3+q/Qtvk5yrLzJdXQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 os&quot; title=&quot;&quot; src=&quot;/static/49dddd9be03a982b48d084da791498cf/6ee55/2023-08-11-AWS-EC2-os.png&quot; srcset=&quot;/static/49dddd9be03a982b48d084da791498cf/0eb09/2023-08-11-AWS-EC2-os.png 500w,
/static/49dddd9be03a982b48d084da791498cf/1263b/2023-08-11-AWS-EC2-os.png 1000w,
/static/49dddd9be03a982b48d084da791498cf/6ee55/2023-08-11-AWS-EC2-os.png 1496w&quot; sizes=&quot;(max-width: 1496px) 100vw, 1496px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;우리 컴퓨터에 윈도우 OS가 설정되어 있으면, 우리는 그 기본 설정과 소프트웨어, 데이터를 가지고 있는 컴퓨터를 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;이와 비슷하게, ****AMI******는 가상 컴퓨터를 실행하기 위해 어떤 운영 체제(OS)를 사용하고, 어떤 소프트웨어들을 설치해 놓을 것인지 등을 담고 있는 이미지(자원을 생성, 구성하는 데 사용되는 설정과 값을 포함하는 파일)** 입니다.&lt;/p&gt;
&lt;p&gt;커스텀 PC처럼 AWS에서 제공하는 공식 AMI뿐만 아니라 사용자가 직접 정의한 AMI를 생성할 수도 있습니다.&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h3&gt;둘째, &lt;strong&gt;인스턴스 유형 (서버의 사양)을 설정&lt;/strong&gt;해야한다.&lt;/h3&gt;
&lt;p&gt;사용할 목적에 따라 컴퓨터의 사양도 달라야겠죠?&lt;/p&gt;
&lt;p&gt;인스턴스 유형 설정을 통해 CPU, 메모리, 스토리지 및 네트워킹 용량 등 컴퓨터 사양을 선택할 수 있습니다.&lt;/p&gt;
&lt;p&gt;예) Amazon EC2 T4g 인스턴스&lt;br/&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1564px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/6b15459bfe5e8ef3260f91134192c34d/a7dde/2023-08-11-AWS-EC2-oo.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 10.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAACCAYAAABYBvyLAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAeklEQVR42h2MWw6CMAAEuf/5fMWoUaFY6BNrKaRNRujHJLPZzTajNshR8ZsT1k9YNxHTguglclCEOPPuBIPSaOO43R+0Xc9n6w7HM89XizKW0+VKK3oarTXGGNZ1JcZY2T2EUNndObfhyTmTUqrkXPDe8902pZT6sec/oMyXkUwhCK0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 oo&quot; title=&quot;&quot; src=&quot;/static/6b15459bfe5e8ef3260f91134192c34d/a7dde/2023-08-11-AWS-EC2-oo.png&quot; srcset=&quot;/static/6b15459bfe5e8ef3260f91134192c34d/0eb09/2023-08-11-AWS-EC2-oo.png 500w,
/static/6b15459bfe5e8ef3260f91134192c34d/1263b/2023-08-11-AWS-EC2-oo.png 1000w,
/static/6b15459bfe5e8ef3260f91134192c34d/a7dde/2023-08-11-AWS-EC2-oo.png 1564w&quot; sizes=&quot;(max-width: 1564px) 100vw, 1564px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;br&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;지금까지 가이드&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;루트 사용자의 리소스에 접근할 수 있는 가상의 신분으로(IAM) 로그인 했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 첫 번째 설정, 인스턴스의 운영체제와 사양을 설정했습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;3. VPC 이해하기 (1) &lt;/br&gt; : 리전과 가용영역, 가상 사설 네트워크(VPC)란?&lt;/h2&gt;
&lt;p&gt;인스턴스를 생성하기 위해서는 VPC를 설정해야 합니다.&lt;/p&gt;
&lt;p&gt;5단계를 거쳐 VPC의 개념, VPC 생성에 필요한 것들을 배우고 서브넷과 CIDR에 대해서도 가볍게 익혀봅시다.
&lt;/br&gt;&lt;/br&gt;&lt;/p&gt;
&lt;h3&gt;리전(Region)과 가용 영역(AZ: *&lt;strong&gt;*Availability Zone**&lt;/strong&gt;)&lt;/h3&gt;
&lt;p&gt;AWS 서비스들을 제공하기 위해서는 실제 데이터를 저장할 데이터 센터가 필요하겠죠?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;가용 영역(Availability Zone)&lt;/strong&gt; 은 &lt;strong&gt;같은 지역 내에서 물리적으로 독립된 데이터 센터들의 그룹&lt;/strong&gt;을 말합니다.&lt;/li&gt;
&lt;li&gt;리전(Region)은 가용 영역이 2개 이상 구성된 지리적 영역입니다. 국가/도시 단위 별로 서비스를 제공하기 위한 단위로 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;예를 들어, AWS 서울 리전 (ap-northeast-2)은 현재 3개의 가용 영역(서울에 위치해 있는 데이터 센터 그룹)을 보유하고 있는데요. 이렇게 지역별로 서비스를 제공함으로써 네트워크 속도를 높이고, 데이터 지역성을 고려한 서비스를 제공할 수 있습니다.
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h3&gt;VPC 개념 이해하기&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;VPC(Virtual Private Cloud)&lt;/strong&gt; 는 &lt;strong&gt;가상 사설 네트워크&lt;/strong&gt;입니다.
&lt;/br&gt;쉽게 말하자면, AWS클라우드에서 독립된 “&lt;strong&gt;나만의 개인 네트워크 망&lt;/strong&gt;”을 만드는 것이지요.&lt;/p&gt;
&lt;p&gt;나만의 네트워크 망을 만들기 위해서는,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;첫째, 내 네트워크 망이 어느 지역에 속할지 설정해야합니다&lt;/strong&gt;.
&lt;/br&gt;즉, 하나의 “리전”을 선택해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;둘째, 내 네트워크 망이 사용 할 IP 주소 범위를 설정해야 합니다.&lt;/strong&gt;
&lt;/br&gt;후술할 “IPv4 CIDR 블록” 설정을 통해 IP주소 대역을 설정하면, &lt;strong&gt;이 VPC 내에서 사용되는 모든 리소스들은 사용자가 지정한 IP 주소 범위 내에서 IP주소를 할당받게 됩니다.&lt;/strong&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1158px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/b9a2d4cb00f850bd9e014cc8d621ac5e/69083/2023-08-11-AWS-EC2-vpc-region.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 23.400000000000002%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA6klEQVR42o2QPUvEUBBF96f7TwSxsFK00UIW1sZ13aDRdQuFoNglRkLWfLx5k3ecJCKxs7jMcO+b84aZpa8JZZai1Retc7SqOO9x1v9X7VAFEWF2cnjM2+U5+cUZPnlGWguq2sKAuG6s/kfWOxnrr3pfzTepLTMrkoR6fUO8d0B6uuRzHqF3t9g7tJdM5MEsOiaZA2kYoANQLQxNS7V5odi+U11dk+0fkc3X+GhpA3YCG5QekH+wWywoV9GYlTvE6L4JE2DXEfq1zezhFDkar9DNPd1jZBTF/hs2pizG7Z8eCNuYUNtp9C/wG2znfddV7W3HAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 vpc region&quot; title=&quot;&quot; src=&quot;/static/b9a2d4cb00f850bd9e014cc8d621ac5e/69083/2023-08-11-AWS-EC2-vpc-region.png&quot; srcset=&quot;/static/b9a2d4cb00f850bd9e014cc8d621ac5e/0eb09/2023-08-11-AWS-EC2-vpc-region.png 500w,
/static/b9a2d4cb00f850bd9e014cc8d621ac5e/1263b/2023-08-11-AWS-EC2-vpc-region.png 1000w,
/static/b9a2d4cb00f850bd9e014cc8d621ac5e/69083/2023-08-11-AWS-EC2-vpc-region.png 1158w&quot; sizes=&quot;(max-width: 1158px) 100vw, 1158px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h3&gt;VPC에 IP 주소 범위를 정해야하는 이유?&lt;/h3&gt;
&lt;p&gt;만약 우테코라는 기업 네트워크가 있다고 가정해봅시다. 이 기업 네트워크에 연결될 장치들(컴퓨터, 휴대폰 등)의 수가 최대 50개라면, IP주소 개수가 50개면 되겠죠?&lt;/p&gt;
&lt;p&gt;IP주소는 소중한 자원이기 때문에 이를 효율적으로 할당하고 관리하기 위해 IP주소가 50개만 나올 수있는 네트워크 주소 범위를 정하는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;즉, 주소 범위를 정하면, 범위만큼의 IP주소 개수를 할당할 수 있는 것입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;br&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;지금까지 가이드&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;루트 사용자의 리소스에 접근할 수 있는 가상의 신분으로(IAM) 로그인 했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 첫 번째 설정, 인스턴스의 운영체제와 사양을 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 두 번째 설정, 인스턴스의 VPC를 설정했습니다.
&lt;ol&gt;
&lt;li&gt;VPC 이해하기(1) : VPC는 인스턴스가 속한 가상 네트워크 망으로, 리전, IP주소 범위를 설정해야한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;4. VPC 이해하기 (2) &lt;/br&gt; : VPC의 IPv4 CIDR 블록 이해하기&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;IPv4 CIDR 블록&lt;/strong&gt;은 &lt;strong&gt;CIDR 표기법을 사용하여 표현된 IPv4 주소 범위&lt;/strong&gt;를 말합니다. CIDR란 무엇일까요?&lt;/br&gt;
결론부터 말하자면, CIDR란 &lt;em&gt;서브넷 마스크를 사용해 IP 주소 범위를 표현하는 주소 체계&lt;/em&gt; 입니다. CIDR 주소체계를 통해 네트워크의 IP주소 할당을 더 효율적으로 관리할 수 있게 되었습니다.&lt;/p&gt;
&lt;p&gt;구체적인 설명에 앞서 이해를 돕기 위해 간단히 비유를 들어보겠습니다. 이 부분을 읽지 않고 바로 아래로 넘어가도 좋습니다. &lt;/br&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;우리가 도시의 주소를 관리하는 기관에 근무하고 있다고 상상해봅시다.&lt;/br&gt;
예전에는 &lt;strong&gt;도시를 크기&lt;/strong&gt;에 따라 &lt;strong&gt;‘클래스’&lt;/strong&gt; 로 나눠서 도시마다 가질 수 있는 주소 범위를 할당했습니다.&lt;/br&gt;
큰 도시에는 A클래스라는 큰 주소 공간을, 중간 도시에는 B클래스 중간 크기의 주소 공간을, 작은 도시는 C클래스라는 작은 크기의 주소공간을 주소를 할당하는 식이었어요.&lt;/br&gt;
그런데 실제 도시의 크기와는 상관 없이 미리 정해진 클래스의 주소 공간이 할당되어 주소들이 낭비되는 경우가 생겼습니다.&lt;/br&gt;
예를 들어, C클래스인 도시에 100개의 주소를 할당한다면, 50개의 주소만 필요한 작은 도시는 50개를 낭비하는 셈이죠&lt;/br&gt; &gt; &lt;strong&gt;CIDR은 이 문제를 해결하기 위해, 도시의 크기와 상관없이 도시가 필요한 만큼의 주소 공간을 할당해주는 방식입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h3&gt;첫째, IP 주소의 구조&lt;/h3&gt;
&lt;p&gt;IP주소의 구조는 192.168.1.0 과 같이 .을 구분자로 한 4개의 숫자로 이루어져 있고, 네트워크 ID와 호스트 ID로 구성됩니다.&lt;/p&gt;
&lt;p&gt;예를 들어, 데이터는 IP주소를 통해, 선릉 캠퍼스 중 이레라는 사람을 찾아와야 합니다.
“네트워크 ID는 집 주소 = 서울시 강남구 선릉역 성담빌딩 13층”, “호스트 ID는 내 이름 = 이레”로 이루어진 꼴입니다.
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h3&gt;둘째, 네트워크 내에서 IP주소를 할당하는 방법 (old.ver) &lt;br/&gt;&lt;/h3&gt;
&lt;p&gt;이전에는 고정된 크기의 &lt;strong&gt;IP 주소 범위(A,B,C 클래스)&lt;/strong&gt; 을 사용하여 네트워크를 구성했습니다.&lt;br/&gt;
무슨 소리냐구요?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IP 주소의 첫 번째 숫자를 네트워크 ID로 사용하고, 나머지 세 개의 숫자를 호스트 ID로 사용하는 IP 주소범위를 A 클래스&lt;/li&gt;
&lt;li&gt;IP 주소의 첫 번째와 두 번째 숫자를 네트워크 ID로 사용하고, 나머지 두 개의 숫자를 호스트 ID로 사용하는 IP 주소범위를 B 클래스&lt;/li&gt;
&lt;li&gt;IP 주소의 세 개의 숫자를 네트워크 ID로 사용하고, 나머지 한 개의 숫자를 호스트 ID로 사용하는 IP 주소범위를 C 클래스
와 같이 고정된 주소 범위(클래스)를 통해 네트워크의 크기를 결정했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;즉, 하나의 네트워크가 선택할 수 있는 IP주소 범위는 A,B,C 세 가지 중 하나였던 것입니다.
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h3&gt;셋째, 고정된 주소 범위로 네트워크를 구성하는 것의 문제점&lt;/h3&gt;
&lt;p&gt;예를 들어, 우테코 회사는 IP주소가 50개만 필요합니다. 그 중 C클래스가 가장 작은 네트워크 규모(=IP 주소범위)를 갖고 있기 때문에, 우테코 회사는 C클래스의 네트워크 망을 구성하게 됩니다.&lt;br/&gt;
하지만, C 클래스의 주소범위는 &lt;strong&gt;192.0.0&lt;/strong&gt;.0 ~ &lt;strong&gt;223.255.255&lt;/strong&gt;.255 이고, IP 주소의 처음 세 개의 숫자가 네트워크 ID로 사용되면, 호스트 ID의 개수는 0~255로 256개가 됩니다.&lt;br/&gt;
우테코 회사에서는 IP주소가 50개만 필요하다고 했는데, 나머지 106개의 소중한 IP주소 자원은 쓰이지 않고 버려지는 거죠!
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h3&gt;넷째, IP주소를 효율적으로 할당하는 새로운 방식 : CIDR (new.ver)&lt;/h3&gt;
&lt;p&gt;그래서 등장한 것이 CIDR(Classless Inter-Domain Routing)라는 클래스 없는 IP주소 할당 방식입니다.
네트워크에 연결된 장치 수(호스트 수)에 따라 효율적으로 사용할 수 있는 &lt;strong&gt;서브넷(Subnet)&lt;/strong&gt; 을 만드는 것이죠.
쉽게 말하자면 서브넷은 IP주소 범위(블록)을 더 작은 범위(블록)으로 나누어 네트워크를 구성하는 것을 말합니다.
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h3&gt;다섯째, 더 큰 네트워크를 분할한 더 작은 하위 네트워크 : 서브넷&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;서브넷을 만든다는 것&lt;/em&gt; 은 &lt;em&gt;&lt;strong&gt;호스트ID 영역을 더 잘게 쪼개어 네트워크ID 영역을 늘리는 작업(서브네팅)&lt;/strong&gt;&lt;/em&gt; 을 뜻합니다.&lt;/br&gt;
&lt;/br&gt;
예를 들어, 우테코는 50개의 IP주소만이 필요합니다.&lt;/br&gt;
IP주소 범위가 &lt;strong&gt;192.167.10.&lt;/strong&gt; 0~ &lt;strong&gt;192.167.10.&lt;/strong&gt; 255인 네트워크는 호스트 IP 범위가 (0~255)로 256개의 IP를 할당할 수 있습니다. 이 네트워크를 더 작은 범위의 네트워크(서브넷)으로 분할해, 우테코가 가져가도록 하게 하면 될 것입니다.
&lt;/br&gt;
호스트ID 영역을 1/4로 줄여서, 우테코는 64개의 호스트 수를 갖는 네트워크(서브넷)를 구성하게 됩니다.
호스트ID 영역이 3/4인 나머지 네트워크(서브넷)는 다른 기관이 쓸 수 있을테고요.&lt;/p&gt;
&lt;p&gt;네트워크 ID영역을 늘려 더 작은 네트워크(서브넷)를 구성함으로써, IP 주소 낭비가 획기적으로 줄어들었습니다!
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h3&gt;여섯째, CIDR(Classless Inter-Domain Routing)이란?&lt;/h3&gt;
&lt;p&gt;지금까지, 고정된 주소범위(클래스)에 국한되지 않고, 네트워크 ID영역의 크기 조절을 통해 IP주소를 유연하게 할당하는 방식에 대해 배웠습니다.
이렇게 잘게 쪼개어진 네트워크까지 표현하기 위한 IP주소 체계를 CIDR이라고 합니다.&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;앞서, CIDR란 &lt;em&gt;&lt;strong&gt;서브넷 마스크를 사용해 IP 주소 범위를 표현하는 주소 체계&lt;/strong&gt;&lt;/em&gt; 라고 설명드렸습니다.
서브넷 마스크란 해당 네트워크의 네트워크 ID 부분의 비트 수를 말하는데요. 서브넷 마스크를 통해 호스트 ID의 비트 수, 즉 IP 주소 범위를 알 수 있게 됩니다.&lt;/p&gt;
&lt;p&gt;예를 들어, &lt;em&gt;줄어든 우테코의 네트워크 IP 주소 범위&lt;/em&gt; 는 192.167.10.0~ 192.167.10.63인데요.
이를 CIDR 형식으로 나타내면, “&lt;strong&gt;192.167.10.0/26&lt;/strong&gt;” 으로 표현할 수 있습니다.&lt;/p&gt;
&lt;p&gt;CIDR 주소의 “/26”는 네트워크 ID부분의 비트 수(서브넷 마스크의 길이)를 의미합니다. 즉, &lt;strong&gt;192.167.10.0/26&lt;/strong&gt; 는 &lt;strong&gt;&lt;em&gt;네트워크 ID 영역이 26비트이고 호스트 ID 영역이 6비트인 네트워크&lt;/em&gt;&lt;/strong&gt; 를 표현하는 것이지요.
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/a43e072875c564f245e6e47f54127c5d/d9ac3/2023-08-11-AWS-EC2-vpc-cidr2.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 33.800000000000004%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABiElEQVR42nWR30/acBTF+f/fF/ayhxkjziwakc0lOhkyJSAwYqGtBdrSlpa1X+omtlBG+1llMXFZdpJzb+6v83BuIcsynhCIgJPKCeVymcvaF2zbopLXlQ8fad20GesGiqLytV5H+MH25vn2JQrPzSiKeLdXolh8zdvdEupQZ2//gIP9EposYY41qtULDitl3Jn3f8GnYIQeHXdIY6piC5fU67A0LmCuoIkV52aypRM8EtQGxHWDtDslW/36VzBZrXAfFwzvBXehjx/FxA+CRTjN8xxnkSKHGUpOEYOrWoQjg6U3Y5GEhIlPnAQs1j9INksK8kCm6kzYUbq86Te4dufYEx1dbmIZKp+tDa8kKA7g0sq4PbtFuzrFHTTozxscmTuYznvaeg3fH1GYOg5SKDibjPhkKkjigW9jlxtZpa9bnBopxwZUTJC+Z6gtDdvoYc8krtxzDvVd+s4xd9MeP73JHw9fYpP73PChmT+yK6An/p5HcUSWL/nLgKbXoj3rIOVer9P11sPfA8z/Ffkqs9sAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 vpc cidr2&quot; title=&quot;&quot; src=&quot;/static/a43e072875c564f245e6e47f54127c5d/f97d7/2023-08-11-AWS-EC2-vpc-cidr2.png&quot; srcset=&quot;/static/a43e072875c564f245e6e47f54127c5d/0eb09/2023-08-11-AWS-EC2-vpc-cidr2.png 500w,
/static/a43e072875c564f245e6e47f54127c5d/1263b/2023-08-11-AWS-EC2-vpc-cidr2.png 1000w,
/static/a43e072875c564f245e6e47f54127c5d/f97d7/2023-08-11-AWS-EC2-vpc-cidr2.png 2000w,
/static/a43e072875c564f245e6e47f54127c5d/d9ac3/2023-08-11-AWS-EC2-vpc-cidr2.png 2036w&quot; sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;예시 사진을 보면, IP주소에서 하나의 숫자는 8비트인데요.&lt;/br&gt;
호스트 ID영역을 6비트로 잘게 쪼개면, 네트워크 ID의 영역은 8*3 =24 에서 → 8*3+2 =26 비트가 됩니다.&lt;/br&gt;
이처럼 서브넷 마스크를 설정하면 6비트로 표현되는 호스트 ID 영역과 나머지 26비트로 표현되는 네트워크 ID 영역을 가지는 서브넷을 생성할 수 있는 것 입니다.
(이 계산법에 대해 좀더 자세히 공부하고 싶으신 분들은 서브넷 마스크와 서브네팅 계산법을 공부하시면 됩니다.)&lt;/p&gt;
&lt;/p&gt;
&lt;br&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;지금까지 가이드&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;루트 사용자의 리소스에 접근할 수 있는 가상의 신분으로(IAM) 로그인 했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 첫 번째 설정, 인스턴스의 운영체제와 사양을 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 두 번째 설정, 인스턴스의 VPC를 설정했습니다.
&lt;ol&gt;
&lt;li&gt;VPC 이해하기(1) : VPC는 인스턴스가 속한 가상 네트워크 망으로, 리전, IP주소 범위를 설정해야한다.&lt;/li&gt;
&lt;li&gt;VPC 이해하기(2) : VPC의 IP주소범위는 CIDR방식으로 나타낸다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;5. VPC 생성하기 및 인스턴스에 적용하기&lt;/h2&gt;
&lt;p&gt;VPC를 생성하기 위해서는&lt;/p&gt;
&lt;h4&gt;첫째, 리전을 적용해야하는데, 사용자가 선택한 리전으로 자동 적용됩니다.&lt;/h4&gt;
&lt;h4&gt;둘째, IPv4 CIDR 블록으로 VPC의 IP 대역(네트워크 주소범위)을 설정해야 합니다.&lt;/h4&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1212px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/e9407dfa04463287acbf4c0e3a174351/468b3/2023-08-11-AWS-EC2-vpc-setting.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAABYlAAAWJQFJUiTwAAACW0lEQVR42p1U2W7bMBDU/39WE/StbYo0aCxL1n1ZkSXqtC5PZ2kkSIO4TUx4vJS4Gu4xpHFz+xX3vx7w5eYW3+7ukVQd2q5F14sl2vZsL6Dre426aTSMnePCD0JsTBOWvYMbxAijCJ7va5tkGaI41pBn8dU2DDkP6Bdo3+JwQMPNjSRNEYYRAjqkaYZ8v9dWCJIkJRKun9dSkidpgpjv4vhs9WbcoKwqdIzYOJDZNLf4/uMO8zRhXRZM84yFdqZ9C3n/jNfP0zhqGMMwoCJ7y/yHccK0rMDpxN/fWNf15WOZX4JRKYXt1kbMlHZxgeRJQdUNirKCajocSqXnQvQ8Tu9sKJBhFP2Mu7iBGbB+7YRiWJA1EzLO9+2sbaQGRj/iqSiQscav035bAkMNE7ynBrnqkNdMf5jRMhhyncF5M50w84PheERPiVwiFBjSatfz2CUFx/XYtQT9cKT+qLGu1xC99f3wVz3fg6wZHR1dz6ccMhINmkjIK1WjVFLPWj9LXaX7/4MxjkeUlE7B+ghZPy0Xi/6v7r50eV+18KIUPtP25BTkFfU449qhu2x6ER4fN7AsGzax2zlomlY7rBKdSOUj4J8x8XQ0UidqTeYj5SEXgtirIkzLGj83Dh4sDwHTzWveHONK+SwoGf1HcehmKErOqLoRu6SA6Sf47UQIixYFF/OWQu6mD0MOhRAbhWrghQl8uYaCCNGh1QJ9rslncE65PsJidLZl6TNt806UK0vqeVUNJRoh2G4tOLxsHdfF44Ydt2193D5NKOre73MtFZGNkAbUo8hmuYLwD47DCDWHvIICAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 vpc setting&quot; title=&quot;&quot; src=&quot;/static/e9407dfa04463287acbf4c0e3a174351/468b3/2023-08-11-AWS-EC2-vpc-setting.png&quot; srcset=&quot;/static/e9407dfa04463287acbf4c0e3a174351/0eb09/2023-08-11-AWS-EC2-vpc-setting.png 500w,
/static/e9407dfa04463287acbf4c0e3a174351/1263b/2023-08-11-AWS-EC2-vpc-setting.png 1000w,
/static/e9407dfa04463287acbf4c0e3a174351/468b3/2023-08-11-AWS-EC2-vpc-setting.png 1212w&quot; sizes=&quot;(max-width: 1212px) 100vw, 1212px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;참고로, IPv4 CIDR 블록이 192.168.0.0/16라는 것은, 네트워크 ID 영역이 16비트라는 뜻입니다.&lt;/br&gt;
이 네트워크에서 IP 주소 할당 가능한 호스트 수는 2의 16승(=16비트)으로, 65,536개가 됩니다!
&lt;br/&gt; &lt;br/&gt;
이를 통해, 생성된 VPC 입니다. &lt;br/&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1160px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/5de44e42861ebfee8bcaa9cad97dd6a1/9708d/2023-08-11-AWS-EC2-vpc-create.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 7.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAACCAYAAABYBvyLAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAe0lEQVR42j2NSw6CMAAFuXWDKzyaKzaGxEOUWiwgDVjpbwSMLmY3b16RUiLG+Mf7gHMOM4z0wxOt9YFSCtMbxmnEB897XZnnGdkqrLWktG1DoMg5s0d/fKOeyUUu9ZVTWVKdK4QQNLcG8+ow9nG4y7IgZctdd6zbwd76ABL8lWnOAdR7AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 vpc create&quot; title=&quot;&quot; src=&quot;/static/5de44e42861ebfee8bcaa9cad97dd6a1/9708d/2023-08-11-AWS-EC2-vpc-create.png&quot; srcset=&quot;/static/5de44e42861ebfee8bcaa9cad97dd6a1/0eb09/2023-08-11-AWS-EC2-vpc-create.png 500w,
/static/5de44e42861ebfee8bcaa9cad97dd6a1/1263b/2023-08-11-AWS-EC2-vpc-create.png 1000w,
/static/5de44e42861ebfee8bcaa9cad97dd6a1/9708d/2023-08-11-AWS-EC2-vpc-create.png 1160w&quot; sizes=&quot;(max-width: 1160px) 100vw, 1160px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;br&gt;
인스턴스 생성 시, 인스턴스의 네트워크 설정&amp;gt;VPC에서 생성한 VPC(MY_VPC)를 적용합니다.
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 766px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/1834fc4a544498bbfe2bb730f85be38d/e4da8/2023-09-22-AWS-EC2-vpc-create.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 25%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAsUlEQVR42p2QYQ6CMAyFd0yjF+CnnMXo4WAGjAwCTA2DsbExnoNEfxhjiE2+viZtX9ISIQTimCKKYlCv6TVHpy2sMTB/QObEOUdVVihyhqKs0anhbWitXc1iKKVEKxo8WglpJnzGNE2reAVp/Ml5UeBwPGGz3SEIAuzDEEmSoq45hmFYFpxzX5l7vVLLnOx7EO0Lfrt7gwsoPSPLMjDG0PumUtqfMmIc3U+0nt9jvWo8AYh+ftYcrcUyAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 09 22 AWS EC2 vpc create&quot; title=&quot;&quot; src=&quot;/static/1834fc4a544498bbfe2bb730f85be38d/e4da8/2023-09-22-AWS-EC2-vpc-create.png&quot; srcset=&quot;/static/1834fc4a544498bbfe2bb730f85be38d/0eb09/2023-09-22-AWS-EC2-vpc-create.png 500w,
/static/1834fc4a544498bbfe2bb730f85be38d/e4da8/2023-09-22-AWS-EC2-vpc-create.png 766w&quot; sizes=&quot;(max-width: 766px) 100vw, 766px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;지금까지 가이드&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;루트 사용자의 리소스에 접근할 수 있는 가상의 신분으로(IAM) 로그인 했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 첫 번째 설정, 인스턴스의 운영체제와 사양을 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 두 번째 설정, 인스턴스의 VPC를 설정했습니다.
&lt;ol&gt;
&lt;li&gt;VPC 이해하기(1) : VPC는 인스턴스가 속한 가상 네트워크 망으로, 리전, IP주소 범위를 설정해야한다.&lt;/li&gt;
&lt;li&gt;VPC 이해하기(2) : VPC의 IP주소범위는 CIDR방식으로 나타낸다.&lt;/li&gt;
&lt;li&gt;VPC 생성하기 : 리전과 IP주소범위를 설정한 VPC를 생성한다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 시, 미리 생성된 VPC를 설정해 인스턴스가 속할 네트워크 망을 정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[AWS 로그인부터 시작하는 인프라 구축하기 2편]]></title><description><![CDATA[AWS 로그인부터 시작하는 인프라 구축하기 2편 : 서브넷 / 보안그룹 / 퍼블릭vs프라이빗 인스턴스 / 라우팅 테이블과 인터넷 게이트웨이 6. 서브넷 이해하기 인스턴스를 생성할 때, 설정한 VPC…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-09-22-AWS-EC2-(2)/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-09-22-AWS-EC2-(2)/</guid><pubDate>Wed, 04 Oct 2023 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;AWS 로그인부터 시작하는 인프라 구축하기 2편 :&lt;/h1&gt;
&lt;h2&gt;서브넷 / 보안그룹 / 퍼블릭vs프라이빗 인스턴스 / 라우팅 테이블과 인터넷 게이트웨이&lt;/h2&gt;
&lt;br&gt;
&lt;h2&gt;6. 서브넷 이해하기&lt;/h2&gt;
&lt;p&gt;인스턴스를 생성할 때, 설정한 VPC에 따라 선택할 수 있는 서브넷 목록이 바뀌는 것을 볼 수 있습니다. 서브넷은 VPC에 종속되는 것일까요?
&lt;br&gt;
앞서, 네트워크 내의 IP주소를 효율적으로 할당하기 위해 서브넷이라는 용어가 나왔는데요.&lt;br&gt;&lt;/p&gt;
&lt;p&gt;네트워크에서의 서브넷, AWS에서의 서브넷 개념을 이해하고, AWS에서 서브넷을 만들어봅시다.
&lt;br&gt;&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;6-(1).네트워크에서 서브넷 이해하기&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;네트워크에서 서브넷이란, 큰 네트워크를 더 작은 네트워크로 분할한 것&lt;/strong&gt;을 말합니다.&lt;/p&gt;
&lt;p&gt;더 큰 네트워크에서 호스트ID 영역을 줄여서 더 작은 네트워크(호스트 수가 더 적은)로 분할해 생긴 &lt;strong&gt;작은 네트워크 대역&lt;/strong&gt;을 &lt;strong&gt;서브넷&lt;/strong&gt;이라고 하는 것이죠.&lt;/br&gt;&lt;/br&gt;
서브넷은 독립적인 네트워크로 간주되며, &lt;strong&gt;각 서브넷은 자체적으로 라우팅과 보안 설정을 가지고 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;1. 자체적인 라우팅을 가진다.&lt;/h4&gt;
&lt;p&gt;서브넷은 독립적인 작은 네트워크이므로, &lt;em&gt;자신의 네트워크 주소범위&lt;/em&gt; 를 가지고 있습니다.
따라서, &lt;strong&gt;자체적인 라우팅 테이블을 사용해, 서브넷 내부의 호스트(네트워크 장치)들 사이에서 통신과 다른 서브넷으로의 통신을 처리&lt;/strong&gt;합니다.
(*라우팅 테이블 = 다른 호스트(네트워크장비)로 데이터 전송할 때 어떤 경로 선택해야 하는지 지시하는 경로표)&lt;/p&gt;
&lt;h4&gt;2. 자체적인 보안설정을 가진다&lt;/h4&gt;
&lt;p&gt;서브넷은 독립적인 보안그룹을 구성하여, 원하는 &lt;em&gt;&lt;strong&gt;트래픽을 허용하거나 차단&lt;/strong&gt;&lt;/em&gt; 합니다.
즉, &lt;strong&gt;특정 포트나 프로토콜 통신을 제한하거나 허용할 수 있습니다&lt;/strong&gt;.&lt;/p&gt;
&lt;br/&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6-(2).AWS에서 서브넷 이해하기&lt;/h3&gt;
&lt;p align=&quot;center&quot;&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 526px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/73a6857e1d76da3c9510275df21e59da/55e98/2023-08-11-AWS-EC2-subnet.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 86.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAIAAABSJhvpAAAACXBIWXMAAAsTAAALEwEAmpwYAAACYUlEQVR42pVTu47TQBTNn/AFVPT8AQUSEv9AQ4tEQ8MWNBSgbagAgZBooN16JVjIwi77YgPexImT2PF43jMej8cz3NibRQkVR/bo+Nrn3nPvjHsBsPPEP7wWPr+kjM3n8wIhWNM0dc7BS+/91bqBnsC5eX67fnbLb90gg35WUILxokVZllULYwys8KjW0ZNK1SQVg73kpK+NVYJjjKWUjDGEEG5BKQUO4s5C5wLS9SABMGWb6OIin/xOqYzjGDyDcyCz2SxJEuCj0QhSQCPWWpCBBEgn9pUPzccH4fF1++VtkuaMkul0OhwOoeCqmG+apq5r26LjIJatByN2tvmr+5P3W8NkPh5PkumUcQG9ZotFFEWjOB5PJjlCprJCiCvx0jYwaB5cnUbxwf7X44Nv0eCnq6DJhmK83+9H52dnx0d5OgvBwTjgyzWxYrhRNOBxfH706/SHZDRY7UURjNDJ+cnh9/HFwBkdKsUJcuuVHczZlsr83KUYZaLiQpZSBM1NMSsHnzJhEdNSl7XRsLtrYlvXWvKSoeUcD9/5N3dCY5WUXmKt9XJrPtwLu0/hLRQWFDu3LlaCGZpbeNh74bZvBldpJT3LQAzjaV7f9TuPbCfG/9heVuYEJgHeZgsCxbRgXqC2cpMhQqWuXV0psSlubQsj6DKLVvkiA6sQWdn2RYEE57YGP1KC7Y3KUnCnqOIU6hslFCNGkKAJ+JSkgEgpmaKFN5KTjZ6thaMrGFWMSloIStSSE8goYWUEIi3BsIVK6ctDAv9At8+gr1vY1QV2uuDq/hu8PNscOmnP+n8BJFrrPwQ8vU0IQuh7AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 subnet&quot; title=&quot;&quot; src=&quot;/static/73a6857e1d76da3c9510275df21e59da/55e98/2023-08-11-AWS-EC2-subnet.png&quot; srcset=&quot;/static/73a6857e1d76da3c9510275df21e59da/0eb09/2023-08-11-AWS-EC2-subnet.png 500w,
/static/73a6857e1d76da3c9510275df21e59da/55e98/2023-08-11-AWS-EC2-subnet.png 526w&quot; sizes=&quot;(max-width: 526px) 100vw, 526px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;VPC가 가상 네트워크이고 논리적인 IP대역을 의미한다면,&lt;/p&gt;
&lt;p&gt;서브넷은 VPC의 IP주소 범위를 나누어 &lt;strong&gt;실제 리소스(예: EC2 인스턴스, S3가 서브넷에 설치됨)가 배치되는 물리적인 주소 범위&lt;/strong&gt;를 말합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하나의 VPC에 여러개의 서브넷이 생길 수 있으며, VPC를 잘게 나눈 것이기 때문에 &lt;strong&gt;VPC보다 대역폭(IP주소범위)이 낮아야 합니다&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;서브넷 한 개는 하나의 가용영역(Availability Zone: 데이터 센터 그룹)에만 속합니다.&lt;/strong&gt; (하나의 가용영역에 여러개의 서브넷은 가능합니다)
&lt;br/&gt;
*참고로, VPC를 생성할 때는 사용할 가용영역(AZ)의 개수만 선택하게 됩니다. 이후, 서브넷을 생성할 때, 어느 가용영역에 생성할지 선택하면 됩니다.
&lt;br/&gt;
&lt;p align=&quot;center&quot;&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 741px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/61b0992bbb8062e0949148dbdeecf350/660ed/2023-08-11-AWS-EC2-subnet-az.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 48.800000000000004%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAACg0lEQVR42p2RX0hTURzHj38qbWqZdudMuWrgwHIkFhWuriRWUFQvvoeYRA+CSmQMGdmLGdVLJWIIRr1YREQFgjnRklWTls5tbnPO3N29u/tz5+42/wz9dc5kUT524Ms53+/5/H7nHA5SKs8pCw+dLZbX18uKGk5k/o9ILcKjrKq2DqFH6FLeM6QEQCmMDqWjPrSDiKyT2p5t9xVDFTtJw5Ijagahnl0HlfcasxGCNKxUpNWmarV4bkh4rC2fyP5iACBli9GlIzSURhrS1TW1CLWizMoHDZXtw59O3/k8dvL1rIdGzfmKq6+enuocnawZNBqrqrub9xx72HhUo5tQ92DmvclbkNFeQl97M8h0jo7XDBgMleSAInJD2X051WfsZa0BE7jXXeCPib0tI23aKVEPtoB5gw3/Wu7WP7nw3DTgtwctG/yGG3yx8C3NuKZ/VjKCLWiBRdG55IfNnNxylRq1Tvbvc4W8Y5zEz/ESv+QMubVWn/cGu8wvYW/mJPb7N4+HcYm8npM4CxY7L7JN9oCviw3zbsxY3SFWxwHI9pdXqxHq6Mi7PeLQGzzhBQMnCVw0frfuhbHlrdUvTAsRh0Nc+dn0wXqma9z5w+SLOqZ4ye8Jr1+/MjTTPTwf8GHGOStEvngBshIN23Qv8+2B2MJiaDXOrwF+ZuzxjBDRkLUrtLqKc/9XT+S8I7jCYb8mxAEsvthNsxDp9a4DLIgrcWcw5tQnnowb0jomwwWgmAM4YAMomgDItW1u5tgBiklmiUYL37Hs7iRD8o94fxpzScYcAQX55cSn0ExFAVJk5aNslEckU8kp6jAlT3oi6njpP16uKqMI96dGLqPoy/TeUhVz8TelKoxJo3IsaQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 subnet az&quot; title=&quot;&quot; src=&quot;/static/61b0992bbb8062e0949148dbdeecf350/660ed/2023-08-11-AWS-EC2-subnet-az.png&quot; srcset=&quot;/static/61b0992bbb8062e0949148dbdeecf350/0eb09/2023-08-11-AWS-EC2-subnet-az.png 500w,
/static/61b0992bbb8062e0949148dbdeecf350/660ed/2023-08-11-AWS-EC2-subnet-az.png 741w&quot; sizes=&quot;(max-width: 741px) 100vw, 741px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
  &lt;br&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6-(3). 서브넷 생성하기 및 인스턴스에 적용하기&lt;/h3&gt;
&lt;h4&gt;첫째, 어떤 VPC의 서브넷인지 설정합니다.&lt;/h4&gt;
&lt;h4&gt;둘째, 서브넷이 상주할 데이터베이스 센터 그룹인 가용영역을 설정합니다.&lt;/h4&gt;
&lt;p&gt;(예시 : 서울 리전의 -2a)
&lt;br/&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 954px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/9d47195ab939b3dc5cdb0755b50476b3/65dc2/2023-08-11-AWS-EC2-subnet-create.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 119.19999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAYCAYAAAD6S912AAAACXBIWXMAABYlAAAWJQFJUiTwAAACYUlEQVR42qVV227aQBTcryK/0Hxv+lalVUlaEbUqtG8VFDAXXwDfbXwBMzmzFGK1TmrSlUY2yJ49OzPnWH391sfth4/oPXzBzc1b/Pg5hu1FiKIIgSC8EMrzfKzXa2yzLdI0RZSkiOW63W5fBbVarTEeT7Df75FlGXIh5rUsCpRlqbG7AIovh2GoCevrcDho1O/bQEVxjMXShGU76N7d493te/R6D0iSRD/AjdoQVURVQeV5Dt8P0B8M8Ob6GldXV+h0Ouh277BxPYF7Jn4OJCJPItqrKIoxmUzFmA1cIShEu9Mx+eAlx9VHDoIAw9EvLBZLOI6jnfqfpRKJiWlaWkdWy6qa1smgfxNKRUEQgnn0fF/yKNGRY78WKhY3SUJkImwqG9RBoS+BYpf0B99x/+kzDMN4CriQ53mB3W53EbTL1M+YzTGbLbTTS9HUllzazkqbxG6h+22gyFrviFNUjtfqxfw1QbEjllLhdGroFrzE0UaXWaFl2RhJFplJrnqVVXX4jarVRooPrTcuTCG1bFtHSENmmytRCsLjlW3IzD5t1AxNuBFCmjIcjrQpdDnV8y3TzlGbXVsNycqJw1AHYSwDNtG/4zhBLBU91zkvHnklWbQsC6afIitOFdHhCqVUWBSl7gIdDckmG+A0RBoJOaJm84UMibGe3hNxnJryP2M+1zKEkld+M6gpNWaH1bWjJOw2fWRGx/U8CbMt5D58eYEzkkSe/M9gS5j+Hqo1Qp6Kzyne0OW5VDOVuThzPMRZecxjbdI0OfrnBmdTWD5JGZulaYrrm/OO+iPVooepKc18BIyQQm5mPgEAAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 subnet create&quot; title=&quot;&quot; src=&quot;/static/9d47195ab939b3dc5cdb0755b50476b3/65dc2/2023-08-11-AWS-EC2-subnet-create.png&quot; srcset=&quot;/static/9d47195ab939b3dc5cdb0755b50476b3/0eb09/2023-08-11-AWS-EC2-subnet-create.png 500w,
/static/9d47195ab939b3dc5cdb0755b50476b3/65dc2/2023-08-11-AWS-EC2-subnet-create.png 954w&quot; sizes=&quot;(max-width: 954px) 100vw, 954px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h4&gt;셋째, VPC의 IP주소 범위를 분할한, 서브넷 IPv4 CIDR 블록을 설정합니다.&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;설정한 VPC의 주소 범위가 192.168.0.0/16이기 때문에, &lt;strong&gt;서브넷의 대역폭은 VPC의 대역폭보다 좁은 CIDR블록을 설정해야 합니다.&lt;/strong&gt;&lt;/br&gt;
더 좁은 대역폭이란 IP 주소 범위에서 사용 가능한 IP 주소의 개수가 적다는 것을 의미합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;“/16”이라는 것은 VPC의 서브넷 마스크의 길이가 네트워크ID가 16비트라는 말로, 65,536개의 IP 주소를 가집니다.&lt;/br&gt;
따라서 &lt;strong&gt;서브넷은 VPC보다 네트워크ID 영역의 비트수가 높은&lt;/strong&gt; “/17”부터 아마존의 정책에 따라 “/28”까지 &lt;strong&gt;필요한 IP 개수에 따라 서브넷의 CIDR 블록을 설정&lt;/strong&gt;할 수 있습니다.&lt;br/&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;생성 완료된 서브넷을 확인해봅시다!&lt;br&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/ea554dda2f1463b524a3e1b0c4b77ffe/0beb1/2023-08-11-AWS-EC2-subnet-saved.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 6.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAABCAYAAADeko4lAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAVUlEQVR42g3KzQpAQBSAUe//FjbiMWxmR/nJjJKSZEl0zWSGj7M+kciFiBCCx/vAvKwopdiOHXedHHVBniWYNMZ2FWHQ3P9/AL9MWF3zjj3WNLi25AO1sUmEJYN2IAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 subnet saved&quot; title=&quot;&quot; src=&quot;/static/ea554dda2f1463b524a3e1b0c4b77ffe/f97d7/2023-08-11-AWS-EC2-subnet-saved.png&quot; srcset=&quot;/static/ea554dda2f1463b524a3e1b0c4b77ffe/0eb09/2023-08-11-AWS-EC2-subnet-saved.png 500w,
/static/ea554dda2f1463b524a3e1b0c4b77ffe/1263b/2023-08-11-AWS-EC2-subnet-saved.png 1000w,
/static/ea554dda2f1463b524a3e1b0c4b77ffe/f97d7/2023-08-11-AWS-EC2-subnet-saved.png 2000w,
/static/ea554dda2f1463b524a3e1b0c4b77ffe/0beb1/2023-08-11-AWS-EC2-subnet-saved.png 2202w&quot; sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;br/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;생성한 VPC(vpc-0dde~ = 192.168.0.0/16)의 서브넷임을 확인할 수 있습니다.&lt;/li&gt;
&lt;li&gt;서브넷 &lt;strong&gt;IPv4 CIDR 블록은 192.168.0.0/&lt;strong&gt;24&lt;/strong&gt;로, VPC의 대역폭보다 좁은 CIDR블록&lt;/strong&gt;이 잘 생성되어있음을 볼 수 있습니다.&lt;br/&gt;네트워크ID의 길이가 늘어남(16-&gt;24bit)에 따라 호스트ID의 수가 줄어들은 것이죠!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;가용영역(리전 내의 데이터 센터)도 우리가 설정한 ap-northeast-2a&lt;/strong&gt;로 잘 생성 된 것을 볼 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;넷째, 인스턴스 생성시, 네트워크&gt;서브넷에서 생성한 서브넷을 적용합니다.&lt;/h4&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 756px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/420e83f9e78fc499eb6db8ac1bbfbd3b/68c0a/2023-09-22-AWS-EC2-subnet-create.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 17.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA0klEQVR42m2QSQ6CQBREOQYhkUvBAt3KESDRw3AP9QZ6BxAHZIgogiBTU/5uh7jwJ5Xq6lS/dL4UXB/4N13fo+06dG/1lNkwgDH21UD5d3iWTnGC9WYDwzCgaRp0Xcd4MoG/80WJP/yUf2F80vSCYxDAdT3ExKnrGlIYRVgsVxipKhRFgSzLwqemidlsDtu2YVkWHMdBWVY4E+Sa3ZCRIoIEpxB5URCsQVlVkD6FvLiLy4I8u+XY7w/wtr5QkiQEyNA0LakRqrm3La2DVsFev+fnJy8wG7si2wtMAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 09 22 AWS EC2 subnet create&quot; title=&quot;&quot; src=&quot;/static/420e83f9e78fc499eb6db8ac1bbfbd3b/68c0a/2023-09-22-AWS-EC2-subnet-create.png&quot; srcset=&quot;/static/420e83f9e78fc499eb6db8ac1bbfbd3b/0eb09/2023-09-22-AWS-EC2-subnet-create.png 500w,
/static/420e83f9e78fc499eb6db8ac1bbfbd3b/68c0a/2023-09-22-AWS-EC2-subnet-create.png 756w&quot; sizes=&quot;(max-width: 756px) 100vw, 756px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;지금까지 가이드&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;루트 사용자의 리소스에 접근할 수 있는 가상의 신분으로(IAM) 로그인 했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 첫 번째 설정, 인스턴스의 운영체제와 사양을 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 두 번째 설정, 인스턴스의 VPC를 설정했습니다.
&lt;ol&gt;
&lt;li&gt;VPC 이해하기(1) : VPC는 인스턴스가 속한 가상 네트워크 망으로, 리전, IP주소 범위를 설정해야한다.&lt;/li&gt;
&lt;li&gt;VPC 이해하기(2) : VPC의 IP주소범위는 CIDR방식으로 나타낸다.&lt;/li&gt;
&lt;li&gt;VPC 생성하기 : 리전과 IP주소범위를 설정한 VPC를 생성한다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 시, 미리 생성된 VPC를 설정해 인스턴스가 속할 네트워크 망을 정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;인스턴스 생성 세 번째 설정, 인스턴스의 Subnet을 설정했습니다.
&lt;ol&gt;
&lt;li&gt;서브넷 이해하기(1) : 네트워크에서 서브넷은 큰 네트워크를 더 작은 네트워크로 분할한 것이다.&lt;/li&gt;
&lt;li&gt;서브넷 이해하기(2) : AWS에서 서브넷은 VPC의 IP주소 범위를 나눈 네트워크로, 실제 리소스가 배치된다.&lt;/li&gt;
&lt;li&gt;서브넷 생성하기 : VPC, 가용영역, IP주소 범위을 설정해, 서브넷을 생성한다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 시, 미리 생성된 서브넷을 설정해 인스턴스가 배치 될 네트워크 망을 정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;7. 보안그룹 이해하기&lt;/h2&gt;
&lt;h3&gt;7-(1).보안그룹은 인바운드 아웃바운드 규칙을 통해 EC2 인스턴스의 트래픽 규칙을 설정한다&lt;/h3&gt;
&lt;p&gt;어떤 서브넷(네트워크, IP주소범위)에 인스턴스를 생성할지 결정하고 나면, &lt;em&gt;인스턴스의 보안 규칙&lt;/em&gt; 을 설정해야하는데요. &lt;br/&gt;
&lt;strong&gt;보안 그룹&lt;/strong&gt;은 &lt;em&gt;&lt;strong&gt;연결된 리소스(인스턴스)에 도달하고 나갈 수 있는 트래픽을 제어하는 방화벽 규칙 세트&lt;/strong&gt;&lt;/em&gt; 라고 할 수 있습니다.&lt;br/&gt;
예를 들어, 보안 그룹을 EC2 인스턴스와 연결하면 인스턴스에 대한 인바운드(도달하는 트래픽) 및 아웃바운드(나가는 트래픽) 트래픽을 제어하는 것입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;인바운드 규칙&lt;/strong&gt;은 외부에서 - &gt; &lt;em&gt;인스턴스로 오는 트래픽에 관한 규칙&lt;/em&gt; 을 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;아웃바운드 규칙&lt;/strong&gt;은 인스턴스에서 - &gt; &lt;em&gt;외부로 가는 트래픽에 관한 규칙&lt;/em&gt; 을 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;인바운드, 아웃바운드 규칙을 통해 &lt;strong&gt;어떤 IP 주소, 포트 또는 프로토콜의 트래픽을 허용할지 또는 차단할지를 설정&lt;/strong&gt;하는 것입니다.
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h3&gt;7-(2).보안그룹은 AWS 리소스(EC2 등)에 직접 적용된다.&lt;/h3&gt;
&lt;p&gt;보안 그룹을 생성할 떄 어떤 VPC에 생성될지 지정함으로써, VPC에 여러 보안그룹을 생성하고 하나의 서브넷은 VPC에 생성된 여러 보안 그룹을 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;여러 보안 그룹을 사용하면 &lt;strong&gt;같은 VPC 내에서도 서로 다른 리소스 또는 그룹에 대해 다른 보안 규칙을 적용&lt;/strong&gt;할 수 있는데요. &lt;/br&gt;
예를 들어, &lt;strong&gt;같은 VPC 내에 웹 서버 그룹과 데이터베이스 서버 그룹&lt;/strong&gt;이 있다면, 웹 서버 그룹에는 웹 트래픽을 허용하는 규칙을 설정하고 데이터베이스 서버 그룹에는 특정한 IP 주소만 데이터베이스 접근을 허용하는 규칙을 설정할 수 있습니다.&lt;/br&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 481px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/ddbf59a9085fc33a2ff5537b6c900b6d/213b2/2023-08-11-AWS-EC2-securitygroup.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 75.05197505197505%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAAsTAAALEwEAmpwYAAACnUlEQVR42m2SS47cNhCG5zDxfbLIBbI34APlAga8CILJ7JzYsJ04PY+e6elWq6WW1HpSIiWRoviQRFJS1JkxDMQmasMqfvir/uLFD7+8eCyceZp7o3t9jk6pTivCOtdFmMp5nrtz3pxLWkmtaN/TnnLFL4ImFHq49C+jNmSK4R63qlWzorR/+KNxwvQq+h13uBkaMjRci8EMyoxq1FLLC6GkUP2rP19dOb89nD6tgve79C5HXoOhqob16frl5c83p0934V833rsj3EFRLPp6NGeYDVyP0yKIyqCkANU+TO9B5TNvb6KwD5yqChHYIXgom6xAfiHyfpGVihD+DJcSAugClsFsjYIPRe2yjplBi05AEsD4H5je5LUHyv9go+MDc+/IM4wkXFoFNNs6q93D2yM4CNObce5G7SN/+/jOsv9O6qgogydl1gx19UUZdzAvj6BwvHh3jKxTYjFaGS57RkB+8CLLi60ss3Pk5jxbbP86sxrnfVmlFFec1KLBgtCukb0cjSFC7CFa8liSp2rbt4MZz7A6u80X+qc3+1/t+jZtV2m7KQSgAx3MsuFVxn58vV2l7A7wz3FjlxIJ05lJP63qrGxmn/ZhjpOCxAAlUZSAklqWcQ+tZR3DIllOmsU5jguc8uWrjF9hPc6oMwlcCiOIHGBfZ7HfSmUEF1wWhAJnDdx1VpOkbDOu/g/nQiewOWHphJF///Ho7LCax2kSevLK1rW3/uazF0bfhzOuM9T4QbK6tzd2cLNxCoAmKTlt1zvveuNsD8Hm0UlAlXzb9uIBYNqt+qTVGdNCTWpcXkyDHjs95dx4WMVUg1ZzPS0ePcMnGha8qTvmE3KbV1tU2RWGol0C8DLntJJsX9W3ebkr6yMmyzXnTc4LNrT/AilaOCY+aZRmAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 securitygroup&quot; title=&quot;&quot; src=&quot;/static/ddbf59a9085fc33a2ff5537b6c900b6d/213b2/2023-08-11-AWS-EC2-securitygroup.png&quot; srcset=&quot;/static/ddbf59a9085fc33a2ff5537b6c900b6d/213b2/2023-08-11-AWS-EC2-securitygroup.png 481w&quot; sizes=&quot;(max-width: 481px) 100vw, 481px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;이렇게 &lt;strong&gt;여러 보안 그룹을 사용함으로써, 네트워크 리소스를 보다 세부적으로 제어하고 보호&lt;/strong&gt;할 수 있는 것이죠.&lt;br/&gt;&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;7-(3). 보안그룹 생성하기 및 인스턴스 적용하기&lt;/h3&gt;
&lt;p&gt;아래와 같이, VPC(vpc-0dde~)에 보안그룹을 생성했습니다!&lt;br&gt;&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1178px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/9e8a4eecc4522516e0061e822c060e23/dc992/2023-08-11-AWS-EC2-sg-create.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 31%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABHklEQVR42k1Qy26DQAzkUxFXfiu9RuIcPqJJLyUQItFWVQqBXZblsUCmtpNUHWmE0Y7HHntpliE5pthsXhAEAcIwhO/72O12+Pj8QpKmeE+O2B/ekJ1Oon3dH1BWFWzfozUGputwrWvUjYJ3ufwgP58xDAOGcRRObsK8zFjXFbcbME7TH0VHnKie51lIEgH3em3boq4beXiCJ1bVFUVR4ERbOefQWSsDGDeeQjZat9DGIs9z0VnSeGzWNA2U0vRVtNkiUbiO4xjb7RY9/XMcTcO11sKVdG1n8V0pRFEkupp8PG7mjZ4RGFwbEt83ucPaXu7EcRcy+//2BPd7jVIoy0rWXx6ReAAf3ZHAPW5kTCdJ2Ii5MknP5qzhZJNz+AXOc8SZnoisnwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 sg create&quot; title=&quot;&quot; src=&quot;/static/9e8a4eecc4522516e0061e822c060e23/dc992/2023-08-11-AWS-EC2-sg-create.png&quot; srcset=&quot;/static/9e8a4eecc4522516e0061e822c060e23/0eb09/2023-08-11-AWS-EC2-sg-create.png 500w,
/static/9e8a4eecc4522516e0061e822c060e23/1263b/2023-08-11-AWS-EC2-sg-create.png 1000w,
/static/9e8a4eecc4522516e0061e822c060e23/dc992/2023-08-11-AWS-EC2-sg-create.png 1178w&quot; sizes=&quot;(max-width: 1178px) 100vw, 1178px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br&gt;
인스턴스 생성 시, 인스턴스의 네트워크 설정&amp;gt;방화벽(보안그룹)에서 생성한 보안그룹(MY_VPC_SECURITY_GROUP)를 적용합니다.&lt;br&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 808px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2562311221188e635ef5e3b8b00ca3bb/4a1c5/2023-09-22-AWS-EC2-securitygroup.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 34%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABUElEQVR42n1RO0/DMBjsX0RIPNSBhQmJjY2BH8DEwl+omKhYmBGdmBApUFVtBWpJnNK0Tf1MnOT47LRCZeCkk/Ow7+47N97eexgORxhNpuBKQ0oJ5VcFrQ2yLCPm0KZ+NrRqrWlP/b+mhrUWDo3ZLEHEYoRRBEFiXEgvxoWAJOHXryX645jI0PsIMUk4ptyApQoxUZGgM7G2qAUFCSzSlReqqgrlmkVReNd4lWHOFRbCYMEpeV5C09kNywpbaDDGMI5iCJOjLKwXcsKOW3Dv7ntZehZkthlzW/A7QbJMfSqHnKJLnSGMpwiCLl6CAP3BAIZ6dGNZMnSmxveZ+Q5dGJmtRx6ECR6ennF8cor95hEur67BeI6bu3vsHjSxs3eIs/MLzFV94E9uf1Hc2F/BJOUYfY7Rvm2j1WrhsdOhrhSWnC7HO1soSpdQz2Ld83/4AXwLEaJ5FBYFAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 09 22 AWS EC2 securitygroup&quot; title=&quot;&quot; src=&quot;/static/2562311221188e635ef5e3b8b00ca3bb/4a1c5/2023-09-22-AWS-EC2-securitygroup.png&quot; srcset=&quot;/static/2562311221188e635ef5e3b8b00ca3bb/0eb09/2023-09-22-AWS-EC2-securitygroup.png 500w,
/static/2562311221188e635ef5e3b8b00ca3bb/4a1c5/2023-09-22-AWS-EC2-securitygroup.png 808w&quot; sizes=&quot;(max-width: 808px) 100vw, 808px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;지금까지 가이드&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;루트 사용자의 리소스에 접근할 수 있는 가상의 신분으로(IAM) 로그인 했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 첫 번째 설정, 인스턴스의 운영체제와 사양을 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 두 번째 설정, 인스턴스의 VPC를 설정했습니다.
&lt;ol&gt;
&lt;li&gt;VPC 이해하기(1) : VPC는 인스턴스가 속한 가상 네트워크 망으로, 리전, IP주소 범위를 설정해야한다.&lt;/li&gt;
&lt;li&gt;VPC 이해하기(2) : VPC의 IP주소범위는 CIDR방식으로 나타낸다.&lt;/li&gt;
&lt;li&gt;VPC 생성하기 : 리전과 IP주소범위를 설정한 VPC를 생성한다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 시, 미리 생성된 VPC를 설정해 인스턴스가 속할 네트워크 망을 정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;인스턴스 생성 세 번째 설정, 인스턴스의 Subnet을 설정했습니다.
&lt;ol&gt;
&lt;li&gt;서브넷 이해하기(1) : 네트워크에서 서브넷은 큰 네트워크를 더 작은 네트워크로 분할한 것이다.&lt;/li&gt;
&lt;li&gt;서브넷 이해하기(2) : AWS에서 서브넷은 VPC의 IP주소 범위를 나눈 네트워크로, 실제 리소스가 배치된다.&lt;/li&gt;
&lt;li&gt;서브넷 생성하기 : VPC, 가용영역, IP주소 범위을 설정해, 서브넷을 생성한다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 시, 미리 생성된 서브넷을 설정해 인스턴스가 배치 될 네트워크 망을 정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;인스턴스 생성 네 번째 설정, 인스턴스의 보안그룹을 설정했습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;8. EC2 생성 완료하기&lt;/h2&gt;
&lt;h3&gt;public IP 활성화 vs 비활성화&lt;/h3&gt;
&lt;p&gt;인스턴스의 마지막 네트워크 설정입니다. 바로 인스턴스에 퍼블릭 IP 주소를 줄 것인가 인데요.&lt;/p&gt;
&lt;p&gt;퍼블릭 IP 주소란 외부 인터넷과 통신할 수 있는 공인 IP 주소입니다. EC2 인스턴스가 퍼블릭 IP 주소를 갖게 되면, 이 주소를 사용하여 외부에서 인스턴스에 접근할 수 있게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“퍼블릭 IP 주소 활성화”&lt;/strong&gt; 옵션을 선택하면 &lt;strong&gt;&lt;em&gt;해당 리전(Region)의 AWS에서 관리하는 퍼블릭IP 주소 풀에서&lt;/em&gt; 하나의 퍼블릭 IP 주소를 할당&lt;/strong&gt;받는데요. 이 주소는 EC2 인스턴스와 통신하는 외부 클라이언트들이 사용할 수 있게 됩니다. &lt;strong&gt;주의해야 할 점은 퍼블릭 IP 주소는 고정되지 않고 인스턴스를 중지하고 다시 시작하면 변경될 수 있다&lt;/strong&gt;는 것입니다.&lt;/p&gt;
&lt;p&gt;[퍼블릭 인스턴스가 생성된 예시]&lt;/br&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1906px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/f6b0c5842550891f0884437213209c58/bab36/2023-08-11-AWS-EC2-public-instance.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 14.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAqUlEQVR42m1NywqDMBD08736CT0UeqhKBYMQP6TS3opipZrU+IgmOk0C7akDwyy7OzNeEATwfR9pStB1DGVVoTKsnw3EMGFd17+c5hnvvocQAh3jRgc3e+cwRBRFLkQphW3bYGENeZ4jjmMUt7vbfW+MMefJsgzLsrgCq0ppeK+2BeccUs7QWv9MoxzRtA0G0yzNs8W+704fZY3D8YQkuYBS6ooJIbgWBT5Yxdwj+YqeUAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 public instance&quot; title=&quot;&quot; src=&quot;/static/f6b0c5842550891f0884437213209c58/bab36/2023-08-11-AWS-EC2-public-instance.png&quot; srcset=&quot;/static/f6b0c5842550891f0884437213209c58/0eb09/2023-08-11-AWS-EC2-public-instance.png 500w,
/static/f6b0c5842550891f0884437213209c58/1263b/2023-08-11-AWS-EC2-public-instance.png 1000w,
/static/f6b0c5842550891f0884437213209c58/bab36/2023-08-11-AWS-EC2-public-instance.png 1906w&quot; sizes=&quot;(max-width: 1906px) 100vw, 1906px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;반면, &lt;strong&gt;“퍼블릭 IP 주소 비활성화”&lt;/strong&gt; 옵션을 선택하면, &lt;strong&gt;서브넷의 IP주소 범위 내에서 사설IP주소가 자동으로 할당&lt;/strong&gt;됩니다. 퍼블릭IP주소가 없기 때문에, 해당 인스턴스는 인터넷에서 또는 인터넷으로 직접 접근을 할 수 없는데요.&lt;/p&gt;
&lt;p&gt;다만, &lt;strong&gt;같은 VPC(내부 네트워크)에서는 &lt;em&gt;프라이빗 IP 주소를 통해&lt;/em&gt; 해당 인스턴스에 접근&lt;/strong&gt; 할 수 있습니다. 즉, &lt;strong&gt;프라이빗 IP를 가진 인스턴스는 VPC 내의 리소스 간에 내부 네트워크 통신을 위해 사용&lt;/strong&gt;됩니다&lt;/p&gt;
&lt;p&gt;[프라이빗 인스턴스가 생성된 예시]&lt;/br&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1924px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/609847f85331193f0c9d686f3501016d/2f33f/2023-08-11-AWS-EC2-private-instance.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 12.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAkUlEQVR42m2PCwqCUBBFXXttw3ZibqCC3EEaGqaJ9jLf15NPIQI7cBkY7p1PsNluiaI9TdNwSVOy7EpR3CjrB1JptF5LKYW1Fm0M4tXTdU+qyd+2HUEcxwghGIaBcRzx+Nr37ymoWbN4DscTYbjjnCTfjFeQ5zlSynmjc25uWme5tyXG/Bu4hKu6Jp0+8sf88gFoD+UyCyD0ggAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 private instance&quot; title=&quot;&quot; src=&quot;/static/609847f85331193f0c9d686f3501016d/2f33f/2023-08-11-AWS-EC2-private-instance.png&quot; srcset=&quot;/static/609847f85331193f0c9d686f3501016d/0eb09/2023-08-11-AWS-EC2-private-instance.png 500w,
/static/609847f85331193f0c9d686f3501016d/1263b/2023-08-11-AWS-EC2-private-instance.png 1000w,
/static/609847f85331193f0c9d686f3501016d/2f33f/2023-08-11-AWS-EC2-private-instance.png 1924w&quot; sizes=&quot;(max-width: 1924px) 100vw, 1924px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;지금까지 가이드&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;루트 사용자의 리소스에 접근할 수 있는 가상의 신분으로(IAM) 로그인 했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 첫 번째 설정, 인스턴스의 운영체제와 사양을 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 두 번째 설정, 인스턴스의 VPC를 설정했습니다.
&lt;ol&gt;
&lt;li&gt;VPC 이해하기(1) : VPC는 인스턴스가 속한 가상 네트워크 망으로, 리전, IP주소 범위를 설정해야한다.&lt;/li&gt;
&lt;li&gt;VPC 이해하기(2) : VPC의 IP주소범위는 CIDR방식으로 나타낸다.&lt;/li&gt;
&lt;li&gt;VPC 생성하기 : 리전과 IP주소범위를 설정한 VPC를 생성한다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 시, 미리 생성된 VPC를 설정해 인스턴스가 속할 네트워크 망을 정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;인스턴스 생성 세 번째 설정, 인스턴스의 Subnet을 설정했습니다.
&lt;ol&gt;
&lt;li&gt;서브넷 이해하기(1) : 네트워크에서 서브넷은 큰 네트워크를 더 작은 네트워크로 분할한 것이다.&lt;/li&gt;
&lt;li&gt;서브넷 이해하기(2) : AWS에서 서브넷은 VPC의 IP주소 범위를 나눈 네트워크로, 실제 리소스가 배치된다.&lt;/li&gt;
&lt;li&gt;서브넷 생성하기 : VPC, 가용영역, IP주소 범위을 설정해, 서브넷을 생성한다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 시, 미리 생성된 서브넷을 설정해 인스턴스가 배치 될 네트워크 망을 정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;인스턴스 생성 네 번째 설정, 인스턴스의 보안그룹을 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 다섯 번째 설정, 인스턴스의 PUBLIC IP주소 활성화 옵션을 설정했습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h3&gt;EC2 생성 이후, 새로운 의문점&lt;/h3&gt;
&lt;p&gt;인스턴스의 스토리지 용량까지 결정하고 나면 인스턴스 생성이 완료됩니다. &lt;/br&gt;
이렇게 생성된 인스턴스를 사용하면서 아래와 같은 궁금점이 들지 않으셨나요?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 같은 VPC 내의 서브넷들 사이에서 통신(= 각 서브넷에 배치된 인스턴스 간 통신)은 왜 프라이빗 IP주소로 실행되는 걸까요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. VPC는 나만의, 내가 설정한 가상 네트워킹 환경(VPC)입니다. &lt;br&gt; VPC는 기본적으로 private 환경인데, 어떻게 인스턴스들이 인터넷에 접속할 수 있는 걸까요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;9. EC2 인스턴스와 인터넷의 통신 이해하기&lt;/h2&gt;
&lt;p&gt;설명에 앞서 결론부터 말하자면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;첫째, VPC에 &lt;strong&gt;인터넷 게이트웨이&lt;/strong&gt;라는 것을 생성해, &lt;strong&gt;VPC 내의 리소스가 인터넷에 액세스할 수 있게 됩니다&lt;/strong&gt;.&lt;br/&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;둘째, &lt;strong&gt;서브넷에 연결된 라우팅 테이블&lt;/strong&gt;에 &lt;strong&gt;“0.0.0.0/0” 목적지를 인터넷 게이트웨이로 설정&lt;/strong&gt;합니다.&lt;/p&gt;
&lt;p&gt;여기서, “0.0.0.0/0”은 모든 IP 주소로 아웃바운드 트래픽에 대한 설정이라고 이해하면 됩니다. &lt;/br&gt;
즉, &lt;strong&gt;모든 IP 주소를 목적지로 하는 트래픽을 인터넷 게이트웨이를 통해 라우팅하도록 설정&lt;/strong&gt;하는 것을 의미합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;셋째, 이로써 &lt;strong&gt;&lt;em&gt;퍼블릭 서브넷&lt;/em&gt; 내의 모든 &lt;em&gt;“퍼블릭 인스턴스”&lt;/em&gt; 들이 외부 인터넷과 통신할 수 있게 되는 것&lt;/strong&gt;입니다.&lt;br/&gt;
아래 사진을 보면, 두 개의 서브넷에 연결된 각각의 EC2들은 &lt;strong&gt;자신이 소속된 서브넷이 아닌 다른 네트워크로 요쳥을 보낼 때&lt;/strong&gt;,&lt;br/&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 874px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/a749f3a8d9693b747d9671956d551734/7bff9/2023-08-11-AWS-EC2-internet.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 44.60000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB8ElEQVR42oWSSW/TUBSF/fvYsUL8AXYsWLFBSPwIJLZAEVUjpCARVKWkaUmcoWkGN2lCBtuJ7XhoM7jheXq23+E1DYgFqFf6pCs93fOO7rlCvPeIBpkn1HMGlF516UKp0flQpHTRp4HTpZ59ecfapClAcQ8CGR4jlt4h1ou4litwxmWs1BL64heM6jlgWQWuWwjnZ4hpiH8VY5xdLwRrjQ80sFSqEFt9XBo30CpZKG+fYTJVEU6/wZ83QO0motD/LcFJuRDbkia8T9lOcCGDmSICvQJ12MRFp4p+LQf97BM2loRUK8CTi0iNMmxtBNe9QchnO6sEJLxznL/o4tVBFu4m4IJLBalVB7NFwD4BjALvy0ivatjMTuFXXiPOPQfNPsWy+AYbn6KpOXj4oYHSQN8Kvtz/COHBY+Tro1tBFSl3SI0StFEDuixhKVfROMxg3DziHxzj5+ArwkkekWttBTziot5T4EcJQu7yqFjC+8xn9CZTCN5CYbBE5hl1Vj6XWGe2Zur3A6buv2CGPmL+7JQFdptRp8OiMGC7Bf4hSRIWRSFLk5iFgc+EYDUDsyrcZRkLHsxYKkBuH8LsF3iyfA1ObZtyZJ3/N+W/S6CRT4jzg3hmm/jzJgnMW1oksFvENyXCb5C/9YjvGoTfIcE9/AJ27pTFJNJuBwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 internet&quot; title=&quot;&quot; src=&quot;/static/a749f3a8d9693b747d9671956d551734/7bff9/2023-08-11-AWS-EC2-internet.png&quot; srcset=&quot;/static/a749f3a8d9693b747d9671956d551734/0eb09/2023-08-11-AWS-EC2-internet.png 500w,
/static/a749f3a8d9693b747d9671956d551734/7bff9/2023-08-11-AWS-EC2-internet.png 874w&quot; sizes=&quot;(max-width: 874px) 100vw, 874px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt; 1) VPC 라우터의 라우팅 테이블에 적힌 규칙에 따라 트래픽을 특정경로로 이동시키는데, &lt;br/&gt; 2) VPC 내의 서브넷 끼리는 내부적으로 통신하고&lt;br/&gt; 3) VPC 외부와의 통신은 인터넷 게이트웨이를 통해 인터넷과 연결합니다.&lt;br/&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;퍼블릭 IP만 있으면 되는 것 아닌가? 퍼블릭 서브넷은 뭐고, 라우팅 테이블, 인터넷 게이트웨이는 뭘까? 하는 생각이 들 것입니다.&lt;/p&gt;
&lt;p&gt;다음 장을 읽으며 퍼블릭 서브넷의 개념과 라우팅 테이블, 게이트웨이에 대해 이해해 봅시다. &lt;br/&gt;
&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;10. 퍼블릭 서브넷 vs 프라이빗 서브넷&lt;/h2&gt;
&lt;p&gt;처음 서브넷을 생성할 때, 프라이빗 서브넷인지 퍼블릿 서브넷인지 설정하는 칸이 없지 않았나요?
따라서, &lt;strong&gt;처음 서브넷을 생성하면 모두 프라이빗 서브넷이 됩니다&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;프라이빗 서브넷이란&lt;/strong&gt;,&lt;/br&gt;
생성되는 인스턴스들이 외부와 통신할 수 없는 서브넷을 말합니다.&lt;/br&gt;
프라이빗 서브넷의 트래픽 규칙을 적어둔 라우팅 테이블에는 “0.0.0.0”, 모든 IP주소에 대한 인터넷 게이트웨이로의 라우팅 규칙이 없습니다.
즉, 해당 서브넷 내의 인스턴스들은 직접적으로 인터넷에 접근할 수 없게 됩니다.&lt;/br&gt;
이로 인해 프라이빗 서브넷 내에 생성되는 인스턴스들은 기본적으로 퍼블릭 IP주소를 가질 수 없습니다.
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;퍼블릭 서브넷이란,&lt;/strong&gt;
생성되는 인스턴스 퍼블릭 IP주소를 가질 수 있고, 퍼블릭 IP주소를 통해 외부 인터넷과 직접 통신할 수 있는 서브넷을 말합니다. &lt;/br&gt;&lt;/p&gt;
&lt;p&gt;서브넷의 트래픽 규칙을 적어둔 라우팅 테이블에 “0.0.0.0”, 모든 IP주소를 목적지로 하는 트래픽을 인터넷 게이트웨이로 라우팅하는 설정을하면, 퍼블릭 서브넷이 됩니다.&lt;/p&gt;
&lt;p&gt;퍼블릭 서브넷에서 인스턴스를 생성하면 해당 인스턴스에는 퍼블릭 IP 주소를 할당할 수 있게 되는 것입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;지금까지 가이드&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;[ 인스턴스 생성 ]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;루트 사용자의 리소스에 접근할 수 있는 가상의 신분으로(IAM) 로그인 했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 첫 번째 설정, 인스턴스의 운영체제와 사양을 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 두 번째 설정, 인스턴스의 VPC를 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 세 번째 설정, 인스턴스의 Subnet을 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 네 번째 설정, 인스턴스의 보안그룹을 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 다섯 번째 설정, 인스턴스의 PUBLIC IP주소 활성화 옵션을 설정했습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;[인스턴스의 인터넷 통신]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;프라이빗 서브넷에 연결된 라우팅 테이블에 인터넷 게이트웨이로 라우팅하는 트래픽 규칙을 더해, 퍼블릭 서브넷으로 만든다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;11. 라우팅 테이블이란?&lt;/h2&gt;
&lt;p&gt;서브넷은 &lt;strong&gt;서로 다른 네트워크 영역&lt;/strong&gt;을 가지고 있습니다.&lt;/br&gt;
따라서, 한 서브넷에서 다른 서브넷으로 데이터를 전송하려면( = 다른 네트워크로 데이터를 전송하려면) 라우팅을 해야합니다.&lt;/p&gt;
&lt;p&gt;(*라우팅이란, &lt;strong&gt;네트워크에서 데이터를 출발지에서 목적지로 이동시키는 것&lt;/strong&gt;을 말합니다.)
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;즉, &lt;strong&gt;VPC 안에서 네트워크 요청이 발생하면, 어떤 네트워크 경로를 통해 트래픽을 전송할지 결정&lt;/strong&gt;해야하는데요.&lt;/p&gt;
&lt;p&gt;라우팅 테이블에는 네트워크 내에서 발생하는 트래픽의 이동 경로 규칙이 적혀져 있습니다.
&lt;br&gt;다시말해, 목적지의 IP 주소 범위에 따라 어느 인터페이스로 데이터를 보낼 지에 대한 정보가 저장되있습니다. 일종의 표지판 역할을 하는 것이지요.&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h3&gt;라우팅 테이블 편집하기&lt;/h3&gt;
&lt;p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1176px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/9ad87c8b57cc3223b1bb83d08479484b/08188/2023-08-11-AWS-EC2-rt-add.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 25.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAwUlEQVR42m1PyxLCIAzsDzv6bXqp/od6UW/qpbWFAukDWAOWyqGZ2Ukm2ewmxe3+wPlyxfP1htIaQkrIrkOnVMxryGdhJ58V2hg45+C8h+Vs55r6Hj0jzmZ4xjhNUSRxm7aNvcQpFLsJIeIwj8ladlRMtjDUYxhGOOth2ESYduGF/WjGRwQU4ZJP00SX5BrFDKFShH15xGa7w6E8YWCBWoWP7CJAbBayIeKa+GVtUFU1LIvk8V8gSH4rvJ/6a/HjA1+j6oNzy7l1vgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 rt add&quot; title=&quot;&quot; src=&quot;/static/9ad87c8b57cc3223b1bb83d08479484b/08188/2023-08-11-AWS-EC2-rt-add.png&quot; srcset=&quot;/static/9ad87c8b57cc3223b1bb83d08479484b/0eb09/2023-08-11-AWS-EC2-rt-add.png 500w,
/static/9ad87c8b57cc3223b1bb83d08479484b/1263b/2023-08-11-AWS-EC2-rt-add.png 1000w,
/static/9ad87c8b57cc3223b1bb83d08479484b/08188/2023-08-11-AWS-EC2-rt-add.png 1176w&quot; sizes=&quot;(max-width: 1176px) 100vw, 1176px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt; &lt;br&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;서브넷의 라우팅 테이블에 목적지를 0.0.0.0/0로하는 트래픽에 대한 인터넷 게이트웨이로 라우팅하겠다는 규칙을 추가합니다.&lt;/li&gt;
&lt;li&gt;이를 통해, 서브넷 인스턴스에서 발생한 트래픽 중 특정 목적지로의 트래픽을 특정 경로로 전송할 수 있게 되는 겁니다.&lt;br/&gt;&lt;/li&gt;
&lt;li&gt;이 규칙을 적용함으로써 프라이빗 서브넷이 퍼블릭 서브넷이 됩니다.
&lt;br/&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;서브넷의 라우팅 테이블 살펴보기&lt;/h3&gt;
&lt;p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 998px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/253ebeca214c2c95099749b7ac1660e0/e0a8c/2023-08-11-AWS-EC2-rt-table.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 37.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABcklEQVR42k2QS2/TUBBG/U8TqWzzi6AsERJlUXZZtDuqEtRWVR5FcQOxIUkdP65f1+/kMHbSKouj+TQzd+43Y6xWa5ZLi+FwSK/X493ZGf1+n8FgwHg84fefJbOnX5imifm8YDqdEUYRWZahtX4jCBRxkmD4YcLKVbiei+N6qDAU7RNIXG9e8PwApRSJNMenxPGbTtK0q6cy2FAqxLbtriFtixI72gZpPHXxStYiDrOjDmWGJya05AxdlvzbusR5Sb2HhgM7oRaqI/VJvmgadFl1uaLZyduCrX/YwiifHqmmD5TT+45qdtDF+I79YgbWHGyTnejqWM+lVkxO9OOIxlpQVDVG9vOGSO61zzOaTFNnKXm7fhRzPxrx7esFlxdfePhxK1ZlC52iPAcdBezkTRIEhM4Lu4kYqSqM/O4Gy5wT/LWoI0UTheS+h7d2+H51zeeP53z68J7b6yvQCXv5bLOes908I0cmdjZEK5um3bCq+A9m4QXs0s0duwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 rt table&quot; title=&quot;&quot; src=&quot;/static/253ebeca214c2c95099749b7ac1660e0/e0a8c/2023-08-11-AWS-EC2-rt-table.png&quot; srcset=&quot;/static/253ebeca214c2c95099749b7ac1660e0/0eb09/2023-08-11-AWS-EC2-rt-table.png 500w,
/static/253ebeca214c2c95099749b7ac1660e0/e0a8c/2023-08-11-AWS-EC2-rt-table.png 998w&quot; sizes=&quot;(max-width: 998px) 100vw, 998px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt; &lt;br/&gt;
이 라우팅 테이블의 규칙들을 해석하자면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;첫째 줄은, 대상(Destination)이 “0.0.0.0/0” 에 대해서 대상(Target, 트래픽을 전송할 때 사용할 게이트웨이, 네트워크 인터페이스)을 인터넷 게이트웨이로 설정한다는 말인데요.&lt;br/&gt;
서브넷에서 &lt;strong&gt;모든 퍼블릭 IP주소를 대상(Destination)으로 하는 통신에 대해 인터넷 게이트웨이라는 특정 경로로 라우팅하게 하는 것&lt;/strong&gt; 입니다. &lt;br/&gt;
이로써 VPC 내의 모든 퍼블릭 IP를 가진 인스턴스가 외부 인터넷과 통신할 수 있게 됩니다.&lt;br/&gt;&lt;/li&gt;
&lt;li&gt;둘째 줄은, &lt;strong&gt;VPC 내부 IP 주소 범위(192.168.0.0/16) 내의 리소스를 목적지로하는 모든 트래픽에 대해서 로컬 라우팅이 자동으로 설정&lt;/strong&gt;된 것인데요.&lt;br/&gt;
로컬 라우팅이란, VPC 내부 통신이 발생했을 때, 트래픽이 외부로 나가지 않고 네트워크 내부에서 트래픽을 라우팅하는 것을 의미합니다.&lt;br/&gt;
모든 서브넷에 동일한 로컬 라우팅이 적용됨으로써, VPC내의 자유로운 내부통신이 가능해진 것이죠!&lt;br/&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;특정 IP 대역을 대상(Destination)으로 설정한 것에 &lt;strong&gt;우선순위&lt;/strong&gt;가 있으므로,&lt;br/&gt;
&lt;strong&gt;로컬 라우팅에 의해 우리가 같은 VPC 내의 서브넷들 사이에서 통신(= 각 서브넷에 배치된 인스턴스 간 통신)은 왜 프라이빗 IP주소로 실행되게 됩니다.&lt;/strong&gt;&lt;/p&gt;
&lt;br&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;지금까지 가이드&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;[ 인스턴스 생성 ]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;루트 사용자의 리소스에 접근할 수 있는 가상의 신분으로(IAM) 로그인 했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 첫 번째 설정, 인스턴스의 운영체제와 사양을 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 두 번째 설정, 인스턴스의 VPC를 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 세 번째 설정, 인스턴스의 Subnet을 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 네 번째 설정, 인스턴스의 보안그룹을 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 다섯 번째 설정, 인스턴스의 PUBLIC IP주소 활성화 옵션을 설정했습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;[인스턴스의 인터넷 통신]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;프라이빗 서브넷에 연결된 라우팅 테이블에 인터넷 게이트웨이로 라우팅하는 트래픽 규칙을 더해, 퍼블릭 서브넷으로 만든다.&lt;/li&gt;
&lt;li&gt;서브넷에는 서브넷에서 발생하는 트래픽 경로 규칙이 담긴 라우팅 테이블이 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;12. 인터넷 게이트웨이란?&lt;/h2&gt;
&lt;p&gt;VPC는 나만의, 내가 설정한 가상 네트워킹 환경(VPC)입니다.
VPC는 기본적으로 private한 환경인데, 어떻게 인스턴스들이 인터넷에 접속할 수 있는 걸까요?&lt;br/&gt;
바로, &lt;strong&gt;라우팅 테이블에 규칙으로 설정해 놓은 “모든 IP주소에 대해서 인터넷 게이트 웨이로 연결한다”는 설정 때문입니다.&lt;/strong&gt; &lt;br/&gt;
&lt;br/&gt;
인터넷 게이트 웨이란, VPC에서 호스팅하는 리소스와 &lt;strong&gt;인터넷 간의 통신을 가능하게 해주는 AWS 컴포넌트&lt;/strong&gt;인데요.
&lt;strong&gt;VPC의 리소스와 인터넷 간의 통신을 활성화&lt;/strong&gt;하기 &lt;strong&gt;VPC에 연결된 통로&lt;/strong&gt;라고 할 수 있습니다.&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/e80fd0205cde6156733adb2fe044be5a/663f3/2023-08-11-AWS-EC2-gateway.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 84%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAIAAABSJhvpAAAACXBIWXMAAAsTAAALEwEAmpwYAAACV0lEQVR42n2TO4/TQBDH/an4TnQ0lNAhQYFEg3QFUPBo0AFXQMHjQIegQIouToDEuTzOcWwnthPb+5y1vWszSQCdOJLRyBqP9+/ZnfmtZYCZo7v1fKSbxujKaP3Xt9bsNktEfvn8dvn6jrBfUS4po5SxrXMhKKV7xTSnUKWnb8TJgWREkIwzJilhq0RxKigxxuwUV4Khvnc29eNclyVIWQWjiq6gUFLwSrK63i02BYRh2O3awzOn7wwmriuuXYFPz/KmARQrsU9cgcjSVEooikIIQSiNhj+zRZCSDAA0Vja/e1bX9SWxEnmelWUppPzR7X7v2IxzXJWQTKBYyXpjVVX9p3JBM7qMNDCtRBl5xbRfJmEd9uDtLRr7zed7zdye+PHjJ0/7jvOvWJEUaOqOJ0kYNqYwSmAJ6vXCw5vL4Dw6vMGck6+tzoOHj168PAJQF/ePDZNRFLVa7dFo7AfBLAjK+1fLzseVrhkhi2WaMWF2oGLpAhbzuedN4zheLlfRIooPrqetd7Ss1mOTTOsKSyFxuAZtPJ4whkmNSUuDyLMM+6GU8mazyXS24qLSBtminBecUkJwa+3T9vH7D4PBwPd9HGnH7hBCLPw3yVJAICgJz0fBaCBXsS4UIiI5x1HhwEhOzgPXHtrJMuGMewuv7zo4VwsYoWlSF4Dutr8NvxxHTrfRZSEY0Eyu8Vy3p25q84cWDLaxtQVDG6Nr48783tlwHif4QYLC3goh97GN1wBA4skYZyTPOnY7jiOMMYPk4MEvg3UBkg2VsDEpJcb43L6u8xL2XMlf4HDBR2F4znkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 gateway&quot; title=&quot;&quot; src=&quot;/static/e80fd0205cde6156733adb2fe044be5a/663f3/2023-08-11-AWS-EC2-gateway.png&quot; srcset=&quot;/static/e80fd0205cde6156733adb2fe044be5a/0eb09/2023-08-11-AWS-EC2-gateway.png 500w,
/static/e80fd0205cde6156733adb2fe044be5a/663f3/2023-08-11-AWS-EC2-gateway.png 650w&quot; sizes=&quot;(max-width: 650px) 100vw, 650px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;위의 그림 처럼 VPC 내부의 리소스(인스턴스 등)는 VPC에 연결된 인터넷 게이트웨이를 통해 외부 인터넷과 통신할 수 있는데요.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이때, 리소스는 퍼블릭 IP주소를 가지고 있어야 합니다. 인터넷 상에서 퍼블릭 IP주소가 고유한 식별자 역할을 해주기 때문이죠.&lt;/li&gt;
&lt;li&gt;또, 리소스(인스턴스)가 배치된 서브넷의 라우팅 테이블에 ‘0.0.0.0/0’ 목적지는 인터넷 게이트 웨이로 라우팅한다는 설정이 있어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;지금까지 가이드&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;[ 인스턴스 생성 ]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;루트 사용자의 리소스에 접근할 수 있는 가상의 신분으로(IAM) 로그인 했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 첫 번째 설정, 인스턴스의 운영체제와 사양을 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 두 번째 설정, 인스턴스의 VPC를 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 세 번째 설정, 인스턴스의 Subnet을 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 네 번째 설정, 인스턴스의 보안그룹을 설정했습니다.&lt;/li&gt;
&lt;li&gt;인스턴스 생성 다섯 번째 설정, 인스턴스의 PUBLIC IP주소 활성화 옵션을 설정했습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;[인스턴스의 인터넷 통신]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;프라이빗 서브넷에 연결된 라우팅 테이블에 인터넷 게이트웨이로 라우팅하는 트래픽 규칙을 더해, 퍼블릭 서브넷으로 만든다.&lt;/li&gt;
&lt;li&gt;서브넷에는 서브넷에서 발생하는 트래픽 경로 규칙이 담긴 라우팅 테이블이 있다.&lt;/li&gt;
&lt;li&gt;인터넷 게이트웨이란 인스턴스와 인터넷 간의 통신을 연결해주는 통로이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;13.인스턴스의 네트워크 연결 총 정리하기&lt;/h2&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/0d3e5fb25f5211e94ea99668b080a6d1/a96d7/2023-08-11-AWS-EC2-vpc-map.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 24.400000000000002%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAABBUlEQVR42lWPS26DMBRF2f8KKnXSPXTUQRfApJ0mEWokSsCEmOBv+AROH6gZxPbVk659P06MC7g4wLLIWRjGAWMsznjh2Lhxmgg+oMoaK3eEBaccVVbRnC+EEFjXPM8kzntiPzCJaMUwjrjcYDLDMkiI7FE4awO/O0Xzo5nbmezrSPqRcqlbOmvo+577/U7ixT3EuKWEGLj5G925pZE2S5yk4rQ9PmnHsfEcKkNxjdS2x/cjUTRrKb8WE5/EWMsDuutkBopcc9iX1DqibKR1AiOtnZgqTVa1HMqWomnFyGGFf3hsX94SgqfUHerqeP8uePnc8ZbmvKYn9so+NbFONO5/+mf8AUhlfLjQYay4AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 11 AWS EC2 vpc map&quot; title=&quot;&quot; src=&quot;/static/0d3e5fb25f5211e94ea99668b080a6d1/f97d7/2023-08-11-AWS-EC2-vpc-map.png&quot; srcset=&quot;/static/0d3e5fb25f5211e94ea99668b080a6d1/0eb09/2023-08-11-AWS-EC2-vpc-map.png 500w,
/static/0d3e5fb25f5211e94ea99668b080a6d1/1263b/2023-08-11-AWS-EC2-vpc-map.png 1000w,
/static/0d3e5fb25f5211e94ea99668b080a6d1/f97d7/2023-08-11-AWS-EC2-vpc-map.png 2000w,
/static/0d3e5fb25f5211e94ea99668b080a6d1/a96d7/2023-08-11-AWS-EC2-vpc-map.png 2188w&quot; sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br&gt;
1) MY_VPC에는 퍼블릭 서브넷 2개와 프라이빗 서브넷 1개가 존재합니다.&lt;br&gt;&lt;br&gt;
&lt;ul&gt;
&lt;li&gt;인터넷 게이트 웨이에 대한 라우팅 규칙이 있는 라우팅 테이블(MY_ROUTING_TABLE)과 연결된 퍼블릭 서브넷&lt;br/&gt;&lt;/li&gt;
&lt;li&gt;VPC 내부 통신에 대한 규칙만 있는 기본 라우팅 테이블(rtb-04fe7cca6a359e13f) 프라이빗 서브넷&lt;br/&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2)&lt;strong&gt;퍼블릭 서브넷 내에서 생성한 리소스(예: 인스턴스) 중 퍼블릭 IP를 가지고 있는 리소스만 외부 인터넷과 통신할 수 있습니다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;3)맵 상에는 나오지 않았지만 각각의 &lt;strong&gt;인스턴스들은 MY_VPC에 등록된 보안그룹을 선택해 각각의 인바운드 규칙과 아웃바운드 규칙을 설정&lt;/strong&gt; 합니다.
&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;지금까지 AWS의 인스턴스 생성을 위해서 아래와 같은 절차를 거쳤습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ami(os, 소프트웨어 설치 이미지)와 컴퓨서 사양 결정&lt;/li&gt;
&lt;li&gt;네트워크 설정
&lt;ul&gt;
&lt;li&gt;vpc 생성 및 인스턴스에 적용 : vpc의 리전 설정, ip 주소 범위 설정, 가용영역(리전 내의 데이터 센터) 개수 설정&lt;/li&gt;
&lt;li&gt;subnet 생성 및 인스턴스에 적용 : vpc 설정, 가용영역 선택, IPv4 CIDR(서브넷 네트워크 주소 범위) 설정&lt;/li&gt;
&lt;li&gt;보안 그룹 생성 및 인스턴스에 적용 : 어떤 VPC에 생성될 것인지 설정, 인바운드-아웃바운드 규칙 추가&lt;/li&gt;
&lt;li&gt;인스턴스의 퍼블릭 IP주소 활성화/ 비활성화 옵션 설정 : 퍼블릭 서브넷 내에서만 가능하며, 퍼블릭 IP주소 활성화하면 퍼블릭 인스턴스가 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스토리지 설정
&lt;br/&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;그리고 한 서브넷 내의 인스턴스가 어떻게 다른 서브넷의 인스턴스와, 또는 외부 인터넷과 통신하는지 알아보았습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;프라이빗 서브넷 생성 : 동일 VPC 내부통신(인스턴스의 프라이빗 IP를 통한)의 로컬라우팅만 가능한 상태&lt;/li&gt;
&lt;li&gt;로컬 라우팅 : VPC 내부 통신이 발생했을 때, VPC 내부에서 서브넷 간의 트래픽을 라우팅하는 것&lt;/li&gt;
&lt;li&gt;퍼블릭 서브넷 생성 : 서브넷의 라우팅 테이블에 모든 IP주소(목적지)에 대해 인터넷 게이트웨이로의 라우팅 설정&lt;/li&gt;
&lt;li&gt;인터넷 게이트웨이를 통해, 퍼블릭 서브넷의 퍼블릭 인스턴스가 인터넷과 통신할 수 있다.&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title><![CDATA[선착순 티켓 예매의 동시성 문제: 잠금으로 안전하게 처리하기]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-08-16-concurrency-managing/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-08-16-concurrency-managing/</guid><pubDate>Mon, 18 Sep 2023 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;들어가며&lt;/h1&gt;
&lt;p&gt;대학 축제 티케팅 서비스 &lt;a href=&quot;https://github.com/woowacourse-teams/2023-festa-go&quot;&gt;페스타고&lt;/a&gt;를 개발하면서 아래와 같은 고민을 하게 되었다.&lt;/p&gt;
&lt;p&gt;티켓 오픈 시간에 많은 사용자가 동시에 티켓 예매를 요청하면 어떠한 문제가 발생할까? 그리고 그 문제를 어떻게 해결할 수 있을까?&lt;/p&gt;
&lt;p&gt;여기서 가장 중요한 것은 요청 순서에 따라 티켓을 발급하되, 준비된 수량만큼만 발급하는 것이다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;이번 글에서는 위와 같이 ‘동시성’에 대한 고민을 한 경험을 공유하고자 한다.&lt;/p&gt;
&lt;p&gt;참고로, 이번 글에서는 개념에 대한 깊은 설명은 따로 하지 않는다. 모르는 개념이 있다면 아래 참고자료에서 개념을 익히도록 하자.&lt;/p&gt;
&lt;h1&gt;예제 코드&lt;/h1&gt;
&lt;p&gt;아래는 티켓 예매 상황을 간략하게 구현한 예제 코드이다. (실제로는 더 복잡한 요구사항이 존재하나, 이를 단순화하여 작성하였다.)&lt;/p&gt;
&lt;p&gt;아래 예제 코드는 MySQL 8.0 innoDB 스토리지 엔진 기반으로 동작한다.&lt;/p&gt;
&lt;p&gt;먼저 사용자가 “티켓 예매” 요청 시 호출되는 TicketingService의 ticketing 메서드이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ticketing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; ticketId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Ticket&lt;/span&gt; ticket &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ticketRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ticketId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Ticket Not Found.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    ticket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;increaseReservedAmount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; ticketNumber &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ticket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReservedAmount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    reservationRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Reservation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ticket&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ticketNumber&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;아래는 Ticket 도메인 코드 중 일부이다. increaseReservedAmount 메서드 호출 시 예매 수량과 총수량을 비교해 예매 가능 여부를 판단하고, 불가능할 시 예외가 발생한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Getter&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@NoArgsConstructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;access &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AccessLevel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PROTECTED&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ticket&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; totalAmount&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; reservedAmount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

		&lt;span class=&quot;token comment&quot;&gt;/***/&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;increaseReservedAmount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;reservedAmount &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; totalAmount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Sold out.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        reservedAmount&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;순차적 예매&lt;/h1&gt;
&lt;p&gt;모든 사용자가 순차적으로 요청한다고 가정하자.&lt;/p&gt;
&lt;p&gt;해당 테스트에서는 10장의 티켓을 30명의 사용자가 예매하는 상황을 가정했다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; 티켓_순차적_예매_테스트&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; memberCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; ticketAmount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Ticket&lt;/span&gt; ticket &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ticketRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ticket&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ticketAmount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;AtomicInteger&lt;/span&gt; successCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AtomicInteger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;AtomicInteger&lt;/span&gt; failCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AtomicInteger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; memberCount&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            ticketingService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ticketing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ticket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            successCount&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;incrementAndGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            failCount&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;incrementAndGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;successCount = &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; successCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;failCount = &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; failCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; reservationCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; reservationRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;reservationCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;memberCount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ticketAmount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;실행 결과&lt;/h2&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 334px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/cddcd5e72fd1754fc8f512263ba13f61/ce235/2023-08-16-ash-0.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 28.74251497005988%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA6ElEQVR42m2QVwqEQBBEPY05B0ygiIg5gR+i3v8avVSDbmA/inHKmpp+I8iyTKIosj6/JUkiwzB4vaWqKgs5XdcfaZr2SKjrmuZ5pnEcqWkamqaJ1nWlbdtoWRb2hmHgfdu2ZJomZVnGlx3HwVlFUd6FeZ5T13WE4rIsWfju+56KouA9SquqojRNeaIwDHlFDuWY+il0HIcNBCzL4glwI7ARgA9cYML3fZ9wBv9u/wsZiNd1MTYQgbDvO+MAE75t2zwhis7zZP2+3VOIt4jjmJIkoSAIGCeKIsbzPI9c1+XDyEF37l8ZCl+pX8U+VCSsoAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;순차 예매 테스트 실행 결과&quot; title=&quot;&quot; src=&quot;/static/cddcd5e72fd1754fc8f512263ba13f61/ce235/2023-08-16-ash-0.png&quot; srcset=&quot;/static/cddcd5e72fd1754fc8f512263ba13f61/ce235/2023-08-16-ash-0.png 334w&quot; sizes=&quot;(max-width: 334px) 100vw, 334px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;해당 테스트는 당연하게도 성공했다. 예상한 수량인 10장만 정확하게 예매되었다.&lt;/p&gt;
&lt;p&gt;티켓 예매 내역을 기록하는 Reservation 테이블을 조회해보니, 1~10번 티켓이 각각 한 장씩 예매된 것을 확인할 수 있었다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 902px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/4b5dab5c9305ad0001a24f1ffd807e7d/8d021/2023-08-16-ash-1.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 57.599999999999994%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABnElEQVR42o1SS08bMRjkt5SkWb/t3c1uNg1leQQUccgpNK+26qVSe2orVXDgvw/jzYISFAKHkeXvG49n7O/oIUvws7C4m1zg/1WNf+Maf88/4Z77P3WF+0zg6vQzptMJ6rNzVMMKZVUhn85wObnB+HqCs8sxRicnMMbgaJkZHIcMflDBFINnWEL0S9ymFnWeYTAs0S8KZHkfIQS44Qh5wVq/QE7EXiP41Qt0hYQWAuoJMq4JhFJYO4FC9NDtCVhrCQcROUkCyTVCkOuc3wiug0JXaRitoXegII3Fgv1KCyTkeB8ID8WLtrlxH0K6EVx5iY4kgUX1AkIbzJ1EqShITnRnnYOUcocX9/Gytx0y4pL9UknG1427ePCgw29BNoL2NUEmGNDh24Ktw2UU3BtZbiJTsGgjO8Z174nceUVQkrBIdSsoG4eOB/cKhm1B1VrfAT+FkVfNG24E42g4ih50+IORP7Jg2TDbYM3wV79z8OMbJqyladbg4Kf8th3MxAfM5TEWRFxXxBfWFqaHXz6B56CL1sU+h/F54khF8UcdJHhNOSAN2gAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;순차 예매 테스트 DB&quot; title=&quot;&quot; src=&quot;/static/4b5dab5c9305ad0001a24f1ffd807e7d/8d021/2023-08-16-ash-1.png&quot; srcset=&quot;/static/4b5dab5c9305ad0001a24f1ffd807e7d/0eb09/2023-08-16-ash-1.png 500w,
/static/4b5dab5c9305ad0001a24f1ffd807e7d/8d021/2023-08-16-ash-1.png 902w&quot; sizes=&quot;(max-width: 902px) 100vw, 902px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;아주 성공적이다.&lt;/p&gt;
&lt;p&gt;하지만, 티케팅 상황에서는 다수의 사용자가 순차적으로 요청하기보다, 동시에 요청을 하는 상황이 더 일반적이다.&lt;/p&gt;
&lt;h1&gt;동시 예매&lt;/h1&gt;
&lt;p&gt;동시에 여러 요청이 발생한 상황을 테스트한 코드는 아래와 같다.&lt;/p&gt;
&lt;p&gt;순차 요청 테스트와 같이 10장의 티켓을 30명의 사용자가 예매하는 상황을 가정했다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; 티켓_동시_예매_테스트&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; memberCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; ticketAmount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Ticket&lt;/span&gt; ticket &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ticketRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ticket&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ticketAmount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;ExecutorService&lt;/span&gt; executorService &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Executors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newFixedThreadPool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;CountDownLatch&lt;/span&gt; latch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CountDownLatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;memberCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;AtomicInteger&lt;/span&gt; successCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AtomicInteger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;AtomicInteger&lt;/span&gt; failCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AtomicInteger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; memberCount&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        executorService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;submit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                ticketingService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ticketing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ticket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                successCount&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;incrementAndGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                failCount&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;incrementAndGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                latch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;countDown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    latch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;successCount = &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; successCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;failCount = &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; failCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; reservationCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; reservationRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;reservationCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;memberCount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ticketAmount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;실행 결과&lt;/h2&gt;
&lt;p&gt;정확히 10장의 티켓이 예매되는 것을 기대했으나, 테스트 결과는 그렇지 않았다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 308px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/d566bdefcf309d24f07f8306f72e6d3c/9e38b/2023-08-16-ash-2.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 31.16883116883117%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA90lEQVR42l1RSY6DQAzkNSwJhH1TEIk4cQAECBIgBw7h/1+oUVlqNDMHyy53eam2dr1eYds2LMuCiumVua4Lx3HOd8/zBF8ulz88ZZppmmiaBu/3G9M0oW1bDMOA1+uFZVmwbRu6rpMc8bquCMMQVVUhjmPhPx4PGIYhy2iclGUZns8nyrLE/X4XI4m5oiiQ57lgxmmayoZKCblRFJ0KpCEnKbl8DIJAsK7rQrzdbuKphkYOa8gnVs3Ohn3f4/v9ipxxHDHPMz6fD47jEE/ZSZKcMuu6xr7voogNf/+7pgJO5d/4vi8bMmYxYx6GRdyUnodR+f/H+QFGtcVxH36U6gAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;동시 예매 테스트 실행 결과&quot; title=&quot;&quot; src=&quot;/static/d566bdefcf309d24f07f8306f72e6d3c/9e38b/2023-08-16-ash-2.png&quot; srcset=&quot;/static/d566bdefcf309d24f07f8306f72e6d3c/9e38b/2023-08-16-ash-2.png 308w&quot; sizes=&quot;(max-width: 308px) 100vw, 308px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;10장보다 적은 6장만 예매되었음을 확인할 수 있었다.&lt;/p&gt;
&lt;p&gt;티켓의 수량보다 많은 요청이 있었는데, 왜 더 적게 예매되었을까?&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;출력문을 보니 아래와 같이 &lt;code class=&quot;language-text&quot;&gt;Deadlock found when trying to get lock; try restarting transaction&lt;/code&gt;이라는 문구와 함께 SQL Error가 발생했음을 확인할 수 있었다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1926px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/cc8410f88d230a4487d366c5b8f39c71/5dd2a/2023-08-16-ash-3.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 14.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAdUlEQVR42jWO1w0AIQxDWYhD9N72H8onR+LDshWcR1TvHa01lFIw55TM2d5b3Dknznc6VWuVuTFG3FoL7z201lBjDFAsnXMkr7Vw75XMhfcRnTNCCSAohCA5xojv+6BYJCznLOV3IaHMXHpXvS5F0ANSKSUB/vPdYxA3yEwTAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;동시 예매 테스트 데드락&quot; title=&quot;&quot; src=&quot;/static/cc8410f88d230a4487d366c5b8f39c71/5dd2a/2023-08-16-ash-3.png&quot; srcset=&quot;/static/cc8410f88d230a4487d366c5b8f39c71/0eb09/2023-08-16-ash-3.png 500w,
/static/cc8410f88d230a4487d366c5b8f39c71/1263b/2023-08-16-ash-3.png 1000w,
/static/cc8410f88d230a4487d366c5b8f39c71/5dd2a/2023-08-16-ash-3.png 1926w&quot; sizes=&quot;(max-width: 1926px) 100vw, 1926px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;lock을 얻어오는 과정에서 Deadlock 즉, 교착 상태가 발생했다. lock을 얻어오는 행위를 따로 하지 않았는데 왜 교착 상태가 발생했을까?&lt;/p&gt;
&lt;h3&gt;교착 상태가 발생한 이유&lt;/h3&gt;
&lt;p&gt;티켓 예매 내역을 의미하는 Reservation 엔티티를 보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Getter&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@NoArgsConstructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;access &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AccessLevel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PROTECTED&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Reservation&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ManyToOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fetch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FetchType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;LAZY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ticket&lt;/span&gt; ticket&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; ticketNumber&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 412px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/01aa95d4ffa12adcada14dede843701a/1f9f1/2023-08-16-ash-4.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 126.21359223300972%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAYAAAAxFw7TAAAACXBIWXMAABYlAAAWJQFJUiTwAAAEZElEQVR42m1Va1caSRD112QjM4MgQoB5MO83MLwRjSKuqDFmc5Lsbs7u5/3dd2+NAaMnHy5dU11Vfbu6qjhQVRX1ev0Fjo+PcXR0BK3KvVqNchVH1Sf8yrZGG4kjONA0Db1eD47jwLbtEpZpophMsd7eI4oTpP0Bsv4Qad6H67qwab+zFYi/xCkDVnmqoevotNvodrvQf8jZcITV+hohA06XZ5ifXmA0W8Lq2aWd+AjE3jCM54CqqsDxfAwnMzpn8MIIbhDCdj30HLLhnqw7OH5Q7rteQL8nWfQSTFEUHChKBdFgiu9/bfH5bo7F5hEmDQzH4yrwYdg72YPec+FFKQajCfrFGAlTYZL1nqFE9cIYg6JAnoVwwoQGDiw6Wwxs2swtWckhopPvHtnLTQKmw6XvC4Yqf6Isx923fzA93+D06hYX20esNndYXP6O7efviIeT8vv9zUdc3n7Ccr3FknZBkpHh4DXDCq8QI5/MyS6Gy+uE2QCe5DNO4Sc5onxI3RBOECNh8KhfwE/7sFy/hGH1nhlqFHw6b7/8jcnZGiO+pqxpMUU8GGP+/hrF4hwjYnp+Veaz1dXR1k0+WIAwzWGQoVSLsDyoVCpI8gGWFxv0RzOu11i832C6usBcUnB5g/HynIGp42GSM8t+yq28sPhalJ8ZMmrZFVyrVW1f8RplMWgc1/H4+AldMmq23pVotVmvhoV2R2eX1Ev/faeIkwjCVCDfO8i3tN3idIXBZImUKXBDedmUD5KTYby32/kclGy0Z2a/QqvVROXwEAodNY32cj0JUn6/9N0zPKwoe1QUtYTICvc6bLW9fmfzk+3PtyoZVnlKt6HCbGrQGwreVd9AP64QSonAbKJbr5QodbQxaGucqNBPnhjvGcoJ9aoC32zB4JSRabK5/4g4ZR16HkLWqB+G8IMACXVBFMHzfZiGXtrH5gmO1Mqe6YGqVdGoaYi4YXRaCD0btw8fMBr2sZjPcLVh6SyXJS7XV1itVpiMR3DMNhxLR2Y1SIjstF0dMnJNU9D3OpgUKfLQRuQaCG0dgd1F0OsQ3T1CR0dExC7Z+TYGTvM1Qw019S2K8RR/frvB/fYUs80ndCwHHXZFpyfwKftoWxxhQYKMXZQNxwjyUXlgTeOLq9pzDkUxiS2sxjFmqYnM7yFxuoiJxNV/yB3KT7rcNzGMbKS+hcL9JcNDjIsc26//4uz6A1acJOv7z5wuD8jG83LqnF5tcf3xK9Z3f2DGnnb9kIU+QmK/YqgIQ56Qxx7/NzhF2KsyYWTiBJwoaTFBQFkmjEwaua7sy0DoMWhkt8nw8JmhQoZNKk44ebUv/6G/eeAwuMDs8hajsw28rMDs4gZ2lKHR5n8Ic1keyMPcZMDHe82QQoOKIHagX14jZ9+upgXOV0usFlOcEuerBfLI5avrSAMbozzEmOjzVn2+slTJPuBusjSrh2irb8j2DY61t6jLqv6Gk6NKuTa5vqsraNUUnFQrT6CuWX/Z9/8DdRtjqpaAeLEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;DB ERD&quot; title=&quot;&quot; src=&quot;/static/01aa95d4ffa12adcada14dede843701a/1f9f1/2023-08-16-ash-4.png&quot; srcset=&quot;/static/01aa95d4ffa12adcada14dede843701a/1f9f1/2023-08-16-ash-4.png 412w&quot; sizes=&quot;(max-width: 412px) 100vw, 412px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;Reservation 엔티티에 Ticket 엔티티와의 연관관계를 설정해 줌으로써, DB의 reservation 테이블에 외래 키 칼럼 ticketId가 추가되었다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html&quot;&gt;MySQL 공식문서&lt;/a&gt;에 아래와 같은 내용이 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If a &lt;code class=&quot;language-text&quot;&gt;FOREIGN KEY&lt;/code&gt; constraint is defined on a table, any insert, update, or delete that requires the constraint condition to be checked sets shared record-level locks on the records that it looks at to check the constraint. &lt;code class=&quot;language-text&quot;&gt;InnoDB&lt;/code&gt; also sets these locks in the case where the constraint fails.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;즉, 외래 키 제약 조건이 있는 테이블에서 레코드를 삽입, 갱신, 삭제할 때 해당 제약 조건을 위반하는지 확인하기 위해 관련된 레코드들에 공유 잠금(S lock)을 설정한다는 뜻이다.&lt;/p&gt;
&lt;p&gt;공유 잠금이란 다른 트랜잭션의 데이터 변경을 막고 데이터 일관성을 유지하는 잠금 유형이다. 여러 트랜잭션이 동시에 공유 잠금을 얻을 수 있다. 그러나 공유 잠금을 설정한 트랜잭션이 있을 때, 다른 트랜잭션은 해당 데이터에 대해 배타적 잠금(X lock)을 얻지 못한다.&lt;/p&gt;
&lt;p&gt;배타적 잠금은 한 번에 하나의 트랜잭션만이 특정 데이터에 대한 쓰기 작업을 수행할 수 있도록 하는 잠금 유형이다. 다른 트랜잭션들은 쓰기 작업이 끝날 때까지 대기해야 하며, 이를 통해 데이터 충돌 문제를 방지한다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;실제로 innodb의 로그를 확인해보니, 여러 트랜잭션에서 데이터에 잠금을 거는 과정에서 교착 상태가 발생했음을 알 수 있었다. 아래는 로그 내용을 간략화한 것이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;LATEST DETECTED DEADLOCK
------------------------
Transaction 1 (ID 20238):
- Holding S Lock on data ID 1 in table `ticket`, Waiting for X Lock
Transaction 2 (ID 20240):
- Holding S Lock on data ID 1 in table `ticket`, Waiting for X Lock
Transaction 2 Rolled Back&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;로그를 토대로 아래와 같이 교착 상태가 걸리는 과정을 알 수 있었다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1356px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/9f5d4bf32d734851fba53c6ad6a4ab35/2637b/2023-08-16-ash-5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 66.60000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAACGklEQVR42nVS246iQBDl/x+Niy8mvvoB64OJ2cwm7oMKg5qZHZlBvCAo4gjqgMilz3a14rCb2U5OF9XU9VRJYRjCtm2sVisBz/Ow2WywXq/Fu+M4d1nYbLdbYUMo3sg+SRJIu90OmvaIfq+Her2OSqUCWZZRrVbRarUwGo2gqiq30QQajQZqtRrkbzJkLpvNJvr9PsbjMaIognQ6nbBcWiIbVWFZFteXQpJOmUknUKWLxQKmaWI2mwmQXlR5uVwgZTlwDj28/e5BUTVejYJ2u42HhweRVTdMhOEHGGMAGIT44uR5jizLICVJijiOcAj2oPZd10UQBHjf7xH4e3yfRng/RcjThDsx4UjBy5JA/KVpCoku+lE2un5fK/LODJuQIYpDzA2VV/0kuOx2u+h0OtB1HdPpFMfjUfiJgIS/g+U3nYPLX06Kn1aEMHA51y7m8/mdb+Le930xENEyXRSQiB4OhyK7oihiugvuaLseVA84XPIrVzcSi+RliIAUjPqn/TMMQ2QvpklvH3wL/HMG65jehsL+G+zOISmsNL6Cw+yGmA/OOcSwnTXmiyX0NwODwUB0RKtDbcdx/FnhvxMjgF05pKrynLIn2O88LE0DxuQZEz6MVw7qiraDuiQ/qdifMlKORy+Dss3ww75Kjev6IYfmMzwd2S3RZyFFl9JXS5px+9kJWIXAcwBMOF44zBPD64HBLAKWVq04fwBd29DiSt+R0gAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;교착 상태 과정&quot; title=&quot;&quot; src=&quot;/static/9f5d4bf32d734851fba53c6ad6a4ab35/2637b/2023-08-16-ash-5.png&quot; srcset=&quot;/static/9f5d4bf32d734851fba53c6ad6a4ab35/0eb09/2023-08-16-ash-5.png 500w,
/static/9f5d4bf32d734851fba53c6ad6a4ab35/1263b/2023-08-16-ash-5.png 1000w,
/static/9f5d4bf32d734851fba53c6ad6a4ab35/2637b/2023-08-16-ash-5.png 1356w&quot; sizes=&quot;(max-width: 1356px) 100vw, 1356px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;ol&gt;
&lt;li&gt;트랜잭션 1이 id=1인 데이터에 공유 잠금을 얻었다.&lt;/li&gt;
&lt;li&gt;트랜잭션 2가 id=1인 데이터에 공유 잠금을 얻었다.&lt;/li&gt;
&lt;li&gt;트랜잭션 1이 id=1인 데이터에 배타적 잠금을 얻고싶지만, 트랜잭션 2에서 해당 데이터에 공유 잠금을 걸어두었기 때문에 대기한다.&lt;/li&gt;
&lt;li&gt;트랜잭션 2가 id=1인 데이터에 배타적 잠금을 얻고싶지만, 트랜잭션 1에서 해당 데이터에 공유 잠금을 걸어두었기 때문에 대기한다.&lt;/li&gt;
&lt;li&gt;무한 대기상태에 빠진다. 즉, 교착 상태가 발생한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;연관관계 제거&lt;/h2&gt;
&lt;p&gt;실제로 교착 상태의 위험성 때문에 외래 키를 사용할 때는 신중해야 한다.&lt;/p&gt;
&lt;p&gt;교착 상태의 문제를 해결하기 위해 Ticket과 Reservation간의 외래 키 관계를 제거했다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Getter&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@NoArgsConstructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;access &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AccessLevel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PROTECTED&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Reservation&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; ticketId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; ticketNumber&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Reservation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Ticket&lt;/span&gt; ticket&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; ticketNumber&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ticketId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ticket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ticketNumber &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ticketNumber&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;연관관계 제거 후 다시 동시 예매 테스트를 실행하니, 교착 상태는 발생하지 않았다.&lt;/p&gt;
&lt;p&gt;하지만 총 수량은 10장뿐이지만, 30명의 사용자 모두 예매에 성공하는 결과가 발생했다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 286px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/8386a9547c96ca05ff2650044c725bb4/9e45f/2023-08-16-ash-6.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 27.97202797202797%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABAklEQVR42k2Q38qCUBDEexrN1CwtNRVE0gtBvNGk8G+C0Pu/wMQsnL7vYjjn7I4/d3bnOA5s28b5fAbvSqfTCbfbDa7rwrIsqQVBIHX6+T4ej0jTVHymaYp227ahbVt0XYdhGPB6vTBNE97vt9TXdcXj8cDz+cSyLIjjGFEUIc9zAX8+H8zz/AfkX33fx+VykQk8z0MYhqLr9So93tnntJxqv9/jcDgIUPV+QJpo4MkYjM63ruswDEMAPAmg6KFXAVTvBxzHEX3fo65rNE2DqqrAGmNyDYxJaFEUMk1ZluLTNE12q/QDMtL9fpfdJEkiyrJMRAD3RSDj8QO1Ak6mIP/1BU0LyC8w8gHFAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;연관관계 제거 후 테스트 실행 결과&quot; title=&quot;&quot; src=&quot;/static/8386a9547c96ca05ff2650044c725bb4/9e45f/2023-08-16-ash-6.png&quot; srcset=&quot;/static/8386a9547c96ca05ff2650044c725bb4/9e45f/2023-08-16-ash-6.png 286w&quot; sizes=&quot;(max-width: 286px) 100vw, 286px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;어떻게 티켓의 수량보다 많은 사용자가 티켓을 예매할 수 있었을까?&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;DB의 reservation 테이블을 확인하니 그 이유를 알 수 있었다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 882px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/63e80a317763eb47b903c2a81e6c8571/5f5e5/2023-08-16-ash-7.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 156.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAfCAYAAADnTu3OAAAACXBIWXMAABYlAAAWJQFJUiTwAAACc0lEQVR42q2WSXPaQBBG+SU5uAKSZrSMAC3BdiLQAiYL5JSY3ZVU5eb/f/vSMygq2cUMSpUPXWJ9vK+7JdGb3N6imiRYpWMclwvs5iU2VYHHMsf+8wN+Tu+xTGOU9PzblwofsxnSNMXdPb3+dYVy/oCiWiAvKnyYTNCbZRn+jDjWvI/vbh9rd4C1Z2FFJY9r38azsBC7DMx1IcIQQgi49NjzPCq/Lg9hOESvIuBmyHHDON5zj8ptqs853nkBfgiGmNmw6DMSFgQBGGPg9H67hAjPwAMBXc7gufxF+WTl+D62EsgdONKQgLIkQFq2qzHcE/DSL8ofGVCcnXAaQ2nn6wwVcDqlyK4RuK2BtgSSnR8IM3BPwEsRZGzLD7ChyImMzM9DCUyRy+yTimwCSsOk7mFQD8XVAeczMhyZDXc10ObyS6GapsEw62DIGkO1NprIsh29RV7gFNEHqMmXgDYBHxWQkSFHFCUIh0M9cJ7nOI39y0D+LzKrI3OMx5H6otHwGJ33Shd5H7aAUax6pTec5TjEgTayMmwBR2R4HWgwtBWQv11kCdwoIGsiC5PhQ1HgKQqMQ2n3MIoTraHawwVN+WgA2q96aIqsgNLwGIvLkTWGuj1sgCddD2vDbXsohrVRwGVZ4inuDozjlAxH1w07D0VN2dRDMjzEofnUaxl27iF/qx4uOgB3LyIn2sVuIl9b7N3rtbkGPMbdI0dJej3y4T97eOVMKXGiM4U7jgK0y5cXBLqN7pt7ynkPh8Y9rCP3ydCiF9tl09XmRt1GHaRd16agi8PvyEPmDJBxm8pSxykdZd15HL8CC2Pm0D8H1kxZ99/mL3y6ytGHTHLpAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;동시 실행 DB 결과&quot; title=&quot;&quot; src=&quot;/static/63e80a317763eb47b903c2a81e6c8571/5f5e5/2023-08-16-ash-7.png&quot; srcset=&quot;/static/63e80a317763eb47b903c2a81e6c8571/0eb09/2023-08-16-ash-7.png 500w,
/static/63e80a317763eb47b903c2a81e6c8571/5f5e5/2023-08-16-ash-7.png 882w&quot; sizes=&quot;(max-width: 882px) 100vw, 882px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;1~10번 티켓이 각각 한 장씩 예매된게 아니라 1번 티켓 10장, 2번 티켓 9장, 3번 티켓 9장, 4번 티켓 2장이 예매되었다. 즉, 하나의 티켓이 여러 사용자에게 발급되는 문제가 발생했다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;순차적으로 예매를 요청할 때는 발생하지 않았던 문제였는데, 동시에 예매를 요청하는 상황에서는 왜 이와 같은 문제가 발생했을까?&lt;/p&gt;
&lt;h3&gt;순차적 예매 실행 흐름&lt;/h3&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1186px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/bd0a1777fe65ab9db8874f20b5ffb230/7e08f/2023-08-16-ash-8.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 92%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAABYlAAAWJQFJUiTwAAAC0klEQVR42oWU3U/TYBSHuTImJiYmJvrfEBNjonf6D6BGYzQhMcYrrxEixuCFRuONmIi4iFygcRjGlwjIwjYsMDbpuo7ysY1t7INt3br2sS3b2ATk3LQ97+8873lP33NaDMPAsnQ6zdzcHP6VAD6fj2wuZ/uTqsGADAVtTyeKor0eCATweDzkqroap4WqaZqGsLTM1PQ0Ht8CqlpEzII7buBcB28C4irspFJ4TeC0qQsGg6ZOpdHqwNoOYWXdhlummz7ddIsZ7KdR1eq6jqIoTXFNwIrtMzNRdFq/qPQKaXzuWSZd33m1kOXCCExFjeomUC6XWV1dPQ5YosOd5/xnaBuUmJlwkUvGuDuR50yfwZvAPrBSqRAKhY4G5nZ3Se+kKJtr31a2yJW0uiBrvjo3zRo3xFlASZKOBpbMI6RTSQJKnG6nwLKyzW+fl0Q8hpws0DkqsZlRm4DhcPhw4PquQTAkE1xwc+O1i5MPx7n3bgbBO8/w0CAXOwY59cDFs9G9I1ZMgKZVEKtH5l9gomiwsRVjS4kwL29z84MHz9rOXnBZZT6S5NbnAHKqWA/SzAyD4n9q2Lhg1cb6izXZr8gOV3oFHPNhfk6O4xcWeDsj0vpylqlwdWN9H9piVGHW3bI7wQSWSmVqmu4xiRP3ndx57yaxtc7U+CiXOgc5/WiSrmF/vQzHZlgztaLjCGTJqHrdl8yXccz9QdV0spk0hULhYOsdBUxk8vR89aAkMkghkbAUIhJL0eMYQYklyKS27X4+EmhdBwuoV787+8Y4d/Uxl9u7+eToJ7iyxO0n/Zy91kX78wHEzQRrZrv6/X477gDQ6oDGDBflGNdfOBnzraJre/4fixJtTz/ileNE8xphOYIgCHbtDwBlWW4C2r5c83U7tlNomCJDQ0NNIymvWUPDsANrwRsbm7auFtO4dgBozTgrg/2/DP50UxIUi0Wi0eihGf4FNkw9dGkC8dYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;순차적 예매 실행 흐름&quot; title=&quot;&quot; src=&quot;/static/bd0a1777fe65ab9db8874f20b5ffb230/7e08f/2023-08-16-ash-8.png&quot; srcset=&quot;/static/bd0a1777fe65ab9db8874f20b5ffb230/0eb09/2023-08-16-ash-8.png 500w,
/static/bd0a1777fe65ab9db8874f20b5ffb230/1263b/2023-08-16-ash-8.png 1000w,
/static/bd0a1777fe65ab9db8874f20b5ffb230/7e08f/2023-08-16-ash-8.png 1186w&quot; sizes=&quot;(max-width: 1186px) 100vw, 1186px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;순차적 예매 시 위 그림과 같이 한 스레드의 작업이 끝난 후 다음 스레드에서의 작업이 시작되기 때문에 충돌이 일어나지 않는다.&lt;/p&gt;
&lt;h3&gt;동시 예매 실행 흐름&lt;/h3&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1306px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/61863fc9cd77a53b821987e4f2698cb3/2d324/2023-08-16-ash-9.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 84%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAABYlAAAWJQFJUiTwAAACv0lEQVR42n2UTU8TURSGXRh/hHEjSxf+ABN/g0tj3PgHXBhjou4Ie2M0MTGaaERjCIoYDQINFQG1LZWPFiilH9S2M/0YoJ3PltKZx9vptJSCnNXcO+e+95z3fc894zgOrahUKgQCAUKLiyQSWzRshwPx60MGlnbaOYVisZ0TChGPx2k0GvTHmc5HvV4nnUqyuR6lWJCxBNqWBut7EKs6FGpgmSbJZJJIJIIkSdi2fTKg4y0qVp1wVnG/dV3DrltEKqDUDg/UajVkWeZ/4QI2PcT7YYcLH+Hp9Cozn94z7F9iYAxuzTvdA5Zlkc/nTwdsF37A86jBtRmbX6kyjqVSEpXd+CEuiB32YRomsmj3VMB2izqqppKVC+7a04qo4HBnX1QmKGi1a1g1VraLpwNWq1VMQbglknN5yavYJqPDpXG4OdsksDCH7+s4DyZTXPwMY5n2jU3nBMCW/KZpYYgqe9tR6nB9AYYiTXdt7Ck8C8hcnYSJtOXSYPcDmg0bQ/BSlLK8mV3h7pdNvgeXCYeCGFqVIf82L4OHIphClL9SQdis5tJ0rOVMtUF4aZl0LMLtsQ3O3pnmtX+V3VyaFyPjnH84xZVHv9lvtonQxeU5rwtd01yqev3Y9qGnQKKk8mo+zkGPX30plbCkH7GNLOU9IQ0XsHP+GGAruSAd9djb5TKTW3vksxnSyQRr+V2Gvm0QTeXQ1Qqd8y1xnF7buPwIYXLS4RTIWp2BwVkuD07xbmSUTHyNe6N/OCdoeezfQqlqrEdWyKRTXeA+QLM7BZ2qh1fKTGzudHNSZZUn/k2yepNwqYEvVmIuteuO6F69D9Dlx5vTDi2tabEOeiZFGFzeTqCJvaDiENyF+bJ4lbahaPVxqCgKPp+vbWtvzy87ZA2nC1gsiYoWfvZ04fQ8L30ctgyuquoRUYRNj5m3N/oh/wHtTv4Y8K8zDgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;동시 예매 실행 흐름&quot; title=&quot;&quot; src=&quot;/static/61863fc9cd77a53b821987e4f2698cb3/2d324/2023-08-16-ash-9.png&quot; srcset=&quot;/static/61863fc9cd77a53b821987e4f2698cb3/0eb09/2023-08-16-ash-9.png 500w,
/static/61863fc9cd77a53b821987e4f2698cb3/1263b/2023-08-16-ash-9.png 1000w,
/static/61863fc9cd77a53b821987e4f2698cb3/2d324/2023-08-16-ash-9.png 1306w&quot; sizes=&quot;(max-width: 1306px) 100vw, 1306px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;순차적 예매와 달리, 동시 예매 시 위 그림과 같이 한 스레드가 데이터를 읽고 이를 갱신하기 전, 다른 스레드에서 데이터를 읽기 때문에 충돌이 발생한다. 이 이유로 여러 개의 트랜잭션에서 동일한 티켓을 예매할 수 있었다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;이제부터 위 문제 상황에 대한 해결책을 알아보자.&lt;/p&gt;
&lt;h2&gt;해결책&lt;/h2&gt;
&lt;h3&gt;synchronized&lt;/h3&gt;
&lt;p&gt;java에서 한 자원에 synchronized 키워드를 붙이면, 멀티 스레드 환경에서 단일 스레드만이 해당 자원에 접근가능하도록 보장한다.&lt;/p&gt;
&lt;p&gt;그렇다면 ticketing 메서드에 synchronized 키워드를 붙임으로써, 동시에 들어오는 요청들을 순차적으로 처리할 수 있지 않을까?&lt;/p&gt;
&lt;p&gt;한 번 실험해보자.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;아래와 같이 ticketing 메서드에 synchronized 키워드를 붙여주고, 동시 예매 테스트를 실행하였다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ticketing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; ticketId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Ticket&lt;/span&gt; ticket &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ticketRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ticketId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Ticket Not Found.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    ticket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;increaseReservedAmount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; ticketNumber &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ticket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReservedAmount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    reservationRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Reservation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ticket&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ticketNumber&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 312px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/3f77a06fb1689024e2334be7b010f384/f00bc/2023-08-16-ash-10.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 30.76923076923077%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA3ElEQVR42l1QyaqEQBCbjxF3VBB0BsSD+4oKetCb//8RGVJQ82beIXSqqiud9MPzPFiWJTBN83MStm0LtObMdV04jgPufXPFg82yLFHXNfq+R9u2GIYBXddhnmeM4yicmKYJcRzj9XohDEOs64pt2+D7/p8gX6iqSi5ziYLkBAVZ8+SsKApEUYQkSUSQvaZpfgWVaESNbxiGQHsaWaPqjvZ+HC7LgvM8se+7RDyOQzhxXReezyfyPJe4dHXftzil8Le7j8MgCJBlmSBNU/kjrRmPy7zDZc7Z/y+kgm9k38N5p+nfGwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;synchronized 테스트 실행 결과&quot; title=&quot;&quot; src=&quot;/static/3f77a06fb1689024e2334be7b010f384/f00bc/2023-08-16-ash-10.png&quot; srcset=&quot;/static/3f77a06fb1689024e2334be7b010f384/f00bc/2023-08-16-ash-10.png 312w&quot; sizes=&quot;(max-width: 312px) 100vw, 312px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;여전히 결과는 옳지 않았다. 요청이 순차적으로 진행되지 않고, 병렬적으로 진행되었다.&lt;/p&gt;
&lt;p&gt;@Transactional 키워드와 synchronized 키워드를 동시에 사용하면 어떤 문제가 발생하는 것일까?&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;@Transactional이 붙은 메서드가 호출되면, Spring은 프록시 객체를 생성한다.&lt;/p&gt;
&lt;p&gt;해당 프록시 객체는 원본 객체를 감싸며, 메서드 호출 전 후로 transaction begin, commit을 수행한다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1782px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/3a151c092d7ea2c8921ece68c113e71b/26cba/2023-08-16-ash-11.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 83.80000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAABYlAAAWJQFJUiTwAAAClklEQVR42p2Uy08TURSHu0BJlOgeMCYmBqOJCx9RkK0LYzQkrsAYV/4JLkxcuHbjY6EChtgGEKggpCImxAJBoUEFBUoLtDxaWlugD+hzOo/P207RqguLJ/nNTObe+eaec3/3GBCxGofJMHwJgTuGHmurYsANniWIhCg2DNlLPAPfBdQVgY24QiYj6RCfB7zLENsuHmhNb2FJhxnMRLCqW3RGfUx5VnKDmqax2zDUBp2U+75xMbjApU0XJYtjtC45c4OKpmapuwOefS9h6EiypzvF3h7xbAxjmvLoQFWHaXkVBawfgpOv4XQv1LyB4wJumvQiobCuyIRVmU2hVHa1xQCr38GpfqgbhqsjcKhVosr6iQthO+cCDqqDDo76Z2hPhPKrzpZBSM1L25GWk6HGomJ4rnDQKFNmUjnwLEXZsI2K7RlqQw6uRRfZH/zM40QgB5T/kbxh1bzAXNMsDqMD+8tFlh5OcL2zl0rHOIed0xyZn2Hf3FeaN4OClkFadqGuB1ADftQVN4p7AcUxi7LoRN1YFz6c8MKQMPDYCqrNB5Zx7t5u4fwdK+WPfFQIld730Ti8BokwadsokuUVUl8X0kAf6c4XSL0dSIP9Aj6PIVvqHcm5IiW5+dZHudioY0In+qDSDEaXnpIii6QlYXxFAXEAtFRSfCjrytYQVS9m9p5ziZygvsfLGQvcGNG49UGjqlvjiZNfVtL+kH4K8sCCyBlDTXLZ7BcbBSVtGqXtotDN8MCuz5HVvyGF5v8NmHutpLjXNU9dS5QGYfIGU5grTWEG7Gn9p1oRzaEwkloGf9tHaJxAa5sC8zQ8tYlW5KMY4k/gTiOIRCOMDgwJWyTJ+OPIWa3FUBOZgjR2AYzFYszO2f+72/wAYgXftqFrHRMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;synchronized 실행 흐름&quot; title=&quot;&quot; src=&quot;/static/3a151c092d7ea2c8921ece68c113e71b/26cba/2023-08-16-ash-11.png&quot; srcset=&quot;/static/3a151c092d7ea2c8921ece68c113e71b/0eb09/2023-08-16-ash-11.png 500w,
/static/3a151c092d7ea2c8921ece68c113e71b/1263b/2023-08-16-ash-11.png 1000w,
/static/3a151c092d7ea2c8921ece68c113e71b/26cba/2023-08-16-ash-11.png 1782w&quot; sizes=&quot;(max-width: 1782px) 100vw, 1782px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;여기서 프록시 객체에서 수행되는 transaction begin, commit 코드는 synchronized의 영향을 받지 않는다.&lt;/p&gt;
&lt;p&gt;즉, 위 그림과 같이 T1 스레드에서 commit 되기 전 T2 스레드는 메서드를 시작할 수 있다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;이와 별개로 synchronized 키워드에는 몇 가지 단점이 존재한다.&lt;/p&gt;
&lt;p&gt;한 스레드가 메서드 작업을 완료할 때까지 다른 스레드들은 대기해야 한다. 이로 인해 프로그램의 성능이 저하될 수 있다.&lt;/p&gt;
&lt;p&gt;또한 이는 하나의 프로세스 안에서만 보장이 되기 때문에, 서버가 여러 대인 분산 환경에서는 데이터의 정합성을 보장할 수 없다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;따라서, synchronized 키워드는 우리 문제 상황의 해결책으로 적절하지 않다.&lt;/p&gt;
&lt;h3&gt;잠금&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;잠금(Locking)&lt;/strong&gt; 은 데이터가 읽힌 후 사용될 때까지 데이터가 변경되는 것을 방지하기 위한 조치이다.&lt;/p&gt;
&lt;p&gt;잠금 전략으로는 여러 트랜잭션 간 충돌이 일어나지 않을 것이라 가정하는 &lt;strong&gt;낙관적 잠금(Optimistic Lock)&lt;/strong&gt; , 여러 트랜잭션 간 충돌이 일어날 것이라 가정하는 &lt;strong&gt;비관적 잠금(Pessimistic Lock)&lt;/strong&gt; 이 있다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;낙관적 잠금과 비관적 잠금을 통해 문제상황 해결을 시도해 보자.&lt;/p&gt;
&lt;h4&gt;낙관적 잠금&lt;/h4&gt;
&lt;p&gt;우선 우리의 코드에 낙관적 잠금을 적용해 보자.&lt;/p&gt;
&lt;p&gt;낙관적 잠금은 아래와 같이 @Version 어노테이션을 통해 처리할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Getter&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@NoArgsConstructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;access &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AccessLevel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PROTECTED&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ticket&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; totalAmount&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; reservedAmount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Version&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt; version&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

		&lt;span class=&quot;token comment&quot;&gt;/***/&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;DB의 Ticket 테이블에는 아래와 같이 version 칼럼이 추가되었다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1174px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/1497ddb93d5df5a437de21bc3260aca7/683d4/2023-08-16-ash-12.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 5.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAABCAYAAADeko4lAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAQklEQVR42j2KSQ6AMAzEeFK5JIitZCYV/P9DQ1UhTpYtT2YuZFOwqQYGr073Reu2K+/n78dZRw/k91HWnYSIUCmzXml2Ip+OwzUiAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Ticket 테이블 version 칼럼&quot; title=&quot;&quot; src=&quot;/static/1497ddb93d5df5a437de21bc3260aca7/683d4/2023-08-16-ash-12.png&quot; srcset=&quot;/static/1497ddb93d5df5a437de21bc3260aca7/0eb09/2023-08-16-ash-12.png 500w,
/static/1497ddb93d5df5a437de21bc3260aca7/1263b/2023-08-16-ash-12.png 1000w,
/static/1497ddb93d5df5a437de21bc3260aca7/683d4/2023-08-16-ash-12.png 1174w&quot; sizes=&quot;(max-width: 1174px) 100vw, 1174px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;이제 동시 예매 테스트를 실행해 보자.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 290px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/18ca5286a1800b6552bf57f371c5f607/bd800/2023-08-16-ash-13.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 31.724137931034484%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA50lEQVR42mWRRw6EMAxF5y6I3mGB6EUgtvSyAe5/CY++JdCUxcfGdn7ykpeu66QoCsmyzPHOb9m2/dXHPKSqKhmGQa7rPjXohc84jtT3PavrOpqmiYZhoHmeWfhHb993njVNk6qqIt/3ads2Nn4MNU2joiiormvKsozSNKWyLFlN03ANfRggRlHEC2EG4ziO/w2BJYoiNyzL4kFJkkgQBI7AA/Kdw8zzvAf9Cxn3AqTzPB9sYK3ryrjHcTAqNmnbljdfloWu6+Lap9lzQsdxKM9zPn4QBBSGIaMlScI5avcDwOSz9mv4BmeMxVYXq1FIAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;낙관적 잠금 테스트 실행 결과&quot; title=&quot;&quot; src=&quot;/static/18ca5286a1800b6552bf57f371c5f607/bd800/2023-08-16-ash-13.png&quot; srcset=&quot;/static/18ca5286a1800b6552bf57f371c5f607/bd800/2023-08-16-ash-13.png 290w&quot; sizes=&quot;(max-width: 290px) 100vw, 290px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;결과는 여전히 실패이다. 10장이 아닌 5장만 예매되었다.&lt;/p&gt;
&lt;p&gt;reservation 테이블을 조회하니, 잠금을 걸지 않은 코드와 차이점을 발견할 수 있었다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/cd219d4ae481d269d8ea1671131368f5/249e2/2023-08-16-ash-14.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 36.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABXklEQVR42n2PW2/TQBBG80uQSmMn3ou968tmmyYqSZzEpRcpD23FAyoRVCAq3vj/h7EVIYQQD0czmplv5ptROV/wUmb87NZ8fb/ne9fyslry2m35sm/5ER3H6Lk93NJ1OxaLJTHOuL675/rmnv3NHZvtnna7I4TAKF6t+Fxp5k1D1szQdYOpw0AqrJqKD17jYyA0NReXS7z31DJf1TVlVQ2xqnoqRvPVhmcRhGlCphRGZWjBSp5mGa2zPJiUt1OFNYYmyFHpKempPp5yozVODo3m6w2fSs1MTcmMxYiopxen2rArC55syljyIs8Js/h75k+stfiylJffrTn6jKgmKBFZudSTC1NhVxiexOG5kpqIBodS/5t+qfNl77DlWRwW8uaZyRmLy3MhEd5oy9I7HvMJyclhvLj8v8NqccU3N+GozjjohAc95vHEQY15dSkfbTIcKnqHw8v2nwud8/wCQVHg+JqyJEkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;낙관적 잠금 테이블&quot; title=&quot;&quot; src=&quot;/static/cd219d4ae481d269d8ea1671131368f5/249e2/2023-08-16-ash-14.png&quot; srcset=&quot;/static/cd219d4ae481d269d8ea1671131368f5/0eb09/2023-08-16-ash-14.png 500w,
/static/cd219d4ae481d269d8ea1671131368f5/249e2/2023-08-16-ash-14.png 810w&quot; sizes=&quot;(max-width: 810px) 100vw, 810px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;한 티켓이 여러 번 발급된 이전과 달리, 1~5번 티켓이 각각 한 장씩만 발급되었다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 308px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/d8349964ba560546c4370fe7984eb2d9/9e38b/2023-08-16-ash-15.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 28.57142857142857%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA6ElEQVR42mVQyaqDUBTzc9Q6teKsKLpxJ85QiwvXgv+/SskBxb63CMcc781Noui6DsIwDDwejwuWZQk0TRNu2zZM0/z57zjOv3vK5/NB27ZomgbDMAimacL7/Ubf9+i6TnbruuL1eqGqKnieh7Isse87wjCEqqqXsJJlGdI0RRzHF7hLkgRBEMh3FEUCXqJjJvJ9H3mei1PyyyFf5UHO5/Mp0c4KXNeVSU4hcgqfVdDZXUwEt23DOI4SeVkWiTfPs+xYB50URSGzrmscxyGuKXbv9Mche+Dr7IZgVHI6ppvz8Jnkb8y74BfJsMVTfzsVCAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;낙관적 잠금 100명 테스트 실행 결과&quot; title=&quot;&quot; src=&quot;/static/d8349964ba560546c4370fe7984eb2d9/9e38b/2023-08-16-ash-15.png&quot; srcset=&quot;/static/d8349964ba560546c4370fe7984eb2d9/9e38b/2023-08-16-ash-15.png 308w&quot; sizes=&quot;(max-width: 308px) 100vw, 308px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;사용자 수를 100명으로 늘리고 다시 테스트를 실행하니 10장이 다 발급되었다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;위와 같은 상황이 발생한 이유에 대해 알아보자.&lt;/p&gt;
&lt;p&gt;낙관적 잠금을 사용하면 아래와 같이 버전 정보가 update문의 조건으로 포함된다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 382px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/f4483cb15aa12d529de117839109ce5d/29984/2023-08-16-ash-16.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAABYlAAAWJQFJUiTwAAAC4UlEQVR42pVUyW7TUBTtP9DQQaJNaZx4eLYzOYNTZ7Azk5EmoQ1iwwaEBBs2XYJACImEYUERtJvCArHgGw/3PZrIEWlFFkf3DX7nnXfvuV7TNA26riMajSKRSMzBGAPfWxVriqLAtm0MBgOMx2MMh0P0+33E43HwvZUJZwPDMETkyjhUVRWYra1MyA9xRalUCq1WC+12W6jtdruIxWJz8v8i5B/Lsiwiz6dlWUgmkwKr5lPkMJ1Oo9frodFoYHaBH7M0+FPixwIhP8ArfHBwIBTxeSQSmYMrD4fDInJIkrSwzwX5i7fwZA5e3Xq9jnK5jFqthkKhIOau6wo3dDodeJ6HSqUi4DgOcrncnHShKIamImEacPN5VN0SKoQ8kdQ8F6W8Q+sOWpQWTl4sFsVl2WxW4N8qE/ZVDVIsAT1jQ7PSMLI5hIwo5HgSJo1lWmNUpDQ5geedi5g9e4GQk0U0hvtyEJN4CBd1G2dlC+eNHF7GJJxVMvjRc/GrmcNJrQiv0UTpUt1S2+hcHRF+UDbxWN3DXVPBKKpgTBjFGe7RvGuqOGFBvApvYFehvFO+l9nJR6jjUziAh5aB0YDar1bF4Z0mju/28aD3txCPMjG8iGzhNjOgs2uMLZ6sE5EWwjd1ExNlC+/kLUzVbUwIH+UNvJU3cS4HxAtC9Bp2lbG5VfJU1eOjI3SGI3jtLlL5AvYUlQ7q4vA+o0jYu5yz6zqFe5D/GHi/xgkmtR7fUOgi/ix2mRIeDd/Yv760l3nkFrDIEvyHUCVT26Q0QlZS6RKFFMoElc3GDBKpVTS2/G8zI46aJqysDdfU8UXbxjR0g4q1jqkUwERax5vwTbwnTCinF0oAR+q+cIi+jJAb1CLj5mt1PC3Z+EyeLFSq6Hgl0S2tCrWjV0azVEI0lcEzeQfP1R3sUm6vJqQfRJna69Cx8Tt2C9/dBKY5A2eOgVM3ia+OjlNbw2sjiJ/yOp6oQQR9hH8AEAqCToRqbToAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;낙관적 잠금 query version&quot; title=&quot;&quot; src=&quot;/static/f4483cb15aa12d529de117839109ce5d/29984/2023-08-16-ash-16.png&quot; srcset=&quot;/static/f4483cb15aa12d529de117839109ce5d/29984/2023-08-16-ash-16.png 382w&quot; sizes=&quot;(max-width: 382px) 100vw, 382px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;일부 요청에서 데이터를 읽어왔을 때의 버전 정보와 현재 DB의 버전 정보가 일치하지 않아 요청에 실패한 것이다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1732px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/610cd73b5014848934fa470368cb463b/3553b/2023-08-16-ash-17.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 68.80000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAACLUlEQVR42o2STWsTYRDHV/Doxc/gp5EevAqevKrFmyB4EiFQXw7i2Q8gCGmLHko1tNUIIc0qSds0G7dtdpM02U32Nfu++/eZJ1ntQlv7wD+b4Zn5zcwzI2BxMv6TIUoBdTaXwhSkuNLJMk6AUACy48bAuzbw+gAo/YrxotLDq50eVrZ6eFtV+f+X23OtrO/iTbmK95si4iQtAsMowsFeCyPLw/2Kh7tffNxenUBYXoXwsAzhQRk3n3zC9cdrEB4xe3kN1+6UcGPpGW7dK6HdkYrAiAFFUYRhmuieKOge99Dc20dttwF1eIpDZn+tNSCxO0k+wqEk4Xu1ipOeiv12B/V6vQgsvEeSwPBCqDaT5WMaAkmzgWB7E6hW4AchFCeCanrQwgxp9i9WyB+UlKYpzYUBY3R0D2K3j2+NJsTfY5xufIb8/CmU8gcYfoqNhoTazxZ22n3ExEiTiyuMWfs2q3BgRzjSXXgsSfRjC+b6R6TtFp+8agaQNQdGWIwVgiCAqqpQFAXT6ZRXqWk6dF2D51gYDfuYTnSwCziuO08Yx/zN3YWdrwwHkkEOYRgiSRKcHRL5JWwdInZPiVzH+QukrXDOAxLEsiw4zJlENsENwwBVT2AC+L6PyWTCfS6tkACapmEwGPCWyXk2m/FA+ubyPA8mW6nxeMyTXtoytXN22v87BKcunMUTFIZC2WgosixjOBzyNm3bvlAEodap2tFoxGMLwPMqy+2rKO8uB/4BRJgcXRu8Z8sAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;낙관적 잠금 version 흐름&quot; title=&quot;&quot; src=&quot;/static/610cd73b5014848934fa470368cb463b/3553b/2023-08-16-ash-17.png&quot; srcset=&quot;/static/610cd73b5014848934fa470368cb463b/0eb09/2023-08-16-ash-17.png 500w,
/static/610cd73b5014848934fa470368cb463b/1263b/2023-08-16-ash-17.png 1000w,
/static/610cd73b5014848934fa470368cb463b/3553b/2023-08-16-ash-17.png 1732w&quot; sizes=&quot;(max-width: 1732px) 100vw, 1732px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;위 그림과 같이 트랜잭션 1과 트랜잭션 3은 버전 충돌이 없어 티켓 예매에 성공하였지만, 트랜잭션 2는 트랜잭션 1에서 업데이트한 버전과 충돌이 생겨 update문이 성공적으로 수행되지 못하였다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;낙관적 잠금은 버전 불일치 시 처리를 어플리케이션 레벨에서 담당하게 된다. 이는 티켓 예매 요청이 버전 충돌로 인해 실패할 경우, 직접 예외를 처리하여 재시도하는 로직을 구현해야 함을 뜻한다.&lt;/p&gt;
&lt;p&gt;이러한 재시도 로직을 AOP를 통해 구현해보자.&lt;/p&gt;
&lt;p&gt;먼저 @Retry 어노테이션을 정의한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RUNTIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;METHOD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Retry&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다음은 낙관적 잠금 재시도 로직을 구현한 Aspect이다. 최대 1000번까지 0.1초 간격으로 재시도하도록 했다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Order&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Ordered&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;LOWEST_PRECEDENCE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Aspect&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OptimisticLockRetryAspect&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;MAX_RETRIES&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;RETRY_DELAY_MS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Pointcut&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@annotation(Retry)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;retry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Around&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;retry()&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;retryOptimisticLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProceedingJoinPoint&lt;/span&gt; joinPoint&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; exceptionHolder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; attempt &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; attempt &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;MAX_RETRIES&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; attempt&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; joinPoint&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;proceed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;OptimisticLockException&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ObjectOptimisticLockingFailureException&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StaleObjectStateException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                exceptionHolder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RETRY_DELAY_MS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; exceptionHolder&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다음과 같이 ticketing 메서드에 @Retry 어노테이션을 적용하면 낙관적 잠금에서 버전이 맞지 않을 때 재시도한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retry&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ticketing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; ticketId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; memberId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Ticket&lt;/span&gt; ticket &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ticketRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findByIdForUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ticketId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Ticket Not Found.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    ticket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;increaseReservedAmount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; sequence &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ticket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReservedAmount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    reservationRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Reservation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ticket&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sequence&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; memberId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 328px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/ce10ab1adffb731a10bd79002b5477f7/c4cd0/2023-08-16-ash-18.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 28.04878048780488%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA8klEQVR42mVRSYqEQBDsz7jv7YYK4i4e3FAEl6v/f0IOkdAO03MIKszMiopIX5IkkSiKDwRBYMiyTJqmMUcdc6qqch2nruvc/8ar6zrq+/7BNE00DAOt60rjODIwsywLhWFIcRyT67pU1zUdx0Ge55GiKL+CWZZR0zSU5zmBV1XF323bUpqmTw/ccRyGaZqUJAmVZckcjh9B27Y5jmEYZFkWn5/4GMTr6AOYDYKAZxD9s4Y/ked5puu6OCLibttG53nSvu/Mi6LgmFEUsaP7vtktBCH8b4co4lXf9+n9fvNOwFGDI7jGDwAQFz3c+Xb2EfwBZn/Dynw10SEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 08 16 ash 18&quot; title=&quot;&quot; src=&quot;/static/ce10ab1adffb731a10bd79002b5477f7/c4cd0/2023-08-16-ash-18.png&quot; srcset=&quot;/static/ce10ab1adffb731a10bd79002b5477f7/c4cd0/2023-08-16-ash-18.png 328w&quot; sizes=&quot;(max-width: 328px) 100vw, 328px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;테스트 실행 결과 성공적으로 10장의 티켓이 예매되었지만, 이때 주의할 점이 있다.&lt;/p&gt;
&lt;p&gt;그것은 요청이 들어온 순서대로 처리되는 것이 아니라, 재시도의 타이밍에 따라 결정된다는 점이다.&lt;/p&gt;
&lt;p&gt;이는 요청의 순서에 따라 티켓 번호를 할당해야하는 상황에서는 적절하지 않다.&lt;/p&gt;
&lt;h4&gt;비관적 잠금&lt;/h4&gt;
&lt;p&gt;그러면 이제 비관적 잠금을 통해 해결해 보자.&lt;/p&gt;
&lt;p&gt;아래와 같은 방법으로 비관적 잠금을 적용해 보았다. 우리는 Ticket의 reservedAmount를 갱신해줘야 하기 때문에 PESSIMISTIC_WRITE 즉, 배타적 잠금을 걸었다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TicketRepository&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JpaRepository&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Ticket&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Lock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LockModeType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PESSIMISTIC_WRITE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;select t from Ticket t where t.id = :id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Ticket&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findByIdForUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ticketing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; ticketId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Ticket&lt;/span&gt; ticket &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ticketRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findByIdForUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ticketId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Ticket Not Found.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    ticket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;increaseReservedAmount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; sequence &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ticket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReservedAmount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    reservationRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Reservation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ticket&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sequence&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;테스트 실행 결과 정확히 10장만 발급하는 데 성공했다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 328px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/ce10ab1adffb731a10bd79002b5477f7/c4cd0/2023-08-16-ash-18.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 28.04878048780488%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA8klEQVR42mVRSYqEQBDsz7jv7YYK4i4e3FAEl6v/f0IOkdAO03MIKszMiopIX5IkkSiKDwRBYMiyTJqmMUcdc6qqch2nruvc/8ar6zrq+/7BNE00DAOt60rjODIwsywLhWFIcRyT67pU1zUdx0Ge55GiKL+CWZZR0zSU5zmBV1XF323bUpqmTw/ccRyGaZqUJAmVZckcjh9B27Y5jmEYZFkWn5/4GMTr6AOYDYKAZxD9s4Y/ked5puu6OCLibttG53nSvu/Mi6LgmFEUsaP7vtktBCH8b4co4lXf9+n9fvNOwFGDI7jGDwAQFz3c+Xb2EfwBZn/Dynw10SEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;비관적 잠금 테스트 실행 결과&quot; title=&quot;&quot; src=&quot;/static/ce10ab1adffb731a10bd79002b5477f7/c4cd0/2023-08-16-ash-18.png&quot; srcset=&quot;/static/ce10ab1adffb731a10bd79002b5477f7/c4cd0/2023-08-16-ash-18.png 328w&quot; sizes=&quot;(max-width: 328px) 100vw, 328px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 890px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/dc635a507f55f85f17cc2d170ca160dc/f3cf6/2023-08-16-ash-19.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 57.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABJElEQVR42o2Ta46DMAyEex0IISS8kbY8W0C0vf9tZu0ssLQVpT8sOc5k+OyEU1EUyPMcWZZhvD3QXQdc+gHttccwPXAuK6Rphr5vcZ8u+DnTOklQ1jXG6U76kbQDmq5DHMc4VbTBiecJSCnh+/5TqCCANgbS8+DTvglDW2ftVh+QLtCGDRtEUQTHcSCEeArXdSFZrLXNXapxzkZCuP9a1lFNBXo2JEI+4BHFNljs+8p+mXOusSETvWoXymNCycI/Qq5x/kYoXgmjD4RK2RkuhJxbwnn9Tth8P8N9QjETkmFZ1dZQfD1DczBDIowPLkWbcNNy+KFlmmHdtvaW91peCG3LFPZNUnvLCLaXshomabpLGIYxPeZoPRzTX6NUsBIvIeef4BcieFbPI1s/lgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;비관적 잠금 테스트 실행 테이블&quot; title=&quot;&quot; src=&quot;/static/dc635a507f55f85f17cc2d170ca160dc/f3cf6/2023-08-16-ash-19.png&quot; srcset=&quot;/static/dc635a507f55f85f17cc2d170ca160dc/0eb09/2023-08-16-ash-19.png 500w,
/static/dc635a507f55f85f17cc2d170ca160dc/f3cf6/2023-08-16-ash-19.png 890w&quot; sizes=&quot;(max-width: 890px) 100vw, 890px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;또한 출력 쿼리문에서 select for update문을 통해서 DB의 특정 row에 잠금을 거는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 470px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/f9a3c367ab55bed5c7bef89adb942c3a/c52b7/2023-08-16-ash-20.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 74.8936170212766%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB1klEQVR42p1T207CQBTkN0ATEkPplXu5lNJyvzXCK34H0UQSoz+ARhP93nHnxDWAVaMPhz1dunNmZ6YZ13VRq9XQbDZlrdfr4J7neVLs/1IZ/kynUyRJgtlshuFwCMuy4DiOrP8C1IfJrtPpoN1uo9VqodvtolQqHTE+rVRADer7Pvr9PsIwRBAEGI1GAmiappRt2zKYPVc+81wqIP+kjgQkM1YcxyLD4UpJlsulDGOVy+Uj0COG1WpVmPHKZKtBOYg95SB7PZTvVSqVdEBegS9MJhMB4SH2i8UCURTJ3nq9FgPJjGd49VQNGRdOYtEYsiNbHaNGoyEsuc+e+9+aQrqDwQCr1UomG4bxKbw24XDl+6dGpJqimTIuZKL1Ya+Z/gR0BMjpZMaD1Gw+n0vQec3xeIyRcpYmmAz6L1+PAFJ0xoEOMwYcoL8SW7EyGHwmQYHZqpyPsl0PRdf7CkgNdc7Iklpp5q4C2zp57IpZ7Kwz3Jg5bI0sros53Kr+zj5Pv3KhUBAHCU7GPZW3sRqyiHt49Qu4Ws4xaPpIoh42lwk2iZKmH2Pv5OC7trD9BNT2nwY7DLuoqyH3Xh5vgYOHygX2bQcvURVPgYfHloVnBVhylfMfDN8BTR3cFiDYN00AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;비관적 잠금 select for update&quot; title=&quot;&quot; src=&quot;/static/f9a3c367ab55bed5c7bef89adb942c3a/c52b7/2023-08-16-ash-20.png&quot; srcset=&quot;/static/f9a3c367ab55bed5c7bef89adb942c3a/c52b7/2023-08-16-ash-20.png 470w&quot; sizes=&quot;(max-width: 470px) 100vw, 470px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;이렇게 비관적 잠금을 통해 동시 예매에서 발생하는 문제상황을 해결할 수 있었다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;하지만 모든 상황에서 비관적 잠금이 효율적인 해결책인 건 아니다.&lt;/p&gt;
&lt;p&gt;비관적 잠금은 단일 DB 환경에만 적용 가능하다. 분산 DB 환경에서는 동시성 제어의 효력을 잃는다.&lt;/p&gt;
&lt;p&gt;또한, 많은 대기 시간이 발생한다는 단점도 존재한다. 한 트랜잭션이 완료되기 전까지 다른 트랜잭션들은 대기상태에 빠진다. 동시에 많은 요청이 들어왔을 때, 사실상 하나의 요청씩만 작업할 수 있으므로 요청의 수가 많아지면 대기 시간이 길어진다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;우리는 단일 DB 환경이고, 몇만 건의 동시 요청이 예상되는 상황은 아니므로 위의 단점들이 치명적이진 않다고 판단했다. 따라서 간단하게 적용할 수 있고 데이터의 정합성을 보장하는 비관적 잠금을 통한 해결책을 채택했다.&lt;/p&gt;
&lt;h1&gt;마무리&lt;/h1&gt;
&lt;p&gt;티켓 예매 예제를 통해 동시 예매 상황에서 발생할 수 있는 문제 상황과 그에 대한 해결책들을 알아봤다.&lt;/p&gt;
&lt;p&gt;메세지큐 도입이나 분산 잠금 등의 다른 해결책들도 많이 존재하지만, 우선 가장 간단하게 해결할 수 있는 방법인 비관적 잠금을 통해 해결했다.&lt;/p&gt;
&lt;p&gt;서비스가 커지며 분산 DB 환경이 되거나, 현 상황에서 발생할 수 있는 문제가 발견된다면 그때 다른 구조를 도입하도록 하자.&lt;/p&gt;
&lt;hr&gt;
&lt;h4&gt;참고 자료&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html&quot;&gt;15.7.3 Locks Set by Different SQL Statements in InnoDB | Mysql&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-07-11-database-transaction/&quot;&gt;데이터베이스 트랜잭션 | 테코블&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/jpa-pessimistic-locking&quot;&gt;낙관적 잠금과 비관적 잠금 | Baeldung&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html&quot;&gt;공유 잠금(S lock)과 배타적 잠금(X lock) | Mysql&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[JDBC와 DataSource 이해하기]]></title><description><![CDATA[JDBC란? JDBC(Java Database Connectivity)는 자바 프로그래밍 언어를 사용해 데이터베이스에 접근할 수 있도록 하는 자바 API이다. 이를 통해서 우리는 데이터베이스에 접속하고, SQL…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-06-28-JDBC-DataSource/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-06-28-JDBC-DataSource/</guid><pubDate>Mon, 07 Aug 2023 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;JDBC란?&lt;/h2&gt;
&lt;p&gt;JDBC(Java Database Connectivity)는 자바 프로그래밍 언어를 사용해 데이터베이스에 접근할 수 있도록 하는 자바 API이다.&lt;br&gt;
이를 통해서 우리는 데이터베이스에 접속하고, SQL을 실행하고, 이를 통해 데이터를 가져오거나 삭제하는 등 데이터를 다룰 수 있게 된다.&lt;br&gt;
단순하게 데이터베이스와 통신할 수 있는 기술이구나! 라고 생각할 수도 있지만 그보다 더 중요하게 이해해야 할 것이 있다.&lt;/p&gt;
&lt;h2&gt;JDBC가 등장하게 된 배경&lt;/h2&gt;
&lt;p&gt;JDBC가 등장하게 된 이유는 무엇일까? 바로 결론부터 말하자면 데이터베이스 접근의 표준화를 위해서다.&lt;br&gt;
데이터베이스에는 Oracle Database, MySQL, PostgreSQL 와 같이 여러 종류의 데이터베이스가 있다는 사실을 알고 있을 것이다.&lt;br&gt;
각각 데이터베이스마다 SQL를 전달하거나 결과를 응답받는 방법들이 다 다르고 데이터베이스의 종류는 수십 개가 존재한다.&lt;br&gt;
JDBC가 존재하기 전에는 이런 데이터베이스마다 존재하는 고유한 API를 직접 사용했었다.&lt;br&gt;
이에 따라 개발자는 기존의 데이터베이스를 다른 데이터베이스로 교체해야 하는 경우에는 데이터베이스에 맞게 기존의 코드를 모두 수정해야 했으며 심지어 각각의 데이터베이스를 사용하는 방법도 새로 학습해야 했다.&lt;br&gt;
따라서 표준이라는 게 필요했으며 JDBC의 표준 인터페이스 덕분에 개발자는 데이터베이스를 쉽게 변경할 수 있게 되었고 변경에 유연하게 대처할 수 있게 되었다.&lt;/p&gt;
&lt;h2&gt;JDBC를 알아야 하는 이유&lt;/h2&gt;
&lt;p&gt;JDBC는 JDK 1.1 버전에 출시된 매우 오래된 기술이며 사용하는 방법도 많이 복잡하다.&lt;br&gt;
그래서 JDBC를 직접 사용하기보다는 데이터베이스 접근을 더 편리하게 하고 개발 생산성을 높이기 위한 기술인 SQL Mapper와 ORM(Object-Relational Mapping)을 주로 사용하게 된다.&lt;br&gt;
응?? 그렇다면 JDBC를 굳이 알아야 하나? 라는 의문점이 들 수도 있다.&lt;br&gt;
사실 SQL Mapper나 ORM은 JDBC를 기반으로 동작한다. 이들의 기술은 JDBC의 기능과 개념을 내부적으로 활용하여 데이터베이스와의 상호작용을 처리한다.&lt;br&gt;
따라서 JDBC를 이해하면 SQL Mapper나 ORM이 어떻게 동작하는지 더 잘 이해할 수 있는 베이스가 되며 또한 문제가 발생했을 때 근본적인 문제를 해결할 수 있다.&lt;/p&gt;
&lt;h2&gt;JDBC 동작 흐름&lt;/h2&gt;
&lt;p&gt;그러면 이제 JDBC가 어떤 식으로 구성되어 있고 흐름은 어떻게 흘러가는지 알아보자.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 400px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e4faf004626753e6592dc7e2975fea6c/a6407/2023-06-28-jdbc-architecture.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 82%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wgARCAAQABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAgDBQn/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAHehYpa0b0UAP/EABsQAAMAAgMAAAAAAAAAAAAAAAQFBgIDABEU/9oACAEBAAEFAjevHB5r9jniU8xxVyJuT3bGmubJP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EACcQAAICAQIFAwUAAAAAAAAAAAIDAQQFERIABhMhIhAjMRQVJEJi/9oACAEBAAY/ArUFt2zWfBbyIA06Za7jDyAdPkh8hjvHfjKFU+zyyK7AOce/K2LEBFlWgtO97PTj+Pd3aa+PpzLhX5S8w8CpQOW/GIRQf9epo1m12ptSw4rsWe9RiMsjQiLU505qhORtQzBXH4Qhfh6lMFWVbLEWa01LX5SZXuEVtMdsl34LLY3mnJVK6r97HSi7hceDhbRdKj02WLAmsuxAfUn52/rx/8QAGhABAQADAQEAAAAAAAAAAAAAAREAITEQQf/aAAgBAQABPyGojAFzM70o7xrGLT90lRKUgDahzzub/p8sT1VWBaGTPkgSR6J3uKBA3PKsbhSkDf/aAAwDAQACAAMAAAAQsw//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/ED//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/ED//xAAZEAEBAQEBAQAAAAAAAAAAAAABABEhUZH/2gAIAQEAAT8QPfSAiyXiYWgVNL3aqvQjaqhxaen20bXwXe+k4QQuzh7ocjripHomfO3J/iNdMaz/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;흐름도1&quot;
        title=&quot;&quot;
        src=&quot;/static/e4faf004626753e6592dc7e2975fea6c/a6407/2023-06-28-jdbc-architecture.jpg&quot;
        srcset=&quot;/static/e4faf004626753e6592dc7e2975fea6c/a6407/2023-06-28-jdbc-architecture.jpg 400w&quot;
        sizes=&quot;(max-width: 400px) 100vw, 400px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;먼저 JDBC API를 사용하기 위해서는 JDBC 드라이버를 먼저 로딩한 후 데이터베이스와 연결해야 한다.&lt;br&gt;
JDBC 드라이버는 JDBC 인터페이스를 구현한 구현체라고 생각할 수 있으며&lt;br&gt;
특정 데이터베이스 벤더(Oracle, MySQL, PostgreSQL 등)에 대한 연결과 데이터베이스에 대한 작업을 가능하게 해준다.&lt;br&gt;
(JDBC 드라이버의 구현체를 이용해서 특정 벤더의 데이터베이스에 접근할 수 있음)&lt;br&gt;
JDBC가 제공하는 DriverManager가 드라이버들을 관리하고 Connection을 획득하는 기능을 제공한다.&lt;br&gt;
이 획득한 Connection을 통해서 데이터베이스에 SQL을 실행하고 결과를 응답받을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 792px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a33fc6426aa55649d643c34fcca9416f/59f54/2023-06-28-jdbc-cycle.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 39.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABJ0AAASdAHeZh94AAABRklEQVR42p1Si26DMAzk/39xXUcpKY+EQN4hPG4OrJu0TZo0Syhw2OfzOQUotm0Da3pcrjdcbzUu5R0hzvhPFPu+Iy0LWD/iWnNcKiKuB8K2z6Sck5/f4jteWGsxSgmlNbQcsISAZV3h6QwxItC50vdOUwTv4X1AJPX5PaX5J+E58g7rZ3R8QttJ1K2A6Dl42+GVLCgZRyc1HmSLURrjIFHeG7yUDI1QR21yHjs1Lp6yjYtgbSaTGJTHtm5YU4K2EXcm8CDcUWF0DkqOaBsORo3YQ4BLBUOTLqS6eC5FG41pEEjeHeNmC4w10ErBTRNmZw8FjoomwixhK+VmkmyLpnw3qZPwGWlO8C5gGkdUVYXy7Q23soQSAtEYUnZ6vSwrBmrOeY+hbQ9/85SB6ou/roEnBYoa+DAfy/ra+vlfk9JIy/vYOd4BSuNsJgyfhMcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;흐름도2&quot;
        title=&quot;&quot;
        src=&quot;/static/a33fc6426aa55649d643c34fcca9416f/59f54/2023-06-28-jdbc-cycle.png&quot;
        srcset=&quot;/static/a33fc6426aa55649d643c34fcca9416f/0eb09/2023-06-28-jdbc-cycle.png 500w,
/static/a33fc6426aa55649d643c34fcca9416f/59f54/2023-06-28-jdbc-cycle.png 792w&quot;
        sizes=&quot;(max-width: 792px) 100vw, 792px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위의 과정을 코드로 간단하게 살펴보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;DriverManager를 통해 Connection 획득&lt;/li&gt;
&lt;li&gt;Connection을 통해 Statement 생성&lt;/li&gt;
&lt;li&gt;질의 수행 및 Statement를 통해 ResultSet 생성&lt;/li&gt;
&lt;li&gt;ResultSet을 통해 데이터 획득&lt;/li&gt;
&lt;li&gt;리소스 정리&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sql&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Jdbc&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 0. 데이터베이스 연결 정보 설정&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jdbc:mysql://localhost:3306/mydatabase&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; username &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;myusername&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; password &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;mypassword&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;Connection&lt;/span&gt; connection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Statement&lt;/span&gt; statement &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ResultSet&lt;/span&gt; resultSet &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 1. DriverManger를 통해 Connection 획득&lt;/span&gt;
            connection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DriverManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; username&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token comment&quot;&gt;// 2. Connection을 통해 Statement 생성&lt;/span&gt;
            statement &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createStatement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SELECT * FROM users&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token comment&quot;&gt;// 3. 질의 수행 및 Statement를 통해 ResultSet 생성&lt;/span&gt;
            resultSet &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; statement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;executeQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sql&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token comment&quot;&gt;// 4. ResultSet을 통해 데이터 획득&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resultSet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; resultSet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; resultSet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; age &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; resultSet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;age&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

                &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ID: &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;, Name: &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;, Age: &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; age&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SQLException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 5. 리소스 정리&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resultSet &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    resultSet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;statement &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    statement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connection &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SQLException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;참고로 DriverManager를 사용하기 전에 &lt;code class=&quot;language-text&quot;&gt;Class.forName(&quot;com.mysql.jdbc.Driver&quot;);&lt;/code&gt; 을 통해 해당 드라이버를 불러와야 하는데&lt;br&gt;
JDBC 4.0 버전 이후부터는 JDBC 드라이버 자동 로딩이 도입되어 생략이 가능하다.&lt;br&gt;
해당 코드에 관한 자세한 설명은 생략하기로 하고 다음으로 넘어가 보자.&lt;/p&gt;
&lt;h2&gt;커넥션 풀과 DataSource&lt;/h2&gt;
&lt;p&gt;위의 과정에서 우리는 DriverManager를 통해 데이터베이스 커넥션을 획득했다.&lt;br&gt;
JDBC 드라이버가 데이터베이스와 연결을 맺고 부가 정보를 데이터베이스에 전달한 후 연결 응답을 받으면 커넥션를 생성해 넘겨준다.&lt;br&gt;
커넥션을 사용하고 나면 해당 커넥션을 종료시킨다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DriverManger 사용시 ▼&lt;br&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1054px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/586216a16ddf201879ab077be50bcfbb/33740/2023-06-28-jdbc-driverManager.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 47.199999999999996%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABGUlEQVR42o2S2a6EQAhE/f+PNHEdd2fGfeX2IYMx8eWSoHY1FAXoyT9sWRbZ9139OI4LX9f18m3bFPPO85RhGOT7/Uqe55KmqRRFIV3XKQ5BkiRSVZX6NE0XGTG8Kdj3vZJ6JJBIIIRhGEocx/J+vxW3guBRFOk3icTP8/xQ63EgqK5radtWlX4+H/0Gx4zAyDDOptZiVCEP5I7jKFmWqZLX66UKSbjPDEMxhjpaZQxN02jcpZBE5gHx3U2BkZyO4HRJbjtKdm+ZM65LMUIqMUeUmkLuCazd3ey6WMtSdpbhOgN/ECKVdlEEWRAE4vu+lC6RItxDHLtNd67I4vD1t137C2yxOkPYDUSNuWGPllkKyn7Kbdv2H/4BXOO8wquB5fIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 06 28 jdbc driverManager&quot; title=&quot;&quot; src=&quot;/static/586216a16ddf201879ab077be50bcfbb/33740/2023-06-28-jdbc-driverManager.png&quot; srcset=&quot;/static/586216a16ddf201879ab077be50bcfbb/0eb09/2023-06-28-jdbc-driverManager.png 500w,
/static/586216a16ddf201879ab077be50bcfbb/1263b/2023-06-28-jdbc-driverManager.png 1000w,
/static/586216a16ddf201879ab077be50bcfbb/33740/2023-06-28-jdbc-driverManager.png 1054w&quot; sizes=&quot;(max-width: 1054px) 100vw, 1054px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;하지만 이렇게 매번 커넥션을 생성하기 위해서는 네트워크와 연결하고 서버의 자원을 사용해야 한다.&lt;br&gt;
SQL을 실행할 때마다 커넥션을 획득해야 하므로 연결하는 데에 추가적인 시간이 걸리며 매번 리소스를 사용하게 된다.&lt;br&gt;
그래서 이러한 문제점을 해결하기 위해 커넥션 풀(Connection Pool)을 사용하게 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;커넥션 풀 사용시 ▼&lt;br&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1348px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/51dd80adc51785771c42bface43aea2e/f7833/2023-06-28-jdbc-connectionPool.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 53.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABGElEQVR42oWT6aqFMAyEff931H/uorjv5vD1MlLPFU4h2CaZySStgb2s67oee9l5no/Y2wqU7IP9tSyLJUliURRZ13V2HMejGEXwCRe0bWtpmloYhhbHsfV9b3Vd2zzPNo6jIwSwbZut6+pMZOyJY9M0/RHiFBgnwaZpXIE8z11sGAajMDkUhBzj7HfiCCEhCQBfAiTjBwBhVVWubeL4aRNjjyAKSrkjRFGWZVaW5Q1gCVQUxT0OijACDCIpvQlRxEEz4qwBA4KQmUohcV0ERMp9tExLKEAhLe777kCameLcMnHIyIGEM3ZfCgcSAWpGKoIqYpBDoMv4frP+cwu+3x5gKqOW2VJQl/BG+O9hvzmlyFcl+/WnfADIT1ufP7PPDQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 06 28 jdbc connectionPool&quot; title=&quot;&quot; src=&quot;/static/51dd80adc51785771c42bface43aea2e/f7833/2023-06-28-jdbc-connectionPool.png&quot; srcset=&quot;/static/51dd80adc51785771c42bface43aea2e/0eb09/2023-06-28-jdbc-connectionPool.png 500w,
/static/51dd80adc51785771c42bface43aea2e/1263b/2023-06-28-jdbc-connectionPool.png 1000w,
/static/51dd80adc51785771c42bface43aea2e/f7833/2023-06-28-jdbc-connectionPool.png 1348w&quot; sizes=&quot;(max-width: 1348px) 100vw, 1348px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;데이터베이스와의 연결이 필요할 때마다 매번 새로운 커넥션을 생성하는 대신 미리 생성된 커넥션을 커넥션풀에 보관하고 필요할 때마다 커넥션을 꺼내서 사용하는 것이다.&lt;br&gt;
커넥션 풀을 통해 커넥션을 사용하고 나면 커넥션을 종료하지 않고 커넥션 풀에 반납한다.&lt;br&gt;
이와 같은 방법으로 커넥션을 재사용하게 되면 리소스를 효율적으로 활용할 수 있으며 성능을 높일 수 있다.&lt;/p&gt;
&lt;p&gt;이처럼 커넥션을 획득할 때 DriverManager를 통해서 커넥션을 획득하거나 커넥션풀을 통해서 커넥션을 획득하는 등 여러 방법이 존재한다.&lt;br&gt;
그래서 DataSource 인터페이스를 통해서 커넥션을 획득하는 방법을 추상화한다.&lt;br&gt;
어떤 방식으로 커넥션을 획득하는지 상관없이 DataSource 인터페이스를 통해 일관된 방식으로 데이터베이스와 통신할 수 있는 것이다.&lt;br&gt;
또한 인터페이스를 구현한 구현체를 쉽게 교체할 수 있어 애플리케이션의 유연성을 높일 수 있다.&lt;br&gt;
스프링과 스프링 부트에서는 DataSource 인터페이스를 구현한 여러 구현체를 제공하는데&lt;br&gt;
대표적으로 JDBC DriverManager 기반으로 한 DriverManagerDataSource와 HikariCP 커넥션 풀을 기반으로 한 HikariDataSource가 있다.&lt;br&gt;
그래서 만약에 DB에 연결할 때마다 커넥션을 생성해서 획득하는 게 아닌 미리 커넥션을 생성해 놓은 커넥션풀을 사용해서 커넥션을 획득하고 싶은 경우&lt;br&gt;
DataSource의 구현체 DriverManagerDataSource를 HikariDataSource로 바꾸어 끼기만 하면 되는 것이다..!
이로써 커넥션을 획득할 때 리소스를 효율적으로 활용할 수 있고 빠른 속도로 커넥션을 획득할 수 있다.&lt;/p&gt;
&lt;h2&gt;마치며&lt;/h2&gt;
&lt;p&gt;지금까지 JDBC와 DataSource에 대해서 알아보았다.&lt;br&gt;
개발을 하면서 데이터 접근 기술을 처음 사용할 때 대부분 JDBC가 아닌 SQL Mapper와 ORM을 먼저 접하게 된다.&lt;br&gt;
그래서 JDBC의 전체적인 동작 흐름에 대한 이해가 부족한 채로 다른 기술을 사용하는 경우가 있으며&lt;br&gt;
또한 JDBC를 직접 사용하지 않는다고 해서 JDBC에 대한 학습을 넘어가는 경우가 있다.&lt;br&gt;
필자 역시 그랬으며 필자와 비슷한 다른 분들에게도 많은 도움이 되기를 바라며 글을 마치도록 하겠다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tutorialspoint.com/jdbc/jdbc-introduction.htm&quot;&gt;What is JDBC? - JDBC 개념&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://www3.ntu.edu.sg/home/ehchua/programming/java/JDBC_Basic.html&quot;&gt;JDBC Programming by Examples with MySQL - JDBC 사용법&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://ittrue.tistory.com/250&quot;&gt;JDBC와 커넥션풀&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[React 18 - 동시성 렌더링]]></title><description><![CDATA[들어가며 2022년 3월, React 18이 발표되었습니다. 여러 변경 사항이 있었지만 그 중 핵심은 Concurrency(동시성)입니다. 기존 experimental 버전에서만 동작하던 ‘concurrent mode…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-07-09-concurrent_rendering/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-07-09-concurrent_rendering/</guid><pubDate>Mon, 31 Jul 2023 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;들어가며&lt;/h2&gt;
&lt;p&gt;2022년 3월, React 18이 발표되었습니다.&lt;/p&gt;
&lt;p&gt;여러 변경 사항이 있었지만 그 중 핵심은 Concurrency(동시성)입니다.&lt;/p&gt;
&lt;p&gt;기존 experimental 버전에서만 동작하던 ‘concurrent mode’를 공식 지원하게 되었고, 따라서 명칭도 ‘concurrent features’가 되었습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This means there is no concurrent mode, only concurrent features. - &lt;a href=&quot;https://react.dev/blog/2021/12/17/react-conf-2021-recap#react-18-and-concurrent-features&quot;&gt;React 18 and concurrent features&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 글에선 React의 Concurrency에 대해 가볍게 살펴보고, 이를 사용하기 위해 추가된 &lt;code class=&quot;language-text&quot;&gt;useTransition&lt;/code&gt; hook과 &lt;code class=&quot;language-text&quot;&gt;useDeferredValue&lt;/code&gt; hook에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;h2&gt;Concurrency?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://react.dev/blog/2022/03/29/react-v18&quot;&gt;React 공식 문서&lt;/a&gt;에선 Concurrency를 다음과 같이 설명하고 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Concurrency is not a feature, per se. It’s a new behind-the-scenes mechanism that enables React to prepare multiple versions of your UI at the same time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;즉, 동시성 그 자체로는 기능이 아니고, React에서 여러 버전의 UI를 동시에 준비할 수 있도록 하는 메커니즘이라는 겁니다.&lt;/p&gt;
&lt;p&gt;그렇다면 어떤 메커니즘인지, Concurrency와 함께 자주 언급되는 Parallelism(병렬성)과 비교해 보면 이해가 쉬울 것 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1920px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/235cd72b9aeff9752983dccae652b048/aaf92/concurrency_vs_parallelism.jpg&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 56.199999999999996%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAUHAgj/xAAXAQADAQAAAAAAAAAAAAAAAAAEBQYI/9oADAMBAAIQAxAAAAHrZlJqwdLPzQi0Z//EABwQAAICAgMAAAAAAAAAAAAAAAIFAwQAAQYSFP/aAAgBAQABBQJVw+m2Qq1+mK/zDUMG7OtArkONf22ef//EACMRAAEDBAEEAwAAAAAAAAAAAAIBAwQFESEiEgAGBxMUIzH/2gAIAQMBAT8B8VyYtQrLkanxvhSI3aTsaS9q175ST6XeTzZ+wrqirvtmy/pKrIEDLQOFzcBsBM87GIohFnOxJfOev//EACIRAQABAwEJAAAAAAAAAAAAAAECAxIhEQAEBRMiQVFx8f/aAAgBAgEBPwHicJ06N1Sd5LfCcTLbT5c+jOPvrRRVDQVQ8HY2/8QAIhAAAwACAgICAwEAAAAAAAAAAQIDBBIFEQATITEiUWEk/9oACAEBAAY/AuV56vIctHJ46eQ8cXDyMJMX/JM0U3jfj60eNm+L6ZYqVBEkmdWaEzl5uH6137wnx0Zy99OqHIxcntFBJUIJnfpmZlXQ1x1elfVas/dbQ2to5T2WM5ymasFG5nKSE/KzX68fjoZ2RLByCTbFV+o0NAEcsn7ZQAT/ADzH0dk2Ug6nrsCjMPr9MAfN2JZn7dmP2zN+TE/0kk+f/8QAGxABAQADAQEBAAAAAAAAAAAAAREAITFRQWH/2gAIAQEAAT8hWuVAtfYhzTdiP3GH6J1ttnkhTKGlndPMqFBYe8T/AI8Q9fIN0xpYWowc6cJ4ni3aWSSpqX1Y/XP/2gAMAwEAAgADAAAAEJwP/8QAFxEBAQEBAAAAAAAAAAAAAAAAAREAIf/aAAgBAwEBPxAIAljOkNqZAiJMEtA0ru3CosCtSrv/xAAaEQEAAgMBAAAAAAAAAAAAAAABESEAMVFh/9oACAECAQE/EJoXn4RHSB1AKhgGPZ1pCtFUIVzP/8QAGBABAQEBAQAAAAAAAAAAAAAAAREAITH/2gAIAQEAAT8QIfAtCFEYgRU93ZDudKFZpjTVPg8vP+PqwVNJ2OGcnJRxgHLqRnCIl08jAIHFSAwnN+qMvqSED//Z&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;concurrency vs parallelism&quot; title=&quot;&quot; src=&quot;/static/235cd72b9aeff9752983dccae652b048/aaf92/concurrency_vs_parallelism.jpg&quot; srcset=&quot;/static/235cd72b9aeff9752983dccae652b048/953fe/concurrency_vs_parallelism.jpg 500w,
/static/235cd72b9aeff9752983dccae652b048/0a251/concurrency_vs_parallelism.jpg 1000w,
/static/235cd72b9aeff9752983dccae652b048/aaf92/concurrency_vs_parallelism.jpg 1920w&quot; sizes=&quot;(max-width: 1920px) 100vw, 1920px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;em&gt;&lt;a href=&quot;https://deview.kr/2021/sessions/518&quot;&gt;- DEVIEW 2021, Inside React (동시성을 구현하는 기술)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;위에서 설명하고자 하는 것을 간단히 표로 만들어 보겠습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concurrency&lt;/th&gt;
&lt;th&gt;Parallelism&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;싱글 코어에서도 작동&lt;/td&gt;
&lt;td&gt;멀티 코어에서만 작동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;동시에 실행되는 것처럼 보임&lt;/td&gt;
&lt;td&gt;실제로 동시에 실행 됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;논리적 개념&lt;/td&gt;
&lt;td&gt;물리적 개념&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;요약하자면, 실제론 동시에 실행하지는 않지만, CPU가 현재 실행중인 프로세스를 중단하고 다른 프로세스를 실행하는 작업인 context switching을 하면서 ‘동시에 실행되는 것처럼 보이게 하는 것’이 바로 Concurrency라고 할 수 있겠습니다.&lt;/p&gt;
&lt;p&gt;그렇다면 왜 React에서 Concurrency가 필요하게 된 걸까요? 🤔&lt;/p&gt;
&lt;h2&gt;Blocking Rendering&lt;/h2&gt;
&lt;p&gt;React 18 이전까지는 렌더링 시 발생하는 문제점이 하나 있었습니다.&lt;/p&gt;
&lt;p&gt;브라우저의 메인 스레드는 싱글 스레드이기 때문에 한 번에 한 가지의 작업만 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;HTML을 파싱하거나 JS를 실행하거나 화면에 렌더링하는 등, 모든 작업을 하나씩 처리해 나갑니다.&lt;/p&gt;
&lt;p&gt;그런데 만약 연산이 오래 걸리는 화면을 렌더링해야 한다면 어떻게 될까요?&lt;/p&gt;
&lt;p&gt;페이지 지연이 발생하여 사용자가 불편을 겪는 Blocking Rendering이 발생하게 됩니다.&lt;/p&gt;
&lt;p&gt;다음은 해당 문제가 발생하는 예시 코드입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setText&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; filteredItems &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;filterItems&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;updateFilter&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;container&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;input type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text&quot;&lt;/span&gt; onChange&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;updateFilter&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;List items&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;filteredItems&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;List&lt;/code&gt;는 1부터 10000까지의 숫자를 리스트로 출력하는 컴포넌트입니다.&lt;/p&gt;
&lt;p&gt;숫자를 입력받을 수 있고, 입력한 숫자를 포함한 것만 필터링하여 출력합니다.&lt;/p&gt;
&lt;p&gt;따라서 입력값이 바뀔 때마다 리스트도 업데이트되기 때문에 새로 렌더링해야 합니다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/327f5704deefbaebd0b5dde6e8f63f57/e7448/cpu-6x-slowdown.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 26.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAABEElEQVR42jWPyU7EQAxE8wtc58AwC4Hs6XQnnX0hCSQizAziMBL//x+F3RIHy3ap9Fy2lMqgdYGyrBFGAm03outHqFRDKW3moqgRRQmadqB9wuFwhPv4AD8I4LoOHMfBbrdDFApYZdkgywpIAjMgJFEIhSCI4fkREkl6miOl0ro04PP5BU/7PU6nZ9j2K2kCSaLQND0Da+R5ZcychKFZlkMSiItBkjRp9MIcZGgcK6NpXaGnL8bxHS19YHEaXi6Xb8zzavr2dcW6brjdfjAME3xKyhDXDShJirJqoSnENC9YyDdT53dt22GgxLZdcb//GvA4fRgjz133hon2Zfk0Hv4gDATqujfpOP2/hw97Xog/1XKliPz6EKgAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;cpu 6x slowdown&quot; title=&quot;&quot; src=&quot;/static/327f5704deefbaebd0b5dde6e8f63f57/e7448/cpu-6x-slowdown.png&quot; srcset=&quot;/static/327f5704deefbaebd0b5dde6e8f63f57/0eb09/cpu-6x-slowdown.png 500w,
/static/327f5704deefbaebd0b5dde6e8f63f57/e7448/cpu-6x-slowdown.png 900w&quot; sizes=&quot;(max-width: 900px) 100vw, 900px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;차이점을 확인하기 쉬운 무거운 작업으로 만들기 위해, 크롬 개발자 도구로 CPU 성능 제한을 걸고 테스트해 보았습니다.&lt;/p&gt;
&lt;img src=&quot;/93e842ba9acfdb658d644dda482d010d/blocking-rendering.gif&quot;&gt;
&lt;p&gt;숫자를 입력하거나, 백스페이스를 계속 눌러도 인풋 창은 매우 느리게 응답하는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;누가 보더라도 바람직하지 못한 UX인 것 같습니다.&lt;/p&gt;
&lt;p&gt;이러한 문제를 해결하기 위해 추가된 기능이 바로 &lt;code class=&quot;language-text&quot;&gt;useTransition&lt;/code&gt; hook입니다.&lt;/p&gt;
&lt;h2&gt;&lt;code class=&quot;language-text&quot;&gt;useTransition&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;useDeferredValue&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;바로 예시코드부터 살펴봅시다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setText&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;isPending&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; startTransition&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useTransition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; filteredItems &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;filterItems&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;updateFilter&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;startTransition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;setText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;container&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;input type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text&quot;&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;user-input&quot;&lt;/span&gt; onChange&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;updateFilter&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;label htmlFor&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;user-input&quot;&lt;/span&gt; className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;isPending &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;show&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;hide&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        pending&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;label&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;List items&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;filteredItems&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;useTransition&lt;/code&gt;은 2개의 원소를 가진 배열을 반환합니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;isPending&lt;/code&gt;이라는 boolean 값과, &lt;code class=&quot;language-text&quot;&gt;startTransition&lt;/code&gt; 함수입니다.&lt;/p&gt;
&lt;p&gt;간단하게 코드를 설명하자면, 우선 &lt;code class=&quot;language-text&quot;&gt;startTransition&lt;/code&gt;으로 &lt;code class=&quot;language-text&quot;&gt;setText&lt;/code&gt;를 래핑하여 상태 업데이트를 낮은 우선순위로 설정합니다.&lt;/p&gt;
&lt;p&gt;그러면 더 우선순위가 높은 이벤트(input값 변경)가 있는 경우 &lt;code class=&quot;language-text&quot;&gt;setText&lt;/code&gt;는 지연 시키고 이전 값을 보여주게 됩니다.&lt;/p&gt;
&lt;p&gt;또한 &lt;code class=&quot;language-text&quot;&gt;isPending&lt;/code&gt;을 이용하여 현재 지연된 상태라는 것도 보여주고 있습니다.&lt;/p&gt;
&lt;img src=&quot;/7548d2a00abf75271b4ec1e0d76a59b8/use-transition.gif&quot;&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;useTransition&lt;/code&gt;을 적용하니 리스트 렌더링과는 별개로, 인풋 창 반응이 빨라졌습니다!&lt;/p&gt;
&lt;p&gt;이로써 조금 더 나은 사용자 경험을 제공할 수 있을 것 같습니다.&lt;/p&gt;
&lt;p&gt;그런데 비슷한 기능을 하는 또 다른 hook이 있습니다.&lt;/p&gt;
&lt;p&gt;바로 &lt;code class=&quot;language-text&quot;&gt;useDeferredValue&lt;/code&gt;입니다.&lt;/p&gt;
&lt;p&gt;이 함수는 &lt;code class=&quot;language-text&quot;&gt;useTransition&lt;/code&gt;과 비슷하게 우선순위를 낮춰서 concurrent features를 사용할 수 있는 hook입니다.&lt;/p&gt;
&lt;p&gt;차이점으론 ‘상태를 업데이트하는 코드’를 래핑하여 우선순위를 낮추는 &lt;code class=&quot;language-text&quot;&gt;useTransition&lt;/code&gt;과 달리 &lt;code class=&quot;language-text&quot;&gt;useDeferredValue&lt;/code&gt;는 ‘값’의 업데이트 우선순위를 낮춘다는 것입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It’s useful when the value comes “from above” and you don’t actually have control over the corresponding setState call.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;또한 React 팀원인 &lt;a href=&quot;https://github.com/reactjs/rfcs/pull/212#issuecomment-1077901737&quot;&gt;Dan Abramov의 말&lt;/a&gt;에 따르면
&lt;code class=&quot;language-text&quot;&gt;useDeferredValue&lt;/code&gt;는 상태를 props로 받는 등 제어할 수 없을 때 사용하는 것을 권장하고 있습니다.&lt;/p&gt;
&lt;p&gt;그러면 이제 예제 코드에 적용해 보겠습니다.&lt;/p&gt;
&lt;p&gt;사실 현재 코드에선 상태를 &lt;code class=&quot;language-text&quot;&gt;App&lt;/code&gt;에서 제어할 수 있지만, 그렇게 하지 않고 하위 컴포넌트인 &lt;code class=&quot;language-text&quot;&gt;List&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;useDeferredValue&lt;/code&gt;를 사용해 보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;List&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; items &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ul&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;items&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;li key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;li&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;ul&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;List&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; items &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; deferredItems &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useDeferredValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;items&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ul&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;deferredItems&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;li key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;li&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;ul&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위에는 기존 &lt;code class=&quot;language-text&quot;&gt;List&lt;/code&gt; 컴포넌트 코드고, 아래가 바꾼 코드입니다.&lt;/p&gt;
&lt;p&gt;기존에 props로 받던 &lt;code class=&quot;language-text&quot;&gt;items&lt;/code&gt;를 &lt;code class=&quot;language-text&quot;&gt;useDeferredValue&lt;/code&gt;로 래핑하였습니다.&lt;/p&gt;
&lt;p&gt;이후 &lt;code class=&quot;language-text&quot;&gt;App&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;useTransition&lt;/code&gt;을 사용하지 않고 실행해도,&lt;/p&gt;
&lt;p&gt;결과적으로 &lt;code class=&quot;language-text&quot;&gt;startTransition&lt;/code&gt;을 사용했을 때와 유사한 결과를 얻을 수 있었습니다.&lt;/p&gt;
&lt;h2&gt;🤔 어디서 본 것 같은데?&lt;/h2&gt;
&lt;p&gt;hook을 살펴보다 보니 비슷한 기능을 봤던 것 같습니다.&lt;/p&gt;
&lt;p&gt;바로 ‘debounce’와 ‘throttle’입니다.&lt;/p&gt;
&lt;p&gt;다만 두 방법 Blocking Rendering을 해결하기엔 아쉬운 점이 있습니다.&lt;/p&gt;
&lt;p&gt;여러 번 발생하는 이벤트에서 가장 마지막 또는 제일 처음 이벤트만을 실행하도록 하는 debounce와,&lt;/p&gt;
&lt;p&gt;여러 번 발생하는 이벤트를 일정 시간 동안 한 번만 실행되도록 만드는 throttle 모두 개발자가 timeout을 지정해 줘야 합니다.&lt;/p&gt;
&lt;p&gt;그 말인즉슨, 만약 사용자의 기기 성능은 뛰어난데 timeout이 길게 설정되어 오래 기다리게 되거나,&lt;/p&gt;
&lt;p&gt;반대로 오래된 기기를 사용하는데 너무 자주 이벤트가 실행되어 화면이 버벅대면, 사용자는 매우 불쾌한 경험을 하게 된다는 것입니다.&lt;/p&gt;
&lt;p&gt;그러므로 concurrent features를 잘 활용한다면, debounce와 throttle을 대체하거나, 혹은 함께 사용하여 더 좋은 사용자 경험을 제공할 수 있을 것입니다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;지금까지 React 18에 concurrent features가 추가된 배경과, 관련 hook들을 알아보았습니다.&lt;/p&gt;
&lt;p&gt;물론 언제나 &lt;code class=&quot;language-text&quot;&gt;useTransition&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;useDeferredValue&lt;/code&gt;를 사용하는 것이 좋은 최적화 방법은 아닐 것입니다.&lt;/p&gt;
&lt;p&gt;lazy loading, pagination 등의 기법을 사용하거나, 서버 사이드에서 연산하는 등 다양한 방법이 존재합니다.&lt;/p&gt;
&lt;p&gt;따라서 무작정 남용하지 않고, 최적화할 수 있는 다른 방법은 없는지 충분히 고민한 후, 적용해야 하겠습니다.&lt;/p&gt;
&lt;p&gt;어쨌든 프론트엔드 개발을 하다보면 UX를 우선하여 생각하게 되는 경우가 많은 만큼,&lt;/p&gt;
&lt;p&gt;멋진 UX를 위해 고려할 수 있는 수단이 늘어나는 것은 언제나 환영입니다! 😆&lt;/p&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://react.dev/blog/2022/03/29/react-v18&quot;&gt;React v18.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-07-24-concurrent-mode&quot;&gt;Tecoble - 사용자 경험 개선 2편 - react concurrent mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://deview.kr/2021/sessions/518&quot;&gt;DEVIEW 2021 - Inside React (동시성을 구현하는 기술)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/guridaek/react-18-concurrent-rendering&quot;&gt;예시 코드 저장소&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[@Configuration은 어떻게 빈을 등록하고, 싱글톤으로 관리할까?]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-05-22-configuration/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-05-22-configuration/</guid><pubDate>Wed, 28 Jun 2023 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;어노테이션을 사용해서 빈을 생성하는 방법은 크게 두 가지가 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;@Bean&lt;/code&gt;을 사용해 빈 설정 정보 클래스 생성&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@Component&lt;/code&gt;와 이를 상속한 &lt;code class=&quot;language-text&quot;&gt;@Repository&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;@Controller&lt;/code&gt; 어노테이션등을 사용해 자바 컴포넌트 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;은 어떻게 빈을 등록하고, 싱글톤으로 관리하는지 알아보자!&lt;/p&gt;
&lt;h2&gt;@Configuration이란?&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;은 빈을 등록할 수 있는 어노테이션 중 하나이다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html&quot;&gt;Spring 공식 문서&lt;/a&gt;에서는 다음과 같이 설명하고 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Indicates that a class declares one or more &lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@Bean&lt;/code&gt;&lt;/a&gt; methods and may be processed by the Spring container to generate bean definitions and service requests for those beans at runtime&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt; 어노테이션을 사용하면, 하나 이상의 &lt;code class=&quot;language-text&quot;&gt;@Bean&lt;/code&gt;이 붙은 메서드가 포함된 빈 설정용 클래스를 만들 수 있다.&lt;/li&gt;
&lt;li&gt;스프링 컨테이너는 &lt;code class=&quot;language-text&quot;&gt;@Bean&lt;/code&gt;이 붙은 메서드를 통해 빈을 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;으로 어떻게 빈을 등록하는지 자세히 살펴보자!&lt;/p&gt;
&lt;h3&gt;@Configuration 사용 예시&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 빈으로 등록할 클래스&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyBean&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sayHello&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;안녕하세요, MyBean입니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Config&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyBean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;myBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ApplicationContext&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;이 붙은 클래스 내부에서 &lt;code class=&quot;language-text&quot;&gt;@Bean&lt;/code&gt;이 붙은 메서드들을 찾는다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@Bean&lt;/code&gt; 이 붙은 메서드들은 자바 객체를 반환하는 메서드이다.&lt;/li&gt;
&lt;li&gt;반환된 자바 객체는 스프링 컨테이너에 빈으로 등록된다.&lt;/li&gt;
&lt;li&gt;빈의 이름은 &lt;code class=&quot;language-text&quot;&gt;@Bean&lt;/code&gt;이 붙은 메서드 이름이 된다. (이 코드의 경우 메서드명과 같은 &lt;code class=&quot;language-text&quot;&gt;myBean&lt;/code&gt; 빈이 생성된다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;등록한 빈 가져오기 - getBean()&lt;/h2&gt;
&lt;p&gt;앞서 말했듯이, &lt;code class=&quot;language-text&quot;&gt;ApplicationContext&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt; 클래스가 정의한 빈에 대한 정보를 전부 읽어서 등록한다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ApplicationContext&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;getBean()&lt;/code&gt;메서드를 통해 빈에 대한 정보를 가져올 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;getBean()&lt;/code&gt;에 들어가는 인자는 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;getBean(빈의 이름, 빈의 타입);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;getBean(빈의 이름)만으로도 빈을 가져올 수 있긴 하지만 Object 타입으로 반환되기 때문에 타입 변환이 필요하다. 빈의 타입을 안다면 명시해 주는 게 좋다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;getBean() 사용 예시&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// getBean()을 사용해서 빈 객체를 불러올 수 있음&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootTest&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfigTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;ApplicationContext&lt;/span&gt; applicationContext&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;MyBean&lt;/span&gt; bean &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; applicationContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;myBean&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        bean&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sayHello&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// 실행 결과
안녕하세요, MyBean입니다.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Configuration의 속성&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RUNTIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Documented&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Configuration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;token annotation punctuation&quot;&gt;@AliasFor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;annotation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Component&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;proxyBeanMethods&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;에는 두 개의 속성이 존재한다. 속성 &lt;code class=&quot;language-text&quot;&gt;value&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;이 붙은 클래스가 빈으로 등록될 때의 이름을 설정할 수 있게 해준다.&lt;/p&gt;
&lt;p&gt;우리가 이번에 자세히 알아볼 속성은 &lt;code class=&quot;language-text&quot;&gt;proxyBeanMethods&lt;/code&gt;이다. &lt;code class=&quot;language-text&quot;&gt;proxyBeanMethods&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;이 빈을 싱글톤으로 관리하는 것과 연관이 있는 속성이다. 아래에서 더 살펴보자!&lt;/p&gt;
&lt;h3&gt;proxyBeanMethods&lt;/h3&gt;
&lt;p&gt;빈에 대한 프록시 객체를 생성할지 여부를 결정한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;디폴트 값은 true이다. 즉, 디폴트로 빈에 대한 프록시 객체가 생성된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;code class=&quot;language-text&quot;&gt;proxyBeanMethods = true&lt;/code&gt;일 때의 config빈의 상태&lt;/h4&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1123px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/c54b8bda5edc3072031d7daf77f56bc3/323a1/configuration-config-value-proxy-true.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 13.799999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAhklEQVR42j2OSRLDMAgE/bHEskAskirLIf9/ygRkJ4euAapo2IZX7JVQuKEQo1Q+k9o/a1McLIusE1YHi4FEAzv7YBsuEM6FS1LPPJYwZCHJxZ+I1NDMId6XiMRBOb/Em7jiMzte18VmHRKoD9iY0D4xHi/MZ/JeoiSlJR7Z2XA/GLdSIwlfH3dsxrCsqX0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;configuration config value proxy true&quot; title=&quot;&quot; src=&quot;/static/c54b8bda5edc3072031d7daf77f56bc3/323a1/configuration-config-value-proxy-true.png&quot; srcset=&quot;/static/c54b8bda5edc3072031d7daf77f56bc3/0eb09/configuration-config-value-proxy-true.png 500w,
/static/c54b8bda5edc3072031d7daf77f56bc3/1263b/configuration-config-value-proxy-true.png 1000w,
/static/c54b8bda5edc3072031d7daf77f56bc3/323a1/configuration-config-value-proxy-true.png 1123w&quot; sizes=&quot;(max-width: 1123px) 100vw, 1123px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;h4&gt;&lt;code class=&quot;language-text&quot;&gt;proxyBeanMethods = false&lt;/code&gt;일 때의 config빈의 상태&lt;/h4&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 820px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/4b2eaee47e8072a8e1b403f9d9ffd623/0bbcd/configuration-config-value-proxy-false.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 18.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAqUlEQVR42k2Paw7DIAyDOdemtkAIz8I29f5n8ULGpP745AARtk2rHpYDLBE8RzhiuBB/rNkLds2cKyhlUMwIokG1yH1BlDdzdcKIjN0TbAw4RHfnBVI2u+alhxfzP2JyZxqaIOna+UYXt8w3x0WqDbE0JCG3E7UPSdgQ6oCXXeKk+5pW1LjAuF6MjxyGXE6nWWfW97r8qzY/n2ZaN1Vw6Vp/Jn1sB567Vb7pVJBzY76VywAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;configuration config value proxy false&quot; title=&quot;&quot; src=&quot;/static/4b2eaee47e8072a8e1b403f9d9ffd623/0bbcd/configuration-config-value-proxy-false.png&quot; srcset=&quot;/static/4b2eaee47e8072a8e1b403f9d9ffd623/0eb09/configuration-config-value-proxy-false.png 500w,
/static/4b2eaee47e8072a8e1b403f9d9ffd623/0bbcd/configuration-config-value-proxy-false.png 820w&quot; sizes=&quot;(max-width: 820px) 100vw, 820px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;두 &lt;code class=&quot;language-text&quot;&gt;config&lt;/code&gt;의 차이가 보이는가? 프록시 객체로 생성한 빈의 클래스 이름을 보면 &lt;code class=&quot;language-text&quot;&gt;$$EnhancerBySpringCGLIB&amp;amp;&amp;amp;&lt;/code&gt; 라는 게 추가된 것을 알 수 있다.&lt;/p&gt;
&lt;h4&gt;CGLIB?&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;cglib is a powerful, high performance and quality Code Generation Library. It is used to extend Java classes and implements interfaces at runtime.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Byte Code Generation Library is high level API to generate and transform JAVA byte code. It is used by AOP, testing, data access frameworks to generate dynamic proxy objects and intercept field access.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;바이트 코드를 가지고 프록시 객체를 만들어주는 라이브러리이다. 런타임 시에 &lt;strong&gt;자바 클래스를 상속&lt;/strong&gt;하고 인터페이스를 구현해 동적 프록시 객체를 만든다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code class=&quot;language-text&quot;&gt;proxyBeanMethods&lt;/code&gt;가 true인 상태에서 사용되는 &lt;code class=&quot;language-text&quot;&gt;config&lt;/code&gt; 빈은 우리가 직접 생성한 객체가 아니라 &lt;code class=&quot;language-text&quot;&gt;CGLIB&lt;/code&gt; 라이브러리에서 생성해 준 프록시 객체임을 의미한다.&lt;/p&gt;
&lt;h3&gt;왜 프록시 객체를 생성할까?&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html&quot;&gt;Spring 공식 문서 - Configuration : proxyBeanMethods&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Specify whether &lt;code class=&quot;language-text&quot;&gt;@Bean&lt;/code&gt; methods should get proxied in order to enforce bean lifecycle behavior, e.g. to return shared singleton bean instances even in case of direct &lt;code class=&quot;language-text&quot;&gt;@Bean&lt;/code&gt; method calls in user code. This feature requires method interception, implemented through a runtime-generated CGLIB subclass which comes with limitations such as the configuration class and its methods not being allowed to declare &lt;code class=&quot;language-text&quot;&gt;final&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;스프링은 싱글톤 타입인 빈을 만들기 위해 프록시 객체를 생성한다. &lt;code class=&quot;language-text&quot;&gt;CGLIB&lt;/code&gt;라이브러리를 사용해 &lt;code class=&quot;language-text&quot;&gt;Configuration&lt;/code&gt; 클래스를 그대로 사용하지 않고, &lt;code class=&quot;language-text&quot;&gt;Configuration&lt;/code&gt;을 상속한 프록시 객체를 새로 만든다고 한다.&lt;/p&gt;
&lt;p&gt;아래는 그 예시이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Config&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 정의한 Configuration 클래스&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyBean&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfigExt&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Config&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 실제로 빈을 반환하는 데 사용되는 클래스&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; beans &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyBean&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;beans&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;containsKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;MyBean&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            beans&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;MyBean&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;super&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;MyBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MyBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; beans&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;MyBean&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;물론 실제 코드는 이보다 더욱 복잡하다고 한다. 예시용으로 참고만 하자.&lt;/p&gt;
&lt;p&gt;기존 클래스를 상속해야 하므로, &lt;code class=&quot;language-text&quot;&gt;Configuration&lt;/code&gt;은 final 클래스로 생성할 수 없다고 한다.&lt;/p&gt;
&lt;h3&gt;proxyBeanMethods 적용 예시&lt;/h3&gt;
&lt;h4&gt;&lt;code class=&quot;language-text&quot;&gt;proxyBeanMethods = true&lt;/code&gt; 일 때&lt;/h4&gt;
&lt;p&gt;사용 예시를 위해 빈으로 등록할 세 개의 클래스를 생성했다. (&lt;code class=&quot;language-text&quot;&gt;BabyBean&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;MotherBean&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;FatherBean&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;세 클래스는 모두 생성이 될 때, &lt;code class=&quot;language-text&quot;&gt;BabyBean&lt;/code&gt;을 출력한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BabyBean&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BabyBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;애기 빈 : &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MotherBean&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MotherBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;BabyBean&lt;/span&gt; babyBean&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;엄마 빈 : &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; babyBean&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FatherBean&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FatherBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;BabyBean&lt;/span&gt; babyBean&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;아빠 빈 : &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; babyBean&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;세 클래스를 &lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;을 사용해 빈으로 등록해 보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Config&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BabyBean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;babyBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BabyBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MotherBean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;motherBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MotherBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;babyBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FatherBean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fatherBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FatherBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;babyBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// 실행 결과

애기 빈 : com.example.controlleradvicetest.config.BabyBean@2d0566ba
엄마 빈 : com.example.controlleradvicetest.config.BabyBean@2d0566ba
아빠 빈 : com.example.controlleradvicetest.config.BabyBean@2d0566ba&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;실행 결과를 보면, &lt;code class=&quot;language-text&quot;&gt;FatherBean&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;MotherBean&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;BabyBean&lt;/code&gt; 생성자로 출력된 &lt;code class=&quot;language-text&quot;&gt;BabyBean&lt;/code&gt; 객체가 전부 같은 것을 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;이는 &lt;code class=&quot;language-text&quot;&gt;BabyBean&lt;/code&gt; 객체는 한 번 생성된다는 것을 의미한다. (싱글톤)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;code class=&quot;language-text&quot;&gt;proxyBeanMethods = false&lt;/code&gt; 일 때&lt;/h4&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1625px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/fe58fecf4c95e11a54a2dcb8878248df/6f6d1/configuration-proxy-false.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 33.599999999999994%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABJ0AAASdAHeZh94AAAA2klEQVR42qXR2WrDMBCFYb2OLUWr7bZObIxJmtRrk0Ch7/8gfxUHUuh2kV58zCDBcEYShU8IweN8wBiNWSmklHcTwVvyYLHW/GvQbaBeSayOrEMpRZIkpGl6N1HkjjxzPBQhrp6R5WFJ+539xeed1hrhnMbGt3NWo2LkdrtlOp7oxol+mhluJoZ4dqn9OC6+9lVdI7wz5N5Q5D4ONrT7A69v73TzmefhzK4/xnpi143sXoafHXr2w8y6bhBSpksyqa6/+1iWNG1L3TSUm4qn9ebq0v+hrGqyouADf//jQTLzs8cAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;configuration proxy false&quot; title=&quot;&quot; src=&quot;/static/fe58fecf4c95e11a54a2dcb8878248df/6f6d1/configuration-proxy-false.png&quot; srcset=&quot;/static/fe58fecf4c95e11a54a2dcb8878248df/0eb09/configuration-proxy-false.png 500w,
/static/fe58fecf4c95e11a54a2dcb8878248df/1263b/configuration-proxy-false.png 1000w,
/static/fe58fecf4c95e11a54a2dcb8878248df/6f6d1/configuration-proxy-false.png 1625w&quot; sizes=&quot;(max-width: 1625px) 100vw, 1625px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;ul&gt;
&lt;li&gt;동일한 &lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt; 클래스 내에서는 &lt;code class=&quot;language-text&quot;&gt;proxyBeanMethod&lt;/code&gt;가 false 이면 아예 할당이 안 되는 것을 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Cofiguration&lt;/code&gt; 클래스를 두 개로 분리해서 의존성을 주입해 주면 작동하지 않을까?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;proxyBeanMethods &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// babyBean 프록시 객체를 생성하지 않는 @Configuration 클래스&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Config&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BabyBean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;babyBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BabyBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ParentConfig&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Config&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// babyBean을 생성하는 @Configuration 빈 객체를 주입받는다&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MotherBean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;motherBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MotherBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;babyBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// babyBean 주입&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FatherBean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fatherBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FatherBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;babyBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// babyBean 주입&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfigTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;AnnotationConfigApplicationContext&lt;/span&gt; context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AnnotationConfigApplicationContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ParentConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;BabyBean&lt;/span&gt; babyBean &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;babyBean&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BabyBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;babyBean&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;MotherBean&lt;/span&gt; motherBean &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;motherBean&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MotherBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;FatherBean&lt;/span&gt; fatherBean &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fatherBean&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FatherBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// 실행 결과

애기 빈 : com.example.controlleradvicetest.config.BabyBean@c8b96ec

애기 빈 : com.example.controlleradvicetest.config.BabyBean@2fa7ae9
엄마 빈 : com.example.controlleradvicetest.config.BabyBean@2fa7ae9

애기 빈 : com.example.controlleradvicetest.config.BabyBean@7577b641
아빠 빈 : com.example.controlleradvicetest.config.BabyBean@7577b641&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;proxyBeanMethods = false&lt;/code&gt;로 설정하니, &lt;code class=&quot;language-text&quot;&gt;babyBean&lt;/code&gt;이 싱글톤으로 생성되지 않는 것을 볼 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;motherBean&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;fatherBean&lt;/code&gt;에 &lt;code class=&quot;language-text&quot;&gt;babyBean&lt;/code&gt;을 주입할 때, &lt;code class=&quot;language-text&quot;&gt;config&lt;/code&gt;에서 새로운 &lt;code class=&quot;language-text&quot;&gt;babyBean&lt;/code&gt;객체를 생성해 주입해 주는 것을 알 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;@Configuration vs @Component&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;은 내부적으로 &lt;code class=&quot;language-text&quot;&gt;@Component&lt;/code&gt;를 상속받고 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RUNTIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Documented&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Configuration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Component&lt;/code&gt;를 사용해서 원하는 클래스를 빈으로 등록할 수 있는데 &lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;이 왜 필요한 걸까?&lt;/p&gt;
&lt;p&gt;포인트는 &lt;code class=&quot;language-text&quot;&gt;@Component&lt;/code&gt;는 구현한 클래스 위에 선언해야 하지만, &lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;@Bean&lt;/code&gt;이 붙은 메서드 내부에서 생성한 객체를 빈으로 등록할 수 있다는 점이다.&lt;/p&gt;
&lt;p&gt;외부에서 구현한 클래스를 빈으로 등록하고 싶은데, 이 클래스가 read-only로 쓰였다면 &lt;code class=&quot;language-text&quot;&gt;@Component&lt;/code&gt;를 클래스 위에 선언할 수 없다. &lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;을 사용하면, 메서드 내부에서 해당 클래스를 호출해 반환함으로써 빈으로 등록할 수 있다.&lt;/p&gt;
&lt;p&gt;또, 하나의 클래스에서 빈들을 관리하고 싶거나, 특정 패키지 내부에 있는 빈들만 스프링 컨테이너에 등록하고 싶은 경우는 &lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;을 사용하면 편리하게 관리할 수 있다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;으로 빈을 등록하는 방법과 &lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;이 빈을 싱글톤으로 관리하는 방법에 대해 알아보았다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;@Bean&lt;/code&gt;이 붙은 메소드에서 호출한 객체를 빈으로 등록할 수 있기에 외부 라이브러리의 클래스도 빈으로 등록하는 것이 가능하다. 또한 &lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;은 빈 객체를 프록시 객체로 만들어 싱글톤으로 관리한다는 것을 알 수 있었다.&lt;/p&gt;
&lt;p&gt;여러 개의 빈을 함께 관리하고 싶을 때, 외부 라이브러리 등 개발자가 관리할 수 없는 클래스를 빈으로 등록하고 싶은 경우에는 &lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;을 활용해 보도록 하자.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;최범균 지음, 스프링5 프로그래밍 입문&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html&quot;&gt;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/cglib/cglib/wiki&quot;&gt;https://github.com/cglib/cglib/wiki&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[이미지 사이즈 최적화]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-05-02-image-size-optimization/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-05-02-image-size-optimization/</guid><pubDate>Thu, 08 Jun 2023 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;들어가며&lt;/h2&gt;
&lt;p&gt;사용하고자 하는 서비스의 기능은 잘 구현되어 있지만, 사용자에게 제공하는 시간이 느리다면 좋은 서비스라고 할 수 있을까? 사용자의 경험을 고려해 봤을 때, 기능적인 측면도 중요하지만 다양한 기기와 네트워크 환경에서도 원활하게 제공하기 위해서 속도는 중요한 요소이다. 실제로, BBC는 페이지 로드 시간이 &lt;code class=&quot;language-text&quot;&gt;1초&lt;/code&gt; 추가될 때마다 사용자 수가 추가로 &lt;code class=&quot;language-text&quot;&gt;10%&lt;/code&gt; 감소한다는 것을 발견했다. 속도 향상을 위해 최적화에 관심을 가지게 되었고, 이 중 이미지를 최적화하는 간단한 방법을 작성해 본다.&lt;/p&gt;
&lt;h2&gt;웹 페이지 용량에서 이미지가 차지하는 비중&lt;/h2&gt;
&lt;p&gt;웹 페이지를 보여주기 위해 서버에 리소스(HTML, CSS, Javascript, 이미지, 동영상, 폰트 등)를 요청해 받아온다. 그렇다면 웹 페이지 용량에서 이미지가 차지하는 비중은 얼마일까?&lt;/p&gt;
&lt;figure align=&quot;center&quot;&gt;
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 600px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/657d55ea6d818e410b3011e6b090ce49/dface/2023-05-02-image-in-web.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 61.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAAB4UlEQVR42o2SbWvTUBTH8139BoIg4qu9HRN8IQVxDGEoA0EQpmDnCy2buM25OGYf0q5N22TtYrYkN/fmOX/PvWmllVk94XBzn373fx40kJUllLW7Jj4fneG81UPbMHH2o4vByMbxaVP9GxcjGL0hTr630KHxq97EyJpikaFhwQImMJm6cK99DIYWQdoYjm2cNw10+yZ6/SFanZ7aYyGHfenAD8JFRAUsigJpluFPS/IUHgvAOYfjevB8H3meY5UpoDwUx7HSHcYCTHBaK2haqscqL5WrtbnP9uRaOYtZU+rSFFEUVXmcmjCsMQSPb1Uwv3ybK6BPYcRJoqBSwYfOIR5t7+Bd/VAdkKlQSlaAloAsYEgIKEOWFw8GOu6uP0Ft64064AmGG5/RfvpPhdK1YAasFBbY75/iweOneP7yvQJ86pzgdb1BLWNhXsC/AqVCzgUBU6VQqm70v+HeRg3Ptt8qwM5RHXfur2O3/kXNoySmVuFVgehL8wwiSparPK+0VHpwoWOttoUXr/akHuzqDTzc2MTHfV0GDMM2cUwN7VMaJHzi/oQ9cdRdqX4JmFHYIQ8xti8RUuNmVBAuBK5vPNWLgjrhigCWPaWIKO8S6Di4ctzfKdPwnyZTw1hIj6xu7F8j6pL4w7lwQgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 05 02 image in web&quot; title=&quot;&quot; src=&quot;/static/657d55ea6d818e410b3011e6b090ce49/dface/2023-05-02-image-in-web.png&quot; srcset=&quot;/static/657d55ea6d818e410b3011e6b090ce49/0eb09/2023-05-02-image-in-web.png 500w,
/static/657d55ea6d818e410b3011e6b090ce49/dface/2023-05-02-image-in-web.png 600w&quot; sizes=&quot;(max-width: 600px) 100vw, 600px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
  &lt;figurecaption&gt;출처: Web Almanac&lt;/figurecaption&gt;
&lt;/figure&gt;
&lt;br&gt;
&lt;p&gt;위의 통계에서 이미지가 웹 페이지 용량의 가장 큰 비중을 차지하는 것을 볼 수 있다. 이미지가 많은 페이지라면 이 용량을 줄이는 것만으로 성능 향상을 가져올 수 있을 것이다. 이미지의 용량을 줄이는 여러 가지 방법 중, 이미지 사이즈 설정을 통한 방법에 대해 알아보자.&lt;/p&gt;
&lt;h2&gt;렌더링 사이즈에 맞추기&lt;/h2&gt;
&lt;p&gt;특정 크기로 보이는 이미지에서 이미지 사이즈 설정 전후 웹페이지 로딩 속도 차이를 비교해 보았다.
(아래의 내용은 리액트 개발 환경에서 진행했다.)&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1000px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2615565940dc9154fd38683596e9ce03/1263b/2023-05-02-image-size-test-1.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 57.599999999999994%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABcElEQVR42qWSzWrCQBCAcylISJAgRShWPHhpe/DgawievHkp1fpTFRp0U9JiSx/NB/AdSg/2B0wMNj+bNdPZ6JZahbY68LGbycy3ye5Kx0eans3njZOzwiCTyZF0OkNkOUkSCYXISnKJvEJZ5gWKohEtdUiSWspQk5quqmpK6vdJYN4NwbwdRsQwodvTod3pQbPVgYtaA2r1BtQvmzhvxs+tdgfpQvuqB9f6AO4fHiPjxuTzsFo9L0hRFLkAECEhwgSYZ9PplFmWxWaz2Rffa1bwvgXij8fjghRgAM8sFlwKXkDh3bLB9zzwEN/34xHfw6ruJ3EfasJYSCldE/qUgj2fA8V1XNeNZULIwS9fQ/ShZ4sQC2jIwPlwIQxDvmoM1myI/iQURTzEKGIv4X/YEIpDYYxFWzb8V3jf2qHgXgXir2C3iPvQsxTatv2E9+sNmSAvOzBxHOcV7+vzaDQ6lUqlUrZcLuf2pVKpZIvF4sEnDewFE1a/xGQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 05 02 image size test 1&quot; title=&quot;&quot; src=&quot;/static/2615565940dc9154fd38683596e9ce03/1263b/2023-05-02-image-size-test-1.png&quot; srcset=&quot;/static/2615565940dc9154fd38683596e9ce03/0eb09/2023-05-02-image-size-test-1.png 500w,
/static/2615565940dc9154fd38683596e9ce03/1263b/2023-05-02-image-size-test-1.png 1000w&quot; sizes=&quot;(max-width: 1000px) 100vw, 1000px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;직경 60px 크기의 원에 이미지를 넣는다고 가정해 보자. 상단의 이미지는 원본 이미지로 용량이 &lt;code class=&quot;language-text&quot;&gt;389kb&lt;/code&gt;이고, 하단의 이미지는 크기를 축소한 이미지로 용량이 &lt;code class=&quot;language-text&quot;&gt;12.8kb&lt;/code&gt;이다. 보이는 크기에 맞게 축소했을 뿐인데 용량을 &lt;code class=&quot;language-text&quot;&gt;97%&lt;/code&gt;나 줄일 수 있다. 이번엔, 여러 네트워크 상황에서 두 이미지를 받아오는 시간을 비교해 보자.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1000px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/e6e4fe830dfbaf68dce3b7a9476f8664/1263b/2023-05-02-image-size-test-2.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 57.599999999999994%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACSklEQVR42oWSz08TQRTHG4KmIRBiDAcNqQ1HjemBQw8cOHLsiXghWlSKKChpwhJD4r9k9OLFeOCqFg8aicH+2N222x/7++cw2/n6dgUCQeIkn+zO253PvHlvMnduT1dzc3O79+4XdmZnc9LMzC0pe31CGh/PSmNjFxlP4ufIZqelyckbUnZiepfeq1NTUzcz5cpLtrktYWNrR6y/qOJxZQsPVzew+mQDa5XnZ1SIcvkpyqtreHTyXH+2ib29N2JHeo1X21W+srJSyLz96gXvap54f+Dzjz+i+NPhKP7wfRQfaiIGcAHDNGPLtmPLsmLHcWKT5jQ4fRsRUa1WK2SaDZnJag/awBS9oQVFM6DpLvq6A8Ny4IcMA8tGGIYIowiMMfi+DxIhGaPRSCRPivNUKNfrzDBMdHs9obQ76Gh9KO0uhhTTaAMviGB5XioKguAMEp2SCo+Pj/8K6y2FqSQgifjdaKEpqzhqNNFUVNqkT5lFcIIQnPNkEVgCyYUQKZeEcrPB1E4X/aEukiMmEllt09yAaXv0I0+Pliw+P64Uur7H3Igj4rFIsqBSwHFcuH5ANWNpZqeL/8Ul4UA3mOuHVDNLmJYFs9+FrWsw9AFsEicZnqvXJag5F5tCEpZ0zXZ9Yesd+O0vCIZ18OFPRCEHjzn+M1IhneSkhorSUtV2nzrclVt1Tf31WVOPDohvmqJ0NN0wNMe2NftqunQne3Qnlf39/buZhYWFXHGxmC8WExbzxcUHRClfIJL40tJSvlQq/Zfl5eXc/Pz8tT/qF/A+J6MihAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 05 02 image size test 2&quot; title=&quot;&quot; src=&quot;/static/e6e4fe830dfbaf68dce3b7a9476f8664/1263b/2023-05-02-image-size-test-2.png&quot; srcset=&quot;/static/e6e4fe830dfbaf68dce3b7a9476f8664/0eb09/2023-05-02-image-size-test-2.png 500w,
/static/e6e4fe830dfbaf68dce3b7a9476f8664/1263b/2023-05-02-image-size-test-2.png 1000w&quot; sizes=&quot;(max-width: 1000px) 100vw, 1000px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1000px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/6d44c09f4f007969e7b25e1fb8a3aaae/1263b/2023-05-02-image-size-test-3.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 57.599999999999994%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACVElEQVR42oWSy08TURTGG4OmIRBiDAsNqQ1LjemCRRcsWLLsirghWlSKKChp0hJD4t+kbtwQF2wNYEyIxEhpO+9O59mZ6dzOzP08U4RQ8XGT35zcmTnfPed8N3P3zlQ1Nzu7ff9BoTYzk6tPT9+uZ7Pj9bGxbP3ab4zdGB8hOz5Vn5i4mcbtbHaqOjk5eStTrrxiG1t1rG/W+NrLKp5UNvFoZR0rT9exWnlxQYUol5+hvLKKx7/i2vMN7Oy85bXaG7zeqkbLy8uFzLt9L3i/7/EPh360exTGn46T+ONREh+rPAYwgmlZsW07hB27rhtbtKcV0beECA8ODgqZ5mmbtUUNim7xjmFDVLqQdRcdw4Fpu/D7DLrtoN/vox+GYIzB932QENKVJAlPI72PhoJis8noVCiqxgVZhqxqaEsyuqYFtWvDC0LYnjcUCoLgAhI6Zyg4GAzOBBstgYmyCkGS+clpC822iJNGEy1BokM6VFkIN+gjiqI0CSyFxDnnQ64ICq1TJkgKOrrBLacHTdMhiBI0vQvL9ejHaNhamnx5/VXQ9W3mhdTOIOGGa0On+fTChKoagFNLaWXnyX/iiqBuGqznBzQzm3c6GoyuBMdUYZtdaGRKwEiYJ5dnNgKZM2qKZTksdc3pedw1FPjyIXp6A4nxDV91A1Z45uZow6Pdpw/q5EywLQgtUZI6ZIoitBqq9P2zKv44JL6ooiiplmmqjuP8C4XupEZ3Utjb27uXmZ+fzxUXivliMWUhX1x4SJTyhRTaLy4u5kul0n9ZWlrKzc3NXf8Jj53vxY+jDnMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 05 02 image size test 3&quot; title=&quot;&quot; src=&quot;/static/6d44c09f4f007969e7b25e1fb8a3aaae/1263b/2023-05-02-image-size-test-3.png&quot; srcset=&quot;/static/6d44c09f4f007969e7b25e1fb8a3aaae/0eb09/2023-05-02-image-size-test-3.png 500w,
/static/6d44c09f4f007969e7b25e1fb8a3aaae/1263b/2023-05-02-image-size-test-3.png 1000w&quot; sizes=&quot;(max-width: 1000px) 100vw, 1000px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1000px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/39e0467d36301bad467b1008a55b6aa1/1263b/2023-05-02-image-size-test-4.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 57.599999999999994%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACU0lEQVR42o2SzWvUQBTAl1IllC5FRESpy9KjInvoYQ899NjjnoqXoq3ardXWamVTVPBfEj15EA89Ct3qoVDFNrubr9mPJJM0X7NJ5vkmpdJSvx78GOYl78ebN1O4cX1iszQ1tXXrdqUxOVmSr165JksXx+TRUUkeGTnLqMifQpIm5PHxS7I0NrElScUXxWLxcmGx/pStPZNhdb3BV548h/v1dbi7tApLD1Zhuf74F3VkcfEhsgz3xLq0DCuP1uD1qze80XgJGxubycLCQqXwdscP3+34/P1ukHzci9NP+1n6YS9L9wlPAeAMtuOk1HVTSmnqeV7q4B4jwW8ZEjebzUqhpXRYR+uC2Xd4z6KgmgMw+h70LBds6kEQMehTF6IogiiOgTEGQRAAikBElmVcrJhPcqGmKIxigUm6XDMMMEgXOroBA9sBMnDADxlQ389FYRghYQ6KTsiFw+HwWHjYVplmENB0gx8obWh1NDhQWtBSNTC7PewsBg9FSZKIImAClHPOc84JOy2FqTqBbt/iNnYqOmyjDPfguD4MkyQ/mig+HX8U2tRh1pEvElzMKWZDCMIY4phBJDrDWZ0U/45zwl7fYi4KLZty27bBtQh4tgm21ccOj/DH5PS8zoGXc/ZSHOqyIAiBHvnctQzw9V0IBgpY2lcIEnwNPIN/RC7EGR8LVVVta5reU3XD7LQPif79M9F/fCHqtyYxDJNg18R13b9h4pvs4ptUt7e3bxZmZmZK1epsuVqtlitinbtTrs7WyhVE5OZqtXLtP5ifny9NT09f+AmHmvAIkWGeigAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2023 05 02 image size test 4&quot; title=&quot;&quot; src=&quot;/static/39e0467d36301bad467b1008a55b6aa1/1263b/2023-05-02-image-size-test-4.png&quot; srcset=&quot;/static/39e0467d36301bad467b1008a55b6aa1/0eb09/2023-05-02-image-size-test-4.png 500w,
/static/39e0467d36301bad467b1008a55b6aa1/1263b/2023-05-02-image-size-test-4.png 1000w&quot; sizes=&quot;(max-width: 1000px) 100vw, 1000px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;원본 이미지가 현재 사용하는 네트워크 속도에서 &lt;code class=&quot;language-text&quot;&gt;2배&lt;/code&gt;, Fast 3G 환경에서 &lt;code class=&quot;language-text&quot;&gt;4배&lt;/code&gt;, Slow 3G 환경에서 &lt;code class=&quot;language-text&quot;&gt;4배&lt;/code&gt; 더 오래 걸리는 것을 확인할 수 있다.&lt;/p&gt;
&lt;h2&gt;반응형 이미지&lt;/h2&gt;
&lt;p&gt;이번에는 데스크톱과 모바일 환경에서 이미지를 생각해 보자. 두 환경은 화면의 크기가 다르므로, 보이는 이미지의 크기도 다르다. 데스크톱의 큰 화면과 모바일의 작은 화면을 비교했을 때, 데스크톱의 큰 이미지는 모바일에서 필요하지 않을 수 있다. 이런 상황에서 반응형 이미지를 사용할 수 있다. 반응형 이미지는 환경에 맞게 적절한 사이즈를 제공해 용량을 줄이는 방법이다.&lt;/p&gt;
&lt;p&gt;반응형 이미지는 아래와 같이 사용할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;horong-480w.jpg 480w, horong-800w.jpg 800w&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;sizes&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;(max-width: 480px) 440px,
            800px&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;horong-800w.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;밥상 위에 올라간 호롱이&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;srcset: 브라우저에 제시할 이미지 목록과 크기를 정의한다. 480w, 800w는 이미지의 고유 픽셀 너비로 단위는 w를 사용한다.&lt;/li&gt;
&lt;li&gt;sizes: 화면 크기 조건을 정의한다. 특정 크기일 때 이미지를 크기를 나타낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위와 같이 작성하면 브라우저는 다음과 같이 확인한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;기기 너비 확인&lt;/li&gt;
&lt;li&gt;sizes 목록에서 조건 확인&lt;/li&gt;
&lt;li&gt;해당 조건에서 이미지 크기 확인&lt;/li&gt;
&lt;li&gt;크기에 근접한 srcset에 작성한 이미지를 불러온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;img width=&quot;600px&quot; height=&quot;473px&quot; src=&quot;/d4677fe83b3380bc135a1a7d4f6b067c/2023-05-02-responsive-image-test.gif&quot;&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;69.1kb&lt;/code&gt;의 이미지를 모바일 환경에서 &lt;code class=&quot;language-text&quot;&gt;29kb&lt;/code&gt;로 용량을 &lt;code class=&quot;language-text&quot;&gt;58%&lt;/code&gt; 줄였다!&lt;/p&gt;
&lt;h2&gt;실전 적용&lt;/h2&gt;
&lt;p&gt;테코블에 글을 올리기 전 프로필을 만들었다. 프로필 배경에 적용하고 싶은 사진이 있는데 사진의 크기가 &lt;code class=&quot;language-text&quot;&gt;3.7mb&lt;/code&gt;이다..! 용량을 줄이기 위해 위에서 작성한 내용을 적용해 보았다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;렌더링 사이즈에 맞추기&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이미지 자르기: 2,560x2,560 -&gt; 2,560x1,280, &lt;code class=&quot;language-text&quot;&gt;3.7mb -&gt; 2.2mb&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;이미지 사이즈 축소: 2,560x1,280 -&gt; 1240x620, &lt;code class=&quot;language-text&quot;&gt;2.2mb -&gt; 1.3mb&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;이미지 압축: 75%, &lt;code class=&quot;language-text&quot;&gt;1.3mb -&gt; 227kb&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이미지 압축을 하면 불필요한 용량을 줄일 수 있다. 보통 75% 정도(이미지 내용과 용도에 따라 다를 수 있다)의 압축은 사용자가 차이를 인지하지 못해 동일하게 보인다. 비슷한 품질로 용량을 줄일 수 있는 방법이다.&lt;/p&gt;
&lt;p&gt;위의 과정을 거쳐 이미지의 용량이 &lt;code class=&quot;language-text&quot;&gt;98%&lt;/code&gt; 감소했다!&lt;/p&gt;
&lt;h2&gt;나오며&lt;/h2&gt;
&lt;p&gt;리소스의 용량은 페이지 로드 속도에 큰 영향을 준다. 그중 이미지는 가장 큰 비율을 차지하고 있다. 이미지 최적화 방법 중에 이미지 사이즈를 줄이는 방법을 정리해 보면 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;렌더링 사이즈에 맞추기: 원본 이미지를 렌더링 되는 사이즈에 맞게 축소하여 용량을 줄인다.&lt;/li&gt;
&lt;li&gt;반응형 이미지: 스크린 크기에 맞게 이미지를 제공해 용량을 줄인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이외에도 알맞은 이미지 포맷 사용하기, 이미지 압축 등을 통해 이미지의 용량을 줄일 수 있다. 간단한 방법으로 속도와 성능을 높이고 사용자 경험을 개선해 보자!&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://almanac.httparchive.org/en/2022/page-weight&quot;&gt;Web Almanac - Page Weight&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/serve-images-with-correct-dimensions/&quot;&gt;web.dev - 올바른 크기의 이미지 제공&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images&quot;&gt;MDN - 반응형 이미지&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.creativebloq.com/features/how-the-bbc-builds-websites-that-scale&quot;&gt;Creative Bloq - How the BBC builds websites that scale&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[ExceptionHandler와 ControllerAdvice를 알아보자]]></title><description><![CDATA[콘솔 애플리케이션을 구현할 때, 우리는 예외를 핸들링하기 위해 try / catch…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-05-03-ExceptionHandler-ControllerAdvice/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-05-03-ExceptionHandler-ControllerAdvice/</guid><pubDate>Sun, 21 May 2023 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;콘솔 애플리케이션을 구현할 때, 우리는 예외를 핸들링하기 위해 try / catch문을 사용했습니다.&lt;/p&gt;
&lt;p&gt;그러나 웹 애플리케이션에서는 예외 처리 방법이 조금 다릅니다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 스프링을 사용한 웹 애플리케이션 적용할 수 있는 예외 처리 방법인 &lt;code class=&quot;language-text&quot;&gt;@ExceptionHandler&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;@ControllerAdvice&lt;/code&gt;에 대해 알아보겠습니다.&lt;/p&gt;
&lt;!-- end --&gt;
&lt;h2&gt;스프링부트의 기본적인 예외 처리&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/errorExample&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invokeError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;라는 예시에서 예외를 던지는 메서드를 호출하는 URL &lt;code class=&quot;language-text&quot;&gt;/errorExample&lt;/code&gt;에 요청을 보내봅시다.&lt;/p&gt;
&lt;p&gt;웹으로 요청을 보낸다면 스프링부트가 제공하는 기본 에러 페이지가 표시됩니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;BasicErrorController&lt;/code&gt; 는 요청의 Accept 헤더 값이 text/html일 때, 예외가 발생하면 &lt;code class=&quot;language-text&quot;&gt;/error&lt;/code&gt; 라는 경로로 재요청을 보내줍니다.&lt;/p&gt;
&lt;p&gt;해당 페이지는 &lt;code class=&quot;language-text&quot;&gt;/error&lt;/code&gt; 경로에 등록된 기본 에러 페이지입니다.&lt;/p&gt;
&lt;p&gt;만약 기본 에러 페이지가 아닌 스스로 작성한 커스텀 페이지를 보여주고 싶다면 뷰 템플릿 경로에 커스텀 페이지 파일을 만들어서 넣어두면 됩니다.&lt;/p&gt;
&lt;p&gt;오류 페이지 파일명에 따라 표시되는 웹페이지를 다르게 설정할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4xx.html: 400대 오류 페이지&lt;/li&gt;
&lt;li&gt;5xx.html: 500대 오류 페이지&lt;/li&gt;
&lt;li&gt;404.html: 404 오류 페이지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;만약 페이지 변경뿐 아니라 더 상세하게 예외의 내용을 응답에 담고 싶다면, &lt;code class=&quot;language-text&quot;&gt;BasicErrorController&lt;/code&gt;를 상속한 &lt;code class=&quot;language-text&quot;&gt;@Controller&lt;/code&gt; 클래스를 만들어 &lt;code class=&quot;language-text&quot;&gt;errorHtml()&lt;/code&gt; 메서드와 &lt;code class=&quot;language-text&quot;&gt;error()&lt;/code&gt; 메서드를 재정의해주면 됩니다.&lt;/p&gt;
&lt;p&gt;다만 이 방법은 url만 알면 누구나 마음대로 error 페이지에 접근할 수 있다는 단점이 있습니다.&lt;/p&gt;
&lt;p&gt;이러한 단점을 &lt;code class=&quot;language-text&quot;&gt;@ExceptionHandler&lt;/code&gt;를 사용해 해결할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;@ExceptionHandler&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@ExceptionHandler&lt;/code&gt; 어노테이션을 사용하면 value로 원하는 예외를 지정하고 이를 핸들링 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;예외에 대한 세부적인 정보 또한 응답으로 전달해 줄 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ExceptionHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invokeError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;error Message&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;BAD_REQUEST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위 처럼 컨트롤러별로 반환하고자 하는 body와 메시지를 예외별로 적절하게 선택하여 세밀한 정보를 제공할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@ExceptionHandler&lt;/code&gt;는 value 속성으로 지정한 예외뿐 아니라 예외의 자식 클래스도 전부 캐치해 지정된 응답을 반환하게 됩니다. (value 속성을 지정하지 않는다면 메서드의 파라미터에 있는 예외가 자동으로 지정됩니다.)&lt;/p&gt;
&lt;p&gt;만약 자식 클래스는 다른 예외 처리를 적용하고 싶다면 다음과 코드를 같이 작성하여 처리할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ExceptionHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invokeError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;부모 클래스&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;BAD_REQUEST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

		&lt;span class=&quot;token annotation punctuation&quot;&gt;@ExceptionHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentExtendsException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invokeError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;자식 클래스&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;BAD_REQUEST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@ExceptionHandler&lt;/code&gt;는 코드를 작성한 컨트롤러에서만 발생하는 예외만 처리됩니다.&lt;/p&gt;
&lt;p&gt;만약 같은 예외에 대해 여러 컨트롤러에서 같은 처리를 하고 싶다면 컨트롤러마다 같은 메서드를 작성해 주어야만 합니다.&lt;/p&gt;
&lt;p&gt;즉, 코드의 중복이 발생할 수밖에 없습니다.&lt;/p&gt;
&lt;h2&gt;ControllerAdvice&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@ControllerAdvice&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;@Component&lt;/code&gt; 어노테이션의 특수한 케이스로, 스프링 부트 애플리케이션에서 전역적으로 예외를 핸들링할 수 있게 해주는 어노테이션입니다.&lt;/p&gt;
&lt;p&gt;이를 통해 코드의 중복을 해결할 수 있습니다.&lt;/p&gt;
&lt;p&gt;또한, 하나의 클래스 내에서 정상 동작 시 호출되는 코드와 예외를 처리하는 코드를 분리할 수 있습니다.&lt;/p&gt;
&lt;p&gt;다음은 &lt;code class=&quot;language-text&quot;&gt;@ControllerAdvice&lt;/code&gt; 와 &lt;code class=&quot;language-text&quot;&gt;@RestConrollerAdvice&lt;/code&gt; 의 구현의 일부입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RUNTIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Documented&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ControllerAdvice&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ResponseBody&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RestControllerAdvice&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RUNTIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Documented&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ControllerAdvice&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Component&lt;/code&gt;어노테이션을 붙이면 Component Scan 과정을 거쳐 Bean으로 등록됩니다.&lt;/p&gt;
&lt;p&gt;즉, 이 어노테이션을 사용하는 &lt;code class=&quot;language-text&quot;&gt;ControllerAdvice&lt;/code&gt; 또한 Bean으로 관리된다는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p&gt;또한, &lt;code class=&quot;language-text&quot;&gt;@RestControllerAdvice&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;@ResponseBody&lt;/code&gt; 어노테이션이 붙어있으므로 응답을 Json으로 처리한다는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@ControllerAdvice&lt;/code&gt; 어노테이션을 통해 예외를 핸들링하는 클래스를 구현해 보았습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@ControllerAdvice&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleControllerAdvice&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ExceptionHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;badRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위 처럼 코드를 작성하게 되면 애플리케이션 내의 모든 컨트롤러에서 발생하는 &lt;code class=&quot;language-text&quot;&gt;IllegalArgumentException&lt;/code&gt;을 해당 메서드가 처리하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;주의점&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;여러 &lt;code class=&quot;language-text&quot;&gt;ControllerAdvice&lt;/code&gt;가 있을 때 &lt;code class=&quot;language-text&quot;&gt;@Order&lt;/code&gt;어노테이션으로 순서를 지정하지 않는다면 Spring은 &lt;code class=&quot;language-text&quot;&gt;ControllerAdvice&lt;/code&gt;를 임의의 순서로 호출합니다. 즉, 사용자가 예상하지 못한 예외 처리가 발생할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;basePackages&lt;/h3&gt;
&lt;p&gt;만약 여러 &lt;code class=&quot;language-text&quot;&gt;ControllerAdvice&lt;/code&gt;를 세분화하고 싶다면 &lt;code class=&quot;language-text&quot;&gt;basePackages&lt;/code&gt; 속성을 이용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;작성된 패키지와 하위 패키지에서 발생하는 예외는 해당 &lt;code class=&quot;language-text&quot;&gt;ControllerAdvice&lt;/code&gt;에서 처리하도록 지정할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@ControllerAdvice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;basePackages &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;org.woowa.tmp.pkg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleControllerAdvice&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ExceptionHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;badRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;basePackages&lt;/code&gt; 속성을 통해 뷰와 모델을 이용해 직접 페이지를 반환하는 Controller는 예외가 발생하면 직접 예외 페이지를 응답하게 하고, JSON 형식으로 데이터를 주고받는 Controller는 예외가 발생하면 예외에 대한 정보를 JSON 형식으로 응답하게 할 수도 있습니다.&lt;/p&gt;
&lt;h3&gt;assignableTypes&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;assignableTypes&lt;/code&gt; 속성을 이용하면 클래스 단위로도 &lt;code class=&quot;language-text&quot;&gt;ControllerAdvice&lt;/code&gt;를 적용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;클래스 단위로 사용되는 만큼 &lt;code class=&quot;language-text&quot;&gt;basePackages&lt;/code&gt; 속성보다 조금 더 세밀하게 처리를 분리해 주고 싶을 때 사용하면 유용합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@ControllerAdvice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;assignableTypes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SimpleController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleControllerAdvice&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ExceptionHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;badRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;ResponseEntityExceptionHandler&lt;/h3&gt;
&lt;p&gt;Spring은 스프링 예외를 미리 처리해 둔 &lt;code class=&quot;language-text&quot;&gt;ResponseEntityExceptionHandler&lt;/code&gt;를 추상 클래스로 제공하고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ResponseEntityExceptionHandler&lt;/code&gt;에는 스프링 예외에 대한 &lt;code class=&quot;language-text&quot;&gt;ExceptionHandler&lt;/code&gt;가 모두 구현되어 있습니다.&lt;/p&gt;
&lt;p&gt;하지만 에러 메시지는 반환하지 않으므로 스프링 예외에 대한 에러 응답을 보내려면 이 클래스를 상속하여 아래 메서드를 오버라이딩 해야 합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntityExceptionHandler&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleExceptionInternal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
			&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; ex&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@Nullable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpHeaders&lt;/span&gt; headers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpStatus&lt;/span&gt; status&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;INTERNAL_SERVER_ERROR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WebUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;ERROR_EXCEPTION_ATTRIBUTE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ex&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SCOPE_REQUEST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; headers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; status&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;스프링에서는 &lt;code class=&quot;language-text&quot;&gt;@ExceptionHandler&lt;/code&gt; &lt;code class=&quot;language-text&quot;&gt;@ControllerAdvice&lt;/code&gt;를 통해 편리한 예외 처리 기능들을 제공합니다.&lt;/p&gt;
&lt;p&gt;또한 여러 가지 속성을 통해 직접 메서드를 오버라이딩하는 것처럼 상세하게 세부 사항들을 지정해 줄 수도 있습니다.&lt;/p&gt;
&lt;p&gt;코드가 더 간결해지고, 에러 처리 코드의 위치를 사용자가 유연하게 관리할 수 있다는 장점도 있습니다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc&quot;&gt;https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-05-10-controller_advice_exception_handler/&quot;&gt;https://tecoble.techcourse.co.kr/post/2021-05-10-controller_advice_exception_handler/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/spring-controllers&quot;&gt;https://www.baeldung.com/spring-controllers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[DAO와 Repository]]></title><description><![CDATA[DAO를 사용해서 DB에 접근할 때, 대부분 DAO 클래스에 Spring의 @Repository를 달아 둡니다.
왜 DAO를 Repository라고 하지? DAO가 Repository의 일종인가? 싶었는데, DAO와 Repository…]]></description><link>https://tecoble.techcourse.co.kr/post/2023-04-24-DAO-Repository/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2023-04-24-DAO-Repository/</guid><pubDate>Mon, 15 May 2023 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;DAO를 사용해서 DB에 접근할 때, 대부분 DAO 클래스에 Spring의 @Repository를 달아 둡니다.
&lt;em&gt;왜 DAO를 Repository라고 하지? DAO가 Repository의 일종인가?&lt;/em&gt; 싶었는데, DAO와 Repository라는 클래스를 함께 사용하는 코드도 꽤 있어서 매우 혼란스러웠습니다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;조금 찾아보니, Spring 공식 문서의 @Repository에 대한 설명은 다음과 같습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Teams implementing traditional Jakarta EE patterns such as “Data Access Object” may also apply this stereotype to DAO
classes, though care should be taken to understand the distinction between Data Access Object and DDD-style
repositories
before doing so.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;위의 설명에 따르면 DAO 클래스에 @Repository를 사용할 수 있지만, &lt;strong&gt;DAO와 DDD에서 정의된 Repository의 차이&lt;/strong&gt;를 잘 알고
조심해서 사용해야 한다고 합니다.&lt;br&gt;
그러면 두 개념이 어떻게 다를까요? 나름대로 이해한 바를 아래의 차례로 정리해보겠습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;DAO 패턴과 Repository 패턴의 등장 배경&lt;/li&gt;
&lt;li&gt;DAO 패턴&lt;/li&gt;
&lt;li&gt;Repository 패턴&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;
&lt;h2&gt;DAO와 Repository의 등장 배경&lt;/h2&gt;
&lt;p&gt;Application을 구현할 때, &lt;a href=&quot;https://www.mongodb.com/databases/data-persistence&quot;&gt;영속성(Data Persistence)&lt;/a&gt;을 가진 영구저장소를 필요로 하는 경우가
많습니다. Application에서 영구저장소에 접근하기 위해서는 각 영구저장소 벤더가 제공하는 API를 사용합니다.&lt;br&gt;
이 때, 만약 영구저장소의 API가 다른 비즈니스 로직들과 함께 존재한다면 어떤 문제들이 발생할까요?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;구현체와 로직이 너무 강한 결합을 가진다.&lt;/strong&gt;&lt;br&gt; 만약 기존의 영구저장소와 다른 벤더의 영구저장소를 사용하게 된다면, 비즈니스 로직들 내부에서 기존 영구저장소의 API를 사용한 모든 부분을 변경해야 합니다. 즉, 객체지향의 5원칙 중 확장에 대해서는 열려있고 수정에는 닫혀 있어야 한다는 OCP(개방-폐쇄 원칙)를 위반하게 됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;계층화가 깨진다.&lt;/strong&gt; &lt;br&gt;
보통 웹 Application의 구조는 아래와 같은 &lt;a href=&quot;https://www.baeldung.com/cs/layered-architecture&quot;&gt;Layered Architecture&lt;/a&gt;로 되어있습니다.
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 653px; &quot;&gt;
      &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 39.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAAsTAAALEwEAmpwYAAABt0lEQVR42mOYcG9l+LnanCv9ZdenlVybUgxCk4Eo50ofkF1wdWLEubrmuws+f/r04cOH//////v37z8MMPicKmFYry28y0t1f4TyvlD5vSHSewKU94dL7vFX2hchvcePYb2exYnUVy9ePn36FKjh79+//2CAIfnsR5ltr3R2v7Q5+MLy0CvLgy9tD782P/DS7MArm0Mv9fe8lN76Mvrcl3evXjx99gzd5sTDnyWXv9Vf/95xy3uHDa8d1r923PDacdNb23WvnLe+N974XnLZm8jD3z68eXXm7Jn7Dx/evHnjzt07t+/eATqE4X9Q0H9W1v9CQv/k5P8aGf8zNQWSf9XU/1lY/JOT+ycg+A8o6+Hx4v37q5cvf/r06dnzZy9fvQSCd+/eM/wsqP6m7/DN0uu7Y9CPwMTvQUk/AhO+e0V/B5L2Ad+tvL4ZOP7LLH3x9u0zsJ9RnH2o/NhS89XbE/duj9+9LXL79ugdm4O2bI/dtTlw87boXTuS9i2zWr0/8/DVa1cuXrwI0vwXSfPjvY+uzr1ya+XNmytu3lh2A0QuBZI3QORykODVeVce73gEBPfv30ezGQA/zFJwUdpNzwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;layers&quot; title=&quot;&quot; src=&quot;/static/1736a95e6b5bc7df5becf32096a48f38/ffe7f/layers.png&quot; srcset=&quot;/static/1736a95e6b5bc7df5becf32096a48f38/0eb09/layers.png 500w,
/static/1736a95e6b5bc7df5becf32096a48f38/ffe7f/layers.png 653w&quot; sizes=&quot;(max-width: 653px) 100vw, 653px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
    &lt;/span&gt;
영구저장소는 Infrastructure 계층에 속하고, 비즈니스 로직들은 Application 계층에 속하는데, 영구저장소에 대한 API가 Application 계층에 속하게 되면 계층화가 깨지게 됩니다.
그 결과, Layered Architecture의 이점인 모듈화, 유연성 등이 사라지게 되는 문제가 발생합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그러므로 비즈니스 로직과 영구저장소의 API를 분리할 필요가 있습니다.&lt;br&gt;
즉, 데이터에 접근하는 행위를 추상화, 캡슐화해서 비즈니스 로직과 데이터 접근 로직을 분리해야 합니다. 이를 구현하기 위한 방법으로 DAO 패턴과 Repository 패턴이 등장했습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;DAO 패턴&lt;/h2&gt;
&lt;p&gt;DAO(Data Access Object)는 이름 그대로 데이터에 접근하기 위한 객체입니다. 이름부터 자신이 영속성 계층과 연관이 있음을 버젓이 나타내고 있습니다. 그 자체가 영속성의 추상화이기 때문에 도메인 계층이 아닌
&lt;strong&gt;영속성 계층에 속합니다&lt;/strong&gt;. 그래서 도메인 패키지에 DAO가 있으면 어색한 느낌을 줍니다. 일반적으로 DAO는 DB의 테이블과 일치합니다. 즉, 테이블 중심이라고 할 수 있습니다.
필연적으로, DAO와 매핑되는 엔티티는 테이블의 칼럼과 (거의) 동일한 필드를 가지게 됩니다.
아래의 코드 예시를 살펴보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; position&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; position&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;position &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; position&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같이 Car라는 클래스가 존재한다고 하겠습니다.&lt;/p&gt;
&lt;br&gt;
이제 Car 인스턴스의 데이터를 DAO를 사용하여 영속성 계층에 CRUD를 해보겠습니다.  
이 때, 일반적이지는 않지만 MySQL과 MongoDB 모두 사용하는 Application이라고 가정하겠습니다. 각 영구저장소의 벤더에 따라서 DAO를 만들어야 하므로 저는 DAO 인터페이스를 하나 만들어서 추상화하려고 합니다. 그리고 각 영구저장소의 벤더에 따라 DAO 인터페이스의 구현체를 만듭니다. 각 구현체에서는 해당하는 영구저장소의 API들을 사용합니다.
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CarDao&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; car&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; car&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MySQLCarDao&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CarDao&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;//DB connection etc...&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; car&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;INSERT INTO CARS(id, name, position) VALUES(?, ?, ?)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//parameter binding etc...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SELECT * FROM CARS WHERE id = ?&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//parameter binding etc...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MongoDBCarDao&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CarDao&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;//DB connection etc...&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; car&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//doc -&gt; Document 인스턴스&lt;/span&gt;
        doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;car&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; car&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPosition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//etc...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//MongoCollection의 find() 메서드 이용&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Document&lt;/span&gt; doc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//etc...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이처럼, 직접적인 DB와의 상호작용을 추상화하고 쿼리를 실행하는 객체를 DAO라고 하고, DAO를 이용하여 데이터에 접근하는 패턴을 DAO 패턴이라고 합니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;Repository 패턴&lt;/h2&gt;
&lt;p&gt;네이버 사전에 등록된 Repository의 의미는 아래와 같습니다.
&lt;img src=&quot;https://i.imgur.com/CkBosVS.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;저장소라는 뜻이네요.
Spring 공식 문서에 있는 @Repository에 대한 설명도 다음과 같습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Indicates that an annotated class is a “Repository”, originally defined by Domain-Driven Design (Evans, 2003) as “a
mechanism for encapsulating storage, retrieval, and search behavior which emulates a collection of objects”.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;즉, Repository는 단순히 저장소라는 의미입니다. 이름에서는 전혀 영속성 계층과 연관이 있음을 알 수 없습니다. (그리고 실제로 연관이 없을 수도 있습니다!) 그저 객체들의 집합(Collection)의 관리를 추상화한 메커니즘일 뿐입니다.
도메인과 아주 밀접한 관계가 있고, 영속성 계층과의 연관이 불확실하므로 &lt;strong&gt;Repository의 인터페이스는 도메인 계층에 속해야합니다&lt;/strong&gt;. 무슨 말인지 감이 오시나요?&lt;/p&gt;
&lt;p&gt;조금 더 명확한 설명을 위해 코드를 한번 작성해보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cars&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; cars &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; car&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        cars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;car&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        cars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Car의 일급컬렉션인 Cars는 필드로 Car의 리스트를 가지고 있습니다. 그리고 이 리스트에 Car를 저장하고, 가져오고, 삭제하는 메서드를 제공하고 있습니다.&lt;br&gt;
그러면 우리는 이 Cars를 Car의 In-memory Repository라고 부를 수 있습니다. 말 그대로 저장소(Car 리스트)의 관리 역할을 하니까요!&lt;br&gt;
그러면 이 Cars라는 클래스는 어디에 위치해야 할까요? Car가 존재하는 도메인 패키지에 있는 것이 자연스럽겠죠?&lt;/p&gt;
&lt;p&gt;이처럼, &lt;code class=&quot;language-text&quot;&gt;객체의 정보를 가진 저장소에 대한 관리&lt;/code&gt;에 대한 책임을 위임받은 인터페이스인 Repository를 사용한 패턴을 Repository 패턴이라고 합니다.&lt;br&gt;
저장소는 In-memory에 위치할 수도 있고, Application 외부의 어떤 file이 될 수도 있습니다. 어디에 저장하는지가 중요한 것이 아니라, 그냥 어떤 저장소에 데이터를 넣고, 읽어오고, 삭제하는 역할을 해주는 것이 Repository의 역할이니까요.&lt;/p&gt;
&lt;p&gt;마찬가지로, Car의 데이터를 영속성 계층에 저장할 때에도 Repository 패턴을 사용할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CarDatabaseRepository&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; car&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Database에 Car를 저장&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Database에서 Car를 조회&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Database에서 Car를 삭제&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;여기서, DAO와 Repository가 혼동이 생깁니다. Repository가 관리하는 저장소가 영속성 계층에 위치할 때, 이 Repository는 DAO와 동일하게 보이거든요. CarDao와 CarDatabaseRepository가 비슷하게 보이지 않나요?&lt;br&gt;
그러나 ”&lt;em&gt;영속성 계층에 위치한 저장소를 관리하는 Repository는 DAO다!&lt;/em&gt;“라고 하기 어려운 개념적인 이유가 있습니다.&lt;/p&gt;
&lt;p&gt;혹시 CarDao와 CarDatabaseRepository의 차이를 눈치채셨나요?&lt;br&gt;
네, CarDatabaseRepository에는 Update에 관한 메서드가 존재하지 않습니다. 왜 그럴까요? 아래의 코드로 해당 개념을 설명하겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; car1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;머스탱&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; car2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;박스터&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
    &lt;span class=&quot;token class-name&quot;&gt;Cars&lt;/span&gt; cars &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cars&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
    cars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;car1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    cars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;car2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
    car1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;리오의 머스탱&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    car2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;박스터의 박스터&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
    &lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; savedCar1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; savedCar2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
    &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;savedCar1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 리오의 머스탱&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;savedCar2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 박스터의 박스터&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위의 코드에서 마지막 출력 때 주석에 달린 대로 각 Car들의 변경된 이름이 나오는 것이 너무나 자연스러워보입니다. savedCar1이 Car1과 동일하고, savedCar2가 Car2와 동일할테니까요.
이게 바로 Repository에는 Update 메서드가 없는 이유입니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://stackoverflow.com/questions/8550124/what-is-the-difference-between-dao-and-repository-patterns&quot;&gt;StackOverFlow의 글&lt;/a&gt;
에 이런 내용이 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A method like Update is appropriate on a DAO, but not a Repository. When using a Repository, changes to entities are
usually tracked by a separate UnitOfWork.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;CRUD는 DB의 가장 기본적인 기능이고, DAO는 DB의 테이블과 밀접하게 연관되어 있습니다. 그러므로 DAO에 Update 메서드가 존재하는 것이 너무나 당연합니다.&lt;br&gt;
그러나 Repository는 다릅니다. 위의 코드에서처럼 Car1과 Car2가 Cars 내부의 Car리스트에 담긴다고 해서 새로운 객체가 되는 것이 아니기 때문에, 이름을 변경하면 당연히 변경된 이름이 나올 겁니다.&lt;br&gt;
그런데 저장소가 영속성 계층에 위치한다면 어떨까요? application에서 Car1과 Car2의 이름을 바꾸면 자동으로 DB Row 내용도 변할까요? 당연히 바뀌지 않을 겁니다. 그치만 Repository 패턴을 사용한다면 DB의 내용도 당연하게 바뀌어야 합니다. 저장소가 어디에 위치하냐에 따라서 Repository의 책임이 달라지면 안되니까요. 그러므로 외부 저장소(DB나 File 등)에 대한 Repository는 Update 메서드 없이 객체의 변경이 저장소에 반영되도록하는 기능을 지원해야합니다.&lt;br&gt;
이것이 Repository가 Update 메서드를 지원하지 않는 이유이고, JPA의 영속성 컨텍스트에서 &lt;code class=&quot;language-text&quot;&gt;변경 감지&lt;/code&gt;가 이루어지는 이유입니다. 인용한 문장에 있는 &lt;a href=&quot;https://zetlos.tistory.com/1179902868&quot;&gt;UnitOfWork&lt;/a&gt;라는 디자인 패턴의 개념을 간단하게 설명해봤습니다. 더 자세히 알고 싶으시다면 링크된 블로그를 참고해주세요!&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;마치며&lt;/h3&gt;
&lt;p&gt;이상으로 DAO와 Repository에 대해 알아봤습니다.&lt;br&gt;
간단한 어노테이션이라도 정확하게 개념을 알아서 적절하게 사용할 수 있도록 노력하려고 합니다.
긴 글 읽어주셔서 감사합니다!&lt;/p&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/stereotype/Repository.html&quot;&gt;스프링 공식문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/8550124/what-is-the-difference-between-dao-and-repository-patterns&quot;&gt;StackOverFlow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/java-dao-vs-repository&quot;&gt;Baeldung&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[데드락 해결 모험기]]></title><description><![CDATA[용어 설명  Cycle이란, 사용자가 특정 챌린지에 도전할 때 생성되는 객체이다. 비즈니스 규칙 상 하루에 한번씩 총…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-11-01-mysql-dead-lock/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-11-01-mysql-dead-lock/</guid><pubDate>Mon, 21 Nov 2022 15:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;용어 설명&lt;/h2&gt;
&lt;p&gt; Cycle이란, 사용자가 특정 챌린지에 도전할 때 생성되는 객체이다. 비즈니스 규칙 상 하루에 한번씩 총 3일을 인증해야 하며, 하루에 여러번 인증을 할 수는 없다. 아직 인증을 하지 않은 상황에서 활동을 인증하게 되면 인증 내역인 CycleDetail이라는 객체가 생성된다. 따라서, 3일 동안 정상적으로 하루헤 한번씩 3번 인증을 하게 되면 총 3개의 CycleDetail이 생성되고, 하나의 Cycle은 최대 3개의 CycleDetail을 가지게 된다&lt;/p&gt;
&lt;h2&gt;문제 상황(1)&lt;/h2&gt;
&lt;p&gt; QA 시 일반적 시나리오에서는 Cycle의 인증이 예상한대로 동작했으나, “하나의 계정에 두 기기가 접속하여 동일한 Cycle을 인증하는 경우”가 궁금해져 QA를 진행하게 되었다. 이 때 “한번에 하나의 인증만 이루어져야 한다”라는 비즈니스 로직에 의해 인증하려는 Cycle에 대응되는 CycleDetail이 하나만 있는 것을 기대했으나, 실제로는 2개 이상의 CycleDetail이 남아있는 문제가 발생하게 되었다.&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
       &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199098853-f7b8e40b-c5ca-4655-bfec-85b98bea4bac.png&quot; align=&quot;center&quot; width=&quot;1000&quot; height=&quot;200&quot;&gt;
      &lt;/p&gt;
&lt;p&gt; 전형적인 동시성 문제로 판단 되었고, 이에 대한 해결책은 lock을 거는 것으로 알고 있었다. 하지만 이미 mysql 내부에서 우리도 모르는 lock을 비일비재하게 걸고 있기 때문에 추가적으로 lock을 걸었을 때 다른 쿼리들이 예상치 못하게 대기하는 상황이 발생할 수 있을 수 있다. 따라서, 최대한 lock을 지양한 채로 해당 문제를 풀고 싶었다.&lt;/p&gt;
&lt;p&gt; 문제가 되는 부분은 CycleDetail이 두번 이상 삽입되는 것이 문제이기 때문에 이것을 방지한다면, 문제를 해결하는 것과 같다고 판단하여 CycleDetail의 외래키인 cycle_id와 현재 몇번째 인증인지를 나타내는 progress를 복합 unique 제약조건을 걸게 되면 삽입이 한번만 이루어질 것이라고 예상하였다&lt;/p&gt;
&lt;h2&gt;문제 상황(2)&lt;/h2&gt;
&lt;p&gt; 결과적으로는 하나의 기기는 성공하고 다른 기기는 실패하는 것을 확인하였으나, 문제는 에러 메세지였다. 실패한 쪽에서의 예상되는 에러 메세지는 미리 정의한 커스텀한 에러 메세지인 “인증 시간이 지났습니다” 였으나, 실제 에러 메세지는 “알 수 없는 에러가 발생하였습니다”가 나타났다. 로그를 분석해보니, 데드락이 발생하였다는 에러 로그를 마주하게 되었다.&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
       &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199099254-7bc6cbf1-3a1a-4b3f-a3db-d8a956fe6093.png&quot; align=&quot;center&quot; width=&quot;1000&quot; height=&quot;200&quot;&gt;
      &lt;/p&gt;
&lt;h2&gt;가설 수립 및 원인 규명&lt;/h2&gt;
&lt;p&gt; 따라서, 실제 Mysql에서 어떤 쿼리가 데드락을 발생시켰는지 알고자 DB 서버에 접속하여 확인해보았다. 이 때 데드락을 발생시킨 직접적인 원인이 되는 쿼리는 Cycle의 progress를 바꾸는 Update문과 CycleDetail을 insert하는 쿼리였다&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
       &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199100038-5aaf25ee-ce34-4a04-b0a2-60b70e016d7e.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;600&quot;&gt;
      &lt;/p&gt;
&lt;p&gt;문제를 해결하기 위한 unique 제약조건이 추가된 설정을 기준으로 트랜잭션의 각 흐름에 따른 lock의 획득 과정을 그림으로 도식화하면 다음과 같다&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
       &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199100493-4fec7d93-4714-4916-abd5-8511e801b729.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;600&quot;&gt;
      &lt;/p&gt;
&lt;p&gt;먼저 트랜잭션 1이 insert문을 통해 이유는 알 수 없지만 Cycle에 대한 shared lock을 걸게 된다. 설명의 편의를 위해 트랜잭션 1이 Cycle에 대해 건 shared lock을 초록색 타원으로 표기하였다&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
       &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199100699-f36fd92f-85eb-403b-b344-3db1c238be91.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;600&quot;&gt;
      &lt;/p&gt;
&lt;p&gt; 이후 트랜잭션 2가 역시 insert문을 통해 이번에도 이유는 알 수 없지만 Cycle에 대한 shared lock을 걸게 된다. 이 때 shared lock은 말 그대로 lock간의 공유가 가능하기 때문에 여기서 데드락이 발생하지는 않는다. 마찬가지로 설명의 편의를 위해 트랜잭션 2가 Cycle에 대해 건 shared lock을 파란색 타원으로 표기하였다&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
       &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199100923-f1b97cff-a46e-4339-9fe5-d100ef2512d4.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;400&quot;&gt;
      &lt;/p&gt;
&lt;p&gt; 먼저 insert한 트랜잭션 1에 의해 unique 제약조건으로 생성된 인덱스에 CycleDetail이 삽입이 되고, 이에 대한 exclusive lock이 걸리게 된다. 따라서 트랜잭션 2에서의 insert 쿼리는 첫번째 트랜잭션이 획득한 exclusive lock에 의해 대기하게 된다&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
       &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199101485-f429da01-62fe-415f-a0b9-7313353729c0.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;600&quot;&gt;
      &lt;/p&gt;
&lt;p&gt; 이후 트랜잭션 1이 Cycle의 상황을 바꾸려한다. 이 때 데드락이 발생하게 되는데, 그 이유는 트랜잭션 1의 입장에서는 update하기 위해서는 트랜잭션 2의 Cycle에 대한 shared lock을 풀어야 하고, 트랜잭션 2의 입장에서는 insert하기 위해서는 트랜잭션 1의 CycleDetail에 대한 exclusive lock을 풀어야 하기 때문이다. 따라서, unqiue 제약조건으로는 데드락 문제를 해결할 수 없었다&lt;/p&gt;
&lt;p&gt; 그런데 사실 Cycle에 대한 shared lock이 애초에 없었다면 데드락이 발생하지는 않았을 것이다. 따라서 왜 설정하지도 않은 shared lock이 발생하는지에 대해 알아보기로 하였다. 여러 가설이 있었으나 가장 유력한 가설은 학부 시절 데이터베이스 개론에 배웠던 “참조 무결성 원칙”이 공유락을 발생시킨다는 가설이었다&lt;/p&gt;
&lt;p&gt; 참조 무결성이란 외래키는 참조할 수 없는 값을 가질 수 없다는 규칙이다. CycleDetail을 삽입할 때 왜래키인 cycle_id를 참조하고 있다. 해당 쿼리를 보는 개발자의 입장에서는 모든 맥락을 이해하기 때문에 insert 쿼리에 참조하는 cycle_id는 존재한다는 사실을 알지만, DB입장에서는 전혀 그렇지 않기 때문에 현재 쿼리가 참조 무결성을 위반할 수 있다고 생각할 수 있다. 실제로 문제 해결을 위해 참고하던 Real MySQL에서도 외래키와 데드락의 연관성에 대해 지적하고 있었다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“외래키는 부모테이블이나 자식 테이블에 데이터가 있는지 체크하는 작업이 필요하므로 잠금이 여러 테이블로 전파되고,
그로인해 데드락이 발생할 수 있다. 그래서 실무에서는 잘 사용하지 않는다.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;해결책&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Optimistic lock&lt;/p&gt;
&lt;p&gt; unique 제약 조건에 의해서도 문제 상황을 풀 수는 없었다. 또한 insert와 delete의 쿼리를 바꿔버리게 되면 Cycle에 대한 shared lock을 두 트랜잭션 모두 들고 있을 필요가 없기 때문에 데드락이 발생하지 않을 수 있다는 생각이 들었다. 하지만, JPA는 insert 쿼리에 대해서는 쓰기 지연을 하지 않고 즉시 DB로 보내기 때문에 굳이 update를 먼저 실행시키고 싶다면 강제적으로 flush를 해줘야 한다. 하지만 그렇게 되면 도메인 레벨에서 DB 관련된 로직이 불필요하게 들어가게 되기 때문에 도메인의 책임이라는 관점에서는 부자연스럽다고 판단하였다&lt;/p&gt;
&lt;p&gt; 그런데, 데드락을 발생시키는 상황 자체가 상당히 낮은 확률로 이루어지기 때문에 Optimistic lock을 사용하는 것이 좀 더 자연스럽다고 판단하여 Optimistic lock을 해당 문제를 해결해보려고 시도했다. 또한, 기존의 unique 제약조건에 의해 insert시 오히려 추가적인 배타 락이 발생하며 데드락 문제를 해결하지 못하기 때문에 unique 제약 조건은 제거하기로 하였다. 그러나 결론적으로 Optimistic lock도 데드락을 해결해주지 못했다&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199102173-456c03e8-2c48-497a-bb60-d6070d93ac4d.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;600&quot;&gt;
 &lt;/p&gt;
&lt;p&gt;초기 상황은 위 그림과 같다. 초기의 Cycle의 version은 0으로 초기화 된 상황이다. 단순히 조회만 하였기 때문에 version의 변화는 없다&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199102457-4a3bf6ba-709c-41f2-879c-d7a48b994f10.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;600&quot;&gt;
 &lt;/p&gt;
&lt;p&gt; 이후 트랜잭션 1이 CycleDetail을 삽입하게 된다. 이 때 참조 무결성 원칙에 의해 트랜잭션 1은 Cycle에 대한 shared lock을 점유하게 된다. 이 때 주의해야 할 점은 비록 CycleDetail이 삽입된다고 하더라도 Cycle의 version은 증가하지 않는다는 점이다. 설명의 편의를 위해 트랜잭션 1이 Cycle에 대해 건 shared lock을 주황색 타원으로 표기하였다&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199102641-54f0b393-9c56-4e56-ba01-58a642d820a9.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;600&quot;&gt;
 &lt;/p&gt;
&lt;p&gt; 트랜잭션 2 역시 후속과정으로 CycleDetail을 삽입하게 된다. 같은 논리에 의해 트랜잭션 2 역시 Cycle에 대한 shared lock을 획득한다. 설명의 편의를 위해 트랜잭션 2가 Cycle에 대해 건 shared lock을 청록색 타원으로 표기하였다&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199102954-be31623b-59c7-4a1d-961f-f9aa358c0ff1.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;600&quot;&gt;
 &lt;/p&gt;
&lt;p&gt; 위 그림 8에서는 트랜잭션 2가 트랜잭션 1이 점유한 shared lock에 의해 update 쿼리가 대기하는 모습이다. 결국 versioning도 쿼리가 실행되어야 하기 때문에 대기에 의해 versioning이 이루어지지 않는다&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199103167-3f7c8029-778d-4314-bde1-73c4621efdb2.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;600&quot;&gt;
 &lt;/p&gt;
&lt;p&gt; 현재 트랜잭션 1이 바꾸려는 Cycle의 version이 최신이므로, 트랜잭션 1의 update문이 실행되게 된다. 그러나, 트랜잭션 1도 update문이 위 그림 8에서의 논리에 의해 대기하게 된다. 따라서, 양 측이 commit이나 rollback이 되지 않는 이상 두 트랜잭션 모두 update 쿼리가 무한히 대기하게 된다. 따라서, Optimistic lock으로도 데드락을 해결할 수 없다. 결국 최종적으로 mysql에서 트랜잭션 1과 2 중 임의의 트랜잭션을 rollback하게 되고, 최종적으로는 Cycle의 versioning이 0에서 1로 이루어진다&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pessimistic lock&lt;/p&gt;
&lt;p&gt;따라서, “데드락을 해결”하기 위해서는 남은 선택지가 Pessimistic lock밖에 없었고, 해당 방법으로 동시성 문제를 해결하게 되었다. Pessimistic lock이 데드락을 해결하는 방법은 아래 그림과 같다&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199103706-d666402e-577f-485d-b73e-27795a7a71f6.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;600&quot;&gt;
 &lt;/p&gt;
&lt;p&gt;위 그림은 트랜잭션 1이 Cycle에 대한 exclusive lock을 거는 모습을 도식화한 모습이다. 설명의 편의를 위해 트랜잭션 1이 Cycle에 대해 건 exclusive lock을 보라색 타원으로 표기하였다&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199103818-34934345-e924-4680-af72-d80a6488793a.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;600&quot;&gt;
 &lt;/p&gt;
&lt;p&gt;exclusive lock은 exclusive lock 사이의 공유를 허용하지 않으므로 트랜잭션 2의 Cycle에 대한 select문은 대기하게 된다&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199104101-b5f9ad83-7916-4fef-a5b2-f8d0ae8c2974.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;600&quot;&gt;
 &lt;/p&gt;
&lt;p&gt; 이후 트랜잭션 1은 insert와 update를 정상적으로 진행되게 된다. 이 때 데드락은 발생하지 않는데, 트랜잭션 2가 select 조차 하지 못하고 대기 중이기 때문이다. 따라서 트랜잭션 1은 무사히 커밋되게 된다&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199104421-05bb29a3-a6a7-4e87-a19c-33559e45729a.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;600&quot;&gt;
   &lt;/p&gt;
&lt;p&gt; 트랜잭션 1이 커밋되었기 때문에 보라색 타원으로 나타내었던 트랜잭션 1의 Cycle에 대한 exclusive lock이 해제되었다. 따라서 트랜잭션 2는 비로소 select 쿼리를 실행할 수 있다. 그러나, 이미 상태가 바뀐 Cycle에 대해 트랜잭션 2가 update를 하려하는 순간, 서비스 레이어에서 작성된 방어 로직에 의해 DB에서 예외가 발생하지 않고 Application단에서 정의된 커스텀 예외가 발생하게 된다. 따라서 데드락은 발생하지 않는다. 아래는 Pessimistic lock을 설정한 코드이다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Repository Layer&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
     &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199104655-b68789ff-d4d5-428d-8e0f-1f182d8f0ced.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;300&quot;&gt;
   &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Service Layer&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199104935-93bccacd-40f8-47df-ae00-af78b815ccf5.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;500&quot;&gt;
   &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Test code&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199105211-33363d22-bf61-4f21-a353-71e4873a69c2.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;500&quot;&gt;
   &lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199105328-521b681a-0cba-4d4f-a20a-b9b8eccc6e79.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;500&quot;&gt;
   &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;의문점&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;첫번째 의문점&lt;/p&gt;
&lt;p&gt; 그런데 데드락을 해결한 이후, 한가지 의문점이 들었다. “과연 아무것도 하지 않았을 때, 즉 락이나 unqiue 제약조건, versioning 따위를 하지 않았을 때도 데드락이 발생하는가?”이다. 실제로 테스트를 해본 결과 동시성 문제와 데드락 문제가 동시에 발생하는 것을 알 수 있었다. 동시성과 데드락이 양립할 수 없다고 생각했으나, 아래와 같은 흐름이라면 충분히 양립할 수 있다는 것을 파악하였다&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
   &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199105639-b1aebbc3-bb3a-465a-91a5-1a1c75de391d.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;400&quot;&gt;
 &lt;/p&gt;
&amp;nbsp;우선 트랜잭션 1과 트랜잭션 2의 관계부터 살펴보자. 트랜잭션 1은 트랜잭션 2보다 더 빨리 insert를 하였고 그에 따라 Cycle에 대한 shared lock을 획득한다. 트랜잭션 2는 트랜잭션 1이 update문을 실행하기 전에 동일한 Cycle에 대한 shared lock을 역시 획득한다. 따라서 트랜잭션 1이 update를 수행하게 되면 트랜잭션 2가 가진 shared lock에 의해 update가 대기하게 된다. 이후 트랜잭션 2가 update를 수행하려는 순간, 서로의 shared lock을 놓아야 update가 어느 한쪽에서라도 진행되기 때문에 데드락이 발생한다.
&amp;nbsp;데드락이 발생하였기 때문에 mysql에서는 트랜잭션 1과 트랜잭션 2 중 어느 하나를 롤백시켜야 한다. 이 때 설명의 편의를 위해 트랜잭션 2를 rollback한다고 하자. 따라서, 트랜잭션 1은 정상적으로 커밋이 된다
&amp;nbsp;다음으로 트랜잭션 1과 트랜잭션 3의 관계를 살펴보자. 트랜잭션 3은 트랜잭션 1이 commit되기 이전에 시작이 됨을 알 수 있다. 따라서, mysql의 기본 트랜잭션 격리 속성인 REPEATABLE READ에 의해 아직 상태가 바뀌지 않은 Cycle을 획득하게 된다. 이후 트랜잭션 1이 커밋이 되고, 그 이후에 트랜잭션 3이 insert를 수행하면서 Cycle에 대한 shared lock을 획득하게 된다. 이 시점에는 이미 트랜잭션 1이 commit 되었기 때문에 현재 Cycle에 대한 shared lock은 트랜잭션 3만 가지고 있다
&amp;nbsp;따라서, 트랜잭션 3은 무난하게 update 쿼리까지 수행하게 되고, commit까지 완료된다. 따라서, 같은 Cycle에 대해 트랜잭션 1과 트랜잭션 3에서 수행한 insert에 의해 동시성 문제가 발생하게 된다. 왜냐하면 “하루에 한번만 인증할 수 있다”라는 규칙에 의해 insert가 한번만 정상적으로 이루어져야 하기 때문이다. 따라서, 위 경우라면 충분히 데드락과 동시성 문제가 양립할 수 있다
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;두번째 의문점&lt;/p&gt;
&lt;p&gt; 추가로, “이 문제를 DB에 의존하여 풀지 않고 application단에서 풀 수 없을까?”라는 의문점도 생각해보았다. 자바에서의 대표적 동시성 처리 방법인 synchronized와 동시성을 지원하는 자료구조인ConcurrentMap을 이용하여 문제를 해결할 수 있다고 생각했다. 코드는 아래와 같다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Concurrency Layer&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
      &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199106502-3a583879-3fea-44b4-91c9-fd0f591049b8.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;500&quot;&gt;
     &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Controller Layer&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199106703-ae0b869a-43cb-4787-8ac7-5ebf1c32a8f1.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;500&quot;&gt;
  &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Service Layer&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199106790-9b8a8b2c-cb59-4bc2-9ffb-41e19c27dd69.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;600&quot;&gt;
  &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Test code&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199107007-9eac3e47-96ef-49ab-9bd1-be09287589c7.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;500&quot;&gt;
&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199107093-0b19d9bb-7df8-47ca-b6a3-3e31cef208a0.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;500&quot;&gt;
&lt;/p&gt;
&lt;p&gt; 위 코드가 정상적으로 동작하는 이유는 SyncManager 객체가 멀티스레드에 의해 각자 생성된다고 하더라도, 특정 id에 대한 lock 객체(여기서는 Object 객체)는 static 변수인 ConcurrentMap에 의해 공유되기 때문이다. 또한, 특정 id가 ConcurrentMap가 없다면 computeIfAbsent를 통해 락 객체를 삽입하게 되는데, ConcurrentMap의 특성 상, computeIfAbsent는 각 id별로 atomic하기 때문에 lock 객체의 동일성을 보장할 수 있다&lt;/p&gt;
&lt;p&gt; 여기서 어차피 lock 객체가 같다면 SyncManager의 getLock을 접근 제어자를 private에서 public으로 바꾸고 Service Layer에서 별도의 synchronized block으로 처리하면 되지 않느냐고 생각할 수 있다. 하지만, 이미 비즈니스 로직을 담당하는 메소드에 @Transactional이 붙어있기 때문에 원하는 대로 동작하지 않는다. 이유를 간단히 말하면 @Transactional은 프록시로 동작하기 때문이다. 따라서, 프록시로 진행되는 부분은 동시성 제어를 받지 않는다&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img src=&quot;https://user-images.githubusercontent.com/50986686/199107505-366309ca-fd6f-4736-afdd-b430fd5f35b5.png&quot; align=&quot;center&quot; width=&quot;800&quot; height=&quot;500&quot;&gt;
&lt;/p&gt;
&lt;p&gt; 위 그림에서 스레드 1이 먼저 lock 객체를 획득하여 비즈니스 로직을 진행하게 된다. 따라서 스레드 2는 프록시 객체에서 target 객체의 메소드를 실행할 수 없기 때문에 대기하게 된다. 이 때 스레드 1에서 실행했던 비즈니스 로직이 끝나게 된다. 이후는 다시 프록시가 주체가 되어 진행이 된다. 그런데, 바로 비즈니스 로직이 끝나고 프록시의 로직이 실행되는 순간, lock 객체에 대한 획득을 잃어버리게 되므로 스레드 2의 target 객체가 비즈니스 로직을 실행할 수 있는 빌미를 제공한다. 왜냐면 스레드 1의 현재 실행 주체는 프록시이기 때문이다. 따라서, 경우에 따라 동시성 문제가 발생할 수도 있고 데드락 문제가 발생할 수도 있다.&lt;/p&gt;
&lt;p&gt; 결국 원하는 결과를 만들어내기 위해서는 비즈니스 로직 외부에서 synchronized로 Service Layer의 비즈니스 로직을 감싸주어야 한다. 그렇기 때문에 Controller Layer에서 Concurrent Layer를 통해 Service Layer의 로직을 실행하는 흐름으로 진행된다&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;세번째 의문점&lt;/p&gt;
&lt;p&gt; 하나의 WAS와 하나의 DB라면 synchronized로 해결할 수 있고, 복수의 WAS와 하나의 DB라면 DB lock을 통해 해결할 수 있음을 파악하였다. &lt;br&gt;그렇다면 최종적으로 “복수의 WAS와 복수의 DB라면 해당 문제를 어떻게 해결할 수 있을까?”라는 의문이 발생하였다. 코치님들에게 자문한 결과, &lt;br&gt;“분산 락”을 통해 해결할 수 있다고 하셨다. 구현하는 방법은 Redis나 ZooKeeper, 혹은 mysql에서도 named lock을 통해 직접 구현할 수 있다고 한다&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt; 그럼에도 불구하고 현재 exclusive lock을 걸어서 해결한 것이 최선인지는 솔직히 의문이다. 결국 lock을 건다는 것은 성능상으로 매우 위험한 선택이기 때문이다. 이미 이에 대해 팀에서 많은 논의를 거쳤고 각종 테스트를 통해 lock에 의한 유의미한 성능 저하는 관찰되지 않았으나, 예상치 못한 부분에서 성능 저하가 발생할 수 있는 가능성이 있다는 점은 부인할 수 없다.&lt;/p&gt;
&lt;p&gt; 결국 데드락의 직접적 원인인 “외래키 제약조건”을 아예 삭제하는 것이 가장 문제를 근본적으로 해결하는 방법이라고 생각한다. 즉, 테이블을 최초에 잘못 설계한 측면이 있다고 생각한다. 결국 처음 설계할 때부터 외래키 제약이 없도록 설계했으면 데드락이 발생하지 않고 단순히 동시성 문제에서 끝났을 것이기 때문이다.&lt;/p&gt;
&lt;p&gt; 외래키를 없애면 데드락 문제를 해결할 수 있지만 그럼에도 불구하고 발생가능성이 있는 동시성 문제를 반드시 죄악시해야 하는 것인가에 대한 의문이 남는다. 사실 하나의 Cycle에 대해 동시에 여러번 인증이 이루어져도 금전적이거나 프로젝트 사이트에서 이용상 불편한 점, 혹은 다른 사용자에게 피해를 입히는 점이 없다면 사실 데이터 정합성이 맞지 않더라도 사용자에게는 하루에 하나만 인증되는 것처럼 보여주기만 해도 문제가 없다&lt;/p&gt;
&lt;p&gt; 어쨌든 처음으로 동시성 문제를 마주하였는데 생각보다 알아야 할 내용이 많았다. 또한 전공 시간에 배운 “참조 무결성 원칙”이 직접적인 원인으로 나타난 문제 상황을 마주하여 흥미로웠다. 일상적으로 쓰이는 쿼리에 mysql이 어떻게 내부적으로 lock을 수행하는지도 조금이나마 알게 되어 흥미로웠으나, 이해하는 과정이 매우 어려웠다. 마지막으로 synchronized와 @Transactional을 같이 사용할 때의 문제까지 추가되어 해결하고 글을 작성하기까지 오랜 시간이 걸려서 해결하는 내내 힘들었다. 그러나, 하나의 문제의 원인을 파악하고, 그에 대한 여러 해결책을 적용해보고 각 결과의 원인을 파악하는 것은 재미있었고 결과적으로 lock에 대한 학습을 많이 하게 된 계기가 되어 뿌듯하고 쉽게 하기 힘든 경험을 해서 좋았다. 마지막으로 해당 문제 해결에 도움을 주신 구구 코치님, 토미 코치님, 제이슨 코치님, 관심가지고 논의해준 스모디 백엔드 크루들인 토닉,더즈,조조그린에게 감사한 마음을 전하고 싶다&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;백은빈, 이성욱 지음 Real MySQL 8.0&lt;/li&gt;
&lt;li&gt;김연희 지음, 데이테베이스 개론 2판&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kdhyo98.tistory.com/59&quot;&gt;synchronized와 @Transactional 을 동시에 사용 시 문제점&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[AOP에 대한 사실과 오해 그런데 트랜잭션을 사알짝 곁들인..]]></title><description><![CDATA[들어가며 트랜잭션을 공부하다보면서 자연스럽게 AOP에 대해 접하게 되었습니다.
AOP를 사용하게 되면서 직접 경험했던 사실과 오해에 대해서 공유하려고 합니다. 첫 번째 사실: public이외의 메서드는 AOP…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-11-07-transaction-aop-fact-and-misconception/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-11-07-transaction-aop-fact-and-misconception/</guid><pubDate>Tue, 15 Nov 2022 13:10:00 GMT</pubDate><content:encoded>&lt;h2&gt;들어가며&lt;/h2&gt;
&lt;p&gt;트랜잭션을 공부하다보면서 자연스럽게 AOP에 대해 접하게 되었습니다.
AOP를 사용하게 되면서 직접 경험했던 사실과 오해에 대해서 공유하려고 합니다.&lt;/p&gt;
&lt;h2&gt;첫 번째 사실: public이외의 메서드는 AOP가 걸리지 않는다.&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 365px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/af573b452edb91f619a33bf55f35f8e3/dbde8/2022-11-07-aop1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41.917808219178085%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABJ0AAASdAHeZh94AAABlUlEQVR42mWQyY7bMBBE9RuBAyeYzFiyJFsLd1GUKEv2LIcgQHLK//9HpUQghyCHQrPZ5OvqzpreQK9vUPMNMt4hA+O4QDDq5Y6m06jyCvX5goqq/1P9T54pNcAT4H3EFB8YCHJuwjxtCHGDNR6611C7OoW+EZCMopUQjeRZQ6R8r0lkq/T4ISf8lDN+ywVbY3HrLL53I5bWYqX77cZGZsRgR7TXHlI4aOXRE+bSfYAfZngXkDV1iw+6/MXHj8njYx4wOY11IjhYbMzX4BC9wWhUqs1U9NSgk1Pv2OjS4lo1yErO7TuJVVvMo8Lbwk+mx2vUuE8KW9glk4IRhGu8LwaPSM0aHR1b5RLsUl6RFc8FitMZXSvYpYOWFlb79FAJyxE7SO4vcMf7yMfDEZ8/HXGgjoevePryhNO3U1JyWLwUqIqKS1cw0mCNC6YxcDcOYRjh6Hw/G6lTXTQtmvqKvu0JeUnAMw3lz/lf4BllXtKZgVEWyxwJMXSnEyyGyHuTdqSFgrcD32qMzqMkaAfupnJOugP/APZiAnX0CzSJAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 11 07 aop1&quot;
        title=&quot;&quot;
        src=&quot;/static/af573b452edb91f619a33bf55f35f8e3/dbde8/2022-11-07-aop1.png&quot;
        srcset=&quot;/static/af573b452edb91f619a33bf55f35f8e3/dbde8/2022-11-07-aop1.png 365w&quot;
        sizes=&quot;(max-width: 365px) 100vw, 365px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;실제 인텔리제이에서 private 메서드에 &lt;code class=&quot;language-text&quot;&gt;@Transactional&lt;/code&gt; 을 선언하면 위와 같이 컴파일 에러를 띄워줍니다.
해석해보자면 private을 사용하지 말라는군요!&lt;/p&gt;
&lt;h3&gt;왜 private 메서드에는 &lt;code class=&quot;language-text&quot;&gt;@Transactional&lt;/code&gt;이 붙을 수 없을까요?🤔&lt;/h3&gt;
&lt;p&gt;해당 원인을 파악하려면 먼저 프록시에 대해서 알아봐야합니다.
스프링 aop에서 프록시는 크게 &lt;code class=&quot;language-text&quot;&gt;JDK Dynamic proxy&lt;/code&gt;또는 &lt;code class=&quot;language-text&quot;&gt;CGLIB&lt;/code&gt;으로 작동합니다.
그리고 spring boot 1.4 버전 이후부터는 default로 &lt;code class=&quot;language-text&quot;&gt;CGLIB&lt;/code&gt;을 사용합니다.&lt;/p&gt;
&lt;p&gt;CGLIB은 동적으로 상속을 통해 프록시를 생성합니다.
따라서 private 메서드는 상속이 불가능하기 때문에 프록시로 만들어지지 않는것이죠!&lt;/p&gt;
&lt;p&gt;그러면 아래처럼 &lt;code class=&quot;language-text&quot;&gt;protected&lt;/code&gt;나 &lt;code class=&quot;language-text&quot;&gt;public&lt;/code&gt;으로 메서드를 만들면 정상적으로 트랜잭션이 동작할까요?
정답은 protected일 때 또한 정상 동작하지 않습니다🥲&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 203px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6bd9ea66b78dcca5fcf91445b4e191f8/187ee/2022-11-07-aop2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 25.615763546798032%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA90lEQVR42nWQy27CMBRE8zdV4sRv14kDpCLlpQIVaYAIsaiqbvv/y+lN0pZuWIw0c2zNvXakH0v4egXlArTNoWwBaTyUyUfmCvIe2peQyiGNM2QJv6so7M8oXw8odi1mbYfp8YrZ8YKwaxC2LSry06bDpDkPpeyBIU2yoXgQlfwfEmlu4JiEjTlsIqBjMWZGnsSJu2RkirLkGkpoaGGgek/S0twK32yFq6twck/4zBfo3BwX8l9hg430WKsS777GR77EijvUyzUO9KLtyx7P8wWCD6TyVmiYAu/X7gEbISP9Mv5zkbMx95tZ+ksjLWSmIFJJZ+Kv8BtNaqMB8CAneAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;protected&quot;
        title=&quot;&quot;
        src=&quot;/static/6bd9ea66b78dcca5fcf91445b4e191f8/187ee/2022-11-07-aop2.png&quot;
        srcset=&quot;/static/6bd9ea66b78dcca5fcf91445b4e191f8/187ee/2022-11-07-aop2.png 203w&quot;
        sizes=&quot;(max-width: 203px) 100vw, 203px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt; &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 180px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/67116898ef1328f37a97abf57cb70666/e9ea2/2022-11-07-aop3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 31.666666666666664%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABL0lEQVR42mWQS2/CMBCE82daILYTx06MnUcjUwgECC9B1XKpemlP/f/H6fKooOrhkz1e7+xqAjdboT4cMfQN3LRD3q7hJnNYws062KbDcHSqLVEstzD1M3iPQbAYIoz+ETztj7DTORJXwy13yBdrYodqs4eZzGDGLWy7giPKzQFp6cEeGfjJYCDAiT+GMowh+xyOSbh+BN3jiKlBPAyg+wKG6imdkraKqUGJBJky0DKFkhop3SWXZHw1fMk8vvIpvosWr8bjbTjCQueopMWna/Bux/ggvHYYNy22mx26xQrNc4PcVfC1R0JDfjcNbJQiEwqKpmiu4EgntG1MOuEJjNCEOuvClShsgTKvaLMMkoxi+hPd5RmwUICdsiDBiDC8ZcLPtcv7WV8zu3HJ8T7DHzvYw7wJRKS/AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;public-method&quot;
        title=&quot;&quot;
        src=&quot;/static/67116898ef1328f37a97abf57cb70666/e9ea2/2022-11-07-aop3.png&quot;
        srcset=&quot;/static/67116898ef1328f37a97abf57cb70666/e9ea2/2022-11-07-aop3.png 180w&quot;
        sizes=&quot;(max-width: 180px) 100vw, 180px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;분명히 인텔리제이로 확인을 해보면 컴파일 에러는 나오지 않는데 말이죠. 해당 이유는 앞서 말했던 JDK Dynamic proxy 가 원인이었기 때문입니다.
JDK Dynamic proxy는 인터페이스를 기반으로 동작합니다. 따라서 protected 메서드에서는 프록시가 동작할 수 없는 것이죠.
그래서 스프링에서는 일관된 AOP적용을 위해서 protected로 선언된 메서드 또한 트랜잭션이 걸리지 않도록 한 것입니다.
즉, 프록시 설정에 따라 트랜잭션이 적용되었다 안되었다 하는 변칙적인 결과를 막기 위함인거죠.&lt;/p&gt;
&lt;h2&gt;두 번째 사실: 같은 클래스내에서 트랜잭션이 걸린 메소드를 호출하면 트랜잭션이 작동하지 않는다.&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;A&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;progress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;progress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;예를들어 다음과 같은 상황에서 &lt;code class=&quot;language-text&quot;&gt;progress()&lt;/code&gt;는 정상적으로 트랜잭션이 적용될까요?&lt;/p&gt;
&lt;p&gt;이번에도 안됩니다.&lt;/p&gt;
&lt;p&gt;Spring AOP에서 프록시의 동작 과정을 보면 프록시를 통해 들어오는 외부 메서드 호출을 인터셉트 하여 작동합니다. 바로 이러한 성격때문에 &lt;code class=&quot;language-text&quot;&gt;self-invocation&lt;/code&gt;이 라고 불리는 현상이 발생합니다.&lt;/p&gt;
&lt;p&gt;프록시의 내부 빈에서 프록시를 호출 했다는 것이죠. 그림으로 보면 이해하는데 도움이 될것같아서 그려봤습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1440px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/230f19e03e489df6862ff483c3371996/89066/2022-11-07-aop4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 69.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAABjklEQVR42o1S2W7CMBDk/z+I90o8lkogoYBUAZHa5iBOnBPbSZh6l4QmlKasNLKVtScz65lhokzdwI8yxGmFShk8U7OpZnnWeHM+8PL6jjDOYZoGZ11DmTHoG/UmCUndZyCZNMkqpPkZiVX6CNL2A5Gjbtq/CbVp2CpVY//edAru63K58Epn6c4koZAl7/0gwPHoQkqJwO5Pp9MNvu8jy1LIQkFb6zP6wxA3y6zwSpimKYqi4D4p1Voz2rZFVVUQUYSs0FBE2BPQwSGhHhDKjpDKdV2sVitst1smNMYgjgXSnnC322GxWGC5XOJwOCDPc7705fn2kBoppFJKMUghCaB1RBiGIRzHwX6/t7PI+FBr1RZlxa/LCmWKsryq3Ww2LGA+n2O9XkMIYfuJTYEaW/4Vm7sZ9oS09zwPJIT25EgmMbtRzz6K7C4OY3KbtbUsRPRjeSrYfQ5pZhQPigkpC210eLXw7KyLImfL2tT/5fD6EG1r1ZO6B2g7wZTZyWA3NhKByJj0P0RJzuOhcXwD7wQ/6h4VgLYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;proxy&quot;
        title=&quot;&quot;
        src=&quot;/static/230f19e03e489df6862ff483c3371996/89066/2022-11-07-aop4.png&quot;
        srcset=&quot;/static/230f19e03e489df6862ff483c3371996/0eb09/2022-11-07-aop4.png 500w,
/static/230f19e03e489df6862ff483c3371996/1263b/2022-11-07-aop4.png 1000w,
/static/230f19e03e489df6862ff483c3371996/89066/2022-11-07-aop4.png 1440w&quot;
        sizes=&quot;(max-width: 1440px) 100vw, 1440px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
main메서드에서 그림과 같이 A의 init()를 호출하고 init()에서 progress()를 호출하면 그림과 같이 프록시 내부에서 호출하게 됩니다.
따라서 proxy가 인터셉트하지 못해서 트랜잭션이 동작하지 않는것입니다.&lt;/p&gt;
&lt;h2&gt;오해: 그렇다면 내부 메서드에서 호출은 AOP적용이 불가능하다&lt;/h2&gt;
&lt;p&gt;해당 주장은 사실이 아닙니다. 공식문서를 잘 보면 해결 방법이 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;self-invocation(in effect, a method within the target object calling another method of the target object)
does not lead to an actual transaction at runtime…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;타겟 내에서 타겟의 다른 메서드를 호출할 때, 런타임에 실제 트랜잭션이 작동하지 않는다고 합니다. 즉, 런타임 시점에 작동은 안 하지만 이것을 컴파일 시점에 적용하면 된다는 것이죠.&lt;/p&gt;
&lt;p&gt;그렇다면 어떻게 컴파일 시점에 프록시를 적용할 수 있을까요?&lt;/p&gt;
&lt;p&gt;스프링에서 AOP를 구현하는 방식에는 스프링 AOP도 있지만 AspectJ라는 강력한 도구도 있습니다. AspectJ는 스프링AOP와 다르게 컴파일 시점에 위빙이 이루어집니다. 따라서 AspectJ를 사용하면 self-invocation문제를 해결할수 있는것이죠!&lt;/p&gt;
&lt;p&gt;그런데 만약 Spring AOP를 쓰고있는 상황에서 이런 문제가 발생하면 어떡하죠? 이럴경우 다른 방법도 존재합니다!&lt;/p&gt;
&lt;h3&gt;첫번째 방법은 &lt;code class=&quot;language-text&quot;&gt;AopContext를 이용하는 방법&lt;/code&gt;입니다.&lt;/h3&gt;
&lt;p&gt;AopContext의 &lt;code class=&quot;language-text&quot;&gt;currentProxy()&lt;/code&gt; 메서드는 현재 AOP proxy를 반환합니다.
이번엔 코드로 확인해봅시다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Aspect&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AopService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Pointcut&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;execution(void kitchenpos.application.MenuService*.progress(..))&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pointCut&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Before&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;pointCut()&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;aop works!!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;우선 aop를 적용해줍니다.
progress() 메서드가 동작하기 전에 &lt;code class=&quot;language-text&quot;&gt;aop works!!&lt;/code&gt; 라는 에러 메시지를 출력해주는 aop입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;progress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;progress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;PROGRESS&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그리고 다음과 같이 실행을 해주면 아래와 같이 적용되지 않습니다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 697px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e538de1361186e34fd1bb296a0773730/11663/2022-11-07-aop5.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 19.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABJ0AAASdAHeZh94AAAAvElEQVR42nWO2w6CMBBE+RMvQAt0e6GgGJRGiJcY//97xm1JfNKHk810O7OTvR83LNMZS7hg5nm/BkynAYYsOtdjGgP69pC08x6t8ehYx13E2y7N+G6URWaM5c8tCwdZ1okylxC5gCwqqEqhkSunYcTz/sIc5mS25L5ErRuDTAoBTQqeg3VNbGxQ7Ark2zyx2+xRFRKONNNy2yO38sn8i6wRCkSE8ciXlOZA+jaK1Ly3ijD4tYGqCFTrv4Ef6cZ7Usr23mIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;test-fail&quot;
        title=&quot;&quot;
        src=&quot;/static/e538de1361186e34fd1bb296a0773730/11663/2022-11-07-aop5.png&quot;
        srcset=&quot;/static/e538de1361186e34fd1bb296a0773730/0eb09/2022-11-07-aop5.png 500w,
/static/e538de1361186e34fd1bb296a0773730/11663/2022-11-07-aop5.png 697w&quot;
        sizes=&quot;(max-width: 697px) 100vw, 697px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이번에는 AopContext를 활용하여 아래와 같이 수정해보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MenuService&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AopContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentProxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;progress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;progress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;PROGRESS&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그리고 &lt;code class=&quot;language-text&quot;&gt;@EnableAspectJAutoProxy(exposeProxy = true)&lt;/code&gt; 옵션을 적용해서 작동시켜 봅니다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 563px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/93516c3962ab6fe2b965d04d916c541c/e80ce/2022-11-07-aop6.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 18.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABJ0AAASdAHeZh94AAAAzUlEQVR42lWQ606EMBCFeREVxULb6QVYCLLXbLLJ6g8T3/9lPgcwRn98mUl75sxpCwmOJJno40pdNZiXWmu99n/xjSBWcYHsM/uw4006Rt9ySiPHPFAYY/DeknxSYcQaS/PaUJUVz4/lL+XDE0aXrPdKqyE+r3e+bh+8X24c8sgUeop1axCGoWdW+tzRxo7gBFs73A+LUZcSY5eZ+qyalmk6cD5d2M9HgmyBCmvcOpiTMO8Cvl4SWpZz989QNSL0cSNLRJqwfYPWqC9c+AbtPXkUGyqzrQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;test-success&quot;
        title=&quot;&quot;
        src=&quot;/static/93516c3962ab6fe2b965d04d916c541c/e80ce/2022-11-07-aop6.png&quot;
        srcset=&quot;/static/93516c3962ab6fe2b965d04d916c541c/0eb09/2022-11-07-aop6.png 500w,
/static/93516c3962ab6fe2b965d04d916c541c/e80ce/2022-11-07-aop6.png 563w&quot;
        sizes=&quot;(max-width: 563px) 100vw, 563px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;aop가 작동하는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;두 번째 방법은 &lt;code class=&quot;language-text&quot;&gt;자기 자신을 호출하는 방법&lt;/code&gt;입니다.&lt;/h3&gt;
&lt;p&gt;내부에서 프록시를 호출하면 인터셉터가 작동하지 않으므로 외부에서 호출하는 방식으로 해결하는 것입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@Resource&lt;/li&gt;
&lt;li&gt;@Autowired&lt;/li&gt;
&lt;li&gt;@Inject&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;등 여러 방법이 있습니다.&lt;/p&gt;
&lt;p&gt;모두 적용이 가능하지만 이번 예시에서는 &lt;code class=&quot;language-text&quot;&gt;@Resource&lt;/code&gt; 어노테이션을 사용하여 AOP를 적용해보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MenuService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Resource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;menuService&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;MenuService&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같이 &lt;code class=&quot;language-text&quot;&gt;@Resuroce&lt;/code&gt; 를 통해 빈 객체로 등록해주고 이번에는 &lt;code class=&quot;language-text&quot;&gt;self.progress()&lt;/code&gt;를 호출해줍니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;progress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;progress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;PROGRESS&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;해당 방법이 성공한 이유는 외부에서 내부 빈으로 호출을 하므로 아래와 같이 aop가 정상 동작합니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 558px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/affb2d369a238af7b33a70adad04b814/65207/2022-11-07-aop7.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 20.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABJ0AAASdAHeZh94AAAAuElEQVR42mWPawrCQAyEvYhibZO63ey23dYniApCBcH732ZMYxHUHx+BJDOTzKrSoxKH6CNqqREqQZEVBq3oD0dOdwKiCzhJh4NvcQ0b7EPSfsQsVyEzTaYNvC76tYaUFbJF9sVyvlRThtPZKH4ODzzvDwyXG5I0b8NRbIjgeEg475NdO/KZTYzfpKiX7Vq0oUZqevTd1qqomRlyXloq5YSS9FJme63I6N3/gXPGmlgDBGLfiBEmwxeAlXfTdQ5HXwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;test-success&quot;
        title=&quot;&quot;
        src=&quot;/static/affb2d369a238af7b33a70adad04b814/65207/2022-11-07-aop7.png&quot;
        srcset=&quot;/static/affb2d369a238af7b33a70adad04b814/0eb09/2022-11-07-aop7.png 500w,
/static/affb2d369a238af7b33a70adad04b814/65207/2022-11-07-aop7.png 558w&quot;
        sizes=&quot;(max-width: 558px) 100vw, 558px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;*주의: 해당 방식은 2.6 보다 낮은 버전에서 가능합니다.
스프링 부트 2.6 버전부터는 기본적으로 순환 참조를 금지하도록 변경되었습니다.
만약 2.6 버전 이상에서 실습해보고 싶으시다면 아래의 설정을 추가하시면 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;spring&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;allow-circular-references&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.6-Release-Notes&quot;&gt;Spring Boot 2.6 Release Notes&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;지금까지 트랜잭션과 AOP에 대한 사실과 오해를 잠깐 맛보았습니다.&lt;/p&gt;
&lt;p&gt;AOP는 내용이 많고 어렵기도 해서 추가적인 학습들이 필요합니다.&lt;/p&gt;
&lt;p&gt;AOP를 사용하면 부가 기능을 따로 추출하여 비즈니스 로직에만 집중할 수 있는 장점이 생기지만
잘못 사용하면 그만큼 side-effect가 생깁니다.&lt;/p&gt;
&lt;p&gt;이 글을 읽고 낯설었던 aop에 대해 한걸음 가까워졌으면 좋겠습니다!&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction-declarative-annotations&quot;&gt;공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/34197964/why-doesnt-springs-transactional-work-on-protected-methods&quot;&gt;stack-overflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=MFckVKrJLRQ&amp;#x26;t=19s&quot;&gt;JDK Dynamic Proxy 와 CGLIB (1)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=RHxTV7qFV7M&amp;#x26;t=291s&quot;&gt;JDK Dynamic Proxy 와 CGLIB (2)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[스프링 이벤트 적용기]]></title><description><![CDATA[상황 스모디 프로젝트를 하면서 랭킹 기능을 도입하기로 했습니다. 랭킹 기능은 유저가 활동을 했을 때, 활동에 따라 랭킹 점수를 부여해야 합니다. 저희 서비스는 챌린지에 도전하고 매일 챌린지에 해당하는 활동을 인증하여 총…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-11-14-spring-event/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-11-14-spring-event/</guid><pubDate>Mon, 14 Nov 2022 13:10:00 GMT</pubDate><content:encoded>&lt;h2&gt;상황&lt;/h2&gt;
&lt;p&gt;스모디 프로젝트를 하면서 랭킹 기능을 도입하기로 했습니다. 랭킹 기능은 유저가 활동을 했을 때, 활동에 따라 랭킹 점수를 부여해야 합니다. 저희 서비스는 챌린지에 도전하고 매일 챌린지에 해당하는 활동을 인증하여 총 3회 인증 시 성공하는 사이클로 이루어져 있습니다. 위 기능을 추가하기 위해선 기존 인증을 하는 서비스 로직에 랭킹 점수를 올리는 기능을 추가해야 합니다. 더즈와 페어로 구현하면서 이벤트 적용, 트랜잭션 분리, 비동기 처리 등 활동과 랭킹 관련 로직을 분리하는 과정을 글로 정리했습니다.&lt;/p&gt;
&lt;h3&gt;용어 정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;인증: 사용자가 챌린지에 관련된 글과 이미지로 인증&lt;/li&gt;
&lt;li&gt;사이클(Cycle): 총 3번의 인증으로 이루어져 있으며 3번의 인증을 마치면 챌린지에 대해 한 번의 사이클을 성공(NOTHING -&gt; FIRST -&gt; SECOND -&gt; SUCCESS)&lt;/li&gt;
&lt;li&gt;랭킹 정책: 3번의 인증에 따라 점수를 부여한다. (FIRST:10점, SECOND:30점, SUCCESS:60점)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;인증을 하면 해당 사용자의 랭킹 점수를 올려주어야 합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProgressResponse&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;increaseProgress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TokenPayload&lt;/span&gt; tokenPayload&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProgressRequest&lt;/span&gt; progressRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 인증&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Cycle&lt;/span&gt; cycle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cycleService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;increaseProgress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        tokenPayload&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        progressRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCycleId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        progressRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProgressTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;progressRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProgressImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; imageStrategy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        progressRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDescription&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// 랭킹 관련 로직 추가&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProgressResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cycle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// response dto 반환&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;사용자가 글과 이미지로 인증을 하면 위 서비스 메서드가 호출됩니다. 기존 활동 로직이 끝나면 랭킹 로직 관련 로직 부분에 구현해주어야 합니다.&lt;/p&gt;
&lt;h3&gt;인증과 랭킹 로직은 같은 트랜잭션인가?&lt;/h3&gt;
&lt;p&gt;페어인 더즈와 이에 대해 많은 이야기를 했습니다. 2가지 의견이 있었습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;같은 트랜잭션이다!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;트랜잭션을 합치게 되면 랭킹 관련 로직에서 에러가 발생했을 때 인증한 기록들도 롤백됩니다. 랭킹 점수에는 정합성이 맞지만, 트랜잭션의 전파가 넓어지고 랭킹 점수를 올릴 때 에러가 발생했는데 인증했던 기록도 롤백된다는 것이 맞는가라는 의문이 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;다른 트랜잭션이다!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;트랜잭션을 분리하면 랭킹 관련 로직에서 에러가 발생하더라도 인증했던 기록에는 영향을 주지 않습니다. 하지만, 인증을 하고 랭킹 점수가 올라가지 않아 점수가 누락되는 상황이 생길 수 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;트랜잭션은 분리하되 점수가 누락되는 상황을 예방하자!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;트랜잭션을 분리했을 때 문제가 되는 상황은 점수가 누락되는 것 입니다. 점수가 누락되는 문제만 해결된다면 트랜잭션을 분리하는 것이 좋겠다고 결정했습니다.&lt;/p&gt;
&lt;h2&gt;1. 관심사 분리&lt;/h2&gt;
&lt;p&gt;이벤트를 사용하여 인증 로직과 랭킹 로직간의 결합도를 끊고 관심사를 분리할 수 있습니다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1729px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c53511aedb4293338926fc8415e6feb5/429a5/spring-event-2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 17.400000000000002%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wgARCAADABQDASIAAhEBAxEB/8QAFwABAAMAAAAAAAAAAAAAAAAAAAIFCf/EABQBAQAAAAAAAAAAAAAAAAAAAAL/2gAMAwEAAhADEAAAAdzZiN2Cv//EABcQAQEBAQAAAAAAAAAAAAAAAAQCAwH/2gAIAQEAAQUCFlNXJ8+IMfIuf//EABoRAAICAwAAAAAAAAAAAAAAAAEhAhARMbH/2gAIAQMBAT8BLMcvfDX/xAAaEQABBQEAAAAAAAAAAAAAAAACEBESIWKx/9oACAECAQE/AQqTZ6Kf/8QAJhAAAgIABAQHAAAAAAAAAAAAAQIDEQASEzEEBSEjIkFRYWKhov/aAAgBAQAGPwLmIYyMH4qSw00xoEMKS37a1sseUDcAHAHcpcyAa85GWh6yfe/v1xpQhgllvFJJKbPzlZ3/AFQ8sf/EABgQAQEBAQEAAAAAAAAAAAAAAAERITEA/9oACAEBAAE/ISG1gqXZ0Oj4gfDPI0QlSKLeKkUAIedK2Vaq7gAwEAe//9oADAMBAAIAAwAAABALz//EABkRAQEBAAMAAAAAAAAAAAAAAAERIRAxUf/aAAgBAwEBPxAgADMdI0I5GtOmvvH/xAAYEQEAAwEAAAAAAAAAAAAAAAABEBFBIf/aAAgBAgEBPxBNqRFCKIjCJogjiWdj/8QAFxABAQEBAAAAAAAAAAAAAAAAASERAP/aAAgBAQABPxCHaPC0/hbGAAXhyza90I+Jig0Y2NyQnGYASDv/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;spring event 2&quot;
        title=&quot;&quot;
        src=&quot;/static/c53511aedb4293338926fc8415e6feb5/429a5/spring-event-2.jpg&quot;
        srcset=&quot;/static/c53511aedb4293338926fc8415e6feb5/953fe/spring-event-2.jpg 500w,
/static/c53511aedb4293338926fc8415e6feb5/0a251/spring-event-2.jpg 1000w,
/static/c53511aedb4293338926fc8415e6feb5/429a5/spring-event-2.jpg 1729w&quot;
        sizes=&quot;(max-width: 1729px) 100vw, 1729px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Spring에서 이벤트 사용 방법&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;• Event publication, namely to beans that implement the &lt;code class=&quot;language-text&quot;&gt;ApplicationListener&lt;/code&gt; interface, through the use of the &lt;code class=&quot;language-text&quot;&gt;ApplicationEventPublisher&lt;/code&gt; interface. -spring docs-&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Spring의 &lt;code class=&quot;language-text&quot;&gt;ApplicationContext&lt;/code&gt; 는 이벤트를 활용할 수 있습니다. &lt;code class=&quot;language-text&quot;&gt;BeanFactory&lt;/code&gt; 에서 향상된 기능 중 하나입니다. &lt;code class=&quot;language-text&quot;&gt;ApplicationEventPublisher&lt;/code&gt; 인터페이스로 이벤트를 &lt;code class=&quot;language-text&quot;&gt;ApplicationContext&lt;/code&gt; 에 넘겨주고 이를 &lt;code class=&quot;language-text&quot;&gt;Listener&lt;/code&gt; 가 받아서 처리하는 구조로 되어있습니다. 여기선 어노테이션 기반의 &lt;code class=&quot;language-text&quot;&gt;Listener&lt;/code&gt; 로 설명하겠습니다.&lt;/p&gt;
&lt;h3&gt;ApplicationEventPublisher&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@FunctionalInterface&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApplicationEventPublisher&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;publishEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ApplicationEvent&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;publishEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;publishEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ApplicationContext&lt;/code&gt; 에 이벤트를 발행해주는 인터페이스입니다. &lt;code class=&quot;language-text&quot;&gt;publishEvent(Object event)&lt;/code&gt;  메서드의 &lt;code class=&quot;language-text&quot;&gt;event&lt;/code&gt; 는 &lt;code class=&quot;language-text&quot;&gt;ApplicationEvent&lt;/code&gt; 추상 클래스를 상속한 객체여도 되지만, 직접 구현한 POJO로도 동작을 합니다. 이 객체에는 이벤트에 필요한 데이터를 저장할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;@EventListener&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ApplicationContext&lt;/code&gt; 에서 이벤트가 발행되면 &lt;code class=&quot;language-text&quot;&gt;@EventListener&lt;/code&gt; 가 붙은 적합한 메서드를 찾아 실행합니다. &lt;code class=&quot;language-text&quot;&gt;publicEvent(Object event)&lt;/code&gt; 로 발행했던 &lt;code class=&quot;language-text&quot;&gt;event&lt;/code&gt; 를 파라미터로 받는 메서드를 모두 실행합니다. 이 때 반드시 이벤트를 받을 메서드의 파라미터는 &lt;code class=&quot;language-text&quot;&gt;event&lt;/code&gt; 만 가지고 있어야 합니다.&lt;/p&gt;
&lt;h3&gt;이벤트로 랭킹 로직 분리&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProgressResponse&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;increaseProgress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TokenPayload&lt;/span&gt; tokenPayload&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProgressRequest&lt;/span&gt; progressRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 인증&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Cycle&lt;/span&gt; cycle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cycleService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;increaseProgress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        tokenPayload&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        progressRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCycleId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        progressRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProgressTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;progressRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProgressImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; imageStrategy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        progressRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDescription&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// 이벤트 발행&lt;/span&gt;
    applicationEventPublisher&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;publishEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CycleProgressEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cycle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProgressResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cycle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 이벤트를 발행한 시점에 실행&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@EventListener&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CycleProgressEvent&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token class-name&quot;&gt;Cycle&lt;/span&gt; cycle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCycle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 1~2번 과정&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RankingActivity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; activities &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findTargetActivities&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cycle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// 3번&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;updateActivities&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cycle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLatestCycleDetail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; activities&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RankingActivity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findTargetActivities&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Cycle&lt;/span&gt; cycle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// 1번&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;LocalDateTime&lt;/span&gt; latestProgressTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cycle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLatestProgressTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RankingPeriod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; inProgressPeriods &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rankingService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findInProgressPeriod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;latestProgressTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;addPeriodIfAbsent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inProgressPeriods&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; latestProgressTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cycle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMember&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// 2번&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RankingActivity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; myActivities &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rankingService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAllActivity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inProgressPeriods&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;addActivityIfAbsent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inProgressPeriods&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; myActivities&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; myActivities&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;인증 로직에서 &lt;code class=&quot;language-text&quot;&gt;CycleProgressEvent(cycle)&lt;/code&gt; 을 발행하면 위 이벤트 메서드가 실행됩니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;인증한 사이클의 최근 인증한 시간 즉 방금 인증한 시간을 조회하고 이에 맞는 주간 랭킹 기간(&lt;code class=&quot;language-text&quot;&gt;RankingPeriod&lt;/code&gt;)을 조회한다.
&lt;ul&gt;
&lt;li&gt;만약 랭킹 기간이 없다면 그 주의 랭킹 기간을 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;랭킹 기간들을 순회하면서 사용자가 참여중인 랭킹 활동을 조회한다.
&lt;ul&gt;
&lt;li&gt;만약 랭킹 기간에 참여중이지 않는다면 랭킹 활동을 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인증한 활동에 대한 점수를 랭킹 활동에 추가한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;2. 트랜잭션 분리&lt;/h2&gt;
&lt;p&gt;이벤트를 활용하여 로직을 분리했지만, 위 로직은 활동과 랭킹이 같은 트랜잭션으로 묶여있습니다. 그리고 로직은 분리했지만, 동기적으로 실행하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1110px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e849d45469e7a7b1e6b327965b6fa06e/ffc38/spring-event-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 18.999999999999996%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA7ElEQVR42k2PXW+CQBBF/f//polPTdon0yb2A0yridBSCKCigOwCbi0LpyONSW8ymYd7Z3LupK5rlNK0bYM+WbSBoZetNU3TjN5JvPYM6gTHsmC99kiShCiKUHI/aEUvmYsmr67L2/uShfOM6x1wIihKzdN8jiteEAQUeYafGrwMXhYrbqZTbu/ueZjN0LstXRjQHfZ/D/dxTFkUHPMD28Lw6AtFpeTRJ47jEIbRSJUrSymE+YVstST+8Cm3G/g2QtfSG6nGwKTzPfo0wX4F2KrCDtB13VjXSKiVsLWWq3qpd96k/GQ7mWy8+a9f6qwscWbsqzAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;spring event 3&quot;
        title=&quot;&quot;
        src=&quot;/static/e849d45469e7a7b1e6b327965b6fa06e/ffc38/spring-event-3.png&quot;
        srcset=&quot;/static/e849d45469e7a7b1e6b327965b6fa06e/0eb09/spring-event-3.png 500w,
/static/e849d45469e7a7b1e6b327965b6fa06e/1263b/spring-event-3.png 1000w,
/static/e849d45469e7a7b1e6b327965b6fa06e/ffc38/spring-event-3.png 1110w&quot;
        sizes=&quot;(max-width: 1110px) 100vw, 1110px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProgressResponse&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;increaseProgress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TokenPayload&lt;/span&gt; tokenPayload&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProgressRequest&lt;/span&gt; progressRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 인증&lt;/span&gt;
	&lt;span class=&quot;token class-name&quot;&gt;Cycle&lt;/span&gt; cycle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cycleService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;increaseProgress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        tokenPayload&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        progressRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCycleId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        progressRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProgressTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;progressRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProgressImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; imageStrategy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        progressRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDescription&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// 이벤트 발행 (랭킹 로직 처리)&lt;/span&gt;
    applicationEventPublisher&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;publishEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CycleProgressEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cycle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// 이벤트 처리가 끝나면 이어서 실행&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProgressResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cycle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@EventListener&lt;/code&gt; 는 발행되자마자 실행됩니다. 하나의 스레드에서 실행되기 때문에 이벤트 처리가 끝나야 이벤트를 발행한 곳의 남은 로직을 처리하고 트랜잭션을 커밋할지 롤백할지 결정하게 됩니다.&lt;/p&gt;
&lt;h3&gt;&lt;code class=&quot;language-text&quot;&gt;@TransactionalEventListener&lt;/code&gt;를 사용하여 이벤트 발행 시점 결정하기&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1246px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f284f1e91973fd37a5da562716a70ff7/47fb9/spring-event-4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 29.599999999999998%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABBElEQVR42pWPfU+DMByE+f5fy6iJ4hIzt8zx0k1hYzAECgxKoY8NMSaL/qGXPElzuV7u5zRNQ57nyKqkqAeaHpTqKYpipqoqotygRxBhSGjZ73a8bresNxtCIfCt53kebdviRO97VqsXNusla1HxJAzJ6YTruvh+QJYmPIuW+APuH1xu7m55XCwojweaOKI+xMjAp71cGMfRFqYX0lxSlBLZakRq7KrSlvkEQTC/O2XoBqjeduR2WWFXTXUNg8LYa+gtX3LO1vcSWEZQd2Y2p8meqPWMMeY7TNvYkMTIiqnv+E0OuueYKbaRIso0/WCuAleFf5AzxDEqOVkSdHbmn/9/FH4Cz7fMENAPT5YAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;spring event 4&quot;
        title=&quot;&quot;
        src=&quot;/static/f284f1e91973fd37a5da562716a70ff7/47fb9/spring-event-4.png&quot;
        srcset=&quot;/static/f284f1e91973fd37a5da562716a70ff7/0eb09/spring-event-4.png 500w,
/static/f284f1e91973fd37a5da562716a70ff7/1263b/spring-event-4.png 1000w,
/static/f284f1e91973fd37a5da562716a70ff7/47fb9/spring-event-4.png 1246w&quot;
        sizes=&quot;(max-width: 1246px) 100vw, 1246px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
이 어노테이션을 사용하면 이벤트 발행 시점을 결정할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@TransactionalEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;phase &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TransactionPhase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;AFTER_COMMIT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;afterCommit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// 이벤트 발행 주체가 커밋되면 실행 (default)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@TransactionalEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;phase &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TransactionPhase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;AFTER_ROLLBACK&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;afterRollBack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// 이벤트 발행 주체가 롤백되면 실행&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@TransactionalEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;phase &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TransactionPhase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;AFTER_COMPLETION&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;afterCompletion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// 이벤트 발행 주체가 끝나면 실행 (롤백, 커밋)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@TransactionalEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;phase &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TransactionPhase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;BEFORE_COMMIT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;beforeCommit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// 이벤트 발행 주체가 커밋되기 전에 실행&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;phase&lt;/code&gt; 를 적어주지 않으면 default로 커밋 후에 실행하게 됩니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;그럼 &lt;code class=&quot;language-text&quot;&gt;@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)&lt;/code&gt; 을 사용해서 이벤트 발행 주체가 커밋하고 실행하도록 하면 되겠네요?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;커밋 후에 이벤트를 발행해서 이벤트에서 에러가 발생하더라도 이벤트를 발행한 주체는 이미 커밋이 됐기 때문에 영향을 주지 못합니다. 하지만, 같은 트랜잭션으로 묶여있는 상황에서 이벤트를 발행하기 전에 커밋이 됐기 때문에 조회는 가능하지만, &lt;strong&gt;쓰기는 불가능&lt;/strong&gt;합니다. 트랜잭션이 사라진 것이 아니라 이벤트가 이미 커밋된 트랜잭션에 참여한 상황이 발생한 것 입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@TransactionalEventListener&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// default = TransactionPhase.AFTER_COMMIT&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CycleProgressEvent&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token class-name&quot;&gt;Cycle&lt;/span&gt; cycle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCycle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 1~2번 과정&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RankingActivity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; activities &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findTargetActivities&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cycle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// insert 쿼리 발생&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// 3번&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;updateActivities&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cycle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLatestCycleDetail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; activities&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// update 쿼리 발생&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;1~3번 과정에서 랭킹 활동과 기간이 없다면 생성하는 로직도 포함되어 있고 활동의 점수들을 추가도 하여 insert, update 쿼리문을 사용해야 합니다. 만약 위 처럼 작성되어 있고 랭킹 관련 서비스 로직이 &lt;code class=&quot;language-text&quot;&gt;@Transactional&lt;/code&gt; 로 되어 있으면 insert와 update 쿼리문을 실행하지 못하게 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RankingActivity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findTargetActivities&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Cycle&lt;/span&gt; cycle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// 1번&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;LocalDateTime&lt;/span&gt; latestProgressTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cycle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLatestProgressTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RankingPeriod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; inProgressPeriods &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rankingService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findInProgressPeriod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;latestProgressTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;addPeriodIfAbsent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inProgressPeriods&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; latestProgressTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// insert 쿼리 발생&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cycle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMember&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// 2번&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RankingActivity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; myActivities &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rankingService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAllActivity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inProgressPeriods&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;addActivityIfAbsent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inProgressPeriods&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; myActivities&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// insert 쿼리 발생&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; myActivities&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 만약 이번주 랭킹 기간이 없다면 생성한다. -&gt; Insert 쿼리 발생&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;addPeriodIfAbsent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RankingPeriod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; periods&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LocalDateTime&lt;/span&gt; progressTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;periods&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        periods&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rankingService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createWeeklyPeriod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMonday&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;progressTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 만약 이번주 랭킹 활동이 없다면 생성한다. -&gt; Insert 쿼리 발생&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;addActivity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                         &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RankingActivity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; myActivities&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                         &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RankingPeriod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; myPeriods&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                         &lt;span class=&quot;token class-name&quot;&gt;RankingPeriod&lt;/span&gt; inProgressPeriod&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;myPeriods&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inProgressPeriod&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        myActivities&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rankingService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createFirstActivity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; inProgressPeriod&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 활동 점수들을 업데이트 한다. -&gt; Update 쿼리 발생&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateActivities&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CycleDetail&lt;/span&gt; cycleDetail&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RankingActivity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; activities&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Progress&lt;/span&gt; progress &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cycleDetail&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProgress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RankingActivity&lt;/span&gt; activity &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; activities&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        activity&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;plusPoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;progress&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;RankingService.createWeeklyPeriod()&lt;/code&gt; → insert 쿼리&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;RankingService.createFirstActivity()&lt;/code&gt; → insert 쿼리&lt;/li&gt;
&lt;li&gt;RankingActivity의 상태 변경으로 인한 변경 감지 -&gt; update 쿼리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Propagation.REQUIRES_NEW&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1238px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7002e6bccf6536d34ae7bc826ce096cf/74b07/spring-event-6.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 28.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABBUlEQVR42n2P6Y6CQBAGef+n2uwTrCJ4LKLgBcqAoIvAHLUz8suYOEmlk68n1d0eSoGS6L7DOLRGPzFgqfOceLkkS1N+w5BVEJCs10Tz+TOLFwvSKGIfx3R1jbez4d5yFJrrpeJ0PJBlJ7Jzyb6AdJvw9f3NZDplZgl9n9XMJ7TyQggeXTfS9yhbvWW0IdnGTFLDeiNYhD5BECJKwalQ7HYX/MkPG7eBFXCtUOfcXtODMS8YJ0xKyGuNsplsW+63huZ2Q0qJe6b9Qzc10kmGwdJjbM8J37B972C3CNMB0SjMo7WDDKNprDLPRooCVVXjZh+eNxQl8t7a+92U4f3Hy1naBR+F/9HYy3CpDFn5AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;spring event 6&quot;
        title=&quot;&quot;
        src=&quot;/static/7002e6bccf6536d34ae7bc826ce096cf/74b07/spring-event-6.png&quot;
        srcset=&quot;/static/7002e6bccf6536d34ae7bc826ce096cf/0eb09/spring-event-6.png 500w,
/static/7002e6bccf6536d34ae7bc826ce096cf/1263b/spring-event-6.png 1000w,
/static/7002e6bccf6536d34ae7bc826ce096cf/74b07/spring-event-6.png 1238w&quot;
        sizes=&quot;(max-width: 1238px) 100vw, 1238px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
트랜잭션 전파 속성을 &lt;code class=&quot;language-text&quot;&gt;REQUIRES_NEW&lt;/code&gt; 로 바꾸면 트랜잭션을 분리할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;propagation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Propagation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;REQUIRES_NEW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@TransactionalEventListener&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// default = TransactionPhase.AFTER_COMMIT&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CycleProgressEvent&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token class-name&quot;&gt;Cycle&lt;/span&gt; cycle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCycle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 1~2번 과정&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RankingActivity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; activities &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findTargetActivities&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cycle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// 3번&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;updateActivities&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cycle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLatestCycleDetail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; activities&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;3. 비동기 적용&lt;/h2&gt;
&lt;p&gt;앞서 말한 트랜잭션 전파 레벨을 수정하는 것으로 트랜잭션도 분리도 할 수 있었습니다. 그런데 만약 이벤트를 발행했을 때 해당 이벤트를 여러번 처리하게 된다면 어떻게 될까요?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;활동을 인증한다.&lt;/li&gt;
&lt;li&gt;인증을 했다는 이벤트를 발행한다.
&lt;ol&gt;
&lt;li&gt;인증을 했다는 푸시 알림을 사용자에게 보낸다.&lt;/li&gt;
&lt;li&gt;인증에 맞는 랭킹 점수를 올린다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위 상황은 하나의 스레드에서 실행하게 됩니다. 따라서 인증, 푸시 알림, 랭킹 중 하나라도 예상치 못한 에러가 발생하면 순서에 따라 서로 영향을 줄 수 있는 것이죠.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;인증한다.&lt;/li&gt;
&lt;li&gt;푸시 알림을 보낸다. (에러)&lt;/li&gt;
&lt;li&gt;랭킹 점수를 올린다. (무시)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위 문제는 푸시 알림에서 에러 처리를 해주면 해결할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;하나의 스레드, 다수의 커넥션&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;# application&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;properties
spring&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;datasource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hikari&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maximum&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;pool&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;size&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;데이터 커넥션 풀 사이즈를 1로 설정하고 앞선 로직을 실행하면 커넥션을 얻기 위해 계속 기다리게 됩니다.&lt;/p&gt;
&lt;p&gt;트랜잭션을 분리하게 되면 분리된 트랜잭션은 기존의 커넥션과 다른 커넥션으로 연결됩니다. 그럼 이벤트에 따라 실행되는 로직이 n개라면 n개의 커넥션으로 연결됩니다. 이 스레드가 끝나지 않는 이상 다수의 커넥션은 연결되어 있는 상태고 이는 성능에 문제가 발생할 수 있습니다.&lt;/p&gt;
&lt;p&gt;트랜잭션 분리와 더불어 더 확실하게 이벤트가 서로 영향을 주지 않으려면 비동기를 사용해야 합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Async&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@TransactionalEventListener&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// default = TransactionPhase.AFTER_COMMIT&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CycleProgressEvent&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token class-name&quot;&gt;Cycle&lt;/span&gt; cycle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCycle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 1~2번 과정&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RankingActivity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; activities &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findTargetActivities&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cycle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// 3번&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;updateActivities&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cycle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLatestCycleDetail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; activities&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootApplication&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableAsync&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SmodyApplication&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;SpringApplication&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SmodyApplication&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;메서드에 &lt;code class=&quot;language-text&quot;&gt;@Async&lt;/code&gt; 를 붙이고 &lt;code class=&quot;language-text&quot;&gt;@SpringBootApplication&lt;/code&gt; 이 붙은 main 메서드가 있는 클래스에 &lt;code class=&quot;language-text&quot;&gt;@EnableAsync&lt;/code&gt; 를 붙여주면 쉽게 비동기로 처리할 수 있습니다.&lt;/p&gt;
&lt;p&gt;위 처럼 구현하면 매번 이벤트가 발생할 때 마다 새로운 스레드를 생성하게 됩니다. 스레드를 생성하는 작업도 비용이 들 수 있고 메모리 영역을 차지하기 때문에 비동기 처리를 위한 스레드 풀도 설정하여 보다 효율적으로 사용할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AsyncConfig&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TaskExecutor&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;asyncExecutor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ThreadPoolTaskExecutor&lt;/span&gt; asyncExecutor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ThreadPoolTaskExecutor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        asyncExecutor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setThreadNamePrefix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;async-pool&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        asyncExecutor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setCorePoolSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        asyncExecutor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; asyncExecutor&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;Event listeners are encouraged to be as efficient as possible, individually using asynchronous execution for longer-running and potentially blocking operations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@EventListener&lt;/code&gt; 의 설명에 보면 비동기로 사용할 것을 권장하고 있습니다. 그래도 이벤트를 적용하게 된다면 무조건 비동기를 사용하는 것 보다 비즈니스 정책을 고려하여 적절하게 비동기, 트랜잭션 분리를 하는 것이 좋을 것 같습니다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#context-introduction&quot;&gt;https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#context-introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/spring-events&quot;&gt;https://www.baeldung.com/spring-events&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[트랜잭션 격리 수준]]></title><description><![CDATA[Transaction Isolation Level 애플리케이션을 개발 중 데이터베이스를 도입하면 격리 수준에 대한 이해가 자연스럽게 요구됩니다. 격리 수준은 데이터베이스의 ACID 성질 중 Isolation…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-11-07-mysql-isolation/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-11-07-mysql-isolation/</guid><pubDate>Mon, 07 Nov 2022 13:10:00 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;strong&gt;Transaction Isolation Level&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;애플리케이션을 개발 중 데이터베이스를 도입하면 격리 수준에 대한 이해가 자연스럽게 요구됩니다. 격리 수준은 데이터베이스의 ACID 성질 중 Isolation 에 해당하며, 여러 트랜잭션이 동시에 변경을 수행할 때 성능과 안정성, 일관성 및 재현성 간의 규형을 미세하게 조정하는 설정입니다. SQL:1992 표준에서 제공하는 4가지 격리 수준의 종류는 아래와 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;READ UNCOMMITTED&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;READ COMMITTED&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;REPEATABLE READ&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;SERIALIZABLE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MySQL 의 &lt;code class=&quot;language-text&quot;&gt;InnoDB&lt;/code&gt; 는 MVCC 와 2-Phase Locking 의 장점을 적절히 조합하여 트랜잭션 모델을 구현하였는데요. 이를 이해하기 위해선 &lt;code class=&quot;language-text&quot;&gt;InnoDB&lt;/code&gt; 의 Locking, Consistent Non-Locking Read, Locking Read 등과 같은 개념이 선행되어야 합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; test &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  id &lt;span class=&quot;token keyword&quot;&gt;BIGINT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  name &lt;span class=&quot;token keyword&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  age &lt;span class=&quot;token keyword&quot;&gt;BIGINT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;into&lt;/span&gt; test &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; age&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;quaritch&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;into&lt;/span&gt; test &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; age&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;orca&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;26&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;into&lt;/span&gt; test &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; age&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;chan&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;26&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;into&lt;/span&gt; test &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; age&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ori&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;27&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;into&lt;/span&gt; test &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; age&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;awesomeo&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;26&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 글에선 여러 예시들이 사용될 예정입니다. 모든 예시는 위 테이블을 바탕으로 진행되는 점을 참고하며 읽으시면 좀 더 원활한 이해가 가능할 것 같습니다.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;InnoDB Locking&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;MySQL 의 &lt;code class=&quot;language-text&quot;&gt;InnoDB&lt;/code&gt; 에선 다양한 Locking 기법을 사용합니다. 그 중 &lt;code class=&quot;language-text&quot;&gt;InnoDB&lt;/code&gt; 의 트랜잭션 격리 수준을 구현하기 위해 사용되는 대표적인 3가지 Lock 에 대해 우선 살펴보고자 합니다.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Record Lock&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Record Lock&lt;/code&gt; 은 인덱스 레코드에 설정되는 Lock 입니다. 테이블에 인덱스가 정의되지 않았어도 &lt;code class=&quot;language-text&quot;&gt;InnoDB&lt;/code&gt; 에선 숨겨진 Clustered Index 를 생성하기 때문에 이를 활용하여 &lt;code class=&quot;language-text&quot;&gt;Record Lock&lt;/code&gt; 을 적용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;예를 들어, &lt;code class=&quot;language-text&quot;&gt;SELECT name FROM test WHERE id = 1 FOR UPDATE;&lt;/code&gt; 라는 쿼리를 실행하면 &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt; 가 1 에 해당하는 레코드에 &lt;code class=&quot;language-text&quot;&gt;Record Lock&lt;/code&gt; 이 설정되기 때문에 다른 트랜잭션이 &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt; 가 1 인 레코드를 변경하는 작업을 수행할 수 없습니다.&lt;/p&gt;
&lt;p&gt;아래는 해당 상황을 연출하고 &lt;code class=&quot;language-text&quot;&gt;SHOW ENGINE INNODB STATUS&lt;/code&gt; 를 통해 &lt;code class=&quot;language-text&quot;&gt;Record Lock&lt;/code&gt; 이 설정된 것을 확인한 로그입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;RECORD LOCKS space id &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; page &lt;span class=&quot;token keyword&quot;&gt;no&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; n bits &lt;span class=&quot;token number&quot;&gt;72&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;transaction_test&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
trx id &lt;span class=&quot;token number&quot;&gt;3162&lt;/span&gt; lock_mode X locks rec but &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; gap waiting
Record &lt;span class=&quot;token keyword&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; heap &lt;span class=&quot;token keyword&quot;&gt;no&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; PHYSICAL RECORD: n_fields &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; compact format&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; info bits &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
 &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;: len &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; hex &lt;span class=&quot;token number&quot;&gt;8000000000000001&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;asc&lt;/span&gt;         &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;: len &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; hex &lt;span class=&quot;token number&quot;&gt;000000000&lt;/span&gt;c4b&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;asc&lt;/span&gt;      K&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;: len &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; hex &lt;span class=&quot;token number&quot;&gt;820000008&lt;/span&gt;e0110&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;asc&lt;/span&gt;        &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;: len &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; hex &lt;span class=&quot;token number&quot;&gt;7175617269746368&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;asc&lt;/span&gt; quaritch&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;: len &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; hex &lt;span class=&quot;token number&quot;&gt;8000000000000019&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;asc&lt;/span&gt;         &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;&lt;strong&gt;Gap Lock&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Gap Lock&lt;/code&gt; 은 인덱스 레코드 간의 Gap 혹은 첫 번째 또는 마지막 인덱스 레코드 전/후의 Gap 에 설정되는 Lock 입니다. Gap 자체에 Lock 이 설정되었기 때문에 삽입하려는 곳의 값 존재 여부와 관계 없이 다른 트랜잭션에서 데이터를 삽입할 수 없습니다.&lt;/p&gt;
&lt;p&gt;예를 들어, &lt;code class=&quot;language-text&quot;&gt;SELECT name FROM test WHERE id &gt;= 1 FOR UPDATE;&lt;/code&gt; 라는 쿼리를 실행하고 다른 트랜잭션이 &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt; 가 8 인 데이터를 삽입하려고 할 경우 해당 &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt; 값은 &lt;code class=&quot;language-text&quot;&gt;Gap Lock&lt;/code&gt; 이 설정된 상태이기 때문에 삽입 작업을 수행할 수 없습니다.&lt;/p&gt;
&lt;p&gt;아래는 해당 상황을 연출하고 &lt;code class=&quot;language-text&quot;&gt;Gap Lock&lt;/code&gt; 이 설정된 것을 확인한 결과입니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1586px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/835d1e9d20122dc2da7d30c264faeccf/867ee/Screen_Shot_2022-11-08_at_4.07.08_PM.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 29.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABEUlEQVR42lVRbY6FIAz0NqsIfiGgiIr68ty9/4Fm2764yf6YAJPpTFuKKx/I1wtxWXFshPNCTBvWGJFzxpZPhBBgR4ctJdzvb+FiXJCXhHxybYJzXlD4aUZjGlRlBT9FjC6gLL/Q9gMGMumtJc4L78IkYVprKFWjHyx0XUNVFWo6da1R7JRm7YiKyLRlKeA7dzTNCziwI3Mu4Pf1/kHTNGI6kqbrenkL2hYFt2/HkRIrhDlKkVIKbdf9CQ0V18RZ55D2A8Z8DDn0072XO6PYjgsDdchtz7SLmXbJhpzMRTKKnArOB7D+MWzbToJY8+iK43VTspe98bhx3Wnk8p8hg9fg/EQfeEMbI5zskDQPjDH4BTZFv+Joq30qAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Screen Shot 2022-11-08 at 4.07.08 PM.png&quot;
        title=&quot;&quot;
        src=&quot;/static/835d1e9d20122dc2da7d30c264faeccf/867ee/Screen_Shot_2022-11-08_at_4.07.08_PM.png&quot;
        srcset=&quot;/static/835d1e9d20122dc2da7d30c264faeccf/0eb09/Screen_Shot_2022-11-08_at_4.07.08_PM.png 500w,
/static/835d1e9d20122dc2da7d30c264faeccf/1263b/Screen_Shot_2022-11-08_at_4.07.08_PM.png 1000w,
/static/835d1e9d20122dc2da7d30c264faeccf/867ee/Screen_Shot_2022-11-08_at_4.07.08_PM.png 1586w&quot;
        sizes=&quot;(max-width: 1586px) 100vw, 1586px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;RECORD LOCKS space id &lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt; page &lt;span class=&quot;token keyword&quot;&gt;no&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; n bits &lt;span class=&quot;token number&quot;&gt;72&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;transaction_test&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
trx id &lt;span class=&quot;token number&quot;&gt;3194&lt;/span&gt; lock_mode X locks gap before rec &lt;span class=&quot;token keyword&quot;&gt;insert&lt;/span&gt; intention waiting
Record &lt;span class=&quot;token keyword&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; heap &lt;span class=&quot;token keyword&quot;&gt;no&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; PHYSICAL RECORD: n_fields &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; compact format&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; info bits &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
 &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;: len &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; hex &lt;span class=&quot;token number&quot;&gt;800000000000000&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;asc&lt;/span&gt;         &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;: len &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; hex &lt;span class=&quot;token number&quot;&gt;000000000&lt;/span&gt;c74&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;asc&lt;/span&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;: len &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; hex &lt;span class=&quot;token number&quot;&gt;82000000&lt;/span&gt;ab0110&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;asc&lt;/span&gt;        &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;: len &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; hex &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;f726361&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;asc&lt;/span&gt; orca&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;: len &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; hex &lt;span class=&quot;token number&quot;&gt;800000000000001&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;asc&lt;/span&gt;         &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;&lt;strong&gt;Next-Key Lock&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Next-Key Lock&lt;/code&gt; 은 &lt;code class=&quot;language-text&quot;&gt;Record Lock&lt;/code&gt; 과 해당 인덱스 레코드 앞의 Gap 에 대한 &lt;code class=&quot;language-text&quot;&gt;Gap Lock&lt;/code&gt; 이 조합된 Lock 입니다. 예시에 해당하는 &lt;code class=&quot;language-text&quot;&gt;Next-Key Lock&lt;/code&gt; 은 아래 명시된 것과 같이 적용됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;(negative infinity, 1]
(1, 2]
(2, 3]
(3, 6]
(6, 10]
(10, positive infinity)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;마지막 범위에 대한 &lt;code class=&quot;language-text&quot;&gt;Next-Key Lock&lt;/code&gt; 은 실제 인덱스의 가장 큰 값과 가장 높은 값을 갖는 허위 인덱스, &lt;code class=&quot;language-text&quot;&gt;supremum pseudo-record&lt;/code&gt; 를 통해 Lock 을 설정합니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1552px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/10f54dc94aebbb6eafbb2225d22d9337/cc6c5/Screen_Shot_2022-11-08_at_3.46.45_PM.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 24.200000000000003%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA6UlEQVR42jVQW5KEIAz0NuMLUBBdEUXwMVO19z9PL4k7H10VOp10kyKFgHicWPyK4BxiSvDbDpfrffUIe4JbPLbF4b7fCDFhzu9He2ANO6y1jCGjmJcVSnUoXy+YwcL5gKqq0LYtjB2Z6/qe6y2ezFNfdh3zQghIKRnUK87PL8ZpRlWWmH4c0vVB0zRZoNBrDakU2jxEmitriW/qGtoMsOPEqciUIPLSwvkNWhvUWUQkJSanx1Xx8u8COoUQkjlFRm3uNfWjyeCEe7qyy8jfIMctHv8JJZ+CRDREmnjczNNbG8OJaClpvvgDdhWgRioPjd0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Screen Shot 2022-11-08 at 3.46.45 PM.png&quot;
        title=&quot;&quot;
        src=&quot;/static/10f54dc94aebbb6eafbb2225d22d9337/cc6c5/Screen_Shot_2022-11-08_at_3.46.45_PM.png&quot;
        srcset=&quot;/static/10f54dc94aebbb6eafbb2225d22d9337/0eb09/Screen_Shot_2022-11-08_at_3.46.45_PM.png 500w,
/static/10f54dc94aebbb6eafbb2225d22d9337/1263b/Screen_Shot_2022-11-08_at_3.46.45_PM.png 1000w,
/static/10f54dc94aebbb6eafbb2225d22d9337/cc6c5/Screen_Shot_2022-11-08_at_3.46.45_PM.png 1552w&quot;
        sizes=&quot;(max-width: 1552px) 100vw, 1552px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;실제로 &lt;code class=&quot;language-text&quot;&gt;SELECT … FROM performance_schema.data_locks;&lt;/code&gt; 를 통해 확인해보면 &lt;code class=&quot;language-text&quot;&gt;supremum pseudo-record&lt;/code&gt; 가 &lt;code class=&quot;language-text&quot;&gt;LOCK_DATA&lt;/code&gt; 로 잡혀있는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Consistent Non-Locking Reads&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;MySQL 의 &lt;code class=&quot;language-text&quot;&gt;InnoDB&lt;/code&gt; 는 동시성 성능을 최대화하기 위해 Multiversion Concurrency Control, MVCC 라는 개념을 도입했습니다. 이는 격리 수준에 따라 상이하지만, 특정 시점의 &lt;code class=&quot;language-text&quot;&gt;Snapshot&lt;/code&gt; 정보를 바탕으로 Locking 이 필요하지 않은 Consistent Read 를 제공한다는 공통점이 있습니다. 이를 MySQL 에선 &lt;code class=&quot;language-text&quot;&gt;Consistent Non-Locking Read&lt;/code&gt; 라고 합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;REPEATALBE READ&lt;/code&gt; 격리 수준에선 최초 &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; 문이 수행된 시점을 기준으로 &lt;code class=&quot;language-text&quot;&gt;Snapshot&lt;/code&gt; 이 생성되어 다른 트랜잭션에 의해 해당 데이터가 변경되어도 &lt;code class=&quot;language-text&quot;&gt;Undo Log&lt;/code&gt; 에 저장된 내용을 기반으로 기존 데이터를 재구성합니다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;READ COMMITTED&lt;/code&gt; 격리 수준에선 각 &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; 문 마다 &lt;code class=&quot;language-text&quot;&gt;Snapshot&lt;/code&gt; 이 초기화되기 때문에 다른 트랜잭션의 커밋에 의해 해당 데이터가 변경되면 같은 읽기 작업이라도 다른 결과를 반환할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;strong&gt;Locking Reads&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;기본적인 &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; 문을 통해 조회한 데이터는 기본적으로 &lt;code class=&quot;language-text&quot;&gt;Non-Locking Read&lt;/code&gt; 입니다. 때문에 다른 트랜잭션에 의해 변경될 가능성이 높습니다. &lt;code class=&quot;language-text&quot;&gt;InnoDB&lt;/code&gt; 는 이를 해결하기 위해 두 가지 &lt;code class=&quot;language-text&quot;&gt;Locking Read&lt;/code&gt; 를 제공합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;SELECT … FOR SHARE&lt;/code&gt; 는 읽은 Row 에 &lt;code class=&quot;language-text&quot;&gt;S-Lock&lt;/code&gt; 을 설정합니다. 다른 트랜잭션이 Row 를 읽을 순 있지만, &lt;code class=&quot;language-text&quot;&gt;S-Lock&lt;/code&gt; 을 설정한 트랜잭션이 커밋되기 전까지 Row 를 수정할 수 없습니다. 아직 커밋되지 않은 다른 트랜잭션에 의해 해당 Row 가 변경될 경우, 트랜잭션이 종료될 때까지 기다린 뒤 가장 최신화된 값을 사용하여 쿼리를 수행합니다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;SELECT … FOR UPDATE&lt;/code&gt; 는 검색에서 발견한 인덱스 레코드에 대해 &lt;code class=&quot;language-text&quot;&gt;UPDATE&lt;/code&gt; 문을 실행한 것과 동일하게 Row 및 관련 인덱스에 Lock 을 설정합니다. 다른 트랜잭션은 해당 Row 를 &lt;code class=&quot;language-text&quot;&gt;UPDATE&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;SELECT ... FOR SHARE&lt;/code&gt; 를 수행하는 것이 제한됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;FOR SHARE&lt;/code&gt; 혹은 &lt;code class=&quot;language-text&quot;&gt;FOR UPDATE&lt;/code&gt; 로 설정된 모든 Lock 은 트랜잭션이 커밋 또는 롤백될 때 반환됩니다.&lt;/p&gt;
&lt;p&gt;여기까지 &lt;code class=&quot;language-text&quot;&gt;InnoDB&lt;/code&gt; 의 Locking, Consistent Non-Locking Read, Locking Read 등에 대해 알아보았습니다. 이제 &lt;code class=&quot;language-text&quot;&gt;InnoDB&lt;/code&gt; 가 4가지 트랜잭션 격리 수준을 이를 통해 어떻게 구현했는지 살펴보겠습니다.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;READ UNCOMMITTED&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;먼저 &lt;code class=&quot;language-text&quot;&gt;READ UNCOMMITTED&lt;/code&gt; 입니다. 트랜잭션의 변경 내용이 커밋이나 롤백 여부와 관계 없이 다른 트랜잭션에 보이는 가장 낮은 수준의 격리 레벨입니다. &lt;code class=&quot;language-text&quot;&gt;READ UNCOMMITTED&lt;/code&gt; 격리 수준에서 일반적인 &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; 문은 &lt;code class=&quot;language-text&quot;&gt;Non-Locking Read&lt;/code&gt; 로 수행되지만 MVCC 를 사용하지 않아 &lt;code class=&quot;language-text&quot;&gt;Consistent Read&lt;/code&gt; 를 보장하지않습니다.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Dirty Read&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;READ UNCOMMITTED&lt;/code&gt; 격리 수준에서 발생할 수 있는 읽기 부정합 현상입니다. 다른 트랜잭션에 의해 영향 받은 Row 를 읽을 수 있기 때문에 발생하는 현상입니다. 커밋 또는 롤백에 관계 없이 보여지기 때문에 부정확한 데이터를 사용할 수 있는 위험한 상황이 발생할 수 있습니다.&lt;/p&gt;
&lt;p&gt;이는 데이터베이스의 ACID 성질을 준수하지 못하기에 이를 해결하기 위해 &lt;code class=&quot;language-text&quot;&gt;InnoDB&lt;/code&gt; 에선 MVCC 를 통한 &lt;code class=&quot;language-text&quot;&gt;Consistent Read&lt;/code&gt; 를 제공합니다.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;READ COMMITTED&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;READ COMMITTED&lt;/code&gt; 격리 수준부터 MVCC 가 사용되어 &lt;code class=&quot;language-text&quot;&gt;Consistent Read&lt;/code&gt; 를 지원하지만 동일한 트랜잭션 내에서 각 &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; 문은 자체적으로 새로운 &lt;code class=&quot;language-text&quot;&gt;Snapshot&lt;/code&gt; 을 생성하기 때문에 완전한 &lt;code class=&quot;language-text&quot;&gt;Consistent Read&lt;/code&gt; 를 보장하진 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Locking Read&lt;/code&gt; &lt;code class=&quot;language-text&quot;&gt;UPDATE&lt;/code&gt; &lt;code class=&quot;language-text&quot;&gt;DELETE&lt;/code&gt; 문의 경우 &lt;code class=&quot;language-text&quot;&gt;Record Lock&lt;/code&gt; 만을 설정하고 &lt;code class=&quot;language-text&quot;&gt;Gap Lock&lt;/code&gt; 은 설정하지 않기에 다른 트랜잭션에서 자유롭게 Gap 에 접근할 수 있습니다. &lt;code class=&quot;language-text&quot;&gt;Gap Lock&lt;/code&gt; 을 설정하지 않기 때문에 범위 검색과 같은 쿼리에서 &lt;code class=&quot;language-text&quot;&gt;Phantom Read&lt;/code&gt; 현상 역시 발생할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TRANSACTION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ISOLATION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LEVEL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;READ&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COMMITTED&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;START&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TRANSACTION&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; test &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;meatsby&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;quaritch&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1586px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a8d3cdc9e255b3831d6e6eedc0a52dd5/867ee/Screen_Shot_2022-11-09_at_12.57.59_PM.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 9.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAACCAYAAABYBvyLAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAe0lEQVR42h2NURKDIBQDvU1VdIqjIvBQELD3v9FW/cxmM2lqTJz1QvZIFE8plf1m3jlyPIi54CWwrit226gpUa4fRzoJ3pOPe5crEgLGGBorgtYTbfthNhtOdrquYxhHVuuYl4VpXlDDQK8U/j62Tl7nYV+tUX3/dk/+Ayr1QaumzipLAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Screen Shot 2022-11-09 at 12.57.59 PM.png&quot;
        title=&quot;&quot;
        src=&quot;/static/a8d3cdc9e255b3831d6e6eedc0a52dd5/867ee/Screen_Shot_2022-11-09_at_12.57.59_PM.png&quot;
        srcset=&quot;/static/a8d3cdc9e255b3831d6e6eedc0a52dd5/0eb09/Screen_Shot_2022-11-09_at_12.57.59_PM.png 500w,
/static/a8d3cdc9e255b3831d6e6eedc0a52dd5/1263b/Screen_Shot_2022-11-09_at_12.57.59_PM.png 1000w,
/static/a8d3cdc9e255b3831d6e6eedc0a52dd5/867ee/Screen_Shot_2022-11-09_at_12.57.59_PM.png 1586w&quot;
        sizes=&quot;(max-width: 1586px) 100vw, 1586px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;추가적으로 위 그럼처럼 &lt;code class=&quot;language-text&quot;&gt;UPDATE&lt;/code&gt; 쿼리를 위한 풀 테이블 스캔 시 &lt;code class=&quot;language-text&quot;&gt;READ COMMITTED&lt;/code&gt; 는 스캔 중 마주치는 모든 인덱스 레코드에 &lt;code class=&quot;language-text&quot;&gt;X-Lock&lt;/code&gt; 을 설정하고 &lt;code class=&quot;language-text&quot;&gt;WHERE&lt;/code&gt; 절이 수행될 때 조건에 부합하지 않는 인덱스 레코드의 &lt;code class=&quot;language-text&quot;&gt;X-Lock&lt;/code&gt; 을 반환하는 특징을 가지고 있습니다.&lt;/p&gt;
&lt;p&gt;변경할 인덱스 레코드를 제외한 다른 인덱스 레코드의 Lock 을 반환하기 때문에 다른 트랜잭션과의 충돌로 인한 데드락 발생 확률을 현저히 줄였다고 MySQL 은 소개합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TRANSACTION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ISOLATION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LEVEL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;REPEATABLE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;READ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;START&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TRANSACTION&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; test &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;meatsby&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;quaritch&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1586px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/26bd47bd970acf624fb5f49fb22206ed/867ee/Screen_Shot_2022-11-09_at_12.57.15_PM.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 23.400000000000002%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA5UlEQVR42lWQWZLDIAxEOY4NBPCCF2yMtzhTNfc/T4/QJKnkQyWJeuqWEPu2Yz1OhDDhSAkr9dMcsYRA9Ya07uj7Ab7rEKeA634h7QczKYzMz3GB9x6+bSGGMENJBSklQkxomhZlUcC6CkoplGXJWcoSNQ3E7YAxN3ovUBPru55z03oOcf78wlrHQ9v5wEgGebiqG2it36HI0HcD7sS76t+sJsYY+xVinCJuxjCQt80uSkna0H0LPgXCvMBYC029zVkrrjkTJ9J+smA+OdJ/df3IZ7rnya/IW2ez9bxI0DH/uuKT+wPzh5+Grv/UBwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Screen Shot 2022-11-09 at 12.57.15 PM.png&quot;
        title=&quot;&quot;
        src=&quot;/static/26bd47bd970acf624fb5f49fb22206ed/867ee/Screen_Shot_2022-11-09_at_12.57.15_PM.png&quot;
        srcset=&quot;/static/26bd47bd970acf624fb5f49fb22206ed/0eb09/Screen_Shot_2022-11-09_at_12.57.15_PM.png 500w,
/static/26bd47bd970acf624fb5f49fb22206ed/1263b/Screen_Shot_2022-11-09_at_12.57.15_PM.png 1000w,
/static/26bd47bd970acf624fb5f49fb22206ed/867ee/Screen_Shot_2022-11-09_at_12.57.15_PM.png 1586w&quot;
        sizes=&quot;(max-width: 1586px) 100vw, 1586px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;반면 &lt;code class=&quot;language-text&quot;&gt;REPEATABLE READ&lt;/code&gt; 에선 스캔 중 마주치는 모든 인덱스 레코드에 &lt;code class=&quot;language-text&quot;&gt;X-Lock&lt;/code&gt; 을 설정하여 커밋 또는 롤백까지 Lock 을 반환하지 않습니다.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Non-Repeatable Read&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;READ COMMITTED&lt;/code&gt; 격리 수준까지 발생할 수 있는 읽기 부정합 현상입니다. 앞서 설명한 것 처럼 &lt;code class=&quot;language-text&quot;&gt;READ COMMITTED&lt;/code&gt; 격리 수준에서 &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; 문은 항상 &lt;code class=&quot;language-text&quot;&gt;Snapshot&lt;/code&gt; 을 초기화하기 때문에 한 트랜잭션에서 동일한 &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; 문을 수행해도 일관된 데이터를 반환하지 않는 문제가 발생할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;REPEATABLE READ&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;InnoDB&lt;/code&gt; 에서 기본값으로 제공하는 격리 레벨로, &lt;code class=&quot;language-text&quot;&gt;READ COMMITTED&lt;/code&gt; 격리 수준과는 달리 처음 생성된 &lt;code class=&quot;language-text&quot;&gt;Snapshot&lt;/code&gt; 을 통해 &lt;code class=&quot;language-text&quot;&gt;Consistent Read&lt;/code&gt; 를 보장합니다. 즉, 동일한 트랜잭션 내의 일반적인 &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; 문의 일관성을 보장합니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Locking Read&lt;/code&gt; &lt;code class=&quot;language-text&quot;&gt;UPDATE&lt;/code&gt; &lt;code class=&quot;language-text&quot;&gt;DELETE&lt;/code&gt; 문의 경우 검색 조건에 따라 사용하는 &lt;code class=&quot;language-text&quot;&gt;Lock&lt;/code&gt; 이 달라집니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;고유한 검색 조건이 있는 고유 인덱스에 대한 쿼리는 &lt;code class=&quot;language-text&quot;&gt;Record Lock&lt;/code&gt; 이 적용됩니다.&lt;/li&gt;
&lt;li&gt;범위 검색 조건과 같은 다른 검색 조건의 경우 스캔한 인덱스 범위에 &lt;code class=&quot;language-text&quot;&gt;Gap Lock&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;Next-Key Lock&lt;/code&gt; 을 설정하여 다른 트랜잭션이 해당 범위에 접근하는 것을 제한합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;아래 예시를 통해 위 두 상황을 좀 더 자세히 살펴보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Session A&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TRANSACTION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ISOLATION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LEVEL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;REPEATABLE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;READ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;START&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TRANSACTION&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; test &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1586px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3d7e993f8b993a9d167a1848b0a9460f/867ee/Screen_Shot_2022-11-09_at_2.46.55_PM.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 9%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAACCAYAAABYBvyLAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAfElEQVR42j2NQRKDIBAE/Y0oGhONqLiwCtH//6hDLjlN9fRUTZWPk/NzswflkJ2UP4TSee9JMaIp40WYnWNdV7Iq6brRMyF72YfIkS8kKm5ZqLwEHsOAqWvc5tkKN8bQ9T1L4Wl2jNMbay1t2yJaziTSNAbbdTxf49/98gs5NkHb5HEfBAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Screen Shot 2022-11-09 at 2.46.55 PM.png&quot;
        title=&quot;&quot;
        src=&quot;/static/3d7e993f8b993a9d167a1848b0a9460f/867ee/Screen_Shot_2022-11-09_at_2.46.55_PM.png&quot;
        srcset=&quot;/static/3d7e993f8b993a9d167a1848b0a9460f/0eb09/Screen_Shot_2022-11-09_at_2.46.55_PM.png 500w,
/static/3d7e993f8b993a9d167a1848b0a9460f/1263b/Screen_Shot_2022-11-09_at_2.46.55_PM.png 1000w,
/static/3d7e993f8b993a9d167a1848b0a9460f/867ee/Screen_Shot_2022-11-09_at_2.46.55_PM.png 1586w&quot;
        sizes=&quot;(max-width: 1586px) 100vw, 1586px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;현재 예시에선 &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt; 가 인덱스로 작용하고 있기 때문에 고유한 &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt; 조건을 통해 &lt;code class=&quot;language-text&quot;&gt;FOR UPDATE&lt;/code&gt; 를 수행할 경우 해당하는 인덱스 레코드에만 &lt;code class=&quot;language-text&quot;&gt;Record Lock&lt;/code&gt; 이 설정됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Session B&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; test &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; age&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;test&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;26&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 614px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0d232d19ac084ba02b5e8d6588430a5c/c42f5/Screen_Shot_2022-11-09_at_2.56.23_PM.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 53%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABlUlEQVR42o1S2U7DQAzsz4CyTVqa5tgz2dwtlzgkEBIS/P9XDPZCUSkt4sHSRjMZj8eeOWehdIXn+wxvrxmmqzt0w4BN0+J9GjDd3uLx6QVt18EYg77vUVcVlLV4pffD5QY3j08YxhHee8wsAXleoLJrVH0KW3tqoGGVhnclXNdgmC5hrCNeDiklyrJEXhTwSsI2Gk0/wlITrQ1mjh7xfA4hEogoQRRFWK1SFFLB9xsiNtSkRTtssFgsMCdukiSI4xiCai5inJ+dQRlHXHJY1TUSApi0IwbBUgZSSU5ZaIdzHX5zk6JUkMQlQR8cshAXgxcXK3IooUnQ2CoI7HN2jXclhAgGpKKRPYUfHzhM12uUUsP5NtS+m+MOBeVuoSlnyvD3yCyoKGBN7pj0n5E5mjByzSMfZpimIRPOkEUXyyVh8Z+CkgwYVx8fmZfCebAYuxUi+t7uaUELaezX2cQ/l5LRXYaQqWtG97bO8lCnlsL/MM7/fW55zyGDVdMF+92wpaOdMG6v0Y3bb2c/HdJt0pZ5Gr7DD78ZYEEH3VxkAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Screen Shot 2022-11-09 at 2.56.23 PM.png&quot;
        title=&quot;&quot;
        src=&quot;/static/0d232d19ac084ba02b5e8d6588430a5c/c42f5/Screen_Shot_2022-11-09_at_2.56.23_PM.png&quot;
        srcset=&quot;/static/0d232d19ac084ba02b5e8d6588430a5c/0eb09/Screen_Shot_2022-11-09_at_2.56.23_PM.png 500w,
/static/0d232d19ac084ba02b5e8d6588430a5c/c42f5/Screen_Shot_2022-11-09_at_2.56.23_PM.png 614w&quot;
        sizes=&quot;(max-width: 614px) 100vw, 614px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;때문에 다른 트랜잭션이 Gap 에 데이터를 삽입할 때 아무 제한 없이 정상적으로 데이터가 삽입됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Session A&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TRANSACTION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ISOLATION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LEVEL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;REPEATABLE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;READ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;START&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TRANSACTION&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; test &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; age &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;25&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1584px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/859ab579499b580a8ad941bf4e6ce877/bf788/Screen_Shot_2022-11-09_at_2.51.22_PM.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 23.400000000000002%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA4klEQVR42lWPa5KEIAyEPc6ALKCCDxRRRt2t2vufpzdkHjX7I5UEvnTSVd4ztvuBEGbklLDtd8xLxBoC1TvSljEMI3zfI84B13kh5QeTwkRMxhJXeO85qjEsUHUNKSVCTOicg7jdYGyDmt6FEJylFGg7h0gLtf5ipqHe9wO/d85zro6fXxhjeWg/vjHNkYebtoNS6h01LSzDJ/G2eSxridHafIRGVQRKUYByrfM91ZIutP8F6b8sCcsKbQz3hrJSNTvkTFyV8smCxXJMGf04sU37tPyKcnWxtR0XCVrmXy4+uT/2Ep+JwAYGtAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Screen Shot 2022-11-09 at 2.51.22 PM.png&quot;
        title=&quot;&quot;
        src=&quot;/static/859ab579499b580a8ad941bf4e6ce877/bf788/Screen_Shot_2022-11-09_at_2.51.22_PM.png&quot;
        srcset=&quot;/static/859ab579499b580a8ad941bf4e6ce877/0eb09/Screen_Shot_2022-11-09_at_2.51.22_PM.png 500w,
/static/859ab579499b580a8ad941bf4e6ce877/1263b/Screen_Shot_2022-11-09_at_2.51.22_PM.png 1000w,
/static/859ab579499b580a8ad941bf4e6ce877/bf788/Screen_Shot_2022-11-09_at_2.51.22_PM.png 1584w&quot;
        sizes=&quot;(max-width: 1584px) 100vw, 1584px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;반면 &lt;code class=&quot;language-text&quot;&gt;age&lt;/code&gt; 와 같이 현재 테이블 상태에선 고유하지만 인덱스가 아닌 조건을 통해 &lt;code class=&quot;language-text&quot;&gt;FOR UPDATE&lt;/code&gt; 를 수행할 경우 풀 테이블 스캔을 통해 해당 인덱스 레코드를 탐색하기 때문에 그 과정에서 마주친 모든 인덱스 레코드에 &lt;code class=&quot;language-text&quot;&gt;X-Lock&lt;/code&gt; 이 적용됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Session B&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; test &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; age&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;test&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;26&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1584px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/130c876ff424dc40c230d161d88f7b1c/bf788/Screen_Shot_2022-11-09_at_2.52.42_PM.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 29.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABCUlEQVR42k1Qa3KGIAzkOFXAJ+IDFEU/Z3r/A22TWDv9kSGbbHZD1BFX5Hxh3TPiEnAeWfJlDkgxIhEOxHHOYfIjPteF4/wgrglhmpB/+SPl3nuoOUT0bkBRFLBVhTlsMMZCa0N1Dzd46Q9+QtcPWEio719+DWst5V8oy1JCbfuBQCStNZqmwXHd9LZC7HonJtZWgivKz/sb47zQcIG27TDTr9j0NVbrtourMQZ1XSMSZmHDAoSNLqVnyJBFuc/DmrapicccNnpDrWmXDR/BRu7RtM+GPGCMfgQpuLYdJxzdkgXFmOr8uzdUIsKasgB2S/lDQu3fxjz4krm2nzeGcZQb8ml4q/If5wdmWL/9bkMoLwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Screen Shot 2022-11-09 at 2.52.42 PM.png&quot;
        title=&quot;&quot;
        src=&quot;/static/130c876ff424dc40c230d161d88f7b1c/bf788/Screen_Shot_2022-11-09_at_2.52.42_PM.png&quot;
        srcset=&quot;/static/130c876ff424dc40c230d161d88f7b1c/0eb09/Screen_Shot_2022-11-09_at_2.52.42_PM.png 500w,
/static/130c876ff424dc40c230d161d88f7b1c/1263b/Screen_Shot_2022-11-09_at_2.52.42_PM.png 1000w,
/static/130c876ff424dc40c230d161d88f7b1c/bf788/Screen_Shot_2022-11-09_at_2.52.42_PM.png 1584w&quot;
        sizes=&quot;(max-width: 1584px) 100vw, 1584px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이 상태에서 다른 트랜잭션이 Gap 에 &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt; 가 5 인 데이터를 삽입하려고 할 경우 &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt; 가 6 인 인덱스 레코드의 &lt;code class=&quot;language-text&quot;&gt;Gap Lock&lt;/code&gt; 으로 인해 데이터 삽입이 제한됩니다. 이를 통해 &lt;code class=&quot;language-text&quot;&gt;InnoDB&lt;/code&gt; 에선 &lt;code class=&quot;language-text&quot;&gt;REPEATABLE READ&lt;/code&gt; 수준에서도 &lt;code class=&quot;language-text&quot;&gt;Phantom Read&lt;/code&gt; 현상을 방지할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Phantom Read&lt;/strong&gt;&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;A : &lt;span class=&quot;token keyword&quot;&gt;START&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TRANSACTION&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
A : &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; test&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 최초 Snapshot 생성&lt;/span&gt;

B : &lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; test &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; age&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;test&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;26&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Lock 이 걸려있지 않기 때문에 Gap 에 데이터 삽입 가능&lt;/span&gt;

A : &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; test&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 최초 Snapshot 을 그대로 사용&lt;/span&gt;
A : &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; test &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;twenty-six&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; age &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;26&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Locking Read 를 통해 새롭게 읽은 후 UPDATE&lt;/span&gt;
A : &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; test&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Snapshot 이 초기화되어 Phantom Read 발생&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;여전히 이런 구조에도 불구하고 &lt;code class=&quot;language-text&quot;&gt;REPEATABLE READ&lt;/code&gt; 에서 &lt;code class=&quot;language-text&quot;&gt;Phantom Read&lt;/code&gt; 가 발생할 수 있는 상황이 있습니다.&lt;/p&gt;
&lt;p&gt;위 상황처럼 다른 트랜잭션이 추가한 레코드에 &lt;code class=&quot;language-text&quot;&gt;UPDATE&lt;/code&gt; 쿼리를 수행하게 될 경우, 첫 &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; 쿼리로 생성된 &lt;code class=&quot;language-text&quot;&gt;Snapshot&lt;/code&gt; 에는 존재하지 않지만 실제 디스크에는 데이터가 존재하기 때문에 해당 레코드가 영향을 받아 이후 &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; 쿼리를 수행할 때 &lt;code class=&quot;language-text&quot;&gt;Snapshot&lt;/code&gt; 이 초기화되는 시점에 해당 현상을 발견할 수 있습니다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code class=&quot;language-text&quot;&gt;Consistent Non-Locking Read&lt;/code&gt; 와 &lt;code class=&quot;language-text&quot;&gt;Locking Read&lt;/code&gt; 의 조합 때문에 발생할 수 있는 현상입니다.&lt;/p&gt;
&lt;p&gt;앞서 설명한 것 처럼 일반적인 &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; 문은 &lt;code class=&quot;language-text&quot;&gt;Non-Locking Read&lt;/code&gt; 를 기반으로 하기 때문에 &lt;code class=&quot;language-text&quot;&gt;UPDATE&lt;/code&gt; 를 위한 &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; 는 &lt;code class=&quot;language-text&quot;&gt;SELECT … FOR UPDATE&lt;/code&gt; 와 같이 &lt;code class=&quot;language-text&quot;&gt;Locking Read&lt;/code&gt; 를 사용하는 것이 더 적합합니다.&lt;/p&gt;
&lt;p&gt;따라서, 위 예시에서 연출하는 상황 자체가 부적절하다고 생각하지만, 여전히 발생할 수 있는 특정 상황이 존재한다는 사실을 알고있는 것이 좋을 것 같습니다.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;SERIALIZABLE&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;마지막 &lt;code class=&quot;language-text&quot;&gt;SERIALIZABLE&lt;/code&gt; 은 가장 높은 수준의 격리 레벨입니다. 이름처럼 직렬화된 방식처럼 작동해야합니다. &lt;code class=&quot;language-text&quot;&gt;InnoDB&lt;/code&gt; 에선 이를 구현하기 위해 일반적인 &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; 문을 모두 &lt;code class=&quot;language-text&quot;&gt;SELECT … FOR SHARE&lt;/code&gt; 문으로 변환하여 &lt;code class=&quot;language-text&quot;&gt;S-Lock&lt;/code&gt; 을 통한 &lt;code class=&quot;language-text&quot;&gt;Locking Read&lt;/code&gt; 를 수행하게끔 합니다.&lt;/p&gt;
&lt;p&gt;글 초반부 설명되어있듯이 &lt;code class=&quot;language-text&quot;&gt;S-Lock&lt;/code&gt; 은 다른 트랜잭션도 독립적으로 설정이 가능하여 &lt;code class=&quot;language-text&quot;&gt;S-Lock&lt;/code&gt; 이 설정된 레코드를 읽을 수 있지만 최초로 &lt;code class=&quot;language-text&quot;&gt;S-Lock&lt;/code&gt; 을 설정한 트랜잭션이 종료되기 전까진 변경을 가할 수 없습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TRANSACTION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ISOLATION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LEVEL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SERIALIZABLE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;START&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TRANSACTION&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; test&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1582px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c481804ab6644faae76245d685532e91/d3b5d/Screen_Shot_2022-11-09_at_1.23.06_PM.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 23.400000000000002%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA40lEQVR42lVPW5KEIAz0OKIoKj5GQFHUmZravf91epNYfuxHF3TS6U6ytCfEdMB7jxQjInEfFkTnsKVd+DRNGMYRi3e4ruvWk2YjDffDumIYBkH2ch5lUQhciOhsD5XnMG2LsixRKHW/1Ofeuh2o61o0rbUUNFHdwvaD9LPz8yMCrTX284PZBRpWaDsrtQccyMPX9xdN21EIBZDGmIZgyMOITzb7BVVVyRCbcRKLTdP8N6QtOcSFFTUZMOdX6xKa/o8u463YkAVxPzG+ZiiV0xb3yQ/4dNuPON5fCWPOpz5hD/4A8/2fmwwAW0IAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Screen Shot 2022-11-09 at 1.23.06 PM.png&quot;
        title=&quot;&quot;
        src=&quot;/static/c481804ab6644faae76245d685532e91/d3b5d/Screen_Shot_2022-11-09_at_1.23.06_PM.png&quot;
        srcset=&quot;/static/c481804ab6644faae76245d685532e91/0eb09/Screen_Shot_2022-11-09_at_1.23.06_PM.png 500w,
/static/c481804ab6644faae76245d685532e91/1263b/Screen_Shot_2022-11-09_at_1.23.06_PM.png 1000w,
/static/c481804ab6644faae76245d685532e91/d3b5d/Screen_Shot_2022-11-09_at_1.23.06_PM.png 1582w&quot;
        sizes=&quot;(max-width: 1582px) 100vw, 1582px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위 그림처럼 단순히 &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; 문을 수행해도 모든 레코드에 &lt;code class=&quot;language-text&quot;&gt;S-Lock&lt;/code&gt; 이 설정된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;마무리&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;여기까지 MySQL 의 &lt;code class=&quot;language-text&quot;&gt;InnoDB&lt;/code&gt; 가 4가지 격리 수준을 구현한 방법에 대해 알아보았습니다. 격리 수준은 동시성과 격리성의 Trade-Off 인 만큼 자세히 알고 사용하면 더 좋은 효과를 나타낼 수 있을 것 같습니다.&lt;/p&gt;
&lt;p&gt;MySQL 은 다른 DBMS 벤더들과는 달리 &lt;code class=&quot;language-text&quot;&gt;InnoDB&lt;/code&gt; 를 통해 &lt;code class=&quot;language-text&quot;&gt;REPEATABLE READ&lt;/code&gt; 에서도 충분한 격리성을 보여주고 있습니다. 하지만 여러가지 Locking 기법을 활용하여 격리 수준을 구현했기 때문에 Locking 으로 인해 발생할 수 있는 여러 문제점 역시 충분히 이해하는 것이 중요하다고 생각합니다.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html&quot;&gt;MySQL Docs - InnoDB Locking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html&quot;&gt;MySQL Docs - Consistent Nonlocking Reads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html&quot;&gt;MySQL Docs - Locking Reads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html&quot;&gt;MySQL Docs - Transaction Isolation Levels&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-next-key-locking.html&quot;&gt;MySQL Docs - Phantom Rows&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[interface vs type alias]]></title><description><![CDATA[what is the difference between interface and type alias? typescript…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-11-07-typeAlias-interface/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-11-07-typeAlias-interface/</guid><pubDate>Mon, 07 Nov 2022 13:10:00 GMT</pubDate><content:encoded>&lt;h2&gt;what is the difference between interface and type alias?&lt;/h2&gt;
&lt;p&gt;typescript를 공부해본 사람이라면 위의 키워드를 사용해서 구글링을 해본 적이 있을 것이다. &lt;br /&gt;
(최소한, 나는 그렇다…🙄)&lt;/p&gt;
&lt;p&gt;실제로, 아래와 같이 단순히 interface와 type alias의 차이가 무엇인지에 대해 물어보는 전세계 개발자들을 찾을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 770px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/18adf2b624a246bded93d0900dac2c7e/5b400/2022-11-07-interfacetype-sof.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 80.19999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAIAAACZeshMAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB7ElEQVR42oVT25ajIBDM/3/kjIriFZqrIGAyhcnuTvKyZR8OcrqBqmpuKSVjjDVWKbLWKF1hjH4uYrwWdAgBmd47H8t+3IHH43ErOe0XnLUeo8Ow+3/AisNKjMeRErY4Ir7yp7gU6c8QD2TknK/UgJqUM1LzayxIfRYgPx3H48ItpjKKelw9BXUhPP6D151rMS6xLmJdxbYJIqMUWDqtwRZXiDmfOZ1/sz9wgxB80PNkt9WyTq6r7TvqWolf3utltnwwzsXzLKBwnvf34j0Ow7Juou9507TA93fz9dV0jLVt1zTdsixEShJJSSHEt2JsZw0kQFTNLDT3PgRIAO2s8w5uYbx+XYhvitxQIzZCbBsp5TRCe0VOKQtGIIyrlnJP6UzX/KM4T6PuGU2THjlImnHQA1Ocq6Gnnkk+EBIQQ6+NDp+cezYOfGSsb7tOCAG5lVKiAg5sy7LKC6pC/z4cHXYPO5QsuD9Mvpop7C/4lI5YEfaqQo3ftt3QdtO0zbNYFimEJWnFZsFZCnR2DKHEgH3PI1bOnz4fEZzNtvp1seOowBAmz5MGZ8YkqI7cwG1M5tmgwyHab8ESkSWicZo4520Ls2F3wxhrLt8nKKkVKbqgSjnffMaLSCnHi/OT9vNtodNr+OcECkCOtyb5ASNYldzjSnrLAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;sof&quot;
        title=&quot;&quot;
        src=&quot;/static/18adf2b624a246bded93d0900dac2c7e/5b400/2022-11-07-interfacetype-sof.png&quot;
        srcset=&quot;/static/18adf2b624a246bded93d0900dac2c7e/0eb09/2022-11-07-interfacetype-sof.png 500w,
/static/18adf2b624a246bded93d0900dac2c7e/5b400/2022-11-07-interfacetype-sof.png 770w&quot;
        sizes=&quot;(max-width: 770px) 100vw, 770px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이런 질문들을 할 수밖에 없는 게, type alias는 interface가 하는 것을 거의 다 대체 할 수 있는 것처럼 보이기 때문이다. 예를 들어, 아래와 같은 Person이라는 interface가 있는 데, 이는 객체에 대한 타입을 설정해준 것이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;똑같은 타입을 type alias를 활용해서도 똑같이 만들 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;오늘은, type alias와 interface가 각각 왜 존재하는 지 살펴보는 시간을 가져보려한다.&lt;/p&gt;
&lt;h2&gt;type alias와 interface의 정의&lt;/h2&gt;
&lt;p&gt;먼저, 둘의 정의를 보면 무엇이 다른 지 알 수 있지 않을까해서 공식문서에서 정의를 찾아보았다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A type alias is exactly that - a name for any type. An interface declaration is another way to name an object type&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;즉, 둘 다 타입에 이름을 부여해주는 것이지만 type alias는 모든(any) 타입에 이름을 달아줄 수 있지만, interface는 오직 객체 타입에만 가능하다는 것이다. 이것이, type alias와 interface에 가장 명확한 차이가 아닐까한다.&lt;/p&gt;
&lt;p&gt;하지만, 이렇다고 의문점이 해결된 것은 아니다. 위의 정의만 보더라도, type alias가 object에 대한 type을 선언해주는 interface를 대체할 수 있는 것처럼 보이기 때문이다.&lt;/p&gt;
&lt;p&gt;interface의 존재의 의의에 대한 의문이 생기는 상황이다. 그래서, 많은 사람들이 혼란을 겪는 것 같다. 자세히 말하면, object에 대한 타입을 선언하는 데 type alias와 interface 중에 무엇을 사용해야하나에 대한 혼란이다.&lt;/p&gt;
&lt;h2&gt;Differences Between Type Aliases and Interfaces (typescript 공식문서)&lt;/h2&gt;
&lt;p&gt;typescript 공식문서에 type alias와 interface의 차이에 관한 내용이 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Type aliases and interfaces are very similar, and in many cases you can choose between them freely. Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;해석을 해보면, interface의 대부분의 기능들은 type에서도 가능하고, 중요한 차이점이라고 한다면 type alias는 새로운 프로퍼티에 열려있지 않다는 것이고, interface는 항상 열려있다는 점이라는 것이다.&lt;/p&gt;
&lt;h2&gt;type cannot be re-opened to add new properties vs an interface which is always extendable??&lt;/h2&gt;
&lt;p&gt;typescript 공식문서의 설명(type alias와 interface의 차이점)에서 중요한 키워드를 발견한 것 같다.&lt;/p&gt;
&lt;p&gt;type alias는 새로운 프로퍼티에 열려있지 않다는 것이고, interface는 항상 열려있다&lt;/p&gt;
&lt;p&gt;아래의 예시를 봐보자&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 996px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/33901ee3f6eecc05ab8b35de2871a072/0e821/2022-11-07-interfacetype-interfacevstype.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 35.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAIAAACHqfpvAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAoklEQVR42m1QixZDEQzz/z/rzqtosajNbLtxDu1JQsMkSinGNFfyPuAcb7TW0qTBRw8uxOC9sGyByTk/FMyMvve+OdSwXddVSulKnOw011qz4odYZthAQTPuYECHEJxz/wqYiQgD3179Mq9Iazbk3DoUsCGnRuWh9HmLWc/Cb61FvB1+fhjEzL3WVooQSc7YUX9lhho7Xl51OyAin0bnOrr+BB+sm+zL1IrXAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;interfacevstype&quot;
        title=&quot;&quot;
        src=&quot;/static/33901ee3f6eecc05ab8b35de2871a072/0e821/2022-11-07-interfacetype-interfacevstype.png&quot;
        srcset=&quot;/static/33901ee3f6eecc05ab8b35de2871a072/0eb09/2022-11-07-interfacetype-interfacevstype.png 500w,
/static/33901ee3f6eecc05ab8b35de2871a072/0e821/2022-11-07-interfacetype-interfacevstype.png 996w&quot;
        sizes=&quot;(max-width: 996px) 100vw, 996px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;왼쪽은 interface로 타입을 선언 했을 때의 코드이다.&lt;/p&gt;
&lt;p&gt;interface 키워드를 통해서 선언된 Window라는 타입을 선언한 후, 바로 아래에서 다시 다른 프로퍼티를 가진 Window 타입을 선언해주었다. 선언된 타입의 이름과 같은 이름으로 새로운 타입을 선언했지만, 에러가 나지 않고 나중에 선언된 타입의 프로퍼티를 가지고 있는 것을 볼 수 있다. 그래서, interface는 확장 가능하다라고 설명하는 것 같다. (이를, 선언 병합이라고 한다.)&lt;/p&gt;
&lt;p&gt;이에 반해, 오른쪽 코드를 보면 type alias를 통해서 Window 타입을 선언해주고, 다시 아래에서 Window 타입을 재선언 해주고 있다. interface에서는 에러가 나지 않앗지만, 여기서는 식별자 ‘Window’가 중복된다라는 에러를 보여주고 있다. 그래서, type은 새로운 프로퍼티를 추가할 수 없다고 하는 것 같다.&lt;/p&gt;
&lt;p&gt;이제, interface와 type alias가 object에 대한 타입을 선언하는 과정에서 어떻게 다른 지는 알 것 같다. 근데, 왜 굳이 확장 가능한 방법과 확장 가능하지 않은 방법으로 나눠놓았을까?라는 의문이 강하게 들었다.&lt;/p&gt;
&lt;h2&gt;왜 굳이 확장 가능한 방법과 확장 가능하지 않은 방법으로 나눠놓았을까?&lt;/h2&gt;
&lt;p&gt;typescript 개발자들이 두 가지 방법을 모두 제공했다는 것은 각각 필요하기 때문에 만들 것이라고 추론이 가능하다.&lt;/p&gt;
&lt;p&gt;확장 가능하지 않도록 타입을 선언할 수 있는(type alias) 방법이 존재하는 이유는 조금 명확한 것 같다. 타입의 속성들이 추후에 추가될 수도 있다면, 개발자들은 타입이 변할 수도 있다는 생각을 하면서 개발을 해야하기 때문이다. (마치 let보다는 const를 활용하여 상수로 선언해주는 게 좋은 이유가 비슷한 맥락이 아닐까 한다.)&lt;/p&gt;
&lt;p&gt;그렇다면, 확장 가능한 방법은 어느 상황에서 필요할까?&lt;/p&gt;
&lt;p&gt;먼저, interface를 활용하면 선언 병합(Declaration merging)이라는 것이 가능하다. 아래의 코드처럼, 기존에 선언된 타입을 확장해서 다른 속성을 추가로 선언할 수 있는 것이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; user&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Person &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;al-bur&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이는, 라이브러리를 사용하는 상황에서 추가적으로 타입의 속성들을 선언할 때 유용하다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// @emotion/react/types&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Theme&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// emotion.d.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@emotion/react&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@emotion/react&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Theme&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    colors&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; Colors&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;emotion의 types를 보면 interface 키워드를 통해 Theme이라는 타입을 제공해준다. emotion 라이브러리를 사용하는 개발자는 해당 타입에 선언 병합을 활용해 본인들이 원하는 속성들을 선언해 사용해줄 수 있다.&lt;/p&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;type-alias는 모든 타입을 선언할 때 사용될 수 있고, interface는 객체에 대한 타입을 선언할 때 사용될 수 있다. 둘 다 객체에 대한 타입을 선언하는 데 사용될 수 있는데, 확장 측면에서 사용 용도가 달라진다. 확장이 불가능한 타입을 선언하려면 type-alias를 사용하면 되고, 확장이 가능한 타입을 선언하려면 선언 병합이 가능한 interface를 사용하면 된다.&lt;/p&gt;
&lt;p&gt;이외에도 여러 차이점들이 있을 텐데, 꾸준히 탐구해보려고한다&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces&quot;&gt;https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[무중단 배포]]></title><description><![CDATA[무중단 배포를 도입하게 된 계기 프로젝트를 론칭한 이후, 사용자의 피드백을 반영하다보니 운영 환경으로의 배포가 점차 잦아졌습니다.
문제는 배포를 할 때마다 기존 서버를 내리고 새롭게 띄우는 과정에서 평균 2…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-11-01-blue-green-deployment/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-11-01-blue-green-deployment/</guid><pubDate>Tue, 01 Nov 2022 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;무중단 배포를 도입하게 된 계기&lt;/h2&gt;
&lt;p&gt;프로젝트를 론칭한 이후, 사용자의 피드백을 반영하다보니 운영 환경으로의 배포가 점차 잦아졌습니다.
문제는 배포를 할 때마다 기존 서버를 내리고 새롭게 띄우는 과정에서 평균 20초 이상의 다운타임이 발생한다는 것이었습니다.&lt;/p&gt;
&lt;p&gt;운영 서버는 사용자의 경험과 직결되는데, 배포할때마다 서버가 20초 이상 다운되는 것은 큰 문제라고 생각했습니다.
기능이 점점 많아짐에 따라 다운타임도 비례해 증가할 것이라고 판단했고, 따라서 운영 서버의 다운타임을 개선하고자 &lt;strong&gt;무중단 배포&lt;/strong&gt;를 프로젝트에 도입하기로 결정했습니다.&lt;/p&gt;
&lt;h2&gt;무중단 배포란&lt;/h2&gt;
&lt;p&gt;서비스가 운영 중일 때 새로운 버전을 배포하기 위해서는 1)기존 서비스를 종료하고 2)새로운 서비스를 시작해야 합니다.
1번과 2번 사이의 과정에서 다운타임이 필연적으로 발생하며, 다운타임 동안 사용자들은 서비스를 이용할 수 없게 됩니다.
이 다운타임을 해결해주는 방법이 바로 무중단 배포입니다.
즉, 말그대로 중단이 없는 배포를 무중단 배포라고 합니다.&lt;/p&gt;
&lt;h2&gt;무중단 배포 구현 방식&lt;/h2&gt;
&lt;p&gt;무중단 배포를 구현하는 방식에는 여러가지가 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AWS에서 Blue-Greem 무중단 배포&lt;/li&gt;
&lt;li&gt;도커를 이용한 무중단 배포&lt;/li&gt;
&lt;li&gt;L4, L7 스위치를 이용한 무중단 배포&lt;/li&gt;
&lt;li&gt;Nginx를 이용한 무중단 배포&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;대표적으로 위와 같은 방법들이 있는데 저희 프로젝트에서는 제일 마지막에 있는 Nginx를 이용한 무중단 배포 방식을 선택했습니다.&lt;/p&gt;
&lt;h3&gt;무중단 배포 구현 방식으로 Nginx 방식을 선택한 이유&lt;/h3&gt;
&lt;p&gt;Nginx를 이용하여 무중단 배포를 구현하면 저희 프로젝트에서 다음 2가지 장점이 존재했습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Nginx 서버를 이미 이용하고 있어 환경 구축을 따로 안해줘도 된다.&lt;/li&gt;
&lt;li&gt;쉽고 저렴하다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위와 같은 이유로 Nginx를 이용한 무중단 배포를 적용해 보기로 결정했습니다.&lt;/p&gt;
&lt;h2&gt;무중단 배포 전략&lt;/h2&gt;
&lt;p&gt;구현 방식을 정했으니 이제 배포 전략을 정해야 합니다.
배포 전략에는 대표적으로 3가지가 존재합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Rolling 배포&lt;/li&gt;
&lt;li&gt;Canary 배포&lt;/li&gt;
&lt;li&gt;Blue/Green 배포&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;각 배포 전략에 대한 자세한 설명은 이 글에선 생략하겠습니다.
위 세 가지 전략중 저희 프로젝트에서 선택한 전략은 &lt;strong&gt;Blue/Green 배포&lt;/strong&gt;이었습니다.&lt;/p&gt;
&lt;h3&gt;Blue/Green 배포를 선택한 이유&lt;/h3&gt;
&lt;p&gt;우선 롤링 배포와 비교해 봤을 때, 좀 더 신속하게 롤백이 가능하다고 생각했습니다.
또한 롤링 배포는 두 대 이상의 WAS 서버를 이용해 로드 벨런싱을 이용했을 때 큰 이점이 있다고 판단했는데, 저희는 추가 서버 증설에 제약이 있는 상황이었습니다.
마지막으로 롤링 배포에서는 구버전과 신버전의 공존으로 인한 호환성 문제도 발생할 수 있다는 단점도 존재했습니다.&lt;/p&gt;
&lt;p&gt;카나리 배포와 비교해 봤을 때는 카나리 방식의 최대 이점인 A/B 테스트가 현재 서비스에선 필요가 없었기 때문에 선택하지 않습니다.&lt;/p&gt;
&lt;p&gt;Blue/Green 배포 전략을 취함으로써 신속하게 롤백이 가능하다는 점과 배포하는 속도가 다른 배포법들에 비해 빠르다는 이점을 얻을 수 있었습니다.&lt;/p&gt;
&lt;h2&gt;Blue/Green 배포 적용&lt;/h2&gt;
&lt;p&gt;우선 무중단 배포 적용 전의 인프라 구조와 배포 스크립트를 살펴 봅시다.&lt;/p&gt;
&lt;h3&gt;무중단 배포 전, 인프라 구조와 배포 스크립트&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;인프라 구조&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ba69a59706c73c38650c763a313cd6e0/c7a69/before-infra-structure.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB10lEQVR42qVUS24TQRScU3IITpFVxAGQ2EbKIisWeAOKRJARCAP5ClkWwZ7EkTP22N09/e9pjFy8nsRWEiUhwiPVpj/1XlW9nsw5h4dgCdIFaB+gnMdjZ5fIHtvURDKqFzi0C6h6DmPt+oTDuMCRA2S8IvTrEFoiZCR5LA1UiLB2TcnBk29aQzCGOgR48vK/CS3JU0ZhVs1wWRaQWkCxydMI02VjzGrRk1RjSWatEOc1IhbQZY7i9Sbt3R+Mj3/gQo0skcUYkb5Asux1kp7k9qoedke7eF/s4dP4I5i4aIrdVqKJKECc7sPOLpElXw4POnj18gXyfNDMntEG2mgUrkBf9XGmzzC0Q4ggEYgwFUtw3kG4KZ0bofywBZ0fk2RfY6+1g43nz3Cw/5WIDBiFIISgUZGo6gpybjGd/sKkvU3rHIxzCC5obYpz+RM/+GcwXzbNZFVV4eT4BJ0vHQwG+bVckk5eTdwEpSdEhmJ2ivL7m6vuKfkEpRUGvItv/TbORa+xowmFUzclVZNS3vKnK7poDVt4d/EW7XGbuuA0SmEluZGdHoDSK2+zZQApkLspJ7l+7huY3wbCcrh7hntJ/M851JSgtBLSSCirmiJrvZTU6U085W/zF3VEdkMP+Nb2AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;before infra structure&quot;
        title=&quot;&quot;
        src=&quot;/static/ba69a59706c73c38650c763a313cd6e0/f97d7/before-infra-structure.png&quot;
        srcset=&quot;/static/ba69a59706c73c38650c763a313cd6e0/0eb09/before-infra-structure.png 500w,
/static/ba69a59706c73c38650c763a313cd6e0/1263b/before-infra-structure.png 1000w,
/static/ba69a59706c73c38650c763a313cd6e0/f97d7/before-infra-structure.png 2000w,
/static/ba69a59706c73c38650c763a313cd6e0/c7a69/before-infra-structure.png 2560w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;배포 스크립트&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;PID&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;pgrep &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; teatime&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$PID&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;kill&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-9&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$PID&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;nohup&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;java&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-jar&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-Dspring.profiles.active&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;dev teatime-0.0.1-SNAPSHOT.jar &lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;1&lt;/span&gt;&gt;&lt;/span&gt;stdout.txt &lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;2&lt;/span&gt;&gt;&lt;/span&gt;err.txt &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;적용 전 인프라 구조와 배포 스크립트에 대해 간략하게 알아보자면 다음 순서로 동작합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;젠킨스에서 WAS 서버로 jar 파일이 전송되고 배포 스크립트가 실행된다.&lt;/li&gt;
&lt;li&gt;배포 스크립트에 의해 현재 구동 중인 8080포트의 서버가 kill된다.&lt;/li&gt;
&lt;li&gt;새로운 jar파일로 8080포트에 새로운 서버를 띄운다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;여기서 한가지 문제점이 발생합니다❗️&lt;br&gt;
앞서 언급했듯이 기존 서버가 죽고 새로운 서버가 띄워지는 사이에 다운타임이 발생하는 것입니다.&lt;/p&gt;
&lt;p&gt;다음으로 무중단 배포 적용 후의 인프라 구조와 배포 스크립트를 살펴 봅시다.&lt;/p&gt;
&lt;h2&gt;무중단 배포 후, 인프라 구조와 배포 스크립트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;인프라 구조&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2f584bd5b14ca2a9732f1798c9d9e0e2/c7a69/after-infra-structure.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB70lEQVR42q1Ty2oUQRTtD/UX/AG/wZVLlwHXijCIEgijEBTBLARNdMLopJ/TXd1dz67qzOR4bzUjOpgRjIvT1dQ9nPs4txJrLW6Dsg7KDZDO4RDvVyS3BbQx6P01zgxQ+A3cXQWNsRAsaFlwe/cKGXLwqKVGZ/5Dy4Za5jabdQ0tJdzIVbrf4lobGK0J02mtOSBIwWEgQ0hMa4X242sY2cGSSSwWEw6OkhJvC9Rnx+jePZ0EByJ573+KOaqksQ1Se4ViKJGqFco3R9DtGoMPUczTOLbYoLIr9KNAMAKmyZFwsG5q5GURidZZeEezMzXm+Ryz7zOclqcoXAlFlTKnWleQvUK4cfhQzPGpeQ+/sTFJ4ujz5PFDPLh/D1mWoes6iEaQER3kKNGHHjJINMVn1FVJyRsIIaaubjyW1Vcsm0v4rY93Sdt2eHvyEiezZ7hK01iBkjQz26LzLbVDYqZC/uIRVF3A7Vr2A8gnXAgg1QBtGM3cIVFKYflthfMviykzGfHHlk0eW3b0ahQ5GkiwD8Dz82u8WmwgPRBYkAmBDBnHEMnsLpsirEDmMuQ2R0nGtF6AkzNHk6DnxFThZWWwqGz8HyiW7HZqh/21YbAIm7WLRy6dXGUIASOBE/D9wcXex36cK9VGx3Na7L88vX/BD5CUeNGIz+m+AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;after infra structure&quot;
        title=&quot;&quot;
        src=&quot;/static/2f584bd5b14ca2a9732f1798c9d9e0e2/f97d7/after-infra-structure.png&quot;
        srcset=&quot;/static/2f584bd5b14ca2a9732f1798c9d9e0e2/0eb09/after-infra-structure.png 500w,
/static/2f584bd5b14ca2a9732f1798c9d9e0e2/1263b/after-infra-structure.png 1000w,
/static/2f584bd5b14ca2a9732f1798c9d9e0e2/f97d7/after-infra-structure.png 2000w,
/static/2f584bd5b14ca2a9732f1798c9d9e0e2/c7a69/after-infra-structure.png 2560w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;배포 스크립트&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 현재 profile 찾기&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-name function&quot;&gt;find_current_profile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token assign-left variable&quot;&gt;RESPONSE_CODE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; /dev/null &lt;span class=&quot;token parameter variable&quot;&gt;-w&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;%{http_code}&quot;&lt;/span&gt; http://&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;WAS ip주소&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;:8080&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;${RESPONSE_CODE}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;404&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 404가 아니면&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
        &lt;span class=&quot;token assign-left variable&quot;&gt;CURRENT_PROFILE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;prod2
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
        &lt;span class=&quot;token assign-left variable&quot;&gt;CURRENT_PROFILE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;prod1
    &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;

		&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${CURRENT_PROFILE}&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 현재 port 찾기&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-name function&quot;&gt;find_current_port&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token assign-left variable&quot;&gt;CURRENT_PROFILE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;find_current_profile&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;${CURRENT_PROFILE}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; prod1 &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
      &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;8080&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;8081&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 쉬고 있는 profile 찾기: dev1이 사용중이면 dev2가 쉬고 있고, 반대면 dev1이 쉬고 있음&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-name function&quot;&gt;find_idle_profile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token assign-left variable&quot;&gt;CURRENT_PROFILE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;find_current_profile&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;${CURRENT_PROFILE}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; prod1 &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
      &lt;span class=&quot;token assign-left variable&quot;&gt;IDLE_PROFILE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;prod2
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;token assign-left variable&quot;&gt;IDLE_PROFILE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;prod1
    &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
	
    &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${IDLE_PROFILE}&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 쉬고 있는 profile의 port 찾기&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-name function&quot;&gt;find_idle_port&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token assign-left variable&quot;&gt;IDLE_PROFILE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;find_idle_profile&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;${IDLE_PROFILE}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; prod1 &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
      &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;8080&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;8081&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# idle profile로 jar 파일 실행&lt;/span&gt;

&lt;span class=&quot;token assign-left variable&quot;&gt;REPOSITORY&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;/home/ubuntu/jar

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; Build 파일 복사&quot;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; cp &lt;span class=&quot;token variable&quot;&gt;$REPOSITORY&lt;/span&gt;/*.jar &lt;span class=&quot;token variable&quot;&gt;$REPOSITORY&lt;/span&gt;/&quot;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$REPOSITORY&lt;/span&gt;/*.jar &lt;span class=&quot;token variable&quot;&gt;$REPOSITORY&lt;/span&gt;/

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; 새 어플리케이션 배포&quot;&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;JAR_NAME&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-tr&lt;/span&gt; $REPOSITORY/*.jar &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tail&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; JAR Name: &lt;span class=&quot;token variable&quot;&gt;$JAR_NAME&lt;/span&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; &lt;span class=&quot;token variable&quot;&gt;$JAR_NAME&lt;/span&gt; 에 실행권한 추가&quot;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;chmod&lt;/span&gt; +x &lt;span class=&quot;token variable&quot;&gt;$JAR_NAME&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; &lt;span class=&quot;token variable&quot;&gt;$JAR_NAME&lt;/span&gt; 실행&quot;&lt;/span&gt;

&lt;span class=&quot;token assign-left variable&quot;&gt;CURRENT_PORT&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;find_current_port&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;IDLE_PROFILE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;find_idle_profile&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;IDLE_PORT&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;find_idle_port&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; &lt;span class=&quot;token variable&quot;&gt;$JAR_NAME&lt;/span&gt; 를 profile=&lt;span class=&quot;token variable&quot;&gt;$IDLE_PROFILE&lt;/span&gt; 로 실행합니다.&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;nohup&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;java&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-jar&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-Dspring.profiles.active&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$IDLE_PROFILE&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$JAR_NAME&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;1&lt;/span&gt;&gt;&lt;/span&gt;stdout.txt &lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;2&lt;/span&gt;&gt;&lt;/span&gt;err.txt &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# health check&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; Health Check Start!&quot;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; IDLE_PORT: &lt;span class=&quot;token variable&quot;&gt;$IDLE_PORT&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; curl -s http://{WAS ip주소}:&lt;span class=&quot;token variable&quot;&gt;$IDLE_PORT&lt;/span&gt; &quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token for-or-select variable&quot;&gt;RETRY_COUNT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;token assign-left variable&quot;&gt;RESPONSE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; http://&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;WAS ip주소&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;:$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;IDLE_PORT&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token assign-left variable&quot;&gt;UP_COUNT&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;RESPONSE&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;timestamp&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;${UP_COUNT}&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-ge&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# $up_count &gt;= 1 &lt;/span&gt;
      &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; Health check 성공&quot;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;# 기존 포트에서 idle port로 switch&lt;/span&gt;

			&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; 전환할 Port: &lt;span class=&quot;token variable&quot;&gt;$IDLE_PORT&lt;/span&gt;&quot;&lt;/span&gt;
			&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; Port 전환&quot;&lt;/span&gt;
			&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;set \&lt;span class=&quot;token variable&quot;&gt;$service_url&lt;/span&gt; http://{WAS ip주소}:&lt;span class=&quot;token variable&quot;&gt;${IDLE_PORT}&lt;/span&gt;;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ssh&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; key-teatime.cer ubuntu@&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;ngnix1 ip주소&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tee&lt;/span&gt; /etc/nginx/conf.d/service-prod-url.inc
			&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;set \&lt;span class=&quot;token variable&quot;&gt;$service_url&lt;/span&gt; http://{WAS ip주소}:&lt;span class=&quot;token variable&quot;&gt;${IDLE_PORT}&lt;/span&gt;;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ssh&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; key-teatime.cer ubuntu@&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;ngnix2 ip주소&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tee&lt;/span&gt; /etc/nginx/conf.d/service-prod-url.inc

			&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; 엔진엑스 Reload&quot;&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;ssh&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; key-teatime.cer ubuntu@&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;ngnix1 ip주소&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;service&lt;/span&gt; nginx reload
			&lt;span class=&quot;token function&quot;&gt;ssh&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; key-teatime.cer ubuntu@&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;ngnix2 ip주소&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;service&lt;/span&gt; nginx reload
      &lt;span class=&quot;token builtin class-name&quot;&gt;break&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; Health check의 응답을 알 수 없거나 혹은 실행 상태가 아닙니다.&quot;&lt;/span&gt;
      &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; Health check: &lt;span class=&quot;token variable&quot;&gt;${RESPONSE}&lt;/span&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;${RETRY_COUNT}&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-eq&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; Health check 실패. &quot;&lt;/span&gt;
    &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; 엔진엑스에 연결하지 않고 배포를 종료합니다.&quot;&lt;/span&gt;
    &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;

  &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; Health check 연결 실패. 재시도...&quot;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 기존 포트 stop&lt;/span&gt;

&lt;span class=&quot;token assign-left variable&quot;&gt;CURRENT_PID&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;$(&lt;/span&gt;lsof &lt;span class=&quot;token parameter variable&quot;&gt;-ti&lt;/span&gt; tcp:&lt;span class=&quot;token variable&quot;&gt;${CURRENT_PORT}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-z&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;${CURRENT_PID}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다.&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; kill -15 &lt;span class=&quot;token variable&quot;&gt;$CURRENT_PID&lt;/span&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;kill&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-15&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;${CURRENT_PID}&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Blue/Green 무중단 배포 적용 후 변경된 인프라 구조와 배포 스크립트입니다.
배포 스크립트가 약간 길어 보기 불편할 수 있어 간단하게 정리하면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;현재 8080포트로 서버가 구동 중인 상황에서의 배포 동작 순서입니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;idle port를 찾는다. 즉 8081포트에 새롭게 배포될 서버가 띄워져야 한다는 사실을 인지한다.&lt;/li&gt;
&lt;li&gt;idle port(8081포트)로 jar 파일 실행한다.&lt;/li&gt;
&lt;li&gt;health check를 통해 8081포트에 서버가 띄워 질 때까지 current port(8080포트)의 서버를 kill 하지 않는다.&lt;/li&gt;
&lt;li&gt;8081포트에 서버가 잘 띄워졌으면 Nginx에서 가리키고 있는 WAS 포트를 current port(8080) -&gt; idle port(8081)로 스위칭해준다.&lt;/li&gt;
&lt;li&gt;current port(8080포트)를 kill한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;8081포트에 서버가 띄워진 후의 인프라 구조&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d4af184cb317fd03bb58bd12d16e841c/c7a69/8081-infra-structure.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB6klEQVR42q2T224TMRCG86y8Ai/AJW/AA0CvueGmqtpIoSVSBFIEESBFWbU0abLrPXjXp3FOP2NvQgkoEQIuZq21x5//sf/pGGNwLBrjUFuK46m8n6NzbEFzCFpjoIHcr+P/PwMzBo7M9v8AQ1TaIq1qSGNhtDoNs5zDeceBmlU2CmWew3GyXa0PK9D6IJQsYFRzBKjDgzSotUSpJKpyATF4A2t0VBEANuQ5isr0Csi6L9GM37dAy5NEFEERqLhcqjBpJkj0LZL8M9LB6xayU0fkoZfASDDMAiticCNb4CQZ4+OnIZRSEe7YJsIKXM2ucDm9xHV6w5sKEFsorCsGLr3HVAFnQ4/+/Rab7TYe1NFcwqsXz/Hs6RPM5wtIKSEygZpq0IawAt+drZCNuiiLIh4aNq6XS5RujeHtHEmhH4EFJ33o36Df6+JhPm8NLRtIJzHTM6SUY5aPkb49Qy0rCCGQZWm8O79xuFMJ6mXFwM1OIX8eFim+TWeo6xrWtSXnNsfF9ALn9+foLXrIjIglG2tiFZYrK6jA9d07fMm/PgKDInIOntr72b9yUFhSCUntWOoiQvZO8HyHfuOxKFJIKw+Bv3VJ8BUbudHBOm2oX4wdrRPKdp4F0Y+5k50SFO3jaIvujP1Hrfc38R26fHknz9DnowAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;8081 infra structure&quot;
        title=&quot;&quot;
        src=&quot;/static/d4af184cb317fd03bb58bd12d16e841c/f97d7/8081-infra-structure.png&quot;
        srcset=&quot;/static/d4af184cb317fd03bb58bd12d16e841c/0eb09/8081-infra-structure.png 500w,
/static/d4af184cb317fd03bb58bd12d16e841c/1263b/8081-infra-structure.png 1000w,
/static/d4af184cb317fd03bb58bd12d16e841c/f97d7/8081-infra-structure.png 2000w,
/static/d4af184cb317fd03bb58bd12d16e841c/c7a69/8081-infra-structure.png 2560w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위와 같은 flow로 Blue/Green 배포를 적용하여 결론적으로 평균 20초이상의 다운 타임을 0.2초로 줄였습니다.
여기서 0.2초의 다운 타임이 존재하는 이유는 proxy switching을 하고 nginx를 reload하는 과정이 0.1초인데, 저희 프로젝트에선 nginx를 두 대 이용하기 때문에 0.2초의 다운타임이 발생했습니다.&lt;/p&gt;
&lt;h2&gt;추후 고민해 볼 점&lt;/h2&gt;
&lt;p&gt;무중단 배포를 구현한 뒤 추가로 고민해보면 좋을 점들입니다.&lt;/p&gt;
&lt;h3&gt;1. Health check&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;RESPONSE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; http://&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;WAS ip주소&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;:$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;IDLE_PORT&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token assign-left variable&quot;&gt;UP_COUNT&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;RESPONSE&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;timestamp&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;${UP_COUNT}&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-ge&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# $up_count &gt;= 1 &lt;/span&gt;
      &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; Health check 성공&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
  &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
	   &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt; Health check 실패&quot;&lt;/span&gt;
       &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;현재 Health check를 위와 같이 진행하고 있습니다.
신버전이 배포된 포트의 {WAS ip주소}:{IDLE_PORT}로 요청을 보내 응답에 timestamp라는 문자열이 존재하면 새로운 서버가 띄워졌다고 판단하고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1430px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/81e55d859d7f6cecc079a217d1926005/1d7f3/white-page.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 58.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAvUlEQVR42u2QuQqEQBBE/f9PEkMTjYxMRBQDRccT1PEuqQZlwWA32Gy3oehr5nXPGKZpwrZtWJYF13XhOA48zxOxV5YlaMdx4BMzlFKg8jxH0zSo61pyguI4Rtd1mOcZWmuM44hhGDBNk3jWHsAsy0AVRSFK0xTXkL7v0batQOlZ4xnGXIDDH8AoihCGIYIggO/74pMkkctVVd0igFu9ffK2bdj3HZe/4mVZsK7rLebs8S9f9QDiy/YH/gLwBOXNrDawoowOAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;white page&quot;
        title=&quot;&quot;
        src=&quot;/static/81e55d859d7f6cecc079a217d1926005/1d7f3/white-page.png&quot;
        srcset=&quot;/static/81e55d859d7f6cecc079a217d1926005/0eb09/white-page.png 500w,
/static/81e55d859d7f6cecc079a217d1926005/1263b/white-page.png 1000w,
/static/81e55d859d7f6cecc079a217d1926005/1d7f3/white-page.png 1430w&quot;
        sizes=&quot;(max-width: 1430px) 100vw, 1430px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위와 같이 ubuntu os로 띄워진 ec2 서버의 해당 포트가 띄워졌을 때, 보이는 화면에 너무 종속적인 health check가 될 수도 있다는 생각이 들었습니다.
추후에 팀원들과 논의 끝에 더 좋은 방법으로 개선해야 될 것 같습니다.&lt;/p&gt;
&lt;h3&gt;2. WAS 서버 증설&lt;/h3&gt;
&lt;p&gt;현재 무중단 배포를 통해 운영 서버의 다운 타임 문제는 해결 됐으나, WAS ec2 자체가 다운되면 서비스가 무한정 중단될 수 있는 상황입니다.
이렇게 시스템 구성 요소 중에서 장애가 발생하면 전체 시스템이 다운되는 지점을 SPOF(single point of failure, 단일 장애 지점)이라고 합니다.
따라서 WAS 서버를 한 대 더 증설하는 것이 안전하다고 판단됩니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[테스트코드 성능 개선기]]></title><description><![CDATA[들어가며 코드를 수정하면, 검토 요청(PR…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-10-15-test-code-optimization/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-10-15-test-code-optimization/</guid><pubDate>Thu, 27 Oct 2022 13:10:00 GMT</pubDate><content:encoded>&lt;h2&gt;들어가며&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;코드를 수정하면, 검토 요청(PR) 과정의 첫 번째에 &lt;strong&gt;전체 테스트 코드&lt;/strong&gt;가 잘 돌아가는지 확인하는 과정이 주기적으로 일어나고 있어요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;혼자 개발하며 테스트 코드를 접했을 때는 비지니스 로직의 안정성을 보장해주고, 문서화 기능도 있어 테스트는 무조건 많으면 좋은 것이라고 생각했었어요.
그러나 프로젝트가 진행되면서 요구사항이 계속 추가되다보니 테스트 개수와 유지보수 포인트가 늘어났습니다. 이에따라 속도가 저하되어 개발 생산성이 낮아지는 경험을 했습니다.&lt;/p&gt;
&lt;p&gt;‘이것이 단순히 테스트 개수의 문제 일까?‘하는 궁금증이 들었고, 속도를 개선을 진행하며 느꼈던 점을 공유하고자 합니다.&lt;/p&gt;
&lt;h2&gt;마틴 파울러의 테스트 피라미드에 맞춰서 테스트 비율 조정하기&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;테스트 피라미드는 상위 계층의 테스트일수록 비용이 높기 때문에 최대한 적은 비율로 유지하는 것이 좋다는 것을 나타내요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1392px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a7f10ca9141edd95e55997bd5ceb9d7f/e9bec/2022-10-15-test-optimization1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 71.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB9ElEQVR42p1SYW/SUBTt756JSoJEP4ypccQgmw5c6SgFJLC6uYiTUgrEAQMM+EWFjrbQMRFjhBKOr+0Ya8jm5ktO3s295553T3sp3OBMxn8s3ORQ11ZnM+tqCwJkMXeemv2n4HnjUFGQ966isObFSFUdtVsI2g0TY4LPOwwSKyuIE3xhIzAM4/YTzm2dCCIktwf7Lhf4e/dR9DyEIkrXWqeuEuv3u8hxAeTZAN7TPhxsr0MkcS66AU2TrxSlnEYXVg9ru3jzKQS+FgF/vIO9Oot0lUHyKIQP1RTG4/G/J5y/2Owcg80GsB5+hMdbD+B95cZzdhUvk0/xJOgBJ26i8a3sGGJJcC6mnXbwthLCQYPGuzoNvhwEJ/gRF19gv7ZtwayZ+W7v65J1avFPgfFkipRUBZOVwApFRAhihTLixYoFTjqycqxQsjjJfIVYn+KyBnV5rUotFVsZBeGsDvpjH68PNfgSDRvxOvypFsn3rJrJMbmlpupYTWoeyL0hgpk26GyXNMikQcHmXgt3n3Fw+9Nw+RK4s8Zgg28iLCgWx+QGMx10tOGFdWtCc1l3i20iIiMqnBBLNiI5GTFJQ1RUrDsmqcSyfFE3uQzpSRe+k80wFpYNY4qfo98Y/XJCH5yhrw+gn56hp+t2PPixxDN7jan9Lf8CNQ8BSWNjym0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;테스트 피라미드&quot;
        title=&quot;&quot;
        src=&quot;/static/a7f10ca9141edd95e55997bd5ceb9d7f/e9bec/2022-10-15-test-optimization1.png&quot;
        srcset=&quot;/static/a7f10ca9141edd95e55997bd5ceb9d7f/0eb09/2022-10-15-test-optimization1.png 500w,
/static/a7f10ca9141edd95e55997bd5ceb9d7f/1263b/2022-10-15-test-optimization1.png 1000w,
/static/a7f10ca9141edd95e55997bd5ceb9d7f/e9bec/2022-10-15-test-optimization1.png 1392w&quot;
        sizes=&quot;(max-width: 1392px) 100vw, 1392px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;인수 테스트(&lt;strong&gt;UI Test&lt;/strong&gt;)&lt;/strong&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;인수 테스트는 사용자 스토리(시나리오)에 맞춰 수행하는 테스트이다. 시나리오에서 요구하는 것은 &lt;code class=&quot;language-text&quot;&gt;누가, 어떤 목적으로, 무엇을 하는가&lt;/code&gt;이다. 이는 API를 통해 드러난다.  인수 테스트는 소프트웨어 인수를 목적으로 하는 테스트이다. 소프트웨어를 인수하기 전에 명세한 요구사항(인수 조건)대로 잘 작동하는지 검증이 필요하다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/woowacourse-teams/2022-ternoko&quot;&gt;저희 프로젝트&lt;/a&gt;에서는 &lt;code class=&quot;language-text&quot;&gt;RestAssured&lt;/code&gt; 를 사용하여
실제 HTTP 요청을 보내 인수 테스트를 진행하고 있어요. 네트워크 응답 대기 시간이 소요되기 때문에 인수 테스트를 실행하는데 가장 오랜 시간이 걸렸어요.&lt;/p&gt;
&lt;p&gt;왜그럴까 살펴보니 &lt;strong&gt;인수테스트&lt;/strong&gt;에서 검증하는 대다수의 행위를 &lt;strong&gt;통합테스트&lt;/strong&gt;에서 &lt;strong&gt;중복해서 검증&lt;/strong&gt;하고 있었어요. 예외 케이스 까지도요.
저희가 인수테스트를 진행한 목적은 서비스의 요구사항이 제대로 동작하는 가를 확인하기 위함이었는데 말이죠.&lt;/p&gt;
&lt;p&gt;따라서 인수테스트에서는 API의 성공 사례들만 검증하고, &lt;strong&gt;예외 처리에 대해서는 통합테스트로 내리면&lt;/strong&gt; 테스트 시간도 줄일 수 있고, 테스트 목적에도 더 부합하지
않을까 생각되었습니다.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;통합 테스트(Integration Test)&lt;/strong&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;통합 테스트는 단위 테스트보다 더 큰 동작을 달성하기 위해 여러 모듈들을 모아 이들이 의도대로 협력하는지 확인하는 테스트이다. @SpringBootTest 를 사용하여
작성할 수 있다. 통합 테스트는 단위 테스트와 달리 개발자가 변경할 수 없는 부분(ex. 외부 라이브러리)까지 묶어 검증할 때 사용한다.
이는 DB에 접근하거나 전체 코드와 다양한 환경이 제대로 작동하는지 확인하는데 필요한 모든 작업을 수행할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;통합테스트는 @SpringBootTest 애너테이션을 사용하여 여러 객체들이 서로 잘 협력하는지, 협력하는 과정에서 side effect 가 발생하지는 않는지 확인하는 용도로 작성했어요.&lt;/p&gt;
&lt;p&gt;그런데 코드를 살펴보니, 객체 간 협력이 아닌 도메인 로직의 검증이 Service 나 Controller 에 작성되어 있었습니다. 즉 여러 객체 사이의 상호 작용이 아니라 단일 객체에 대한 테스트가 진행되고 있었습니다.&lt;/p&gt;
&lt;p&gt;해당 테스트 계층의 책임이 아니라고 판단되어 각 단위로 적절하게 책임을 내려주었습니다.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;단위 테스트(Unit Test)&lt;/strong&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;단위 테스트는 응용 프로그램에서 테스트 가능한 가장 작은 소프트웨어를 실행하여 예상대로 동작하는지 확인하는 테스트이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;단위 테스트는 테스트 피라미드 가장 아래에 위치하는 테스트 입니다. 독립적인 객체 테스트이기 때문에 테스트 비용이 가장 적습니다.
비즈니스를 구성할 때 각 도메인이 유효성 검증에 대한 책임과 행위에 대한 책임 등 적절한 책임을 가지고 있으면 객체 끼리 메세지를 주고 받기 수월해지고
객체지향적임에 한걸음 다가갈 수 있게 되는 것 같아요.&lt;/p&gt;
&lt;p&gt;따라서 단위 테스트는 꼼꼼하게 작성해주면 좋아요.&lt;/p&gt;
&lt;h3&gt;테스트 피라미드에 맞춰서 테스트 비율 조정해보며&lt;/h3&gt;
&lt;p&gt;불필요한 테스트 비용을 제거하니 같은 로직을 더 빠르게 테스트 할 수 있게 되었어요. 😀여기서 주의할 점은 &lt;strong&gt;모든 경우에 항상 피라미드형 테스트 비율을
유지할 수는 없다&lt;/strong&gt;는 점이예요. 도메인이 간단해 필요한 테스트의 양이 많지 않은 경우에 API 개수만큼 인수테스트를 작성한다고 가정하면 아래처럼 인수 테스트의
비율과 단위 테스트의 비율이 비슷할 수도 있겠죠? 따라서 현재 가지고 있는 도메인의 특성을 파악해서 테스트 비율을 조정하겠다는 유연한 사고를 한다면 더 좋겠네요.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 769px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7e7dc898cc717192a2d67937a2fe2707/e4e90/2022-10-15-test-optimization2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABPElEQVR42pWSb0vCUBTG/cjRJxCGSFm9KVKXFJJmOhOsMXBt6tz6Z+LmWq7R6MWcmy8iavhu6NMcGAoR64HLc87hnh/ncG8MK5rP56F7nofRaBQey7Iwdl3YjgPbtsPawn3fx2+KrSaz2Sx0lmUhiiLkfh+9wQDsWRFSUJNVFYosg6ZpGIax1vMnUBCEcMqF/M8vtBNJaKXyzz3TNDEcDqMDFxM0Gg10ez0c5/awHd/ADrGJ6kUBivIEiqL+B+R5Hu7YgWZ2QdJbyNRTOGJ2UWT38f7hQJYVaM9adGCT51CpXSF1QiGZK4I4zCOROQWRLSBzToOqVKHrenTgdfAAefoRWeYNBzUV8TQDguSQrusgmVeULlswXiKsvPw27mQCri1BvH1AU5CCuAOu1YEg3aFzcx/6dDpd61kCvwEPNEXMNqaF4AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;test pyramid&quot;
        title=&quot;&quot;
        src=&quot;/static/7e7dc898cc717192a2d67937a2fe2707/e4e90/2022-10-15-test-optimization2.png&quot;
        srcset=&quot;/static/7e7dc898cc717192a2d67937a2fe2707/0eb09/2022-10-15-test-optimization2.png 500w,
/static/7e7dc898cc717192a2d67937a2fe2707/e4e90/2022-10-15-test-optimization2.png 769w&quot;
        sizes=&quot;(max-width: 769px) 100vw, 769px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Application Context  를 살펴보자.&lt;/h2&gt;
&lt;p&gt;그 다음으로는 Application Context 에 대한 학습을 진행했어요. 전체 테스트를 돌리면 스프링 부트가 여러번 띄워지는 경우를 보셨을거예요.
애플리케이션을 로드할 때 Application Context에 빈을 등록하기 위해서 @Bean 으로 선언된 객체들을 찾는 등의 작업이 진행되기 때문에 시간이 오래 걸려요.
따라서 Application Context 재사용하면, 전체 테스트를 돌리는 시간도 줄겠다고 생각했어요.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1260px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2a07d9117771f32c940323d34a608e54/58c38/2022-10-15-test-optimization3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 30.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAkUlEQVR42pWQRxIDIAwD+U7oxdT//0tBzCSHJAdy0Ei2YSnKGgNrKQez85es/UvqEQQuV+Sc4JyD9x6lFPidWaeUTs3888APqVwERQjMiDEeADOdYo9A+hUwhoC0F4vIAdEJqLWeTOhrdgXkE3vvWGsdb629YQTT2Qv7YK31HZAbxhiYc56Ndn8uxQWsOb+94RMcZLiN/ydoFQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;boot&quot;
        title=&quot;&quot;
        src=&quot;/static/2a07d9117771f32c940323d34a608e54/58c38/2022-10-15-test-optimization3.png&quot;
        srcset=&quot;/static/2a07d9117771f32c940323d34a608e54/0eb09/2022-10-15-test-optimization3.png 500w,
/static/2a07d9117771f32c940323d34a608e54/1263b/2022-10-15-test-optimization3.png 1000w,
/static/2a07d9117771f32c940323d34a608e54/58c38/2022-10-15-test-optimization3.png 1260w&quot;
        sizes=&quot;(max-width: 1260px) 100vw, 1260px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;현재 아래와 같은 항목에 대해서 Application Context를 사용하고 있어요.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Acceptance Test&lt;/li&gt;
&lt;li&gt;Service Test&lt;/li&gt;
&lt;li&gt;DataJpa Test&lt;/li&gt;
&lt;li&gt;WebMvc Test&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;따라서 각 계층마다 한번씩만 Application Context가 띄워지는 것을 목표로 삼고 리팩터링을 진행해봤어요.&lt;/p&gt;
&lt;h3&gt;@DirtiesContext 걷어내기&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Use this annotation if a test has modified the context — for example, by modifying the state of a singleton bean, modifying the state of an embedded database, etc. Subsequent tests that request the same context will be supplied a new context.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;데이터 격리&lt;/strong&gt;를 위해 사용했던 &lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/annotation/DirtiesContext.html&quot;&gt;@DirtiesContext&lt;/a&gt;
때문에 모든 테스트가 종료되고 Context 를 재생성하기 때문에 시간이 오래 걸렸어요.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;데이터 격리&lt;/strong&gt;라는 목적은 Bean 재생성 없이 DB를 수동으로  초기화 해주면 되겠다는 생각으로 이어졌고, DirtiesContext 를 걷어내고,
DataBaseCleaner 를 사용해서 Context 재생성을 줄였습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DatabaseCleaner&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EntityManager&lt;/span&gt; entityManager&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; tableNames&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tableClear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createNativeQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;SET REFERENTIAL_INTEGRITY FALSE&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;executeUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; tableName &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; tableNames&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createNativeQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;TRUNCATE TABLE &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; tableName &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; RESTART IDENTITY &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;executeUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createNativeQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;SET REFERENTIAL_INTEGRITY TRUE&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;executeUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;&lt;strong&gt;Context Caching&lt;/strong&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Once the TestContext framework loads an ApplicationContext (or WebApplicationContext) for a test, that context is cached and reused for all subsequent tests that declare the same unique context configuration within the same test suite. To understand how caching works, it is important to understand what is meant by “unique” and “test suite.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;또한 SpringBootTest 를 사용하면서 &lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#testcontext-ctx-management-caching&quot;&gt;Context를 캐싱&lt;/a&gt;할
수 있다는 점을 발견하게 되었어요.&lt;/p&gt;
&lt;p&gt;‘동일한 고유 컨텍스트 구성을 선언하는 모든 후속 테스트에 재사용 된다’는 부분에서 &lt;code class=&quot;language-text&quot;&gt;고유 컨텍스트 구성&lt;/code&gt;이 무엇을 의미하는지 살펴봤어요. 그중에서 아래 부분이 눈에 띄었어요.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;contextCustomizers&lt;/code&gt;(from &lt;code class=&quot;language-text&quot;&gt;ContextCustomizerFactory&lt;/code&gt;) – this includes &lt;code class=&quot;language-text&quot;&gt;@DynamicPropertySource&lt;/code&gt; methods as well as various features from Spring Boot’s testing support such as &lt;code class=&quot;language-text&quot;&gt;@MockBean&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;@SpyBean&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;바로 테스트 격리를 위해서 사용했던 &lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.mocking-beans&quot;&gt;MockBean, SpyBean&lt;/a&gt; 이 객체마다 다르게 선언되어 있었던 점이 문제였는데요.&lt;/p&gt;
&lt;h3&gt;@MockBean , @SpyBean&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;When you are using &lt;code class=&quot;language-text&quot;&gt;@SpyBean&lt;/code&gt; to spy on a bean that is proxied by Spring, you may need to remove Spring’s proxy in some situations, for example when setting expectations using &lt;code class=&quot;language-text&quot;&gt;given&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;when&lt;/code&gt;. Use &lt;code class=&quot;language-text&quot;&gt;AopTestUtils.getTargetObject(yourProxiedSpy)&lt;/code&gt;to do so.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;해당 어노테이션을 사용하면 대리 객체가 생성되어 실제 객체가 아니라, 대리 객체가 생성되고 있었어요. 따라서 당연하게 Context 에 생성되는 기존 객체가 아닌 대리 객체가 추가 되기 때문에 고유 컨텍스트 구성이 오염되었다고 판단해서 새로운 Context 를 띄우게 되는 것이었어요!&lt;/p&gt;
&lt;h3&gt;재사용 가능한 테스트 구조 🤔&lt;/h3&gt;
&lt;p&gt;처음부터 필요한 대리 객체가 무엇인지 파악한 뒤  Mock, Spy 한번에  선언하여 Context 에 올려두고, Test Class 들이 상속 받아 사용하는 형태로 구현한다면 컨텍스트 초기화를 한번만 하는 것이 가능해질 것 같았어요.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@WebMvcTest&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebMvcTestSupporter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MockMvc&lt;/span&gt; mockMvc&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt; realClassA&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@MockBean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt; classA&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@MockBean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt; classB&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@SpyBean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt; classC&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ControllerTestA&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebMvcTestSupporter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;testA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;구조를 변경했더니 정말 Context 초기화가 한번만 이뤄졌습니다. 여기서 그럼 격리하고 싶은 객체 전부 최상위 객체에 테스트 더블을 사용하면 생각하실 수도 있는데요. 실제 객체가 필요한 상태인데 테스트 더블을 사용하는 것이 아닌지 유의하여 객체를 설계해야 유의미한 테스트가 가능하다는 점을 주의 하셨으면 좋겠습니다.&lt;/p&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;테스트 피라미드를 참고해서 계층별 테스트 비율을 조절하자&lt;/li&gt;
&lt;li&gt;불필요한 설정을 제거하자 (ControllerTest → WebMvcTest 로 내린다든가) ✍🏻&lt;/li&gt;
&lt;li&gt;데이터 격리 방식으로 DirtiesContext 걷어내자&lt;/li&gt;
&lt;li&gt;재사용 가능한 테스트 구조를 고민해보자&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;테스트 코드 성능 개선을 하고자 했던 리팩토링을 통해 더욱 객체지향적인 코드 작성이 가능해지고, 전체 테스트가 돌아가는 속도가 향상되어 팀의 개발 생산성이 한층 좋아졌습니다. 제가 접근한 개선 방향이 정답이 아닐 수도 있지만, 해당 내용을 경험하며 재미를 느끼고, 팀에 생산성을 향상 시키는데 기여 했다는 느낌에 행복했습니다. 오늘도 즐거운 학습 하시길 바랍니다.  👏&lt;/p&gt;
&lt;h3&gt;참고자료&lt;/h3&gt;
&lt;h2&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#testcontext-ctx-management-caching&quot;&gt;Spring.io Context Caching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.mocking-beans&quot;&gt;Spring.io Mocking and Spying Beans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/annotation/DirtiesContext.html&quot;&gt;Spring.io DirtiesContext&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[OpenID와 OAuth2.0]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-10-24-openID-Oauth/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-10-24-openID-Oauth/</guid><pubDate>Mon, 24 Oct 2022 13:10:00 GMT</pubDate><content:encoded>&lt;p&gt;요즘 많은 서비스가 소셜 로그인을 지원합니다.
저만 해도 새로운 서비스를 이용할 때, 네이버 또는 카카오 로그인이 있으면 거기에 손이 먼저 가는데요.
번거로운 개인 정보 입력을 생략할 수 있는 소셜 로그인은 사용자 입장에서 참 고맙습니다.
개발자 입장에서도 신규 유저들의 가입 장벽을 낮추고, 인증/인가를 대신 처리해주니 정말 좋죠.&lt;br&gt;
이번 우테코 4기 레벨3 프로젝트에도 거의 모든 팀이 소셜 로그인을 적용했는데요,
제가 속한 줍줍 팀도 &lt;code class=&quot;language-text&quot;&gt;Slack&lt;/code&gt; 소셜 로그인을 도입했습니다.&lt;br&gt;
그런데 &lt;code class=&quot;language-text&quot;&gt;Slack&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;OAuth2.0&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;OpenID Connect&lt;/code&gt;를 이용한 두 가지 로그인 방식을 제공하고 있어서, 둘 중 하나를 선택해야 했습니다.
여러분은 이 둘의 차이를 아시나요?&lt;/p&gt;
&lt;h2&gt;OAuth2.0와 OpenID Connect&lt;/h2&gt;
&lt;p&gt;이 둘을 완벽하게 분리해서 얘기하기는 힘듭니다.
&lt;code class=&quot;language-text&quot;&gt;OpenID&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;OAuth&lt;/code&gt; 프로토콜 위에서 동작하기 떄문이죠.
HTTP 통신이 소켓 통신 위에서 동작하는 것과 유사합니다.
아직 &lt;code class=&quot;language-text&quot;&gt;OAuth&lt;/code&gt;가 생소한 분들은 같은 테코블의 글 &lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-07-10-understanding-oauth/&quot;&gt;OAuth 개념 및 동작 방식 이해하기&lt;/a&gt;를 먼저 읽으시는 걸 추천합니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;OpenID&lt;/code&gt;를 관리하는 비영리 재단인 &lt;code class=&quot;language-text&quot;&gt;OpenID Foundation&lt;/code&gt;에서 직접 정의한 &lt;code class=&quot;language-text&quot;&gt;OpenID&lt;/code&gt;의 개념은 다음과 같습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;OpenID Connect 1.0은 OAuth 2.0 프로토콜 위에서 동작하는 간단한 ID 레이어입니다. 이를 통해 클라이언트는 인증 서버에서 수행한 인증을 기반으로 최종 사용자의 신원을 확인할 수 있을 뿐만
아니라, 최종 사용자에 대한 기본 프로필 정보를 상호 운용 가능하며 REST와 유사한 방식으로 얻을 수 있습니다.
OpenID Connect를 사용하면 웹 기반, 모바일, 자바스크립트 클라이언트 등을 포함한 모든 유형의 클라이언트에서 인증 세션과 최종 사용자에 대한 정보를 요청하고 받을 수 있습니다. 스펙은 확장 가능하므로
필요에 따라 참가자들에게 ID 데이터 암호화, OpenID 제공자 확인, 로그아웃 등을 이용할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;최종 사용자&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;인증 서버&lt;/code&gt;등 생소한 용어가 보이네요.
더 쉬운 이해를 위해 &lt;code class=&quot;language-text&quot;&gt;OpenID&lt;/code&gt;가 동작하는 과정을 예시로 간단히 살펴보겠습니다.&lt;/p&gt;
&lt;img width=&quot;714&quot; alt=&quot;openid&quot; src=&quot;https://user-images.githubusercontent.com/80666066/197448314-3be8b68d-debf-4db7-a0cf-25bdd611b55d.png&quot;&gt;
&lt;ol&gt;
&lt;li&gt;최종 사용자(서비스 유저)가 소셜 로그인 버튼을 클릭합니다.&lt;/li&gt;
&lt;li&gt;웹사이트나 앱 등이 클라이언트로써 OpenID 제공자에 &lt;code class=&quot;language-text&quot;&gt;ID token&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;access token&lt;/code&gt;을 요청합니다.&lt;/li&gt;
&lt;li&gt;OpenID 제공자는 승인 페이지에서 최종 사용자의 승인을 요청합니다.&lt;/li&gt;
&lt;li&gt;최종 사용자는 승인 버튼을 눌러 허가합니다.&lt;/li&gt;
&lt;li&gt;OpenID 제공자는 일회용 &lt;code class=&quot;language-text&quot;&gt;인증 코드(Authorization code)&lt;/code&gt;를 클라이언트에 발급합니다.&lt;/li&gt;
&lt;li&gt;클라이언트는 발급받은 &lt;code class=&quot;language-text&quot;&gt;인증 코드&lt;/code&gt;와 클라이언트 정보로 &lt;code class=&quot;language-text&quot;&gt;ID token&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;access token&lt;/code&gt;을 다시 요청합니다. 이 떄 클라이언트 정보는 &lt;code class=&quot;language-text&quot;&gt;Client ID&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;Client secret&lt;/code&gt;을
의미합니다.&lt;/li&gt;
&lt;li&gt;OpenID 제공자는 &lt;code class=&quot;language-text&quot;&gt;ID token&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;access token&lt;/code&gt;을 클라이언트에 발급합니다.&lt;/li&gt;
&lt;li&gt;클라이언트는 &lt;code class=&quot;language-text&quot;&gt;access token&lt;/code&gt;으로 자원 서버에 필요한 자원을 요청합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;OAuth&lt;/code&gt;절차를 이미 알고 계시다면 차이점이 없다고 생각하실 수 있습니다. 사실 절차상 보이는 차이점은 &lt;code class=&quot;language-text&quot;&gt;access token&lt;/code&gt;과 함께 하이라이트 된 &lt;code class=&quot;language-text&quot;&gt;ID token&lt;/code&gt;을 추가 요청/발급한다는 것 밖에 없습니다.
&lt;code class=&quot;language-text&quot;&gt;OAuth&lt;/code&gt; 위에서 동작한다는 의미가 이제 명확히 보이시나요?&lt;/p&gt;
&lt;h2&gt;OpenID의 차별점&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;OpenID Connect&lt;/code&gt;는 어떤 차별점을 추가해 사용자에게 어필하고 있을까요?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;스코프(Scopes):
&lt;code class=&quot;language-text&quot;&gt;OpenID Connect&lt;/code&gt;의 변경 사항 중 가장 핵심적인 부분입니다.
&lt;code class=&quot;language-text&quot;&gt;OAuth 2.0&lt;/code&gt;애서 제공자가 원하는 대로 요청 범위를 설정할 수 있었습니다.
이는 유연한 사용이 가능했지만, 상호 운용을 거의 완벽히 불가능하게 만들었습니다.
&lt;code class=&quot;language-text&quot;&gt;OpenID Connect&lt;/code&gt;는 이 요청 범위를 openid, 프로필, 이메일, 주소로 표준화했습니다.&lt;/li&gt;
&lt;li&gt;클레임:
요청 범위를 표준화 한 것에 더해, &lt;code class=&quot;language-text&quot;&gt;OpenID Connet&lt;/code&gt;는 요청 범위에 속하는 클레임의 종류도 표준화 했습니다.
사용자의 특정 정보와 인증의 세트가 이에 속합니다.
예를 들어 &lt;code class=&quot;language-text&quot;&gt;given_name&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;family_name&lt;/code&gt;이라는 이름의 클레임을 지정한다면,
외부의 다른 시스템에서 반복과 예측 가능한 형식으로 사용자 정보를 받을 수 있습니다.&lt;/li&gt;
&lt;li&gt;ID token&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;OpenID Connect&lt;/code&gt;에서 스코프는 특정한 정보 세트를 요청하는데 사용가능합니다.
이 정보는 클레임 값들로 만들 수 있습니다.
&lt;code class=&quot;language-text&quot;&gt;ID token&lt;/code&gt;의 ID 정보는 상호 운용성에 중점을 둡니다.
&lt;code class=&quot;language-text&quot;&gt;ID token&lt;/code&gt;을 통해 서드 파티 어플리케이션이 여러 웹 어플리케이션에 대해 동일한 인증 정보를 제공받을 수 있습니다.&lt;br&gt;
이 &lt;code class=&quot;language-text&quot;&gt;ID token&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;JWT&lt;/code&gt;로 생성됩니다.&lt;/li&gt;
&lt;li&gt;사용자 정보 요청 엔드포인트 통일&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;ID token&lt;/code&gt;에 이어, &lt;code class=&quot;language-text&quot;&gt;OpenID Connect&lt;/code&gt; 구현을 통해 표준화된 엔드포인트를 제공합니다.
예를 들어 엔드포인트 중 &lt;code class=&quot;language-text&quot;&gt;/userinfo&lt;/code&gt;를 통해 사용자 메타데이터 정보를 검증할 수 있습니다.
&lt;code class=&quot;language-text&quot;&gt;OpenID Connect&lt;/code&gt;는 엔드포인트 통일로 상호 운용성을 보장하기에 엔터프라이즈급 솔루션에 적합합니다.
상기한 차별점들을 종합해 볼 때, 결국 &lt;code class=&quot;language-text&quot;&gt;OpenID&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;OAuth&lt;/code&gt; 스펙을 통일해 클라이언트간의 상호 운용성을 보장하는 것을 최우선 가치로 둠을 알 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;무엇을 선택할까?&lt;/h2&gt;
&lt;p&gt;사실 일반 &lt;code class=&quot;language-text&quot;&gt;OAuth2.0&lt;/code&gt;와 확장된 &lt;code class=&quot;language-text&quot;&gt;OpenID Connect&lt;/code&gt; 중 무엇을 선택할지, 웹 개발자 입장에서 기술 본연의 특성을 놓고 논의할 일은 적다고 생각합니다.
&lt;code class=&quot;language-text&quot;&gt;OAuth&lt;/code&gt; 제공사가 각자의 이유로 제공하는 것에 따를 수 밖에 없으니까요.
저희는 서비스 이용자의 특성을 고려해 적절한 제공사를 고르는 것이 더 중요하게 보입니다.
예를 들어 개발자를 위한 서비스라면 &lt;code class=&quot;language-text&quot;&gt;GitHub&lt;/code&gt; 로그인이 우선시 될 것 같습니다.
아쉽게도 &lt;code class=&quot;language-text&quot;&gt;GitHub&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;OpenID&lt;/code&gt;를 지원하지 않아서, 일반 &lt;code class=&quot;language-text&quot;&gt;OAuth&lt;/code&gt;를 사용할 수 밖에 없겠네요.
반면, &lt;code class=&quot;language-text&quot;&gt;Google&lt;/code&gt; 로그인은 &lt;code class=&quot;language-text&quot;&gt;OpenID&lt;/code&gt;가 적용된 &lt;code class=&quot;language-text&quot;&gt;OAuth&lt;/code&gt;가 기본 사양이니 &lt;code class=&quot;language-text&quot;&gt;OpenID&lt;/code&gt;를 쓰지 않을 수가 없습니다.&lt;/p&gt;
&lt;p&gt;잘 쓰이는 다른 소셜 로그인도 살펴볼까요?
카카오 로그인은 &lt;code class=&quot;language-text&quot;&gt;OAuth&lt;/code&gt;를 기본으로, 선택에 따라 &lt;code class=&quot;language-text&quot;&gt;OpenID&lt;/code&gt;를 활성화 할 수 있습니다!
카카오 로그인을 쓴다면 이제 개발 상황과 기술 특성을 놓고 얘기할 수 있겠네요.
저희가 프로젝트에 적용했던 &lt;code class=&quot;language-text&quot;&gt;Slack&lt;/code&gt;은 기본 &lt;code class=&quot;language-text&quot;&gt;OAuth&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;OpenID&lt;/code&gt;를 둘 다 지원했지만,
기본 &lt;code class=&quot;language-text&quot;&gt;OAuth&lt;/code&gt;는 레거시 시스템으로 부르고 &lt;code class=&quot;language-text&quot;&gt;OpenID&lt;/code&gt; 사용을 권장하고 있었습니다.
하지만 &lt;code class=&quot;language-text&quot;&gt;OAuth&lt;/code&gt; 특징인 요청 스코프를 명확하게 보여준다는 것이, 사용자에게 더 신뢰를 줄 거라 생각해 &lt;code class=&quot;language-text&quot;&gt;OAuth&lt;/code&gt;를 채택했습니다.&lt;/p&gt;
&lt;p&gt;이상으로 &lt;code class=&quot;language-text&quot;&gt;OpenID&lt;/code&gt;에 대해 알아봤는데요, 앞으로 소셜 로그인 방식을 두고 선택을 해야할 때 도움이 되었으면 좋겠습니다.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://openid.net/connect/&quot;&gt;OpenID Foundation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.okta.com/openid-connect/&quot;&gt;Okta - What is OpenID Connect?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[JPA의 동적 쿼리]]></title><description><![CDATA[F12 프로젝트를 진행하면서 검색기능을 개발하는 과정에서 고민했던 부분을 소개해드리려고 합니다. F1…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-10-11-jpa-dynamic-query/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-10-11-jpa-dynamic-query/</guid><pubDate>Mon, 24 Oct 2022 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;F12 프로젝트를 진행하면서 검색기능을 개발하는 과정에서 고민했던 부분을 소개해드리려고 합니다.&lt;/p&gt;
&lt;p&gt;F12 서비스에는 회원을 검색할 수 있는 기능과 제품을 검색할 수 있는 기능이 존재합니다. 이 글에서는 좀 더 복잡한 조건이 들어가 있는 회원을 검색하는 기능을 개발했던 과정을 살펴보면서 JPA에서 동적 쿼리를 생성하고 적용하는 방법을 살펴본 다음 프로젝트에서 선택했던 방법에 대해 설명드리겠습니다.&lt;/p&gt;
&lt;h2&gt;F12의 회원 검색 기능&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1908px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/034c20f95ecb58d853d2d8abe63f366b/41afc/f12-profile-search-view.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 72.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAACZUlEQVR42n1Ty2pTURTtPznUj9BJP8BZB4KCM3GgIDgoCv6AoFMHOlA6kBYNtKEQEEIetnnYvJObm+S+z7mv5dqnN7EP2wM7J+fufdZee+2zt2KtcZulSYI/pRLmJydI8xyxUrfGb2kGXLW1M4ljBL6Ps0oF03YbSZreCLi+t+W6LlzHMbZaLBB43iZALAwCeLQgDKGjCDcRkMRiG4ZxsQsrKTMTIyOxvNjlW1p8l9iISa4xXAMYBz+mPAuryXiE2XSK6WSCGc1ZrTAZjcx5bllILoBc0tCazdDpdMiMmhFYEohDQC/aOaPAsLpJcwMYBucBDrUMuYuWC9uG4n8ghzadjqEiBaUTo5M1s5CybFnryjaAvIN+v4dXjx7i3YtnWLCsgM3xeNFjg0QOSbh70MOTz6eMHcOfTRHHBCeZpPD/A+Sq1ap4cO8OdrbvwyFQlmc4LB/h4MdPw8ILNR6/+YDtp68xmDtIsww2qzitN8j0CqAccgYcHR+j3e0io6ZLPuJpvQ6f2iqyzOgfdVvotppIlwssmk3YjAnaLajV0siyAVw/YFlyMZJS2VHFPWYzVPE0JGnOSZFzIHrTNGMUYy41RbobhRH2vn7B79ovWEM+jeHQJKC8JrtYoiLkiYbNCoZkLsllCZnrGlar2N25i9LH5/DtJexGA+5ggCUl0AX78v43vH/70nTfqtXgj8eYs/T4KqCZV89Ft/yJ2pShlhzB3hmcfh8+mUYsSValfIjS/nfkvLQ6o38whMNdpPmvhhnrS/ijOQUC5BJQc1KMloxZl6j5pCI+rVWvB0WWEfWOi2EQwL/Vc3Kbu8IYMgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;f12 profile search view&quot;
        title=&quot;&quot;
        src=&quot;/static/034c20f95ecb58d853d2d8abe63f366b/41afc/f12-profile-search-view.png&quot;
        srcset=&quot;/static/034c20f95ecb58d853d2d8abe63f366b/0eb09/f12-profile-search-view.png 500w,
/static/034c20f95ecb58d853d2d8abe63f366b/1263b/f12-profile-search-view.png 1000w,
/static/034c20f95ecb58d853d2d8abe63f366b/41afc/f12-profile-search-view.png 1908w&quot;
        sizes=&quot;(max-width: 1908px) 100vw, 1908px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;F12 프로젝트에서는 서비스를 이용하는 회원을 검색하는 기능이 있습니다. 검색할 수 있는 방법 중 가장 간단한 방법은 회원의 깃허브 아이디를 통해서 검색하는 것입니다. 검색하면 사용자가 검색한 아이디와 일치하는 회원을 찾아주는 방식입니다. 추가로 경력과 직군을 선택해서 검색 결과를 필터링할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;다양한 조건을 이용해서 회원을 검색하는 기능을 구현하는 방법이 여러 가지가 있었습니다. 구현 방법을 선택하기에 앞서 방법들의 장단점을 살펴보았었습니다. 저희 팀에서 고려했던 방식들을 하나씩 살펴보면서 최종적으로 F12 팀에서 선택했던 방법까지 살펴보도록 하겠습니다.&lt;/p&gt;
&lt;h2&gt;동적 쿼리를 사용하지 않는 경우&lt;/h2&gt;
&lt;p&gt;현재 Member의 검색 조건은 &lt;code class=&quot;language-text&quot;&gt;gitHubId&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;careerLevel&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;jobType&lt;/code&gt; 총 3가지입니다. 따라서 사용자가 검색을 하기 위해서 입력할 수 있는 조건의 경우의 수는 총 &lt;strong&gt;8가지&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 1. 검색 조건 없이 모두 조회&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 2. 깃허브 아이디 선택 조회&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findByGitHubId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; keyword&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 3. 깃허브 아이디와 경력 선택 조회&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findByGitHubIdAndCareerLevel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; keyword&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CareerLevel&lt;/span&gt; careerLevel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 4. 깃허브 아이디와 직군 선택 조회&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findByGitHubIdAndJobType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; keyword&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JobType&lt;/span&gt; jobType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 5. 깃허브 아이디, 경력, 직군 선택 조회&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findByGitHubIdAndCareerLevelAndJobType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; keyword&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CareerLevel&lt;/span&gt; careerLevel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JobType&lt;/span&gt; jobType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 6. 경력 선택 조회&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findByCareerLevel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CareerLevel&lt;/span&gt; careerLevel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 7. 경력, 직군 선택 조회&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findByCareerLevelAndJobType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CareerLevel&lt;/span&gt; careerLevel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JobType&lt;/span&gt; jobType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 8. 직군 선택 조회&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findByJobType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;JobType&lt;/span&gt; jobType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다음과 같이 모든 경우의 수에 대응할 수 있도록 Repository에서 모두 구현해보았습니다. 조건의 유무에 따라 사용되는 쿼리가 다르니 사용할 수 있는 메서드를 모두 만들어줘야 하는 상황입니다.&lt;/p&gt;
&lt;p&gt;그런데 Repository의 함수만 늘어나는 것일까요? 이를 호출하는 Service 레이어에서도 들어오는 요청에 따라서 알맞은 Repository 함수를 호출하는 분기 처리가 되어야 합니다. 극단적인 예시일 수 있으나 이를 사용하는 서비스 로직을 잠시 살펴보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 1번&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StringUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gitHubId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; careerLevel &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; jobType &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    slice &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; memberRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 2번&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StringUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gitHubId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; careerLevel &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; jobType &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    slice &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; memberRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findByGitHubId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gitHubId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
  
&lt;span class=&quot;token comment&quot;&gt;// 8번&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StringUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gitHubId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; careerLevel &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; jobType &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    slice &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; memberRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findByJobType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jobType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;현재는 8개의 경우의 수이기 때문에 아직은 분기 처리할 수 있고, 모두 정의하고 사용하는 것이 좋을 수도 있습니다. 하지만, 주석이 없다면 어떤 조건에서 실행되는 함수인지 판단하기 힘들 것이고 반복되는 코드가 증가하고 있는 모습을 볼 수 있습니다. 또한 서비스의 정책으로 회원의 검색조건이 추가된다면 기하급수적으로 생성해야 할 함수들이 늘어나기 때문에 관리하기 점점 어려울 것입니다.&lt;/p&gt;
&lt;p&gt;이러한 단점을 해결하기 위해 &lt;strong&gt;JPA의 동적 쿼리&lt;/strong&gt;를 활용하기로 했습니다. JPA에서 동적 쿼리를 생성하는 방법은 JPQL, Criteria, Specification, Querydsl 이렇게 4가지가 있습니다. 예시를 보면서 각각의 장단점을 알아보겠습니다.&lt;/p&gt;
&lt;h2&gt;JPQL&lt;/h2&gt;
&lt;p&gt;JPA에서는 JPQL을 통해 동적쿼리를 생성할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findWithSearchConditions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; gitHubId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CareerLevel&lt;/span&gt; careerLevel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                                  &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JobType&lt;/span&gt; jobType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                                  &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 조건에 따라서 where문을 다르게 가지는 JPQL 생성&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; jpql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;select m from Member m&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; whereSql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; where &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; whereCondition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StringUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gitHubId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        whereCondition&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;m.gitHubId like concat(&apos;%&apos;,:gitHubId,&apos;%&apos;)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;careerLevel &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        whereCondition&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;m.careerLevel = :careerLevel&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jobType &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        whereCondition&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;m.jobType = :jobType&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;whereCondition&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        jpql &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; whereSql&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        jpql &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot; and &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; whereCondition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    jpql &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; whereSql&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    jpql &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot; and &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; whereCondition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		
    &lt;span class=&quot;token comment&quot;&gt;// 조건에 따라서 각각의 where문에 parameter 설정&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;TypedQuery&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; query &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jpql&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StringUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gitHubId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setParameter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gitHubId&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gitHubId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;careerLevel &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setParameter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;careerLevel&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; careerLevel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jobType &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setParameter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;jobType&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jobType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; members &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFirstResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getOffset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setMaxResults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPageSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getResultList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toSlice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; members&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; slice &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; memberRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findWithSearchConditions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gitHubId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; careerLevel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jobType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;if 문이 많고 함수가 복잡해 보이지만, 동적 쿼리를 이용하지 않을 때와 비교해서 Repository의 메서드와 Service의 코드가 줄어들었습니다.&lt;/p&gt;
&lt;p&gt;하지만 여전히 단점은 존재합니다. 각 조건에 따라서 if 문을 사용한 방식으로 쿼리를 생성하고 있지만, 문자열을 통해서 하고 있다는 것입니다. 문자열을 통해서 쿼리를 만들게 되면 작성한 문자열 쿼리 중 띄어쓰기 혹은 알파벳의 오류가 있을 경우 이를 컴파일 타임에서 잡아주지 못한다는 단점이 존재합니다. 즉, 쿼리를 동적으로 생성하다 보니 개발자가 실수할 수 있는 포인트는 더 늘어났지만 쿼리를 실행하기 전까지는 오류를 찾을 수 없다는 것이죠.&lt;/p&gt;
&lt;h2&gt;Criteria&lt;/h2&gt;
&lt;p&gt;Criteria는 JPQL을 문자열이 아닌 자바 코드로 작성하도록 도와주는 빌더 클래스 API입니다. 문법적인 오류를 컴파일 단계에서 잡을 수 있으므로 JPQL보다 좀 더 안전하게 동적 쿼리를 생성할 수 있도록 도와줍니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findWithSearchConditions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; gitHubId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CareerLevel&lt;/span&gt; careerLevel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                                  &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JobType&lt;/span&gt; jobType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                                  &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;CriteriaBuilder&lt;/span&gt; builder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCriteriaBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;CriteriaQuery&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; cq &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Root&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; root &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cq&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Predicate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; predicates &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StringUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gitHubId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        predicates&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;like&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gitHubId&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;%&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; gitHubId &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;%&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;careerLevel &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        predicates&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;careerLevel&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; careerLevel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jobType &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        predicates&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;jobType&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jobType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    cq&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;predicates&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toArray&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Predicate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;TypedQuery&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; query &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cq&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; members &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFirstResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getOffset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setMaxResults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPageSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getResultList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toSlice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; members&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;JPQL을 이용해서 동적쿼리를 생성하는 것보다 훨씬 간단해졌습니다. 또한 문자열을 통해서 쿼리를 생성하지 않아 개발자가 실수할 수 있는 포인트도 줄었습니다. 이처럼 JPA에서 제공하는 Critera를 사용하게 되면 동적 쿼리를 더 간단하고 안전하게 생성할 수 있다는 장점이 있습니다.&lt;/p&gt;
&lt;p&gt;하지만 쿼리를 생성하기 위한 코드가 어려워졌습니다. 그리고 생성해야 하는 &lt;code class=&quot;language-text&quot;&gt;Predicate&lt;/code&gt;가 많아진다면 관리하기 어려워지고 직관적으로 이해하기 힘들다는 단점이 발생합니다.&lt;/p&gt;
&lt;h2&gt;JPA Specification &amp;#x26; Criteria&lt;/h2&gt;
&lt;p&gt;Spring Data JPA에서는 Criteria의 사용성을 높이기 위해서 &lt;code class=&quot;language-text&quot;&gt;org.springframework.data.jpa.domain.Specification&lt;/code&gt;으로 해결법을 제공하고 있습니다. Specification을 사용하기 위해서는 Repository에서 &lt;code class=&quot;language-text&quot;&gt;org.springframework.data.jpa.repository.JpaSpecificationExecutor&lt;/code&gt;을 상속 받아야 합니다.&lt;/p&gt;
&lt;p&gt;Specifiation을 사용한 동적 쿼리를 방식을 살펴보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MemberRepository&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JpaRepository&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JpaSpecificationExecutor&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Page&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Specification&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; spec&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; slice &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; memberRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MemberSpec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;searchWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gitHubId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; careerLevel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jobType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;기존의 Spring Data JPA와 달라진 점은 &lt;code class=&quot;language-text&quot;&gt;Specification&amp;lt;Member&gt; spec&lt;/code&gt;을 받아서 사용하고 있다는 점입니다. Specification을 이용해서 동적 쿼리를 구현할 수 있게 되는 것입니다. 동적 쿼리를 이용했기 때문에 Service 로직도 깔끔해진 모습을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;파라미터로 사용되는 Specification을 생성하는 코드도 함께 보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Specification&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;searchWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; gitHubId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CareerLevel&lt;/span&gt; careerLevel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                                   &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JobType&lt;/span&gt; jobType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; query&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; builder&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Predicate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; predicates &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StringUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gitHubId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            predicates&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;like&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gitHubId&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;%&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; gitHubId &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;%&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;careerLevel &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            predicates&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;careerLevel&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; careerLevel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jobType &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            predicates&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;jobType&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jobType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;predicates&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toArray&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Predicate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;MemberSpec&lt;/code&gt; 클래스를 만들고 정적 메서드를 활용해서 다양한 검색 조건을 조합해서 생성해주는 모습을 볼 수 있습니다. Criteria만 활용하는 것보다 코드가 훨씬 간단해진 모습을 볼 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;CriteriaBuilder&lt;/span&gt; builder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCriteriaBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;CriteriaQuery&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; cq &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Root&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; root &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cq&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같은 CriteriaQuery를 작성을 위한 준비 코드를 작성하지 않아도 되고, Spring Data JPA를 활용한 쿼리를 사용할 수 있으니 이전보다 보기 좋고 쉬운 코드가 완성되었습니다.&lt;/p&gt;
&lt;p&gt;하지만 여전히 Criteria를 통한 동적 쿼리 생성 방식은 복잡하고 장황해 보입니다. 무엇보다 코드의 형태가 SQL스럽지 않습니다.&lt;/p&gt;
&lt;h2&gt;Querydsl&lt;/h2&gt;
&lt;p&gt;Querydsl은 Criteria의 복잡함을 개선할 수 있는 JPQL 빌더 역할을 하는 오픈 소스 프로젝트입니다. 문자열이 아닌 자바 코드로 쿼리를 생성하는 것임에도 불구하고, 더 쉽고 간결하며 형태도 SQL과 비슷하게 개발할 수 있다는 것이 Querydsl의 장점입니다.&lt;/p&gt;
&lt;p&gt;Querydsl을 활용해서 동적 쿼리를 만드는 코드를 살펴보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findWithSearchConditions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; gitHubId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CareerLevel&lt;/span&gt; careerLevel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                                  &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JobType&lt;/span&gt; jobType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                                  &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;BooleanBuilder&lt;/span&gt; builder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BooleanBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StringUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gitHubId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gitHubId&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gitHubId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;careerLevel &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;careerLevel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;careerLevel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jobType &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jobType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jobType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;JPAQuery&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; jpaQuery &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; jpaQueryFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;selectFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;builder&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getOffset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPageSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orderBy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;makeOrderSpecifiers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toSlice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jpaQuery&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Criteria와 비교했을 때 불필요한 &lt;code class=&quot;language-text&quot;&gt;Predicate&lt;/code&gt;를 생성하는 과정이 없어 더 간결하고 가독성이 좋아졌습니다. 그리고 더 SQL스럽게 코드가 작성된 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;하지만 여전히 &lt;code class=&quot;language-text&quot;&gt;BooleanBuilder&lt;/code&gt;를 통해서 조건을 생성하는 코드가 있어서 코드가 복잡해 보입니다. Querydsl에서도 Specification을 사용한 것과 마찬가지로 &lt;code class=&quot;language-text&quot;&gt;BooleanBuilder&lt;/code&gt;를 생성하는 과정을 분리하면 최종적으로는 다음과 같은 코드가 완성됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findWithSearchConditions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; gitHubId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CareerLevel&lt;/span&gt; careerLevel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                               &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JobType&lt;/span&gt; jobType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                               &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;JPAQuery&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; jpaQuery &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; jpaQueryFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;selectFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;token function&quot;&gt;toContainsExpression&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gitHubId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token function&quot;&gt;eqCareerLevel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;careerLevel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token function&quot;&gt;eqJobType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jobType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getOffset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPageSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orderBy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;makeOrderSpecifiers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toSlice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jpaQuery&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BooleanExpression&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toContainsExpression&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; gitHubId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StringUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gitHubId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gitHubId&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gitHubId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BooleanExpression&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;eqCareerLevel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CareerLevel&lt;/span&gt; careerLevel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;careerLevel &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;careerLevel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;careerLevel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BooleanExpression&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;eqJobType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JobType&lt;/span&gt; jobType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jobType &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jobType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jobType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Where 문에 어떤 조건들이 들어가는지 확인할 수 있어 훨씬 가독성이 좋아졌습니다. 이처럼 Querydsl을 통해서 동적 쿼리를 생성하는 방식이 Spring Data Specification에 비해서 가독성이 더 좋다는 것을 확인해 보았습니다.&lt;/p&gt;
&lt;p&gt;하지만 Querydsl에도 단점이 없는 것은 아닙니다. Criteria보다는 쉽지만, SQL 작성을 위해서 새로운 방법에 적응하고 공부하는 시간이 필요하기 마련입니다.
또한 &lt;code class=&quot;language-text&quot;&gt;Q.Class&lt;/code&gt;에 의존하여 쿼리를 생성하는 것도 단점이라고 볼 수 있습니다. &lt;code class=&quot;language-text&quot;&gt;Q.Class&lt;/code&gt;를 생성하는 방법이 Gradle의 플러그인을 활용하는 방법에서 &lt;code class=&quot;language-text&quot;&gt;Annotation Processor&lt;/code&gt;로 바뀐 것처럼 어떤 버전과 빌드 방식을 사용하는가에 따라서 신경을 쓰고 다르게 설정해주어야 하기 때문입니다.&lt;/p&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;지금까지 검색 기능을 개발하기 위해 JPA에서 선택할 수 있는 대안들에 대해서 살펴보았습니다. 프로젝트 초반에는 Service 레이어에서 if 문을 통해서 각 쿼리를 실행하는 것이 직관적이고 코드를 작성하기 편했었습니다. 하지만 검색에 대한 조건이 점차 늘어남에 따라서 동적 쿼리의 필요성을 느꼈습니다.&lt;/p&gt;
&lt;p&gt;최종적으로 저희 팀은 Querydsl을 활용해서 동적 쿼리를 생성하는 방법을 선택했습니다. 그 이유는 SQL 문을 생성함에 있어서 컴파일 타임에 오류를 잡아줄 수 있어야 했고, 여러 개발자가 같이 프로젝트를 진행하기 위해서는 가독성이 중요하다고 느꼈기 때문입니다.&lt;/p&gt;
&lt;p&gt;프로젝트의 상황에 따라 달라지겠지만, 동적 쿼리의 적용을 고민하는 개발자가 있다면 이 글이 조금이나마 도움이 되었으면 합니다.&lt;/p&gt;
&lt;p&gt;긴 글 끝까지 읽어주셔서 감사합니다.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;김영한 지음, 자바 ORM 표준 JPA 프로그래밍&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jojoldu.tistory.com/394&quot;&gt;이동욱님 블로그 Querydsl 다이나믹 쿼리 사용하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/&quot;&gt;Spring.io blog Specification and Querydsl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/spring-data-criteria-queries&quot;&gt;Baeldung Criteria and Specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Git Merge]]></title><description><![CDATA[와  branch로 merge 관련된 여러 개념을 이해해봅시다!  : base branch  :  branch에서 나눠진 하위 branch 오늘 생각해볼 내용은 이렇습니다. merge fast-forward merge non fast-forward…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-10-23-git-merge/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-10-23-git-merge/</guid><pubDate>Sun, 23 Oct 2022 12:00:00 GMT</pubDate><content:encoded>&lt;br&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt; branch로 merge 관련된 여러 개념을 이해해봅시다!&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; : base branch&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt; : &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch에서 나눠진 하위 branch&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br&gt;
&lt;h2&gt;오늘 생각해볼 내용은 이렇습니다.&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;merge&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;fast-forward merge&lt;/li&gt;
&lt;li&gt;non fast-forward merge&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Github에서의 여러 가지의 merge&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;merge&lt;/li&gt;
&lt;li&gt;rebase and merge&lt;/li&gt;
&lt;li&gt;squash and merge&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2&gt;fast-forward merge&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/197388035-aa2e600a-7604-49b2-a573-b2748fb7a9e3.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/197388040-1627f400-f9cc-4819-a0a3-3cb415a1d3cb.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt; branch를 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch로 병합할 때, &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch에 새로운 작업 내역이 없다면 &lt;strong&gt;merge commit 없이&lt;/strong&gt; HEAD만 변경할 수 있습니다.&lt;br&gt;
이를 &lt;strong&gt;fast-forward&lt;/strong&gt;(빨리 감기) merge라고 부릅니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;현재 branch(&lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt;)의 HEAD를 대상 branch(&lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt;)의 HEAD로 옮깁니다.&lt;/li&gt;
&lt;li&gt;이는 github에서 지원하지 않습니다. - &lt;a href=&quot;https://stackoverflow.com/questions/60597400/how-to-do-a-fast-forward-merge-on-github&quot;&gt;참고&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;즉, github에서 merge를 진행할 경우 fast-forward 옵션은 활용할 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/197388150-1e96e79d-cf66-484d-9962-fca5c402881d.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;fast-forward merge의 결과물은 이와 같습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;정리하자면, 상위 branch에 변경사항이 없을 때만 fast-forward merge가 가능합니다.&lt;br&gt;
하지만 github에서 fast-forward option을 지원하지 않습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br&gt;
&lt;br&gt;
&lt;h2&gt;non fast-forward merge&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;만약 merge하려는 상위 branch에 변경사항이 있다면?&lt;/li&gt;
&lt;li&gt;이런 경우에는 양쪽의 변경을 가져오는 merge commit이 있어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/197388229-a77fa910-0006-4a96-aeed-48c561360a09.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt; branch에서 작업을 완료하고 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch로 합치려고 하는데, &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch에 다른 협업자들이 작업한 결과물이 새로 commit 되어 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch가 달라졌다면?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt; branch를 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt;에 merge하기 위해서는 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch 내의 변경 내용과 &lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt; branch 내의 &lt;strong&gt;변경 내용을 하나로 통합할 필요&lt;/strong&gt;가 있습니다.&lt;/li&gt;
&lt;li&gt;따라서 양쪽의 변경을 가져온 &lt;strong&gt;merge commit&lt;/strong&gt;을 실행하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/197388465-d9f1f41f-07f0-4b3e-818c-7c1581af7eff.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;이를 &lt;strong&gt;non fast-forward merge&lt;/strong&gt;라고 합니다.&lt;br&gt;
non fast-forward merge를 하게 되면 branch가 그대로 남기 때문에, 해당 branch로 작업한 내용들을 확인하기에 유용합니다.&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;h2&gt;Github에서의 여러가지 merge&lt;/h2&gt;
&lt;p&gt;저는 보통 github 페이지에서 쉽고 간단하게 버튼 하나로 merge를 진행합니다.&lt;br&gt;
github 페이지에서 할 수 있는 merge는 세 가지가 존재합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/197388617-3387ae4b-79be-4dd2-99da-d9cad63b16bd.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;create a merge commit&lt;/li&gt;
&lt;li&gt;squash and merge&lt;/li&gt;
&lt;li&gt;rebase and merge&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위와 같이 존재하는데, merge 버튼 옆에 있는 토글을 열면 merge option 들을 확인할 수 있습니다.&lt;br&gt;
기본으로는 create a merge commit으로 설정되어 있습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;1️⃣ create a merge commit&lt;/h3&gt;
&lt;p&gt;앞에서 설명한 non fast-forward 입니다.&lt;br&gt;
base branch로 merge를 진행할 때, merge commit을 통해 코드를 합칩니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/197388669-2ffbb51b-b35b-4fcd-a96a-69296727f510.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;merge 버튼을 누르면, merge commit을 작성하는 칸이 나옵니다.&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;2️⃣ squash and merge&lt;/h3&gt;
&lt;p&gt;다음은 squash and merge인데,&lt;br&gt;
squash의 사전적 의미는 과일 같은 것을 쭈욱 으깨는 것을 말합니다.&lt;br&gt;
과일 대신.. commit을 생각해봅시다!&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch로 merge하는 상황을 가정했을 때,&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/197388748-32e4d59c-2ec3-466a-9fa6-43be00fdcad4.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;위와 같이 merge되는 것을 squash merge라고 합니다.
&lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt; branch의 &lt;code class=&quot;language-text&quot;&gt;x&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;y&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;z&lt;/code&gt; 커밋을 하나로 쭈욱 합쳐서 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch로 병합될 수 있도록 해줍니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;x&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;y&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;z&lt;/code&gt;를 합쳐서 새로운 commit을 만듭니다.&lt;/li&gt;
&lt;li&gt;이 새로운 commit은 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch의 HEAD를 parent로 갖습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;blockquote&gt;
&lt;p&gt;❓ &lt;strong&gt;잠깐 ! squash and merge는 어떨 때 유용할까요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;저는 진행하고 있는 프로젝트에서 대댓글 기능 구현 역할을 맡았습니다.&lt;br&gt;
저는 프론트로서 대댓글 기능 구현을 하기 위해, 여러 commit을 진행할 것 입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt; UI 만들어야지 ..
 mocking 해야지 ..
 함수 연결해야 하지 ..
 어 스타일.. 맘에 안 드네 고쳐서 commit 해야지 ..&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같이, 한 기능당 여러 commit이 발생할 수 있는데,&lt;br&gt;
이 commit들이 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch commit 내역에 그대로 남는다면, 기능 별로 commit log를 확인하는 데에 있어 불편함을 겪게 되고, 미관상으로도 좋지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;sqaush and merge&lt;/strong&gt; 기능을 활용하면, 제가 &lt;code class=&quot;language-text&quot;&gt;feature/대댓글&lt;/code&gt; branch에서 작업한 내용들이 하나의 커밋으로 합쳐지게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/197388928-8cc65958-1fd3-4111-be9d-f9592c32345d.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;예를 들면, 기능(feature) 하나로 commit이 쭈욱 합쳐집니다. (&lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch commit log가 아주 예뻐지겠죠 ?!)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/197389013-521cad31-e7c9-438d-9db6-a00eef56424b.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;제가 &lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt; branch에서 진행한 commit들은 합쳐진 commit 하나 속의 details로 남겨집니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br&gt;
&lt;br&gt;
&lt;h3&gt;3️⃣ rebase and merge&lt;/h3&gt;
&lt;p&gt;rebase and merge를 알기 전에, 우선 rebase가 뭔지 알아야합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;rebase&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;두 개의 공통 base를 가진 branch에서 &lt;code class=&quot;language-text&quot;&gt;(A, B)&lt;/code&gt;,&lt;br&gt;
한 branch&lt;code class=&quot;language-text&quot;&gt;(A)&lt;/code&gt;의 base를 다른 branch&lt;code class=&quot;language-text&quot;&gt;(B)&lt;/code&gt;의 최신 commit으로 base를 옮기는 작업을 말합니다.&lt;/p&gt;
&lt;p&gt;이름에서 알 수 있듯이 re(다시) base를 설정하는 것 입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br&gt;
&lt;p&gt;예시로 이해해봅시다!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/197389467-96f98331-9096-4c0d-935b-001a3026f4bf.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;현재 저는 &lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt; branch에서 작업을 진행하고 있습니다.&lt;br&gt;
(&lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt; branch는 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch로부터 파생된 branch이며 &lt;code class=&quot;language-text&quot;&gt;b&lt;/code&gt; commit까지 쌓인 이후, 저는 &lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt; branch를 생성했습니다.)&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt; branch에서 &lt;code class=&quot;language-text&quot;&gt;x&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;y&lt;/code&gt; commit을 하며 열심히 작업하고 있는 도중,&lt;br&gt;
저의 동료 개발자가 &lt;code class=&quot;language-text&quot;&gt;c&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;d&lt;/code&gt;라는 작업을 완료해서 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt;에 merge를 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch는 그 전과 작업 내용(&lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt; branch를 생성하던 시기)이 달라졌기 때문에,&lt;br&gt;
제가 작업을 완료하고 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch로 merge할 시기에 충돌이 날 가능성이 있습니다. 이때 &lt;strong&gt;rebase를 활용&lt;/strong&gt;할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/197389651-780d2a76-a7b2-4b67-a13f-c5b48a559fb2.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;rebase를 활용하면 제 branch(&lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt;)의 base commit은 &lt;code class=&quot;language-text&quot;&gt;b&lt;/code&gt;지만.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt;의 최신 작업 내용인 &lt;code class=&quot;language-text&quot;&gt;d&lt;/code&gt;로 base commit을 변경할 수 있습니다.&lt;br&gt;
즉, 전과 달리 위와 같은 그림이 됩니다.&lt;/p&gt;
&lt;br&gt;
&lt;blockquote&gt;
&lt;p&gt;❓ &lt;strong&gt;base를 바꾼다는 것은 알겠는데, rebase를 활용하면 얻는 이점은 무엇이 있을까요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch의 최신 변경사항을 즉각 반영할 수 있습니다. (즉, 최신 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt;의 최신 commit을 반영하면서 작업을 해야할 때 좋습니다.)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;예를 들어, 저는 대댓글 기능을 작업하고 있고, 동료 개발자는 댓글 부분의 오류를 수정하고 있다고 가정해봅시다.&lt;br&gt;
대댓글을 작업하는 동시에, 동료 개발자가 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch로 merge 완료된 작업을 rebase를 통해 버그가 수정된 작업본을 바로 이어받아 진행할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. merge commit 이력을 남기지 않아, 깔끔한 commit history를 유지할 수 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;만약 동료 개발자가 한 작업(&lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt;에 merge됨)이 저의 코드와 충돌이 발생한다면 어떻게 할까요?&lt;br&gt;
rebase를 활용하지 않는다면, 충돌을 해결한 merge commit을 push 해야 해서 commit history가 더럽혀질 수 있습니다.&lt;br&gt;
하지만 rebase를 활용하면, 제 commit들이 마치 충돌이 발생하지 않았던 것처럼 동료 개발자 commit 뒤에 위치하게 되므로 깔끔하게 commit history를 유지할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br&gt;
&lt;blockquote&gt;
&lt;p&gt;❓ &lt;strong&gt;어.. 그런데 rebase를 했더니 계속 같은 충돌을 해결하래요 ㅠㅠ 왜이래요 이거!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;제가 처음 rebase를 알았을 때, 계속 충돌이 나서 저는 ‘내가 뭘 잘못 만졌구나..’라고만 생각했습니다.&lt;br&gt;
하지만 이는 rebase의 과정을 생각해보면 이해할 수 있습니다.&lt;br&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/197389944-f87b7727-623f-454f-949b-ff16568d308b.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;위 그림에서 다시 생각해봅시다.&lt;/p&gt;
&lt;p&gt;만약 동료 개발자가 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt;로 merge한 PR의 작업(&lt;code class=&quot;language-text&quot;&gt;c&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;d&lt;/code&gt;)들이 나의 코드와 충돌이 나는 작업이었다면?&lt;br&gt;
rebase를 딱 시작했을 때, 제 commit은 &lt;code class=&quot;language-text&quot;&gt;x&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;y&lt;/code&gt;가 존재합니다.&lt;br&gt;
제 commit인 &lt;code class=&quot;language-text&quot;&gt;x&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;y&lt;/code&gt;는 모두 &lt;code class=&quot;language-text&quot;&gt;b&lt;/code&gt;를 기준으로 작업했기 때문에 &lt;code class=&quot;language-text&quot;&gt;x&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;y&lt;/code&gt; 둘 다 &lt;code class=&quot;language-text&quot;&gt;d&lt;/code&gt;의 뒤로 보내주기 위해서는 &lt;code class=&quot;language-text&quot;&gt;x&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;y&lt;/code&gt; commit 둘 다 충돌을 해결해주어야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/197390033-abea7d6b-e6a8-4293-886e-60c8a4de843f.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;이런 이유로 계속 충돌을 해결하라는 메시지가 뜨는 것 입니다.&lt;br&gt;
이는 rebase의 단점으로 꼽히는데요. commit 마다 충돌 처리를 해주어야 하기 때문입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br&gt;
&lt;p&gt;&lt;strong&gt;다시 rebase and merge로 돌아와봅시다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/197390192-ad08287a-cc6f-4ae4-8032-b36a375d44ec.png&quot; alt=&quot;image&quot;&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/197390214-4e20fecf-2533-4d43-87df-906b65c9ac82.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;rebase and merge를 진행하면 위와 같은 그림이 그려질 것 입니다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;x&lt;/code&gt; commit의 base를 &lt;code class=&quot;language-text&quot;&gt;b&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;d&lt;/code&gt;로 변경하며 merge가 됩니다.&lt;br&gt;
rebase and merge를 활용하면 merge commit 없이 merge를 진행할 수 있고,&lt;br&gt;
squash merge와는 다르게 하나의 커밋으로 합쳐지지 않고 PR의 commit 하나하나 살아서 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch로 merge 됩니다.&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;h2&gt;다양한 merge, 어떻게 활용하면 좋을까요?&lt;/h2&gt;
&lt;p&gt;저희 팀에서는 &lt;strong&gt;rebase를 한 후, squash and merge하는 전략&lt;/strong&gt;을 채택하고 있습니다.&lt;br&gt;
만약 slack에서 동료 개발자 혹은 저의 PR이 merge되었다는 알림이 오면 rebase를 진행합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;git fetch origin dev
git rebase origin/dev&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같이 입력하고 rebase를 진행하며 충돌이 있다면, 충돌을 하나하나 해결해줍니다.&lt;br&gt;
(만약 IDE에 현재 작업 내용이 있다면 &lt;code class=&quot;language-text&quot;&gt;stash&lt;/code&gt;를 활용합니다.)&lt;/p&gt;
&lt;p&gt;rebase를 활용하면, 최신 작업 내용을 가져올 수 있고, 충돌 해결을 위한 commit이 없어져서 활용하고 있습니다.&lt;br&gt;
이후, squash and merge를 진행하는데, &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt;에 기능 별로 commit이 쌓이기 때문에 commit log가 깔끔해지는 이유로 사용하고 있습니다.&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;h3&gt;이 방식은 주의해야할 점이 있습니다.&lt;/h3&gt;
&lt;p&gt;하지만 이 방식에는 주의해야할 점이 있습니다.&lt;br&gt;
만약 &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt;을 서비스용 branch로 사용하고,&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt;를 개발용 branch로 사용한다고 하면&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch를 &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt; branch로 merge할 때 squash merge를 사용하면 안됩니다.&lt;/p&gt;
&lt;img width=&quot;829&quot; alt=&quot;스크린샷 2022-10-24 오전 12 20 26&quot; src=&quot;https://user-images.githubusercontent.com/52737532/197402097-c00aea67-8486-4d33-9b82-384eaab6c514.png&quot;&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt;에 &lt;code class=&quot;language-text&quot;&gt;a&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;b&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;c&lt;/code&gt; commit이 있고 &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt;에 squash merge를 한다고 가정해봅시다.&lt;/p&gt;
&lt;img width=&quot;829&quot; alt=&quot;스크린샷 2022-10-24 오전 12 22 25&quot; src=&quot;https://user-images.githubusercontent.com/52737532/197402139-4215d8a5-ebd5-40ae-b79d-2c270d611835.png&quot;&gt;
&lt;p&gt;squash merge 이후 상황은 위와 같습니다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt; branch는 서비스용 branch기 때문에, 추후 개발 기간동안 구현된 기능은 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch에 merge 될 것이고,&lt;br&gt;
개발 완료된 기능들이 서비스로 release 될 때, &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt;에서 다시 &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt;으로 merge가 될 것 입니다.&lt;/p&gt;
&lt;img width=&quot;728&quot; alt=&quot;스크린샷 2022-10-24 오전 12 33 56&quot; src=&quot;https://user-images.githubusercontent.com/52737532/197402173-107f4147-0485-48fa-97b7-fa75b6c92988.png&quot;&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt;으로의 release 이후 새로운 커밋 &lt;code class=&quot;language-text&quot;&gt;d&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;e&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt;에 merge 되었습니다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;d&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;e&lt;/code&gt;를 release 하기 위해서는 우리는 다시 &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt;으로 merge를 할 것 입니다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;d&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;e&lt;/code&gt;를 포함한 PR을 작성하는 순간 충돌이 발생하게 됩니다!&lt;/p&gt;
&lt;img width=&quot;728&quot; alt=&quot;스크린샷 2022-10-24 오전 12 40 46&quot; src=&quot;https://user-images.githubusercontent.com/52737532/197402193-d1c8d4f1-bdfe-4e0a-b6b9-4ee5bca9efb1.png&quot;&gt;
&lt;p&gt;이는 &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt;에 squash merge된 &lt;code class=&quot;language-text&quot;&gt;a, b, c&lt;/code&gt; commit이 &lt;code class=&quot;language-text&quot;&gt;a&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;b&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;c&lt;/code&gt; commit을 포함하고 있다는 사실을 알 수 없기 때문에 발생하는 현상입니다.&lt;br&gt;
PR에 포함된 commit을 살펴보면 &lt;code class=&quot;language-text&quot;&gt;d&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;e&lt;/code&gt; 뿐만 아니라, 이전에 이미 &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt;에 merge한 commit인 &lt;code class=&quot;language-text&quot;&gt;a&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;b&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;c&lt;/code&gt;도 commit 내역에 포함되어 있는 것을 볼 수 있습니다.&lt;br&gt;
즉, &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt; branch의 &lt;code class=&quot;language-text&quot;&gt;a, b, c&lt;/code&gt; commit 이전 조상을 &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt;의 공통 조상으로 인식하게 됩니다.&lt;br&gt;
이런 이유로 충돌이 발생합니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt; branch의 경우 해당 기능 구현 이후 사용되지 않을 branch이기 때문에 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt;로의 squash merge 이후 발생할 문제가 없습니다.&lt;br&gt;
하지만 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch의 경우 &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt;에 merge된 이후라고 해도 이전 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch를 삭제하고 새로운 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt; branch를 &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt;에서 파생시키지 않는 이상 재활용되게 됩니다.&lt;br&gt;
그렇기 때문에 두개의 base를 맞춰 주는 것이 중요합니다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://backlog.com/git-tutorial/kr/stepup/stepup1_4.html&quot;&gt;누구나 쉽게 이해할 수 있는 Git 입문 - merge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kotlinworld.com/277&quot;&gt;Git merge 한번에 정리하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://meetup.toast.com/posts/122&quot;&gt;Meetup - GitHub의 Merge, Squash and Merge, Rebase and Merge 정확히 이해하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://seosh817.tistory.com/240&quot;&gt;Git Rebase란?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sokdak-sokdak.tistory.com/18&quot;&gt;속닥속닥이 겪은 Squash merge 이후 rebase 문제&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[슬랙 소셜 로그인과 알림을 위한 앱 설치 가이드]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-10-22-slack-login-and-alarm/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-10-22-slack-login-and-alarm/</guid><pubDate>Sat, 22 Oct 2022 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;슬랙 소셜 로그인과 알림을 위한 앱 설치 가이드&lt;/h1&gt;
&lt;p&gt;구글, 깃허브, 카카오, 네이버와 같은 서비스는 소셜 로그인을 제공합니다. 슬랙도 해당 기능을 지원하지만 사용자가 많지 않아 한글로 작성된 가이드를 찾기 어렵습니다. 슬랙은 앱을 배포하면 워크스페이스에 봇을 설치해
사용자에게 알림을 보낼 수 있는 기능을 제공합니다. 위 글에서 &lt;a href=&quot;https://github.com/woowacourse-teams/2022-kkogkkog&quot;&gt;꼭꼭 프로젝트&lt;/a&gt; 에 슬랙 로그인과 알림을 지원하기 위해
탐구한 내용을 공유합니다.&lt;/p&gt;
&lt;p&gt;슬랙은 OAuth2, OpenId Connect 방식의 소셜 로그인을 지원합니다. 해당 글은 OAuth2 표준의 동작 방식을 알고 있는 사람을 대상으로 작성하였습니다. 만약 OAuth 동작 방식을 모른다면
OAuth2 동작 방식을 검색하거나 &lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-07-10-understanding-oauth/&quot;&gt;OAuth 개념 및 동작 방식 이해하기&lt;/a&gt; 를
참고해주세요.&lt;/p&gt;
&lt;h2&gt;슬랙 소셜 로그인&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://api.slack.com/authentication/sign-in-with-slack&quot;&gt;Sign in with Slack 문서&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;OpenId Connect를 사용하는 방법입니다. 슬랙은 해당 엔드포엔트에서 사용자 정보를 요청할 수 있는 엑세스 토큰을 제공합니다.&lt;/p&gt;
&lt;h3&gt;슬랙 로그인 과정&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Resource Owner가 슬랙으로 로그인 버튼을 눌러 Slack Authorization Server의 엔드포인트로 접속하여 로그인하고 정보 제공을 승인합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Slack Authorization Server의 엔드포인트&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;http&quot;&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;GET https://slack.com/openid/connect/authorize?
 scope=email openid profile
&amp;amp;client_id=4274003xxxxxx.xxxxxxxxxxxxx
&amp;amp;redirect_uri=https://kkogkkog.com/redirect
&amp;amp;response_type=code&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ffb3a5e19197e60b8e54999010b795b6/f97d7/2022-10-22-slack1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 46.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAy0lEQVR42q2PywqCQBiFfe1oEW1qE0HQIiyk20yWCraSnqSWPoTOTHPxlGYWooHQDx/Mz8x8nGN5no8wPMPzA5A9xb6EEArXPbZCqVu9/cbCn8dKkgRNpGkKKWUrnPPGf1Z+0YQQAsaYD1n2otzf0jo/hVrrKpHirKA4KwUlVXdhnqQaIwDNqrVTQpayZz2D+BrD7ttwRhuc5gHcmYdFb4FoGxXpOesqvMVYDR3sxmscpgRkQrAcrHChl27CemVjJO5KQGr56pu1V34AKjagVT+nRDYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack1&quot;
        title=&quot;&quot;
        src=&quot;/static/ffb3a5e19197e60b8e54999010b795b6/f97d7/2022-10-22-slack1.png&quot;
        srcset=&quot;/static/ffb3a5e19197e60b8e54999010b795b6/0eb09/2022-10-22-slack1.png 500w,
/static/ffb3a5e19197e60b8e54999010b795b6/1263b/2022-10-22-slack1.png 1000w,
/static/ffb3a5e19197e60b8e54999010b795b6/f97d7/2022-10-22-slack1.png 2000w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Slack Authorization Server는 설정한 Client의 URL로 리다이렉트시킵니다. 이때 QueryString으로 Authorization code를 전달합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;http&quot;&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;GET https://kkogkkog.com/redirect?code=12345678.90ab&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Client는 Resource Owner를 거치지 않고 ClientId, Client Secret, Code를 Slack Authorization Server에 전달 합니다. 만약 올바른 코드이면 엑세스 토큰을
반환합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;OpenId Connect 표준에 따라 id_token의 payload 부분에도 유저 정보가 인코딩되어 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;http&quot;&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;POST https://slack.com/api/openid.connect.token
&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;application/x-www-form-urlencoded;charset=UTF-8&lt;/span&gt;&lt;/span&gt;

client_id=&quot;4274003xxxxxx.xxxxxxxxxxxxx&quot;
&amp;amp;client_secret=&quot;3001a9f1xxxxxxxxxxxxxxxxxxxxxxxx&quot;
&amp;amp;redirect_uri=&quot;https://kkogkkog.com/redirect&quot;
&amp;amp;code=&quot;12345678.90ab&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;http&quot;&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;application/json;charset=UTF-8&lt;/span&gt;&lt;/span&gt;

{
    &quot;ok&quot;: true,
    &quot;access_token&quot;: &quot;xoxp-1234&quot;,
    &quot;token_type&quot;: &quot;Bearer&quot;,
    &quot;id_token&quot;: &quot;eyJhbGcMjY5OTA2MzcWNrLmNvbVwvdGVhbV9p...&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;이제 엑세스 토큰으로 Resource Server에서 유저 정보와 같은 자원을 가져와 내가 만든 서버에서 사용할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;http&quot;&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;GET https://slack.com/api/openid.connect.userInfo
&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;application/x-www-form-urlencoded;charset=UTF-8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Authorization&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;Bearer {Access Token}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;http&quot;&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;application/json;charset=UTF-8&lt;/span&gt;&lt;/span&gt;

{
    &quot;ok&quot;: true,
    &quot;sub&quot;: &quot;U0R7JM&quot;,
    &quot;https://slack.com/user_id&quot;: &quot;U0R7JM&quot;,
    &quot;https://slack.com/team_id&quot;: &quot;T0R7GR&quot;,
    &quot;email&quot;: &quot;krane@slack-corp.com&quot;,
    &quot;name&quot;: &quot;krane&quot;,
    &quot;picture&quot;: &quot;https://secure.gravatar.com/....png&quot;,
    &quot;locale&quot;: &quot;en-US&quot;,
    &quot;https://slack.com/team_name&quot;: &quot;kraneflannel&quot;,
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;슬랙 앱 설정&lt;/h3&gt;
&lt;p&gt;슬랙 로그인을 위한 앱 설정 과정입니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://api.slack.com/apps&quot;&gt;https://api.slack.com/apps&lt;/a&gt; 에 접속하여 Go to Slack 버튼을 클릭합니다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 37%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABJ0lEQVR42nWR3U7CQBCF+6he6oVe+VKU8lMwQd9DIZJULKUVU0htodvuT3vcGdIQDG7yZWfO7s6cyTrD0Riz5xf4/gTjsQ9/MuWY9F7PRb/vwfMGF5Dmun3eB8MRZtMnPDze4u7+Bo7SBmhb0GqaBkopGGM47/S/q7V6B+dGYxG84j14gxN8RljM5zgcDkjTFMvlEmG4xrEUZ45HPhdCoKoqhuKyLHkn2qa1hlo4q3CDTRRBSsmQw0oSBqXUELWCsA+pYOecJqnr+gJRCcb5sAW/kuRUyHbmC+SC3FiUklyIoPM8z8/3ruCs1jG+t9uTYB2m+x/E2xS73R5ZlrHrbhVFgcQ2F9xIXS8YhBGSOOaHJNAnSaVh7Fg0mra51pohl/QRFP/n8BdUBQxcDt8wVAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack2&quot;
        title=&quot;&quot;
        src=&quot;/static/1949cc5c766607b2207635ce29f2592e/f97d7/2022-10-22-slack2.png&quot;
        srcset=&quot;/static/1949cc5c766607b2207635ce29f2592e/0eb09/2022-10-22-slack2.png 500w,
/static/1949cc5c766607b2207635ce29f2592e/1263b/2022-10-22-slack2.png 1000w,
/static/1949cc5c766607b2207635ce29f2592e/f97d7/2022-10-22-slack2.png 2000w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;등록하고자 하는 워크스페이스에 소속된 계정으로 로그인합니다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/51e8fd65deb25753056b1a663cff2e16/f97d7/2022-10-22-slack3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.400000000000006%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABeUlEQVR42pVSy07CUBDlM90bPwBQXgrshK1ufCx1jRsRFibGpGqIVKQaERKEBAXaWvoCWujreFtDTGwxMsnJJHPnnjnn3gmlttNIZ7JIprYRiycQjkR9iESjiMUSiCdSiG5uBfYsEMI/wrAdfPADvLXb0DTNqzmOE9gbcg+CYJomJpMJdH0GfTzGoNdFo/EKnucxn88xnU5hGIbvnk/hYjLHcSiXyzgrFEBRFGq1RzAMg+J5ETRNo1QqodPp+NQuteyqYFkO/cEQ7JAlGEJRZCiyDFlWIIqSp9BneRmhO1Qj/XPTxkgl5CrQl+Blt64T2AHPuJTQtgmRpEIQFbS772i1e3hpdtEkWRC/65Zlr6LQgU5+dKZrUInVkcDjk2chjQRo04mHoJ/+0/LMsuBqsL3VsYnFHwKT+F3Jstv7UGng6uIW1OU9qndPYKpN0DfPqFzXUa+2VrPsxkn+FOm1LHIbeRwkD7EX3sfueo7UMjjaOYZFdvX32nwBeV4robgr7BkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack3&quot;
        title=&quot;&quot;
        src=&quot;/static/51e8fd65deb25753056b1a663cff2e16/f97d7/2022-10-22-slack3.png&quot;
        srcset=&quot;/static/51e8fd65deb25753056b1a663cff2e16/0eb09/2022-10-22-slack3.png 500w,
/static/51e8fd65deb25753056b1a663cff2e16/1263b/2022-10-22-slack3.png 1000w,
/static/51e8fd65deb25753056b1a663cff2e16/f97d7/2022-10-22-slack3.png 2000w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;로그인 후 앱을 설정할 워크스페이스를 선택합니다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5c9dbf02efbfa96ccde0255c664738a9/f97d7/2022-10-22-slack4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 37.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABFklEQVR42o2QS0vDQBSF8//3uhOXbtq8rO4FFcGVDSikRPKYaZ2ZNk1SQ9JkJschwZKiFgc+7jzOPXO4huteYza7geO6PZZlYzo1j5iYJizbgeO4w3nEWGdrjYF/rH1dIXj34c09SCVPag0pJcY0TQNOORhhEEsx7BOGOIwRxRGEEKCUIkkIiK6cCyilDv0/Eip9mbIUuSiw0ZWGFCQgCBYBwijURPB9vzcjdAnG2HHC32IX2Rb+2ysWmrLI0O4rtHWNrm0GZKt/VgNd97fh95N/f4er8zPYlxcg3guqJUFJE6hUoEvXPWojINcckn/oRnXCUA999fyEW3OC+eMDSr4Cyh3wWQC7/ECXb9FkKToSQw/+YPgF3xQJB8+7+wQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack4&quot;
        title=&quot;&quot;
        src=&quot;/static/5c9dbf02efbfa96ccde0255c664738a9/f97d7/2022-10-22-slack4.png&quot;
        srcset=&quot;/static/5c9dbf02efbfa96ccde0255c664738a9/0eb09/2022-10-22-slack4.png 500w,
/static/5c9dbf02efbfa96ccde0255c664738a9/1263b/2022-10-22-slack4.png 1000w,
/static/5c9dbf02efbfa96ccde0255c664738a9/f97d7/2022-10-22-slack4.png 2000w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/475acf31e1976a8b39b19b3e01d5582b/b3ebc/2022-10-22-slack5.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 21%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA80lEQVR42m2OyUrDABRF8xeFaAxUW1sb21QrWKtF/YCCigtFFFGboXGpuCn+gbrOYGoc/jFkOCbBZuWDy30P7jtcwZ8FOI6Hbbu47jtf3z8cn5yidnv0trZpKR1Wao1StUz11SZLcpXR6Igg+MR2XBzXw599IPA3aZrOV8zJPcq6SkfdoLmmZPBNdgZDBsMD+nv7mR9m8CYPj09FPo7j8lfIj1xRFBWeg8/OL6hURBYlGVGUqC7XabTatDP1FYVdtYu0IKMbVgEJw5A559+GL69vXF5dY5gWmm4y1gxu7nSsscazaTC1Jui3Oo7nF/kkScqGvwzO09kNuUr/AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack5&quot;
        title=&quot;&quot;
        src=&quot;/static/475acf31e1976a8b39b19b3e01d5582b/f97d7/2022-10-22-slack5.png&quot;
        srcset=&quot;/static/475acf31e1976a8b39b19b3e01d5582b/0eb09/2022-10-22-slack5.png 500w,
/static/475acf31e1976a8b39b19b3e01d5582b/1263b/2022-10-22-slack5.png 1000w,
/static/475acf31e1976a8b39b19b3e01d5582b/f97d7/2022-10-22-slack5.png 2000w,
/static/475acf31e1976a8b39b19b3e01d5582b/b3ebc/2022-10-22-slack5.png 2278w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://api.slack.com/apps&quot;&gt;https://api.slack.com/apps&lt;/a&gt; 에 접속하여 Create an App 버튼을 클릭하여 앱을 생성합니다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 40.400000000000006%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABTklEQVR42oWRy0rDUBCG88I+gIK9WChSr+ALKILgwoWggiupL+AiRReaNmlDmiZp0ubWNE3yOxOb1NqCB34mZOZ8/8wZodk8xOnZOVpHx6gfNH5Ub6y+l6pUaqhUa6jW6lu1T3lmCeFshgz/nzRNkWXbK7MsxWKxQJIkEHTTgaIoME0TURShr/QhiiIkScKMzApxjhWGIYIgWNPvOmEwNNDpiLBtG/P5PP/JkR1ZruvC87wyMnTVWZbXFmYsQSWgLMsljCG+5xPAw9gao9vrQSJ9dbv4pK7VgQqPctPpNO82juN1oGZY+cicLIG+j8lkgpFhwCH5JM8w4VIcahocxyGomwM2gLI6hEwdFG/BQD7JcgHP7QfsXLawd3OB3asTBFq/mDcPGyOPLBu6rpcPz468rTghMN15/3jD9cs9bl+fcNd+RORYSNlwudW/wG+2aUhPcRXkywAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack6&quot;
        title=&quot;&quot;
        src=&quot;/static/02af30fd479033c3e20be92c629a153f/f97d7/2022-10-22-slack6.png&quot;
        srcset=&quot;/static/02af30fd479033c3e20be92c629a153f/0eb09/2022-10-22-slack6.png 500w,
/static/02af30fd479033c3e20be92c629a153f/1263b/2022-10-22-slack6.png 1000w,
/static/02af30fd479033c3e20be92c629a153f/f97d7/2022-10-22-slack6.png 2000w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
    &lt;/span&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 57.599999999999994%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAACAUlEQVR42m1TyW4TQRT0X5FVnIBwAWR5He/72P9B7r7CxQuyfMNWuIULOOdgk0SyEIF4vCYOQxwnXjUu+r24RzHJSKXXb1F1dVeP5cXOSzx7voOt7adYW9/E+sbmMm6tgGqy/xio92RtA5Z4Io54/A6qqkJNqkgmk7xOJBImotEoIpEIYrHYo5B9i6Io8Pl8XAgEAnC5XHA4HFwLh8MIhUIcJSi/D1knMiZ0Op3weDyc0AAReb1eVpxKpVidVEG1+2uCJDIJSU0wGOSEBoi8UCjge62GSqWC46MjtFstaI0GNE1DU4DWvV4Xn/f3eXPawCT0iCPLhNRYrVaUy2XQ93cwwPDmBtPZDJPpjON0NsfteMz9arUKu92+ovSBQqfbjb3CB0yav9Gvn2DabWHSaWLcaiyhYSQi9D5qB195Pv6/Qro7k1Dke+/fYdjrQDv9iX67hUuBQf8Ct/qfO1xdAfMZauLINL9CqIgdpEvUsAuTPgmF0C8wODvF9dkvjLttLC7PTRgCuNaFwi88v0JILiuKh0lJoc1mQ6n0EefdDo6FMT/qdTSbbQx0HUOhjDAdjfgOvx0e8hNbuUO3UEjvT5ry6vUblJamzOdzGIbBcbFYwBDgKGrSlAeE9Ezkkcl+v9+Pt7u7KBaLyGQyyGazJijP5XLI5/PcT6fTPC//EiL8B2GwdaYRoFxCAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack7&quot;
        title=&quot;&quot;
        src=&quot;/static/35d0d65bc040a3ae08c1dad68615482e/f97d7/2022-10-22-slack7.png&quot;
        srcset=&quot;/static/35d0d65bc040a3ae08c1dad68615482e/0eb09/2022-10-22-slack7.png 500w,
/static/35d0d65bc040a3ae08c1dad68615482e/1263b/2022-10-22-slack7.png 1000w,
/static/35d0d65bc040a3ae08c1dad68615482e/f97d7/2022-10-22-slack7.png 2000w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
    &lt;/span&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 58.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAACTElEQVR42nVT224SURTltyql2sRY9RkI1xnul4/wwTTRPlihqZeS9MEHffXBWJtqxQtpbCItiEQtVzVttA0UYQbmwpTlPicBQXSSlZO9z9nrrL3OHtPC5au4tHAFFst5nDNbOMyzc5ileBxmyk/EdIafJczMmGGZu4D5+YswRSIRRKNRjlgshng8Dp/PB0EQIIoiRFoFwQu32z2Cy+Xi+7EY1VF9KBhCIBBAOByGiW16PB4eeL1e2KxWXF9cRHJ1FcuJBJaTSdxKJHF7ZQX3UincXVtDan0dN5aW6DKRkwWDwRFMNquN3xgK0S1+P0SfH3uZDBrlLzg++IxmpYQTtlbLgNzBWecXYOjI776Dk+qYkAlClvQTEQsYqZ/WwvYWpJ8/oEodQNcw0FQMVBVGtwtdkoCzM+xvPuWEkb8J7Xb7BKGXvMm/2ka/30e720Nb7qIlyZCJcAASR2Ts23++CYfTCfYGE4QuMpk9wihJpPkXGziqfsL3Ugmdk2O0Cb3TJqD0YMhMocEVstqplh0Ox0ghA2s5n3kD6VuFfCuh/bWGVq3CwfxsENBqIP/29X88dNII0OsOE0zth0IBmmGg1ZHQId+0vsFjjWxQ9T5vOZfLgYkJ/8tDNjZDD1kb2WwWqqKg2WjgtNmERA/RlWVI5Kmi6JzwPZ1xOh3THnrH1DFCgYa5WCzyIoVIGXRNh6pp0Ihwd28Hj3e2kH72hM/tlMLhH/GnZRHp9EscHh6hVquhXq9zlKs1HHws4sHDFK7dv4lHqTtklcBFjBP+BgtWhzYuLAMbAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack8&quot;
        title=&quot;&quot;
        src=&quot;/static/567adf89f86db0ad20852fc73e9a3b97/f97d7/2022-10-22-slack8.png&quot;
        srcset=&quot;/static/567adf89f86db0ad20852fc73e9a3b97/0eb09/2022-10-22-slack8.png 500w,
/static/567adf89f86db0ad20852fc73e9a3b97/1263b/2022-10-22-slack8.png 1000w,
/static/567adf89f86db0ad20852fc73e9a3b97/f97d7/2022-10-22-slack8.png 2000w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;초기 화면에서 OAuth &amp;#x26; Permissions 탭을 선택합니다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/093b984ae5599d79b5d62db294169606/f97d7/2022-10-22-slack9.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB3klEQVR42o1TS2/TQBj07+VaiTYBgUJVVLhw5IDE3+BED3DjAEqQEhoSx7XjteNX0vhtx5nuLLKhaaSy0mjt/caz8816tRcvB7h88xYXry/x6vwCg8E5ev3nOOs968D3vsTpWR9PT3v3ah1HgnUtzzP879jtdmia5mhtv9+jrmto0W2CIk+BRpJ3FRxHYDgcYjKZYDweYzQcIQxD5EWONE2RZZmaD8H1PM+hfRvNca0L2EEK093idpsgCAL4fqCEfN9HkshNi0I5PIaqqlSd0J68+4GT999x8mGC3scpvLBC4K8wm81hGAYsy8LSWqqZLf0rxHe2yhg6QSE8WLYrLVcSpdpts9nAdVw4woHruvA8D6vVSjmN41jNxHa7VXwKsl0lSBc3NwbW67VaJIFz+2GW/smMz6wdOwy67Ryapql2p0grGEURhBBqnRk6jqNaZratCMFBPlvvBNmSsG3lggsksBWKUYDPj/1K9wSXtsDv2QymDD6WbdVSkKdLh3RG0Hnr7HA8cGj9GkP/OUJgGiiSWBLqzmEURupAmF97mod44ND+/AmLL1ewv16hdAUKWWwkqc2pDZ1OjoFiZVn+FfTkzTB1HZ50VMjbQALbXCwW0Oc6ptOpugWP5dgK3gHH3snzqQnGaQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack9&quot;
        title=&quot;&quot;
        src=&quot;/static/093b984ae5599d79b5d62db294169606/f97d7/2022-10-22-slack9.png&quot;
        srcset=&quot;/static/093b984ae5599d79b5d62db294169606/0eb09/2022-10-22-slack9.png 500w,
/static/093b984ae5599d79b5d62db294169606/1263b/2022-10-22-slack9.png 1000w,
/static/093b984ae5599d79b5d62db294169606/f97d7/2022-10-22-slack9.png 2000w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Https로 시작하는 Client의 Redirect Url을 설정합니다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/379608204df939f046282d268e74cc0d/f97d7/2022-10-22-slack10.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60.199999999999996%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABx0lEQVR42oWSD2/aMBDF+dqUUmhFN3XVvtQ0Tds0TeqKWjF1hUJIYpeSv07sBPJ659QItm6z9GTH8f3u3dmd07MRLi/f4+LiHc7P32I0emPn4fAMg8EpTk6G6PcHVt1uD92jYxz1+n+od9y3/zpKKTQ4HJvNBlVVoa5rNE1zsL//vT94n893lp6H++kUT0mGoiigtUaSJJBSWnFCBvFhnt16XxzjYjtXVz8wHo8RRxGMMXaTg7bb7V8Bv4ur4TgLlFJAiBBpmlog/5RCYj6fwyP3tiVUjkvC2nfrEpZl2QLXVKpcpyheNhgohMBsNoO3WOCBZwKzgiDA0ictl3bt+z5Wq5WF7hwm4gkqU9BcbtkCQ/mIh4WHKEmRZBli6mmrFFFWQmnTuia37rJ2QJ+y5RTk+mcMAUOJXz/v4M3JlR/CEyvIOENaGCRKY50VNlkUre3FcRt2JcdxjDzPCcQZqIdlgeD7N0y+fsHt50+YfvyAjNzlukJhaitFa1PV1gSDDhxyic5dSXOtcjxOJrinXiWUQIcBKAr/GgdAt2hFQApOb68hb66xkQTzZmjIffPyeF+Te4uvAFvVusSWSq+4FSq3FfxPDvgMlpKED2cfb7MAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack10&quot;
        title=&quot;&quot;
        src=&quot;/static/379608204df939f046282d268e74cc0d/f97d7/2022-10-22-slack10.png&quot;
        srcset=&quot;/static/379608204df939f046282d268e74cc0d/0eb09/2022-10-22-slack10.png 500w,
/static/379608204df939f046282d268e74cc0d/1263b/2022-10-22-slack10.png 1000w,
/static/379608204df939f046282d268e74cc0d/f97d7/2022-10-22-slack10.png 2000w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open Id connect를 사용하기 위한 openid, profile, email Scopes를 추가합니다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9831d11d42b84757afa097ea80a3d596/f97d7/2022-10-22-slack11.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 59.199999999999996%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABU0lEQVR42o1S2U7DMBDM39KmB5QKVfwfT/AA4iNokybkjp1z8LjdKpGaUkujidfeyax3neftC3a7V2wNP2222BisHzd4mC0wM7A8P3277gqL5RruYjXC3F2e7hg4XduiPaPrOstKKdR1Da6+70f7a4t5vEc4vKy1voCJWZYhjmPLRVEgz3PLZVlacUkWMEfOnKEYA03TQCuNNE2hSoV7FnPE2EhQHAZBgMPhAM/zbIwJjAu4r6rKlkowJvlXBY++j/1+D98wnRIsX0onMybvSp50yL8XpUY1aIK8nziS5nGR/y85NcJtxxbbZDqKogjRb4QkSRCGoY2xISz9tqA5zFVthPqLQ4rxPfkEZE7AsMPTgpw3dtm46wddpIvhJFBIZvS2IB1WGvkxQJPE6FLjxOxLc5klSmNkkKXDE4IKiodpguL9DeH3J7KvD8D7wdnOXXP4ByEOiL/NEtjUAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack11&quot;
        title=&quot;&quot;
        src=&quot;/static/9831d11d42b84757afa097ea80a3d596/f97d7/2022-10-22-slack11.png&quot;
        srcset=&quot;/static/9831d11d42b84757afa097ea80a3d596/0eb09/2022-10-22-slack11.png 500w,
/static/9831d11d42b84757afa097ea80a3d596/1263b/2022-10-22-slack11.png 1000w,
/static/9831d11d42b84757afa097ea80a3d596/f97d7/2022-10-22-slack11.png 2000w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install to Workspace를 클릭하여 앱을 설치합니다. 앞으로 변경사항이 있을 때 마다 앱 재설치가 필요합니다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a7b1685ef9aa6553e43e7be49417ca83/f97d7/2022-10-22-slack12.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 59.199999999999996%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAAB6UlEQVR42o1STW/TQBDNz+ZMmxREadqiip/AEZBAcONAD4CIWkKAOP5I7Dhe24ntJI6/kjiPmW1THDWVWOlp7H07b+bNbu34eRMXFy9xdv4CzdMznDRP8ez4BI2jp6g3nuzg8UEdB4cNHNaP9oK52nK5xP+szWaDsixlfIhbr9eojcMp8ixFnufIsgz9fh+tVgvdbhedTgftdhtqT0WSJJLnmCQpFotFBQnSNJV8TdN1BJOJFGSkdDgIAkRRJGMYhgjpmxMSTkpvile72uYyajoJDgamTNhuaromu9I0DYZhoNdT0KeoqiqUrgJd02Ux7pZXURT/BE3ThOu6dxtM+p4HIcQNiBN+iJEfwKfxzOdzCe6c7d4T5CSPBGazGZZExDTPRx9e4ZP2Sx5ekaWS7d1i39oRVBQF4/FYzoKHWtCti3iKRZHd3eAW1f8HBUcjB2w7iqZyg7v0XR/2SMByBByybNs2zXkAy7LAz4yLr1Yr+c0XsyMYjj0EnkA8DehQgYK6FJQ4sh1YA4qWSUJDKjqQhR3HkbOd0MuI41iK7gi+uezg9eef+PjdQNsKUYoh5r9/IOhcI7r6hnJo3Fot947gnuX3X/7gLYm++9rDVd9HGc+Quw4yVyB3bCyjECuyxRYZW7tVVN/hX/4/e+yfG+2DAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack12&quot;
        title=&quot;&quot;
        src=&quot;/static/a7b1685ef9aa6553e43e7be49417ca83/f97d7/2022-10-22-slack12.png&quot;
        srcset=&quot;/static/a7b1685ef9aa6553e43e7be49417ca83/0eb09/2022-10-22-slack12.png 500w,
/static/a7b1685ef9aa6553e43e7be49417ca83/1263b/2022-10-22-slack12.png 1000w,
/static/a7b1685ef9aa6553e43e7be49417ca83/f97d7/2022-10-22-slack12.png 2000w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;이제 슬랙 로그인을 사용하기 위한 설정을 완료하였습니다. Basic Information에서 Client Id와 Client Secret을 기억해둡니다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/44e0195d08ac533f5273dafa552fb8fb/f97d7/2022-10-22-slack13.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 61%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABhElEQVR42q2SS2+CUBCFSdou+uepVnzbtCTu69p/4NaFtWqil4XyEBBBHr5O70ykUdw6yZfDZe7MPcNFqdUb+PzS0e58oNlso9XqoNXuoFqvg3JatYaKVsVbqQz1rYRS+f2GcrnCSvlarQElyzJQnM9n5HE4HHA8HllPp9NdvhiUo32E4rgeTMvGdrtFksQIggDj8S9Gox8Mh0MYhgHf9+G6LtbrNSvhOA7CMJQ1CXa7HWscx1B820MQxkil0zRNmbyQdLPZIIoiPpCgJrnSBPv9/r+OUFzpLpXd8xd0kmM7/Eyj5KOS89VqBcu0kKUZj0cNsysj3NCRLshynkjiBLPZDJPJhHU6nWK5XPIez/N4/ORigGruHF4vcmzbRr/fR7fbha7rGAwG7JKKiQNrdlkXHBZvjDaZponedw+qqkKraAwdkv8Blvwkrrdhp8Ugh4ZEXCO/l5BNxXw+F4vFgpEXIeTYQl6Q8INQ+NtIyMMl2U0tOXyVPEmeHwE1fFEeGY9u+Afq3NlBx6xlSwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack13&quot;
        title=&quot;&quot;
        src=&quot;/static/44e0195d08ac533f5273dafa552fb8fb/f97d7/2022-10-22-slack13.png&quot;
        srcset=&quot;/static/44e0195d08ac533f5273dafa552fb8fb/0eb09/2022-10-22-slack13.png 500w,
/static/44e0195d08ac533f5273dafa552fb8fb/1263b/2022-10-22-slack13.png 1000w,
/static/44e0195d08ac533f5273dafa552fb8fb/f97d7/2022-10-22-slack13.png 2000w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3, 4 과정 예시 코드&lt;/h3&gt;
&lt;p&gt;→ 예시 코드이며 실제 서비스마다 다르게 활용할 수 있다.&lt;/p&gt;
&lt;p&gt;백엔드 WAS에서 프론트엔드 Client로부터 인가 코드를 받는 컨트롤러 예시이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SlackController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/code&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SlackOpenIdConnectUserInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestParam&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;SlackOpenIdConnectTokenResponse&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;slackOpenIdConnectToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; accessToken &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAccessToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;SlackOpenIdConnectUserInfo&lt;/span&gt; userInfo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;slackConnectUserInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;accessToken&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://api.slack.com/methods/openid.connect.token&quot;&gt;openid connect token docs&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SlackOpenIdConnectTokenResponse&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;slackOpenIdConnectToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;RestTemplate&lt;/span&gt; restTemplate&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RestTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; requestUrl&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://slack.com/api/openid.connect.token&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token class-name&quot;&gt;MultiValueMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; parameters&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LinkedMultiValueMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  parameters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;client_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;4274003xxxxxx.xxxxxxxxxxxxx&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  parameters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;client_secret&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&quot;&lt;span class=&quot;token number&quot;&gt;3001&lt;/span&gt;a9f1xxxxxxxxxxxxxxxxxxxxxxxx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  parameters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;redirect_uri&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://kkogkkog.com/redirect&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  parameters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;code&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token class-name&quot;&gt;HttpHeaders&lt;/span&gt; headers&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpHeaders&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setContentType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MediaType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;APPLICATION_FORM_URLENCODED&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAccept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;singletonList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MediaType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;APPLICATION_JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token class-name&quot;&gt;HttpEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MultiValueMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;httpEntity&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parameters&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; restTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postForObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;requestUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;httpEntity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SlackOpenIdConnectTokenResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SlackOpenIdConnectTokenResponse&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

 &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; ok&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

 &lt;span class=&quot;token annotation punctuation&quot;&gt;@JsonProperty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;access_token&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; accessToken&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;


 &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SlackOpenIdConnectTokenResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

 &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getOk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; ok&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

 &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getAccessToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; accessToken&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://api.slack.com/methods/openid.connect.userInfo&quot;&gt;openid connect userInfo docs&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SlackOpenIdConnectUserInfo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;slackConnectUserInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;RestTemplate&lt;/span&gt; restTemplate&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RestTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; requestUrl&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://slack.com/api/openid.connect.userInfo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token class-name&quot;&gt;HttpHeaders&lt;/span&gt; headers&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpHeaders&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setBearerAuth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setContentType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MediaType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;APPLICATION_FORM_URLENCODED&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAccept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;singletonList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MediaType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;APPLICATION_JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token class-name&quot;&gt;HttpEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MultiValueMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;httpEntity&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; restTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postForObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;requestUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;httpEntity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SlackOpenIdConnectUserInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SlackOpenIdConnectUserInfo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;slackConnectUserInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;RestTemplate&lt;/span&gt; restTemplate&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RestTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; requestUrl&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://slack.com/api/openid.connect.userInfo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token class-name&quot;&gt;HttpHeaders&lt;/span&gt; headers&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpHeaders&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setBearerAuth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setContentType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MediaType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;APPLICATION_FORM_URLENCODED&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAccept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;singletonList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MediaType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;APPLICATION_JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token class-name&quot;&gt;HttpEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MultiValueMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;httpEntity&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; restTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postForObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;requestUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;httpEntity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SlackOpenIdConnectUserInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;슬랙 앱 설치&lt;/h2&gt;
&lt;p&gt;슬랙은 토큰 종류를 Bot과 User로 구분합니다. 알림을 보낼 수 있는 Bot Token 또한 OAuth 엔드포인트를 사용하여 User Token과 같은 방식으로 얻을 수 있습니다.&lt;/p&gt;
&lt;h3&gt;앱 설치 과정&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Add To Slack 버튼을 눌러 Slack Authorization Server의 엔드포인트로 접속하여 앱 설치를 승인합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;http&quot;&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;GET https://slack.com/oauth/v2/authorize?
client_id=4274003xxxxxx.xxxxxxxxxxxxx
&amp;amp;scope=chat:write,users:read,users:read.email
&amp;amp;user_scope=openid,profile,email&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Slack Authorization Server는 설정한 Client의 URL로 리다이렉트시킵니다. 유저 로그인과 마찬가지로 Slack App에서 Redirect Uri를 미리 등록해야 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;http&quot;&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;GET https://kkogkkog.com/redirect/download?code=12345678.90ab&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Client는 Resource Owner를 거치지 않고 ClientId, Client Secret, Code를 Slack Authorization Server에 전달 합니다. 만약 올바른 코드이면 봇 엑세스 토큰을 반환합니다. 이 시점에 워크스페이스에 앱이 설치됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;http&quot;&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;POST https://slack.com/api/oauth.v2.access
&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;application/x-www-form-urlencoded;charset=UTF-8&lt;/span&gt;&lt;/span&gt;

client_id=&quot;4274003xxxxxx.xxxxxxxxxxxxx&quot;
&amp;amp;client_secret=&quot;3001a9f1xxxxxxxxxxxxxxxxxxxxxxxx&quot;
&amp;amp;redirect_uri=&quot;https://kkogkkog.com/redirect/download&quot;
&amp;amp;code=&quot;12345678.90ab&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;http&quot;&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;application/json;charset=UTF-8&lt;/span&gt;&lt;/span&gt;

{
    &quot;ok&quot;: true,
    &quot;access_token&quot;: &quot;xoxb-17653672481-19874698323-pdFZKVeTuE8sk7oOcBrzbqgy&quot;,
    &quot;token_type&quot;: &quot;bot&quot;,
    &quot;scope&quot;: &quot;chat:write,users:read,users:read.email&quot;,
    &quot;bot_user_id&quot;: &quot;U0KRQLJ9H&quot;,
    &quot;team&quot;: {
        &quot;name&quot;: &quot;Slack Softball Team&quot;,
        &quot;id&quot;: &quot;T9TK3CUKW&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;슬랙 앱 배포 설정&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;OAuth &amp;#x26; Permissions 탭을 선택하고 chat:write, users:read, users:read.email Scopes를 추가합니다. 앱을 배포하고 나면 해당 스코프를 사용하는 이유를 작성해야
합니다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1346px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/321a2ebb91e486978ede46e41b6566d1/e19bc/2022-10-22-slack14.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAABYlAAAWJQFJUiTwAAACf0lEQVR42qVUaZPaMAzl//+0ftotyxnu0AGCQy47F4FXPS0Gdme222k98yI5lmU9SXav6zq0bQvKy+WC6/X6T+Cgj17TNJjNZnh5ecF6tcb/DHXIqIwx2O12Ko/HIw6HA06nk84J6kQcxzI/ip7AFDVOuYMR+zRNHw75sdbCOacySRJFURRqmGWZgjptqBd5gcI6gdV5WZYfHYZhiCAIsFgssFwuVRLr9Rrz+Vz1cBOq3Wq1UhluNrpOfbvdqlOy7V26C2xhlVKapMjzHAkpCVVGerxR8sl/LpzXvdQIWeHhYIh+v4/RaKSSzrjIgnHdd8Gfxvl8VnulbEyMSCJhND4yQqOW6AifJ98iX1dZKBdFLslN0coJz5tc6dQxI/Tj2z7shH8sLRBlJVpx/uyQUTGnhK9wczv0edCe6/cqmyRHdMpgXYmmrlELGJUvCvuSPRhFkTqn0/pm58H/zKM6jGODw36nmz2Yv6aVopzbe2EIOquqSqOn9OD/B2XbYp/KQtt9zKHQ4MlZmqnOhiZttslnyrwId8qZbEqzHJUUoWlq3exp+GvnbwypMyKuM0pK2j8ilELsTIZfUYJQEGf2nkN/5ehwv99rTumQOeV/2vkUMH83yh1cfUZRtcjLFlXzaOBUHGzlasXSm4zC5453/nMr8fA75USoxYxGDHPZcJJ8JKIbScM+FsqyZov3/Pk28g3PqL2zd4eSNzcfw87GcIup6BPYYIRK9GoZoFnN4DZLfVnsDSyAl/6VYuTq8LqcIhv9xGE8wGEg93gyRLVd4yjz3dsrzHSE7vUHif3lA5sYNMEQpaAKBiinQ5zlkHI6gJu8oRR0Mr/cXpWv4F+b3wVVBzS428OGAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack14&quot;
        title=&quot;&quot;
        src=&quot;/static/321a2ebb91e486978ede46e41b6566d1/e19bc/2022-10-22-slack14.png&quot;
        srcset=&quot;/static/321a2ebb91e486978ede46e41b6566d1/0eb09/2022-10-22-slack14.png 500w,
/static/321a2ebb91e486978ede46e41b6566d1/1263b/2022-10-22-slack14.png 1000w,
/static/321a2ebb91e486978ede46e41b6566d1/e19bc/2022-10-22-slack14.png 1346w&quot;
        sizes=&quot;(max-width: 1346px) 100vw, 1346px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;초기 화면에서 Distribute App을 클릭하고 안내 사항을 체크한 후 Activate Public Distribution을 눌러 앱을 배포합니다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c55528034a7c325d2f8055b9d586ef0f/f97d7/2022-10-22-slack15.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAAB1ElEQVR42oVSS2/TYBD0r+POhSM3DqinSCi0KlIhcRt+GRfEhVJESohNnMTP+IHfz+nuFhtDImFptPbn/WZnZ1d5+uI1nr28xJPnE5xNr6DOVUwmrzCdXuD84pLiOcU3mKsLqNcLiY+4wWx+jau3M8G7mYqbxXsoH74a+LSy8VEPYUUl6rLAZrPBbreDaZpwHAeu66JpGtR1LbFtGS1OPYpjbuE7FrqmRFWVKMsSWZYhTVMkSSoxz3MhqqpKSHswKef+QQ5leX8P07IQJwmK32S2bcPzPFHGMAwDURSJgq7rJIZZgpy6qcoKRVEMUHRdh0WE/MHqWNF+v5d2D4fDoOzfFtuulXO+8xfhdrvFer0WUk5ISGnvIZ+xUib2fX/w06NvPmeyI0K+zIjjePCPi/BFJh97xOrH76z+pEKNFLKK3nRO7gv87xmTPRKS4T8NmnQUoybClFRxEZN8NEh54Afil6wLoRlWp6X8+nTLPNWMVoPXhtvUaFA6F6J/mqaJn1xkZ3uwvUDywzCUiR8Rfrm9xY/VSpSV7Iltwv/+jbBEZu2RkF9JEosFWV4ICaOf+lHL7sGH7frIeIGphYoWPbj7jF/LO3SBB966ji6PicbgpR/jAW5XfotHopZzAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack15&quot;
        title=&quot;&quot;
        src=&quot;/static/c55528034a7c325d2f8055b9d586ef0f/f97d7/2022-10-22-slack15.png&quot;
        srcset=&quot;/static/c55528034a7c325d2f8055b9d586ef0f/0eb09/2022-10-22-slack15.png 500w,
/static/c55528034a7c325d2f8055b9d586ef0f/1263b/2022-10-22-slack15.png 1000w,
/static/c55528034a7c325d2f8055b9d586ef0f/f97d7/2022-10-22-slack15.png 2000w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1402px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ee14e5adecd0861a2b96b34d40f1b632/23039/2022-10-22-slack16.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 91.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAABYlAAAWJQFJUiTwAAACn0lEQVR42n1T227TQBDt7yHxiHjgk5B4QBU/gHiAPoBAPMEPACqoaptr49zseH23E9/ixM5hZhybtFhddTq7E8/smTlnz4IgQL/fx93dHUajEXq3PQyHQ/R6PYkPBgNcX1/j6uoKi8UCzTocDq1v9rvdDmdRFEmipmnix+MxyrLkT1FVVWscq8qqTX5YtC2YpikM3YBpmjAMQ8yyXSjXh+WQkfepC754s9kgDEOx4Bjjved5iOMY+/0eZ1maQZkKlmVBKTIqvLIcmE4I0w3IQqw3MTK6mC9PkgTb7bZtla2sauSCkP+lx4/5FkaRUlLGxjE6c2y9XotnVHmeo2tJQS7C89OXurSrlJLhz+cLzGbzezN7uJrfJr6FKI1R7qhlhu/7/r25sGdEjLo6tsN2upfzoZKCbrJBVmyxF1KSVBAtl0tCNYeu61itVnBd954suowv4LWMiJQ8o4L7mmVmiVExUtt24NKZCzqOQwQUbXJjzbmWF2BEPpJtXrPMrDEiTnbJbIsYpzkqZYrxSLqQQv7qGXpZjHxX1C0zM47jCkJF3iKpuH5IMrLheCQd24NJMmI5KblItZo1V3XMcz0R/Y4RFjRMTZsQozOMpwsMNWJYNzGd6xitQkwNW86sBP5Gm2gy69l0VhvFWMPcrrScFyWhCIhlnqGHdRQen1kpgi2rUs4Se8Dyv9ZPdGjHB0zsLfKUnlW0/k/EPIqGMPaHqpvxtmBOdPvEKsNmYjiJWed9lmWPSqeRjU6ySRrZ5JTkExmh54sPaMC+40ER8/yKeHW1eipsO45q2TDC79otnr57hecfzvHs/Wu8uHiDJ29f4vzHt7rYI0+v8y3PfAefbn7hS/8Sn3uX+Dr4jY83P/HHmMq0mZRTUXdZQ1hRFPgLPARd5uoVi9EAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack16&quot;
        title=&quot;&quot;
        src=&quot;/static/ee14e5adecd0861a2b96b34d40f1b632/23039/2022-10-22-slack16.png&quot;
        srcset=&quot;/static/ee14e5adecd0861a2b96b34d40f1b632/0eb09/2022-10-22-slack16.png 500w,
/static/ee14e5adecd0861a2b96b34d40f1b632/1263b/2022-10-22-slack16.png 1000w,
/static/ee14e5adecd0861a2b96b34d40f1b632/23039/2022-10-22-slack16.png 1402w&quot;
        sizes=&quot;(max-width: 1402px) 100vw, 1402px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;배포가 완료되면 Add to Slack 버튼을 받을 수 있습니다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1468px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/80da0a6ef8e3cbe38f1d6b7979a01017/5040b/2022-10-22-slack17.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB30lEQVR42p1TaY/qMAzkzyL4zA8FpF3OcogrbZOeFMpZZjNmU7H73pPQqzSyk7j2jJ00NpsNer0exuMxBoOB+J8fn+j3+1itVjidTijL8m00tNbwPA+j0QjL5RKLxUIs93zfx/1+x/V6FbyTvHG5XJClGfI8R5qmAvpxHCNJEiilwKL0mfxf3+PxwPl8fiak7OnUw3w2x2g4xNBKn808zLwplos5JpOJMD4ej2A8f3wF9+qElJJlGYLARxil2GqLMMbKN9jR1wnivLByS+z3e4mlJaiEoE/2TCwMoygWlsr2TPkBQm1Q2Yr36glWf+cThrfbDSaybNQWaqdgbDLu/c/37KGVXNre5FYKG09J7BVlOFsURW0dDodDPTyeuV6K5KI4IAxDKz0ScNLGGPGDIBDLNcGCPM++CfCMt4DFOQ9JyI31ei3W3TvK/g3eQxZ+BYsw/seUSZ1MmJAB9HmpeQcJrrnf7XbRbrfRarXENptNdDode+Wm9ZX6nnKEjWUY2CS77U6kVFX1B1OuX1+Fezk/GLqEKtDwdQxtImk4K7qfCK7dntjj84yxtPWUmZ1T8kMDFdqhxAlSy5CNf206/b+9FK4Zy0K1ZB7k8gJyAX+m5STdNNlnMmH8bzDGnX0BFU10Z6UqLdsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack17&quot;
        title=&quot;&quot;
        src=&quot;/static/80da0a6ef8e3cbe38f1d6b7979a01017/5040b/2022-10-22-slack17.png&quot;
        srcset=&quot;/static/80da0a6ef8e3cbe38f1d6b7979a01017/0eb09/2022-10-22-slack17.png 500w,
/static/80da0a6ef8e3cbe38f1d6b7979a01017/1263b/2022-10-22-slack17.png 1000w,
/static/80da0a6ef8e3cbe38f1d6b7979a01017/5040b/2022-10-22-slack17.png 1468w&quot;
        sizes=&quot;(max-width: 1468px) 100vw, 1468px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;슬랙 알림 보내기&lt;/h2&gt;
&lt;p&gt;이제 사용자가 로그인할 때 받았던 User Id와(channel) 사용자 워크스페이스의 봇 엑세스 토큰이 있으면 DM을 보낼 수 있습니다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 616px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4a44662a8f92fe025eae9d54a50e904e/274fb/2022-10-22-slack18.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 35.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABcklEQVR42o2R0W7aQBBF/e995YF/SH8gEKlRUxSJSJWaklZtDYmjyASw11Bs1sTG3vUane66JM+xdDxreXTn7h2vqiryPMcY09Ufd3cEQYDv+8ymM+I4Ru4kh8OBsiwpiqKrTdN0aK1tNejT2XOvXq9Hv98nSRKGF0POB0M+X10xGo3wp1MiIaywIIoiwjBks9lQWyNO2BmqSkld1yil8LDPeDzm7OMZ+/yFcjpHP8dWXBA8PpBHguo+RCxXfLu95btlMpkwOB9wYYd/GY3ZTj5QRZcoA55Tbu112yMoGbNbPRM8CR4XltWGhdgSLtb8zXL2+5wsy7po0jRFyh3SmjjIOfV+idItnrPp7Ff6SJv8Il8/cTlL+fqQ8Onnkmt/xc29YL6WHFvzlp3L/C3HpkXZHJWq/wsqd39zRIvflj8YK7oWdhlZSmtFTKO7wGs33Pa+ok7f6hWXoWvssBOaIkUXGdU2stt86Tbr/il16nkH/wDhEQp0f3brSwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 22 slack18&quot;
        title=&quot;&quot;
        src=&quot;/static/4a44662a8f92fe025eae9d54a50e904e/274fb/2022-10-22-slack18.png&quot;
        srcset=&quot;/static/4a44662a8f92fe025eae9d54a50e904e/0eb09/2022-10-22-slack18.png 500w,
/static/4a44662a8f92fe025eae9d54a50e904e/274fb/2022-10-22-slack18.png 616w&quot;
        sizes=&quot;(max-width: 616px) 100vw, 616px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://api.slack.com/methods/chat.postMessage&quot;&gt;Slack Chat PostMessage Docs&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;http&quot;&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;POST https://slack.com/api/openid.connect.token
&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;application/x-www-form-urlencoded;charset=UTF-8&lt;/span&gt;&lt;/span&gt;
or Content-Type: application/json;charset=UTF-8
&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Authorization&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;Bearer {Access Token} =&gt; BotToken&lt;/span&gt;&lt;/span&gt;

{
&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;channel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;&quot;U0R7JM&quot;, # userID 또는 ChannelID 입력&lt;/span&gt;&lt;/span&gt;

1개 이상 필수
&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;&quot;Hello World!&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;blocks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
attachments:
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;http&quot;&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;application/json;charset=UTF-8&lt;/span&gt;&lt;/span&gt;

{
    &quot;ok&quot;: true,
    &quot;channel&quot;: &quot;C123456&quot;,
    &quot;message&quot;: {
        &quot;text&quot;: &quot;Here&apos;s a message for you&quot;,
        &quot;username&quot;: &quot;ecto1&quot;,
        &quot;bot_id&quot;: &quot;B123456&quot;,
        &quot;attachments&quot;: [
            {
                &quot;text&quot;: &quot;This is an attachment&quot;,
                &quot;id&quot;: 1,
                &quot;fallback&quot;: &quot;This is an attachment&apos;s fallback&quot;
            }
        ]
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[JPA Hibernate 프록시 제대로 알고 쓰기]]></title><description><![CDATA[JPA…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-10-17-jpa-hibernate-proxy/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-10-17-jpa-hibernate-proxy/</guid><pubDate>Mon, 17 Oct 2022 13:10:00 GMT</pubDate><content:encoded>&lt;p&gt;JPA를 사용할 때 장점이 뭘까요? 많은 장점들이 있겠지만, 그 중 하나로 객체 그래프를 통해 연관관계를 탐색할 수 있다는 것을 꼽을 수 있습니다. 하지만 엔티티들은 데이터베이스에 저장되어 있기 때문에 한 객체 조회 시 연관되어 있는 엔티티들을 모두 조회하는 것 보다는 필요한 연관관계만 조회해 오는 것이 효과적입니다. 이런 상황을 위해 JPA는 지연 로딩이라는 방식을 지원하는데요, 그 중에서도 우리가 일반적으로 가장 많이 사용하는 JPA 구현체인 하이버네이트(Hibernate)는 프록시 객체를 통해 지연 로딩을 구현하고 있습니다. JPA 프록시는 지연 로딩이 가능하게 해주는 고마운 기술이지만 잘못 사용하면 예상하지 못한 예외들을 만나게 될 수 있습니다. 오늘 이 시간에는 JPA를 사용하며 만나는 예외에 당황하지 않기 위해 JPA 프록시에 대해 알아보는 시간을 갖도록 하겠습니다.&lt;/p&gt;
&lt;h1&gt;JPA 프록시(Proxy) 란?&lt;/h1&gt;
&lt;p&gt;JPA 프록시에 대해 알아보기에 앞서, 프록시란 무엇일까요? 프록시는 ‘대신하다‘라는 의미를 가지고 있는 단어인데요, 동작을 대신해주는 가짜 객체의 개념이라고 생각하시면 되겠습니다. 프록시는 JPA 하이버네이트에만 존재하는 개념은 아닙니다. 대표적으로 스프링에서도 초기화를 지연시켜준다든가, 트랜잭션을 적용한다든가 하는 부가 기능을 추가할 때 프록시 기술을 사용하는 것을 생각하면 이해하기 쉽습니다.&lt;/p&gt;
&lt;p&gt;위에서 설명했듯이 하이버네이트는 지연 로딩을 구현하기 위해 프록시를 사용합니다. (JPA 명세에는 지연 로딩의 구현 방법은 나와있지 않습니다. 즉, 구현체에 지연 로딩의 방법을 위임합니다. 때문에 이후로 설명하는 모든 내용은 하이버네이트 기준입니다.) 지연 로딩을 하려면 연관된 엔티티의 실제 데이터가 필요할 때 까지 조회를 미뤄야 합니다. 그렇다고 해당 엔티티를 연관관계로 가지고 있는 엔티티의 필드에 null 값을 넣어 둘 수는 없겠죠? 하이버네이트는 지연 로딩을 사용하는 연관관계 자리에 프록시 객체를 주입하여 실제 객체가 들어있는 것처럼 동작하도록 합니다. 덕분에 우리는 연관관계 자리에 프록시 객체가 들어있든 실제 객체가 들어있든 신경쓰지 않고 사용할 수 있습니다.
참고로 프록시 객체는 지연 로딩을 사용하는 것 외에도 &lt;code class=&quot;language-text&quot;&gt;em.getReference&lt;/code&gt;를 호출하여 프록시를 호출할 수도 있습니다.&lt;/p&gt;
&lt;h1&gt;프록시는 실제 객체의 상속본이다&lt;/h1&gt;
&lt;p&gt;프록시가 어떻게 실제 객체처럼 동작을 할 수 있을까요? 이는 프록시가 실제 객체를 상속한 타입을 가지고 있기 때문입니다. 그리고 프록시 객체는 실제 객체에 대한 참조를 보관하여, 프록시 객체의 메서드를 호출했을 때 실제 객체의 메서드를 호출합니다. 그래서 실제 객체 타입 자리에 들어가도 문제 없이 사용할 수 있는 것이죠.
프록시 객체가 실제 객체의 상속본인 것은 JPA 엔티티 생성의 중요한 규칙을 만들어내기도 했습니다. 바로 ‘기본 생성자는 최소 protected 접근 제한자를 가져야 한다.’ 는 규칙과 ‘엔티티 클래스는 final로 정의할 수 없다. 입니다.’ 만약 기본 생성자가 private이면 프록시 생성 시 &lt;code class=&quot;language-text&quot;&gt;super&lt;/code&gt;를 호출할 수 없을 것이고, 엔티티를 final로 선언한다면 상속이 불가능하게 되겠죠?&lt;/p&gt;
&lt;h1&gt;프록시의 초기화&lt;/h1&gt;
&lt;p&gt;그런데 최초 지연 로딩 시점에는 당연히 참조 값이 없습니다. 때문에 실제 객체의 메서드를 호출할 필요가 있을 때 데이터베이스를 조회해서 참조 값을 채우게 되는데요, 이를 프록시 객체를 초기화한다고 합니다. 앞서 말씀드렸듯이 실제 객체의 메서드를 호출할 필요가 있을 때 select 쿼리를 실행하여 실제 객체를 데이터베이스에서 조회해오고, 참조 값을 저장하게 됩니다. 예를 들어볼까요? 다음과 같은 두 엔티티가 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@NoArgsConstructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;access &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AccessLevel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PROTECTED&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Getter&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Column&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@JoinColumn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;team_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ManyToOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fetch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FetchType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;LAZY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; team&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; team&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;team &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; team&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@NoArgsConstructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;access &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AccessLevel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PROTECTED&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Getter&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Column&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(간략한 코드를 위해 Lombok을 사용한 점 이해 부탁드립니다)
Member의 연관관계 Team은 지연 로딩으로 설정되어 있기 때문에, Member를 조회해오게 되면 team 필드 자리에는 프록시가 들어가 있습니다. 이 때 프록시 Team의 getName 메서드를 호출하게 되면 select 쿼리가 실행되고 프록시가 초기화됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;========&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; team &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTeam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;team&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 이 시점에 프록시 초기화!&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/ohzzi/post/9053cb2c-4838-4698-8cdc-4230e54bf7ad/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;단, 이 때 프록시가 실제 객체를 참조하게 되는 것이지 프록시가 실제 객체로 바뀌지는 않는다는 점을 주의하셔야합니다.
참고로 프록시를 초기화하지 못하는 경우도 있습니다. 프록시의 초기화는 영속성 컨텍스트의 도움을 받습니다. 때문에 영속성 컨텍스트의 관리를 받지 못하는 상황, 즉 준영속 상태의 프록시를 초기화 한다거나 OSIV 옵션이 꺼져 있는 상황에(기본값으로는 켜져 있습니다.) 트랜잭션 바깥에서 프록시를 초기화 하려 하는 경우 &lt;code class=&quot;language-text&quot;&gt;LazyInitializationException&lt;/code&gt;을 만나게 되실 수 있습니다. 때문에 프록시를 초기화할 때는 반드시 프록시가 &lt;code class=&quot;language-text&quot;&gt;영속 상태&lt;/code&gt;여야 한다는 점에 주의해주세요!&lt;/p&gt;
&lt;h2&gt;id를 조회할 때는 프록시가 초기화되지 않는다&lt;/h2&gt;
&lt;p&gt;그런데 getName이 아닌 getId를 호출할 때도 초기화될까요? 테스트해보도록 하겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;========&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; team &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTeam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;team&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/ohzzi/post/05499387-2b31-4fcf-82c7-53905d1bcd4d/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;id를 그대로 출력하고 select 쿼리는 발생하지 않습니다! 우리는 여기서 식별자를 조회할 때는 프록시를 초기화하지 않는다 라는 사실을 유추할 수 있습니다.
좀 더 자세히 들어가볼까요? 프록시 초기화 과정은 프록시 객체 내부의 &lt;code class=&quot;language-text&quot;&gt;ByteBuddyInterceptor&lt;/code&gt;라는 클래스가, 정확히는 상위의 추상 클래스인 &lt;code class=&quot;language-text&quot;&gt;AbstractLazyInitializer&lt;/code&gt;가 담당하게 됩니다. AbstractLazyInitializer의 초기화 로직을 확인해보도록 하겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Serializable&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getIdentifier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isUninitialized&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isInitializeProxyWhenAccessingIdentifier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;엔티티의 식별자를 조회하는 메서드를 호출하게 되면 최종적으로는 AbstractLazyInitializer의 getIdentifier 메서드를 호출하게 되는데요, 이 때 if 문 바깥쪽을 보면 그대로 id를 반환하는 것을 알 수 있습니다. AbstractLazyInitializer가 id 값을 가지고 있기 때문에 가능한 일입니다. if문 안쪽은 초기화되지 않았을 것과, 식별자 접근시 프록시를 초기화 하는 옵션을 켰을 때 true가 되는 조건문인데요, hibernate.jpa.compliance.proxy 라는 설정값을 true로 해 줘야만 해당 옵션이 켜집니다. 기본 설정값은 false이기 때문에 일반적으로는 id의 getter를 호출하게 되면 프록시가 초기화되지 않는 것이죠.
그런데 주의하실 점이 있습니다. 제가 호출한 메서드는 &lt;code class=&quot;language-text&quot;&gt;getId&lt;/code&gt;로, 자바 빈 규약에 맞는 get + 필드명 이기 때문에 getIdentifier 메서드가 호출된 것입니다. 만약 &lt;code class=&quot;language-text&quot;&gt;findId&lt;/code&gt;와 같이 getter에 대한 자바 빈 규약을 만족시키지 못하거나, &lt;code class=&quot;language-text&quot;&gt;getTeamId&lt;/code&gt;와 같이 식별자 이름과 매칭되지 않을 경우 getIdentifier 메서드를 호출하지 못하고 프록시가 초기화되게 됩니다.
또하나 주의하실 점은, id값은 프록시가 아니라 프록시 내부의 인터셉터에 들어있고, 프록시 객체가 가진 필드값들은 모두 null이라는 것입니다. 일반적으로는 필드로 꺼내 쓰기 때문에 상관 없겠지만, 만약 필드가 public이어서 바로 접근한다면 null에 접근하게 되는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/ohzzi/post/765c9b32-9cec-4cbd-aea8-79cdd1fe17c4/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h1&gt;프록시의 equals를 재정의 할 때는 instanceof와 getId를 사용할 것&lt;/h1&gt;
&lt;p&gt;우리는 흔히 equals / hashcode 메서드를 만들 때 인텔리제이의 기능을 빌리곤 합니다. 그리고 보통 JPA 엔티티의 equals는 id값만 비교해서 재정의하게 되죠. 결국 다음과 같은 메서드가 만들어질 것입니다!&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; o&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; o&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;o &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; o&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; team &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; o&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; team&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 equals를 재정의하게 된다면, 프록시 객체의 equals를 호출했을 때 문제가 생기게 됩니다! 다음 테스트 코드를 통해 확인해보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; team &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTeam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; sameTeam &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTeam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;team&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sameTeam&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/ohzzi/post/bec03142-1fc3-4b80-bc4a-b5bdbf404371/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;주소값도 같은 객체인데 equals 메서드를 통해 동등성을 비교하는 isEqualTo를 통과하지 못합니다. 주소값이 같기 때문에 &lt;code class=&quot;language-text&quot;&gt;==&lt;/code&gt;로 비교하면 같은 객체라고 하는데, 정작 동등성은 만족시키지 못하는 것이죠. 어떻게 된 일일까요? 답은 &lt;code class=&quot;language-text&quot;&gt;getClass&lt;/code&gt;에 있습니다.
sameTeam을 인수로 하여 team의 equals를 호출합니다. getId가 아닌 메서드를 호출했으니 프록시 team이 초기화되고 프록시 team 내부의 실제 객체의 equals를 호출합니다. 그렇다면 getClass는 어떻게 될까요? equals 안에서 호출되는 getClass의 결과는 Team.class인 반면, 인수로 전달받은 sameTeam의 getClass의 결과로는 프록시 타입이 나오게 되어 equals 결과값이 false가 나오게 됩니다.&lt;/p&gt;
&lt;p&gt;때문에 일반적으로 equals를 비교하던 것처럼(물론 애초에 리스코프 치환 원칙을 위반하는 코드이기는 합니다!) getClass를 비교하는 것으로 equals를 작성해서는 안되고, &lt;code class=&quot;language-text&quot;&gt;o instanceof Team&lt;/code&gt;과 같은 형태로 코드를 작성해주셔야 합니다! 프록시는 원본 객체의 상속이라고 했었죠? 때문에 instanceof를 통과하게 됩니다.
그리고 또 하나 수정해줘야 하는 부분이 있습니다. 일반적인 객체의 상속관계라면 문제가 없겠지만, 위에서 설명드렸듯이 &lt;code class=&quot;language-text&quot;&gt;프록시의 필드값은 모두 null&lt;/code&gt;입니다. 그래서 team.id는 사실 null인 것이죠. 그래서 &lt;code class=&quot;language-text&quot;&gt;Objects.equals(id, team.id)&lt;/code&gt;는 id값과 null을 비교하게 되어 false가 반환되게 됩니다. 하지만 getId는 실제 id를 반환하므로, 우리는 equals 코드를 다음과 같이 고칠 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; o&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; o&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;o &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; team &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; o&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; team&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;프록시의 equals 부분은 간과하고 넘어가기 쉬운 부분이면서, 많은 버그를 발생시키는 부분이니 꼭 주의해주시길 바랍니다 :)&lt;/p&gt;
&lt;h1&gt;프록시가 생성되면 영속성 컨텍스트는 프록시를 반환한다&lt;/h1&gt;
&lt;p&gt;프록시로 만들어진 엔티티에 대한 조회가 들어온다면, 영속성 컨텍스트는 실제 엔티티와 프록시 객체 중 어떤 객체를 반환해야 할까요?
영속성 컨텍스트의 특징으로 &lt;code class=&quot;language-text&quot;&gt;동일성 보장&lt;/code&gt;이 있습니다. 그런데 위에서 한 번 프록시로 만들어진 객체는 프록시 초기화를 하더라도 실제 엔티티로 변환되지 않고 참조값만 가지게 된다고 언급했습니다. 때문에 동일성을 보장해주기 위해서 한 트랜잭션 내에서 최초 생성이 프록시로 된 엔티티는 이후 초기화 여부에 상관 없이 영속성 컨텍스트가 무조건 같은 프록시 객체를 반환해주게 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; team &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; teamRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;team&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isInstanceOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HibernateProxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/ohzzi/post/204c244d-3647-4072-bf40-26f024de360e/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;반대로 영속성 컨텍스트에 최초로 저장될 때 실제 엔티티로 저장될 경우, 이후로는 프록시가 아닌 실제 엔티티가 반환됩니다. 이는 지연 로딩으로 인해 프록시가 들어갈 자리에도 마찬가지로, 이미 영속성 컨텍스트에 엔티티가 저장된 상태에서 해당 엔티티를 FetchType.LAZY로 가지고 있는 엔티티를 조회하는 경우라면, 프록시 객체를 반환하는 대신 실제 엔티티를 반환해주게 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;========&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; team &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; teamRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; memberRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTeam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isNotInstanceOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HibernateProxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/ohzzi/post/ef85f291-c9fd-463b-9a54-7cc59d9b9255/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;p&gt;JPA Hibernate의 프록시는 지연 로딩을 통해 불필요한 데이터 조회를 방지해주는 고마운 기술입니다. 하지만 그만큼 기술에 대한 이해를 하고 사용하지 못하면 예상치 못한 결과로 오히려 버그와 씨름하느라 더 많은 자원을 낭비할수도 있는 기술입니다. 대부분의 기술이 버그와 부작용을 막으려면 그 기술에 대해 깊게 이해해야 하겠지만, 제 생각에 프록시는 눈으로 보이는 부분이 많지 않기 때문에 더더욱 그렇습니다. 물론 이 글을 끝까지 정독해주신 여러분이라면 큰 문제 없이 프록시를 다루게 되실 수 있을 겁니다. 프록시에 대해서 제대로 알고, 편하고 안전하게 JPA를 사용해보도록 합시다!&lt;/p&gt;
&lt;h1&gt;참고 자료&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;자바 ORM 표준 JPA 프로그래밍 (김영한 저)&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[함수 오버로딩]]></title><description><![CDATA[서언 TypeScript에는 Function Overloading 기능이 있습니다. 이번에 Function Overloading…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-10-15-function-overloading/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-10-15-function-overloading/</guid><pubDate>Sat, 15 Oct 2022 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;서언&lt;/h1&gt;
&lt;p&gt;TypeScript에는 &lt;strong&gt;Function Overloading&lt;/strong&gt; 기능이 있습니다. 이번에 Function Overloading의 강력함을 깨닫고, 이를 공유하고자 아티클을 씁니다. 함수 오버로딩이 왜 필요한지 이해하고, &lt;code class=&quot;language-text&quot;&gt;useRef&lt;/code&gt; 예시와 저희 프로젝트에 적용해본 예시를 설명드리겠습니다.&lt;/p&gt;
&lt;br/&gt;
&lt;h1&gt;타입스크립트에서 함수 오버로딩이 왜 필요한가?&lt;/h1&gt;
&lt;p&gt;자바스크립트는 인자의 개수와 타입 제한이 없습니다. 코드 예시를 보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;c&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; b &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 6&lt;/span&gt;
&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 3&lt;/span&gt;
&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1&lt;/span&gt;

&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;시&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;지&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;프&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 시지프&lt;/span&gt;
&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;시&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;짚&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 시짚&lt;/span&gt;
&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;짚&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 짚&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;add 함수만 보고는 숫자를 더하는 함수인지, 문자를 더하는 함수인지 역할을 파악하기 어렵습니다. 가령 저는 숫자를 더하기 위해 만들었지만, 다른 팀원은 문자를 합치기 위해 사용하고 있다면 소통이 원활히 이루어졌다고 말하기 어려울 것입니다. 이때 타입스크립트가 강력한 역할을 합니다. 타입스크립트가 즉, JSDoc과 같이 코드 명세 역할을 하여 더 나은 협업을 이끌 수 있습니다.&lt;/p&gt;
&lt;p&gt;위 코드에 타입을 얹어봅시다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; c&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;c&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; b &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// Operator &apos;+&apos; cannot be applied to types &apos;string | number&apos; and &apos;string | number&apos;.&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;가장 먼저 떠오르는 것은 위와 같이 타이핑하는 것인데요. 공교롭게도 이러한 타이핑은 에러를 발생시킵니다. string | number에 ’+’ 연산을 할 수 없다는 것입니다. 다른 방식의 해결이 필요합니다.&lt;/p&gt;
&lt;p&gt;저는 타입스크립트에서 &lt;strong&gt;무분별한 &amp;#x3C;유니온&gt; 사용을 경계&lt;/strong&gt;해야 한다고 생각합니다. 무분별한 유니온은 가짓수 확장을 곱절로 늘립니다. 위 케이스를 보면, add 함수가 가질 수 있는 a, b, c, ReturnType의 가짓수를 고려하면 최대 2⁴ = 16개 입니다.&lt;/p&gt;
&lt;p&gt;과연 add 함수가 16가지를 모두 커버하는 것이 좋은 타이핑일까요? 아닐 것입니다.&lt;/p&gt;
&lt;p&gt;우리가 저 함수를 정의한 의도는 (1) 숫자 끼리 연산하여 숫자를 반환하거나, (2) 문자끼리 연산하여 문자를 반환하는, 2가지일 것입니다.&lt;/p&gt;
&lt;p&gt;이런 &lt;strong&gt;16가지 함수 타입을 2가지로 좁혀주는 방법이 바로 함수 오버로드(function overload)입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h1&gt;함수 오버로드는 어떻게 사용하는가?&lt;/h1&gt;
&lt;p&gt;함수 오버로드는 &lt;strong&gt;오버로드 시그니처(overload signature)&lt;/strong&gt;를 활용합니다.&lt;/p&gt;
&lt;p&gt;아래와 같이 &lt;strong&gt;overload signature&lt;/strong&gt;와 &lt;strong&gt;implementation signature&lt;/strong&gt;를 정의합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; c&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// (1) overload signature&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; c&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// (2) overload signature&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; c&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// (3) implementation signature&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;c&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; b &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;add 함수는 (1) 또는 (2) 함수 타입으로 실현됩니다. 이는 args, return의 타입에 따라 동적으로 정해집니다.&lt;/p&gt;
&lt;p&gt;숫자를 넣으면 (1) 오버로드 시그니처로 실현되고,&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1116px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/9a50e583f8e692a2f61d1cb6ad4eb7f6/6f641/1_code.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 5.6000000000000005%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAABCAYAAADeko4lAAAACXBIWXMAABYlAAAWJQFJUiTwAAAATElEQVR42h3ISQ5AQBQAUcfQvyV2YvgI6QmN+5+qDKvKq0I0Y/RC+oTG+21AXEbGRNV6zLJh1gNpHDIErD+x3fd3zJyoNVKO4bedEg+p8R4Q0EtOHwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;코드&quot; title=&quot;&quot; src=&quot;/static/9a50e583f8e692a2f61d1cb6ad4eb7f6/6f641/1_code.png&quot; srcset=&quot;/static/9a50e583f8e692a2f61d1cb6ad4eb7f6/0eb09/1_code.png 500w,
/static/9a50e583f8e692a2f61d1cb6ad4eb7f6/1263b/1_code.png 1000w,
/static/9a50e583f8e692a2f61d1cb6ad4eb7f6/6f641/1_code.png 1116w&quot; sizes=&quot;(max-width: 1116px) 100vw, 1116px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;
&lt;br/&gt;
문자를 넣으면 (2) 오버로드 시그니처로 실현됩니다.
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;시&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;지&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;프&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1118px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/81c104db7b7570bc19e51ec729040e59/99dcf/2_code.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 5.6000000000000005%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAABCAYAAADeko4lAAAACXBIWXMAABYlAAAWJQFJUiTwAAAASUlEQVR42hXIQRZAIBRAUTupZGDCV34qctr/qp7M7rnTHDtWXrxUtHXcnjFnwevD8jtkrN74YbslbBofCkYunFbWWDHHcG44SXzPrR7wa/PPZQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;코드&quot; title=&quot;&quot; src=&quot;/static/81c104db7b7570bc19e51ec729040e59/99dcf/2_code.png&quot; srcset=&quot;/static/81c104db7b7570bc19e51ec729040e59/0eb09/2_code.png 500w,
/static/81c104db7b7570bc19e51ec729040e59/1263b/2_code.png 1000w,
/static/81c104db7b7570bc19e51ec729040e59/99dcf/2_code.png 1118w&quot; sizes=&quot;(max-width: 1118px) 100vw, 1118px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;이때 주의할 점은, &lt;strong&gt;(3) 구현부 타입은 절대 실현되지 않습니다.&lt;/strong&gt; &lt;strong&gt;구현부는 모든 오버로드 시그니처를 포괄하는 타입일 뿐입니다.&lt;/strong&gt; add 함수가 실제로 받아올 수 있는 모든 인자타입과 모든 ReturnType을 적어줍니다.&lt;/p&gt;
&lt;p&gt;아래와 같이 string | number로 타이핑해도 되지만, 여전히 구현부에서 string | number에 대해 ’+’ 연산을 수행할 수 없으므로 에러가 발생합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; c&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;정리하자면, 실행 시그니처는 오버로드 시그니처의 타입을 포함하고 있어야 합니다.&lt;/p&gt;
&lt;br/&gt;
&lt;h1&gt;useRef를 통해 함수 오버로드 익히기&lt;/h1&gt;
&lt;p&gt;함수 오버로드를 공부한 후, 가장 밀접하게 다가오는 장점은 아마 &lt;strong&gt;.d.ts 파일을 읽어낼 수 있다는 것&lt;/strong&gt;입니다. 라이브러리 함수 중 다수가 함수 오버로드로 정의되어 있습니다. d.ts 파일을 보면, 하나의 타입만을 가지는 함수는 아마 드물 것입니다.&lt;/p&gt;
&lt;p&gt;가령 React 라이브러리의 index.d.ts를 들어가보면, useRef의 타입을 확인할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;useRef&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;initialValue&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MutableRefObject&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// (1)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;useRef&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;initialValue&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RefObject&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// (2)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;useRef&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MutableRefObject&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// (3)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;useRef는 3가지 오버로드 시그니처로 정의 되어 있습니다. 뭔가 비슷한 형태이지만, 어디에 null이나 undefined이 포함되어 있습니다.&lt;/p&gt;
&lt;p&gt;(2) 초기값에 null이 포함되는지, (3) 초기값이 비어있는지(undefined), (1) null도 undefined도 아닌 제네릭 T 타입인지에 따라 ReturnType이 달라집니다.&lt;/p&gt;
&lt;p&gt;MutableRefObject 또는 RefObject를 반환합니다.&lt;/p&gt;
&lt;p&gt;MutableRefObject는 말 그대로 변할 수 있는 Ref 객체이고, RefObject는 불변 Ref 객체입니다. d.ts를 읽어보면 특별한 것 없습니다.&lt;/p&gt;
&lt;p&gt;current가 불변(readonly)이냐 아니냐를 정의하는 인터페이스일 뿐입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MutableRefObject&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  current&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RefObject&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;readonly&lt;/span&gt; current&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;RefObject는 DOM에 ref를 걸어줄 때 사용됩니다. 그러므로 우리가 이 RefObject를 사용하려면 null 값을 넣어줘야하는 것입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; inputRef &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;useRef&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;HTMLInputElement&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// RefObject&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;useRef에 DOM이 아닌 일반 변수값을 지정하고 싶을 때에는 초기값을 명확히 넣어주거나, undefined으로 할당하면 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; numberRef &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;useRef&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// MutableRefObject&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; timerRef &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;useRef&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;NodeJS&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Timeout&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// MutableRefObject&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이처럼 함수 오버로딩을 이해하면, 라이브러리 제작자의 의도를 파악하고, 함수를 적재적소에 잘 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;다음은 직접 함수 오버로딩을 활용해본 경험을 공유하겠습니다.&lt;/p&gt;
&lt;br/&gt;
&lt;h1&gt;함수 오버로드로 undefined 또는 null을 제거하기&lt;/h1&gt;
&lt;p&gt;가상의 함수를 하나 정의해보겠습니다. 쿠폰 리스트를 받아서, 보낸 쿠폰만 필터링해서 반환하는 함수입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Coupon&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  isSent&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;filterSentCoupon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;coupons&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Coupon&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Coupon&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; coupons&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;coupon &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; coupon&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isSent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 함수가 실현될 수 있는 케이스는 몇 가지 일까요? 인자 타입 2가지와 리턴 타입 2가지를 조합하면 4가지 경우로 실현될 수 있습니다.&lt;/p&gt;
&lt;p&gt;하지만 실제로 이 함수는 2가지 케이스에 사용됩니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Coupon[] | undefined 이 들어오면, Coupon[] | undefined를 반환한다.&lt;/li&gt;
&lt;li&gt;Coupon[] 이 들어오면 Coupon[] 을 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;지금은 (2)의 경우에도 undefined일 가능성도 포함하여 반환합니다. 타입 내로잉이 얼마나 까다롭고 성가신 작업인데, Coupon[]을 넣었더니 undefined를 더해서 뱉어주다니, 이는 여간 나쁜 타이핑이 아닙니다.&lt;/p&gt;
&lt;p&gt;저희는 이를 함수 오버로드로 통해 해결했습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;filterSentCoupon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;coupons&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Coupon&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Coupon&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;filterSentCoupon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;coupons&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Coupon&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Coupon&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;filterSentCoupon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;coupons&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Coupon&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Coupon&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; coupons&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;coupon &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; coupon&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isSent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 Coupon[] 타입만 인자로 들어갈 경우, undefined을 더해서 뱉어주는 일이 없을 것입니다.&lt;/p&gt;
&lt;br/&gt;
&lt;h1&gt;마치며&lt;/h1&gt;
&lt;p&gt;함수 오버로딩은 제네릭이나 구별된 유니온에 비해 덜 주목 받는 것 같습니다. 함수의 타입을 제대로 정의하기 위해, 제네릭, 구별된 유니온 뿐만 아니라 함수 오버로딩도 필수적으로 이해해야 합니다. 이번 아티클을 통해, 라이브러리의 .d.ts 코드를 더 수월하게 읽고, 함수 타입을 좁혀나갈 수 있길 바랍니다.&lt;/p&gt;
&lt;h1&gt;참고&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads&quot;&gt;https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Spring에서 Server-Sent-Events 구현하기]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-10-11-server-sent-events/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-10-11-server-sent-events/</guid><pubDate>Tue, 11 Oct 2022 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;웹 애플리케이션을 개발하다보면 클라이언트의 요청이 없어도 서버에서 데이터를 전달해줘야 하는 경우가 있습니다.&lt;/p&gt;
&lt;p&gt;대표적으로 뉴스피드나 댓글 알림처럼 실시간으로 서버의 변경사항을 웹 브라우저에 갱신해줘야 하는 경우를 예로 들 수 있을 것 같습니다.&lt;/p&gt;
&lt;p&gt;제가 참여한 &lt;a href=&quot;https://github.com/woowacourse-teams/2022-gong-check&quot;&gt;프로젝트&lt;/a&gt;에서도 이와 비슷한 요구사항이 있었습니다.&lt;/p&gt;
&lt;p&gt;아래처럼 다른 클라이언트의 요청(체크리스트 버튼 클릭)으로 발생한 변경 사항을 해당 화면을 보고 있는 모든 클라이언트에게 전달해주어야 했는데요.&lt;/p&gt;
&lt;img src=&quot;/c6ed615dd12c22bcfaf4dc409fcfed6c/checklist.gif&quot; width=&quot;630&quot;&gt;
&lt;p&gt;전통적인 Client-Server 모델의 HTTP 통신에서는 이런 기능을 구현하기가 조금 난감합니다. 클라이언트의 요청이 있어야만 서버가 응답을 할 수 있기 때문이죠.&lt;/p&gt;
&lt;p&gt;HTTP를 기반으로 이 문제를 해결하려면 세 가지 방법을 고려해볼 수 있습니다.&lt;/p&gt;
&lt;h3&gt;Short Polling&lt;/h3&gt;
&lt;p&gt;클라이언트가 주기적으로 서버로 요청을 보내는 방법입니다. 일정 시간마다 서버에 요청을 보내 데이터가 갱신되었는지 확인하고 만약 갱신되었다면 데이터를 응답 받습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1256px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/fd0d860cab587b024f564f210056b29b/30373/polling.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 102.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAYAAABG1c6oAAAACXBIWXMAABYlAAAWJQFJUiTwAAACXUlEQVR42p2VC2/aQBCE+f9/sUipaIE0Kg227/2azpzthICrprU08gPv3O63e2aXc4bUWutKKaGUgr8dtVbEGPt5lWJ3MvPeY7/f43A4wDn3KUPFyUAxij0ej7OhVgkh9IeSDPXyZw1PpxPO5zMul0t/tltLXl/Q+TbDUhusKzBUzu3tufCUJU5J6b4bri8YazGOFkouxNoVU8VkMw5nVnBweB25aGkIaVapgI+ZVfm3hXZr+uM4svS4rE7hXZVIWmID6FBpUBOroHLIMKODNfajoYImGvphAJR+8DcKKK+vaNNA04Sm3xdBiMjc2TtDHeJgJnaYnDJLzbmSJUsPBT9+RpxeQme51Rw18mOGrHGcJnIMoA85zco6k+kwFbxcElzgrHHBKIZiHBUXME13GaqrYqgsOaGdV9OwS7xXyWUc+nUvm6odTUBknGUyDyV7pu2vV7pn9FZLZZaM2ZH3RaRltDZLnhkmTMYvZbYuXRvf8PRdY+Mx2G2G1t4Zah8aY+B87LM1m5JXLEgcjUiNVw83WJhfIzPSdi2d47TFUKsMwxXJGt6kXm4ls/D1CfHbAen5GWH/BfF84vUZlXu/xcBVI5KZthkGcgjMkh16Y9Sb0OoyqfM09Kl/KHljDtc9iZsd0tmS5TPnULK+/oHhhqE6ZTmH85zN+1hz5mhyGQqOL5FNK30Gu/Qem2+cmnlnmJa9nFOcc1OZq9Q0cmrOzNfabtaTt+tndx0xDeNjhpb8AuFqj+pjIPCrCp+XySzNiJ3zLA497601j4b6Fq4M/+VQozYZrv8p/3Pcxv0GXSVyXewRZEoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;polling&quot;
        title=&quot;&quot;
        src=&quot;/static/fd0d860cab587b024f564f210056b29b/30373/polling.png&quot;
        srcset=&quot;/static/fd0d860cab587b024f564f210056b29b/0eb09/polling.png 500w,
/static/fd0d860cab587b024f564f210056b29b/1263b/polling.png 1000w,
/static/fd0d860cab587b024f564f210056b29b/30373/polling.png 1256w&quot;
        sizes=&quot;(max-width: 1256px) 100vw, 1256px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;클라이언트와 서버 모두 구현이 단순하다는 것이 특징입니다. 서버가 요청에 대한 부담이 크지 않고 요청 주기를 넉넉하게 잡아도 될 정도로 실시간성이 중요하지 않다면 고려해 볼 만한 방법입니다.&lt;/p&gt;
&lt;h3&gt;Long Polling&lt;/h3&gt;
&lt;p&gt;요청을 보내고 서버에서 변경이 일어날 때까지 대기하는 방법입니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1298px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7e3a34a031897051c1c0e96ca2843ef4/11824/longpolling.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 101%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAABYlAAAWJQFJUiTwAAABwklEQVR42p1Ua4+CMBD0//9DE2OMhvvA4YtHSx90b6a0RjwQ75qMUthOd3em3YQQxBgjwzBEOOci1oa19rHOex/nfN5kwuPxKIfDQeq6jgH8+G5wU6WUbLdb2e/30rbtSMifvu+lLEvZ7XbSdV0M5kZrGZKwKAqpqio+PzIcy/SpBB8zXBu5ilw65+TaPAc0jUKAIABZG/YmYD7I6UvLsdCohH1GAnifKyAZy81jQqh1H58ZS1gXIkiue2YTJu96zNvOyO12i8S/CNnL9yMkTHvJdez9PwiX7ZPLXiUcsMCjFb5txF2vEtjk3Jdkn/v9jnbpmR5yFyjHRSFZx54r8VUJfIuBRTz9RhNj82BQbttBzGZelLZuQORHsgRvnTStE22CDGG55FlCpTQsIVAQp8eO0FCzPBtYR0FRF63kktKM7ZSJJWcbvdhGvxWAGZIsg/7X2sRTQnxGGEYTLx1FlkxwbTx6f8lwiZDuyA5ZJkwZedw+7nIRh9PQn05R5ZCcwJotiNhDm+w0FYXyU11agqAHEUz/Ofx7kAYoGngRsIEgNkjiOZEpYfKhrFxdcz1csI2aFWWCTwmp4totPXtdvDjgB3SkKbZDu11SAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;long_polling&quot;
        title=&quot;&quot;
        src=&quot;/static/7e3a34a031897051c1c0e96ca2843ef4/11824/longpolling.png&quot;
        srcset=&quot;/static/7e3a34a031897051c1c0e96ca2843ef4/0eb09/longpolling.png 500w,
/static/7e3a34a031897051c1c0e96ca2843ef4/1263b/longpolling.png 1000w,
/static/7e3a34a031897051c1c0e96ca2843ef4/11824/longpolling.png 1298w&quot;
        sizes=&quot;(max-width: 1298px) 100vw, 1298px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;실시간 메시지 전달이 중요하지만 서버의 상태가 빈번하게 변하지 않는 경우에 적합합니다. 서버로부터 응답을 받고 나면 다시 연결 요청을 하기 때문에, 상태가 빈번하게 바뀐다면 연결 요청도 늘어나게 됩니다.&lt;/p&gt;
&lt;h3&gt;Server-Sent Events(SSE)&lt;/h3&gt;
&lt;p&gt;서버와 한번 연결을 맺고나면 일정 시간동안 서버에서 변경이 발생할 때마다 데이터를 전송받는 방법입니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1260px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e9061ada0ae476d3fd73f9271d6d585d/58c38/sse.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 103.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAYAAABG1c6oAAAACXBIWXMAABYlAAAWJQFJUiTwAAABtklEQVR42pVU2W7CMBDM//8cL8AbEqoKKPSBI4lve7uz2CkRpiSWRr7Wk53xxk1KiWr41BATY5zMgcZaS1prGoaBlFJkjCHv/UdCnEPs7Xajy+VCfd/LWuOco+v1SqvVijabDWE+hxBxSGS9XtN2u6W2bSXjBl8Bc9d1izMEaekLmuIFJBvrKPAYiPHhI3ptAu/FibdlDEIkMnpYApTSfBByOchFgfORlA70ddC0/1YyBjHgPT5ArMiJqtJGwppUZFcyfZ4D1oM4Udeb+YR/+ogeClPG9HKWE/7TcAY+vhBisUYY2XAgcBVYlAaPJd0QpHe5HquEFhtMGrmPPE9YO53Inc/kfn6kj1x7Ke8RygbVUSXkRXO/y5cTlw1AQBoNXCa5FOaLZA4W8F4YehmPdZglvyW0LAdBkiEyZZjDQWCPR1K7HQVWkTgWtogl+Idrkj3MRTCyyR4W4yeysx0pvzRvJWOjJnnSsr+zPJwQ4lDODqXigHNLer+nyLcqduRbdjyvSoa5mt828SeXBRDYowLUIPZBWCrAl3KrXYrJl0IzXuxZZfO8Mbe9/ZcDywCWNrylz+d+AXv0eMBpOHVtAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;sse&quot;
        title=&quot;&quot;
        src=&quot;/static/e9061ada0ae476d3fd73f9271d6d585d/58c38/sse.png&quot;
        srcset=&quot;/static/e9061ada0ae476d3fd73f9271d6d585d/0eb09/sse.png 500w,
/static/e9061ada0ae476d3fd73f9271d6d585d/1263b/sse.png 1000w,
/static/e9061ada0ae476d3fd73f9271d6d585d/58c38/sse.png 1260w&quot;
        sizes=&quot;(max-width: 1260px) 100vw, 1260px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;응답마다 다시 요청을 해야하는 Long polling 방식보다 효율적입니다(상황에 따라 달라질 수 있습니다). SSE는 서버에서 클라이언트로 text message를 보내는 브라우저 기반 웹 애플리케이션 기술이며 HTTP의 persistent connections을 기반으로하는 HTML5 표준 기술입니다.&lt;/p&gt;
&lt;p&gt;이 외에도 웹소켓을 이용해 서버와 양방향 통신을 하는 방법이 있습니다. 서비스 상황에 맞게 적절한 방법을 선택하면 됩니다.&lt;/p&gt;
&lt;p&gt;저희 팀은 구현이 간단한 short polling으로 해당 기능을 구현했었습니다. 하지만 사용자에게 높은 실시간성을 제공하기 위해 0.1초마다 서버에 요청을 보내다보니 서버에도 부담이 크고, 변경 사항이 없는데도 DOM을 계속 교체해야 하는 문제가 있었습니다. 이를 해결하기 위해 SSE로 구현을 변경하기로 결정했습니다.&lt;/p&gt;
&lt;p&gt;서비스 특성 상 SSE보다는 WebSocket이 더 적절하다는 의견도 있었으나, 지금 당장은 새로운 라이브러리 추가나 프로토콜 학습 없이 바로 적용할 수 있는 SSE를 적용하기로 했습니다.&lt;/p&gt;
&lt;h2&gt;SSE 통신 개요&lt;/h2&gt;
&lt;p&gt;SSE를 이용하는 통신의 개요는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c9d3a25dbd760dd65d96cdb48af73160/ffe51/sse2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.60000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAABWElEQVR42p1Ti26DMBDj/z9zlTYoz5aEvOOdI6igK620k6LAcefY51BhjZQSrLUw1iCEgLNwzpW6s6i2h5wzhmHE5etSAPn+Ktq2RV3XnwGV1Rj1gGb6hvUOxHMhw7gM57MoyA8lMcbPgIyYIhaRE5M0Ss8yW4yDRt9qmCUIcxmL8VBaQy8LtOzP8ism7vc71KyglIKTQkSRLCxj3yJeG4T6B77rkGV+0XsYY8sax7GAHgC9zIvJeZ4LMIf+MMoYRKUR9UKtB/NYx1lz34zit4NkJjwl0BSZU5SDwu2GMAxI0sRcEoY3ydGcvu/RCXMS4e7l2x9At58JnZFFIKyu0xA27m/HqSmFISUTRFhGOZmz88ImyUHMkek0TeXqkFXTNOWdO6W/BuS8CLrue7ZJGLKRtfu1/QzVwQCa0ncPA17Fs+Tze8iTpNA/XYP/A64Djm/YbTXpTc0vTu//ymHV/ZQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;sse2&quot;
        title=&quot;&quot;
        src=&quot;/static/c9d3a25dbd760dd65d96cdb48af73160/f97d7/sse2.png&quot;
        srcset=&quot;/static/c9d3a25dbd760dd65d96cdb48af73160/0eb09/sse2.png 500w,
/static/c9d3a25dbd760dd65d96cdb48af73160/1263b/sse2.png 1000w,
/static/c9d3a25dbd760dd65d96cdb48af73160/f97d7/sse2.png 2000w,
/static/c9d3a25dbd760dd65d96cdb48af73160/ffe51/sse2.png 2018w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Client: SSE Subscribe 요청&lt;/h3&gt;
&lt;p&gt;우선 클라이언트에서 서버의 이벤트를 구독하기 위한 요청을 보내야합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;http&quot;&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;&lt;span class=&quot;token request-line&quot;&gt;&lt;span class=&quot;token method property&quot;&gt;GET&lt;/span&gt; &lt;span class=&quot;token request-target url&quot;&gt;/connect&lt;/span&gt; &lt;span class=&quot;token http-version property&quot;&gt;HTTP/1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Accept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;text/event-stream&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Cache-Control&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;no-cache&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이벤트의 미디어 타입은 &lt;code class=&quot;language-text&quot;&gt;text/event-stream&lt;/code&gt;이 표준으로 정해져있습니다. 이벤트는 캐싱하지 않으며 지속적 연결을 사용해야합니다(HTTP 1.1에서는 기본적으로 지속 연결을 사용합니다).&lt;/p&gt;
&lt;h3&gt;Server: Subscription에 대한 응답&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;http&quot;&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;HTTP/1.1 200
&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;text/event-stream;charset=UTF-8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Transfer-Encoding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;chunked&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;응답의 미디어 타입은 &lt;code class=&quot;language-text&quot;&gt;text/event-stream&lt;/code&gt; 입니다. 이때 &lt;code class=&quot;language-text&quot;&gt;Transfer-Encoding&lt;/code&gt; 헤더의 값을 &lt;code class=&quot;language-text&quot;&gt;chuncked&lt;/code&gt;로 설정합니다. 서버는 동적으로 생성된 컨텐츠를 스트리밍하기 때문에 본문의 크기를 미리 알 수 없기 때문입니다.&lt;/p&gt;
&lt;h3&gt;Server: 이벤트 전달&lt;/h3&gt;
&lt;p&gt;클라이언트에서 subscribe를 하고나면 서버는 해당 클라이언트에게 비동기적으로 데이터를 전송할 수 있습니다. 이때 데이터는 &lt;code class=&quot;language-text&quot;&gt;UTF-8&lt;/code&gt;로 인코딩된 텍스트 데이터만 가능합니다(바이너리 데이터는 전송 불가능).&lt;/p&gt;
&lt;p&gt;서로 다른 이벤트는 줄바꿈 문자 두개(\n\n)로 구분되며 각각의 이벤트는 한 개 이상의 &lt;code class=&quot;language-text&quot;&gt;name: value&lt;/code&gt; 필드로 구성되며 이들은 줄바꿈 문자 하나로 구분됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;http&quot;&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;type1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;An event of type1.&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;type2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;An event of type2.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;코드로 살펴봅시다&lt;/h2&gt;
&lt;p&gt;사용된 예시 코드는 &lt;a href=&quot;https://github.com/awesomeo184/sse-sample&quot;&gt;여기&lt;/a&gt;서 확인하실 수 있습니다.&lt;/p&gt;
&lt;p&gt;기본적인 흐름을 먼저 설명하면 다음과 같습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;클라이언트에서 SSE 연결 요청을 보낸다.&lt;/li&gt;
&lt;li&gt;서버에서는 클라이언트와 매핑되는 SSE 통신 객체를 만든다.&lt;/li&gt;
&lt;li&gt;서버에서 이벤트가 발생하면 해당 객체를 통해 클라이언트로 데이터를 전달한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그림으로 보면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/07d241cd25a664abebd2e6ce4154af70/a7a60/sse3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 58.20000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABLElEQVR42qWT626DMAxG+/5viDq0grhKgLjfweNYMqXV9qezFIU4zvFnJ9zksH3fxez6/YndpmmSYRhenAat61pc15WqqnS9bZvu/TUU2Pf9CQRQlqWs66rrrusUxj4w/MzLssg8z+dgPY7jUyEOMrRtK3EcSxAECicYsyoYJDH/1RCmwKZpFHTtHdkAmvI0TcXzPE3kOI5kea5+U2fVKDAMQ1VlZRqYQMvKnGWZ5AeIfdrAGZQzXoDvN0uPUAbQFBKcHCqrqtY1fTaVFnOWjIOSLZCyyI7PGs33/f4lSZLoOooi+X48tCpiEGRgvWWcSKck1NiNGpDZ933tJQcB01NiOW9VKZCDlGdv7Gr47YnYDPz6VBBiz0eBNJsSf3sKH/0p3FhRFOf1/xf4A3fHq2hQi118AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;sse3&quot;
        title=&quot;&quot;
        src=&quot;/static/07d241cd25a664abebd2e6ce4154af70/a7a60/sse3.png&quot;
        srcset=&quot;/static/07d241cd25a664abebd2e6ce4154af70/0eb09/sse3.png 500w,
/static/07d241cd25a664abebd2e6ce4154af70/1263b/sse3.png 1000w,
/static/07d241cd25a664abebd2e6ce4154af70/a7a60/sse3.png 1900w&quot;
        sizes=&quot;(max-width: 1900px) 100vw, 1900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1956px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/cda947189eb317f424fdbcae6a969fe9/973af/sse4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.400000000000006%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABJklEQVR42o2T2a6DMAxE+f9f5AkQa9n30oIvx5KjXJ5qyVmcZDweQyC3XdelbuavP5+P+q8WMMzzLPu+a+A8T3fIOo5jSZJEz/H3+y3Hccj3+9VEzLiRcIBc4kLf95JlmT4GcNs2jfNgXVd5vV6Spqk6e854SyIHSNCYMS/LIm3b6kX2gJOkKAqJokiaptG4sWI2WQIWlERmXzvMygEY5sawrmuNmWM2B4DAwALPZphWlARzwNC1uSvArFTH0IJ2MAyDlGWpPo6jlgZoWVWqG9Z1vaS3BMbsX8kM0zQpIAAwYE+jiBnLPM+VGWu0DMPQdRxAVzKDlcxjwGiACe9/JqanJfF1dAx51HWdOmsO7ZskMzHf/Zj9EH7HFbC69Xl2+dnxX/+UP1cKXKh7TdOmAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;sse4&quot;
        title=&quot;&quot;
        src=&quot;/static/cda947189eb317f424fdbcae6a969fe9/973af/sse4.png&quot;
        srcset=&quot;/static/cda947189eb317f424fdbcae6a969fe9/0eb09/sse4.png 500w,
/static/cda947189eb317f424fdbcae6a969fe9/1263b/sse4.png 1000w,
/static/cda947189eb317f424fdbcae6a969fe9/973af/sse4.png 1956w&quot;
        sizes=&quot;(max-width: 1956px) 100vw, 1956px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;우선 클라이언트에서는 &lt;code class=&quot;language-text&quot;&gt;EventSource&lt;/code&gt;라는 인터페이스로 SSE 연결 요청을 할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EventSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:8080/connect&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;spring framework 4.2부터 SSE 통신을 지원하는 &lt;code class=&quot;language-text&quot;&gt;SseEmitter&lt;/code&gt; API를 제공합니다. 이를 이용해 SSE 구독 요청에 대한 응답을 할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;lombok&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;extern&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;slf4j&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Slf4j&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;http&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MediaType&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;http&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;web&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;annotation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GetMapping&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;web&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;annotation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RestController&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;web&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;servlet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mvc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;annotation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SseEmitter&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RestController&lt;/span&gt;  
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Slf4j&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SseController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SseEmitters&lt;/span&gt; sseEmitters&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SseController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SseEmitters&lt;/span&gt; sseEmitters&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sseEmitters &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sseEmitters&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
  
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/connect&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; produces &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MediaType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TEXT_EVENT_STREAM_VALUE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SseEmitter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token class-name&quot;&gt;SseEmitter&lt;/span&gt; emitter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SseEmitter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        sseEmitters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;emitter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
            emitter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SseEmitter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;connect&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;connected!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;emitter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;생성자를 통해 만료시간을 설정할 수 있습니다. 디폴트 값은 서버에 따라 다릅니다. 스프링 부트의 내장 톰캣을 사용하면 30초로 설정됩니다. 만료시간이 되면 &lt;strong&gt;브라우저에서 자동으로 서버에 재연결 요청&lt;/strong&gt;을 보냅니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;SseEmitter&lt;/span&gt; emitter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SseEmitter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;이때 생성된 &lt;code class=&quot;language-text&quot;&gt;SseEmitter&lt;/code&gt; 객체는 향후 이벤트가 발생했을 때, 해당 클라이언트로 이벤트를 전송하기 위해 사용되므로 서버에서 저장하고 있어야 합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;sseEmitters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;emitter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;주의해야할 점은 Emitter를 생성하고 나서 만료 시간까지 아무런 데이터도 보내지 않으면 재연결 요청시 503 Service Unavailable 에러가 발생할 수 있습니다. 따라서 처음 SSE 연결 시 더미 데이터를 전달해주는 것이 안전합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;emitter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SseEmitter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;connect&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;         &lt;span class=&quot;token comment&quot;&gt;// 해당 이벤트의 이름 지정&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;connected!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;    &lt;span class=&quot;token comment&quot;&gt;// 503 에러 방지를 위한 더미 데이터&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;이벤트 이름을 설정해주면 클라이언트에서 해당 이름으로 이벤트를 받을 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EventSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:8080/connect&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

sse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;connect&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; receivedConnectData &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;connect event data: &apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;receivedConnectData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// &quot;connected!&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;SseEmitter&lt;/code&gt;를 생성할 때는 비동기 요청이 완료되거나 타임아웃 발생 시 실행할 콜백을 등록할 수 있습니다. 타임아웃이 발생하면 브라우저에서 재연결 요청을 보내는데, 이때 새로운 Emitter 객체를 다시 생성하기 때문에(&lt;code class=&quot;language-text&quot;&gt;SseController&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;connect()&lt;/code&gt;메서드 참조) 기존의 Emitter를 제거해주어야 합니다. 따라서 &lt;code class=&quot;language-text&quot;&gt;onCompletion&lt;/code&gt; 콜백에서 자기 자신을 지우도록 등록합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;  
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Slf4j&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SseEmitters&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SseEmitter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; emitters &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CopyOnWriteArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
    &lt;span class=&quot;token class-name&quot;&gt;SseEmitter&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SseEmitter&lt;/span&gt; emitter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;emitters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;emitter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        log&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;new emitter added: {}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; emitter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        log&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;emitter list size: {}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; emitters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        emitter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onCompletion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
            log&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;onCompletion callback&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
            &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;emitters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;emitter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;    &lt;span class=&quot;token comment&quot;&gt;// 만료되면 리스트에서 삭제&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        emitter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
            log&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;onTimeout callback&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
            emitter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;complete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; emitter&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;주의할 점은 이 콜백이 &lt;strong&gt;SseEmitter를 관리하는 다른 스레드에서 실행&lt;/strong&gt;된다는 것입니다. 따라서 &lt;code class=&quot;language-text&quot;&gt;thread-safe&lt;/code&gt;한 자료구조를 사용하지 않으면 &lt;code class=&quot;language-text&quot;&gt;ConcurrnetModificationException&lt;/code&gt;이 발생할 수 있습니다. 여기서는 thread-safe한 자료구조인 &lt;code class=&quot;language-text&quot;&gt;CopyOnWriteArrayList&lt;/code&gt;를 사용하였습니다.&lt;/p&gt;
&lt;p&gt;이제 서버에서 무언가 변경 사항이 생겼을 때, 클라이언트의 요청이 없어도 데이터를 전송할 수 있습니다. 본 예시에서는 누군가 &lt;code class=&quot;language-text&quot;&gt;/count&lt;/code&gt;를 호출하면 서버에 저장된 숫자를 1 증가시키고 이를 SSE 커넥션이 열려있는 모든 클라이언트에게 전달하도록 해보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RestController&lt;/span&gt;  
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Slf4j&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SseController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SseEmitters&lt;/span&gt; sseEmitters&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SseController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SseEmitters&lt;/span&gt; sseEmitters&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sseEmitters &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sseEmitters&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  

	&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@PostMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/count&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        sseEmitters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;  
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Slf4j&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SseEmitters&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AtomicLong&lt;/span&gt; counter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AtomicLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SseEmitter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; emitters &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CopyOnWriteArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
  
	&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
	
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; count &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; counter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;incrementAndGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
        emitters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;emitter &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
            &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
                emitter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SseEmitter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
                &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br&gt;
클라이언트에서는 `count`라는 이름의 이벤트가 발생할 때 콘솔에 변경된 데이터를 출력하도록 이벤트 리스너를 등록해둡니다.
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;sse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;&apos;count&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; e &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; receivedCount &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;count event data&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;receivedCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;receivedCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 만약 다른 브라우저에서 count를 호출한다면 내 브라우저에 서버에서 변경된 값이 찍히게 됩니다.&lt;/p&gt;
&lt;h2&gt;주의점&lt;/h2&gt;
&lt;p&gt;스프링에서도 API를 지원해주고 있기 때문에 SSE 통신을 구현하는 것 자체는 굉장히 쉽습니다. 하지만 기능을 구현하고 서비스에 배포하기까지 여러 어려움들을 겪었는데요, SSE 통신을 구현하면서 진행한 트러블슈팅을 공유하면서 글을 마치도록 하겠습니다.&lt;/p&gt;
&lt;h3&gt;503 Service Unavailable&lt;/h3&gt;
&lt;p&gt;위에서 언급했듯이 처음에 SSE 응답을 할 때 아무런 이벤트도 보내지 않으면 재연결 요청을 보낼때나, 아니면 연결 요청 자체에서 오류가 발생합니다.&lt;/p&gt;
&lt;p&gt;따라서 첫 SSE 응답을 보낼 시에는 반드시 더미 데이터라도 넣어서 데이터를 전달해야합니다.&lt;/p&gt;
&lt;h3&gt;헤더에 토큰 전달&lt;/h3&gt;
&lt;p&gt;SSE 연결 요청을 할 때 헤더에 JWT를 담아서 보내줘야했는데, &lt;code class=&quot;language-text&quot;&gt;EventSource&lt;/code&gt; 인터페이스는 기본적으로 헤더 전달을 지원하지 않는 문제가 있었습니다. &lt;a href=&quot;https://www.npmjs.com/package/event-source-polyfill&quot;&gt;event-source-polyfill&lt;/a&gt; 을 사용하면 헤더를 함께 보낼 수 있습니다.&lt;/p&gt;
&lt;h3&gt;JPA 사용시 Connection 고갈 문제&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;SSE 통신을 하는 동안은 HTTP Connection이 계속 열려있습니다.&lt;/strong&gt; 만약 SSE 연결 응답 API에서 JPA를 사용하고 &lt;code class=&quot;language-text&quot;&gt;open-in-view&lt;/code&gt; 속성을 true로 설정했다면, &lt;strong&gt;HTTP Connection이 열려있는 동안 DB Connection도 같이 열려있게 됩니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;즉 DB Connection Pool에서 최대 10개의 Connection을 사용할 수 있다면, &lt;strong&gt;10명의 클라이언트가 SSE 연결 요청을 하는 순간 DB 커넥션도 고갈되게 됩니다.&lt;/strong&gt; 따라서 이 경우 &lt;code class=&quot;language-text&quot;&gt;open-in-view&lt;/code&gt; 설정을 반드시 false로 설정해야 합니다.&lt;/p&gt;
&lt;h3&gt;ConcurrentModificationException&lt;/h3&gt;
&lt;p&gt;마찬가지로 위에서 언급했듯이, &lt;code class=&quot;language-text&quot;&gt;onCompletion&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;onTimeout&lt;/code&gt; 콜백은 별도의 스레드에서 호출됩니다. 만약 이 콜백을 통해 &lt;code class=&quot;language-text&quot;&gt;SseEmitter&lt;/code&gt; 객체가 담긴 컬렉션에 연산을 한다면 해당 컬렉션은 thread-safe한 자료구조를 사용해야합니다. 처음에 그냥 &lt;code class=&quot;language-text&quot;&gt;ArrayList&lt;/code&gt;를 사용했다가 SseEmitter가 만료될 때마다 ConcurrentModificationException이 발생하는 문제를 겪었었습니다.&lt;/p&gt;
&lt;h3&gt;Nginx 사용시 주의할 점&lt;/h3&gt;
&lt;p&gt;로컬에서 잘 동작하는 것을 확인하고 배포를 했더니 SSE 통신이 동작하지 않는 문제가 발생했었습니다.&lt;/p&gt;
&lt;p&gt;저희 팀은 리버스 프록시로 Nginx를 사용하고 있었는데요&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1734px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4b12547a044695854a0d7371e37c9051/285a5/arch.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAAB7klEQVR42pWTSW4UQRBF+44cghNwBW7gjWHBCiSQQGwRkk1vMML2rvGM3e0au6aseUhXPaKyWupGsLBTyspQKPLHj5+/Zvy1BugKtHNOM38FdQxtyVPWbPy0bUtRlGRJSJf4KAGdO8fw0EKVbJs9FjAI1iQqJVcRfRrgFh6f7w6lUyEs1aa034AOO+DDPzkDGMUxXaenmpFRcMWwvgBlMXTV9vrQyx428Q7jnXgCPF/IqOmU6Uq0e0k9fy3sBFw3Mro2+fLoA/3v7xJXE4iu6PxrqsM9IRJJvmbmRSveP3+B83WPWPRrI5eszTixjg2Y8lYEzlLill/2Aa46gzzG81yayKJtKkLfMo9JpZjd5i7PPr3EudmnyTMG0dCuQt6t5gJSC0lP7nvCsufA+ciZOoUyJ00TtPJo2po4UxPrEXCccmVf0jTZNHIRor0FOryhD6+nQmFHGdGInfrUnsYzeiujN/5C9L43FptprXFsm/E0S1jpUZdv+1JQG102z4DvO6RRQC0My0oa9Q/CvJvOsak8mmE46hGEISpck4VLLqIrfOsnVnRK7t8TJUpGzFgul8avVV1TluV/vWkAu67DcT0R3yIO73hrH3ES/uDL7RuaeI3r+2aKNE0fZ+ytn8RnomHni1ayRx8acz/hT/kDWAWaKOvBbYgAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;프로젝트 아키텍처&quot;
        title=&quot;&quot;
        src=&quot;/static/4b12547a044695854a0d7371e37c9051/285a5/arch.png&quot;
        srcset=&quot;/static/4b12547a044695854a0d7371e37c9051/0eb09/arch.png 500w,
/static/4b12547a044695854a0d7371e37c9051/1263b/arch.png 1000w,
/static/4b12547a044695854a0d7371e37c9051/285a5/arch.png 1734w&quot;
        sizes=&quot;(max-width: 1734px) 100vw, 1734px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;원인을 찾아보던 중 Nginx는 기본적으로 Upstream으로 요청을 보낼때 HTTP/1.0 버전을 사용한다는 것을 확인했습니다.&lt;/p&gt;
&lt;p&gt;HTTP/1.1은 지속 연결이 기본이기 때문에 헤더를 따로 설정해줄 필요가 없지만, &lt;strong&gt;Nginx에서 백엔드 WAS로 요청을 보낼 때는 HTTP/1.0을 사용하고 &lt;code class=&quot;language-text&quot;&gt;Connection: close&lt;/code&gt; 헤더를 사용&lt;/strong&gt;하게 됩니다.&lt;/p&gt;
&lt;p&gt;SSE는 지속 연결이 되어 있어야 동작하는데 Nginx에서 지속 연결을 닫아버려 제대로 동작하지 않았습니다.&lt;/p&gt;
&lt;p&gt;따라서 아래와 같이 nginx 설정을 추가해야 제대로 동작합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;nginx&quot;&gt;&lt;pre class=&quot;language-nginx&quot;&gt;&lt;code class=&quot;language-nginx&quot;&gt;&lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;proxy_set_header&lt;/span&gt; Connection &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;proxy_http_version&lt;/span&gt; 1.1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;또 Nginx의 proxy buffering 기능도 조심해야 하는데요, SSE 통신에서 서버는 기본적으로 응답에 &lt;code class=&quot;language-text&quot;&gt;Transfer-Encoding: chunked&lt;/code&gt;를 사용합니다. SSE는 서버에서 동적으로 생성된 컨텐츠를 스트리밍하기 때문에 본문의 크기를 미리 알 수 없기 때문입니다.&lt;/p&gt;
&lt;p&gt;Nginx는 서버의 응답을 버퍼에 저장해두었다가 버퍼가 차거나 서버가 응답 데이터를 모두 보내면 클라이언트로 전송하게 됩니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;참고: &lt;a href=&quot;https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/&quot;&gt;https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;문제는 버퍼링 기능을 활성화하면 &lt;strong&gt;SSE 통신 시 원하는대로 동작하지 않거나 실시간성이 떨어지게 된다는 것&lt;/strong&gt;입니다. 따라서 SSE 응답에 대해서는 proxy buffering 설정을 비활성화 해주는 것이 좋습니다.&lt;/p&gt;
&lt;p&gt;하지만 Nginx의 설정 파일에서 버퍼링을 비활성화하면 다른 모든 API 응답에 대해서도 버퍼링을 하지 않기 때문에 비효율적일 수 있습니다. 이때 nginx의 &lt;a href=&quot;https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/&quot;&gt;X-accel&lt;/a&gt; 기능을 활용하면 좋습니다.&lt;/p&gt;
&lt;p&gt;백엔드의 응답 헤더에 X-accel로 시작하는 헤더가 있으면 Nginx가 이 정보를 이용해 내부적인 처리를 따로 하도록 만들 수 있습니다. 따라서 SSE 응답을 반환하는 API의 헤더에 &lt;code class=&quot;language-text&quot;&gt;X-Accel-Buffering: no&lt;/code&gt;를 붙여주면 SSE 응답만 버퍼링을 하지 않도록 설정할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;스케일 아웃 시 문제&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;SseEmitter&lt;/code&gt; 객체는 서버의 메모리에 저장되어 있습니다. 따라서 서버 인스턴스를 늘리는 경우 기능이 제대로 동작하지 않습니다.&lt;/p&gt;
&lt;p&gt;이런 경우 &lt;a href=&quot;https://redis.io/docs/manual/pubsub/&quot;&gt;Redis Pub/Sub&lt;/a&gt;같은 메시지 브로커나 분산 캐시를 통해 문제를 해결할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Server-Sent Events는 HTTP를 기반으로 동작하기 때문에 특별한 라이브러리나 프로토콜 추가 없이 빠르게 도입할 수 있다는 장점이 있습니다. 또한 서버에서 클라이언트로의 단방향 통신만 가능하기 때문에 클라이언트의 상호작용 없이 서버로부터 데이터가 전달되어야 하는 상황에 적합합니다. 대표적으로 스포츠 중계 서비스나 SNS 피드 같은 성격의 서비스에서 사용을 고려해볼 수 있습니다.&lt;/p&gt;
&lt;h3&gt;참고자료&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/aliakh/demo-spring-sse&quot;&gt;https://github.com/aliakh/demo-spring-sse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/EventSource&quot;&gt;MDN EventSource&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/4.2.0.RC2_to_4.2.0.RC3/Spring%20Framework%204.2.0.RC3/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.html&quot;&gt;SseEmitter API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/&quot;&gt;Nginx X-accel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/&quot;&gt;Nginx reverse proxy guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[테스트 자동화 라이브러리 개발기]]></title><description><![CDATA[cypress-ci는 Cypress 테스트 자동화 라이브러리로, CI Provider에서 서버 실행과 테스트 실행을 자동화하기 위한 여러가지 기능을 지원하고 있습니다. 개발 배경 체크메이트 팀에서는 Jenkins와 Cypress로 빌드 전 E2E…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-10-11-test-automation/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-10-11-test-automation/</guid><pubDate>Tue, 11 Oct 2022 12:00:00 GMT</pubDate><content:encoded>&lt;br&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/greenblues1190/cypress-ci&quot;&gt;cypress-ci&lt;/a&gt;는 Cypress 테스트 자동화 라이브러리로, CI Provider에서 서버 실행과 테스트 실행을 자동화하기 위한 여러가지 기능을 지원하고 있습니다.&lt;/p&gt;
&lt;h2&gt;개발 배경&lt;/h2&gt;
&lt;p&gt;체크메이트 팀에서는 Jenkins와 Cypress로 빌드 전 E2E 테스트를 하고 있습니다. 그러나 기존 테스트 자동화 코드가 Shell Script로 짜여져 있어 Cypress의 모듈 API를 사용할 수 없어 멀티 리포트를 직접 구현하기 위해 라이브러리를 개발하게 되었습니다.&lt;/p&gt;
&lt;figure&gt;
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1512px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/30bb6318a280e770332aa40b612d95da/2719f/cypress-ci-old-script.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 40%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABeUlEQVR42mWSWU/CUBCF+/ddCpTWW25bIMXSAokxMeGf+KBERSJd2HeURY9zb1EwPnw5s5w706SjzBZr9IcTjKcLTOdLyWwhWB2gmGqL5Rrz5Qpz8ov6+n2Dj83uD5vtHkq7O8T9YxutdohONEA3HqGbDKVG/QnC3hjxYILheIoRLZ3RYLF0TQO2+y9sdkdErry8dvDQesJzu4MwStA98BbGhzyWiDhKeoiTvtQo7iGME4mMqd8fjKBYlo0LNYvLrAY1pyOjGcgSIlazutTLTF6qZjAYrIArk0O/KkD/jTlyeYbKtQ/FoYJKD3I0JDVxMlmwnBK45aDsVuBee3ArHkplF9wugjvFVG3noEWY3EY1qEExmYUzNS+/LJMzcK5q0HRGBg5Gy8QjmwbYTjqAFThMgp0gcoOZqHhVKDc3t3A9H36tDj9ICWqNNCeqQV1uThG1Y+9IA54f4K7ZhCJOYjSZ0jms5G//2O7+ncPPSYjer+eHE89294lv2SgSV3RWjqsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;기존 스크립트&quot; title=&quot;&quot; src=&quot;/static/30bb6318a280e770332aa40b612d95da/2719f/cypress-ci-old-script.png&quot; srcset=&quot;/static/30bb6318a280e770332aa40b612d95da/0eb09/cypress-ci-old-script.png 500w,
/static/30bb6318a280e770332aa40b612d95da/1263b/cypress-ci-old-script.png 1000w,
/static/30bb6318a280e770332aa40b612d95da/2719f/cypress-ci-old-script.png 1512w&quot; sizes=&quot;(max-width: 1512px) 100vw, 1512px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
  &lt;figcaption&gt;
    기존 스크립트
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;요구사항&lt;/h2&gt;
&lt;p&gt;라이브러리 개발 시 다음과 같은 요구사항이 있었습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js, TypeScript로 개발&lt;/li&gt;
&lt;li&gt;기존 Jenkins 머신에 빠른 적용을 위한 non-docker 셋업&lt;/li&gt;
&lt;li&gt;기존 Cypress configuration 파일을 읽어와 필요한 부분만 확장하여 적용할 수 있어야 함&lt;/li&gt;
&lt;li&gt;서버 시작 후 통신 가능한 상태가 되면 테스트 실행&lt;/li&gt;
&lt;li&gt;테스트 실패 혹은 에러 발생 시 빌드 실패 판정&lt;/li&gt;
&lt;li&gt;멀티 리포트 (콘솔 출력 + 파일 출력)&lt;/li&gt;
&lt;li&gt;Graceful shutdown&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;non-docker 셋업&lt;/h2&gt;
&lt;p&gt;Cypress는 headless 브라우저 테스트를 지원합니다. Headless 브라우저란 GUI 없이 백그라운드에서 실행되는 브라우저를 말합니다. CI에서는 렌더링을 하지 않아 얻는 성능상의 이점, 머신의 GPU 미지원 등의 이유로 headless 테스트가 필수적입니다.&lt;/p&gt;
&lt;figure&gt;
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1366px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/cdf77e568e0503505c9c061a6e4aaf7d/e2f49/cypress-ci-xvfb.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 53.800000000000004%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAACcElEQVR42qWSS08TYRiF++c0YWHcuTFx44KdJkY0EjcgXggxonGhMQYIco3xQiDcSlsKVAxyF6bQmc5MO5cWEFpaWtopPL4DhJ0rJ3mSL1/ynXnPe05geWYY21RJGjqVSoX//QJPG+/zou0lM0trWLkSO6UTskWP7FHtEnevjOUWcbIloYwrZHx2zskKrlA4PiHQNTBA/2iYsflVomsJ9MMayZxH8sBDy3sY2SrTnxcIdocwYy7mrM32jI0StdmYThEe3WQ9kiIesdDUPIHdyil7NdgRMuI4Vayh56oYgn5YRUsVScrDoRGTe4MO7d99QYeVsC5iG8Qmt5ga/c1myERTDgi4JVgxsrz90Mm7zh7imRypwokIeqRLpyTTRRIxhyfjLo8nXO4OOwyM2qyGVIIitDilnQlv+ILxC8GOnkFampt52NDAt7Eg2n6FVflJaHkT1cixGXVonszQFHa52ZWmc8jFmbf4OeFbVpiTKS8FbbHY0d3H61ft9Pb08mnwKwsJi8iSwi/NRk0VSMrOxoM2D/otWvsc4iMKnQMK66tf0GY1tLksCdmh6gtmjyG2rtLyvI2PXT2s6C6GhJHYPcIoeGeWt6IWuoShz1okY/tU6m/RUN9Iy1Qrx+tvJJw82xHzXNA+9LCOToRTTDmnZWJf0JS0TTmf7VAm3J51BEl2Zo/89dv0X63j2aMblMJ3UGJlEtELy1ahhrpfPUs2mffr4p89dKmNXyEtfUQ8nD4nlEKZy7D1/geFa3WUmq6gji+iRFziQUMmzBEoVGU6sfZPZGpTqmPqh5iGoOcxnTJGWuplSBtSZbmXO6PAn6LHX3atBzclqGUIAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Headless 브라우저 테스트&quot; title=&quot;&quot; src=&quot;/static/cdf77e568e0503505c9c061a6e4aaf7d/e2f49/cypress-ci-xvfb.png&quot; srcset=&quot;/static/cdf77e568e0503505c9c061a6e4aaf7d/0eb09/cypress-ci-xvfb.png 500w,
/static/cdf77e568e0503505c9c061a6e4aaf7d/1263b/cypress-ci-xvfb.png 1000w,
/static/cdf77e568e0503505c9c061a6e4aaf7d/e2f49/cypress-ci-xvfb.png 1366w&quot; sizes=&quot;(max-width: 1366px) 100vw, 1366px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
  &lt;figcaption&gt;
    Headless 브라우저 테스트
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Cypress는 headless 브라우저 테스트를 위해 여러가지 의존성을 요구합니다. 그 중 하나로 가상 메모리에서 그래픽 작업을 수행하는 in-memory 디스플레이 서버인 &lt;a href=&quot;https://www.x.org/releases/X11R7.6/doc/man/man1/Xvfb.1.xhtml&quot;&gt;XVFB&lt;/a&gt;가 있습니다. Cypress는 Cypress, OS, 브라우저 별 docker 이미지를 지원하지만 이를 사용하지 않는 경우 &lt;a href=&quot;https://docs.cypress.io/guides/continuous-integration/introduction.html#Dependencies&quot;&gt;공식 문서&lt;/a&gt;에서 필요로 하는 의존성을 설치할 수 있게 스크립트를 제공하고 있습니다.&lt;/p&gt;
&lt;h2&gt;기존 Cypress configuration 파일 사용&lt;/h2&gt;
&lt;p&gt;Cypress는 configuration 파일 확장자로 json(구버전에서 사용), js, ts을 지원합니다. 체크메이트 프론트엔드는 타입스크립트로 개발하고 있어 ts 파일로 설정 파일을 관리하고 있습니다. 그런데 빌드된 js 파일에서는 ts 파일을 불러올 수 없었습니다. 이를 위해 &lt;a href=&quot;https://github.com/radarsu/ts-import&quot;&gt;ts-import&lt;/a&gt; 라이브러리를 사용했습니다. ts-import는 &lt;code class=&quot;language-text&quot;&gt;node_modules&lt;/code&gt; 내 자신의 캐시 디렉토리에 컴파일한 js를 저장하여 js에서 ts를 import할 수 있게 합니다.&lt;/p&gt;
&lt;h2&gt;Polling&lt;/h2&gt;
&lt;p&gt;처음에는 서버와 테스트 모두 동시에 시작하여 &lt;code class=&quot;language-text&quot;&gt;Promise.race&lt;/code&gt;를 통해 관리하는 방법을 사용했습니다. 하지만 서버와 Cypress의 콘솔 출력이 섞이고 서버 실행이 늦어지면 Cypress의 테스트가 timeout되는 문제가 있었습니다. 콘솔 출력이 순서대로 출력되게 하기 위해 서버가 통신 가능한 상태가 가능한지 알아낼 필요가 있었습니다.&lt;/p&gt;
&lt;figure&gt;
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/9bd3f41149886daf9556b1bd5df9f44e/4f069/cypress-ci-sequence.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 43.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABx0lEQVR42pWRzWsTURTF579QqYpiG9tqlVZFF678g1y4aDVqsIKIiOBCEPfuBKEfcRKSjnZSIZsEi2MSNE3GSZtkPjKTGEpL0snPyYtJ1ZUeONx338e5974jaZqGaZr0caAm8bcNsS7u2KibX+kyQK83iL7vk81myWQydLvD00NIrVZLLPb297EW59lNb4j89aZNWNnG2hsK9YRYn4MCh/nvlKrVKs1mk36nWi5HVJaxLAvPtbFtc/T4XyHVajUhmM/nxeh9sXK5TNvbxbEbo4tfcnmWV2XkeEJwNRZjOS4TjcVFvrQSFZTMukm7/YNSqURxq4jjONTrdd4V3vAq/YRWZyD6rbjFmrLOurqBmvqIklJJpj7wXk2JvURSIbGmIBmGgRl0lQvGLRQKolNd1/msfyKrp/E54H8gNV6qeJqB5Tp8D4QqlQqe5+E5Ho1g5KEJb5dWuHlrgXDkIQvhiOB8wDv3HhC+v8jtuxERJW/HCiwMPv6vSp1OB9d1R4Y8evyUsdOTTF24zNTFK0xMz3Ls5ARHT4wHcZwjx88wdiqE5Da9kZND/iH4q8Cz5y8InZ/j6vUbzMxdYzoQPRvkoXOzI07OXOInr2RoWVHqKoYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;서버 및 테스트 실행 시퀀스&quot; title=&quot;&quot; src=&quot;/static/9bd3f41149886daf9556b1bd5df9f44e/f97d7/cypress-ci-sequence.png&quot; srcset=&quot;/static/9bd3f41149886daf9556b1bd5df9f44e/0eb09/cypress-ci-sequence.png 500w,
/static/9bd3f41149886daf9556b1bd5df9f44e/1263b/cypress-ci-sequence.png 1000w,
/static/9bd3f41149886daf9556b1bd5df9f44e/f97d7/cypress-ci-sequence.png 2000w,
/static/9bd3f41149886daf9556b1bd5df9f44e/4f069/cypress-ci-sequence.png 2394w&quot; sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
  &lt;figcaption&gt;
    서버 및 테스트 실행 시퀀스
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;서버는 다른 프로세스에서 실행되기 때문에 Node.js적인 방법 대신 시스템 계층에서 접근해야 했습니다. 체크메이트에서는 일정한 간격으로 서버에 HTML GET 요청을 보내 응답(상태 코드 200~206, 304)을 검사하는 방식을 선택했습니다. 이를 위해 CLI argument로 GET 요청을 보낼 url과 timeout 시간을 입력받도록 했습니다.&lt;/p&gt;
&lt;h2&gt;Graceful shutdown&lt;/h2&gt;
&lt;p&gt;Graceful shutdown은 작업을 모두 처리한 후 종료되도록 하는 것을 말합니다. 서버 프로세스의 경우 다른 프로세스에서 실행되기 때문에, 테스트 종료 후 혹은 어떤 에러 발생 시 종료하게끔 시그널을 보내야했습니다. 이를 위해 &lt;code class=&quot;language-text&quot;&gt;SIGTERM&lt;/code&gt;을 보내 서버를 종료시켰습니다. CI가 아닌 로컬 머신에서 사용할 경우 서버를 종료하기 위해 &lt;code class=&quot;language-text&quot;&gt;Ctrl-C&lt;/code&gt;로 인터럽트할 필요가 없는 장점도 있습니다.&lt;/p&gt;
&lt;figure&gt;
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 480px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/98339d02b21e1cdf1e38a0306b03ca62/9aebd/cypress-ci-sigterm.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 150.41666666666666%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAACXBIWXMAAAsTAAALEwEAmpwYAAAIJUlEQVR42hWWa1TT5x3Hs66b89jL1NM5O+tErIpVKoqIVAHlDiGGACEFuUkgQRJCIDEJt3AJJNxCAiRcEpCLEMpFqQqK0Fas6Nm0W7eendN167rTs3O6rac7297s8mKf/fviefec5/y+z/l+P9+fKEyWw3FZAWcKVMQo1aRd0SCuMlHadJUMdR6HQl+noVrK7KCeya5iLuYmUuU2U96h44g4hv0Jb6EwlVHlNFLSqkUUkSMnRm0kzWBFbKwnp85KTqsLQ187SQXpnD9/nNXJKp4tNvJ03kS+4hyq1goqnVoqujRUuPTUjbagcxmo6Tcj2n0kiBRVMbmtdvJau1DahdPtxepzkqOVo8iMxN0oZ9KhYLRViqUxD53PjH64Bo2nksz6bMQaMQmF8eSa8xD9cN8+dgbtRqZToe71onH2UtE3iGPKg6pJi6Ykjnf7LjPXk8+qK5dqewkxguS0Xj0hVTl8LyYMUfBPeW7/a3z3yF5Ez29/if2hwZQ111LlGcY41E+lZ5Dud3w0j9pRVckZqE+lR5/KcK2YM5clfD8jieCSLPbkStiRdp6tZ0+z5VQYW8KPIYpPOsJYnxLPlAPd4DAN4yN0zI3x9NMVPvvjCqPzXVhrZbgMMUy5VSy9dw1lm4Fd4kReSohhS2QEz71xjOeDDvCD/fsRWXUJDNqyaPe1YxodpmN2hJXVIb74eJaPN0dZXxtkcbmP7oY8FuY6ePrkOl//6QPmVnwclYsJFp/mcFoYe84e4OXD+xBVKCJwtb+N744f7/Is08s+FnosLHU3sTZoZ3PJxeNHozx4MsPMrI3F+TZWV9z84sNr1NvKUbem0DaqoM6dxvksQXLkuVCW7vXjuzWIbdyFf6aTGY+JW/5GHi318uhuH5vrA2ysutm4K5z3Bnn/di+33E2MCl51NpTgn67DOVhERkkkop0/2Yl/ysY7972MLHQzMFyLt0/L6JCeteUe3lt2srrUydPbbTy84+DdmTpuB6z0mPPpMlyiq1XDVbuWsopUai1FiEJDDzDut6KzXiYhO55epw6PU0OjWfCez8iteRv3AvV888zEVxtmAs25rHpVVJUmcDHxMKbaHEw9ejJlUUx1ZiPqsV4VJDaTrZRwJukcHXY19VczKSk4R5v1bVydKh4uVPOroUQWdcncaNRyp1nLrK0Sky6D0sI4MopSKTXK6TMJku11NdQbirA4tFQ2KNFpJcilJ0m6EEJ5WTwNlmym/Dq+fFDDnC6K6qSj6KXhGEvj0F2JR12ahM6Yg76+gML0EEQXk06TfSmZ8lolhQUJlObHUpifiDwrGm2FBH1lujBpAT6nmgGbnKy0UCLCXyNPmCz/0ln6+6+SIokk6NAeDn6blGPHgwmPP0NUylniBBCIM2JR6vKpadCiriwgTRJNbrGUS0UXycpJIl2eTKosnvaeajr7LRgblewJeoUfvbqTPcGvIjoVd47QlHhOSFK5bLVQ0dkmUKRLoIcLnbOHRm8vNm8LvsUBHKNNRCgyhTt1OCZaSSnPp8JuwBVw0O5votFTh+jg8eO8ERtJYnE2pn479aMj6P0BjNemsVy7jm06gKGtBudkB/5FBy3dV7AOWzG7qymzqWkdszKwaMd/pxf3rAPR3oup7DpzGoOzljbB2C0TfhoCt2i4Pkfz7AJyvQGLWsbGVL1gkWIqhyzoJuopEqwlNrxNrCKJ2JwE0rVZSDUKRC8lp7AtKobUYjlJ0lQURQqMvd20LyzSPBNAmp/Lw6Fq/rPRi6VUzK7kOIJUeRxQZrNN+C7Rt2B48xjb3opg74mTiLadjeXFsAie3/oy+4IOERUTjaa6lJ4bAWwLARrtFtacKp6NVNKll7Ij4jQvRMeyIzGeHbGxbD0RzndCjnFSkoyqWCJMeDyCPUdP8ML23Rw68DrDDbl8vuFm+n6A8QdLuCacdFdeZKG9DK1aTLhCSpg8le2nThKWGUtpbTpRmSdRFMhY8gqdsvfUm+x+LZht239MSEgI02153Pb1MTKzwtLKA7wj8zSY3TjtE/SP3MPhmUNntqG9ouOyWcnNmwZsjiyUxWnc6BIq4PBbUQKWBsmUZ3DsaAjaS3E0103T63pCt30dt/MhrqHf0tH3GV2e3zMx9gmO9k06XCs4fFamJmrQq+SYNTImO4UHE3NkfPbFJu6+Fg4fOkh8dCRLU3f4YP1njA/PshBYYfHBl0w//pqpR9/gm9ykxz5GU1M3KqE+TS0VpOdl0dZm5IavBdHlqlKW746zHnATevgIC4Md/Pvzx/zjz5vM+ExsrHj46Is/8P5//8eDv/+LpbW7dFiLecdvYePDcX7+6xsMeE34XWbWAi5E+lYDPS3lrPUJ2KrV8denK/ztl7f5y+fv43XquT5iYn7ay/jMJB/dHuDZug9PxxUh10rmrzczf7OT5BMHcVnU3LveiajgSiZmIbfX7OX85s4wv1uf4MlNN//86kP8/dXoBXA6LIUM6mTMmMX42vIZ6ipjrL8Kv2DyvOxYwoP34m6uxGfTIKrKiabGWMzIiJX1ex7W748yP+9kZdnL2FgrleVSmuqVNBkLKclPw96uYfWWi2xZNJGnj5AYdwJrsxqX8JczgnSRPCsKrV7B0EAdJUUSXnxlF1t3vsLBiHDiheRcSE8iWZZGdPIFYa+RCknKQqwQYBIbxgWBUnYhjp8+9vPJwwEeLhkFfEnihLY6T1nqGdLCXifl7FHeFLi272AQUkk4Hc0yHHXpwjqSzYRHiVEofKkinLSso/Q0ZeNvz2PSmc/9wFVs2hj+D52pfunChNRdAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;편안&quot; title=&quot;&quot; src=&quot;/static/98339d02b21e1cdf1e38a0306b03ca62/9aebd/cypress-ci-sigterm.png&quot; srcset=&quot;/static/98339d02b21e1cdf1e38a0306b03ca62/9aebd/cypress-ci-sigterm.png 480w&quot; sizes=&quot;(max-width: 480px) 100vw, 480px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
  &lt;figcaption&gt;
    더 이상의 Ctrl-C는 없다
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;CLI&lt;/h2&gt;
&lt;p&gt;CLI를 위한 라이브러리로는 &lt;a href=&quot;https://github.com/tj/commander.js&quot;&gt;commander&lt;/a&gt;를 사용했습니다. commander는 옵션, 기본값, 도움말, 버전 등의 argument를 파싱해 쉽고 빠르게 CLI 프로그램을 만들 수 있게 도와주는 Node.js 라이브러리입니다. 다음과 같은 형태로 사용할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; program &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;commander&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

program
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;-s, --serve &amp;lt;serve&gt;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;script to run server&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;start&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;-u, --url &amp;lt;url&gt;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;url to test&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;http://localhost:3000&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;-t, --timeout &amp;lt;timeout&gt;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;maximum time in ms to wait for a server response&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;60000&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;0.2.6&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;-s&lt;/code&gt;로 입력받는 서버 시작 명령어는 npm 스크립트의 경우 &lt;code class=&quot;language-text&quot;&gt;npm run&lt;/code&gt;을 생략할 수 있게끔 정규화를 거치도록 했습니다. 이를 위해 &lt;code class=&quot;language-text&quot;&gt;process&lt;/code&gt; 객체의 &lt;code class=&quot;language-text&quot;&gt;cwd&lt;/code&gt; 함수로 현재 Node 프로세스가 동작하는 디렉토리를 찾아 &lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt;을 임포트해 &lt;code class=&quot;language-text&quot;&gt;scripts&lt;/code&gt; 객체에서 해당 명령어가 있는지 검사하여 있다면 &lt;code class=&quot;language-text&quot;&gt;npm run&lt;/code&gt;을 추가했습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;
cypress-ci &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; start
cypress-ci &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;npm run start&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# normalized&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;마치며&lt;/h2&gt;
&lt;p&gt;라이브러리 개발이 불필요했다는 생각도 듭니다. 특히 기존 Jenkins 머신에 바로 올리기 위해 non-docker 셋업으로 구현했지만 결국 docker의 편리함을 더 느꼈습니다. 서버와 테스트 자동 실행은 &lt;a href=&quot;https://github.com/bahmutov/start-server-and-test&quot;&gt;start-server-and-test&lt;/a&gt; 라이브러리와 기능에서 큰 차이가 없고 해당 라이브러리의 경우 테스트 실행 명령어를 인자로 받아 테스트 라이브러리에 의존적이지 않은 장점이 있습니다. 만들며 Node.js, 프로세스, npm 등에 대해 많은 공부가 되었고 멀티 리포트가 가능해졌다는 것에 의의를 두고자 합니다. 본 라이브러리는 다음 링크에서 확인하실 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/greenblues1190/cypress-ci&quot;&gt;https://github.com/greenblues1190/cypress-ci&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.x.org/releases/X11R7.6/doc/man/man1/Xvfb.1.xhtml&quot;&gt;https://www.x.org/releases/X11R7.6/doc/man/man1/Xvfb.1.xhtml&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html&quot;&gt;https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/cypress-io/cypress-docker-images&quot;&gt;https://github.com/cypress-io/cypress-docker-images&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cypress.io/blog/2020/06/18/extending-the-cypress-config-file/&quot;&gt;https://www.cypress.io/blog/2020/06/18/extending-the-cypress-config-file/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[좋아요 개수 조회 최적화하기]]></title><description><![CDATA[속닥속닥 프로젝트(https://github.com/woowacourse-teams/2022-sokdak…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-10-10-like-count/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-10-10-like-count/</guid><pubDate>Mon, 10 Oct 2022 13:10:00 GMT</pubDate><content:encoded>&lt;p&gt;속닥속닥 프로젝트(&lt;a href=&quot;https://github.com/woowacourse-teams/2022-sokdak)%EB%A5%BC&quot;&gt;https://github.com/woowacourse-teams/2022-sokdak)를&lt;/a&gt; 진행하면서 현재는 문제가 되지 않지만 시간이 갈수록 데이터가 쌓이면서 문제가 될 수도 있겠다라고 생각한 부분이 있습니다. 게시글에 대한 ‘좋아요’ 기능인데요, 현재 구조에서는 게시글을 조회할 때, ‘좋아요’ 테이블과 함께 조인을 해서 가져오고 있습니다. 단순히 좋아요 개수만 저장하지 않는 이유는, 한번 좋아요 버튼을 누른 사람은 두 번째 누를 때 취소할 수 있어야 하기 때문입니다.&lt;/p&gt;
&lt;p&gt;하지만 이런 방식이라면 만약 게시글 하나에 좋아요가 100만개가 된다면 게시글을 조회 한번 할때마다 100만개를 조인하게되어 오버헤드가 커지게 됩니다. 실제로 유튜브, 인스타의 경우 좋아요 개수가 100만개를 훌쩍 넘는 경우가 많은데요, 어떻게 이런 문제를 처리할 수 있는지 궁금했습니다. 현재 속닥속닥에는 ‘좋아요’ 개수가 딱 정확하게 나와야 하는 비즈니스적 요구 사항은 없지만, 게시글의 성격에 따라 충분히 발생할 수 있는 요구사항이라고 생각했습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/46641538/193618738-188d04f8-b297-4b9a-9d43-c07c54cd513b.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;실제로 저희가 테스트를 해본 결과 게시글의 좋아요 개수를 조회하는데에만 0.68초가 걸렸습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/46641538/194886450-0c1b956a-d5f4-4d28-9f5d-dcf802d63763.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/46641538/193618985-86ef23c9-87c0-48ee-b740-9b817a306812.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;그래서 저희는 반정규화를 선택했습니다. post 테이블에 like_count 컬럼을 추가함으로서 likes 테이블을 조인하지 않고도 해당 게시글의 좋아요 개수를 알 수 있습니다. 이렇게 해서 해당 게시글의 좋아요 개수를 가져오는데 시간을 0.0013으로 줄일 수 있었습니다.&lt;/p&gt;
&lt;p&gt;하지만 이렇게 되면 좋아요 개수에 대한 정보가 두 테이블에 나타나게 되어 데이터 정합성을 맞추는 것이 중요한 문제가 됩니다. 데이터 정합성이 안맞는다는 말에 대해 풀어보자면, 좋아요를 누를때는 like 테이블에서 삽입, 삭제가 일어나게 되는데, post 테이블의 like_count 에 대해서도 수정을 해주어야 됩니다. 한 가지 데이터에 대해, 두 테이블에서 수정이 이루어지니 데이터가 맞지 않을수도 있게 됩니다.&lt;/p&gt;
&lt;p&gt;예를 들어 A 사용자가 좋아요를 누른다면, likes 테이블에 삽입을 해주고, post 테이블의 like_count에 기존 like_count + 1을 해줍니다. 그런데, 이 트랜잭션이 커밋되기 전에 B 사용자가 좋아요를 누른다면, 변화된 like_count 에 + 1 을 해주는 것이 아니라, 커밋되기 전 상태의 like_count에 + 1 을 해주어 데이터가 맞지 않게 되는 것입니다. 다시 말해, 좋아요가 0개인 상태에서 2명이 좋아요를 눌렀으므로 2개가 되어야 하는데, 1 개가 되는 것입니다. 이를 갱신 분실(Lost Update)라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/46641538/194886588-3f1398dc-ff16-443f-b490-2554f4d17262.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/46641538/194886638-cb20212a-0040-44d3-88e7-ab879d364834.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;화면 왼쪽은 A 트랜잭션, 오른쪽은 B 트랜잭션입니다. 자바 코드로 예시를 작성할 수도 있지만 보다 직관적으로 트랜잭션의 흐름을 보기 위해 MySQL 쿼리로 예시를 만들어봤습니다. 이 트랜잭션은 좋아요 개수를 조회하고, 좋아요의 개수를 1 증가하는 쿼리입니다. 처음 post의 like_count에는 1,000,001개의 데이터가 있었으므로 두 트랜잭션이 정상적으로 실행됐으면 1,000,003개가 되어야합니다. 하지만 A 트랜잭션에서 4번째 줄까지 실행하고 B 트랜잭션이 나머지를 실행하면 like_count는 1,000,002개로 하나의 write 요청이 다른 요청에 의해 덮어쓰여진 것을 알 수 있습니다.&lt;/p&gt;
&lt;h1&gt;1. Lock&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/46641538/194886691-1f8d9b37-ff76-4b20-b758-66a546aea6ee.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/46641538/194886725-2a2370ce-8dc1-418c-80bd-931507754eb8.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;첫번째로 락을 거는 방법을 생각했습니다. 조회 쿼리에 락을 걸어 데이터 정합성을 맞추는 것이죠. A 트랜잭션이 like_count 를 변경하는 업데이트 작업을 하고 커밋을 할 때 까지 B 트랜잭션은 like_count에 대한 조회에 대한 접근이 불가능합니다. 결과적으로 A 트랜잭션이 커밋 되고 나서 B 트랜잭션은 like_count를 얻기 때문에 정확한 like_count 데이터에서 1을 더하게 되고, 데이터 정합성이 맞게 됩니다. 기존 100,001 개의 좋아요 개수에서 두 트랜잭션이 커밋된 후에 100,003 개가 된 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;하지만, 락을 거는 방법은 서비스의 확장성을 고려했을 때 적절하지 않다고 생각했습니다. 조회에 대한 락을 걸게 되면 트랜잭션이 끝나는 것을 다 기다려야 하기 때문에 성능이 매우 나빠지게 될 것이기 때문입니다.&lt;/p&gt;
&lt;h1&gt;2. Native Query&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/46641538/197536594-5155152b-4cb5-40f0-a9f6-e03b65106963.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/46641538/194886836-e557a5cd-7c9c-4c11-8c7a-efcf719e3ea2.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;두번째 방법은 네이티브 쿼리를 이용해 post 테이블의 like_count 데이터 자체를 읽어 1을 더해주는 것입니다. update 명령어는 그 자체로 atomic 합니다. 따라서 값을 읽고 그 읽은 값을 증가시키는 과정을 분리하지 않고 한번에  업데이트 하도록 하면 동시성 문제를 해결할 수 있습니다.&lt;/p&gt;
&lt;p&gt;하지만, 한 트랜잭션이 업데이트를 진행하고 있는 경우에는 다른 트랜잭션에서 해당 row에 대해서는 update 작업을 하지 못합니다. 게시글 수정같이 업데이트가 빈번하지 않은 작업이라면 괜찮겠지만, 좋아요는 클릭 한번으로 수정이 되는 변경이 쉬운 작업입니다. 여러 사용자가 동시에 좋아요를 여러번 누르는 상황이 발생한다면, 서버에 무리가 갈 것이라고 판단되었습니다.&lt;/p&gt;
&lt;h1&gt;3. Sync Schedule&lt;/h1&gt;
&lt;p&gt;세번째 방법은 특정 주기마다 좋아요 개수를 맞추어 주는 것입니다. 처음에 문제였던 것이 동시에 사용자 요청이 들어올 때 같은 like_count 를 얻어 좋아요 개수가 맞지 않는다는 것인데, 이렇게 맞지 않았던 데이터를 주기적으로 한번씩 맞추어 주는 것입니다. like_count 는 데이터가 정확하지 않더라도, like 테이블을 통해서 정확한 좋아요 개수를 얻을  수 있기 때문에 가능한 방법입니다. 이 방법을 사용한다면 like_count 에 대한 접근을 할 때 다른 트랜잭션이 커밋되기를 기다리지 않아도 되기 때문에 성능적으로 이점이 있습니다.&lt;/p&gt;
&lt;p&gt;그러나 매 주기마다 데이터 정합성이 맞추어 지기는 하지만, 업데이트 주기가 오기 전까지는 데이터가 맞지 않을 수 있기 때문에, 정확한 좋아요 개수를 요구하지 않는 환경에서 사용하기에 좋다고 생각됩니다. 이렇게 시간이 지나서 최종적으로 같은 데이터로 동기화하는 것을 궁극적 일관성(Eventual Consistency)라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/46641538/193619447-c3e2ebaa-a882-4ef9-badd-33470d1ceb61.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;실제 인스타나 유튜브를 보면 좋아요 개수가 한 자리 수까지 정확하게 표시되지 않습니다. 일정 크기의 단위로 끊어서 표시하는데요 여기에는 사용자의 편의성을 배려한 이유도 있을 수 있습니다. 실제로 사용자들은 좋아요가 몇 개인지 한 자리 수까지 그렇게 궁금하지 않을 것입니다. 이런 경우라면  sync schedule을 이용해 성능을 최적화 할 수 있을 것입니다.&lt;/p&gt;
&lt;h1&gt;결론&lt;/h1&gt;
&lt;p&gt;저희 속닥속닥은 마지막 방법인 Sync schedule 방법을 선택했습니다. 락을 활용한 방법과 네이티브 쿼리를 사용한 방법은 성능적으로 문제가 발생할 것으로 판단되었기 때문입니다. Sync Schedule 방법은 업데이트 주기가 오기 전에는 데이터의 정합성이 맞지 않아 사용자에게 좋아요 개수가 부정확하게 표시될 수 있습니다. 하지만, 속닥속닥은 좋아요 개수가 일시적으로 부정확하게 표기 되더라도 큰 문제가 되지 않는다고 생각했습니다.&lt;/p&gt;
&lt;p&gt;속닥속닥은 Sync Schedule 방법을 선택했지만, 이 방법이 정답인 것은 아닙니다. 상황에 따라 적합한 방법을 택하는 것이 좋은 서비스를 만드는 길이라 생각합니다.&lt;/p&gt;
&lt;h1&gt;참고 및 출처&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Concurrency_control&quot;&gt;https://en.wikipedia.org/wiki/Concurrency_control&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Eventual_consistency&quot;&gt;https://en.wikipedia.org/wiki/Eventual_consistency&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[서비스 추상화]]></title><description><![CDATA[😘 서론 우아한테크코스에서 사용하는 협업 도구 중 하나인 은 무료 프리티어 사용 시 3개월이 지난 메시지들을 보여주지 않습니다.
레벨…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-10-10-Service/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-10-10-Service/</guid><pubDate>Mon, 10 Oct 2022 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;😘 서론&lt;/h1&gt;
&lt;p&gt;우아한테크코스에서 사용하는 협업 도구 중 하나인 &lt;code class=&quot;language-text&quot;&gt;Slack&lt;/code&gt;은 무료 프리티어 사용 시 3개월이 지난 메시지들을 보여주지 않습니다.
레벨 3 팀 프로젝트인 &lt;a href=&quot;https://github.com/woowacourse-teams/2022-pickpick&quot;&gt;줍줍&lt;/a&gt;에서는 이 사라지는 메시지들을 백업해주는 서비스를 제공합니다.
&lt;code class=&quot;language-text&quot;&gt;Slack&lt;/code&gt;의 메시지에 대한 정보를 주로 다루다 보니 &lt;code class=&quot;language-text&quot;&gt;Slack 라이브러리&lt;/code&gt;에 의존적인 로직이 많습니다.
이 포스팅에서는 외부 라이브러리(&lt;code class=&quot;language-text&quot;&gt;Slack 라이브러리&lt;/code&gt;)를 사용하는 &lt;strong&gt;로직을 어떻게, 왜 추상화했는지&lt;/strong&gt;에 관해 이야기하려고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h1&gt;🤔 왜 서비스를 추상화해야 하는가?&lt;/h1&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/b2dcb1b209c58854930913b00f9529f5/c7a69/2022-10-10-slack.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 25.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAPoAAAD6AG1e1JrAAABBElEQVR42mNgAAKzox86jPYdmKm/rXYVA3awC4i5ccixAPEhOM/s2MfpZkc/bjDau75fb3vtPt1tNZO0VzWwAaU0gXgeECcA8XkgVgXiuUDcD8SeQJwExIuB2BiIrwOxOxDHMXQVLp1Z2rZjSk9PW8DSzV5T+reGrGxoaGACSqYB8SYg9oIaOAOIU4G4DYgnA/F+INYHYj0gfgfEV4CYn+GBfFTfA7moNU+8/Ka/2ay38c0mvUX/V2mDXOgGxI1AfBhqYDEQb4V6rwmIzwLxVCCOAuJbULlQsLfvy0VNeBbtCTJwAVLY8AKxIxCLQl3BDMTmQKwFxJJALAzEttAw1IeqNwQAM61MWZdp0PcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;2022 10 10 slack&quot; title=&quot;&quot; src=&quot;/static/b2dcb1b209c58854930913b00f9529f5/f97d7/2022-10-10-slack.png&quot; srcset=&quot;/static/b2dcb1b209c58854930913b00f9529f5/0eb09/2022-10-10-slack.png 500w,
/static/b2dcb1b209c58854930913b00f9529f5/1263b/2022-10-10-slack.png 1000w,
/static/b2dcb1b209c58854930913b00f9529f5/f97d7/2022-10-10-slack.png 2000w,
/static/b2dcb1b209c58854930913b00f9529f5/c7a69/2022-10-10-slack.png 2560w&quot; sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;p&gt;기존의 테스트에서는 &lt;code class=&quot;language-text&quot;&gt;Slack 라이브러리&lt;/code&gt;를 호출하는 부분은 모킹 처리해두었습니다.
테스트 코드를 리팩토링하다 보니 모킹 작업이 계속 &lt;strong&gt;반복&lt;/strong&gt;되는 &lt;strong&gt;지루&lt;/strong&gt;한 작업으로 느껴졌습니다.
반복되는 모킹 작업을 제거하기 위해 테스트 방식을 &lt;code class=&quot;language-text&quot;&gt;목&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;스텁&lt;/code&gt;으로 변경해야겠다는 결심을 하게 되었습니다.
(테스트에 대한 이야기는 &lt;code class=&quot;language-text&quot;&gt;2기_스티치&lt;/code&gt;의 &lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2020-09-19-what-is-test-double/&quot;&gt;Test Double을 알아보자&lt;/a&gt;를 참고
바랍니다.)&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Slack 라이브러리&lt;/code&gt;의 스텁용 객체를 만들기 위해서는 &lt;code class=&quot;language-text&quot;&gt;MethodsClient&lt;/code&gt;라는 인터페이스를 구현해야 합니다.
해당 인터페이스는 약 250개의 메서드를 갖고 있으며 그중 저희 서비스에서 실제로 사용하는 메서드는 약 10개 정도에 불과합니다.
이를 모두 구현하는 것은 불필요하고 번거로운 작업이기에 더 간편한 방법을 고민하게 되었습니다.
이때 떠오른 방법이 &lt;strong&gt;서비스를 추상화해 필요한 부분만&lt;/strong&gt; 사용하도록 만드는 것이었습니다.
자세한 설명은 아래 예제 코드를 보며 살펴보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h1&gt;😆 예제 코드&lt;/h1&gt;
&lt;h2&gt;0. 기존 코드&lt;/h2&gt;
&lt;p&gt;예제로 채널을 생성하는 &lt;code class=&quot;language-text&quot;&gt;ChannelCreateService&lt;/code&gt;의 기존 로직을 가져와 보았습니다.
로직 대부분이 &lt;code class=&quot;language-text&quot;&gt;Slack 라이브러리&lt;/code&gt;에 의존하고 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelCreatedService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MethodsClient&lt;/span&gt; slackClient&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelRepository&lt;/span&gt; channels&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelCreatedService&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MethodsClient&lt;/span&gt; slackClient&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelRepository&lt;/span&gt; channels&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;slackClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; slackClient&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;channels &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; channels&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; channelSlackId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// slack api를 호출하는 부분&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Conversation&lt;/span&gt; conversation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; slackClient
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;conversationsInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;channel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channelSlackId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// slack의 response를 우리 도메인인 channel 형태로 변환&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt; channel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;conversation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// channel을 저장하고 return&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; channels&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SlackApiException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// slack api 호출로 인해 발생할 수 있는 exception&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SlackApiCallException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 583px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2ddb1dfc74cc90c3fa55a323c5b7fb7c/6d62d/2022-10-10-service-ver1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 86.60000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAIAAABSJhvpAAAACXBIWXMAABJ0AAASdAHeZh94AAADUUlEQVR42o2TW0wTaRTHv0o2aqKo0RhjNuFhs68+GBMvwURf1Jjowz7sk49mFxAqVSKK8QoREVwvWYRdEzeuFyjFe5UaLW3p0Ov0QkGYmdLOtKUzbbcz085QWi5rPVO8Jb44+eXM/zvnOzP/nPkGvX9f/HwV5v4PJeQonwNIVorxOTo1HUrKEEFP8TPzCx93ww0aESguOxuIykR8eoSRDP5kv4N97GKfudkBX0Jnjz91s8AznHvqYn2MRMRzsJkVC9CIInze8o7Xe1Mt+vBFffjSANNmiLQamAt6+qKebnlFN7+iWweYywaF5pfhZj393JMcGufpdB7hoWyfk1tzEkPVRtWRQQRUG5drzJs78E1X3Jva3JU3vOXHLajKqKoZVDbUGMtPWHvsnHMygwIR6dCdMXT4TVmtUlCoNpbVDa5twlaftK5qtK5rwn5QmyC5WC2D/t/e/NId8NMSGqGlg92BFSesx/qDR3XUYlT3Ub8/JKp7iKoeAkStloQMUKulGh4F15zCdt/wQSMaZaS9nf7NV9wLufnxiOSkRAcljjGSN5RxBUV8MgMiOCU7g6IvnCFjcnF2Yfsfnm1XPaMRCQUYaX/XyIYzth4H122OXX8bvWWKdZkVbhpBK8vblqkuUwyqf1umYEAV5+07r3mhEcHnOdAdWNYwBG7rdZSmH+yRoOv6SI2OOloCdMkzeUSrJMsbrbvANlOyvafTv6UdF/m8ixKDMdk6wePBTITLOSgBI3iwTUbloQmeisnYhCBnZiuve7d24F9s/3jO/gRP/oPFH9jZu8Psv8Os1snB8i4WBw3JezYlc9/G6n2pn5odlZ9tH/wrsLRhqE5L1vSSMGGwBz4XowK47VVi1UNl+GotubLRuvtmadrwgH1guwMvyHMCXxCFgo0QYeaAP5x1BzMYIQTo7DAhCEIhnc4X8ws7rnm2d8C0ZTQWlX+9PVpx1vZ2NA2H9KUvpXMneh0cmNS5EhCBx3gSMlB94U1axvmfLzj2/+lXbMPx7HclNp6xIbVJpTGjerOq3rxEY1Z9BSpllBJEtWn96WGtg3NNZlEiO2sZF+CF7a+ZtgGFy9/Q9ikC7QbmhScFLVymoPySQm4ulJyJ/Jf/TmBzenquWCx+AL0D35U3xeXaAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 10 service ver1&quot;
        title=&quot;&quot;
        src=&quot;/static/2ddb1dfc74cc90c3fa55a323c5b7fb7c/6d62d/2022-10-10-service-ver1.png&quot;
        srcset=&quot;/static/2ddb1dfc74cc90c3fa55a323c5b7fb7c/0eb09/2022-10-10-service-ver1.png 500w,
/static/2ddb1dfc74cc90c3fa55a323c5b7fb7c/6d62d/2022-10-10-service-ver1.png 583w&quot;
        sizes=&quot;(max-width: 583px) 100vw, 583px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;1. 외부 라이브러리를 쓰는 로직 추상화&lt;/h2&gt;
&lt;p&gt;위 로직을 아래처럼 두 개의 클래스로 분리하여 역할을 나눠주었습니다.&lt;/p&gt;
&lt;h4&gt;ChannelCreateService&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;채널 저장 로직을 가진 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelCreatedService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CallSlackApi&lt;/span&gt; slackClient&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelRepository&lt;/span&gt; channels&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelCreatedService&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CallSlackApi&lt;/span&gt; slackClient&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelRepository&lt;/span&gt; channels&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;slackClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; slackClient&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;channels &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; channels&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; channelSlackId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt; channel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; slackClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;callChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channelSlackId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; channels&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;CallSlackApi&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Slack 라이브러리&lt;/code&gt;를 호출하고 결괏값을 반환하는 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CallSlackApi&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MethodsClient&lt;/span&gt; methodsClient&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CallSlackApi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MethodsClient&lt;/span&gt; methodsClient&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;methodsClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; methodsClient&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;callChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; channelSlackId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// `Slack 라이브러리`를 호출&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Conversation&lt;/span&gt; conversation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; methodsClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;conversationsInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          request &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;channel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channelSlackId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// 결과값을 우리가 사용하는 객체 형태로 변경&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;conversation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; conversation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SlackApiException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SlackApiCallException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1197px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6231f99a3718bd7e12c64c62874a341e/7e04b/2022-10-10-service-ver2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 35%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAIAAACHqfpvAAAACXBIWXMAABJ0AAASdAHeZh94AAABrUlEQVR42j3O20vbYBgG8PwTXvkvjAnuSkG8qF7tbgMHdoon8EbEAyLeDCZTq4KHVqlSrR01Oqp1uFoUyjxUqphq0pimRkGwVVttapsvn5rPJA1+QhF+vPBcPA8voenGo6Knsko0Abg4YK8kLgHi6Wf4rCHNMBDMS3d5WdSTgnoRUi8pPRPPy2nM0FXi7Pax1smXj51UTNBfHNw3Z9RkY3Csnma3YjmV9YO5ZjBbLy+2Q7ILkh3yfAuYMcvL3doNTwT4h6Le/ZLh8Ichqmz8pGKS/mgJl44eF/ftjpCU7B3IDlZKthowUwccTcDRKE3VSNavWYsJRQOEj818trN7fGY1fId58aVSOwKY9IT72yzCrwblvw3FdhDtQ/Q/xGygyMaLsCe721DEj8tilZXZZEXyMPU7lHQfJF2hZIATe9Yuf2xeo8AE/PsTMT6FWn1z5EHUCl4BC614iAie5z4NHze7z+pcMbOL/+7iaxf41iXBZKWnthMv23bJboZ/emSyE75b6sbPq+dB4gnpwYuch75fi6QxL/MGRz8nilAzstfodKvwMOMroNdx01DgK7knOTQsKaCTAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 10 service ver2&quot;
        title=&quot;&quot;
        src=&quot;/static/6231f99a3718bd7e12c64c62874a341e/7e04b/2022-10-10-service-ver2.png&quot;
        srcset=&quot;/static/6231f99a3718bd7e12c64c62874a341e/0eb09/2022-10-10-service-ver2.png 500w,
/static/6231f99a3718bd7e12c64c62874a341e/1263b/2022-10-10-service-ver2.png 1000w,
/static/6231f99a3718bd7e12c64c62874a341e/7e04b/2022-10-10-service-ver2.png 1197w&quot;
        sizes=&quot;(max-width: 1197px) 100vw, 1197px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;2. 추상화한 로직 인터페이스로 분리&lt;/h2&gt;
&lt;p&gt;좀 더 편리한 스텁 객체를 만들기 위해 &lt;code class=&quot;language-text&quot;&gt;ExternalClient&lt;/code&gt;라는 인터페이스를 만들었습니다.
기존의 &lt;code class=&quot;language-text&quot;&gt;CallSlackApi&lt;/code&gt;에 존재하던 로직은 &lt;code class=&quot;language-text&quot;&gt;ExternalClient&lt;/code&gt; 인터페이스의 구현체인 &lt;code class=&quot;language-text&quot;&gt;SlackClient&lt;/code&gt;로 옮겼습니다.&lt;/p&gt;
&lt;h4&gt;ExternalClient&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;외부 라이브러리의 로직을 추상화한 인터페이스&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExternalClient&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;callChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; channelSlackId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;SlackClient&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Slack 라이브러리&lt;/code&gt;를 호출하는 &lt;code class=&quot;language-text&quot;&gt;ExternalClient&lt;/code&gt;의 구현체&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SlackClient&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExternalClient&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MethodsClient&lt;/span&gt; methodsClient&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SlackClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MethodsClient&lt;/span&gt; methodsClient&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;methodsClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; slackClient&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;callChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; channelSlackId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Conversation&lt;/span&gt; conversation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; methodsClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;conversationsInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          request &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;channel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channelSlackId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;conversation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; conversation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SlackApiException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SlackApiCallException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1192px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d40d3c3babceb5f8800be2be7348675a/bb7f4/2022-10-10-service-ver3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 33.800000000000004%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAIAAACHqfpvAAAACXBIWXMAABJ0AAASdAHeZh94AAABYUlEQVR42mP49uPvu88/rz/+ePnhh4sPPlx6+OHW008fvvz69uPPn7///uMFDAWrbln1n7PtP+c9/WLE/Cs+0y/Z9J+16jtXtPbum08/gSr+/f759/2zP++fAEkQ+vAMKPL/37//f/8wKNcdk284rtF20rDrjPWEc4bdZ9RbTyo2HlevPXTn7NmfB2d/2dr1ZVPb1x39X3f2f9nR/2VT69dtPV/3TP394jaD06TzG8++3HTu5epTL9acBqH1Z15uu/gqt3jGtpKiF22e7+uN3rXYfpgc8mFS8IeJAe+ard/Wm7zvdPl17zSDbf/ZladerDvzcsnxZ8tOPAeSa8+8ACK3KZfuPXj2/8X1X0+u/X567fejS1D0FMx9cvXv57cMVr1nc1bcKFpzO33ZjQwwKlx9K3/VTeeJ5x+9/g7yM+5QY9hy8XX7zoc9ex71QtDex0A2UGTb5Tc/fv0B6QXqxoEAeotOvbRvpyIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022 10 10 service ver3&quot;
        title=&quot;&quot;
        src=&quot;/static/d40d3c3babceb5f8800be2be7348675a/bb7f4/2022-10-10-service-ver3.png&quot;
        srcset=&quot;/static/d40d3c3babceb5f8800be2be7348675a/0eb09/2022-10-10-service-ver3.png 500w,
/static/d40d3c3babceb5f8800be2be7348675a/1263b/2022-10-10-service-ver3.png 1000w,
/static/d40d3c3babceb5f8800be2be7348675a/bb7f4/2022-10-10-service-ver3.png 1192w&quot;
        sizes=&quot;(max-width: 1192px) 100vw, 1192px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;3. 스텁용 객체 생성 및 적용&lt;/h2&gt;
&lt;p&gt;스텁용 객체를 생성한 뒤 &lt;code class=&quot;language-text&quot;&gt;@Component&lt;/code&gt;를 이용해 bean으로 등록하였습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Primary&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// SlackClient 보다 우선 순위가 높은 bean으로 등록하기 위해 붙였다.&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FakeClient&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExternalClient&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; channels &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 채널 데이터 초기화&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;callChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; channelSlackId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; channels&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;it &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isSameSlackId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channelSlackId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAny&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SlackApiCallException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h2&gt;4. 테스트 코드에서 모킹 제거&lt;/h2&gt;
&lt;p&gt;스텁을 적용하였으니 이제 모킹한 코드들을 제거해주는 일만 남았습니다.
전후 코드를 비교하여 코드가 얼마나 깔끔해졌는지 확인해보겠습니다.&lt;/p&gt;
&lt;h4&gt;AS-IS (목 적용)&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// given&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;Workspace&lt;/span&gt; workspace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; workspaces&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WorkspaceFixture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;JUPJUP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt; channel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelFixture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;QNA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;workspace&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;given&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;slackClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;conversationsInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RequestConfigurator&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ConversationsInfoRequestBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;willReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setUpChannelMockData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// when&lt;/span&gt;
  channelCreatedService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// then&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; actual &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; channels&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findBySlackId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSlackId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;actual&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isNotEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConversationsInfoResponse&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setUpChannelMockData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt; channel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;Conversation&lt;/span&gt; conversation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Conversation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  conversation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSlackId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  conversation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token class-name&quot;&gt;ConversationsInfoResponse&lt;/span&gt; conversationsInfoResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConversationsInfoResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  conversationsInfoResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;conversation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  conversationsInfoResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setOk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; conversationsInfoResponse&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;TO-BE (스텁 적용)&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// given&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;Workspace&lt;/span&gt; workspace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; workspaces&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WorkspaceFixture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;JUPJUP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt; channel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelFixture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;QNA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;workspace&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// when&lt;/span&gt;
  channelCreatedService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// then&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; actual &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; channels&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findBySlackId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSlackId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;actual&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isNotEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h1&gt;😉 마무리&lt;/h1&gt;
&lt;p&gt;해당 글에서는 테스트를 간편화하기 위해 서비스 추상화를 진행해보았습니다.
서비스 추상화는 이 외에도 다양한 목적으로 진행할 수 있습니다.&lt;/p&gt;
&lt;p&gt;객체 지향적인 코드는 &lt;code class=&quot;language-text&quot;&gt;단일 책임 원칙&lt;/code&gt;을 준수합니다.
기존의 &lt;code class=&quot;language-text&quot;&gt;ChannelCreateService&lt;/code&gt;에서는 ‘Slack 라이브러리 호출’, ‘예외 처리’, ‘채널 저장’ 3가지 역할을 하고 있었습니다.
개선된 &lt;code class=&quot;language-text&quot;&gt;ChannelCreateService&lt;/code&gt;에서는 &lt;code class=&quot;language-text&quot;&gt;SlackClient&lt;/code&gt;를 통해 채널 정보를 가져오고 이를 DB에 ‘저장’하는 역할을 합니다.
‘Slack 라이브러리 호출’과 이로 인한 ‘예외 처리’의 책임은 &lt;code class=&quot;language-text&quot;&gt;SlackClient&lt;/code&gt;으로 옮겨갔습니다.
이렇듯 서비스를 추상화하면 이전보다 &lt;strong&gt;코드가 간결&lt;/strong&gt;해지고 작업의 &lt;strong&gt;목적이 분명&lt;/strong&gt;하게 드러납니다.&lt;/p&gt;
&lt;p&gt;각 오브젝트의 책임과 역할이 더 분명하게 분리되면 변경 사항이 생기는 경우 &lt;strong&gt;변경 포인트&lt;/strong&gt;를 쉽게 찾아낼 수 있습니다.
이제 &lt;code class=&quot;language-text&quot;&gt;Slack 라이브러리&lt;/code&gt;와 관련된 오류가 발생하면 &lt;code class=&quot;language-text&quot;&gt;ChannelCreateService&lt;/code&gt;가 아닌 &lt;code class=&quot;language-text&quot;&gt;SlackClient&lt;/code&gt;를 살피면 됩니다.
의존되는 외부 라이브러리가 &lt;code class=&quot;language-text&quot;&gt;Slack&lt;/code&gt;이 아닌 &lt;code class=&quot;language-text&quot;&gt;카카오톡&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;라인&lt;/code&gt; 등으로 바뀔 수도 있습니다.
이 경우에도 &lt;code class=&quot;language-text&quot;&gt;ExternalClient&lt;/code&gt;의 구현체를 생성하면 쉽게 대체할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Reference &amp;#x26; Source&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;토비의 스프링 3.1 / 이일민&lt;/li&gt;
&lt;li&gt;대표 이미지: 피에트 몬드리안의 추상화 작 &lt;code class=&quot;language-text&quot;&gt;브로드웨이 부기우기&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Spring Profile: yml 파일 하나로 프로퍼티 관리하기]]></title><description><![CDATA[서비스를 개발하다보면 하나의 서버 애플리케이션을 다양한 환경에서 실행할 일이 생깁니다. 그리고 H2 DB를 사용하는 테스트 환경과 MySQL DB…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-10-04-active_profiles/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-10-04-active_profiles/</guid><pubDate>Tue, 04 Oct 2022 17:00:00 GMT</pubDate><content:encoded>&lt;p&gt;서비스를 개발하다보면 하나의 서버 애플리케이션을 다양한 환경에서 실행할 일이 생깁니다. 그리고 H2 DB를 사용하는 테스트 환경과 MySQL DB를 사용하는 배포 환경에서 사용되는 환경변수 값이 서로 다른 것은 당연한 일이겠죠.&lt;/p&gt;
&lt;p&gt;해당 글에서는 일반적인 스프링 부트 애플리케이션에서 하나의 &lt;code class=&quot;language-text&quot;&gt;application.yml&lt;/code&gt; 파일을 통해 각 &lt;strong&gt;프로파일&lt;/strong&gt;별로 프로퍼티 설정들을 관리하고, 환경변수를 주입하는 몇 가지 방법에 대해 설명하고자 합니다.&lt;/p&gt;
&lt;p&gt;인프라를 다뤄본 경험이 어느 정도 있다는 가정하에서 작성하였습니다.&lt;/p&gt;
&lt;h2&gt;프로퍼티와 JVM 시스템 파라미터&lt;/h2&gt;
&lt;p&gt;우선 스프링 애플리케이션이 실행되는 포트 번호를 수정하는 예시를 통해 프로퍼티를 설정하는 방법에 대해 가볍게 알아봅시다.&lt;/p&gt;
&lt;p&gt;기본적으로 스프링 부트 애플리케이션을 실행하면 8080 포트에서 톰캣 서버가 디폴트로 실행됩니다.&lt;/p&gt;
&lt;p&gt;이때 모종의 이유로 포트번호를 8080 대신 5000 포트로 수정하고 싶다면 &lt;code class=&quot;language-text&quot;&gt;main/resources&lt;/code&gt; 경로에 &lt;code class=&quot;language-text&quot;&gt;application.yml&lt;/code&gt; 파일(이하, &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일)을 생성하고 아래와 같이 포트 번호를 기재해주면 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yml&quot;&gt;&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;server.port&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;한편 이미 배포된 서버를 다른 포트 번호에서 실행되도록 수정해야 하는 경우도 존재합니다. 이 경우 &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일의 값을 수정하고 애플리케이션을 다시 빌드하여 재실행시키는 방법이 있겠죠. 그러나 &lt;strong&gt;JVM 시스템 파라미터&lt;/strong&gt;를 활용하면 &lt;strong&gt;이미 빌드된 애플리케이션에 특정 프로퍼티 값을 주입하여 실행&lt;/strong&gt;하는 것도 가능합니다.&lt;/p&gt;
&lt;p&gt;이해를 위해 우선 스프링 부트 애플리케이션을 &lt;code class=&quot;language-text&quot;&gt;jar&lt;/code&gt; 파일로 빌드하고, 실행시키고, 종료시키는 명령어를 배워봅시다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# gradle을 사용하여 애플리케이션 빌드&lt;/span&gt;
./gradlew build
&lt;span class=&quot;token comment&quot;&gt;# 빌드된 .jar 파일이 있는 장소로 이동&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; build/libs
&lt;span class=&quot;token comment&quot;&gt;# 빌드된 .jar 파일 실행&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;java&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-jar&lt;/span&gt; 애플리케이션-0.0.1-SNAPSHOT.jar &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 특정 포트번호에서 실행되는 프로세스를 종료&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;kill&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lsof&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; -i:포트번호&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위의 명령어를 실행한다면 &lt;strong&gt;빌드될 때 포함된&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일에 설정한 &lt;code class=&quot;language-text&quot;&gt;server.port&lt;/code&gt; 프로퍼티 값을 활용하게 됩니다. 저희의 경우 위에서 설정한 5000 포트가 그대로 활용되겠죠.&lt;/p&gt;
&lt;p&gt;그런데 해당 &lt;code class=&quot;language-text&quot;&gt;.jar&lt;/code&gt; 파일을 실행시키는 명령어에 다음과 같이 &lt;code class=&quot;language-text&quot;&gt;-Dserver.port=7000&lt;/code&gt;라는 내용을 추가하면 어떻게 될까요?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# -Dkey=value 형식으로 JVM 시스템 파라미터 활용&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;java&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-Dserver.port&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7000&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-jar&lt;/span&gt; 애플리케이션-0.0.1-SNAPSHOT.jar &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 경우 5000 포트에서 실행되던 애플리케이션이 7000 포트에서 실행됩니다. 이는 &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일에 포함된 &lt;code class=&quot;language-text&quot;&gt;server.port&lt;/code&gt; 프로퍼티 값이 &lt;code class=&quot;language-text&quot;&gt;7000&lt;/code&gt;이라는 값으로 &lt;strong&gt;완전히 덮어써진 체 실행&lt;/strong&gt;되는 것을 의미합니다. &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일에 &lt;code class=&quot;language-text&quot;&gt;server.port&lt;/code&gt; 정보를 기재하지 않은 경우에도 JVM 시스템 파라미터를 통해 프로퍼티 값을 전달한다면 마찬가지로 7000 포트로 실행됩니다.&lt;/p&gt;
&lt;h2&gt;환경변수의 활용&lt;/h2&gt;
&lt;p&gt;이번에는 &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일의 프로퍼티 값을 전달받아 스프링 애플리케이션 내부에서 사용하는 경우를 다뤄보겠습니다.&lt;/p&gt;
&lt;p&gt;다음과 같이 &lt;code class=&quot;language-text&quot;&gt;security.jwt.token.secret-key&lt;/code&gt;라는 환경변수를 주입받아 사용하는 스프링 빈을 하나 정의해봅시다. &lt;code class=&quot;language-text&quot;&gt;security.jwt.token.secret-key&lt;/code&gt;는 스프링에서 별도로 존재하는 프로퍼티가 아니라 &lt;strong&gt;개발자가 임의로 만든 프로퍼티&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ComponentWithSecretKey&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; secretKey&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ComponentWithSecretKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;${security.jwt.token.secret-key}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; secretKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;secretKey &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; secretKey&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 상태에서 별도의 작업 없이 즉시 애플리케이션을 실행하는 경우, 예외가 발생하며 서버는 즉시 종료합니다.&lt;/p&gt;
&lt;p&gt;이때 &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일에 다음과 같이 환경변수 값을 추가하고 애플리케이션을 실행하게 되면 애플리케이션은 문제 없이 동작합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yml&quot;&gt;&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;security.jwt.token.secret-key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; secret_value_that_is_really_long&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그런데 개발 과정에서 환경변수를 다룰 때 저희는 다양한 문제 상황에 직면하게 됩니다. 동일한 환경변수에 &lt;strong&gt;상황에 따라 다른 값이 주입되어야 하는 경우&lt;/strong&gt;도 존재할 수 있으며, 이러한 값들을 어떻게 &lt;strong&gt;외부로 노출되지 않도록 관리할 것인가&lt;/strong&gt;에 대해서도 고민해야 합니다. 이러한 문제에 대한 다양한 해결방안 중 가장 관리 포인트를 최소화하는 방안은 스프링 프로파일을 활용하는 것이라고 생각합니다.&lt;/p&gt;
&lt;h2&gt;스프링 프로파일과 환경변수&lt;/h2&gt;
&lt;p&gt;이제부터 본격적으로 스프링 프로파일을 활용하여 하나의 &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일 내에 다양한 프로퍼티 값들을 관리하는 방법에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;설명을 위해 &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt;로부터 두 가지 환경변수를 주입받아 사용하는 스프링 빈을 만들어봅시다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ComponentWithSecretKey&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; commonData&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; secretKey&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ComponentWithSecretKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;${common.data}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; commonData&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;${security.jwt.token.secret-key}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; secretKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;commonData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; commonData&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;secretKey &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; secretKey&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그리고 &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일에 다음과 같이 작성해봅시다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yml&quot;&gt;&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 디폴트로 사용하는 프로파일은 test&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;spring.profiles.active&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; test
&lt;span class=&quot;token key atrule&quot;&gt;common.data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;모든_프로파일들에서_공통으로_사용되는_데이터&quot;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 현재 프로파일이 prod인 경우, 해당 프로퍼티들을 사용&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;spring.config.activate.on-profile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; prod
&lt;span class=&quot;token key atrule&quot;&gt;security.jwt.token.secret-key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;prod 프로파일일 때 사용되는 secret key&quot;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 현재 프로파일이 test인 경우, 해당 프로퍼티들을 사용&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;spring.config.activate.on-profile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; test
&lt;span class=&quot;token key atrule&quot;&gt;security.jwt.token.secret-key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;test_프로파일일_때_사용되는_secret_key&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;설명하자면 &lt;code class=&quot;language-text&quot;&gt;---&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일 내에서 &lt;strong&gt;프로파일 영역별 경계를 설정&lt;/strong&gt;하는 기능입니다.&lt;/p&gt;
&lt;p&gt;특정 영역에 &lt;code class=&quot;language-text&quot;&gt;spring.config.activate.on-profile&lt;/code&gt; 프로퍼티를 설정하는 경우, &lt;strong&gt;해당 프로파일이 선택되었을 때만 해당 영역의 프로퍼티들이 사용&lt;/strong&gt;됩니다.&lt;/p&gt;
&lt;p&gt;반대로 &lt;code class=&quot;language-text&quot;&gt;spring.config.activate.on-profile&lt;/code&gt; 프로퍼티가 없는 영역의 프로퍼티들은 &lt;strong&gt;활성화된 프로파일과 무관하게 모든 프로파일들에서 공통적으로 사용&lt;/strong&gt;됩니다. 위의 경우 가독성을 위해 &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일의 최상단에 위치시켰으나, 아래쪽에 있어도 동일하게 동작합니다.&lt;/p&gt;
&lt;p&gt;그리고 &lt;code class=&quot;language-text&quot;&gt;spring.profiles.active&lt;/code&gt;는 디폴트로 사용될 프로파일을 명시하는 기능입니다. 해당 값을 명시하지 않는 경우 &lt;code class=&quot;language-text&quot;&gt;default&lt;/code&gt; 프로파일이 사용되며, JVM 시스템 프로퍼티를 통해 덮어쓸 수 있습니다. 프로파일명으로는 주로 &lt;code class=&quot;language-text&quot;&gt;dev&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;prod&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;test&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;local&lt;/code&gt; 등이 사용되지만
예약어는 아니므로 아무 이름이나 자유롭게 사용하면 됩니다.&lt;/p&gt;
&lt;p&gt;이론만이 아니라 실제로 실행했을 때의 동작방식을 생각해봅시다.&lt;/p&gt;
&lt;p&gt;우선 위와 같이 설정한 프로젝트를 있는 그대로 실행하게 되면 최상단 영역의 설정에 따라 &lt;code class=&quot;language-text&quot;&gt;common.data&lt;/code&gt; 값은 &lt;code class=&quot;language-text&quot;&gt;&quot;모든_프로파일들에서_공통으로_사용되는_데이터&quot;&lt;/code&gt;가 그대로 주입되며, 자동으로 &lt;code class=&quot;language-text&quot;&gt;test&lt;/code&gt; 프로파일이 선택됩니다. 이에 따라 &lt;code class=&quot;language-text&quot;&gt;security.jwt.token.secret-key&lt;/code&gt; 프로퍼티에는 &lt;code class=&quot;language-text&quot;&gt;test&lt;/code&gt; 프로파일 섹션에 설정한 &lt;code class=&quot;language-text&quot;&gt;&quot;test_프로파일일_때_사용되는_secret_key&quot;&lt;/code&gt;라는 내용이 주입됩니다.&lt;/p&gt;
&lt;p&gt;이 상태에서 사용될 프로파일을 수정하여 인텔리제이에서 실행하는 방법은 간단합니다. 우측 상단에서 &lt;code class=&quot;language-text&quot;&gt;Edit Configurations&lt;/code&gt;를 선택하고, &lt;code class=&quot;language-text&quot;&gt;Active profiles&lt;/code&gt;에 실행할 때 활성화시킬 프로파일을 기재하는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50986686/190946160-50e2aae8-d026-4f2e-a13e-0a0b15801763.png&quot; alt=&quot;인텔리제이 Edit config&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50986686/190946407-095cd9ae-e3dc-4bfb-8a45-67e0df160aa2.png&quot; alt=&quot;인텔리제이 프로파일&quot;&gt;&lt;/p&gt;
&lt;p&gt;이처럼 &lt;code class=&quot;language-text&quot;&gt;prod&lt;/code&gt; 프로파일을 활성화시킨 상태로 애플리케이션을 재실행하게 되면, 최상단 영역에 설정한 &lt;code class=&quot;language-text&quot;&gt;common.data&lt;/code&gt; 프로퍼티는 하드코딩한 값이 그대로 주입됩니다. 그러나 &lt;code class=&quot;language-text&quot;&gt;spring.profiles.active&lt;/code&gt; 프로퍼티의 값은 &lt;code class=&quot;language-text&quot;&gt;test&lt;/code&gt; 대신 &lt;strong&gt;외부로부터 주입받은 &lt;code class=&quot;language-text&quot;&gt;prod&lt;/code&gt;로 덮어씌어져 실행&lt;/strong&gt;됩니다. 이에 따라 &lt;code class=&quot;language-text&quot;&gt;security.jwt.token.secret-key&lt;/code&gt; 프로퍼티에는 &lt;code class=&quot;language-text&quot;&gt;prod&lt;/code&gt; 프로파일 섹션에 설정한 &lt;code class=&quot;language-text&quot;&gt;&quot;prod 프로파일일 때 사용되는 secret key&quot;&lt;/code&gt;라는 내용이 주입됩니다.&lt;/p&gt;
&lt;h3&gt;환경변수 하드코딩의 문제점&lt;/h3&gt;
&lt;p&gt;그런데 문제는 DB 관련 정보(주소, 사용자명, 비밀번호)와 같이 외부로부터 숨겨야 하는 프로퍼티 값이 존재하는 경우입니다.&lt;/p&gt;
&lt;p&gt;이러한 상황에서 &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일에 모든 비밀 정보를 직접적으로 하드코딩하고, &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일 자체를 &lt;code class=&quot;language-text&quot;&gt;.gitignore&lt;/code&gt;에 추가하여 &lt;strong&gt;깃의 관리 대상에서 완전히 누락시키는 방안&lt;/strong&gt;을 생각해볼 수 있습니다. 그러나 해당 방법의 문제점은 개발 프로세스가 굉장히 번거로워진다는 점입니다. 예를 들어 개발자마다 로컬 머신에 자신만의 &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일을 관리하여 작업을 하고, Jenkins 등 빌드 작업이 수행되는 곳에 &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일을 만들어 올려놓는다면 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일의 내용이 수정될 때마다 수작업으로 모든 yml 파일들을 고쳐야 합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;특히 &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일에서 &lt;strong&gt;외부에 숨겨야 하는 비밀 정보는 극히 일부분에 불과&lt;/strong&gt;합니다. 외부에 노출되어도 상관 없는 기본적인 설정들이 조금 수정될 때마다 모든 컴퓨터에서 관리되는 &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일들을 전부 수정해야 한다면 이는 너무나도 큰 비용입니다.&lt;/p&gt;
&lt;p&gt;바로 이러한 점 때문에 우리는 &lt;strong&gt;깃헙에 &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일을 올려놓은 상태에서&lt;/strong&gt; 오직 숨겨야 하는 비밀정보들만을 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일 내에서 숨길 방법&lt;/strong&gt;이 필요합니다.&lt;/p&gt;
&lt;h3&gt;yml 파일 내 환경변수 설정&lt;/h3&gt;
&lt;p&gt;이를 위해 저희는 &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일 내에서 &lt;strong&gt;외부로부터 환경변수를 받도록 설정&lt;/strong&gt;하고 프로그램이 &lt;strong&gt;실행될 때 적절한 값을 주입&lt;/strong&gt;해주는 방법을 생각해볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;설정하는 방법 자체는 간단합니다. 아래 예시와 같이 환경변수의 이름(&lt;code class=&quot;language-text&quot;&gt;secret-key&lt;/code&gt;)을 프로퍼티의 ‘값’에 해당되는 부분에 &lt;code class=&quot;language-text&quot;&gt;${환경변수}&lt;/code&gt; 형식으로 추가하면 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight has-highlighted-lines&quot; data-language=&quot;yml&quot;&gt;&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;spring.profiles.active&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; test
&lt;span class=&quot;token key atrule&quot;&gt;common.data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;모든_프로파일들에서_공통으로_사용되는_데이터&apos;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;spring.config.activate.on-profile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; prod
&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;security.jwt.token.secret-key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;secret&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;spring.config.activate.on-profile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; test
&lt;span class=&quot;token key atrule&quot;&gt;security.jwt.token.secret-key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;test_프로파일일_때_사용되는_secret_key&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;해당 프로젝트는 기본적으로 디폴트 프로파일인 &lt;code class=&quot;language-text&quot;&gt;test&lt;/code&gt;를 사용하므로 이전과 마찬가지로 별도의 작업 없이도 언제나 문제 없이 빌드 및 실행될 수 있습니다. 다만, &lt;code class=&quot;language-text&quot;&gt;prod&lt;/code&gt; 프로파일을 활성화시키는 경우 &lt;strong&gt;외부로부터 &lt;code class=&quot;language-text&quot;&gt;secret-key&lt;/code&gt;에 해당되는 환경변수를 전달받아야만 실행&lt;/strong&gt;됩니다.&lt;/p&gt;
&lt;h3&gt;인텔리제이에서의 환경변수 설정(2021.3 버전 기준)&lt;/h3&gt;
&lt;p&gt;그러면 인텔리제이에서 환경변수를 주입하여 실행해보겠습니다. 우선 위에서와 마찬가지로 우측 상단의 &lt;code class=&quot;language-text&quot;&gt;Edit Configurations&lt;/code&gt;를 선택하면 &lt;code class=&quot;language-text&quot;&gt;Active profiles&lt;/code&gt; 칸에 활성화시킬 프로파일을 기재할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50986686/190946407-095cd9ae-e3dc-4bfb-8a45-67e0df160aa2.png&quot; alt=&quot;인텔리제이 프로파일&quot;&gt;&lt;/p&gt;
&lt;p&gt;다음으로 &lt;code class=&quot;language-text&quot;&gt;Modify options&lt;/code&gt;를 선택하면 아래와 같이 환경변수를 입력할 수 있는 칸이 추가됩니다. 이를 통해 다음과 같이 &lt;code class=&quot;language-text&quot;&gt;secret-key&lt;/code&gt; 환경변수의 값을 자유롭게 설정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50986686/190946508-52cdeeb2-3444-449c-a736-275fcc58b157.png&quot; alt=&quot;인텔리제이 환경변수 설정&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50986686/190946573-f1155c9b-b1b6-4f2c-b09f-7e8c9b2fe6fb.png&quot; alt=&quot;인텔리제이 환경변수 설정 입력&quot;&gt;&lt;/p&gt;
&lt;p&gt;이처럼 인텔리제이에서 환경변수를 설정하는 방법은 &lt;strong&gt;각 프로젝트에 한정된 시스템 환경변수를 설정&lt;/strong&gt;할 수 있다는 특징을 지녔습니다. 개발자의 로컬 컴퓨터에서 프로그램을 실행하며 개발하고 싶을 때 사용할 수 있는 최선의 방법입니다.&lt;/p&gt;
&lt;p&gt;최신 버전에서는 Profile을 아래에서 서술할 Enviroment Variable의 설정 방식대로 수정할 수 있습니다&lt;/p&gt;
&lt;h3&gt;JVM 시스템 파라미터를 통한 환경변수 설정&lt;/h3&gt;
&lt;p&gt;한편 개발한 애플리케이션의 빌드 결과물을 원격 서버에서 실행하기 위해서는 리눅스 환경에서 특정 프로파일을 활성화시키고 적절한 환경변수 값을 주입해줄 수 있어야 합니다.&lt;/p&gt;
&lt;p&gt;우선 개인적으로 선호하는 JVM 시스템 파라미터를 통해 &lt;strong&gt;실행 명령어 한 줄에 모든 환경변수를 전달&lt;/strong&gt;하는 방법을 다뤄보겠습니다.&lt;/p&gt;
&lt;p&gt;아래와 같은 명령어를 통해 &lt;code class=&quot;language-text&quot;&gt;prod&lt;/code&gt; 프로파일을 활성화시키도록 주입하고, 환경변수 &lt;code class=&quot;language-text&quot;&gt;secret-key&lt;/code&gt;의 값을 주입할 수 있습니다. &lt;code class=&quot;language-text&quot;&gt;secret-key&lt;/code&gt;에 전달된 값은 그대로 &lt;code class=&quot;language-text&quot;&gt;security.jwt.token.secret-key&lt;/code&gt; 프로퍼티의 값으로 사용됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;java&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-jar&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-Dspring.profiles.active&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;prod -Dsecret-key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;prod_프로파일의_secret_key_값&apos;&lt;/span&gt; 애플리케이션.jar &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;물론 아래와 같이 환경변수 &lt;code class=&quot;language-text&quot;&gt;secret-key&lt;/code&gt;를 사용하지 않고 &lt;code class=&quot;language-text&quot;&gt;security.jwt.token.secret-key&lt;/code&gt; 프로퍼티 자체에 대해 값을 주입하는 것도 가능합니다. 그러나 아래에서 확인할 수 있듯 이러한 프로퍼티명은 일반적으로 굉장히 길기 때문에 가독성이 굉장히 낮아지며 휴먼 에러가 발생하기 쉬워집니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;java&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-jar&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-Dspring.profiles.active&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;prod -Dsecurity.jwt.token.secret-key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;prod_프로파일의_secret_key_값&apos;&lt;/span&gt; 애플리케이션.jar &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;해당 방법은 실행되는 명령어 자체에 모든 정보가 포함되기 때문에 &lt;strong&gt;주입해야 하는 프로퍼티의 개수가 얼마되지 않을 때 굉장히 관리하기 편리합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;운영체제 내 시스템 환경변수 설정&lt;/h3&gt;
&lt;p&gt;다음은 운영체제 자체에 &lt;strong&gt;시스템 환경변수&lt;/strong&gt;를 설정하는 방법입니다. &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일의 &lt;code class=&quot;language-text&quot;&gt;{환경변수}&lt;/code&gt; 문법은 JVM 파라미터만이 아니라 시스템 환경변수 값을 전달받을 수 있습니다.&lt;/p&gt;
&lt;p&gt;다만, 시스템 환경변수의 경우 기본적으로 &lt;code class=&quot;language-text&quot;&gt;CONSTANT_CASE&lt;/code&gt; 형식으로 작성해야 합니다. 현재 프로젝트의 경우 &lt;code class=&quot;language-text&quot;&gt;secret-key&lt;/code&gt; 대신 &lt;code class=&quot;language-text&quot;&gt;SECRET_KEY&lt;/code&gt;로 환경변수명을 수정해야 합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight has-highlighted-lines&quot; data-language=&quot;yml&quot;&gt;&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 생략&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;spring.config.activate.on-profile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; prod
&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;security.jwt.token.secret-key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;SECRET_KEY&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 생략&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;운영체제의 시스템 환경변수를 설정하는 방법은 환경에 따라 조금씩 차이가 있습니다. 그래도 일반적으로 맥이나 리눅스 환경에서는 &lt;code class=&quot;language-text&quot;&gt;.bash_profile&lt;/code&gt; 파일을 수정하여 환경변수를 등록하면 됩니다. 환경변수마다 &lt;code class=&quot;language-text&quot;&gt;export VARIABLE_NAME=&quot;value&quot;&lt;/code&gt; 형식으로 한줄씩 나열하여 복수의 환경변수를 설정할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;SECRET_KEY&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;secret_key_at_bash_profile&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;해당 세션에서 &lt;code class=&quot;language-text&quot;&gt;.bash_profile&lt;/code&gt;이 즉시 적용되도록 아래 명령어를 실행해봅시다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;source&lt;/span&gt; .bash_profile&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 상태로 아래 명령어를 통해 &lt;code class=&quot;language-text&quot;&gt;prod&lt;/code&gt; 프로파일을 활성화시켜 애플리케이션을 실행시켜봅시다. &lt;code class=&quot;language-text&quot;&gt;.bash_profile&lt;/code&gt; 파일에 설정해놓은 시스템 환경변수 &lt;code class=&quot;language-text&quot;&gt;SECRET_KEY&lt;/code&gt;가 문제 없이 등록되었다면, 자동으로 &lt;strong&gt;운영체제의 환경변수 값을 주입받아 사용&lt;/strong&gt;하게 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;java&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-Dspring.profiles.active&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;prod &lt;span class=&quot;token parameter variable&quot;&gt;-jar&lt;/span&gt; 애플리케이션.jar &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;만일 시스템 환경변수를 무시하고 다른 값을 주입하여 실행하고 싶은 경우, JVM 시스템 파라미터로 값을 넘기면 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;java&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-jar&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-Dspring.profiles.active&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;prod -Dsecurity.jwt.token.secret-key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;시스템_환경변수와는_다른_값&apos;&lt;/span&gt; 애플리케이션.jar &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;또한 운영체제에 등록한 시스템 환경변수를 제거하고 싶은 경우, 우선 &lt;code class=&quot;language-text&quot;&gt;.bash_profile&lt;/code&gt; 파일에 추가했던 &lt;code class=&quot;language-text&quot;&gt;export VARIABLE_NAME=&quot;value&quot;&lt;/code&gt; 부분을 제거해야 합니다. 이후 &lt;code class=&quot;language-text&quot;&gt;unset VARIABLE_NAME&lt;/code&gt; 명령어를 통해 특정한 이름의 환경변수를 제거할 수 있습니다. &lt;code class=&quot;language-text&quot;&gt;SECRET_KEY&lt;/code&gt;라는 이름의 환경변수를 제거하려는 경우, 아래와 같이 작성하면 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;unset&lt;/span&gt; SECRET_KEY&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;당연한 얘기지만 해당 방법은 &lt;strong&gt;EC2 인스턴스와 같은 원격 서버에서 사용&lt;/strong&gt;해야 합니다.&lt;/p&gt;
&lt;p&gt;개발자의 로컬 머신에서는 인텔리제이를 활용하여 개별 프로젝트에 대한 시스템 환경변수를 개별적으로 설정하는 것이 이상적입니다. 예를 들어 로컬에서 작업 중인 복수의 프로젝트에서 전부 &lt;code class=&quot;language-text&quot;&gt;DB_URL&lt;/code&gt;이라는 환경변수를 사용하는 경우를 생각해봅시다. 운영체제의 시스템 환경변수로 &lt;code class=&quot;language-text&quot;&gt;DB_URL&lt;/code&gt;이라는 값을 설정하는 경우, 다른 프로젝트를 실행할 때마다 매번 시스템 환경변수인 &lt;code class=&quot;language-text&quot;&gt;DB_URL&lt;/code&gt; 값을 수정해주는 등의 작업을 해야 할 것입니다.&lt;/p&gt;
&lt;h2&gt;맺으며&lt;/h2&gt;
&lt;p&gt;해당 글은 스프링 개발자라면 알아야 하는 환경변수에 관한 기본기를 제공하는 데 초점을 맞추었습니다. 다만, 이러한 기법들이 언제나 best practice가 아니라는 점에 유의해야 합니다. 예를 들어 복수의 서버를 운영하는 경우, 각 서버에서 사용될 환경변수를 실행 시점에 맞춤형으로 주입해주는 작업 자체가 번거로울 수 있습니다. 또한 협업하는 개발자들 사이에 환경변수 정보를 어떻게 공유하고 관리할 것인지에 대한 논의 또한 필요합니다.&lt;/p&gt;
&lt;p&gt;이런 경우에는 &lt;code class=&quot;language-text&quot;&gt;git submodule&lt;/code&gt;을 활용해 비공개 저장소에 프로파일별로 하드코딩된 &lt;code class=&quot;language-text&quot;&gt;yml&lt;/code&gt; 파일들을 관리하고, 빌드 결과물 자체에 적절한 프로퍼티들이 포함되도록 하는 것이 더 적절할 수 있습니다. 그러나 빌드 결과물에 환경변수들을 저장하더라도 특정한 프로파일을 활성화시키거나, 다른 값으로 덮어쓰는 등의 작업을 위해서는 JVM 시스템 파라미터와 같은 기본적인 지식이 필요하다고 생각합니다.&lt;/p&gt;
&lt;h2&gt;참고문서&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/spring-profiles&quot;&gt;Baeldung: Spring Profiles&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[공식 팀에서의 에러 헨들링]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-09-30-error-handling/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-09-30-error-handling/</guid><pubDate>Fri, 30 Sep 2022 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;현재 팀에서의 에러 핸들링&lt;/h1&gt;
&lt;p&gt;공식에서 비동기 통신에 대하여 제대로 된 에러 관리 방식이 없어서 잡지 못하는 에러도 많았고, 컴포넌트내에 에러 로직이 무더기로 있는 경우가 있었다.
그래서 지금부터 공식팀이 에러를 처리한 방식에 대해서 알아볼 것이다!&lt;/p&gt;
&lt;h2&gt;일반적인 에러 처리 방식들&lt;/h2&gt;
&lt;p&gt;먼저 일반적으로 하는 에러처리 방식에 대해서 조사해보았다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;try / catch&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ErrorBoundary&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;react query에서의 onError
이렇게 크게 3가지 방식으로 처리를 많이하는데 이번에는 &lt;strong&gt;ErrorBoundary&lt;/strong&gt;를 이용하여 에러를 처리하기로 하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ko.reactjs.org/docs/error-boundaries.html&quot;&gt;에러 바운더리 공식문서&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;왜 ErrorBoundary를 이용했는가?&lt;/strong&gt;
ErrorBoundary를 이용할시 &lt;strong&gt;선언적&lt;/strong&gt;으로 에러를 처리할수 있다는 장점이 있습니다. try / catch방식을 이용하면 컴포넌트내에 내가 실패했을때 어떤 처리를 할지 남아있게 됩니다. 이렇게 되면 화면을 렌더하는게 주 목적인 컴포넌트가 에러에 대한 처리까지 가지게 됩니다. &lt;br&gt;&lt;br&gt; Errorboundary를 이용하게 되면 에러에 대한 로직을 부모에 위임할수 있으므로 컴포넌트내에서는 성공했을 때만을 신경쓰면 됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;커스텀 에러 코드&lt;/h2&gt;
&lt;p&gt;현재 공식에서는 프로젝트에서 커스텀 에러 코드를 이용하여 에러를 구분하고 처리하고 있습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;에러 번호&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;메시지&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0000&lt;/td&gt;
&lt;td&gt;예상하지 못한 서버 예외가 발생할 때&lt;/td&gt;
&lt;td&gt;알 수 없는 에러가 발생했습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0001&lt;/td&gt;
&lt;td&gt;요청 데이터가 잘못된 경우&lt;/td&gt;
&lt;td&gt;요청 데이터가 잘못되었습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1001&lt;/td&gt;
&lt;td&gt;깃허브에서 응답받은 토큰이 null일 때&lt;/td&gt;
&lt;td&gt;Github API에서 Accesstoken을 받는것에 실패했습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1002&lt;/td&gt;
&lt;td&gt;깃허브에서 유저 정보를 불러오는 것에 실패할 경우&lt;/td&gt;
&lt;td&gt;Github API에서 사용자 정보를 받는 것에 실패했습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1003&lt;/td&gt;
&lt;td&gt;프론트에서 요청하는 토큰이 null일 때&lt;/td&gt;
&lt;td&gt;토큰이 존재하지 않습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1004&lt;/td&gt;
&lt;td&gt;토큰 타입이 잘못되었을 때&lt;/td&gt;
&lt;td&gt;토큰 타입이 올바르지 않습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1005&lt;/td&gt;
&lt;td&gt;유효하지 않은 토큰일 때&lt;/td&gt;
&lt;td&gt;accessToken 이 유효하지 않습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1006&lt;/td&gt;
&lt;td&gt;http servlet request가 null일 때,&lt;/td&gt;
&lt;td&gt;인증할 수 있는 사용자 데이터가 없습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1007&lt;/td&gt;
&lt;td&gt;권한이 없는 경우 : 작성자가 아닐 때 (수정, 삭제)&lt;/td&gt;
&lt;td&gt;작성자가 아니므로 권한이 없습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1008&lt;/td&gt;
&lt;td&gt;권한이 없는 경우 : 회원이 아닐 때 (수정, 삭제)&lt;/td&gt;
&lt;td&gt;회원이 아니므로 권한이 없습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1009&lt;/td&gt;
&lt;td&gt;리프레시 토큰이 유효하지 않을 때&lt;/td&gt;
&lt;td&gt;리프레시 토큰이 유효하지 않습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1010&lt;/td&gt;
&lt;td&gt;유효한 액세스 토큰으로 리프레시 토큰을 발급받을 수 없다.&lt;/td&gt;
&lt;td&gt;“유효한 액세스 토큰으로 리프레시 토큰을 발급할 수 없습니다.”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1011&lt;/td&gt;
&lt;td&gt;리프레시 토큰을 요청할 때, 액세스 토큰이 유효하지 않는 경우&lt;/td&gt;
&lt;td&gt;권한이 없는 유저입니다. 재로그인을 해주세요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5009&lt;/td&gt;
&lt;td&gt;투표 항목수가 2보다 작거나 5보다 클 때&lt;/td&gt;
&lt;td&gt;투표 항목 수는 2이상 5이하여야 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6001&lt;/td&gt;
&lt;td&gt;해시태그 이름이 비었을 때&lt;/td&gt;
&lt;td&gt;해시태그 이름은 비어있을 수 없습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6002&lt;/td&gt;
&lt;td&gt;해시태그 이름이 2자 미만이거나 20자 초과일 때&lt;/td&gt;
&lt;td&gt;해시태그 이름은 2자 이상 20자 이하입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6003&lt;/td&gt;
&lt;td&gt;해시태그 이름이 중복될 때(대소문자 구분x)&lt;/td&gt;
&lt;td&gt;해시태그 이름은 중복될 수 없습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6004&lt;/td&gt;
&lt;td&gt;한 게시글당 태그가 5개 초과일 때&lt;/td&gt;
&lt;td&gt;해시태그는 한 게시글 당 최대 5개입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이렇게 커스텀 에러코드에 따라서 어떤 로직을 실행할지 어떤 ui를 보여줄지를 유연하게 설정할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;프로젝트에서 발생할수 있는 에러의 종류&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;서버문제로 인한 500번대 에러&lt;/li&gt;
&lt;li&gt;런타임 환경에서의 에러&lt;/li&gt;
&lt;li&gt;일반적인 API요청에 대한 에러&lt;/li&gt;
&lt;li&gt;네트워크 문제로 인한 에러&lt;br /&gt;
현재 공식에서 발생할 수 있는 에러는 다음 4개로 나누어 보았습니다.
공식에서는 react-query를 이용하기에 error.response.data에서 에러를 잡을수 있습니다.
그래서 발생할수 있는 4가지 상황에서 다음과 같이 잡을 수 있도록 처리하였습니다.&lt;/li&gt;
&lt;li&gt;서버문제로 인한 500번대 에러
&lt;blockquote&gt;
&lt;p&gt;error.response.data.errorCode === ‘0000’ → 서버 에러 화면을 보여준다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;런타임 환경에서의 에러
&lt;blockquote&gt;
&lt;p&gt;타입스크립트를 이용해서 컴파일 환경에서 최대한 잡아주었다. → 잡지못한 에러(error.response === undefined)는 개발자가 알 수 있도록 에러를 던져줌&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;일반적인 API요청에 대한 에러
&lt;blockquote&gt;
&lt;p&gt;error.response.data.errorCode → 에러코드에 따라 로직을 수행하거나 대체 화면을 보여준다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;네트워크 문제로 인한 에러
&lt;blockquote&gt;
&lt;p&gt;error.response.data === undefined → 다시 ‘0000’ 커스텀 에러를 던져서 서버 에러 화면을 보여준다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2&gt;그래서 어떻게 에러를 처리했을까?&lt;/h2&gt;
&lt;p&gt;에러를 처리하는 방식으로는 &lt;code class=&quot;language-text&quot;&gt;ErrorBoundary&lt;/code&gt;를 에러를 구분할 수 있는 것은 &lt;code class=&quot;language-text&quot;&gt;커스텀 에러코드&lt;/code&gt;로 마지막으로 &lt;code class=&quot;language-text&quot;&gt;어디서 에러가 발생할 수 있는지&lt;/code&gt;까지 조사하였습니다.
그렇다면 어떻게 깔끔하게 여러 군데에서 발생하는 에러를 잡아줄 수 있을까?&lt;/p&gt;
&lt;p&gt;공식에서는 &lt;code class=&quot;language-text&quot;&gt;ErrorBoundary&lt;/code&gt;를 이용한 &lt;strong&gt;중앙집중식 에러핸들링&lt;/strong&gt;을 시도하였습니다.
Error의 특징인 버블링되어 올라간다는 것을 이용하여 여러 컴포넌트에서 발생하는 에러들을 하나의 &lt;code class=&quot;language-text&quot;&gt;전역&lt;/code&gt; 공간에서 처리해주었습니다.
&lt;img src=&quot;https://velog.velcdn.com/images/baby_dev/post/ec60e69c-7573-4313-947e-a93ee08941b3/image.png&quot; alt=&quot;&quot;&gt;
여기서 하나의 에러바운더리에서 모두 처리하는 것이 아니라 관심사의 분리를 위하여 2개의 ErrorBoundary로 나누었습니다.
에러가 발생할 시 대체화면을 보여주는 &lt;code class=&quot;language-text&quot;&gt;FallbackErrorBoundary&lt;/code&gt;, 화면을 바꾸지 않고 특정 로직을 수행하는 &lt;code class=&quot;language-text&quot;&gt;LogicErrorBoundary&lt;/code&gt; 2개로 나누어서 관리하였고 각각은 &lt;code class=&quot;language-text&quot;&gt;CommonErrorBoundary&lt;/code&gt;라는 공용으로 사용되는 메서드를 포함한 ErrorBoundary를 상속하여 구현하였습니다.&lt;/p&gt;
&lt;h3&gt;예시&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LogicErrorBoundary&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LogicErrorBoundary&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CommonErrorBoundary&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LogicErrorBoundaryProps&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token literal-property property&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LogicErrorBoundaryProps &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; ErrorBoundaryProps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;componentDidUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token literal-property property&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; never&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;prevState&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ErrorBoundaryState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prevState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; showSnackBar&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; navigate &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; errorCode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;errorCode&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; errorCode &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;undefined&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; showSnackBar &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;undefined&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; navigate &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;undefined&apos;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;9999&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;빠진 인자가 없는지 확인해주세요&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; errorMessage &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ErrorMessage&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;errorCode&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isExpiredTokenError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;errorCode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;navigate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;REFRESH_TOKEN_HANDLER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isVoteError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;errorCode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;navigate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isNotAccessVoteError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;errorCode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;navigate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;CATEGORY_DISCUSSION&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isCommentError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;errorCode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			queryClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invalidateQueries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;comments&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isAlreayLoginRefreshTokenError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;errorCode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;navigate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;HOME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isInvalidRefreshTokenError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;errorCode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			localStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;ACCESSTOKEN_KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;HOME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 모든 에러에 대해서는 스낵바를 보여준다.&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;showSnackBar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;errorMessage&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// Server에러나 NotFound에러는 LogicErrorBoundary의 부모인 FallbackErrorBoundary에게 에러를 던져 처리를 위임한다.&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isServerError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;errorCode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isNotFoundArticleError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;errorCode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;errorCode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; prevState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FallbackErrorBoundary&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FallbackErrorBoundary&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CommonErrorBoundary&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FallbackErrorBoundaryProps&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token literal-property property&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; FallbackErrorBoundaryProps &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; ErrorBoundaryProps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;componentDidUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token literal-property property&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; never&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;prevState&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ErrorBoundaryState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prevState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prevState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; children&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; serverErrorFallback&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; NotFoundErrorFallback &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;errorCode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;0000&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; serverErrorFallback&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NotFoundErrorFallback&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; children&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;중앙집중식 에러 핸들링의 장점?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;에러를 추가하거나 수정할때 더 편하게 처리할 수 있었습니다.&lt;/li&gt;
&lt;li&gt;컴포넌트를 만들때 마다 에러를 잡기위해 에러바운더리로 감싸야 한다는 것도 신경쓰지 않아도 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;class에서 hook을 사용하고 싶어..&lt;/h2&gt;
&lt;p&gt;위의 &lt;code class=&quot;language-text&quot;&gt;LogicErrorBoundary&lt;/code&gt;를 보면 showSnackBar, navigate 라는것을 이용하여 에러가 발생한 후처리를 합니다.
하지만 showSnackBar는 &lt;code class=&quot;language-text&quot;&gt;useSnackBar&lt;/code&gt;라는 커스텀 훅에서, navigate는 &lt;code class=&quot;language-text&quot;&gt;useNavigate&lt;/code&gt;라는 react-router-dom의 내장 훅을 이용합니다.
하지만 class형 컴포넌트에서는 이러한 훅을 이용할 수 없었기에 class에서 훅을 쓸 수 있게하는 연결체 HOC를 만들어서 처리하였습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useNavigate &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;react-router-dom&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; useSnackBar &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@/hooks/common/useSnackBar&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; WithHooksHOC&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;F&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;(Component: React.ComponentType&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;F&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;) &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Hoc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token literal-property property&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;F&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; showSnackBar &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useSnackBar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; navigate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useNavigate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Component&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token spread&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;showSnackBar&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;showSnackBar&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;navigate&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;navigate&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
export default WithHooksHOC;
// LogicErrorBoundary
export default WithHooksHOC(LogicErrorBoundary);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 이용할시 LogicErrorBoundary내에서도 훅에서 리턴하는 값을 props로 받아서 이용할 수가 있게됩니다.&lt;/p&gt;
&lt;h2&gt;에러도 선언적으로 던지자!&lt;/h2&gt;
&lt;p&gt;에러 바운더리는 Promise에러를 잡을수 없습니다. 사실 try/catch도 동일합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 비동기 에러 발생!&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이런 방식으로 코드를 작성을 한다면 catch에서 에러가 잡히길 기대하지만 사실은 &lt;code class=&quot;language-text&quot;&gt;Uncaught promise Error&lt;/code&gt;의 메시지를 만나게 될것입니다.
이에 대해서 설명하기 위해 잠시 &lt;code class=&quot;language-text&quot;&gt;이벤트 루프&lt;/code&gt; 개념이 나오게 됩니다.
try에서 실행한 비동기 로직은 &lt;code class=&quot;language-text&quot;&gt;callback queue&lt;/code&gt;에 들어가게 되고, 이 &lt;code class=&quot;language-text&quot;&gt;callback queue&lt;/code&gt;는 모든 콜스택이 비어야 실행을 합니다.
그래서 실제로 에러가 던져지는 시점은 catch문까지 모두 끝난시점이기때문에 에러를 잡을수 없는것입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그래서 에러를 선언적으로 던지자&lt;/strong&gt;랑은 무슨 상관..?
에러 바운더리에서 에러를 잡게 하기위해서 error를 상태로 관리하여 component의 &lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt;에서 던져주면 promise가 벗겨진 상태로 에러를 던지게 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setError&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;some url&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;//&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;하지만 공식에서는 react-query를 이용하기 때문에 이렇게 복잡한 과정없이 error를 받을 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ex)&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;9999&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;런타임 에러&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;undefined&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;0000&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;네트워크에 에러가 발생하였습니다.&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 네트워크 에러&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;errorCode&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      ErrorMessage&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;errorCode&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 일반 API에러&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;하지만 이 역시 컴포넌트마다 커스텀 에러를 던지는 로직이 있기 때문에 모처럼 에러를 선언적으로 처리한 보람이 없습니다. 그래서 에러를 던지는것 마저도 &lt;code class=&quot;language-text&quot;&gt;useThrowCustomError&lt;/code&gt;라는 커스텀 훅을 이용하여 처리하였습니다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;이렇게 하여서 에러를 선언적으로 처리하여 컴포넌트내에서의 가독성을, 중앙집중식으로 에러를 관리하여 에러처리의 수정, 확장등이 간편해지도록 관리하였습니다.
추후 &lt;code class=&quot;language-text&quot;&gt;Sentry&lt;/code&gt;라는 라이브러리를 이용하여 미처 잡지 못한 에러들에 대해서 추적할 수 있도록 할 예정입니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Life of Pixel]]></title><description><![CDATA[이 글은 Google Chrome 개발자 Steve Kobes의 Life of Pixel영상을 보고 정리한 내용입니다. 사용된 이미지들은 Steve Kobes의 허락
하에 slide show에서 가져왔습니다. 이 글의 목표 어떤 과정을 통해 HTML…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-09-26-life-of-pixel/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-09-26-life-of-pixel/</guid><pubDate>Mon, 26 Sep 2022 12:00:00 GMT</pubDate><content:encoded>&lt;br&gt;
&lt;p&gt;이 글은 Google Chrome 개발자 Steve Kobes의 &lt;a href=&quot;https://www.youtube.com/watch?v=K2QHdgAKP-s&quot;&gt;Life of Pixel영상&lt;/a&gt;을 보고 정리한 내용입니다. 사용된 이미지들은 &lt;a href=&quot;https://user-images.githubusercontent.com/52737532/195376209-a2eed93c-dbd1-4880-b051-bf33cd869cdf.png&quot; target=&quot;_blank&quot;&gt;Steve Kobes의 허락&lt;/a&gt;
하에 &lt;a href=&quot;https://docs.google.com/presentation/d/1boPxbgNrTU0ddsc144rcXayGA_WF53k96imRH8Mp34Y/edit#slide=id.ga884fe665f_64_6&quot;&gt;slide show&lt;/a&gt;에서 가져왔습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h1&gt;이 글의 목표&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;어떤 과정을 통해 HTML파일이 화면에 그려지고, page가 되는지 이해합니다.&lt;/li&gt;
&lt;li&gt;composite에 대해 이해합니다.&lt;/li&gt;
&lt;li&gt;왜 layer를 분리하는것이 더 빠른 렌더링을 가능하게 하는지 이해합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;간단 요약&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159612-0e7e1ae1-245e-4c9d-92ae-bef108f81aa2.png&quot; alt=&quot;sc_-36&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;HTML content(index.html)를 불러옵니다.&lt;/li&gt;
&lt;li&gt;HTML Parser가 index.html을 parsing 하면서 DOM을 생성합니다. (DOM)&lt;/li&gt;
&lt;li&gt;style 자원을 만나면 CSS Parser가 parsing 후, 각 DOM 노드에 computedStyle을 적용합니다. (style)&lt;/li&gt;
&lt;li&gt;DOM과 computedStyle을 바탕으로 각 노드의 position과 size를 계산합니다. (layout)&lt;/li&gt;
&lt;li&gt;layout tree를 바탕으로 layer tree를 생성합니다. (comp.assign)&lt;/li&gt;
&lt;li&gt;노드를 어떻게 그릴지에 대한 작업 순서를 정합니다. (paint)&lt;/li&gt;
&lt;li&gt;작업 순서를 compositor thread (=impl) 에게 전달합니다. (commit)&lt;/li&gt;
&lt;li&gt;GPU process에서 paint 단계 때 정한 작업 순서들을 바탕으로 bitmap을 생성합니다. (rasterization)&lt;/li&gt;
&lt;li&gt;이제 bitmap을 OpenGL을 통해 화면에 그립니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그림에 나오는 중간에 prepaint, tiling, SKIA는 아래 쪽에서 추가로 설명하겠습니다.&lt;/p&gt;
&lt;h1&gt;화면에 그려지는 과정&lt;/h1&gt;
&lt;h2&gt;Parsing&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159617-6a28988b-2bc6-434c-b537-f911a3318c63.png&quot; alt=&quot;sc_-41&quot;&gt;&lt;/p&gt;
&lt;p&gt;HTMLParser는 HTML tag를 읽으면서 DOM Tree를 생성합니다.&lt;/p&gt;
&lt;p&gt;조금 더 구체적인 parsing 과정은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://develoger.kr/wp-content/uploads/parsing-1200x665.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;from : &lt;a href=&quot;https://web.dev/critical-rendering-path-constructing-the-object-model/&quot;&gt;https://web.dev/critical-rendering-path-constructing-the-object-model/&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;DOM&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159624-f6079a53-0751-4379-b76b-4ce85101b799.png&quot; alt=&quot;sc_-43-1&quot;&gt;&lt;/p&gt;
&lt;p&gt;DOM은 두 가지를 의미합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;HTML Tag를 parsing 해서 만든 C++로 이루어진 트리 형태의 자료 구조 (내부 구현체)&lt;/li&gt;
&lt;li&gt;이 내부 구현체에 Javascript로 접근해서 조작할 수 있도록 만든 API&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Style Calculation&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159629-40f8532e-39fa-4c88-8b84-c5d8e221d165.png&quot; alt=&quot;sc_-45-1&quot;&gt;&lt;/p&gt;
&lt;p&gt;CSSParser는 CSS코드를 파싱하면서 StyleRule을 생성하고 그것을 StyleSheetContents에 담습니다.&lt;/p&gt;
&lt;p&gt;다시 말해서,&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;css&quot;&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.fancy-button&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; green&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 3px solid red&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1em&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이런 css코드를 파싱해서&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159643-910ba991-5d2b-4c80-891d-2497ab2c3f1e.png&quot; alt=&quot;sc_-56&quot;&gt;&lt;/p&gt;
&lt;p&gt;이런 StyleRule을 만들어 내는것이라 &lt;strong&gt;추측&lt;/strong&gt;됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159652-7ba1694d-7b2a-4970-97b5-fa675bec2c57.png&quot; alt=&quot;sc_-57&quot;&gt;&lt;/p&gt;
&lt;p&gt;StyleResolver는 StyleRule이 담겨있는 StyleSheetContents를 바탕으로 각 노드(Element)에 대한 ComputedStyle을 구해서 적용합니다.&lt;/p&gt;
&lt;p&gt;ComputedStyle은 CSS selector 우선순위까지 다 고려(cascading)해서 최종적으로 노드에 적용될 CSS값들의 모음입니다. dev-tools에서 쉽게 확인 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159666-b660e116-2937-4671-90b2-b60ae29d0fbe.png&quot; alt=&quot;sc_-58&quot;&gt;&lt;/p&gt;
&lt;h3&gt;쉽게 말하자면&lt;/h3&gt;
&lt;p&gt;CSS코드를 파싱하고, DOM의 각 노드에 대한 ComputedStyle을 구해서 적용합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159688-b44bf7c4-12fd-44de-a6c8-61f6e1aa504d.png&quot; alt=&quot;sc_-59&quot;&gt;&lt;/p&gt;
&lt;p&gt;from : &lt;a href=&quot;https://developer.chrome.com/blog/inside-browser-part3/#subresource-loading&quot;&gt;https://developer.chrome.com/blog/inside-browser-part3/#subresource-loading&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Layout&lt;/h2&gt;
&lt;p&gt;이제 DOM을 만들었고 각 노드에 대한 style도 알았으니, 위치와 사이즈 값도 알아낼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159692-d7159e57-7ae2-4661-bf0e-c9872ffb483c.png&quot; alt=&quot;sc_-61&quot;&gt;&lt;/p&gt;
&lt;h3&gt;너무 어려운 Layout 과정&lt;/h3&gt;
&lt;p&gt;그런데 이 과정은 꽤나 어렵습니다. 고려해야 할 것이 많기 때문입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159696-0fb835d2-e3db-42e6-9934-a016b6846054.png&quot; alt=&quot;sc_-62&quot;&gt;&lt;/p&gt;
&lt;p&gt;이렇게 라인이 넘어가는 것도 고려해주어야하고,&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159703-a9b3992e-188e-4b52-91e2-5026ee2cb1fc.png&quot; alt=&quot;sc_-63&quot;&gt;&lt;/p&gt;
&lt;p&gt;font도 고려해주어야 합니다. 이 외에도 overflow나 float속성도 고려해서 위치 값을 계산해야 합니다.&lt;/p&gt;
&lt;p&gt;제가 어디서 읽은 바로는 크롬 브라우저 개발자의 대부분이 이 layout계산쪽에서 일을 한다고 합니다. 그 정도로 복잡하고 어려운 일이라고 합니다.&lt;/p&gt;
&lt;h3&gt;Layout Tree 생성&lt;/h3&gt;
&lt;p&gt;메인 스레드에서 이 레이아웃을 계산하면서 레이아웃 트리를 생성합니다. 레이아웃 트리의 LayoutObject들은 각각 DOM Tree의 노드와 연결되어 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159710-4067506f-3d0e-4fa7-936c-d57abfc0343c.png&quot; alt=&quot;sc_-64&quot;&gt;&lt;/p&gt;
&lt;p&gt;우리가 흔히 아는 reflow가 바로 여기서 발생합니다. 즉, reflow는 layout tree를 순회하면서 각 LayoutObject의 위칫값과 사이즈를 다시 계산하는 것을 의미합니다.&lt;/p&gt;
&lt;p&gt;예를 들어서, DOM Element의 width/height/top/right 등을 바꾸면 이 layout tree를 순회하면서 LayoutObject의 위칫값과 사이즈를 다시 계산합니다.&lt;/p&gt;
&lt;h2&gt;Paint&lt;/h2&gt;
&lt;p&gt;이제 각 노드의 스타일과 위칫값을 알았으니, 화면에 그리는 일만 남았습니다. 하지만! 이름과 달리 &lt;strong&gt;paint 단계는 화면에 그리는 일을 하는 단계가 아닙니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;paint 단계에서는 화면에 무엇을 어떤 순서로 그려야 할지에 대한 정보를 기록하는 일이 이뤄집니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159724-0acade83-abd2-4736-9260-75f1fc6278a4.png&quot; alt=&quot;sc_-65&quot;&gt;&lt;/p&gt;
&lt;p&gt;PO즉, Paint Operation은 “[100, 200]에 가로가 200px, 세로가 140px인 사각형을 그려!” 라는 작업 내용입니다. 이것들이 쌓여서 DisplayItem에 들어가고, 이 DisplayItem의 목록은 PaintArtifact에 들어갑니다.&lt;/p&gt;
&lt;p&gt;DisplayItem을 조금 더 확대해보면 이렇게 생겼습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159731-7a47a69b-43f2-43a8-9a1e-63d92a3fb279.png&quot; alt=&quot;sc_-68-1&quot;&gt;&lt;/p&gt;
&lt;p&gt;위 설명에 대한 조금 더 쉬운 그림은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159738-4445cf16-174b-49f7-9bb2-755477dc14b6.png&quot; alt=&quot;sc_-66&quot;&gt;&lt;/p&gt;
&lt;p&gt;from : &lt;a href=&quot;https://developer.chrome.com/blog/inside-browser-part3/#subresource-loading&quot;&gt;https://developer.chrome.com/blog/inside-browser-part3/#subresource-loading&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;z-index를 고려하라&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159745-f6fb4b83-1678-49b8-8934-368b128a394f.png&quot; alt=&quot;sc_-67-1&quot;&gt;&lt;/p&gt;
&lt;p&gt;z-index도 잘 고려돼서 그려질 수 있도록 PO가 구성됩니다.&lt;/p&gt;
&lt;h2&gt;Raster&lt;/h2&gt;
&lt;p&gt;이제 어떤 순서로 그려야 할지 알았기 때문에, 화면에 그리는 일만 남았다고 생각할 수 있지만, 아직 조금 더 남았습니다.&lt;/p&gt;
&lt;p&gt;위에서 구한 정보들(DisplayItem)을 바탕으로 bitmap을 만드는 일을 rasterization이라고 부릅니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159751-f8a94efd-0235-4f96-99a3-bebe14deb18a.png&quot; alt=&quot;sc_-69-2&quot;&gt;&lt;/p&gt;
&lt;p&gt;그리고 이 rasterization은 일반적으로 GPU 안에서 이뤄집니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159756-2fffa8fb-e5fc-4d16-b2ab-23c105a05459.png&quot; alt=&quot;sc_-70-2&quot;&gt;&lt;/p&gt;
&lt;p&gt;현재 사용 중인 크롬 브라우저에서 GPU 가속이 사용되고 있는지 확인하려면 &lt;a href=&quot;chrome://gpu/&quot;&gt;chrome://gpu/&lt;/a&gt; 요기로 들어가면 됩니다.&lt;/p&gt;
&lt;img width=&quot;500px&quot; alt=&quot;sc_-71-1&quot; src=&quot;https://user-images.githubusercontent.com/52737532/192159765-a776cba5-7661-4276-ba26-8a7e976c56fc.png&quot;&gt;
&lt;p&gt;Rasterization에 Hardware accelerated가 되어있는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;h3&gt;raster to screen&lt;/h3&gt;
&lt;p&gt;이렇게 rasterization 되고 나서 screen에 pixel로 그려지는 작업도 GPU에서 일어납니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159769-16ab1039-8f26-4a74-a017-5ea9b8c174f7.png&quot; alt=&quot;sc_-73-3&quot;&gt;&lt;/p&gt;
&lt;h4&gt;SKIA&lt;/h4&gt;
&lt;p&gt;다양한 하드웨어 및 소프트웨어 플랫폼에서 작동하는 공통 API를 제공하는 오픈 소스 2D 그래픽 라이브러리입니다. 구글 크롬, 크롬 OS, 안드로이드, Flutter 등 여러 제품의 그래픽 엔진 역할을 합니다. 구글에서 만들었습니다.&lt;/p&gt;
&lt;h4&gt;OpenGL&lt;/h4&gt;
&lt;p&gt;그래픽 카드와 통신할 수 있도록 지원해주는 API 또는 표준 규격입니다. 이 API는 GPU에게 직접적으로 명령을 내리는 command로 변환됩니다.&lt;/p&gt;
&lt;h4&gt;Skia와 OpenGL의 관계&lt;/h4&gt;
&lt;p&gt;Skia 라이브러리에서 제공하는 API를 사용하면 OpenGL API로 변환됩니다. 즉, Skia는 조금 더 고수준의 API이죠.&lt;/p&gt;
&lt;h3&gt;쉽게 설명하자면&lt;/h3&gt;
&lt;p&gt;Paint이후 GPU에서 rasterization이 이뤄지고 화면에 pixel로 그려집니다.&lt;/p&gt;
&lt;h1&gt;화면에 그린 이후 변화 발생&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159778-abd58db4-af18-4a45-a2ae-32797246892f.png&quot; alt=&quot;sc_-74-4&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;DOM -&gt; style -&gt; layout -&gt; paint -&gt; raster -&gt; GPU -&gt; 화면에 그리기!&lt;/code&gt; 까지 왔습니다. 그런데 사용자가 스크롤을 하거나, 줌인/아웃을 하거나 Javascript로 style을 동적으로 바꾸면 브라우저는 이를 어떻게 처리할까요?&lt;/p&gt;
&lt;h2&gt;Frame&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159783-3f7b1e50-b1da-4577-affc-aca296cf700c.png&quot; alt=&quot;sc_-75-1&quot;&gt;&lt;/p&gt;
&lt;p&gt;초당 60 frame을 그리지 못하면, 화면이 뚝뚝 끊겨 보이는(janky) 현상이 발생합니다.&lt;/p&gt;
&lt;h2&gt;Invalidation&lt;/h2&gt;
&lt;p&gt;렌더링이 빠르게 되도록 하는 여러 방법 중 하나는, 변했는지 변하지 않았는지 체크하고 변한 부분만 업데이트하는 것입니다. 예를 들어서&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159791-5aeaf923-6a91-4977-add0-db92f7c03774.png&quot; alt=&quot;sc_-77-1&quot;&gt;&lt;/p&gt;
&lt;p&gt;DOM노드의 style에 변화가 가해졌으니 다음 프레임때 computedStyle을 다시 구할 필요가 있기 때문에, 표시(mark)해 놓습니다.(SetNeedsStyleRecalc 호출)&lt;/p&gt;
&lt;p&gt;마찬가지로 Layout에 변화가 가해졌다면, 다음 프래임 때 layout을 다시 계산하도록 표시해 놓습니다(SetNeedsLayout).&lt;/p&gt;
&lt;p&gt;변화가 없다면 이전 프레임에서 계산된 결과(DOM Node, LayoutObject 등)를 그대로 사용합니다.&lt;/p&gt;
&lt;p&gt;이렇게 변화가 가해졌을 때 다음 프레임에 새로 계산하도록 표시하는 것을 invalidation이라고 부릅니다. (slide에는 granular asynchronous invalidations 라고 적혀있습니다)&lt;/p&gt;
&lt;h2&gt;repaint&lt;/h2&gt;
&lt;p&gt;하지만 스크롤이나 애니메이션 같은 경우에는 위의 optimization이 큰 효과를 못봅니다. 너무 많이 변하기 때문입니다. 예를 들어서, 스크롤 같은 경우&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159797-74e9f573-0815-462b-9886-bc3a481c6bad.png&quot; alt=&quot;sc_-79-1&quot;&gt;&lt;/p&gt;
&lt;p&gt;매 스크롤 이벤트마다 repaint와 rasterization이 계속 발생합니다. 이는 비용이 많이 들어갑니다.&lt;/p&gt;
&lt;h2&gt;jank&lt;/h2&gt;
&lt;p&gt;scroll으로 인한 repaint - rasterization 외에도 우리가 신경 써야할 것이 있습니다. 바로 Javascript도 main thread에서 실행된다는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159803-a1c5c147-8952-40a7-8d3a-8fcafe487d0f.png&quot; alt=&quot;sc_-80-2&quot;&gt;&lt;/p&gt;
&lt;p&gt;(&lt;code class=&quot;language-text&quot;&gt;scroll로 인한 repaint - rasterization은 어쩔 수 없는 것인데 왜 신경 써야 하나?..&lt;/code&gt;라고 생각하실 수 있습니다. 이는 아래 compositor쪽에 이야기가 다시 나옵니다.)&lt;/p&gt;
&lt;p&gt;그래서 아무리 rendering pipeline이 빠르게 진행된다고 하더라도 javascript 코드 실행이 너무 오래 걸리면 jank가 발생합니다.&lt;/p&gt;
&lt;h1&gt;Compositing&lt;/h1&gt;
&lt;p&gt;그래서 invalidation 같은 최적화 기능도 있지만, scroll로 인한 repaint + rasterization과 Javascript 코드를 실행하는 비용이 많이 들어서 rendering이 늦어지는 문제를 완화하기 위해 compositing이 나왔습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159812-5b0227c9-05e3-4e4e-aeca-aeea90847762.png&quot; alt=&quot;sc_-82-1&quot;&gt;&lt;/p&gt;
&lt;p&gt;먼저 메인 스레드에서 page를 여러 layer로 나누고 compositor thread에서 이를 합성합니다. 이렇게 layer를 나누면 rasterizing이 각 layer에서 독립적으로 발생합니다.&lt;/p&gt;
&lt;h2&gt;예시&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159816-91c23a2b-24f3-4197-b21b-10f3d924f933.gif&quot; alt=&quot;comisiting-layer-1&quot;&gt;&lt;/p&gt;
&lt;p&gt;BBB layer를 rasterizing해서 만든 &lt;strong&gt;bitmap만 transform하면&lt;/strong&gt;, 매 animation frame마다 전체 페이지를 rasterizing 하지 않아도 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159824-2717ecff-ba3f-45ad-a0c9-5222808462c9.gif&quot; alt=&quot;compositing-layer-3&quot;&gt;&lt;/p&gt;
&lt;p&gt;그리고 부모가 layer라면 자식들도 그 layer의 subtree가 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159828-c3a30b98-63fb-4a77-9519-c6ff9087ec78.gif&quot; alt=&quot;compositing-layer-4&quot;&gt;&lt;/p&gt;
&lt;p&gt;이렇게 layer를 분리해서 rasterizing 후 &lt;strong&gt;생성된 bitmap만 변형&lt;/strong&gt;하게 된다면, 매 animation frame 마다 전체 페이지를 rasterizing 하지 않아도 되기 때문에 효율적입니다.&lt;/p&gt;
&lt;h2&gt;threaded input&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159834-9e3fd36d-b926-4f73-aa13-1a679b880bb5.png&quot; alt=&quot;sc_-86&quot;&gt;&lt;/p&gt;
&lt;p&gt;main thread가 바쁠 때 compositor thread는 browser process로부터 사용자의 스크롤 입력을 받아 bitmap을 transform 합니다.&lt;/p&gt;
&lt;p&gt;물론 사용자가 특정 레이어가 아닌 전체 페이지를 스크롤링하면 compositor thread에서 처리하지 않고 main thread로 일을 넘깁니다. 왜냐하면 전체 페이지를 다시 그리는 render pipeline을 거쳐야 하기 때문입니다.&lt;/p&gt;
&lt;p&gt;추가로 Javascript에서 scroll event listener를 걸어놓은 경우에는 사용자 입력을 main thread에서 처리하도록 task queue에 넣습니다.&lt;/p&gt;
&lt;h2&gt;Layer는 어떻게 만들어지는가?&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159838-38d3765d-3783-40b9-b8d0-6d12e174deef.png&quot; alt=&quot;sc_-87-1&quot;&gt;&lt;/p&gt;
&lt;p&gt;Layer는 &lt;strong&gt;transform&lt;/strong&gt;같은 CSS property를 바탕으로 &lt;strong&gt;layout object&lt;/strong&gt;에서 생성됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159845-1b039a75-4e4c-4d43-b213-83a18f7ea0c7.png&quot; alt=&quot;sc_-88-1&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;main thread&lt;/strong&gt;에서 DOM -&gt; style -&gt; layout 이후에 layer가 만들어지고, 이 단계를 compositing assignments라고 부릅니다.&lt;/p&gt;
&lt;p&gt;그리고 paint 단계에서 각 레이어는 자신만의 DisplayItemList를 가지게 됩니다. 즉 “무엇을 어떤 순서로 그릴 것인지”에 대한 정보인 DisplayItemList가 레이어마다 따로따로 설정된다는 의미입니다.&lt;/p&gt;
&lt;h2&gt;pre-paint&lt;/h2&gt;
&lt;p&gt;paint전에 pre-paint 단계가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159850-77d00327-0f74-490a-a16d-4880c6fd6e44.png&quot; alt=&quot;sc_-90&quot;&gt;&lt;/p&gt;
&lt;p&gt;이 단계에서 property tree를 생성합니다. 참고로 property tree는 아래처럼 생겼습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159854-8e47bf6d-bb72-4fc5-b1a1-342ef656eabe.png&quot; alt=&quot;sc_-89&quot;&gt;&lt;/p&gt;
&lt;p&gt;Property tree에 대하여 (&lt;a href=&quot;https://d2.naver.com/helloworld/5237120&quot;&gt;Naver D2 글&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;“레이아웃 트리와 다음에 설명할 페인트 트리 사이에 한 가지 작업이 더 있다. 레이아웃 트리를 순회하면서 속성 트리(property tree)를 만드는 작업이다. 속성 트리는 &lt;code class=&quot;language-text&quot;&gt;clip&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;transform&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;opacity&lt;/code&gt; 등의 속성 정보만 가진 트리이다. 기존에는 이런 정보를 분리하지 않고 노드마다 가지고 있었다. 그래서 특정 노드의 속성이 변경되면 해당 노드의 하위 노드에도 이 값을 다시 반영하면서 노드를 순회해야 했다. 최신 Chrome에서는 이런 속성만 별도로 관리하고 각 노드에서는 속성 트리의 노드를 참조하는 방식으로 변경되고 있다.”&lt;/p&gt;
&lt;h2&gt;Commit&lt;/h2&gt;
&lt;p&gt;paint가 완료되면, 이제 이렇게 만든 레이어들을 &lt;strong&gt;하나의 프레임으로 만들기 위해&lt;/strong&gt;서 레이어들과 property tree를 compositor thread에게 &lt;strong&gt;넘겨&lt;/strong&gt;줘야 합니다. 이 단계를 commit이라 부릅니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159861-e42fd175-4cad-4cd8-a142-9f2b69a4da51.png&quot; alt=&quot;sc_-91&quot;&gt;&lt;/p&gt;
&lt;h2&gt;tiling&lt;/h2&gt;
&lt;p&gt;paint 이후에 layer의 paint operation을 bitmap으로 만드는 작업인 rasterization을 합니다. 그런데 layer가 너무 큰 경우는 어떨까요?&lt;/p&gt;
&lt;p&gt;사용자에게 보이는 view port보다 엄~청 큰 layer의 경우, 이 layer를 rasterizing 하는 것은 너무 비용이 큽니다. 그래서 compositor thread에서 이 layer는 작은 tile 들로 나눠집니다. 이 tile 들은 render process 안에 있는 &lt;strong&gt;여러 raster thread에서 비동기적&lt;/strong&gt;으로 rasterzied 됩니다.&lt;/p&gt;
&lt;p&gt;그런데 아래쪽에서는 rasterization은 &lt;strong&gt;Skia를 통해 GPU&lt;/strong&gt;에서 일어난다고 하는데, &lt;strong&gt;아마도&lt;/strong&gt; render process의 raster thread가 GPU Process의 SKIA를 사용해서 rasterization 한다는 의미 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159871-5d10b544-916c-4509-98c3-8a02ab7feead.png&quot; alt=&quot;sc_-93&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Layer 그리기&lt;/h2&gt;
&lt;p&gt;layer의 모든 tile 들이 rasterizing되면 compositor thread는 각 tile에 대한 DrawQuad를 생성합니다. DrawQuad는 tile을 rasterizing 한 bitmap을 참조하고 있고, tile을 스크린의 어느 위치에 그려야 하는지에 대한 instruction를 가지고 있습니다. 이때 이 위칫값은 property tree를 고려해서 계산됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159877-43cc497e-8a58-46ee-a634-2615709593f7.png&quot; alt=&quot;sc_-94&quot;&gt;&lt;/p&gt;
&lt;p&gt;이렇게 만든 DrawQuad를 묶어서 CompositorFrame 객체에 넣습니다. 그리고 이 CompositorFrame은 GPU Process에게 넘겨집니다.&lt;/p&gt;
&lt;p&gt;지금까지 우리는 renderer process 안에서 main thread + compositor thread + raster thread를 활용해 DOM -&gt; style -&gt; layout -&gt; layer -&gt; pre-paint -&gt; paint -&gt; commit -&gt; tiling 과정을 통해 최종적으로 CompositorFrame을 만들었습니다.&lt;/p&gt;
&lt;p&gt;남은 일은 이 CompositorFrame(=&lt;strong&gt;animation frame&lt;/strong&gt;)을 화면에 그리기만 하면 됩니다.&lt;/p&gt;
&lt;h1&gt;Display(viz)&lt;/h1&gt;
&lt;p&gt;GPU Process는 CompositorFrame을 받아서 SKIA API를 사용해 OpenGL(혹은 Vulkan) API를 부르고, OpenGL은 그래픽카드를 사용해 화면에 tile을 그립니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159884-9ea11ff9-43d8-4056-89ce-54e7825e91a6.png&quot; alt=&quot;sc_-96-1&quot;&gt;&lt;/p&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159893-65f720b8-e309-4dc9-ab92-10d4a10fe1a6.png&quot; alt=&quot;sc_-98&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;브라우저는 web content를 받습니다.&lt;/li&gt;
&lt;li&gt;DOM Tree를 만듭니다.&lt;/li&gt;
&lt;li&gt;style을 계산합니다(resolve styles).&lt;/li&gt;
&lt;li&gt;layout을 계산합니다.&lt;/li&gt;
&lt;li&gt;layer를 만듭니다.&lt;/li&gt;
&lt;li&gt;property tree를 만듭니다.&lt;/li&gt;
&lt;li&gt;layer를 paint합니다.&lt;/li&gt;
&lt;li&gt;layer + DisplayItemList(paint operations) + property tree를 compositor thread로 commit(복사/붙여넣기)합니다.&lt;/li&gt;
&lt;li&gt;layer를 여러 작은 조각(tile)로 나눕니다.&lt;/li&gt;
&lt;li&gt;SKIA library를 사용해 tile을 rasterizing 합니다.&lt;/li&gt;
&lt;li&gt;DrawQuads를 생성합니다.&lt;/li&gt;
&lt;li&gt;Skia와 OpenGL를 통해 DrawQuads를 실제 스크린에 그립니다. (pixel화)&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;질문&lt;/h1&gt;
&lt;h2&gt;external CSS도 HTML parser를 block 하나요?&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159900-c5529510-10a4-4262-b8dc-dee03fce5353.png&quot; alt=&quot;sc_-48&quot;&gt;&lt;/p&gt;
&lt;p&gt;from : &lt;a href=&quot;https://web.dev/preload-scanner&quot;&gt;https://web.dev/preload-scanner&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;네, blocking 합니다. &lt;a href=&quot;https://web.dev/preload-scanner/#whats-a-preload-scanner&quot;&gt;관련글&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&lt;em&gt;n this case, the parser runs into a&lt;/em&gt; &lt;code class=&quot;language-text&quot;&gt;&amp;lt;link&gt;&lt;/code&gt; &lt;em&gt;element for an external CSS file, which blocks the browser from parsing the rest of the document—or even rendering any of it—until the CSS is downloaded and parsed.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;inline style 도 parser를 block 하나요?&lt;/h2&gt;
&lt;p&gt;네 block합니다! &lt;a href=&quot;https://web.dev/render-blocking-resources/#how-to-eliminate-render-blocking-stylesheets&quot;&gt;관련 글&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Similar to inlining code in a &lt;code class=&quot;language-text&quot;&gt;&amp;lt;script&gt;&lt;/code&gt; tag, &lt;strong&gt;inline critical styles&lt;/strong&gt; required for the first paint inside a &lt;code class=&quot;language-text&quot;&gt;&amp;lt;style&gt;&lt;/code&gt; &lt;strong&gt;block&lt;/strong&gt; at the &lt;code class=&quot;language-text&quot;&gt;head&lt;/code&gt; of the HTML page&lt;/p&gt;
&lt;h2&gt;style자원을 가져오고 parsing할때까지 왜 HTML Parser는 멈춰있나요?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Flash_of_unstyled_content&quot;&gt;flash of unstyled content (FOUC)&lt;/a&gt; 문제 때문에 그렇습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/52737532/192159908-8716f000-ce91-46e6-815e-b77abe7257b3.png&quot; alt=&quot;sc_-49&quot;&gt;&lt;/p&gt;
&lt;p&gt;from : &lt;strong&gt;&lt;a href=&quot;https://web.dev/preload-scanner&quot;&gt;https://web.dev/preload-scanner&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;그러니까, style.css파일을 다운받고 있는데, HTML Parser가 다 파싱하고 rendering까지 끝나면 스타일이 적용되지 않은 사이트가 사용자에게 보일것이고, 후에 style.css파일 다운로드가 끝나고 파싱하고 적용하면 그때 스타일이 적용된 사이트가 보일것입니다.&lt;/p&gt;
&lt;p&gt;이는 사용자에게 &lt;strong&gt;번쩍&lt;/strong&gt;! 하는 느낌을 주기 때문에 별로 좋지 않습니다. 그래서 style.css파일을 다운받고 파싱이 끝날때까지 HTML Parser는 기다립니다.&lt;/p&gt;
&lt;h1&gt;참고&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=-P28LKWTzrI&quot;&gt;CPU vs GPU&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ibocon.tistory.com/242&quot;&gt;브라우저 렌더링 Browser rendering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/5237120&quot;&gt;최신 브라우저의 내부 살펴보기 3 - 렌더러 프로세스의 내부 동작 (Naver D2)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/blog/inside-browser-part3/&quot;&gt;Inside look at modern web browser (part 3)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://alistapart.com/article/braces-to-pixels/&quot;&gt;Braces to Pixels&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/preload-scanner/#whats-a-preload-scanner&quot;&gt;Don’t fight the browser preload scanner&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[이론과 실습을 통해 이해하는 Docker 기초]]></title><description><![CDATA[본 포스팅은 도커에 대한 자세한 개념, 도커 기본 명령어, 컨테이너 실행 실습, 도커 컴포즈, 도커 이미지 빌드, 도커 허브에 이미지 배포 등 전반적인 도커 기초에 대한 내용을 다룬다. 도커 (Docker…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-09-20-docker-basic/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-09-20-docker-basic/</guid><pubDate>Sun, 25 Sep 2022 12:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;본 포스팅은 도커에 대한 &lt;strong&gt;자세한 개념&lt;/strong&gt;, 도커 &lt;strong&gt;기본 명령어&lt;/strong&gt;, &lt;strong&gt;컨테이너 실행&lt;/strong&gt; 실습, &lt;strong&gt;도커 컴포즈&lt;/strong&gt;, 도커 &lt;strong&gt;이미지 빌드&lt;/strong&gt;, &lt;strong&gt;도커 허브&lt;/strong&gt;에 이미지 배포 등 전반적인 도커 기초에 대한 내용을 다룬다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;도커 (Docker)&lt;/h2&gt;
&lt;p&gt;도커는 &lt;strong&gt;컨테이너 기반&lt;/strong&gt;의 오픈소스 가상화 플랫폼이다. 솔로몬 하익스가 개발했으며, 2013년에 처음 발표되었다. &lt;strong&gt;고 (Go) 언어&lt;/strong&gt;로 개발되고 있다.&lt;/p&gt;
&lt;h3&gt;해상 운송업의 컨테이너&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/680704b080f71ae75cb91c1ec2f91f9a/docker-basic-real-container.webp&quot; alt=&quot;&quot;&gt;
선박을 이용하여 수출입하는 해상 운송업의 모습을 상상해보자. 모든 선적물은 거대한 상자모양의 &lt;strong&gt;컨테이너를 통해 패키징&lt;/strong&gt; 된 후 화물선에 선적된다. 컨테이너는 &lt;strong&gt;국제적으로 표준화, 규격화&lt;/strong&gt;된 크기를 갖고있다. 컨테이너와 관련된 보관, 운송과 관련한 장비, 제도, 프로세스 등은 모두 국제 표준에 맞게 설계된다.&lt;/p&gt;
&lt;p&gt;이런 표준화의 특징으로 인해 &lt;strong&gt;해상 운송업자는 컨테이너 안에 어떤 물건이 담겨져 있는지 전혀 알 필요가 없다.&lt;/strong&gt; 그저 컨테이너 단위로 선적하여 운송하고 하역하고 보관하면 그만이다. 따라서 한국에서 수출한 물건을 저 멀리 미국에서도 &lt;strong&gt;‘장소에 구애 받지 않고’&lt;/strong&gt; 손쉽게 하역하고, 운송할 수 있는 것 이다.&lt;/p&gt;
&lt;p&gt;만약 해상 운송업에서 컨테이너를 사용하지 않는다면 어떻게 될까? 제각기 다른 물건의 크기, 모양, 심지어 액체/고체 여부에 대응하기 위해 서로 다른 운송 장비, 보관 방법, 프로세스가 필요할 것이다.&lt;/p&gt;
&lt;h3&gt;도커의 컨테이너&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 768px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c3c94f0a97998f5c18c6237f1c88006e/fe486/docker-basic-docker-logo.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 37.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB0klEQVR42n2RS2uTQRhG50tArYqLSigYQRDTlSBuxLUb/4O/oOAPEFy5kmJTYi4SvNRo0i60Nr1F04tpKG0qaaBQ6UKCGIqtBK222Cbm+h2fry1uRAcO78wwc+aZGWNSDcy4SNYxL8TgL0ysinlUwURVIzWO+TdxBXYw96uH7P0bM9vCTDUxb8SkxGPilcQvW7hjO3h757h4J87R/s+cDJU5EviqjZX/COfbmKx4K/GMSDvyFq7kHueCGS73xrnSl8AXyOB7kMPTv6LUukVw9y+ZtS98Z2NyYkFSRz4naaaNe/Qbl0Iprj+Z5kJ4mu6+JKfDy1jPf2Ke1TCPdfWoBOEKVvBQ5neEy+ASVl7SJbFoYznybIOz6TLXUkU6H+bpSHxSeq0dV7qRGpbe2xpUjWkcreCKVOiO65Az78HkJZTUkTvVWmrT9TRHV2iK4wMFPNF5PJEsXv9rTiWKErcOPnKkgTVc5/xEnRsLDYY+ar5Uh3tluPoBvI68oBQFJU1t4BorcWJCqHYMF3El1nCn9XazbXyLLXrW2gRLNutV9lv+u/Y17YPBqiYHtuD2Joxuw131e76Ae1WHrECn6s0NuLVuM7llM7Ntk5LgR5M/bbcJvwGhkbuN9WydPQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;docker basic docker logo&quot;
        title=&quot;&quot;
        src=&quot;/static/c3c94f0a97998f5c18c6237f1c88006e/fe486/docker-basic-docker-logo.png&quot;
        srcset=&quot;/static/c3c94f0a97998f5c18c6237f1c88006e/0eb09/docker-basic-docker-logo.png 500w,
/static/c3c94f0a97998f5c18c6237f1c88006e/fe486/docker-basic-docker-logo.png 768w&quot;
        sizes=&quot;(max-width: 768px) 100vw, 768px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;도커의 컨테이너도 마찬가지이다. 도커는 서비스를 운용하는데 필요한 실행환경, 라이브러리, 소프트웨어, 시스템 도구, 코드 등을 &lt;strong&gt;컨테이너라는 표준화된 단위로 추상화&lt;/strong&gt;한다. 이렇게 만들어진 컨테이너는 컴퓨팅 환경에 구애받지 않고 서비스가 실행될 수 있도록 한다.&lt;/p&gt;
&lt;p&gt;실제 컨테이너의 특성처럼, 서비스 관리자는 &lt;strong&gt;도커라이징(Dockerizing)&lt;/strong&gt; 된 컨테이너가 어떤 런타임을 필요로 하는지, 어떤 라이브러리와 코드를 필요로 하는지 전혀 알 필요가 없다. 그저 컨테이너를 어딘가에서 가져와서 서비스를 운영할 컴퓨팅 환경에서 실행하기만 하면 된다. 실행된 서비스는 컴퓨팅 환경과 &lt;strong&gt;독립된 가상의 환경&lt;/strong&gt;에서 실행되며, &lt;strong&gt;일관된 결과&lt;/strong&gt;를 보장한다.&lt;/p&gt;
&lt;h3&gt;전통적인 서버관리&lt;/h3&gt;
&lt;p&gt;우리가 새로운 서비스를 개발하고 있는 개발자라고 생각해보자. 우리가 런칭하려는 서비스는 규모가 상당하여, 시스템 환경 설정부터, 방화벽, 네트워크 등의 &lt;strong&gt;많은 설정&lt;/strong&gt;이 필요하다. 또한 Java, Python, Node.js 등 &lt;strong&gt;많은 프로그래밍 언어&lt;/strong&gt;를 사용하고, 각각의 언어는 또 &lt;strong&gt;많은 라이브러리 의존성&lt;/strong&gt;을 갖는다. 여기서 끝이 아니다. PostgreSQL, MariaDB, Redis 등 &lt;strong&gt;여러개의 DBMS&lt;/strong&gt; 도 사용한다. 하지만, 우리는 유능한 개발자다. 이 많은 것들을 우리의 로컬 개발환경에 완벽하게 셋업했다.&lt;/p&gt;
&lt;p&gt;우리는 서비스를 완성했고, 이제 서비스를 배포해야할 상황이 찾아왔다. 어라, 급하게 개발하다보니 개발서버 환경을 어떻게 설정했는지, 어떤 언어와 라이브러리를 설치했는지, DBMS 는 무엇을 사용하는지, 각각의 버전은 무엇인지 전혀 기록해두지 않았다. 하지만, 우리는 불굴의 개발자이다. 서버의 모든 환경설정과 Git 로그를 뒤져내어 서버를 세팅하는 매뉴얼을 작성하는데 성공했다.&lt;/p&gt;
&lt;p&gt;자, 이제 &lt;strong&gt;토씨하나 틀리지 않고 메뉴얼대로&lt;/strong&gt; 프로덕션 서버에도 개발환경과 동일한 환경을 구축하면 된다. 결국 서버를 모두 세팅하였고, 서비스를 실행해보았다. 어라, 오류가 발생한다. 수 시간의 삽질 끝에 오류의 원인을 알아내었다. 어떤 &lt;strong&gt;라이브러리의 버전이 개발환경과 달라 발생한 오류&lt;/strong&gt;였다. 아까 서버를 세팅할 때 실수를 한 모양이다. 어찌됐든 성공적으로 서비스를 배포하는데 성공하였다.&lt;/p&gt;
&lt;p&gt;서비스는 성공적이었고, DAU는 치솟았다. 슬슬 서버를 확장해야할 것 같다. 경제적 이유로 확장의 형태는 수평 확장으로 결정되었다. &lt;strong&gt;또 다른 서버에 이전과 똑같은 프로세스대로 환경을 셋업해야한다.&lt;/strong&gt; 정신이 아득해진다.&lt;/p&gt;
&lt;p&gt;위와 같이 컨테이너를 사용하지 않은 전통적인 서버 관리방식에서는 밑 바닥부터 &lt;strong&gt;수작업으로 모든것을 세팅&lt;/strong&gt;해야한다. 이런 환경에서 만약 개발언어를 Python 에서 Go 로 교체하는 등 &lt;strong&gt;서버 환경과 개발 환경이 바뀐다면?&lt;/strong&gt; 운용중인 모든 서버에도 변경 사항을 적용해야한다. 생각만해도 끔찍하다.&lt;/p&gt;
&lt;h3&gt;도커를 사용한 서버관리&lt;/h3&gt;
&lt;p&gt;도커를 사용하면, 위와 같이 귀찮고 고통스러운 작업을 반복할 필요 없다. 도커를 사용하면 OS 환경설정, 언어, 라이브러리, 시스템 도구 등이 설치된 환경을 그대로 &lt;strong&gt;이미지&lt;/strong&gt;로 빌드할 수 있다. 이미지는 후술할 Dockerfile 이란 것을 이용하여 만들 수 있다. 개발 환경에서 만들어진 이미지를 그대로 프로덕션 서버에 전달하고, 이미지를 기반으로 컨테이너를 생성하고 실행하면 끝이다.&lt;/p&gt;
&lt;p&gt;이런 컨테이너의 특징은 곧, 도커의 중요한 특징인 &lt;strong&gt;표준성&lt;/strong&gt;과 상통한다. Java, node.js, Python 등 다양한 언어와 프레임워크를 사용한 서비스들은 제각기 다른 배포 방법을 가지고 있다. 하지만 이를 도커를 통해 패키징하여 컨테이너를 만든다면, 어떤 언어, 프레임워크, 런타임을 사용하던 관계없이 모두 동일한 배포 프로세스를 갖게 된다.&lt;/p&gt;
&lt;p&gt;이런 표준성 덕분에 도커는 높은 &lt;strong&gt;확장성&lt;/strong&gt;과 &lt;strong&gt;이식성&lt;/strong&gt;을 갖는다. 도커가 설치된 환경이라면 어디서든 컨테이너를 실행할 수 있으며, 이를 통해 프로덕션 서버는 물론, 개발 및 테스트 서버 구축과 운영도 아주 쉬워진다. 컨테이너의 설정은 보통 환경변수로 제어하므로, 환경(개발, 테스트, 프로덕션 등)에 따라 환경 변수를 다르게 설정하여 관리할 수 있다.&lt;/p&gt;
&lt;p&gt;단, 도커의 &lt;strong&gt;컨테이너는 삭제되면 모든 데이터가 초기화&lt;/strong&gt;된다. 이를 막기 위해 외부 저장장치에 링크를 하거나, AWS S3 등 별도의 클라우드 스토리지를 사용해야할 필요가 있다.&lt;/p&gt;
&lt;p&gt;이 모든 설정은 코드와 설정파일 기반으로 관리되어 Git 등을 사용하면 히스토리가 남게 되며, &lt;strong&gt;재현과 추적&lt;/strong&gt;이 가능하다.&lt;/p&gt;
&lt;h3&gt;컨테이너 vs VM (Virtual Machine)&lt;/h3&gt;
&lt;p&gt;그렇다면, 도커 같은 컨테이너 기반의 가상화 플랫폼과 VM의 차이점은 무엇일까?&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 991px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d3eace5d768716e419e7656520ae2529/f79d8/docker-basic-vm-vs-docker.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 43.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAACL0lEQVR42iWQy09TQRTG778nLBAjFA1iJC5c6kIXxq0Jxoo1oVgsIkJUEii6gNiHjfJsSytt76MPKH3dR++D9pa0hqYkfJ47neTkfOc7M785Mxxo9ft9tO02LtoXaLVs2KSvrq7A1vU1S4Z5DkM3oTcMyLIGy7RgGCZkRYOuG9QzYFlNcEq7h3rrEhWzi7ITVhcVCsW+JL8Hq9NnwFPFQk5uQqpZSBdlFDUbeaXJdK426Dl7uJvvsxj3C5j4KGDE0YsCbjs15eG5NGZCVQZ8viFhaJ7H2EIW9xfiGP0gYsQn4J4vDpefxw0vjxebuQHQOTzhHwBdlEd9Wdwhb2guA3ekxoARycByTMFaqoH1RA1fkw18OdKwflTHtyMVn+MqonkTnIsOTn4SMbUs0aQ8JpcE3KVpp5ZF3Frg8TZaZ8Azs4ODvIJ0lZ6n2kiVTSRLBtPHFROxoobKeZcm9GUYaHyRx/RqDg9WJIxR7aJ62HsM747MgGuJOl5uFeAOnsD9swBPuITZ0CnTbyg7vY2UDC5c/IdwoYPvfw0EkhoCNL6jf510ESx0Iek9BpwJVtlXTC6J9AIRD+nyabrc0Y436uPZ93Bu8QCz4iHcmT28yx7Ak9nH6/QuPFIMr4R9hNUzBpyX4ni09wOPD7fwJLaFZ4kgnsaDpLfJ22Y9fz4JLqKeIKqdYkc/w6FZZfGnUWJeSC0iZxsM+Ju8lVIKa+U0RQabNQGBKs+0461Sb1cv4z/wzU/epE0L6wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;docker basic vm vs docker&quot;
        title=&quot;&quot;
        src=&quot;/static/d3eace5d768716e419e7656520ae2529/f79d8/docker-basic-vm-vs-docker.png&quot;
        srcset=&quot;/static/d3eace5d768716e419e7656520ae2529/0eb09/docker-basic-vm-vs-docker.png 500w,
/static/d3eace5d768716e419e7656520ae2529/f79d8/docker-basic-vm-vs-docker.png 991w&quot;
        sizes=&quot;(max-width: 991px) 100vw, 991px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위 그림의 왼쪽은 도커의 구조를, 오른쪽은 VM 의 구조를 나타낸다. 가장 먼저 눈에 들어오는 차이점은 &lt;strong&gt;하이퍼바이저 (Hypervisor) 의 유무&lt;/strong&gt;이다. 하이퍼바이저란 Virtual Box 나 VM Ware 와 같은 가상 머신을 생성하고 실행하는 프로세스이다. 하이퍼바이저는 &lt;strong&gt;하드웨어를 에뮬레이션하여&lt;/strong&gt; 하나의 컴퓨터에서 다수의 운영체제를 운영할 수 있게 해주는 소프트웨어이다.&lt;/p&gt;
&lt;p&gt;하이퍼바이저 자체를 실행하는 측을 &lt;strong&gt;호스트 운영체제 (Host OS)&lt;/strong&gt; 라고 하며, 하이퍼바이저가 구동하는 가상화된 OS 를 &lt;strong&gt;게스트 운영체제 (Guest OS)&lt;/strong&gt; 라고 한다. 게스트는 호스트로부터 일정량의 리소스를 할당을 받아 구동된다. 운영체제 자체를 가상화하여 실행하는 특징으로 도커에 비해 상대적으로 무겁다. 호스트 운영체제와 게스트 운영체제의 &lt;strong&gt;기능 중복&lt;/strong&gt;으로 인해 약 10 ~ 20% 의 오버헤드 (Overhead) 를 갖는다고 한다.&lt;/p&gt;
&lt;p&gt;하지만, 도커와 같은 컨테이너 기반 가상화 플랫폼은 &lt;strong&gt;게스트 운영체제 없이&lt;/strong&gt;, &lt;strong&gt;프로세스를 격리&lt;/strong&gt;하는 방식으로 호스트 운영체제의 모든 기능과 리소스를 컨테이너간 공유한다. 운영체제 위에 &lt;strong&gt;또 다른 운영체제를 구동하지 않아&lt;/strong&gt;도 되며, &lt;strong&gt;리소스도 공유&lt;/strong&gt;해서 사용하므로 오버헤드는 3 ~ 5% 이내라고 한다. 또한 운영체제를 별도로 가상화 하지 않으므로 이미지 파일의 용량도 하이퍼바이저에 비해 낮다.&lt;/p&gt;
&lt;h2&gt;도커 시작하기&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;(참고) 본 포스트는 Mac 환경을 기준으로 작성되었다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;설치&lt;/h3&gt;
&lt;p&gt;도커를 사용하기 위해서는 도커를 먼저 설치해야한다. Mac 이나 Windows 를 사용하는 유저는 &lt;strong&gt;&lt;a href=&quot;https://www.docker.com/get-started&quot;&gt;Docker 웹사이트&lt;/a&gt;&lt;/strong&gt; 에 접속하여 &lt;strong&gt;Docker Desktop&lt;/strong&gt; 을 OS 에 맞게 다운로드하여 설치하면 된다.&lt;/p&gt;
&lt;p&gt;리눅스 환경에서는 &lt;code class=&quot;language-text&quot;&gt;sudo&lt;/code&gt; 권한으로 아래의 명령을 입력하면 도커를 설치할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-fsSL&lt;/span&gt; https://get.docker.com/ &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sh&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;설치되었는지 확인하기 위해서 &lt;code class=&quot;language-text&quot;&gt;docker version&lt;/code&gt; 명령을 입력해본다. 아래와 같이 버전정보가 표시된다면 성공적으로 설치된 것이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; version
Client:
 Cloud integration: v1.0.22
 Version:           &lt;span class=&quot;token number&quot;&gt;20.10&lt;/span&gt;.11
 &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;생략&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

Server: Docker Engine - Community
 Engine:
  Version:          &lt;span class=&quot;token number&quot;&gt;20.10&lt;/span&gt;.11
 &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;생략&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;컨테이너를 실행하기 전에…&lt;/h3&gt;
&lt;p&gt;본격적으로 도커를 맛보기 위해 여러가지 도커 이미지를 다운받고, 컨테이너를 실행해보며 대략적인 사용방법을 익혀본다. 일단 첫번째로는 순수하게 아무것도 설치되지 않는 Ubuntu 와 CentOS 이미지를 받아와 컨테이너를 실행해볼 것이다.&lt;/p&gt;
&lt;p&gt;잠깐 다음으로 넘어가기 전에 짚고 넘어가야할 것이 있다. 위에서 도커는 분명 Guest OS 를 구동하지 않는다고 했는데, 어떻게 Ubuntu, CentOS 를 구분지어 구동할 수 있는 것일까? &lt;strong&gt;도커 컨테이너는 Kernel 데이터를 갖고 있지 않다.&lt;/strong&gt; 다만 컨테이너가 필요로 하는 &lt;strong&gt;Kernel 은 Host OS 의 것을 그대로 사용&lt;/strong&gt;하고, &lt;strong&gt;Kernel 외의 데이터만을 패키징하여 컨테이너가 가지고&lt;/strong&gt; 있는다. 이를테면, Ubuntu 와 CentOS 의 차이점만을 컨테이너에 패키징되어 담고 있는 것 이다. Ubuntu와 CentOS 는 서로 다른 리눅스 배포판이지만, 같은 Linux Kernel 을 사용하기에 이것이 가능하다.&lt;/p&gt;
&lt;p&gt;이렇게 말한다면 또 Mac 과 Windows 사용자는 궁금해 할 것이다. ‘내가 사용하는 OS 는 Linux Kernel 기반이 아닌데, 어떻게 도커가 동작하나요?’ 사실 정확히 이야기하자면, &lt;strong&gt;Docker 는 Linux 환경에서만 네이티브로 동작&lt;/strong&gt;한다. Linux 이외의 환경에서 도커를 사용할때에는 &lt;strong&gt;도커 자체가 OS 에서 지원하는 가상화 환경에서 구동&lt;/strong&gt;된다.&lt;/p&gt;
&lt;p&gt;이때 가상화 기술로 &lt;strong&gt;Mac 은 xhyve, Windows 는 Hyper-V&lt;/strong&gt; 를 사용하여 별도의 Linux 환경을 만들 수 있다. Docker 의 핵심인 &lt;strong&gt;Docker Engine 은 이 가상화 환경의 Linux 위&lt;/strong&gt;에서 돌아가게 된다.&lt;/p&gt;
&lt;p&gt;자, 어려운 이야기는 여기서 끝내고 직접 도커를 다뤄보자.&lt;/p&gt;
&lt;h3&gt;Ubuntu 컨테이너 올리기&lt;/h3&gt;
&lt;p&gt;제일 처음으로 할 것은 우분투 이미지를 사용하여 컨테이너를 올려보는 것 이다. 자세한 명령은 더 아래에서 정리해볼테니 명령어를 외우기보단 따라하며 &lt;strong&gt;도커가 무엇인지 직관적으로 이해하는 것에 집중&lt;/strong&gt;하자.&lt;/p&gt;
&lt;p&gt;Docker Desktop 을 실행한 다음 터미널에서 아래 명령을 입력한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run ubuntu:latest&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위 명령을 입력하여, 우분투의 최신 버전 이미지를 다운받고, 컨테이너를 실행할 수 있다. 처음 명령을 입력하는 경우 이미지를 다운로드 해야하므로 조금 기다려야한다. 아래와 같이 출력되면, 이미지 다운로드가 끝나고 컨테이너가 올라간 상태일 것이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Unable to &lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt; image &lt;span class=&quot;token string&quot;&gt;&apos;ubuntu:latest&apos;&lt;/span&gt; locally
latest: Pulling from library/ubuntu
bbf2fb66fa6e: Pull complete
Digest: sha256:669e010b58baf5beb2836b253c1fd5768333f0d1dbcb834f7c07a4dc93f474be
Status: Downloaded newer image &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; ubuntu:latest&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;컨테이너가 올라갔는지 확인하기 위해 Docker Desktop 창을 열어 확인해보자.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 903px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0878b92aae0e27098e0c57af278eb607/8728b/docker-basic-docker-1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 14.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAXElEQVR42mNQMTD/D8Ly2kZgrKgDwQo6xkRhkB5lPdP/qobmYD6Dir7Zfx1z2//6Vo5gWtHA5r+cvu1/BV0Tog1FxgyKQI0qBmZAGyz+KwMNV9Q1AxpmRpZhIAMBsdFabifCD1YAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;docker basic docker 1&quot;
        title=&quot;&quot;
        src=&quot;/static/0878b92aae0e27098e0c57af278eb607/8728b/docker-basic-docker-1.png&quot;
        srcset=&quot;/static/0878b92aae0e27098e0c57af278eb607/0eb09/docker-basic-docker-1.png 500w,
/static/0878b92aae0e27098e0c57af278eb607/8728b/docker-basic-docker-1.png 903w&quot;
        sizes=&quot;(max-width: 903px) 100vw, 903px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;리스트에 무언가 생긴것을 볼 수 있다. 컨테이너 이름은 별도로 지정해주지 않는 이상 임의의 이름으로 설정되므로 사진과 다른 이름일 것 이다. 우측에 &lt;code class=&quot;language-text&quot;&gt;ubuntu:latest&lt;/code&gt; 라고 써있는 것을 보니 우리가 생성한 컨테이너가 맞는 것 같다.&lt;/p&gt;
&lt;p&gt;하지만 이렇게 실행되는 &lt;strong&gt;컨테이너도 프로세스의 일종&lt;/strong&gt;이기 때문에, 우분투에 실행되고 있는 프로세스가 없다면 컨테이너도 종료되어 아무것도 할 수 없다. Docker Desktop 에서 우리가 생성한 컨테이너에 마우스를 올리고 휴지통 버튼을 클릭하여 컨테이너를 일단 삭제해보자. 혹은 아래 명령을 입력하여 컨테이너를 삭제할수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;컨테이너_이름&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그리고, 아래 명령을 입력하여 도커 컨테이너가 올라가자마자 &lt;strong&gt;쉘을 실행&lt;/strong&gt;하도록 하자. 쉘은 프로세스이므로 우분투 컨테이너가 바로 종료되지 않을 것 이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;-it&lt;/span&gt; ubuntu:latest /bin/sh&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;-it&lt;/code&gt; 옵션은 컨테이너를 종료하지 않은 상태로 &lt;strong&gt;터미널의 입력을 컨테이너 내부로 전달&lt;/strong&gt;하기 위해 사용된다. 또한 &lt;code class=&quot;language-text&quot;&gt;docker run&lt;/code&gt; 명령에서 이미지 이름 다음으로 적는것은 &lt;strong&gt;컨테이너가 실행된 직후 이어서 실행되는 명령&lt;/strong&gt;이다. &lt;code class=&quot;language-text&quot;&gt;/bin/sh&lt;/code&gt; 을 입력하였으므로 쉘이 실행된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# echo &quot;hello, docker&quot;&lt;/span&gt;
hello, &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;터미널에 위와 같이 입력하여 컨테이너 내부로 명령을 전달해보자. &lt;strong&gt;우분투 쉘&lt;/strong&gt;에서 &lt;code class=&quot;language-text&quot;&gt;hello, docker&lt;/code&gt; 라는 문자열이 그대로 출력되면 성공이다.&lt;/p&gt;
&lt;h3&gt;CentOS 컨테이너 올리기&lt;/h3&gt;
&lt;p&gt;이번에는 다른 Linux 배포판인 CentOS 이미지를 사용해보자. 아래 명령으로 CentOS 이미지를 다운받고 컨테이너를 실행하자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; pull centos:centos8.4.2105&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이미지 명을 작성할때 &lt;code class=&quot;language-text&quot;&gt;:&lt;/code&gt; 뒤에 오는 &lt;code class=&quot;language-text&quot;&gt;centos8.4.2105&lt;/code&gt; 는 &lt;strong&gt;태그&lt;/strong&gt;라고 한다. 이번에는 &lt;code class=&quot;language-text&quot;&gt;centos8.4.2105&lt;/code&gt; 태그를 사용하여 &lt;strong&gt;명시적으로 사용할 이미지의 버전&lt;/strong&gt;을 입력해주었다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--rm&lt;/span&gt; centos:centos8.4.2105 /bin/sh&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위에서 한 것 처럼 &lt;code class=&quot;language-text&quot;&gt;run&lt;/code&gt; 명령을 이용해 컨테이너를 실행할 수 있다. &lt;code class=&quot;language-text&quot;&gt;--rm&lt;/code&gt; 옵션이 추가되었는데, 이는 &lt;strong&gt;컨테이너가 종료되면 컨테이너를 삭제&lt;/strong&gt;하라는 의미이다.&lt;/p&gt;
&lt;h3&gt;Nginx 웹서버 구축하기&lt;/h3&gt;
&lt;p&gt;이번에는 Nginx 라는 웹 서버를 이용하여, 웹 서버를 올려보고 직접 html 파일을 서빙해보자. 이번엔 컨테이너 내부의 저장공간과 Host OS 저장공간을 이어주어 서빙할 html 파일을 손쉽게 전달해 볼 것이다. 이를 &lt;strong&gt;볼륨 마운트 (Volume Mount)&lt;/strong&gt; 라고 한다. 볼륨 마운트는 &lt;code class=&quot;language-text&quot;&gt;-v&lt;/code&gt; 옵션을 통해 할 수 있다.&lt;/p&gt;
&lt;p&gt;또한, Nginx 의 기본 HTTP 포트는 80을 외부에서 3000으로 접근할 수 있도록 &lt;strong&gt;외부포트와 내부포트를 바인딩&lt;/strong&gt; 해줄 것 이다. 이는 &lt;code class=&quot;language-text&quot;&gt;-p&lt;/code&gt; 옵션을 통해 가능하다. 적당한 디렉토리를 만들고, 그 디렉토리에서 아래의 명령을 입력하자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;:80 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;/:/usr/share/nginx/html/ &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  nginx&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;한번 &lt;code class=&quot;language-text&quot;&gt;localhost:3000&lt;/code&gt; 에 접속해보자. 아래와 같이 403 Forbidden 화면이 출력되면 성공이다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 374px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/471c4c0194cdf838c6ccb293f4506c92/b0af8/docker-basic-nginx-403.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 35.29411764705882%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAtUlEQVR42o1QuwqEMBDM/3+AVmlsNEX8jaABm9j4KOxtIoiPQufIQo4QjtOBYXfZmWEThghN00ApBa012raFMQbTNKGqKprdzmmGYcCyLOS57/vrZ2HYdV3I8xxFUUAIQSFlWUJKiSzLwDlHkiRI05TmcRz/B4bBXuRq3IcBMdh5ngi57zvV4zio37YN67rCWkvV7xxjr+PPC/0F8zyjrmt0XYe+7+kvn8CeBOHz34C9Fb4N/QCz/BftNX4rygAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;docker basic nginx 403&quot;
        title=&quot;&quot;
        src=&quot;/static/471c4c0194cdf838c6ccb293f4506c92/b0af8/docker-basic-nginx-403.png&quot;
        srcset=&quot;/static/471c4c0194cdf838c6ccb293f4506c92/b0af8/docker-basic-nginx-403.png 374w&quot;
        sizes=&quot;(max-width: 374px) 100vw, 374px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;현재 이 컨테이너의 &lt;code class=&quot;language-text&quot;&gt;/usr/share/nginx/html/&lt;/code&gt; 디렉토리는 Nginx 가 서빙할 정적 파일이 위치하는 디렉토리이다. 그 디렉토리는 &lt;strong&gt;명령을 실행한 현재 Host OS의 디렉토리와 연결&lt;/strong&gt;되어 있는 상황이다. 현재 디렉토리에 아래와 같이 &lt;code class=&quot;language-text&quot;&gt;index.html&lt;/code&gt; 파일을 생성해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Docker&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Hello, Docker!&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그리고 다시 접속해보면, 아래와 같이 우리가 만든 HTML 파일이 서빙될 것이다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 356px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ce857f20eb95c64e986157c84a0c27e8/323b2/docker-basic-nginx-hello.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 49.71910112359551%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAlklEQVR42u2PsQrDIBiEfXVHQXDNos8iCJnyCBmyRFBUME4SkisKWUpLhha69IPj4Pg57ichBHjvYa3FPM9oOOewLAtqrVjXFdu2oZTSbxrneeIdRGsNznkXpRTGGEzTBMYYhmGAUgpCCEgpMY7jfeG+78g5I8bYva1ra1JKXS2/vC2+g+DLkOuFZx3H8TL/zcJ/4UeFDyKZCv8ebS8wAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;docker basic nginx hello&quot;
        title=&quot;&quot;
        src=&quot;/static/ce857f20eb95c64e986157c84a0c27e8/323b2/docker-basic-nginx-hello.png&quot;
        srcset=&quot;/static/ce857f20eb95c64e986157c84a0c27e8/323b2/docker-basic-nginx-hello.png 356w&quot;
        sizes=&quot;(max-width: 356px) 100vw, 356px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;PostgreSQL DB서버 구축하기&lt;/h3&gt;
&lt;p&gt;이번엔 RDBMS 중 하나인 PostgreSQL 컨테이너를 올려보자. 이야기 했지만 컨테이너는 삭제되면, 데이터도 함께 삭제된다. &lt;strong&gt;PostgreSQL 은 DBMS 이므로 꼭 볼륨 마운트&lt;/strong&gt;를 해줘야한다. 적당한 디렉토리를 만들고, 아래 명령을 실행하자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; postgres &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5432&lt;/span&gt;:5432 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;password &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;/data/:/var/lib/postgresql/data/ postgres:14.1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이번엔 &lt;code class=&quot;language-text&quot;&gt;--name&lt;/code&gt; 옵션을 통해 컨테이너의 이름을 우리가 직접 &lt;code class=&quot;language-text&quot;&gt;postgres&lt;/code&gt; 로 설정해주었다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;-d&lt;/code&gt; 옵션은 &lt;strong&gt;Detached Mode&lt;/strong&gt; 로 컨테이너를 실행하는 옵션으로, &lt;strong&gt;컨테이너가 백그라운드 모드&lt;/strong&gt;로 돌아가도록 설정해준다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;-e&lt;/code&gt; 옵션을 통해 컨테이너가 사용하는 &lt;strong&gt;환경변수&lt;/strong&gt;를 설정할 수 있다. 우리는 &lt;code class=&quot;language-text&quot;&gt;POSTGRES_PASSWORD&lt;/code&gt; 를 &lt;code class=&quot;language-text&quot;&gt;password&lt;/code&gt; 로 설정하여, PostgreSQL 접속에 사용하는 비밀번호를 설정하였다.&lt;/p&gt;
&lt;p&gt;우리는 &lt;code class=&quot;language-text&quot;&gt;-d&lt;/code&gt; 옵션을 사용하여 컨테이너를 백그라운드에서 실행했다. 외부에서 컨테이너에 명령을 내리는 방법은 &lt;code class=&quot;language-text&quot;&gt;exec&lt;/code&gt; 을 사용하는 방법이 있다. 일단 아래 명령을 통해 현재 실행중인 컨테이너 리스트를 확인해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ps&lt;/span&gt;

CONTAINER ID   IMAGE           COMMAND                  CREATED         STATUS         PORTS                    NAMES
9ccd0f78bcf3   postgres:14.1   &lt;span class=&quot;token string&quot;&gt;&quot;docker-entrypoint.s…&quot;&lt;/span&gt;   &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; minutes ago   Up &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; minutes   &lt;span class=&quot;token number&quot;&gt;0.0&lt;/span&gt;.0.0:5432-&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5432&lt;/span&gt;/tcp   postgres&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;가장 오른쪽 NAMES 에 우리가 올린 &lt;code class=&quot;language-text&quot;&gt;postgres&lt;/code&gt; 를 확인할 수 있다. 이 컨테이너에 접속하여 명령을 실행해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-it&lt;/span&gt; postgres /bin/sh&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;docker run&lt;/code&gt; 과 비슷하지만, 이미지 이름 대신 &lt;code class=&quot;language-text&quot;&gt;postgres&lt;/code&gt; 라는 컨테이너 이름을 넣어줬다. 그리고 마찬가지로 쉘을 실행해준다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# psql -U postgres&lt;/span&gt;
psql &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;14.1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Debian &lt;span class=&quot;token number&quot;&gt;14.1&lt;/span&gt;-1.pgdg110+1&lt;span class=&quot;token punctuation&quot;&gt;))&lt;/span&gt;
Type &lt;span class=&quot;token string&quot;&gt;&quot;help&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; help.

&lt;span class=&quot;token assign-left variable&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;# CREATE DATABASE hello_docker;&lt;/span&gt;
CREATE DATABASE
&lt;span class=&quot;token assign-left variable&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;# \l&lt;/span&gt;
                                  List of databases
     Name     &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  Owner   &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Encoding &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  Collate   &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;   Ctype    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;   Access privileges
--------------+----------+----------+------------+------------+-----------------------
 hello_docker &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; postgres &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; UTF8     &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; en_US.utf8 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; en_US.utf8 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
 postgres     &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; postgres &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; UTF8     &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; en_US.utf8 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; en_US.utf8 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
 template0    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; postgres &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; UTF8     &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; en_US.utf8 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; en_US.utf8 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;c/postgres          +
              &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;          &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;          &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;            &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;            &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;CTc/postgres
 template1    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; postgres &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; UTF8     &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; en_US.utf8 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; en_US.utf8 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;c/postgres          +
              &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;          &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;          &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;            &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;            &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;CTc/postgres&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;PostgreSQL 이 정상적으로 설치되고 실행되는 모습을 확인할 수 있다.&lt;/p&gt;
&lt;h3&gt;Ghost 블로그 구축하기&lt;/h3&gt;
&lt;p&gt;이번엔 Ghost 라는 오픈소스 블로깅 플랫폼을 사용하여 블로그를 구축해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; ghost-blog &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;http://localhost:3001 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3001&lt;/span&gt;:2368 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;/content/:/var/lib/ghost/content/ &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
  ghost:4.34.3&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위 명령을 입력한 후 아래와 같이 Ghost 블로그가 &lt;code class=&quot;language-text&quot;&gt;localhost:3001&lt;/code&gt; 에서 접속이 되면 성공적으로 설치가 완료된 상황이다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1062px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/dea9c8e2b34151cd2812285544a20cc3/3e86e/docker-basic-ghost-1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 125.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAYAAAAxFw7TAAAACXBIWXMAAAsTAAALEwEAmpwYAAAGGElEQVR42lWUC4xcVRnHJwoRJBUNUVtr0qQEQtDabXwQATEgTUUwCq2IQFWwMSaahkYDxKrRtJAopWC2LZSW7nYfUPfRdttp3ffutLvTfXRnOruznX3OPmbnee/Mnffjzsz9+d3ZNsRz87/35Jx7/uf7n+//HYv+lWr09YfR7zyKftcJ9LvrKG6qI/nwSeafrGVwxwcM7DrKyB+PMrrvGCPVxxmsP0HP2Vra+hpocDbTPNPNucWr7FpqwqJ/8cgq2a01FNfUk1//EZH7TuH+zsd0b23Aur2Orpdq6d9zkkv7GrAdaqSr4RRnrU3U2Vp5e6SN3U4r2yatPOg9J4R3HaPw2Royt3yEuqaZuXWnuXJvG2e+fZq6R5to3dFMx+6zdO89Q+cbrVysbuVMYxv1Vivv9razx97FVkcfX/fYqJq3YQl9oYal2z7m+qdOY19zHuu6dk7c28E737LywQ/O0fJSO5feGWLw/Su0H+yktfoiDfUdHDrTw6ud/fzIbudrzlE2eRxUeZ1YBj/fSM9nWmm7xUrdHZ28tbaPvff0sX9LJ0d+2EXd8900vtxO0586aHmzh1OHe3m/po+/tl7m2c4hNtsd3HfNzabpaTYvzmL58HPNVN9+nv1runjlThsvf9nOH+4e5C9V/bz5UA/vPtHN0ee6OPE7wes9vPfPPv7+4QC/ahnigU4H99gnud81x6ZZH99YDmL5zadbePbWizx+Wy+P3D7IU3eMsvNLw/x+4wCvC+m+R/p468e9/PvFXg7s7ue1f1zihUODPNAwwlrrNdbZPGxwLrJxNsL9viQWZ7/CSL+KvV9j1JbAaUsycTnJ1JU4syNxvGMaiy6BW2N+WsPjjeP2p3CoWYa0PKPpIsMZnaGsztV8GUuZMnl5dEFBHvOdk3fGyAnyZCneQIlcuSg9yKdSJBZ9ZAIh0lEFPZvhZrPkckVWwnFWInH8SgJ/JIGWzGEYUCyWiSayhGJpIvEMIS1NMl/EHwjQ22/DPjxCr+0SVx1OtERSthTCgl4koMQJqEIm31gyS6Gg4/F4CIVClMpllIRJmCaspUibhH4/7e0d2ITM5RpncvI6bvckqVTaJNQlwqhEFmM5FCWT12UixYG3D9LU3FyREUtmCEaTBGNJ4uk86XQa78IigWBQVBTRJahiqbQqOS/R+EKqIMpyUCWeypDL5RgaHsbpdFakRyQyU0FAXVWQyxdQtThRLYESjRFWohXJq2cohEuBCMsVKEKqkEpnK5OGPFoyVVEQUGJyJBqqyNeLJSIxDUUQEUKTPJPLf0K4GFb+HyEFX9iMWuwS1KQfr2ApGCMhFnFPeuTMPBhyvsVCgXKx9EmWDb1EQU2TVVMUohnyktGCkhJriHnyGoWcIoisIh/FKElyYjFWEhppsVyspKOJnRLlkljKEEK3ZHJbDcbjtZS+e5jy996jVHUQ48IUqLtgYgN4HwXfT8h5H5YM7WXAKLA9MccOZYad6gLPhxd4zD/HeD6HhTE/qS0H6H3lCOXjw6ivtRDd8gY0ujACT2C4H8KI1mHE/0NkfBuEfs2FbJpv+ifZHJjhfDLG6WScr85MMZY1CUeWSG/4G86f/Qt9TwvpXxwns+HPGDVXKfu+T3n8QQy1HsP7DJGxKvBvpzWdYf28h432AZ658F8em11krWuaYUmmhakwxnMn4elajKeOYjx9jPLWasrd06C8Cm6R6/05LDxJceGnMrafgWyBF30Bfjvt5YX5ZX65FGHnXABPNi/GlnSHFnz4F5YJS12a0JKJSsZUv0JwZgklGCYoPo1Gtcp4RkuyMjmL6gsSVRQiYvBC/oZtUuJ6l9uNc9yF85oLt+c6SakUs10bn5BavczAgF2MPiKlGKmMK6rKmMOBa2JC7OPGMeYgHA6vEpbFS6aRTafHBKZJ09lsxdThiCIXQZClZZ+QRMmJ54riOXNNUs5RiydFjayLJ4QjI+Nim6Ds6nC5yYp0kyiTXS0tQ2rO3ESVakhI0Zv9sJCahPNSx5NTM+RlA3ONuda8E8w1FjMK8wezlUqru9/sd/T0E5OITRJdLoHSjbnFpWVWJPKb/5lENyvlf5awmSnx9nu5AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;docker basic ghost 1&quot;
        title=&quot;&quot;
        src=&quot;/static/dea9c8e2b34151cd2812285544a20cc3/3e86e/docker-basic-ghost-1.png&quot;
        srcset=&quot;/static/dea9c8e2b34151cd2812285544a20cc3/0eb09/docker-basic-ghost-1.png 500w,
/static/dea9c8e2b34151cd2812285544a20cc3/1263b/docker-basic-ghost-1.png 1000w,
/static/dea9c8e2b34151cd2812285544a20cc3/3e86e/docker-basic-ghost-1.png 1062w&quot;
        sizes=&quot;(max-width: 1062px) 100vw, 1062px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Ghost 이미지에는 Ghost 뿐 아니라 &lt;strong&gt;DB 도 포함되어 빌드&lt;/strong&gt;되었기 때문에, 어드민 페이지인 &lt;code class=&quot;language-text&quot;&gt;http://localhost:3001/ghost/&lt;/code&gt; 에 접속하여 계정을 생성하고, 글을 작성할수도 있다.&lt;/p&gt;
&lt;p&gt;본 포스팅은 Ghost 에 대한 포스팅이 아니므로 이 과정은 생략하도록 하겠다. 궁금하다면 어드민 페이지에 접속해보고 여러가지 시도해보면 좋겠다.&lt;/p&gt;
&lt;p&gt;리눅스 배포판, 웹서버, DB 서버, 블로깅 플랫폼 등 여러가지를 도커 명령 한줄로 세팅해보며 도커가 무엇인지, 도커가 왜 인기가 있는 것인지 &lt;strong&gt;직관적으로 이해&lt;/strong&gt;할 수 있는 시간이 되었으면 좋겠다.&lt;/p&gt;
&lt;h2&gt;자주 쓰는 명령어&lt;/h2&gt;
&lt;p&gt;자주 사용되는 도커의 명령어를 알아보자.&lt;/p&gt;
&lt;h4&gt;docker run&lt;/h4&gt;
&lt;p&gt;위에서 알아보았듯 &lt;code class=&quot;language-text&quot;&gt;run&lt;/code&gt; 명령은 도커 이미지를 다운받고, 컨테이너를 실행하는 명령이다. 아래의 포맷을 갖는다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;옵션&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;이미지 이름:태그&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;명령어&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;인자&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;자주 사용되는 옵션은 아래와 같다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;옵션&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;-d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;컨테이너를 백그라운드에서 실행 (Detached Mode)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;-p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;호스트 포트와 컨테이너 내부의 포트를 바인드한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;-v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;컨테이너 내부의 디렉토리를 호스트로 마운트 (연결) 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;-e&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;컨테이너에서 사용되는 환경변수를 설정한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;--name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;컨테이너의 이름을 설정한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;--rm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;컨테이너가 종료될 경우 컨테이너 자체를 삭제한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;-it&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;터미널 입력을 위한 옵션. &lt;code class=&quot;language-text&quot;&gt;-i&lt;/code&gt; 옵션과 &lt;code class=&quot;language-text&quot;&gt;-t&lt;/code&gt; 옵션은 주로 함께 사용된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;-w&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;WORKDIR 를 설정한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;docker ps&lt;/h4&gt;
&lt;p&gt;현재 실행되고 있는 컨테이너를 확인한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ps&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;옵션&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;-a&lt;/code&gt; 옵션을 붙이면 Stop 상태의 컨테이너도 볼 수 있다.&lt;/p&gt;
&lt;h4&gt;docker stop&lt;/h4&gt;
&lt;p&gt;실행중인 컨테이너를 중지한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; stop &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;옵션&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;컨테이너 이름&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;컨테이너 이름&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;docker rm&lt;/h4&gt;
&lt;p&gt;컨테이너를 제거한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;옵션&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;컨테이너 이름&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;컨테이너 이름&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;docker logs&lt;/h4&gt;
&lt;p&gt;컨테이너에 기록된 로그를 확인할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; logs &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;옵션&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;컨테이너 이름&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;-f&lt;/code&gt; 옵션을 사용하면, 로그를 일회성으로 보여주고 끝내지 않고 기다리며 새롭게 발생한 로그도 출력해준다.&lt;/p&gt;
&lt;h4&gt;docker images&lt;/h4&gt;
&lt;p&gt;현재 설치된 이미지 리스트를 출력한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; images &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;옵션&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;레포지토리 이름&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;레포지토리를 넘겨주면 특정 레포지토리의 이미지만 필터링하여 볼 수 있다.&lt;/p&gt;
&lt;h4&gt;docker rmi&lt;/h4&gt;
&lt;p&gt;현재 설치된 이미지를 제거한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; rmi &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;옵션&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;이미지 이름&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;이미지 이름&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;docker pull&lt;/h4&gt;
&lt;p&gt;이미지를 도커 레지스트리로부터 다운로드한다. &lt;code class=&quot;language-text&quot;&gt;run&lt;/code&gt; 명령을 사용할 때, 대상 이미지가 설치되지 않은 이미지일 경우 자동으로 &lt;code class=&quot;language-text&quot;&gt;pull&lt;/code&gt; 해준다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; pull &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;옵션&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;이미지 이름:태그&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;도커 컴포즈 (Docker Compose)&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1851px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d86e4a390573a9521bc71c5e28409990/2fa64/docker-basic-docker-compose.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 48%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wgARCAAKABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAQFBgcJ/8QAFAEBAAAAAAAAAAAAAAAAAAAABP/aAAwDAQACEAMQAAAB642TXWUjQJAMv//EABsQAAICAwEAAAAAAAAAAAAAAAEDBAYCEBIV/9oACAEBAAEFAn2OQ21etZeYxkZIKVFmv//EACARAAIBAwQDAAAAAAAAAAAAAAEDAhESIQAiIzFBUVL/2gAIAQMBAT8BEHXMsbEcu4zUZGVVJ+WLAoMdHxj3r//EAB4RAAICAAcAAAAAAAAAAAAAAAIDAREQEiEyM0Nx/9oACAECAQE/ATJVLtRcWmVldjL3AczfsYf/xAAkEAACAgIBAwQDAAAAAAAAAAACAwESBBEiBRMjABAUMTI1Qf/aAAgBAQAGPwLA6ZFoBOZmqnHwmvO8CjISvI6lpI+CGwsiUK2Jxbi5mQwgEZ/W9LJl+I/NyBuJXgQ3KSoceOJLZgRdyOAiLDWWWta8iR21aTliwLf4iZQMnqNcqxuf56B0qXLQgxBsgPcAWV7kCerDB1G8RPKo7+o9/wD/xAAZEAEBAQEBAQAAAAAAAAAAAAABIREAEDH/2gAIAQEAAT8hGBP/AL5RDjR7IBIRWNQwA82U9pb2PAgoUYFVEJnNQsToG+DyJLLz/9oADAMBAAIAAwAAABDbH//EABoRAQACAwEAAAAAAAAAAAAAAAERIQAxUWH/2gAIAQMBAT8QIe2UCGF7QU0KIcFATMG++5//xAAZEQEBAAMBAAAAAAAAAAAAAAABEQAh8DH/2gAIAQIBAT8QVOEAIiImcTZoVqkDu9z/xAAbEAADAAIDAAAAAAAAAAAAAAAAAREQIUFxsf/aAAgBAQABPxA2NI4tGONPgz166A0qNUHndWQhpipxMjorDNYwGbdJl1Ry+l68f//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;docker basic docker compose&quot;
        title=&quot;&quot;
        src=&quot;/static/d86e4a390573a9521bc71c5e28409990/2fa64/docker-basic-docker-compose.jpg&quot;
        srcset=&quot;/static/d86e4a390573a9521bc71c5e28409990/953fe/docker-basic-docker-compose.jpg 500w,
/static/d86e4a390573a9521bc71c5e28409990/0a251/docker-basic-docker-compose.jpg 1000w,
/static/d86e4a390573a9521bc71c5e28409990/2fa64/docker-basic-docker-compose.jpg 1851w&quot;
        sizes=&quot;(max-width: 1851px) 100vw, 1851px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;지금까지 우리는 도커를 모두 &lt;strong&gt;CLI 으로 조작&lt;/strong&gt;했다. 물론 간단한 작업은 명령어만 사용해서 조작해도 상관없다. 하지만 컨테이너를 실행할때마다 &lt;strong&gt;모든 옵션&lt;/strong&gt;을 넣어주고, &lt;strong&gt;환경변수를 입력&lt;/strong&gt;해주는 것은 귀찮다. 이를 해결하기 위해 도커에서는 &lt;strong&gt;도커 컴포즈 (Docker Compose)&lt;/strong&gt; 라는 것을 제공한다. 도커 컴포즈는 &lt;strong&gt;여러 컨테이너를 모아 하나의 어플리케이션을 구성&lt;/strong&gt;할 때 사용된다.&lt;/p&gt;
&lt;p&gt;도커 컴포즈는 도커 데스크톱을 설치하면 기본으로 제공된다. 리눅스 환경인 경우 &lt;strong&gt;&lt;a href=&quot;https://docs.docker.com/compose/install/&quot;&gt;도커 공식 웹사이트&lt;/a&gt;&lt;/strong&gt; 를 참고하여 설치하자.&lt;/p&gt;
&lt;h3&gt;버전확인&lt;/h3&gt;
&lt;p&gt;아래의 명령을 입력하여 도커 컴포즈 버전이 출력되면 제대로 설치가 되었다는 의미이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker-compose&lt;/span&gt; version

&lt;span class=&quot;token function&quot;&gt;docker-compose&lt;/span&gt; version &lt;span class=&quot;token number&quot;&gt;1.29&lt;/span&gt;.2, build 5becea4c
docker-py version: &lt;span class=&quot;token number&quot;&gt;5.0&lt;/span&gt;.0
CPython version: &lt;span class=&quot;token number&quot;&gt;3.9&lt;/span&gt;.0
OpenSSL version: OpenSSL &lt;span class=&quot;token number&quot;&gt;1.1&lt;/span&gt;.1h  &lt;span class=&quot;token number&quot;&gt;22&lt;/span&gt; Sep &lt;span class=&quot;token number&quot;&gt;2020&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;MySQL 컨테이너 실행하기&lt;/h3&gt;
&lt;p&gt;이번엔 도커 컴포즈를 이용하여 Wordpress 서버를 실행해볼 것이다. 일단, MySQL 컨테이너를 실행해보자. 도커 컴포즈 설정 파일은 &lt;strong&gt;YAML 포맷&lt;/strong&gt;을 사용하여 작성한다. 적당한 디렉토리에서 &lt;code class=&quot;language-text&quot;&gt;docker-compose.yml&lt;/code&gt; 파일을 생성하고 아래의 내용을 입력해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yml&quot;&gt;&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;3&apos;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# docker-compose 파일 규격 버전&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;mysql-db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 컨테이너 이름&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; mysql&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5.7&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 이미지&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# -v 옵션&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ./mysql&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;/var/lib/mysql
    &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# -e 옵션&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; wordpress
      &lt;span class=&quot;token key atrule&quot;&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; wordpress
      &lt;span class=&quot;token key atrule&quot;&gt;MYSQL_USER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; wordpress
      &lt;span class=&quot;token key atrule&quot;&gt;MYSQL_PASSWORD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; wordpress&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;첫번째로 &lt;code class=&quot;language-text&quot;&gt;version&lt;/code&gt; 는 &lt;strong&gt;도커 컴포즈 파일의 규격버전&lt;/strong&gt;을 명시한다. “3” 을 입력하였으므로, 3.X 버전대의 가장 최신 버전을 사용하게 된다. 관련된 자세한 내용은 &lt;strong&gt;&lt;a href=&quot;https://docs.docker.com/compose/compose-file/compose-versioning/&quot;&gt;도커 공식 웹사이트의 가이드&lt;/a&gt;&lt;/strong&gt; 문서를 참고하자.&lt;/p&gt;
&lt;p&gt;그 아래로는 &lt;code class=&quot;language-text&quot;&gt;services&lt;/code&gt; 를 명시하게 된다. &lt;strong&gt;서비스란 도커 컴포즈에서 컨테이너&lt;/strong&gt;를 의미한다. 우리는 MySQL 의 서비스 이름을 &lt;code class=&quot;language-text&quot;&gt;mysql-db&lt;/code&gt; 로 정했다. 컨테이너 이름 다음으로 올 것은 우리가 &lt;code class=&quot;language-text&quot;&gt;docker run&lt;/code&gt; 을 실행하며 넣어준 여러 옵션을 YAML 포맷으로 넣어주면 된다.&lt;/p&gt;
&lt;p&gt;이제 &lt;code class=&quot;language-text&quot;&gt;docker-compose.yml&lt;/code&gt; 이 있는 디렉토리에서 터미널로 아래 명령을 입력하여 컨테이너를 실행하자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$ docker-compose up&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이미지를 Pull 하고 컨테이너가 실행되면 성공이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;M1 맥북 사용자는 위 명령이 제대로 작동하지 않을 것 이다. 맥북 사용자는 도커 컴포즈 파일의 &lt;code class=&quot;language-text&quot;&gt;environment&lt;/code&gt; 옵션 아래에 &lt;code class=&quot;language-text&quot;&gt;platform: linux/x86_64&lt;/code&gt; 를 추가하자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Wordpress 컨테이너 실행하기&lt;/h3&gt;
&lt;p&gt;Wordpress 를 실행할 새로운 서비스를 추가하자. &lt;code class=&quot;language-text&quot;&gt;docker-compose.yml&lt;/code&gt; 에 아래 내용을 이어서 작성하자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yml&quot;&gt;&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;wordpress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; wordpress&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ./wordpress&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;html&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;/var/www/html
    &lt;span class=&quot;token key atrule&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; 3000&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; always
    &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;WORDPRESS_DB_HOST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; mysql&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;db&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3306&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;WORDPRESS_DB_USER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; wordpress
      &lt;span class=&quot;token key atrule&quot;&gt;WORDPRESS_DB_PASSWORD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; wordpress&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다시 &lt;code class=&quot;language-text&quot;&gt;docker-compose up&lt;/code&gt; 명령으로 실행해보자. 그리고 &lt;code class=&quot;language-text&quot;&gt;localhost:3000&lt;/code&gt; 에 접속하여 워드프레스가 제대로 동작하는지 확인해보자. 워드프레스 설치와 사용방법은 &lt;strong&gt;&lt;a href=&quot;https://wordpress.org/support/article/how-to-install-wordpress/&quot;&gt;워드프레스 공식 문서&lt;/a&gt;&lt;/strong&gt; 를 확인하자.&lt;/p&gt;
&lt;p&gt;그런데, 환경변수를 살펴보면 &lt;code class=&quot;language-text&quot;&gt;WORDPRESS_DB_HOST&lt;/code&gt; 에 IP 주소나 도메인 대신 &lt;code class=&quot;language-text&quot;&gt;mysql-db&lt;/code&gt; 를 넣은 것을 알 수 있다. &lt;strong&gt;IP 주소를 넣어주지 않았는데, 어떻게 Wordpress 와 MySQL 은 정상적으로 통신&lt;/strong&gt;할 수 있는 것 일까? 이를 이해하기 위해서는 &lt;strong&gt;도커 네트워크&lt;/strong&gt;에 대해 이해해야한다.&lt;/p&gt;
&lt;h3&gt;도커 네트워크&lt;/h3&gt;
&lt;p&gt;도커는 컨테이너에 내부 IP 를 순차적으로 할당한다. 이 IP 는 컨테이너가 실행될 때 마다 달라진다. 하지만 우리가 워드프레스와 MySQL 컨테이너를 함께 실행한 것을 생각해보자. 워드프레스의 환경변수를 MySQL 컨테이너를 실행할때마다 바꿔주는 것은 매우 번거로운 일이다.&lt;/p&gt;
&lt;p&gt;도커 네트워크는 &lt;strong&gt;실행 중인 각각의 컨테이너를 연결하는 논리적인 네트워크&lt;/strong&gt;이다. 같은 네트워크로 연결된 컨테이너는 별도의 IP 를 지정해줄 필요 없이 컨테이너의 이름만으로 손쉽게 통신할 수 있다.&lt;/p&gt;
&lt;p&gt;그런데, 우리는 별도의 네트워크를 생성하고 설정한 적이 없다. 그렇다면 어떻게 MySQL 과 Wordpress 컨테이너끼리 통신을 하는것일까?&lt;/p&gt;
&lt;p&gt;기본적으로 도커 컴포즈를 사용해 여러 컨테이너를 올리면, 사용자가 별도의 네트워크를 생성하고 설정하지 않아도 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;디렉토리 이름&gt;_default&lt;/code&gt; 라는 네트워크를 생성하고 컨테이너를 연결한다. 따라서 최초로 &lt;code class=&quot;language-text&quot;&gt;docker-compose up&lt;/code&gt; 하였을 때, 아래 메세지가 출력되는 것을 확인해볼 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Creating network &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;디렉토리 이름&gt;_default&quot;&lt;/span&gt; with the default driver&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;도커 네트워크에 대한 깊은 내용은 별도의 포스팅으로 다뤄보겠다. 지금은 도커 컴포즈를 사용하며, 컴포넌트 간 통신이 네트워크를 통해 이뤄지고 있다는 것만 이해하고 넘어가자.&lt;/p&gt;
&lt;h2&gt;도커 이미지 빌드하기 (Dockerfile)&lt;/h2&gt;
&lt;p&gt;지금까지는 다른사람이 빌드해 놓은 이미지를 다운로드 받아서 사용하기만 했다. 이제는 우리가 직접 간단한 node.js 앱을 작성하고, 도커 이미지로 빌드해보자. node.js 를 배운적이 없다면, node.js 부분은 이해하지 않고 그냥 따라서 작성만해도 괜찮다.&lt;/p&gt;
&lt;h3&gt;node.js 프로젝트 생성&lt;/h3&gt;
&lt;p&gt;일단 도커라이징할 node.js 서버를 아주 간단하게 작성해보자. 적절한 디렉토리를 생성하고 아래 과정을 따라오자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; init &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; express&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;먼저 node.js 앱을 초기화해주고, 웹서버 작성을 위한 &lt;code class=&quot;language-text&quot;&gt;express&lt;/code&gt; 프레임워크를 설치한다. 그 다음 &lt;code class=&quot;language-text&quot;&gt;server.js&lt;/code&gt; 파일을 생성한 다음 아래 코드를 그대로 작성한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; express &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;express&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PORT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8080&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;HOST&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;0.0.0.0&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;express&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Hello Docker&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;HOST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Running on http://&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;HOST&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;우리가 빌드할 간단한 서버가 완성되었다.&lt;/p&gt;
&lt;h3&gt;Dockerfile 작성&lt;/h3&gt;
&lt;p&gt;이미지를 빌드하기 위해서는 &lt;code class=&quot;language-text&quot;&gt;Dockerfile&lt;/code&gt; 이라는 파일을 작성해야한다. &lt;code class=&quot;language-text&quot;&gt;Dockerfile&lt;/code&gt; 은 도커 이미지를 빌드하기 위한 스크립트 파일이다. 도커는 &lt;code class=&quot;language-text&quot;&gt;Dockerfile&lt;/code&gt; 을 읽고 순차적으로 명령을 실행하여 이미지를 빌드한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;dockerfile&quot;&gt;&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 사용할 이미지 입력&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; node:16.13.2&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 작업디렉토리 설정&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;WORKDIR&lt;/span&gt; /usr/src/app&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 앱 의존성 설치&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;COPY&lt;/span&gt; package*.json ./&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;RUN&lt;/span&gt; npm install&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 앱 소스 추가&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;COPY&lt;/span&gt; ./server.js ./server.js&lt;/span&gt;

&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;EXPOSE&lt;/span&gt; 8080&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CMD&lt;/span&gt; [ &lt;span class=&quot;token string&quot;&gt;&quot;node&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;server.js&quot;&lt;/span&gt; ]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;FROM&lt;/code&gt; 에 명시된 &lt;strong&gt;이미지를 베이스&lt;/strong&gt;로 작성된 스크립트가 순차적으로 실행되며, 새로운 이미지가 빌드된다. &lt;code class=&quot;language-text&quot;&gt;WORKDIR&lt;/code&gt; 을 통해 컨테이너의 &lt;strong&gt;작업 디렉토리&lt;/strong&gt;를 설정했다. 모든 명령이 &lt;code class=&quot;language-text&quot;&gt;WORKDIR&lt;/code&gt; 경로를 기준으로 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;COPY&lt;/code&gt; 를 통해 npm 패키지 관련 파일을 복사하였고, &lt;code class=&quot;language-text&quot;&gt;RUN&lt;/code&gt; 명령을 통해 &lt;code class=&quot;language-text&quot;&gt;npm install&lt;/code&gt; 명령을 직접 실행해 node.js 의 의존성 모듈을 설치하였다. 또한 &lt;code class=&quot;language-text&quot;&gt;COPY&lt;/code&gt; 명령을 다시한번 사용해 현재 디렉토리의 &lt;code class=&quot;language-text&quot;&gt;server.js&lt;/code&gt; 를 이미지로 복사한다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;EXPOSE&lt;/code&gt; 를 통해 외부로 8080 포트를 노출한다. 마지막으로 &lt;code class=&quot;language-text&quot;&gt;CMD&lt;/code&gt; 를 통해 컨테이너가 실행된 직후 실행될 명령도 정의해주면 끝이다. &lt;code class=&quot;language-text&quot;&gt;RUN&lt;/code&gt; 은 이미지 빌드 단계에서 실행되는 명령이고, &lt;code class=&quot;language-text&quot;&gt;CMD&lt;/code&gt; 는 컨테이너가 실행된 이후 실행될 명령을 정의한다.&lt;/p&gt;
&lt;p&gt;Dockerfile 은 파일이므로 Git 을 통해 관리 가능하여 이미지 변화 히스토리를 추적할 수 있다.&lt;/p&gt;
&lt;p&gt;자세한 Dockerfile 사용 방법을 알고싶다면, &lt;strong&gt;&lt;a href=&quot;https://docs.docker.com/engine/reference/builder/&quot;&gt;도커 공식 웹사이트 문서&lt;/a&gt;&lt;/strong&gt; 를 참조하자.&lt;/p&gt;
&lt;h3&gt;이미지 빌드하기&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; build &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; node-app ./&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위 명령을 입력하여 &lt;code class=&quot;language-text&quot;&gt;Dockerfile&lt;/code&gt; 을 기반으로 이미지를 빌드할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;-t node-app&lt;/code&gt; 은 이미지의 이름을 설정하는 옵션이며, &lt;code class=&quot;language-text&quot;&gt;./&lt;/code&gt; 은 &lt;code class=&quot;language-text&quot;&gt;Dockerfile&lt;/code&gt; 의 경로를 나타낸다. 빌드 과정은 다소 오랜 시간이 소요되니 조금 기다리자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; images

REPOSITORY              TAG              IMAGE ID       CREATED          SIZE
node-app                latest           401a74e859b2   &lt;span class=&quot;token number&quot;&gt;39&lt;/span&gt; seconds ago   855MB
&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위 명령으로 성공적으로 이미지가 빌드되어 &lt;code class=&quot;language-text&quot;&gt;node-app&lt;/code&gt; 이미지가 조회되는지 확인해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;:8080 node-app&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이미지가 조회된다면, 위 명령으로 도커 컨테이너를 실행할 수 있을 것이다.&lt;/p&gt;
&lt;h3&gt;도커 이미지의 레이어 구조&lt;/h3&gt;
&lt;p&gt;마지막으로 도커 이미지의 구조에 대해서 가볍게 이해하고 넘어가자.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 675px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/97c622c247ef510b06fee1aff4ec890f/a7480/docker-basic-docker-image-layer.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 69.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wgARCAAOABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAcEBQj/xAAVAQEBAAAAAAAAAAAAAAAAAAAFBv/aAAwDAQACEAMQAAAB1e65jSTlHaWwZV//xAAbEAABBQEBAAAAAAAAAAAAAAAFAgMEBgcAAf/aAAgBAQABBQKdoVpS5n9vMnSiH4rfhPMxCF1OlDwMjv/EAB4RAAICAgIDAAAAAAAAAAAAAAECAwQREgUQISLR/9oACAEDAQE/AaVHmo70UlieVqyuS6m2zAjUgemxB84OMfev/8QAHREAAgMAAgMAAAAAAAAAAAAAARECAyEABBMxYf/aAAgBAgEBPwG+/qSolGuMRYQEfF9D0JYwC89ojDz/xAAnEAACAQIFAwQDAAAAAAAAAAABAgMEEQUSEyExABQiECMkQSUysv/aAAgBAQAGPwKRO+w9kSeZAHwrD2sEkcD96yMXsLbFrjnqalxerpaiAUTSRRxUFNB5rPEhzNT1FRcabPtsvG4YBTZCqrzZUYD+em/KYxqNJqtbscnvu5IHxc3ieL/X3ffpsQoq7EKgyQtSslZ21rCWOUsNCGM5s0YAuxGUnxvv6f/EABoQAQEAAwEBAAAAAAAAAAAAAAERACExQVH/2gAIAQEAAT8hcIDdgvmCSoIA4x8qUICmdrwORdl66EQ3D5DEuG9RCAV0orw2RWdQv3TQieB0DP/aAAwDAQACAAMAAAAQy8//xAAXEQEBAQEAAAAAAAAAAAAAAAABEQAh/9oACAEDAQE/EEjYlbVZo0aOQjBv/8QAGBEBAQADAAAAAAAAAAAAAAAAAREQITH/2gAIAQIBAT8Qou5SNHOyk9URCwf/xAAbEAEBAQEAAwEAAAAAAAAAAAABEQAhQWGR0f/aAAgBAQABPxCweJwvnld0oEwo1tnrAJPQd65l2h2JJBUCywDxud+wp3jwg7IzKKwOcFiG0AtD2h7+v7v/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;docker basic docker image layer&quot;
        title=&quot;&quot;
        src=&quot;/static/97c622c247ef510b06fee1aff4ec890f/a7480/docker-basic-docker-image-layer.jpg&quot;
        srcset=&quot;/static/97c622c247ef510b06fee1aff4ec890f/953fe/docker-basic-docker-image-layer.jpg 500w,
/static/97c622c247ef510b06fee1aff4ec890f/a7480/docker-basic-docker-image-layer.jpg 675w&quot;
        sizes=&quot;(max-width: 675px) 100vw, 675px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;도커 이미지는 위 그림과 같이 여러개의 &lt;strong&gt;Layer 가 겹겹히 쌓여있는 구조&lt;/strong&gt;로 되어있다. 과연 정말인지 직전에 설치해둔 Wordpress 이미지로 확인해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; inspect wordpress&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;inspect&lt;/code&gt; 명령은 Low Level 에서 컨테이너 혹은 이미지의 정보를 가져오는 명령이다. 굉장히 긴 무언가가 출력될 텐데, &lt;code class=&quot;language-text&quot;&gt;Layers&lt;/code&gt; 라는 부분만 살펴보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;Layers&quot;: [
	&quot;sha256:f9b44a5812beb5294f1cf3b76cf9c29f1e53bf8eec7cece1b002fc5b5fbae8f2&quot;,
	&quot;sha256:243aed6552599c7fb8a7939cef784eb2f84ce49708cefe11cf1057f2a27ba42f&quot;,
	&quot;sha256:4d66bbee2601b1b779aedfe699c91920d637a5f51f91e87c1c04c3bbe9be34a4&quot;,
	&quot;sha256:45dad0035575a38c893da7e0e9f394f2b4a2407b9694e21b53cbdf462c7a8a5b&quot;,
	&quot;sha256:a8ccc2628dd85db2b3648ebddef84047a402104dbeeb14958514fd136aa19813&quot;,
	&quot;sha256:8857c07576848e6d0b2416c84c561b0591536ccd6900cc7b653bdc2186025145&quot;,
	&quot;sha256:6a7f6ea65ec22a59eb3361c2944edfee08d042ec49fbcbbadd8ac1516ac53b04&quot;,
...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같이 이미지가 굉장히 많은 레이어로 구성되어 있는 모습을 살펴볼 수 있다. 도커를 통해 내려받은 이미지를 구성하는 레이어는 모두 &lt;strong&gt;읽기 전용&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1813px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5f06d8e9cb2fa58439ca928ef7594dd0/aae08/docker-basic-docker-image-build-process.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 39.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAAsTAAALEwEAmpwYAAABJklEQVR42nVQa28jIQzc//8Xt21OUdvbZAPGT8zySMldFfVLjT0aLA2MZrn/Xr32w+qR/6HV/0tvxR7tE5cxRu/9G+cIdUoDYTAVNPhUOGM8I+82lanIWS7vuv/h7ZphEbWEFAETMRP3BEdKJYROWIBaGxlSzXn+3Frbcjzx33fbpz4WXiCRWL7sgTUDpOEu5kA8Wi2orY+YMHuZYlFNzqRK2a453RyXmAhJzh9bRIYI3R3FrhF7qw4yxZcQ2ax6t5xRCd5YQt7LtCcLqwVIn9seAGnaVnFMHkNTOdhcK91Ecfo5Wu98mLhRMSwyY1pmDEet2zVMa98pqwyIDzLuvQ3cVMEft3F/nB+1PNl8aaKIvLy+zt627bl8Endf1/V0Oq0vKzN/ASlNzOCacgWYAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;docker basic docker image build process&quot;
        title=&quot;&quot;
        src=&quot;/static/5f06d8e9cb2fa58439ca928ef7594dd0/aae08/docker-basic-docker-image-build-process.png&quot;
        srcset=&quot;/static/5f06d8e9cb2fa58439ca928ef7594dd0/0eb09/docker-basic-docker-image-build-process.png 500w,
/static/5f06d8e9cb2fa58439ca928ef7594dd0/1263b/docker-basic-docker-image-build-process.png 1000w,
/static/5f06d8e9cb2fa58439ca928ef7594dd0/aae08/docker-basic-docker-image-build-process.png 1813w&quot;
        sizes=&quot;(max-width: 1813px) 100vw, 1813px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위 이미지처럼 도커 이미지는 Dockerfile의 &lt;code class=&quot;language-text&quot;&gt;FROM&lt;/code&gt; 에 명시한 베이스 이미지 위에 또 다른 레이어가 얹혀져 빌드된다.&lt;/p&gt;
&lt;h2&gt;도커 허브에 배포하기&lt;/h2&gt;
&lt;p&gt;그런데, 우리가 다운로드한 이미지는 어디서 오는 것 일까? 우리가 다운로드한 이미지는 모두 &lt;strong&gt;도커 레지스트리 (Docker Registry)&lt;/strong&gt; 로부터 온다. 도커 레지스트리란 &lt;strong&gt;도커 이미지를 관리하는 저장소&lt;/strong&gt;를 의미한다.&lt;/p&gt;
&lt;p&gt;도커 레지스트리 중 가장 유명한 것은 도커에서 공식으로 운영하는 &lt;strong&gt;&lt;a href=&quot;https://hub.docker.com/&quot;&gt;도커 허브 (Docker Hub)&lt;/a&gt;&lt;/strong&gt; 이다. 이제, 우리가 빌드한 이미지를 도커 허브에 배포하여 누구나 이미지를 Pull 받을 수 있도록 해보자. 일단 가장 먼저 도커 허브에 가입해야한다. 도커 허브 가입 과정은 생략한다.&lt;/p&gt;
&lt;p&gt;도커 허브에 가입했다면, 이제 본격적으로 도커 허브에 우리의 이미지를 배포해보자. 그 전에 이미지의 이름을 조금 수정할 필요가 있다. 도커 허브에 이미지를 배포할 때에는 만들어둔 이미지의 이름을 &lt;code class=&quot;language-text&quot;&gt;사용자명(소문자)/이미지명:태그&lt;/code&gt; 형태로 바꿔야한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; tag node-app &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;사용자명&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/node-app&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위 명령으로 이미지 이름을 변경하자. 반드시 사용자명은 모두 소문자로 입력하자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; login&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같이 입력하여 도커 허브에 먼저 로그인하자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; push &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;사용자명&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/node-app&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위 명령으로 도커 허브의 자신의 레포지토리에 이미지를 업로드할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; rmi &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;사용자명&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/node-app
$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;사용자명&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/node-app&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 제대로 우리의 이미지가 업로드 됐는지 확인하기 위해 로컬에 설치된 이미지를 제거하고, 도커 허브에 등록된 이미지를 &lt;code class=&quot;language-text&quot;&gt;run&lt;/code&gt; 해보자. 제대로 node.js 앱이 실행된다면 성공이다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.docker.com&quot;&gt;https://docs.docker.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/ko/docker&quot;&gt;https://aws.amazon.com/ko/docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://azure.microsoft.com/ko-kr/overview/what-is-a-container/#overview&quot;&gt;https://azure.microsoft.com/ko-kr/overview/what-is-a-container/#overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.nvidia.com/ko-kr/blog/%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EB%B0%8F-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C%EC%97%90%EC%84%9C-nvidia-nsight-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0/&quot;&gt;https://developer.nvidia.com/ko-kr/blog/%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EB%B0%8F-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C%EC%97%90%EC%84%9C-nvidia-nsight-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;이미지 출처&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://pixabay.com/ko/photos/%ed%8f%ac%ed%8a%b8-%ec%bb%a8%ed%85%8c%ec%9d%b4%eb%84%88-%ec%84%a0%eb%b0%95-%eb%b0%a4-7418239/&quot;&gt;https://pixabay.com/ko/photos/%ed%8f%ac%ed%8a%b8-%ec%bb%a8%ed%85%8c%ec%9d%b4%eb%84%88-%ec%84%a0%eb%b0%95-%eb%b0%a4-7418239/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[트랜잭션 내에 외부 리소스 요청이 담기게 되면 어떤 문제가 발생할까?]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-09-20-external-in-transaction/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-09-20-external-in-transaction/</guid><pubDate>Tue, 20 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;트랜잭션 처리를 위해서는 데이터베이스와 통신하기 위한 &lt;code class=&quot;language-text&quot;&gt;커넥션&lt;/code&gt;이 필요하다. 하지만 커넥션 생성을 위해서 많은 비용이 필요하다. 이러한 비용을 절감하기 위해 커넥션을 미리 생성한 뒤 &lt;code class=&quot;language-text&quot;&gt;커넥션 풀&lt;/code&gt;을 통해 관리한다.&lt;/p&gt;
&lt;p&gt;커넥션 풀의 개수는 &lt;code class=&quot;language-text&quot;&gt;제한&lt;/code&gt;되어 있다. 만약 동시에 데이터베이스 처리 로직이 담긴 요청을 커넥션 풀의 개수 이상으로 받았다고 가정하자. 커넥션을 획득하지 못한 요청은 다른 데이터베이스 커넥션이 반납될 때까지 기다리게 될 것이다. 결국 이러한 상황이 길어지면 &lt;code class=&quot;language-text&quot;&gt;병목 현상&lt;/code&gt;이 발생하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/59357153/192680529-da0076fe-6ac4-4fdc-aae5-49a00551abab.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;불필요한 로직이 섞인 트랜잭션&lt;/h2&gt;
&lt;p&gt;먼저 간단한 예시를 위해 아래와 같은 요구사항을 줬다고 가정한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;회원가입을 진행한다.&lt;/li&gt;
&lt;li&gt;회원가입은 외부 &lt;code class=&quot;language-text&quot;&gt;OAuth&lt;/code&gt;를 통해 진행된다.&lt;/li&gt;
&lt;li&gt;우리는 전달받은 &lt;code class=&quot;language-text&quot;&gt;인가 코드&lt;/code&gt;를 기반으로 &lt;code class=&quot;language-text&quot;&gt;회원 정보를 조회&lt;/code&gt;한 뒤 &lt;code class=&quot;language-text&quot;&gt;회원가입&lt;/code&gt;을 진행한다.&lt;/li&gt;
&lt;li&gt;회원가입이 완료되면 &lt;code class=&quot;language-text&quot;&gt;NEWBIE&lt;/code&gt;라는 칭호를 획득할 수 있다. (회원은 여러 개의 칭호를 가질 수 있다고 가정한다. &lt;code class=&quot;language-text&quot;&gt;회원 : 칭호 = 1 : N&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;회원가입이 완료되면 가입한 email을 통해 가입 완료 메일을 전송한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위 과정을 코드로 표현하면 아래와 같다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MemberService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NEWBIE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;뉴비&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CONGRATULATIONS_MESSAGE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;가입을 축하드립니다!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MemberRepository&lt;/span&gt; memberRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HonorRepository&lt;/span&gt; honorRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OAuthClient&lt;/span&gt; oAuthClient&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EmailSender&lt;/span&gt; emailSender&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; oAuthClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMember&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; savedMember &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; memberRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 2&lt;/span&gt;
        honorRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Honor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;savedMember&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NEWBIE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 3&lt;/span&gt;

        emailSender&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;savedMember&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEmail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CONGRATULATIONS_MESSAGE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 4&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; savedMember&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;1&lt;/code&gt;: 외부 서비스에서 인가 코드를 전달하여 회원 정보를 조회한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;2&lt;/code&gt;: 조회한 회원 정보를 기반으로 저장한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;3&lt;/code&gt;: 가입과 동시에 &lt;code class=&quot;language-text&quot;&gt;NEWBIE&lt;/code&gt; 칭호를 획득한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;4&lt;/code&gt;: 가입 축하 메일을 전송한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 모든 과정은 하나의 논리적인 작업 단위이기 때문에 하나의 트랜잭션으로 묶여 진행된다.&lt;/p&gt;
&lt;p&gt;위 로직 중 데이터베이스 트랜잭션이 실질적으로 사용되는 부분은 어디일까? 바로 &lt;code class=&quot;language-text&quot;&gt;2&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;3&lt;/code&gt;이다. &lt;code class=&quot;language-text&quot;&gt;MemberRepository&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;HonorRepository&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;member&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;honor&lt;/code&gt;를 적절히 &lt;code class=&quot;language-text&quot;&gt;영속&lt;/code&gt;시키기 위해 &lt;code class=&quot;language-text&quot;&gt;데이터베이스 커넥션&lt;/code&gt;을 활용한다. 외부 서비스를 통해 회원 정보를 조회하는 &lt;code class=&quot;language-text&quot;&gt;1&lt;/code&gt;과 메일을 전송하기 위한 &lt;code class=&quot;language-text&quot;&gt;4&lt;/code&gt;는 데이터베이스와 관련된 처리가 아니므로 데이터베이스를 활용한 트랜잭션이라 보기 어렵다.&lt;/p&gt;
&lt;p&gt;또한 외부 서비스는 우리가 &lt;code class=&quot;language-text&quot;&gt;제어할 수 없는 영역&lt;/code&gt;에 가깝다. 데이터베이스의 커넥션과 요청 처리를 위한 스레드는 외부 서비스에서 적절한 응답이 올 때까지 계속 &lt;code class=&quot;language-text&quot;&gt;대기&lt;/code&gt;해야 한다. 특히 데이터베이스의 커넥션은 &lt;code class=&quot;language-text&quot;&gt;한정된 개수&lt;/code&gt;를 가지고 있다. 대기 시간이 길어질수록 &lt;code class=&quot;language-text&quot;&gt;병목 현상&lt;/code&gt;이 발생할 가능성이 커진다.&lt;/p&gt;
&lt;p&gt;위 코드를 그림으로 표현하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/59357153/192531474-38498110-b3c8-47b1-aafd-0c1ab3944c33.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;실질적으로 데이터베이스의 트랜잭션이 필요한 부분은 파란 화살표지만, 데이터베이스 커넥션을 사용하지 않는 시간까지 불필요하게 &lt;code class=&quot;language-text&quot;&gt;낭비&lt;/code&gt;되고 있는 것을 확인할 수 있다.&lt;/p&gt;
&lt;h2&gt;외부 리소스 요청 로직 분리하기&lt;/h2&gt;
&lt;p&gt;트랜잭션은 필요한 부분에 최소한으로 적용되어야 하므로, 제어할 수 없는 외부 요청 로직을 분리해야 한다. 먼저 작성된 코드에서 트랜잭션이 필요한 로직만 남겨둔다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MemberService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NEWBIE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;뉴비&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CONGRATULATIONS_MESSAGE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;가입을 축하드립니다!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MemberRepository&lt;/span&gt; memberRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HonorRepository&lt;/span&gt; honorRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; savedMember &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; memberRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        honorRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Honor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;savedMember&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NEWBIE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; savedMember&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;트랜잭션이 불필요한 로직을 상위 클래스로 분리하여 작성한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MemberRegister&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CONGRATULATIONS_MESSAGE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;가입을 축하드립니다!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MemberService&lt;/span&gt; memberService&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OAuthClient&lt;/span&gt; oAuthClient&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EmailSender&lt;/span&gt; emailSender&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; oAuthClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMember&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; savedMember &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; memberService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        emailSender&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;savedMember&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEmail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CONGRATULATIONS_MESSAGE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; savedMember&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;기존에 &lt;code class=&quot;language-text&quot;&gt;memberService&lt;/code&gt;를 호출하던 부분을 &lt;code class=&quot;language-text&quot;&gt;memberRegister&lt;/code&gt;를 의존하도록 변경한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/members&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MemberController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MemberRegister&lt;/span&gt; memberRegister&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@PostMapping&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MemberResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestBody&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MemberRegisterRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; savedMember &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; memberRegister&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;MemberResponse&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MemberResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;savedMember&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위 로직을 그림으로 표현하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/59357153/192531482-de0d3295-a02c-42c5-a5fe-5493be9d7fc7.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;불필요하게 낭비되던 데이터베이스 커넥션 유지 시간이 줄어든 것을 확인할 수 있다.&lt;/p&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;지금까지 트랜잭션 내의 외부 리소스 요청에 대한 로직을 분리해야 하는 이유와 방법에 대해 간단히 알아보았다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;제어할 수 없는 영역&lt;/code&gt;인 외부 리소스에 대한 요청을 트랜잭션에서 분리하지 않으면 응답이 올 때까지 요청 스레드가 &lt;code class=&quot;language-text&quot;&gt;대기&lt;/code&gt;하게 된다. 이때 요청에 데이터베이스 로직이 포함되면 커넥션도 함께 &lt;code class=&quot;language-text&quot;&gt;대기&lt;/code&gt;하게 된다. &lt;code class=&quot;language-text&quot;&gt;한정된 개수&lt;/code&gt;를 가진 커넥션을 빠르게 반납하지 않으면 &lt;code class=&quot;language-text&quot;&gt;병목 현상&lt;/code&gt;이 일어날 수 있다.&lt;/p&gt;
&lt;p&gt;이런 상황을 피하기 위해서는 트랜잭션이 활성화된 범위를 &lt;code class=&quot;language-text&quot;&gt;최소화&lt;/code&gt;해야한다. 이러한 개선 작업을 통해 불필요하게 낭비되는 서버 자원을 적절히 아낄 수 있다.&lt;/p&gt;
&lt;h2&gt;Referernces.&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Xx9BXrzNHn8&quot;&gt;구구의 Servlet &amp;#x26; Servlet Container 강의&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;백은빈, 이성욱 지음, 『Real MySQL 8.0』 (위키북스, 2021), 158-160.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[이미지 스토리지 서버 구축 및 최적화]]></title><description><![CDATA[레벨 3 프로젝트 과정 중 이미지 서버를 개발하고 개선해나가는 과정을 이야기하려 합니다. 왜 이미지 서버를 만들어야 하는가? 레벨…]]></description><link>https://tecoble.techcourse.co.kr/post/2022-09-13-image-storage-server/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2022-09-13-image-storage-server/</guid><pubDate>Tue, 13 Sep 2022 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;레벨 3 프로젝트 과정 중 이미지 서버를 개발하고 개선해나가는 과정을 이야기하려 합니다.&lt;/p&gt;
&lt;h2&gt;왜 이미지 서버를 만들어야 하는가?&lt;/h2&gt;
&lt;p&gt;레벨 3 프로젝트인 &lt;a href=&quot;https://github.com/woowacourse-teams/2022-gong-check&quot;&gt;공책&lt;/a&gt;에서는 특정 공간에 관해서 설명하는 이미지를 올려 보여주어야 합니다. 글보다는 이미지를 제공함으로써 처음 보는 공간에 대해서도 가시적으로 확인하기 쉽게 하기 위함이죠.&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
&lt;img width=&quot;196&quot; alt=&quot;1&quot; src=&quot;https://user-images.githubusercontent.com/63030569/189906326-0c10be7b-aad2-460e-af18-24581f93317a.png&quot;&gt;
&lt;/p&gt;
&lt;p&gt;이에 따라 공간의 관리자가 특정 공간을 추가할 때 본인이 원하는 이미지를 올려야 하기 때문에 서버 측에서 이미지를 저장할 수 있도록 하는 기능이 필요했습니다.&lt;/p&gt;
&lt;p&gt;기존에 이미 RDB를 사용하고 있었기 때문에 이미지 데이터를 단순히 binary로 전환하여 RDB에 저장하는 방법을 고려했었습니다. 다만 아래의 문제점이 거론되었어요.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;binary 로 관리하게 되는 경우 너무 큰 데이터 파일로 인하여 관리가 힘들다.&lt;/li&gt;
&lt;li&gt;binary의 크기가 크기 때문에 입출력 시 database에 병목현상이 발생하여 다른 데이터에 영향을 준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;저희 팀에서는 이런 문제점을 고려하여 이미지 스토리지 서버를 따로 두어 관리하는 방향으로 결정하였습니다.&lt;/p&gt;
&lt;h2&gt;이미지 스토리지 서버&lt;/h2&gt;
&lt;p&gt;이미지 스토리지 서버는 &lt;em&gt;MVP&lt;/em&gt;로 개발했기 때문에 아래의 두 가지 기능만을 제공하고 있었습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이미지를 업로드한다.&lt;/li&gt;
&lt;li&gt;이미지를 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;따라서 업로드할 수 있는 웹 애플리케이션 서버(WAS)와 업로드된 이미지를 제공할 수 있는 웹 서버(WS)를 구축하였습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;MVP(Minimum Viable Product): 고객의 피드백을 받아 최소한의 기능(features)을 구현한 제품&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63030569/189909124-76ed1d7c-bab4-4f24-9bfe-526f70502f7c.png&quot; alt=&quot;이미지 서버 구조&quot;&gt;&lt;/p&gt;
&lt;p&gt;인증된 사용자만 사용할 수 있게끔 다른 처리는 해두었지만, 대체적인 구조는 위와 같습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// image upload api&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@PostMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/api/image-upload&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;uploadImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestPart&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MultipartFile&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
  
    &lt;span class=&quot;token class-name&quot;&gt;ImageSaveResponse&lt;/span&gt; imageSaveResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; imageService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;storeImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageSaveResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getImagePath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이미지 파일을 HTTP 메시지 바디에 담아 이미지를 업로드하는 API를 호출하면, 서버에서는 &lt;code class=&quot;language-text&quot;&gt;MultipartFile&lt;/code&gt; 타입으로 이미지를 받아 저장하고, 이미지의 경로를 반환합니다. 이후에는 해당 경로를 통해 이미지를 요청하게 됩니다.&lt;/p&gt;
&lt;br&gt;
&lt;blockquote&gt;
&lt;p&gt;Nginx 설정은 nginx.conf 혹은 include한 서버 설정 파일을 통해 설정할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;nginx&quot;&gt;&lt;pre class=&quot;language-nginx&quot;&gt;&lt;code class=&quot;language-nginx&quot;&gt;&lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;server&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;server_name&lt;/span&gt; image.gongcheck.shop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;location&lt;/span&gt; /images&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;alias&lt;/span&gt; /home/ubuntu/images/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;WS(nginx)에서는 요청받은 이미지 경로를 통해 이미지를 반환합니다.&lt;/p&gt;
&lt;h2&gt;단순 이미지 업로드 서버로서의 문제점&lt;/h2&gt;
&lt;p&gt;위의 이미지 서버는 단순히 &lt;code class=&quot;language-text&quot;&gt;이미지 업로드&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;이미지 제공&lt;/code&gt;만을 담당하고 있습니다. 잘 운영되고 있다는 생각 중에 운영상황에서 문제를 발견합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;원본 이미지를 저장하다보니 이미지의 크기가 너무 커서 이미지 로딩 시간이 너무 길다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63030569/189908680-be9385ce-e346-4dcc-8caa-dabe313beb47.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;위 스크린샷은 저희 서비스에서 여러 이미지 파일을 요청하는 페이지를 요청할 때 걸리는 시간들을 나타내고 있습니다.&lt;/p&gt;
&lt;p&gt;위처럼 로딩하는데 최대 &lt;code class=&quot;language-text&quot;&gt;8.59 초&lt;/code&gt;까지 걸리는 이미지도 존재합니다. 이 문제가 단순히 네트워크 환경의 문제인지 이미지 크기의 문제인지 객관적으로 판단하기 위해 웹 페이지 성능 분석 도구인 &lt;code class=&quot;language-text&quot;&gt;lighthouse&lt;/code&gt;를 통해 확실한 지표를 확인해보았습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63030569/189908706-708a42d8-05c0-49f0-bd6e-c33dff1b7305.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63030569/189909707-54e3d097-b0c8-4b74-b58b-dd58a03f964f.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;여러 지표 중 이미지와 관련된 지표를 보면 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이미지의 크기를 사용하는만큼 줄여서 사용하여야 한다.&lt;/li&gt;
&lt;li&gt;이미지 파일 형식을 &lt;em&gt;webp&lt;/em&gt;로 전환하여야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;WebP: 웹사이트의 트래픽 감소 및 시간 단축을 겨냥해 만들어진 이미지 포맷으로, 2010년 9월 구글이 공개하였다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;화면에 필요한 만큼의 크기가 아니라 원본 이미지의 크기를 그대로 사용하기 때문에 이미지를 최적화하여 전달받아야 조금 더 빠르다는 것입니다. 또한 기존의 이미지를 webp 확장자로 변환하여 더 좋은 이미지 압축률을 가진 파일 형식을 제공하도록 하자라는 것이죠.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;정확한 리포트를 전달받았으니 이제는 서버를 고칠 차례입니다. 클라이언트에게 하나의 이미지 파일에 대해 여러가지 크기의 이미지를 제공할 수 있도록 서버 내부에는 원본 이미지 파일을 저장한 후, 클라이언트가 요청한 크기에 맞게 이미지를 변환하여 webp 확장자로 반환하도록 설계했습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63030569/189910301-c77ad999-3aa5-4091-a258-781778389142.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;br&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/api/resize/{imageUrl}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getResizeImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@PathVariable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; imageUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  
                                             &lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;required &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; defaultValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEFAULT_RESIZE_WIDTH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  
                                             &lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;required &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; defaultValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEFAULT_WEBP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; webp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
    &lt;span class=&quot;token class-name&quot;&gt;ImageResponse&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; imageService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resizeImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; webp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contentType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContentType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;변경하고자 하는 크기의 가로 길이를 입력받고, webp로 변환할지 말지 여부를 입력받습니다. 여기에서 webp에 대한 반환 여부를 넣은 이유는 브라우저마다 &lt;a href=&quot;https://caniuse.com/webp&quot;&gt;호환 여부&lt;/a&gt;가 다른데요. 프론트 페이지에서 이를 판단하여 호환되는 곳에서만 webp를 반환하게 하기 위해 이렇게 API를 설계했습니다.&lt;/p&gt;
&lt;p&gt;webp와 관련된 변환 로직은 대표적으로는 2가지가 있습니다. &lt;a href=&quot;https://chromium.googlesource.com/webm/libwebp&quot;&gt;C로 작성된 코덱 파일&lt;/a&gt;을 이용하여 활용하는 방법과 이를 래핑한 바이너리 파일을 실행하는 방법인데요. 해당 api 내에서는 바이너리 파일을 실행하는 방법을 사용하였으며 본 포스팅에서는 다루지 않습니다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;이제 이미지 크기로 인한 문제를 해결했습니다. 이미지도 잘 업로드하고 있고, 이미지 크기에 맞게 최적화된 이미지를 전달해주고 있기 때문입니다. 그러나 클라이언트가 이미지를 요청할 때마다 크기를 변환하는 로직을 수행해야한다는 새로운 문제가 발생하였습니다.&lt;/p&gt;
&lt;h2&gt;이미지 서버 최적화&lt;/h2&gt;
&lt;p&gt;이러한 문제를 해결하고자 캐싱과 etag를 적용하였습니다.&lt;/p&gt;
&lt;h3&gt;nginx를 이용한 캐싱&lt;/h3&gt;
&lt;p&gt;저희 팀은 Nginx의 reverse proxy cache 기능을 활용하기로 결정했습니다.&lt;/p&gt;
&lt;p&gt;Nginx에서는 특정 요청에 대해서 중간에 처리를 해줄 수 있는 reverse proxy 서버와 처리된 요청을 캐싱할 수 있는 기능을 제공합니다. 이를 이용하여 저희는 WAS에서 반환된 자원을 Nginx에 캐싱하도록 만들었습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63030569/189910905-1be63822-2caf-4a80-9950-2299db7ede55.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;(4)에서 캐싱된 이미지는 이후 같은 요청이 들어올 때, WAS까지 가지 않고 바로 Nginx에서 처리해줄 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;nginx&quot;&gt;&lt;pre class=&quot;language-nginx&quot;&gt;&lt;code class=&quot;language-nginx&quot;&gt;&lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;proxy_cache_path&lt;/span&gt; /home/ubuntu/cache levels=1:2 keys_zone=my_zone:20m inactive=60m&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;proxy_cache_key&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$scheme&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$request_method&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$host&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$request_uri&lt;/span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;server&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;server_name&lt;/span&gt; image.gongcheck.day&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;location&lt;/span&gt; ~* ^/images/(.*)\.(?:png|gif|jpg|jpeg)$&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;rewrite&lt;/span&gt; ^/images/(.*)$ /api/resize/&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt; break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

		&lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;proxy_cache&lt;/span&gt; my_zone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;add_header&lt;/span&gt; X-Proxy-Cache &lt;span class=&quot;token variable&quot;&gt;$upstream_cache_status&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;expires&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1M&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;include&lt;/span&gt; /etc/nginx/proxy_params&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;proxy_pass&lt;/span&gt; http://localhost:8080&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	
	&lt;span class=&quot;token comment&quot;&gt;#...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;간단하게 저희팀에서 사용하는 nginx의 conf를 살펴보겠습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;proxy_cache_path : 캐싱할 데이터를 저장할 경로를 설정합니다. 데이터를 저장할 크기, 캐시 레벨 등을 설정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;proxy_cache_key : 캐싱할 요청의 key입니다. 동일한 key라면 동일한 값을 반환합니다.&lt;/li&gt;
&lt;li&gt;location : 유저가 요청한 URI를 원하는 경로로 매핑하기 위한 설정을 정의합니다.&lt;/li&gt;
&lt;li&gt;rewrite : WAS로 전달하기 위해 요청을 api 스펙에 맞게 수정하여 reverse proxy 요청합니다.&lt;/li&gt;
&lt;li&gt;add_header X-Proxy-Cache : header에 cache 상태를 반환합니다.&lt;/li&gt;
&lt;li&gt;proxy_pass : reverse proxy 전달할 서버 url입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이렇게 Nginx의 reverse proxy cache를 활용하여 불필요한 요청이 WAS로 넘어가지 않도록 만들었습니다.&lt;/p&gt;
&lt;h3&gt;Http Header를 이용한 캐싱 적용&lt;/h3&gt;
&lt;p&gt;이미지는 브라우저단에서 캐싱되어 사용될 수도 있습니다. 현재 정적 파일에 대해 필요한 범위에 따라 &lt;code class=&quot;language-text&quot;&gt;Cache-Control&lt;/code&gt;을 조절하여 브라우저에서 어떻게 캐싱하여 사용할 것인지를 판별할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Cache-control: must-revalidate
Cache-control: no-cache
Cache-control: no-store
Cache-control: no-transform
Cache-control: public
Cache-control: private
Cache-control: proxy-revalidate
Cache-Control: max-age=&amp;lt;seconds&gt;
Cache-control: s-maxage=&amp;lt;seconds&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(출처 : &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Cache-Control&quot;&gt;https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Cache-Control&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;저희 서비스의 경우 이미지 파일이 수정될 일이 거의 없기 때문에 &lt;code class=&quot;language-text&quot;&gt;no-cache&lt;/code&gt; 값을 사용하지 않고 &lt;code class=&quot;language-text&quot;&gt;max-age&lt;/code&gt;로 최소한의 갱신 기간만 설정하였습니다.&lt;/p&gt;
&lt;p&gt;만료 기간이 다 되었을 경우 WAS로 다시 리소스를 요청하게 됩니다. 하지만 만료 기간이 다 되었음에도 리소스에 변경 사항이 없다면 어떨까요? 굳이 WAS로 다시 요청할 필요 없이 브라우저에 캐시된 값을 사용하도록 만들면 됩니다. &lt;code class=&quot;language-text&quot;&gt;etag&lt;/code&gt;를 사용하면 이를 구현할 수 있습니다.&lt;/p&gt;
&lt;p&gt;이미지마다 &lt;code class=&quot;language-text&quot;&gt;etag&lt;/code&gt;를 만들어서 반환한다면 이미지에 대한 특정 식별자가 생깁니다. HTTP 통신 시 해당 식별자의 컨텐츠가 변경되었는지 아닌지를 판단할 수 있습니다. 이렇게 WAS에서 이미지에 대한 반환 값으로 &lt;code class=&quot;language-text&quot;&gt;etag&lt;/code&gt;를 달아주면, &lt;code class=&quot;language-text&quot;&gt;max-age&lt;/code&gt;가 지난 후에 다시 요청이 올 때, 만약 해당 컨텐츠가 수정되지 않았다면 WS가 304 응답을 통해 브라우저의 캐시를 사용하도록 만들 수 있습니다.&lt;/p&gt;
&lt;h2&gt;개선된 결과물&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63030569/189909738-c70f77f1-46de-4eda-ae19-b2155f8d7dbb.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;글 초반부에서 이미지가 최악의 상황에는 8초가 걸렸던 것을 기억하시나요?&lt;/p&gt;
&lt;p&gt;이미지 스토리지 서버 최적화를 통해 평균적으로 초 단위가 걸리던 응답을 10ms대로 줄일 수 있었습니다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;많은 개선이 이루어졌지만, 아직도 풀어나가야 할 과제들이 많습니다. 하나의 인스턴스 내에 WS와 WAS가 공존하고 있어서 이를 분리해야 하고, 이미지 스토리지를 확장하게 된다면 어떻게 경로 설정을 하고 캐싱을 할 것 인지 등을 고민해야할 것 같습니다.&lt;/p&gt;
&lt;p&gt;아직 많이 부족한 글이지만 새롭게 이미지 서버를 개발하는 분들에게 도움이 되었으면 좋겠습니다. 감사합니다 🙇‍♂️&lt;/p&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Cache-Control&quot;&gt;MDN - Cache-Control Header&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nginx.com/resources/wiki/start/topics/examples/reverseproxycachingexample/&quot;&gt;Nginx - Reverse Proxy with Caching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jojoldu.tistory.com/60&quot;&gt;이동욱님 블로그 - Nginx Cache 문제 해결 시리즈&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/ko/caching/&quot;&gt;AWS - Caching&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[HashMap vs HashTable vs ConcurrentHashMap]]></title><description><![CDATA[이미지 출처: Top 35 Data Structure & Algorithms Interview Questions and Answers in 2021 각 자료구조는 필요에 따라 선택되고 활용된다.
 인터페이스의 구현체로는 , ,  등이 있다.
Map…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-11-26-hashmap-hashtable-concurrenthashmap/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-11-26-hashmap-hashtable-concurrenthashmap/</guid><pubDate>Fri, 26 Nov 2021 12:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;이미지 출처: &lt;a href=&quot;https://www.simplilearn.com/data-structure-interview-questions-and-answers-article&quot;&gt;Top 35 Data Structure &amp;#x26; Algorithms Interview Questions and Answers in 2021&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br/&gt;
&lt;p&gt;각 자료구조는 필요에 따라 선택되고 활용된다.
&lt;code class=&quot;language-text&quot;&gt;Map&lt;/code&gt; 인터페이스의 구현체로는 &lt;code class=&quot;language-text&quot;&gt;HashMap&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;HashTable&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;ConcurrentHashMap&lt;/code&gt; 등이 있다.
Map 인터페이스를 구현하면, &lt;code class=&quot;language-text&quot;&gt;&amp;lt;Key, Value&gt;&lt;/code&gt; 형태를 띈다.
그렇다면 이 셋은 무슨 특징을 가지고, 서로 어떤 차이가 있을까?
이번 글에서 해당 구현체를 비교하며 확인해보자.&lt;br/&gt;&lt;/p&gt;
&lt;!-- end --&gt;
&lt;br/&gt;
&lt;h2&gt;HashMap&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;key와 value에 null을 허용한다.&lt;/li&gt;
&lt;li&gt;동기화를 보장하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;HashMap은 thread-safe하지 않아, &lt;code class=&quot;language-text&quot;&gt;싱글 쓰레드 환경&lt;/code&gt;에서 사용하는 게 좋다.
한편, 동기화 처리를 하지 않기 때문에 데이터를 탐색하는 속도가 빠르다.
결국 HashTable과 ConcurrentHashMap보다 데이터를 찾는 속도는 빠르지만, 신뢰성과 안정성이 떨어진다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AbstractMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cloneable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Serializable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;putVal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

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

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h2&gt;HashTable&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;key와 value에 null을 허용하지 않는다.&lt;/li&gt;
&lt;li&gt;동기화를 보장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;HashTable은 thread-safe하기 때문에, &lt;code class=&quot;language-text&quot;&gt;멀티 쓰레드 환경&lt;/code&gt;에서 사용할 수 있다.
이는 데이터를 다루는 메소드(get(), put(), remove() 등)에 &lt;code class=&quot;language-text&quot;&gt;synchronized&lt;/code&gt; 키워드가 붙어 있다.
해당 키워드는 메소드를 호출하기 전에 쓰레드간 동기화 락을 건다.
그래서 멀티 쓰레드 환경에서도 데이터의 무결성을 보장한다.
그러나, 쓰레드간 동기화 락은 매우 느린 동작이라는 단점이 있다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Hashtable&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Dictionary&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cloneable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;Serializable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NullPointerException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        
        &lt;span class=&quot;token class-name&quot;&gt;Entry&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; tab&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; table&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; hash &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hashCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hash &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0x7FFFFFFF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; tab&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token annotation punctuation&quot;&gt;@SuppressWarnings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;unchecked&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Entry&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; entry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Entry&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;tab&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; entry &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; entry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;next&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hash &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; hash&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt; old &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; old&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;addEntry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hash&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h2&gt;ConcurrentHashMap&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;key와 value에 null을 허용하지 않는다.&lt;/li&gt;
&lt;li&gt;동기화를 보장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ConcurrentHashMap은 thread-safe하기 때문에, &lt;code class=&quot;language-text&quot;&gt;멀티 쓰레드 환경&lt;/code&gt;에서 사용할 수 있다.
이 구현체는 HashMap의 동기화 문제를 보완하기 위해 나타났다.
동기화 처리를 할 때, 어떤 Entry를 조작하는 경우에 해당 Entry에 대해서만 락을 건다.
그래서 HashTable보다 데이터를 다루는 속도가 빠르다.
즉, Entry 아이템별로 락을 걸어 멀티 쓰레드 환경에서의 성능을 향상시킨다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConcurrentHashMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AbstractMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConcurrentMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Serializable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;putVal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;putVal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; onlyIfAbsent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NullPointerException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; hash &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;spread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hashCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; binCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; tab &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; table&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; i&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fh&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tab &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tab&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                tab &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;initTable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;f &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tabAt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tab&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; hash&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;casTabAt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tab&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; i&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                             &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hash&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fh &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hash&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;MOVED&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                tab &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;helpTransfer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tab&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt; oldVal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;f&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tabAt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tab&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fh &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                            binCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                            &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; e &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;binCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                                &lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt; ek&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hash &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; hash &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                                    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ek &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; key &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;
                                     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ek &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ek&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                                    oldVal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;val&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                                    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;onlyIfAbsent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                                        e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;val &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                                    &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                                &lt;span class=&quot;token class-name&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; pred &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;next&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                                    pred&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;next &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hash&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                                              value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                                    &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;f &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TreeBin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                            &lt;span class=&quot;token class-name&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                            binCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TreeBin&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;f&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;putTreeVal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hash&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                                           value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                                oldVal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;val&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;onlyIfAbsent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                                    p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;val &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;binCount &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;binCount &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;TREEIFY_THRESHOLD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token function&quot;&gt;treeifyBin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tab&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;oldVal &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; oldVal&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;addCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; binCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;HashMap&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;HashTable&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;ConcurrentHashMap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;key와 value에 null 허용&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;동기화 보장(Thread-safe)&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;추천 환경&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;싱글 쓰레드&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;멀티 쓰레드&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;멀티 쓰레드&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;br/&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;싱글 쓰레드 환경이면 HashMap을, 멀티 쓰레드 환경이면 HashTable이 아닌 ConcurrentHashMap을 쓰자.
그 이유는 HashTable보다 ConcurrentHashMap이 성능적으로 우수하기 때문이다.
앞에서도 언급했듯이 HashTable은 쓰레드간 락을 걸어 데이터를 다루는 속도가 느리다.
반면, ConcurrentHashMap은 Entry 아이템별로 락을 걸어 데이터를 다루는 속도가 빠르다.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;HashMap, HashTable, ConcurrentHashMap 내부 코드&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jdm.kr/blog/197&quot;&gt;Hashtable, HashMap, ConcurrentHashMap 비교&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://limkydev.tistory.com/40&quot;&gt;[Java] HashTable, Hashmap, ConCurrentHashMap 차이&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tomining.tistory.com/169&quot;&gt;HashMap, Hashtable, ConcurrentHashMap 동기화 처리 방식&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[SpringBoot 존재하지 않는 API 요청의 응답 커스마이징하기]]></title><description><![CDATA[SpringBoot 에서 존재하지 않는 api 요청에 대한 에러메시지를 커스터마이징한 과정을 소개한다. 배경 프로젝트를 진행하면서 api의 url…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-11-24-spring-customize-unhandled-api/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-11-24-spring-customize-unhandled-api/</guid><pubDate>Wed, 24 Nov 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SpringBoot 에서 존재하지 않는 api 요청에 대한 에러메시지를 커스터마이징한 과정을 소개한다.&lt;/p&gt;
&lt;!-- end --&gt;
&lt;h2&gt;배경&lt;/h2&gt;
&lt;p&gt;프로젝트를 진행하면서 api의 url을 실수로 잘못 입력했을 때, 에러의 원인을 확인하지 못해 디버깅 과정에 많은 시간을 사용했다.&lt;/p&gt;
&lt;p&gt;이런 상황에 해당하는 적절한 에러 메시지를 응답해야 한다 생각했고, 코드를 하나씩 분석하기 시작했다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;DispatcherServlet 들여다보기&lt;/h2&gt;
&lt;p&gt;DispatcherServlet의 doDispatch 메소드를 확인해보면, mappedHandler가 존재하지 않은 때, noHandlerFound 메소드가 실행된다.
메소드 내부를 보면 &lt;code class=&quot;language-text&quot;&gt;throwExceptionIfNoHandlerFound&lt;/code&gt;에 값에 따라, Exception을 throw하는 지의 여부가 결정된다. 기본 설정은 &lt;code class=&quot;language-text&quot;&gt;false&lt;/code&gt; 이기 때문에 프론트 404의 응답만을 보냈다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DispatcherServlet&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doDispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// Determine handler for the current request.&lt;/span&gt;
      mappedHandler &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;processedRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mappedHandler &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token function&quot;&gt;noHandlerFound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;processedRequest&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  
  &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;noHandlerFound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;throwExceptionIfNoHandlerFound&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NoHandlerFoundException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRequestUri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServletServerHttpRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHeaders&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SC_NOT_FOUND&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;그렇다면 해당 설정을 true로 바꿔주면 간단하게 해결할 수 있을 것으로 보인다. 이 설정은 어디서 진행될까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;DispatcherServletAutoConfiguration 설정 확인하기&lt;/h2&gt;
&lt;p&gt;Springboot에서는 이런 상황을 미리 예견했다는 듯이, 이와 관련한 설정을 모두 진행할 수 있도록 작성되어있다.
&lt;code class=&quot;language-text&quot;&gt;setThrowExceptionIfNoHandle&lt;/code&gt;메소드가 사용되는 곳을 확인하면 &lt;code class=&quot;language-text&quot;&gt;DispatcherServletAutoConfig&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;DispatcherServlet&lt;/code&gt; Bean을 생성할 때 적용한다.
또 이는 &lt;code class=&quot;language-text&quot;&gt;WebMvcProperties&lt;/code&gt;의 properties에 따라 설정되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// DispatcherServletAutoConfiguration&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEFAULT_DISPATCHER_SERVLET_BEAN_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DispatcherServlet&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;dispatcherServlet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WebMvcProperties&lt;/span&gt; webMvcProperties&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;DispatcherServlet&lt;/span&gt; dispatcherServlet &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DispatcherServlet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  dispatcherServlet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setDispatchOptionsRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webMvcProperties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isDispatchOptionsRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  dispatcherServlet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setDispatchTraceRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webMvcProperties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isDispatchTraceRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  dispatcherServlet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setThrowExceptionIfNoHandlerFound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webMvcProperties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isThrowExceptionIfNoHandlerFound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  dispatcherServlet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPublishEvents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webMvcProperties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isPublishRequestHandledEvents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  dispatcherServlet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setEnableLoggingRequestDetails&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webMvcProperties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isLogRequestDetails&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; dispatcherServlet&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;WebMvcProperties 설정하기&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@ConfigurationProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prefix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;spring.mvc&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebMvcProperties&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; throwExceptionIfNoHandlerFound &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 생략&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@ConfigurationProperties&lt;/code&gt;의 설정은 외부 properties에 prefix에 해당하는 값들을 읽어드려 사용할 수 있다.
즉, application.properties에 &lt;code class=&quot;language-text&quot;&gt;spring.mvc.throw-exception-if-no-handler-found=true&lt;/code&gt;의 설정을 진행하면 된다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;하지만, 이 경우에도 기존의 404의 에러메시지를 응답한다. 문제는 아직 해결되지 않았다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3&gt;ResourceHttpRequestHandler 가 매핑되고 있었다.&lt;/h3&gt;
&lt;p&gt;매핑된 핸들러가 없다고 생각하고, 코드만을 기반으로 지금까지의 작업을 진행했다. 실제로는 핸들러가 매핑되고 있었다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1382px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b0747bf8105d0b4dd86073e6483635aa/2d788/2021-11-24-spring-customize-unhandled-api-1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 9.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAACCAYAAABYBvyLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAcUlEQVR42iWMSQ7EIBAD8xnCISAWsTYh4v+f8hjmYHXbluuqsaOUgrUWWu/QOeNuDUoEagwoZk8IEPr5vhjMehdUbpxz8N4jsg9b/K8cCnIu+OZEI2gDda24CdpQzeFj7QGNIRAqpfQHxXhk2Rtjzv0BSrFAQ7eEPmcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;ResourceHttpRequestHandler&quot;
        title=&quot;&quot;
        src=&quot;/static/b0747bf8105d0b4dd86073e6483635aa/2d788/2021-11-24-spring-customize-unhandled-api-1.png&quot;
        srcset=&quot;/static/b0747bf8105d0b4dd86073e6483635aa/0eb09/2021-11-24-spring-customize-unhandled-api-1.png 500w,
/static/b0747bf8105d0b4dd86073e6483635aa/1263b/2021-11-24-spring-customize-unhandled-api-1.png 1000w,
/static/b0747bf8105d0b4dd86073e6483635aa/2d788/2021-11-24-spring-customize-unhandled-api-1.png 1382w&quot;
        sizes=&quot;(max-width: 1382px) 100vw, 1382px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
DEBUG로그를 확인해본 결과, 존재하지 않는 api에 &lt;code class=&quot;language-text&quot;&gt;ResourceHttpRequestHandler&lt;/code&gt; 가 매핑되고 있었다.
즉, 기본적으로 해당하는 URL이 없으면 resource를 조회하도록 설계되어 있고, 존재하지 않았을 때 404의 에러 메시지를 응답하는 것이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;모든 경우에서의 ResourceHandler 매핑 끄기&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;WebMvcAutoConfiguration&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;addResourceHandler&lt;/code&gt; 메소드를 살펴보면 된다. ResourceProperties의 addMapping을 &lt;code class=&quot;language-text&quot;&gt;false&lt;/code&gt;로 설정하면 간단하게 해당 설정을 진행할 수 있다.
SpringBoot의 버전에 따라, properties를 관리하는 방식이 다르므로 확인해보고 버전에 맞게 설정을 진행하면 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2.3.3 버전: &lt;code class=&quot;language-text&quot;&gt;spring.resources.add-mappings=false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;2.4.5 버전: &lt;code class=&quot;language-text&quot;&gt;spring.web.resources.add-mappings=false&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;만약, Resource에 접근하는 방식이 기존에 있었다면 별도로 이 설정을 진행해주어야 한다. 우리 팀 같은 경우, swagger를 사용했기 때문에 아래의 설정을 추가해주었다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SwaggerConfig&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebMvcConfigurer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;addResourceHandlers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ResourceHandlerRegistry&lt;/span&gt; registry&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        registry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addResourceHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;swagger-ui.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addResourceLocations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;classpath:/META-INF/resources/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        registry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addResourceHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/webjars/**&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addResourceLocations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;classpath:/META-INF/resources/webjars/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;NoHandlerFoundException 처리하기&lt;/h3&gt;
&lt;p&gt;지금까지 과정을 진행함으로써, 잘못된 url의 요청이 왔을 때 &lt;code class=&quot;language-text&quot;&gt;NoHandlerFoundException&lt;/code&gt;을 throw하도록 수정했다.&lt;/p&gt;
&lt;p&gt;이를 아래처럼 ControllerAdvice의 &lt;code class=&quot;language-text&quot;&gt;@ExceptionHandler&lt;/code&gt;에서 처리하도록 로직을 구현하면 된다.&lt;/p&gt;
&lt;p&gt;예외 처리가 어떤 방식으로 동작하는지 궁금한 분은 &lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-10-25-spring-exceptioin-handle/&quot;&gt;SpringBoot 예외 처리에 관한 고찰&lt;/a&gt; 을 확인해보기 추천한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RestControllerAdvice&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ControllerAdvice&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ExceptionHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;NoHandlerFoundException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ExceptionResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;noHandlerFoundHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;NoHandlerFoundException&lt;/span&gt; exception&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ExceptionResponse&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExceptionResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exception&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NOT_FOUND&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;p&gt;존재하지 않는 url의 api의 응답을 커스터마이징 해보았다.
DispatcherServlet에서의 코드를 분석해 가며 어떤 방식으로 상황을 해결해나갔는지의 과정을 보여주고 싶었다.
코드를 분석해 가며 문제 해결의 단계를 나눠가며 진행해보기를 추천한다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[웹어셈블리(WebAssembly)에 대해 알아보자]]></title><description><![CDATA[최근에 Developer-Roadmap을 둘러보다 라는 개념에 대해 접하게 되었다. 웹 개발을 공부하는 입장에서 처음 접하는 용어에 대한 호기심이 생겼고, 이번 시간을 통해 에 대해 함께 알아보려고 한다. 웹어셈블리 는 201…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-11-24-web-assembly/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-11-24-web-assembly/</guid><pubDate>Wed, 24 Nov 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;최근에 &lt;a href=&quot;https://github.com/kamranahmedse/developer-roadmap&quot;&gt;Developer-Roadmap&lt;/a&gt;을 둘러보다 &lt;code class=&quot;language-text&quot;&gt;웹어셈블리(WebAssembly)&lt;/code&gt;라는 개념에 대해 접하게 되었다.&lt;/p&gt;
&lt;p&gt;웹 개발을 공부하는 입장에서 처음 접하는 용어에 대한 호기심이 생겼고, 이번 시간을 통해 &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;에 대해 함께 알아보려고 한다.&lt;/p&gt;
&lt;h2&gt;웹어셈블리&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;는 2017년에 처음 발표되었고, 현재는 최신 브라우저에서 대부분 사용 가능하며 웹 표준으로 개발되고 있다. 일반적으로 웹 어플리케이션을 개발할 때에는 HTML, CSS, 자바스크립트를 사용하지만, 이들과 함께 &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;도 사용 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;는 어떠한 ‘새로운 언어’가 아니다. &lt;code class=&quot;language-text&quot;&gt;MDN&lt;/code&gt; 문서를 보면 &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;에 대해 ‘최신 웹 브라우저에서 실행할 수 있는 새로운 유형의 코드’라고 설명하고 있다. &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt; 파일인 &lt;code class=&quot;language-text&quot;&gt;wasm&lt;/code&gt;은 직접 코드를 작성하는 것이 아니라 C, C ++, RUST 등의 언어를 컴파일하여 만들게 된다.&lt;/p&gt;
&lt;p&gt;이를 통해 기존에 C, C ++, RUST 언어로 작성된 코드를 웹에서도 네이티브에 가까운 속도로 실행 가능하게 되었고, 자바스크립트만으로는 어려웠던 고성능의 애플리케이션도 구현할 수 있게 되었다.&lt;/p&gt;
&lt;h2&gt;웹어셈블리와 자바스크립트&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;를 사용하면 기존에 자바스크립트만으로 개발하는 것보다 고성능의 애플리케이션을 구현할 수 있다고 한다. 이게 가능한 이유는 &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;와 자바스크립트가 브라우저에서 처리되는 과정이 다르기 때문이라고 한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 500px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5ca962bdf4094efa64b8b1a6173eb0be/0eb09/2021-11-24-web-assembly-1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 25.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAIAAADKYVtkAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA20lEQVR42m2POU/DQBCF/WeJKKgi+BEUKOJX5ECUVOlQGkhFBUmkSE5klDWOsz72mmOdda4C5WmaeXrf6E3UnGWRC0PNNQH5nUYDfFzjrfneKGIfXRKFwVyjQ64dVZaUY0AOWHBEBWmNhQ35BtkvM5NId4I3pRtNxfBTDD5E93VxO5x1+rO7l/lN/+dxHAdyLW1v8NV9er/vTR6eJ2/TX4s+gNGhFf9V0E4NuQKpKZzbGRSlyyrQwMpRkqkk03GqUmlLg+TP8D+Vhla5bQHpwqtH059eo1CkbX/QHsnzF2gJ5aOvAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;web-assembly-1&quot;
        title=&quot;&quot;
        src=&quot;/static/5ca962bdf4094efa64b8b1a6173eb0be/0eb09/2021-11-24-web-assembly-1.png&quot;
        srcset=&quot;/static/5ca962bdf4094efa64b8b1a6173eb0be/0eb09/2021-11-24-web-assembly-1.png 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;자바스크립트 처리 과정&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 500px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b4962aa0050b1de8eb18b4ac053af439/0eb09/2021-11-24-web-assembly-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 42.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsTAAALEwEAmpwYAAABFUlEQVR42qVQTUvDQBDNjxVEihJEBMGf4MfBkxdBRUGtBw89eattCiKC3yRpRUhq0iSlTbqbZrM7s24TLAjFCj7eYYeZN/veaAIwInm/YCccP3vkwR09dsmbT81eOqBcoKSZ+PhMOm6saDvDAckBpYKm2odN79jwT1r+xtX74pm1UrWXq/bSubVwatZeooBww+pXtur6bkPfuVndM66fwoxP1Jr8B36IlRlAFIiMIwdUJRb8fkygBmaLSzAB3ZilOZTliEGkkgPO+bnsq/uZPm2HqeKrE99ZkRMQxHm2y4l2kB61PP3SrlzY6wf3a9v1zf1bkonp9t9sF+GRMsE4MMAM/nawKVRgd8jGOfQSFiRspmcp5RcS+/1Ggc0N8AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;web-assembly-2&quot;
        title=&quot;&quot;
        src=&quot;/static/b4962aa0050b1de8eb18b4ac053af439/0eb09/2021-11-24-web-assembly-2.png&quot;
        srcset=&quot;/static/b4962aa0050b1de8eb18b4ac053af439/0eb09/2021-11-24-web-assembly-2.png 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;웹어셈블리 처리 과정&lt;/p&gt;
&lt;p&gt;위의 그림은 &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;와 자바스크립트 처리 과정을 비교하기 위한 그림이다. 각 과정을 살펴보면서 어떤 차이가 있는지 살펴보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;구문해석 (Parsing)&lt;/p&gt;
&lt;p&gt;자바스크립트 파일이 브라우저에 전달되면, *추상 구문 트리(AST: Abstract Syntax Tree)로 변환이 된다. 그리고 이로 부터 바이트 코드를 생성한다. 반면, &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt; 파일은 이미 바이트 코드 형태이기 때문에, 변환 과정이 필요 없이 해석(decode)만 하여 검증 과정만 거치면 됩니다.&lt;/p&gt;
&lt;p&gt;*추상 구문 트리(AST: Abstract Syntax Tree)는 컴파일러에 널리 사용되는 자료 구조이다. 컴파일러가 요구하는 여러 단계를 통해 프로그램의 중간 표현의 역할을 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;컴파일 및 최적화 (Compile + Optimize)&lt;/p&gt;
&lt;p&gt;자바스크립트와 &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt; 모두 컴파일 과정을 거친다. 차이가 있다면 &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;는 이미 많은 최적화가 진행된 상태이기 때문에 기계 코드와 훨씬 가까운 상태에서 시작한다. 그렇기 때문에 자바스크립트에 비해 최적화 과정이 적다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;재-최적화 (Reoptimizing)&lt;/p&gt;
&lt;p&gt;재-최적화는 &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;에서는 일어나지 않고, 자바스크립트에서만 일어나는 과정이다. 자바스크립트는 컴파일 과정에서 간혹 여러 이유로 인해 기존 최적화를 버리고 다시 최적화를 하는데 이를 재-최적화라고 한다. &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;에서는 타입과 같은 것들이 명시적이기 때문에 재-최적화 단계가 필요없다고 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;실행 (execute)&lt;/p&gt;
&lt;p&gt;자바스크립트의 경우에도 컴파일러의 최적화 방식을 알고 있다면 그에 친화적인 형태의 코드 패턴으로 작성해 성능을 향상시킬 수도 있다. 그러나 대다수 개발자들은 컴파일러의 내부 동작을 알지 못하며, 브라우저에 따라 최적화 방식은 다르게 접근해야 하기 때문에 실제로는 어려운 일이다. 반면 &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;은 컴파일러를 위해 최적화 되어있기 때문에 자바스크립트에 비해 일반적으로 빠르다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;가비지 컬렉션 (Garbage Collection)&lt;/p&gt;
&lt;p&gt;자바스크립트에서는 가비지 컬렉터가 더이상 필요없는 오래된 변수들을 메모리에서 제거하는 작업을 자동으로 해준다. 하지만 가비지 컬렉터를 직접 제어할 수 없기 때문에, 코드 실행을 방해하는 요인이 될 수도 있다. 웹어셈블리는 메모리를 수동으로 관리하기 때문에 성능에 있어서 더 안정적이다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이러한 차이들에 의해서 일반적으로 &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;가 자바스크립트보다 더 좋은 성능을 나타낸다고 한다. 그래서 &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;를 이용하면 게임이나 VR/AR 처럼 높은 성능을 요구하는 프로그램도 웹으로 구현이 가능하고, 프로젝트를 하면서 많이 사용했던 피그마도 &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;로 구현되어 있다고 한다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;이번 시간에는 &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;에 대한 개념과 &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;가 자바스크립트에 비해 더 높은 성능을 낼 수 있는 이유에 대해서 살펴보았다. &lt;code class=&quot;language-text&quot;&gt;웹어셈블리&lt;/code&gt;에 대해 더 자세한 내용들이 궁금하다면 &lt;a href=&quot;https://dongwoo.blog/2017/06/06/%eb%b2%88%ec%97%ad-%ec%b9%b4%ed%88%b0%ec%9c%bc%eb%a1%9c-%ec%86%8c%ea%b0%9c%ed%95%98%eb%8a%94-%ec%9b%b9%ec%96%b4%ec%85%88%eb%b8%94%eb%a6%ac/&quot;&gt;카툰으로 소개하는 웹어셈블리&lt;/a&gt;을 정주행 해보는 것을 추천한다.&lt;/p&gt;
&lt;h2&gt;참고 링크&lt;/h2&gt;
&lt;h3&gt;MDN&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/WebAssembly&quot;&gt;WebAssembly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;기타 사이트&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://dongwoo.blog/2017/06/06/%eb%b2%88%ec%97%ad-%ec%b9%b4%ed%88%b0%ec%9c%bc%eb%a1%9c-%ec%86%8c%ea%b0%9c%ed%95%98%eb%8a%94-%ec%9b%b9%ec%96%b4%ec%85%88%eb%b8%94%eb%a6%ac/&quot;&gt;[번역] 카툰으로 소개하는 웹어셈블리&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://tech.kakao.com/2021/05/17/frontend-growth-08/&quot;&gt;kakao tech - FE개발자의 성장 스토리 08 : WebAssembly 개발기&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/8257914&quot;&gt;NAVER D2 - 2020년과 이후 JavaScript의 동향 - WebAssembly&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[DIP : 변경에 유연하고 테스트하기 좋은 코드 설계]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-11-21-dip/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-11-21-dip/</guid><pubDate>Sun, 21 Nov 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. 들어가며&lt;/h2&gt;
&lt;p&gt;클린 코드 관련 저서들이 공통적으로 강조하는 내용은 단연 &lt;strong&gt;변경에 유연하고 테스트하기 좋은 코드의 중요성&lt;/strong&gt;입니다. 소프트웨어는 항상 변경이 발생하기 쉽습니다. 요구 사항이 변경될 때마다 코드를 대거 수정해야 한다면 유지보수 측면에서 좋지 않습니다. 요구 사항 변경에 유연하게 대처할 수 있는 구조의 코드는 소프트웨어의 지속적인 성장을 위한 초석입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;a code smell is any characteristic in the source code of a program that possibly indicates a deeper problem.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;어떻게 하면 작성한 코드 구조가 좋고 나쁜지를 판별할 수 있을까요? 저희 프로젝트 팀은 &lt;strong&gt;테스트하기 쉬운 코드 구조&lt;/strong&gt;를 지표로 삼았습니다. 만약 작성한 코드가 테스트하기 어렵다면 코드에서 냄새가 난다고 간주했습니다. 코드 냄새는 컴퓨터 프로그래밍 코드에서 더 심오한 문제를 일으킬 가능성이 있는 프로그램 소스 코드의 특징을 가리킵니다. 테스트하기 어려운 코드는 설계가 잘못되었음을 시사하며, 설계를 개선할 경우 문제가 해결될 공산이 큽니다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 프로젝트 Pick-Git 개발 중 DIP를 통해 변경에 유연하고 테스트하기 좋은 코드를 설계한 사례를 공유하고자 합니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;2. Backgrounds&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/139844650-493f9534-dbb9-4efc-9035-b2a4c33c8263.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;프로젝트 Pick-Git은 Github Repo 기반 개발 장려 SNS이며, 어플리케이션 특성상 GitHub Open API를 사용해 클라이언트의 GitHub 통계 정보를 시각화합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/135004231-179ea3de-17bb-4446-8233-1d47ad77a90e.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;프로젝트 아키텍쳐는 Presentation - Application - Domain - Infrastructure 순의 Layered Architecture를 기반으로 구성되었습니다. 상위 계층은 인접한 하위 계층만을 의존하는 것이 특징입니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;3. Problems&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;UserService.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;readOnly &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserRepository&lt;/span&gt; userRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GitHubContributionCalculator&lt;/span&gt; gitHubContributionCalculator&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ContributionResponseDto&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calculateContributions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ContributionRequestDto&lt;/span&gt; requestDto&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findByName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;requestDto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUsername&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InvalidUserException&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;Contribution&lt;/span&gt; contribution &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; gitHubContributionCalculator
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;calculate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;requestDto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAccessToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ContributionResponseDto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;starsCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contribution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getStarsCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;commitsCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contribution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCommitsCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;prsCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contribution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPrsCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;issuesCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contribution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getIssuesCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reposCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contribution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReposCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;GitHubContributionCalculator.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GithubContributionCalculator&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;${github.contribution.url}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Contribution&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calculate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; accessToken&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; username&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;HttpHeaders&lt;/span&gt; httpHeaders &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpHeaders&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        httpHeaders&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setBearerAuth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;accessToken&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;RequestEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; requestEntity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RequestEntity&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;httpHeaders&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;RestTemplate&lt;/span&gt; restTemplate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RestTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; restTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exchange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;requestEntity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Contribution&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;GitHub Open API를 바탕으로 클라이언트의 연동된 GitHub 계정의 통계 정보를 조회하는 예제 코드입니다. 위 서비스 로직은 Application 계층이 Domain 계층만 의존하는 것이 아니라, Infrastructure 구현 기술까지 직접 의존하는 문제가 있습니다.&lt;/p&gt;
&lt;h3&gt;3.1. 변경의 가능성&lt;/h3&gt;
&lt;p&gt;서두에서 언급했듯이, 소프트웨어는 항상 변경이 발생하기 쉽습니다. 만약 요구사항 변경으로 인해 현재 프로젝트가 더이상 GitHub 플랫폼 연동을 지원하지 않고 GitLab 등 다른 플랫폼을 지원하게 된다면 어떻게 될까요?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;UserService.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Contribution&lt;/span&gt; contribution &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; gitHubContributionCalculator
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;calculate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;requestDto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAccessToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;GithubContributionCalculator&lt;/code&gt; 뿐만 아니라 GitHub API 관련 Infrastructure 구현체를 사용하는 코드들이 전부 수정되어야 합니다. 만약 프로젝트 전역에 걸쳐 GitHub API 관련 Infrastructure 구현체를 사용하는 곳이 1000곳이라면, 1000번의 코드 수정이 발생하는 것입니다. 이는 코드가 변경에 유연하지 못하며 유지보수하기 힘들다는 점을 시사합니다.&lt;/p&gt;
&lt;h3&gt;3.2. 테스트하기 어려운 구조&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;UserServiceIntegrationTest.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserRepository&lt;/span&gt; userRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt; userService&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@MockBean&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GitHubContributionCalculator&lt;/span&gt; gitHubContributionCalculator&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@DisplayName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;사용자는 활동 통계를 조회할 수 있다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calculateContributions_LoginUser_Success&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// given&lt;/span&gt;
    userRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;ContributionRequestDto&lt;/span&gt; requestDto &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ContributionRequestDto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;accessToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;access-token&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;test-user-61&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;ContributionResponseDto&lt;/span&gt; contributions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getExpectedContributionResponseDto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;given&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gitHubContributionCalculator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;calculate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;access-token&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;test-user-61&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;willReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contributions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;ContributionResponseDto&lt;/span&gt; responseDto &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;calculateContributions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;requestDto&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;responseDto&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;usingRecursiveComparison&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contributions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;GitHubContributionCalculator&lt;/code&gt;는 RestTemplate을 통해 GitHub로 API 요청을 보냅니다. 이 때, GitHub OAuth 로그인을 통해 얻은 실제 Access Token을 Header에 담습니다. 이에 대한 테스트 코드는 어떻게 작성할까요? 외부 API와의 통신은 직접 테스트하기 현실적으로 번거롭고 어려움이 많습니다. 외부 브라우저를 통해 로그인하고 실제 GitHub Access Token을 가져와 테스트 코드에 입력해야하기 때문입니다.&lt;/p&gt;
&lt;p&gt;따라서 주로 Mocking하는 방식을 주로 사용합니다. 그러나 슬라이스 테스트가 아닌 통합 테스트에 매번 Mockito 혹은 RestClientTest 작업을 해주는 것 또한 번거롭습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;4. DIP(Dependency Inversion Principle)&lt;/h2&gt;
&lt;p&gt;우리가 다루는 모듈은 고수준 모듈과 저수준 모듈로 나눌 수 있습니다. 고수준 모듈이란 의미있는 단일 기능을 제공하는 모듈이며, 저수준 모듈은 고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능의 실제 구현인 모듈입니다. Layered Architecture 상에서 Application 및 Domain 등의 고수준 모듈은 Infrastructure라는 저수준 모듈을 의존합니다. 그 결과, 앞서 언급한 것처럼 구현 부분의 변경에 유연하지 못하고 테스트하기 어렵다는 문제점이 발생합니다.&lt;/p&gt;
&lt;p&gt;DIP(Dependency Inversion Principle)이란 의존 관계를 역전시켜서 저수준 모듈이 고수준 모듈에 의존하도록 구현하는 것을 의미합니다. 고수준 모듈이 저수준 모듈을 직접 의존하는 것이 아니라, 저수준 모듈이 인터페이스 등 추상을 매개체로 고수준 모듈을 참조하도록 합니다. 이를 통해 구현 기술과 관련된 종속성을 쉽게 제거할 수 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PlatformContributionCalculator.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PlatformContributionCalculator&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Contribution&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calculate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; accessToken&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; username&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;GitHubContributionCalculator.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GithubContributionCalculator&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PlatformContributionCalculator&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;UserService.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserRepository&lt;/span&gt; userRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PlatformContributionCalculator&lt;/span&gt; platformContributionCalculator&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ContributionResponseDto&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calculateContributions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ContributionRequestDto&lt;/span&gt; requestDto&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findByName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;requestDto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUsername&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InvalidUserException&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;Contribution&lt;/span&gt; contribution &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; platformContributionCalculator
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;calculate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;requestDto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAccessToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ContributionResponseDto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;starsCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contribution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getStarsCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;commitsCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contribution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCommitsCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;prsCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contribution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPrsCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;issuesCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contribution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getIssuesCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reposCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contribution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReposCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Application 계층이 Infrastructure 계층의 구현체가 아닌 Domain 계층의 &lt;code class=&quot;language-text&quot;&gt;PlatformContributionCalculator&lt;/code&gt; 인터페이스를 의존하도록 코드를 변경했습니다. 만약 요구사항 변경으로 인해 저수준 모듈이 GitHub 플랫폼 연동에서 GitLab 플랫폼 연동으로 변경되더라도, 고수준 모듈에서의 변경을 최소화할 수 있게 됩니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;UserServiceIntegrationTest.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserRepository&lt;/span&gt; userRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt; userService&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@BeforeEach&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;PlatformContributionCalculator&lt;/span&gt; platformContributionCalculator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;accessToken&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; username&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// do something&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    userService &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userRepository&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; platformContributionCalculator&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;        
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;또한 테스트를 진행할 때 &lt;code class=&quot;language-text&quot;&gt;PlatformContributionCalculator&lt;/code&gt;에 적합한 Mock Object를 주입할 수 있게 됩니다. 따라서 GitHub 서버가 아닌 Mock 서버로 API 요청을 보내게끔 테스트 대역을 조절함으로써, Access Token으로 인한 문제에서 벗어나 자동화된 테스트를 쉽게 작성할 수 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;InfrastructureTestConfiguration.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@TestConfiguration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InfrastructureTestConfiguration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PlatformContributionCalculator&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;platformContributionCalculator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MockContributionCalculator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;테스트 클래스에 정의하는 것이 번거롭다면 대역을 테스트용 Bean으로 주입해 여러 통합 테스트에서 사용할 수 있습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;5. 마치며&lt;/h2&gt;
&lt;p&gt;어떤 경우에 인터페이스를 추출하고 DIP를 적용해야할지 등에 대해 아직 감이 잡히지 않는다면, 우아한형제들 기술 블로그의 &lt;a href=&quot;https://techblog.woowahan.com/2561/&quot;&gt;안정된 의존관계 원칙과 안정된 추상화 원칙에 대하여&lt;/a&gt; 글을 참고하시면 큰 도움이 되실겁니다. 의존은 안정적인 쪽(Presentation -&gt; Application -&gt; Domain -&gt; Infrastructure)으로 향해야 한다는 글입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;안정성이란 추상성을 내포한다고 말하기 때문에 따라서 의존 관계는 추상성의 방향으로 흘러야 합니다. 안정된 추상화 원칙은 안정적인 패키지는 그 안정성 때문에 확장이 불가능하지 않도록 추상적이기도 해야하며, 거꾸로 이 원칙에 따르면 불안정한 패키지는 구체적이어야 하는데, 그 불안정성이 그 패키지 안의 구체적인 코드가 쉽게 변경될 수 있도록 허용하기 때문입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Email 발송, Push, SMS 발송, Logging 등등 인프라성 코드는 거의 불안정성이 0에 가깝습니다(안정적). 호출자는 매우 많은데 그 자신이 의존하는 것은 별로 많지 않은 경우가 많습니다. 그러면서도 그 구현체는 시스템의 성장에 따라 바뀌기 쉽습니다(Email의 경우 SMTP → DB → MQ). 안정성이 높으면 변경 대응을 위해 추상적이어야 합니다. 따라서 인프라성 Service는 거의 무조건 interface를 구현해야 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;DIP가 항상 만능은 아닙니다. Runtime에 의존 대상이 결정되기 때문에 코드를 이해하거나 디버깅하기 어려워집니다. 처음부터 모든 의존 관계에 인터페이스를 추출하여 DIP를 적용하면 코드의 복잡도가 높아집니다. 특정 모듈의 변경 가능성 및 의존도 등을 다각도로 검토하여 인터페이스 추출과 DIP를 고려해야 합니다.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.gangnamunni.com/post/dependency-inversion-principle/&quot;&gt;외부 툴 변경에 휘둘리지 않는 서버 코드 작성기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/myrealtrip-product/what-is-domain-driven-design-f6fd54051590&quot;&gt;도메인 주도 설계로 소프트웨어 만들기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://techblog.woowahan.com/2561/&quot;&gt;안정된 의존관계 원칙과 안정된 추상화 원칙에 대하여&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://private-space.tistory.com/92&quot;&gt;DDD 아키텍처&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[함수형 인터페이스와 람다를 이용한 코드 중복 제거]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-11-21-functional-interface-with-lambda/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-11-21-functional-interface-with-lambda/</guid><pubDate>Sun, 21 Nov 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p style=&quot;font-family: sans-serif; text-align: center; color: #aaa; margin-bottom: 3em; font-size: 85%&quot;&gt;image origin: &lt;a href=&quot;https://keefcode.wordpress.com/2013/12/05/lambda-expressions-in-java-8/&quot;&gt;keefcode&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;코드를 작성하다 보면 비슷한 로직의 중복 코드가 생길 수 있고, 그로 하여금 관리 지점이 늘어나기 때문에 대부분 리팩터링의 대상으로 취급한다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 함수형 인터페이스와 람다에 대해 알아보고, 이를 이용해 코드의 중복을 제거하는 방법을 다룰 것이다.&lt;/p&gt;
&lt;h2&gt;함수형 인터페이스(Functional Interface)&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comparator&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;compare&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; o1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; o2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위 코드는 Java API의 함수형 인터페이스인 &lt;code class=&quot;language-text&quot;&gt;Comparator&lt;/code&gt;이다. 이처럼 추상 메서드를 단 하나만 가지고 있는 인터페이스를 함수형 인터페이스라고 한다. 또한 인터페이스에서 모든 메서드는 암시적으로 추상 메서드이기 때문에 &lt;code class=&quot;language-text&quot;&gt;abstract&lt;/code&gt; 키워드를 쓰지 않는다.&lt;/p&gt;
&lt;p&gt;Java 8 부터 인터페이스는 &lt;code class=&quot;language-text&quot;&gt;default method&lt;/code&gt; 를 포함할 수 있는데, 여러 개의 디폴트 메서드가 있더라도 추상 메서드가 오직 하나면 함수형 인터페이스이다. 즉, &lt;code class=&quot;language-text&quot;&gt;default method&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;static method&lt;/code&gt;는 여러 개가 존재해도 함수형 인터페이스일 수 있다.&lt;/p&gt;
&lt;p&gt;그리고 &lt;code class=&quot;language-text&quot;&gt;@FunctionalInterface&lt;/code&gt; 어노테이션을 사용할 수 있는데, 이 어노테이션은 해당 인터페이스가 함수형 인터페이스 조건에 맞는지 컴파일 시점에 검사할 수 있게 한다. &lt;code class=&quot;language-text&quot;&gt;@FunctionalInterface&lt;/code&gt; 어노테이션이 없어도 함수형 인터페이스로 동작하고 사용하는 데 문제는 없지만, 인터페이스 검증과 유지보수를 위해 붙여주는 게 좋다.&lt;/p&gt;
&lt;h2&gt;람다(Lambda)&lt;/h2&gt;
&lt;p&gt;람다는 쉽게 말해서 메서드에 전달할 수 있는 ‘동작’이라고 이해하면 된다.&lt;/p&gt;
&lt;p&gt;메서드와 비슷하지만 특정 클래스에 종속되지 않으며 이름을 갖지 않는다.&lt;/p&gt;
&lt;p&gt;쉽게 말해 메서드를 하나의 식으로 표현한 것이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; y &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위 메서드를 람다로 표현하면 아래와 같다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; y &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;핵심은 간결하고, 유연하다는 것이다. 메서드를 람다 표현식으로 표현하면 클래스 작성 및 객체 생성을 하지 않아도 메서드처럼 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;람다 표현식은 위와 같이 람다 파라미터, 화살표, 람다 바디로 이루어져있다.&lt;/p&gt;
&lt;p&gt;화살표는 람다 파라미터와 람다 바디를 구분하며 람다 바디는 람다의 반환값에 해당하는 표현식이다.&lt;/p&gt;
&lt;p&gt;람다 표현식에는 &lt;code class=&quot;language-text&quot;&gt;return&lt;/code&gt;이 함축되어 있으므로 &lt;code class=&quot;language-text&quot;&gt;return&lt;/code&gt;문과 세미 콜론을 명시적으로 사용하지 않아도 된다.&lt;/p&gt;
&lt;h2&gt;함수형 인터페이스와 람다를 이용한 코드 중복 제거&lt;/h2&gt;
&lt;p&gt;함수형 인터페이스에 람다 표현식을 접목할 수 있다.&lt;/p&gt;
&lt;p&gt;함수형 인터페이스가 가지고 있는 추상 메서드의 구현을 람다 표현식으로 직접 전달할 수 있다.&lt;/p&gt;
&lt;p&gt;함수형 인터페이스는 어떤 식으로든 구현될 수 있는 추상 메서드를 가지고 있고, 람다는 그 추상 메서드를 구현함과 동시에 전달할 수 있는 것이다.&lt;/p&gt;
&lt;p&gt;실제로 사이드 프로젝트를 하면서 활용한 부분을 얘기해보려고 한다.&lt;/p&gt;
&lt;h3&gt;기존의 중복 코드&lt;/h3&gt;
&lt;p&gt;다음 코드는 여러 축구 경기의 &lt;code class=&quot;language-text&quot;&gt;총 헤딩 시도 횟수&lt;/code&gt;와 , &lt;code class=&quot;language-text&quot;&gt;총 중거리 슛 시도 횟수&lt;/code&gt;를 계산하는 로직이다.&lt;/p&gt;
&lt;p&gt;이때, 지표의 종류만 다를 뿐 각 지표의 횟수를 가져오는 로직에서 거의 동일하다고 볼 수 있을 정도의 중복을 볼 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getTotalHeaderAttempt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 각 경기의 총 헤딩 시도 횟수를 가져와 더한다.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; sumOfHeaderAttempt &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Match&lt;/span&gt; match &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; matches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        sumOfHeaderAttempt &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; match&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHeaderAttempt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; sumOfHeaderAttempt&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getTotalMiddleShootAttempt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 각 경기의 총 중거리 슛 시도 횟수를 가져와 더한다.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; sumOfMiddleShootAttempt &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Match&lt;/span&gt; match &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; matches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        sumOfMiddleShootAttempt &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; match&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMiddleShootAttempt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; sumOfMiddleShootAttempt&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;개선된 코드&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@FunctionalInterface&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Counter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; 
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getCountOfStat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Match&lt;/span&gt; match&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;먼저, 함수형 인터페이스 &lt;code class=&quot;language-text&quot;&gt;Counter&lt;/code&gt;를 만들었다. 각 지표의 횟수를 셀 수 있도록 구현될 수 있는 추상 메서드인 &lt;code class=&quot;language-text&quot;&gt;getCountOfStat&lt;/code&gt;를 가지도록 하였다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;총 헤딩 시도 횟수&lt;/code&gt;를 가져오도록 하고 싶을 땐 추상 메서드의 구현으로써 &lt;code class=&quot;language-text&quot;&gt;Match.getHeaderAttempt()&lt;/code&gt;를 전달하고, &lt;code class=&quot;language-text&quot;&gt;총 중거리 슛 시도 횟수&lt;/code&gt;를 가져오도록 하고 싶을 땐 &lt;code class=&quot;language-text&quot;&gt;Match.getMiddleShootAttempt()&lt;/code&gt;를 전달하면 될 것이다.&lt;/p&gt;
&lt;p&gt;실제로 함수형 인터페이스에 추상 메서드의 구현을 전달하는 부분을 살펴보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calculateTotalCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; matches&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Counter&lt;/span&gt; counter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; count &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Match&lt;/span&gt; match &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; matches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        count &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; counter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCountOfStat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;match&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; count&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getTotalHeaderAttempt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calculateTotalCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;matches&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; match &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getHeaderAttemptCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getTotalMiddleShootAttempt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calculateTotalCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;matches&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; match &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getMiddleShootAttemptCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;calcualteTotalCount&lt;/code&gt; 메서드에서 함수형 인터페이스를 파라미터로 받아 그 구현을 실행하는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;getTotalHeaderAttempt&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;getTotalMiddleShootAttempt&lt;/code&gt; 메서드는 &lt;code class=&quot;language-text&quot;&gt;calcualteTotalCount&lt;/code&gt; 메서드로 함수형 인터페이스의 추상 메서드 구현체를 람다를 이용하여 전달하고 있음을 볼 수 있다. 참고로 람다식은 메서드 참조를 이용하여 불필요한 매개 변수를 제거할 수도 있다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;함수형 인터페이스와 람다를 이용하여 중복 코드를 제거하는 방법에 대해 알아보았다.&lt;/p&gt;
&lt;p&gt;다만, Java는 기본적으로 많이 사용되는 함수형 인터페이스를 제공하기 때문에 매번 함수형 인터페이스를 직접 만들어서 사용하는 건 불필요한 작업일 수 있다.&lt;/p&gt;
&lt;p&gt;이 글에서는 연습 삼아 별도의 함수형 인터페이스를 만들었지만, Java에서 제공하는 &lt;code class=&quot;language-text&quot;&gt;ToIntFunction&lt;/code&gt;을 사용해도 무리가 없었다.&lt;/p&gt;
&lt;p&gt;기본적으로 제공되는 것만 사용해도 웬만한 람다식은 다 만들 수 있기 때문에, 어떤 게 있는지 잘 파악해서 활용하자.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.yes24.com/Product/Goods/77125987&quot;&gt;모던 자바 인 액션&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[리액트에서 도넛 차트를 만들어보자!]]></title><description><![CDATA[리액트에서 도넛 차트를 만들어보자 목차 도넛 차트 Canvas? DIV? SVG? stroke-dasharray stroke-dashoffset 직접 만들어보기 애니메이션 keyframes 도넛 차트 Canvas? DIV? SVG…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-11-10-making-donut-chart-react/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-11-10-making-donut-chart-react/</guid><pubDate>Wed, 10 Nov 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;리액트에서 도넛 차트를 만들어보자&lt;/h1&gt;
&lt;h3&gt;목차&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;도넛 차트&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Canvas? DIV? SVG?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;stroke-dasharray&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;stroke-dashoffset&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;직접 만들어보기&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;애니메이션&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;keyframes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;도넛 차트&lt;/h3&gt;
&lt;h5&gt;Canvas? DIV? SVG?&lt;/h5&gt;
&lt;p&gt;도넛 차트는 Canvas, DIV(html, css), SVG 등 크게 3가지 방법을 사용하여 만들 수 있습니다. 각각의 특징을 간단하게 살펴보면, Canvas를 이용한 방식의 특징은 비트맵 영역을 활용하는 것입니다. 즉, 자바스크립트 코드로 화면 위에 픽셀을 그려 넣는 방법입니다. 이와 같은 방식은 픽셀을 다루게 되어 도형의 크기에 따라 해상도를 따로 고려해야 하는 단점이 있습니다. 다음으로 DIV를 이용한 방법입니다. DIV로 도넛 차트를 그리는 경우, 원을 나타내기 위해 radius를 조절하여 그리게 됩니다. 이 방식은 화면의 크기에 상관없이 도형의 크기가 고정되어 있어 반응형에 취약하다는 단점이 있습니다. 마지막으로 SVG를 사용하는 방법입니다. SVG는 벡터 형식으로, 화면의 크기에 따라 그 크기가 유동적으로 변하는 특징이 있습닌다. 또한, SVG 태그 중 하나인 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;circle&gt;&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;stroke&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;stroke-width&lt;/code&gt; 등의 속성으로 쉽게 도형을 표현할 수 있다는 장점이 있습니다.&lt;/p&gt;
&lt;p&gt;세 가지 방법 중에서 SVG를 이용해 도넛 차트를 만들 경우, 반응형을 지원하면서 이미 내장되어 있는 속성인 &lt;code class=&quot;language-text&quot;&gt;stroke&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;stroke-width&lt;/code&gt; 등을 활용하여 쉽게 만들 수 있다는 장점이 있어 SVG를 선택했습니다.&lt;/p&gt;
&lt;h5&gt;stroke-dasharray&lt;/h5&gt;
&lt;p&gt;도넛 차트를 그리기 앞서 먼저 &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray&lt;/code&gt;의 개념을 먼저 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;stroke-dasharray&lt;/code&gt; 속성은 도형 둘레의 dash와 gap을 정의하는 패턴으로, 도형 둘레의 stroke의 길이와 각각의 stroke 사이 공백의 패턴을 정의합니다. 예시를 통해 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;stroke-dasharray&lt;/code&gt; 속성이 적용되지 않은 기본 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;line&gt;&lt;/code&gt;은 다음과 같습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;svg viexBox&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0 0 100 100&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;line x1&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0&quot;&lt;/span&gt; y1&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;5&quot;&lt;/span&gt; x2&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt; y2&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;5&quot;&lt;/span&gt; stroke&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;green&quot;&lt;/span&gt; strokeDasharray&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;svg&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 기본 svg line에 다양한 stroke-dasharray 값을 적용해보겠습니다.&lt;/p&gt;
&lt;img width=&quot;40%&quot; alt=&quot;new-1&quot; src=&quot;https://user-images.githubusercontent.com/40762111/140966870-741fedd3-45fe-46d3-9fee-02d8c10b0be0.png&quot;&gt;
&lt;p&gt;결과는 위와 같습니다. 하나의 값만 할당하고 있는 &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray=&quot;5&quot;&lt;/code&gt;는 각 stroke의 길이와 각각의 stroke 사이 공백의 길이를 5로 정의한다는 의미입니다. stroke 사이 공백의 길이도 따로 정의할 수 있는데, &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray=&quot;5 10&quot;&lt;/code&gt;과 같이 두 개의 값 사이에 공백을 두어 할당하면(공백 대신 &lt;code class=&quot;language-text&quot;&gt;,&lt;/code&gt;도 가능합니다) , 첫 번째 값은 각 stroke의 길이, 두 번째 값은 각각의 stroke 사이 공백의 길이를 의미하게 됩니다.&lt;/p&gt;
&lt;p&gt;추가적으로 패턴은 아래의 예시처럼 svg 도형의 길이까지만 적용되어, 길이를 넘어가는 부분은 잘리게 됩니다.&lt;/p&gt;
&lt;img width=&quot;40%&quot; alt=&quot;new-2&quot; src=&quot;https://user-images.githubusercontent.com/40762111/140972913-d2d2ea1e-550a-4668-b451-a33b19a2618b.png&quot;&gt;
&lt;p&gt;그럼 도넛에도 적용해 보겠습니다.&lt;/p&gt;
&lt;p&gt;도넛의 둘레의 길이가 약 565인 원이 있습니다. 이 도넛에 &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray=&quot;20&quot;&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray=&quot;40&quot;&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray=&quot;60&quot;&lt;/code&gt; &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray=&quot;40 20&quot;&lt;/code&gt;을 각각 적용해 살펴보겠습니다.&lt;/p&gt;
&lt;img width=&quot;40%&quot; alt=&quot;3&quot; src=&quot;https://user-images.githubusercontent.com/40762111/140849596-83847816-4d59-4488-957a-ebb4049f7101.png&quot;&gt;
&lt;img width=&quot;40%&quot; alt=&quot;4&quot; src=&quot;https://user-images.githubusercontent.com/40762111/140849600-51904ca8-09e5-4522-a125-5ad634ad6c33.png&quot;&gt;
&lt;img width=&quot;40%&quot; alt=&quot;5&quot; src=&quot;https://user-images.githubusercontent.com/40762111/140849605-ce7cd8a5-b675-48d7-a94b-8a5d27924a26.png&quot;&gt;
&lt;img width=&quot;40%&quot; alt=&quot;6&quot; src=&quot;https://user-images.githubusercontent.com/40762111/140851420-b836580a-9024-4f09-a237-f8233ebbd9d3.png&quot;&gt;
&lt;p&gt;위의 svg line 예시와 동일합니다. 다만 svg circle은 &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray&lt;/code&gt; 패턴이 시계 3시 방향에서 시작한다는 특징이 있습니다.&lt;/p&gt;
&lt;h5&gt;stroke-dashoffset&lt;/h5&gt;
&lt;p&gt;이번에는 &lt;code class=&quot;language-text&quot;&gt;stroke-dashoffset&lt;/code&gt; 속성을 알아보겠습니다. &lt;code class=&quot;language-text&quot;&gt;stroke-dashoffset&lt;/code&gt;은 dash array를 렌더링 할 때 offset을 정의하는 속성입니다. 예시로 확인해 보겠습니다.&lt;/p&gt;
&lt;img width=&quot;40%&quot; alt=&quot;7&quot; src=&quot;https://user-images.githubusercontent.com/40762111/141059819-b72235f6-811f-4cd4-ab3b-630cd7ab813d.png&quot;&gt;
&lt;img width=&quot;80%&quot; alt=&quot;8&quot; src=&quot;https://user-images.githubusercontent.com/40762111/141062621-9cc22bb1-84ea-4f13-950b-4d1a0dd1d145.png&quot;&gt;
&lt;p&gt;svg line의 기본 형태는 &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray=&quot;10 5&quot;&lt;/code&gt;입니다. 여기에 &lt;code class=&quot;language-text&quot;&gt;stroke-dashoffset&lt;/code&gt; 속성을 적용하면 할당되는 값에 따라 화면에 보이는 패턴이 변경됩니다. 두 번째 그림에서 네모 박스는 실제로 화면에서 보이는 영역입니다. 양수인 경우, 할당된 값만큼 네모 박스가 오른쪽으로 이동하여 박스 안에 속한 패턴이 화면에 보이게 됩니다. 음수인 경우, 그 반대인 왼쪽으로 이동합니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;stroke-dashoffset&lt;/code&gt; 값에 따라 네모 박스가 이동한다고 하여 화면에 보이는 영역 자체가 이동한다고 착각할 수 있습니다. 하지만, 첫 번째 그림과 같이 화면에 보이는 영역의 위치는 동일하고 영역 내 패턴의 모습만 달라지게 됩니다.&lt;/p&gt;
&lt;h5&gt;직접 만들어보기&lt;/h5&gt;
&lt;p&gt;만들고자 하는 차트는 75%를 표시하고 있는 도넛 차트입니다. 바로 진행해 보겠습니다.&lt;/p&gt;
&lt;p&gt;환경은 CRA를 이용하여 구성했고, 전체적인 코드를 보여주기 위해서 도넛 차트를 컴포넌트로 따로 분리하지 않았습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// App.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div style&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;marginTop&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;50px&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;marginLeft&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;50px&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div style&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;200px&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;200px&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;svg viewBox&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0 0 200 200&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;circle cx&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt; cy&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt; r&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;90&quot;&lt;/span&gt; fill&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;none&quot;&lt;/span&gt; stroke&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;beige&quot;&lt;/span&gt; strokeWidth&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;20&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;circle
          cx&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt;
          cy&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt;
          r&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;90&quot;&lt;/span&gt;
          fill&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;none&quot;&lt;/span&gt;
          stroke&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;green&quot;&lt;/span&gt;
          strokeWidth&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;20&quot;&lt;/span&gt;
          strokeDasharray&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.75&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;svg&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; App&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;img width=&quot;40%&quot; alt=&quot;9&quot; src=&quot;https://user-images.githubusercontent.com/40762111/141073412-6a563d47-d5bc-457c-a101-0964bcc1b159.png&quot;&gt;
&lt;p&gt;설명하기 앞서, 올바른 속성은 위 코드처럼 &lt;code class=&quot;language-text&quot;&gt;strokeDasharray={2 * Math.PI * 90 * 0.75}&lt;/code&gt;인데, 이해를 돕기 위해서 &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray=&quot;2πr * 0.75&quot;&lt;/code&gt;라고 표현하겠습니다.&lt;/p&gt;
&lt;p&gt;75%의 도넛 차트를 그리기 위해서 &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray=&quot;2πr * 0.75&quot;&lt;/code&gt; 속성을 사용했습니다. 전체 원의 둘레 공식인 2 πr에 0.75를 곱한 값입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;둘레의 길이는 &lt;code class=&quot;language-text&quot;&gt;2* Math.PI * 반지름&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;document.getElementById().getTotalLength()&lt;/code&gt; 등으로 구할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;다음 과정은 도넛 차트를 반시계 방향으로 90° 회전시키는 것입니다. svg에서 도형을 회전시키기 위한 방법으로 &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;stroke-dashoffset&lt;/code&gt; 속성을 활용할 수 있는데, &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray&lt;/code&gt; 속성으로 먼저 패턴을 정의하고, &lt;code class=&quot;language-text&quot;&gt;stroke-dashoffset&lt;/code&gt; 속성을 이용해서 패턴에서 화면에 보이는 영역을 조정하는 방식입니다.&lt;/p&gt;
&lt;p&gt;먼저 현재 도넛 차트에 정의되어 있는 패턴을 확인해 보겠습니다.&lt;/p&gt;
&lt;img width=&quot;100%&quot; alt=&quot;10&quot; src=&quot;https://user-images.githubusercontent.com/40762111/141143067-03ad26e2-e503-4bdb-b546-20b5f2d0af56.png&quot;&gt;
&lt;p&gt;패턴은 위 그림과 같이 &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray=&quot;2πr * 0.75&quot;&lt;/code&gt;로, stroke의 길이와 stroke 사이 간격 모두 &lt;code class=&quot;language-text&quot;&gt;2πr * 0.75&lt;/code&gt;로 정의되어 있고, 화면에 보이는 영역은 빨간 네모 박스 부분입니다. 하지만 최종적으로 화면에 보여야 하는 영역은 다음과 같습니다.&lt;/p&gt;
&lt;img width=&quot;40%&quot; alt=&quot;11-2&quot; src=&quot;https://user-images.githubusercontent.com/40762111/141103931-7accdbb9-858d-47e2-8a06-afcfec6c8295.png&quot;&gt;
&lt;img width=&quot;50%&quot; alt=&quot;11&quot; src=&quot;https://user-images.githubusercontent.com/40762111/141106723-38c43598-2eed-4f87-8e93-f76a91319c6c.png&quot;&gt;
&lt;p&gt;circle에서 stroke가 3시 방향에서 시작되므로, 12시 방향을 0°라고 했을 때, 3시 방향인 90°에서 9시 방향인 270°까지 색이 채워지고, 270°에서 360(0)°까지 공백, 0°에서 90°까지 다시 색이 채워져야 합니다.&lt;/p&gt;
&lt;p&gt;현재 정의되어 있는 패턴으로는 &lt;code class=&quot;language-text&quot;&gt;stroke-dashoffset&lt;/code&gt;을 조정해도 목표로 하는 패턴을 표현할 수 없기 때문에, 패턴을 다시 정의합니다.&lt;/p&gt;
&lt;img width=&quot;100%&quot; alt=&quot;12&quot; src=&quot;https://user-images.githubusercontent.com/40762111/141143083-b80a70a7-f02c-48bc-9245-d2af384008b2.png&quot;&gt;
&lt;p&gt;stroke의 길이는 전체 둘레의 75%에 해당하는 &lt;code class=&quot;language-text&quot;&gt;2πr * 0.75&lt;/code&gt;, 각 stroke 사이 공백은 전체 둘레의 25%에 해당하는 &lt;code class=&quot;language-text&quot;&gt;2πr * 0.25&lt;/code&gt;로 &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray=&quot;2πr * 0.75, 2πr * 0.25&quot;&lt;/code&gt;로 변경합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;각 stroke 사이 공백을 &lt;code class=&quot;language-text&quot;&gt;2πr * 0.25&lt;/code&gt;로 한 이유는 &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray&lt;/code&gt;의 첫 번째 값과 두 번째 값을 합쳐 원 전체 둘레의 길이가 나오게 하기 위함입니다. 이렇게 하면 패턴이 반복되는 길이의 기준이 전체 원 둘레가 되어, 이후 &lt;code class=&quot;language-text&quot;&gt;stroke-dashoffset&lt;/code&gt; 속성을 적용하기 용이합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;img width=&quot;100%&quot; alt=&quot;13&quot; src=&quot;https://user-images.githubusercontent.com/40762111/141143089-6609274b-b26f-420f-a381-ca588566f2f3.png&quot;&gt;
&lt;p&gt;그러고는 &lt;code class=&quot;language-text&quot;&gt;stroke-dashoffset=&quot;2πr * 0.25&quot;&lt;/code&gt; 속성을 적용하여, 원하는 패턴이 화면에 보이도록 조정합니다. 위 그림에서 화면에 보이는 영역인 빨간 네모 박스의 시작이 180°로 되어 있고 그 위에 있는 도형의 모양도 이전 그림과 동일한데, 이는 이전 그림에서 &lt;code class=&quot;language-text&quot;&gt;stroke-dashoffset&lt;/code&gt; 속성을 적용하여 화면에 보이는 영역이 이동했다는 것을 나타내기 위함입니다. 실제로는 &lt;code class=&quot;language-text&quot;&gt;stroke-dashoffset&lt;/code&gt;의 조정과 동시에 다음과 같이 패턴이 변경됩니다.&lt;/p&gt;
&lt;img width=&quot;100%&quot; alt=&quot;14&quot; src=&quot;https://user-images.githubusercontent.com/40762111/141143093-2d2d07b5-ed87-4e52-a85f-4b613ea75a4a.png&quot;&gt;
&lt;p&gt;최종 코드는 다음과 같습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// App.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div style&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;marginTop&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;50px&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;marginLeft&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;50px&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div style&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;200px&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;200px&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;svg viewBox&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0 0 200 200&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;circle cx&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt; cy&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt; r&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;90&quot;&lt;/span&gt; fill&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;none&quot;&lt;/span&gt; stroke&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;beige&quot;&lt;/span&gt; strokeWidth&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;20&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;circle
          cx&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt;
          cy&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt;
          r&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;90&quot;&lt;/span&gt;
          fill&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;none&quot;&lt;/span&gt;
          stroke&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;green&quot;&lt;/span&gt;
          strokeWidth&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;20&quot;&lt;/span&gt;
          strokeDasharray&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.75&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.25&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          strokeDashoffset&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.25&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;svg&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; App&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;img width=&quot;40%&quot; alt=&quot;15&quot; src=&quot;https://user-images.githubusercontent.com/40762111/141095451-abd5cb83-a6aa-40d6-b299-aab495827b9c.png&quot;&gt;
&lt;p&gt;위 과정을 보면 &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;stroke-dashoffset&lt;/code&gt; 속성을 이용해서 도넛 차트 만드는 방법이 굉장히 복잡해보입니다. 하지만, 다음과 같이 계산하여 쉽게 만들 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// React&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// x = 원의 전체 둘레 길이&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// a = 데이터에 해당하는 원의 둘레 길이 ex) 63% -&gt; x * 0.63&lt;/span&gt;

&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;circle strokeDasharray&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;a&gt; &amp;lt;x - a&gt;&quot;&lt;/span&gt; strokeDashoffset&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;0.25 * x&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;애니메이션&lt;/h3&gt;
&lt;p&gt;애니메이션은 &lt;code class=&quot;language-text&quot;&gt;@keyframes&lt;/code&gt;를 이용하여 간단히 구현할 수 있습니다. 다음 예시는 스타일 라이브러리로는 &lt;a href=&quot;https://emotion.sh/docs/introduction&quot;&gt;emotion&lt;/a&gt;을 사용했습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// App.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; AnimatedCircle &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./App.styles&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div style&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;marginTop&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;50px&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;marginLeft&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;50px&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div style&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;200px&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;200px&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;svg viewBox&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0 0 200 200&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;circle cx&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt; cy&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt; r&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;90&quot;&lt;/span&gt; fill&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;none&quot;&lt;/span&gt; stroke&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;beige&quot;&lt;/span&gt; strokeWidth&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;20&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;AnimatedCircle
          cx&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt;
          cy&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt;
          r&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;90&quot;&lt;/span&gt;
          fill&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;none&quot;&lt;/span&gt;
          stroke&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;green&quot;&lt;/span&gt;
          strokeWidth&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;20&quot;&lt;/span&gt;
          strokeDasharray&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.75&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.25&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          strokeDashoffset&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.25&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;svg&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; App&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// App.styles.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; styled &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@emotion/styled&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; AnimatedCircle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; styled&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;circle&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
  animation: circle-fill-animation 2s ease;

  @keyframes circle-fill-animation {
    0% {
      stroke-dasharray: 0 &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;;
    }
  }
&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; AnimatedCircle &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;App.styles.js&lt;/code&gt; 파일의 &lt;code class=&quot;language-text&quot;&gt;@keyframes&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;0%&lt;/code&gt;는 애니메이션의 시작을 의미합니다. 즉, 애니메이션 초기에는 &lt;code class=&quot;language-text&quot;&gt;stroke-dasharray: 0 ${2 * Math.PI * 90}&lt;/code&gt; 속성으로 각 stroke의 길이를 정의하지 않고, stroke 사이 공백을 원 전체 둘레로 정의하는 것을 의미합니다.&lt;/p&gt;
&lt;video width=&quot;40%&quot; autoplay loop&gt;
    &lt;source src=&quot;https://user-images.githubusercontent.com/40762111/141101301-03ad3f18-d37b-4930-962b-4aba3e1f7c5f.mov&quot; type=&quot;video/mp4&quot;&gt;
&lt;/video&gt;
&lt;h3&gt;참조&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dashoffset&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dashoffset&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/CSS/@keyframes&quot;&gt;https://developer.mozilla.org/ko/docs/Web/CSS/@keyframes&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[웹 소켓에 대해 알아보자! - 테스트 편]]></title><description><![CDATA[이 글은 웹 소켓으로 만든 채팅방을 어떻게 테스트할지 고민하는 독자를 위해 작성되었다. 웹 소켓은 어떻게 테스트하면 좋을까? Babble…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-11-10-web-socket-test/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-11-10-web-socket-test/</guid><pubDate>Wed, 10 Nov 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이 글은 웹 소켓으로 만든 채팅방을 어떻게 테스트할지 고민하는 독자를 위해 작성되었다.&lt;/p&gt;
&lt;h2&gt;웹 소켓은 어떻게 테스트하면 좋을까?&lt;/h2&gt;
&lt;p&gt;Babble 팀의 데모데이 날 부스에서 질문을 받았다. “웹 소켓은 테스트를 어떻게 해야 할 지 감이 안 잡히는데 어떻게 구현하셨나요?” 테스트 코드에 정해진 정답은 없지만, 우리 팀도 웹 소켓 테스트 작성에 꽤 많이 고민했던 적이 있다. 고생해준 프로젝트 팀원 덕에 블로킹 큐를 이용하여 메시지를 저장하고 테스트를 해보는 방식으로 웹 소켓 테스트를 구현했다. 이 방법을 한번 알아보자.&lt;/p&gt;
&lt;p&gt;사전에 웹 소켓 구현에 필요한 코드량이 많기 때문에 코드만 언급하고 테스트 코드 구현으로 넘어간다. 웹 소켓 실습에 대해 연습하고 싶다면 &lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-09-05-web-socket-practice/&quot;&gt;여기&lt;/a&gt;를 먼저 참고하는 것을 추천한다.&lt;/p&gt;
&lt;h2&gt;프로젝트 설명&lt;/h2&gt;
&lt;p&gt;이 코드는 Babble 팀에서 사용하는 도메인 모델과는 차이가 있다. 순전히 테스트를 실습해보기 위해 만든 코드다.&lt;/p&gt;
&lt;p&gt;단순히 채팅하는 프로그램이 아니라 채팅방이 있고 사용자가 채팅방에 들어오거나 나가면서 1:N 의 채팅을 하는 프로그램이다. 채팅방을 위한 도메인부터 컨트롤러까지 모든 코드가 있기 때문에 자세한 구현은 &lt;a href=&quot;https://github.com/lns13301/jwp-web-socket/tree/feature/practice&quot;&gt;예제 저장소&lt;/a&gt;에서 확인한다.&lt;/p&gt;
&lt;h2&gt;테스트 코드 만들기&lt;/h2&gt;
&lt;p&gt;핵심 기능이라고 할 수 있는 채팅방 입장, 채팅방에서 주고받는 메시지가 올바르게 송수신 되는지, 채팅 방 퇴장을 확인하는 테스트는 어떻게 구현할 수 있을까?&lt;/p&gt;
&lt;p&gt;웹 소켓 테스트 코드에 대해 살펴본다. rest-assured 를 사용한 api 테스트를 진행한다. rest-assured 는 given, when, then 패턴으로 테스트 코드를 작성하며 Json Data 를 쉽게 검증할 수 있다.&lt;/p&gt;
&lt;h2&gt;채팅방 관련 기능 테스트&lt;/h2&gt;
&lt;p&gt;메시지를 큐에 저장하고 채팅 송수신이 끝난 후 실제로 받은 메시지가 큐에 올바르게 들어있는지 확인하는 방식으로 테스트를 만들었다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootTest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webEnvironment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebEnvironment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RANDOM_PORT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSocketChattingTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@LocalServerPort&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; port&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BlockingQueue&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SessionResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BlockingQueue&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MessageResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; messages&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserRepository&lt;/span&gt; userRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RoomRepository&lt;/span&gt; roomRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@BeforeEach&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;RestAssured&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;port &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; port&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        users &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LinkedBlockingDeque&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        messages &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LinkedBlockingDeque&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        유저_삽입&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        방_생성&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; 유저_삽입&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        userRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;와일더&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        userRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;마이클&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        userRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;제이슨&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        userRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;오스카&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; 방_생성&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        roomRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Room&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        roomRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Room&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        roomRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Room&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@DisplayName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;유저가 입장하고 메시지를 보내면 해당 방에 메시지가 브로드 캐스팅된다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;enterUserAndBroadCastMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExecutionException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeoutException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Room&lt;/span&gt; room &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; roomRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     	  &lt;span class=&quot;token class-name&quot;&gt;UserResponse&lt;/span&gt; expectedUser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;MessageResponse&lt;/span&gt; expected &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessageResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;채팅을 보내 봅니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Settings&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;WebSocketStompClient&lt;/span&gt; webSocketStompClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; 웹_소켓&lt;span class=&quot;token function&quot;&gt;_STOMP_CLIENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        webSocketStompClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setMessageConverter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MappingJackson2MessageConverter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Connection&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ListenableFuture&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StompSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; connect &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; webSocketStompClient
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ws://localhost:&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; port &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/ws-connection&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StompSessionHandlerAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;StompSession&lt;/span&gt; stompSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; connect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeUnit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SECONDS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        stompSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/sub/rooms/%s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; room&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StompFrameHandlerImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SessionResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        stompSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/pub/rooms/%s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; room&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SessionRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1A2B3C4D&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        stompSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/sub/rooms/%s/chat&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; room&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StompFrameHandlerImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessageResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; messages&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        stompSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/sub/rooms/%s/chat&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; room&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessageRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;채팅을 보내 봅니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;MessageResponse&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; messages&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;poll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeUnit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SECONDS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Then&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;usingRecursiveComparison&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expected&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSocketStompClient&lt;/span&gt; 웹_소켓&lt;span class=&quot;token function&quot;&gt;_STOMP_CLIENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;StandardWebSocketClient&lt;/span&gt; standardWebSocketClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StandardWebSocketClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;WebSocketTransport&lt;/span&gt; webSocketTransport &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSocketTransport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;standardWebSocketClient&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Transport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; transports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;singletonList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSocketTransport&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;SockJsClient&lt;/span&gt; sockJsClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SockJsClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;transports&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSocketStompClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sockJsClient&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;벌써 머리가 아프기 시작한다. 안 그래도 낯선 Web Socket 이란 기술을 테스트해 보려고 하다가 머리가 새하얗게 될 것만 같다. 이쯤 되면 그냥 Postman 으로 테스트를 하고 싶어진다. 그래도 참고 테스트 코드를 구현해볼 의지가 있는 독자는 참고하길 바란다. 핵심이 되는 부분을 살펴보자.&lt;/p&gt;
&lt;p&gt;웹 소켓은 스레드에서 동작하기 때문에 &lt;code class=&quot;language-text&quot;&gt;BlockingQueue&lt;/code&gt; 에 담아둔다. &lt;code class=&quot;language-text&quot;&gt;BlockingQueue&lt;/code&gt;를 사용하지 않으면 메시지를 주고 받고 나서 데이터가 남아있지 않다.&lt;/p&gt;
&lt;p&gt;테스트 코드에서 사용할 &lt;code class=&quot;language-text&quot;&gt;WebSocketStompClient&lt;/code&gt; 를 만든다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSocketStompClient&lt;/span&gt; 웹_소켓&lt;span class=&quot;token function&quot;&gt;_STOMP_CLIENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;StandardWebSocketClient&lt;/span&gt; standardWebSocketClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StandardWebSocketClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;WebSocketTransport&lt;/span&gt; webSocketTransport &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSocketTransport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;standardWebSocketClient&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Transport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; transports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;singletonList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSocketTransport&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;SockJsClient&lt;/span&gt; sockJsClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SockJsClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;transports&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSocketStompClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sockJsClient&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위에서 만든 Client 에 MessageConverter 를 적용한다. StringMessageConverter, SimpleMessageConverter 등 여러 MessageConverter 구현체가 있다. 여기서는 JSON 을 지원하는 &lt;code class=&quot;language-text&quot;&gt;MappingJackson2MessageConverter&lt;/code&gt; 클래스를 사용했다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Settings&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;WebSocketStompClient&lt;/span&gt; webSocketStompClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; 웹_소켓&lt;span class=&quot;token function&quot;&gt;_STOMP_CLIENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
webSocketStompClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setMessageConverter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MappingJackson2MessageConverter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다음은 연결 요청 부분이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Connection&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;ListenableFuture&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StompSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; connect &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; webSocketStompClient
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ws://localhost:&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; port &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/ws-connection&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StompSessionHandlerAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;StompSession&lt;/span&gt; stompSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; connect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeUnit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SECONDS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;방 입장 및 채팅을 보내는 부분이다. 컨트롤러에서 설정해둔 &lt;code class=&quot;language-text&quot;&gt;@MessageMapping&lt;/code&gt; 의 URI 로 접근한다. 여기서 &lt;code class=&quot;language-text&quot;&gt;StompFrameHandlerImpl&lt;/code&gt; 클래스는 직접 구현한 것이니 뒤에서 다루도록 한다. TimeOut 시간은 너무 짧게 설정하면 디버깅 모드로 내부를 살펴볼 때 데이터가 남아있지 않기 때문에 적당하게 시간을 설정한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;stompSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/sub/rooms/%s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; room&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StompFrameHandlerImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SessionResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        stompSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/pub/rooms/%s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; room&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SessionRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1A2B3C4D&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        stompSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/sub/rooms/%s/chat&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; room&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StompFrameHandlerImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessageResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; messages&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        stompSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/sub/rooms/%s/chat&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; room&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessageRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;채팅을 보내 봅니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;BlockingQueue&lt;/code&gt; 에 저장되어 있던 메시지를 꺼낸다. 미리 만들어둔 값과 비교한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;MessageResponse&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; messages&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;poll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeUnit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SECONDS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Then&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUserResponses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;usingRecursiveComparison&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expectedUser&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;messageResponse&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;usingRecursiveComparison&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expectedMessage&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;StompFrameHandlerImpl&lt;/code&gt; 클래스는 직접만든 StompFrameHandler 구현체다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StompFrameHandlerImpl&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StompFrameHandler&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BlockingQueue&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; responses&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StompFrameHandlerImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BlockingQueue&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; responses&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;responses &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; responses&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Type&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getPayloadType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StompHeaders&lt;/span&gt; headers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StompHeaders&lt;/span&gt; headers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; payload&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;payload&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        responses&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;offer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; payload&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Stomp Frame 을 어떻게 처리할지 지정해주는 부분이다. 위의 객체를 생성할 때 payload 를 받을 클래스의 타입을 지정하고, payload 가 담길 BlockingQueue 를 넣어준다. 테스트를 실행하면 테스트가 성공적으로 동작하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Blocking Queue&lt;/code&gt; 에 담긴 메시지를 통해 테스트하는 방식을 알아봤다. 오늘 알아본 테스트 코드 하나면 방 입장, 채팅, 퇴장 등 웬만한 테스트를 응용할 수 있으리라 생각한다. 웹 소켓은 양방향 통신이라는 매력적인 기술이지만 참고할 레퍼런스가 상당히 적다는 점이 아쉽다. 프로젝트에 웹 소켓을 적용하려는 모든 분을 응원한다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.scottlogic.com/2019/07/23/Testing-WebSockets-for-beginners.html&quot;&gt;Testing WebSockets for beginners&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[쿠버네티스 입문하기]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-11-07-kubernetes/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-11-07-kubernetes/</guid><pubDate>Sun, 07 Nov 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;기존에 웹 서버를 이용해 로드밸런싱, 포트포워딩, 헬스체킹, 무중단 배포 등 안정적인 인프라를 구성하고 있었다. 지금의 인프라 구조도 좋은데 쿠버네티스라는 기술을 적용함으로써 얻는 이점은 무엇일까?&lt;/p&gt;
&lt;p&gt;이미지가 변경되면 기존 컨테이너를 새로운 컨테이너로 어떻게 변경할지, 부하가 들어오면 어떻게 부하를 분산할지, 컨테이너가 예기치 못하게 중단될 경우 어떻게 대처할지 등을 웹 서버에서 처리하려면 개발자가직접 설정을 해주고 수정해주어야 했다.&lt;/p&gt;
&lt;p&gt;하지만 쿠버네티스는 일정한 상태를 입력해두면 내부적으로 Master node의 관리 하에 다양한 컨테이너를 구동한다. 컨테이너 단위로 서비스를 관리하니 서로 다른 컨테이너 간 영향을 끼치지 않고 os를 매번 새로 시작할 필요도 없다는 장점을 갖고 있다. 뿐만 아니라, 서버의 리소스를 내부적으로 원하는 상태로 알아서 관리하며 컨테이너 증설 시 내장 컨트롤러가 최적의 노드를 찾아 배치하고 자동으로 리소스 확장이 가능하다.&lt;/p&gt;
&lt;p&gt;정리하자면, 쿠버네티스를 이용해 아래와 같은 기능을 누릴 수 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;무중단 배포&lt;/li&gt;
&lt;li&gt;health checking&lt;/li&gt;
&lt;li&gt;Reverse proxy 감지&lt;/li&gt;
&lt;li&gt;컨테이너 scale-out&lt;/li&gt;
&lt;li&gt;여러 node에서 컨테이너를 관리하며 ip 중복 방지&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이렇게 컨테이너의 배포, 관리, 확장, 네트워킹 등을 자동화하는 것을 &lt;strong&gt;컨테이너 오케스트레이션&lt;/strong&gt;이라고 한다.&lt;/p&gt;
&lt;h3&gt;쿠버네티스 배포 인프라 구성&lt;/h3&gt;
&lt;p&gt;kubectl 명령어가 입력되면 아래와 같은 흐름으로 처리가 진행된다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;kubectl을 이용해 명령하면 api server에 전달된다.&lt;/li&gt;
&lt;li&gt;인증/권한승인/승인제어 등의 확인절차를 거친다.&lt;/li&gt;
&lt;li&gt;api server가 object를 적절히 변형(mutate)한다.&lt;/li&gt;
&lt;li&gt;api server가 유효성 검사(validate)한다.&lt;/li&gt;
&lt;li&gt;etcd에 명령이 기록된다.&lt;/li&gt;
&lt;li&gt;응답&lt;/li&gt;
&lt;/ol&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/43775108/135277150-6c626d51-d338-4125-92b2-6183c5ed5373.png&quot;&gt;
&lt;p&gt;출처: &lt;a href=&quot;https://blog.heptio.com/core-kubernetes-jazz-improv-over-orchestration-a7903ea92ca&quot;&gt;https://blog.heptio.com/core-kubernetes-jazz-improv-over-orchestration-a7903ea92ca&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;kubectl api-resources&lt;/code&gt; 를 입력해 쿠버네티스에서 사용할 수 있는 오브젝트들에 대해 조회할 수 있다. 그 오브젝트 설명이 보고싶다면 &lt;code class=&quot;language-text&quot;&gt;kubectl explain {오브젝트명}&lt;/code&gt; 을 이용한다.&lt;/p&gt;
&lt;p&gt;쿠버네티스에서 모든 리소스는 오브젝트의 형태로 관리한다. 각 오브젝트를 원하는 상태로 유지하는데, 그 상태를 yml로 관리할 수 있다. 쿠버네티스 오브젝트에는 구성단위가 되는 기본 오브젝트와 이를 생성하고 관리하는 역할을 하는 컨트롤러가 존재한다.&lt;/p&gt;
&lt;p&gt;이제부터 쿠버네티스의 각 요소에 대해서 알아보도록 하자.&lt;/p&gt;
&lt;h4&gt;1. Master Node&lt;/h4&gt;
&lt;p&gt;모든 Workder Node들을 제어하고 전체 클러스터를 관리해주는 컨트롤러로, 전체적인 제어/관리를 하기 위한 관리 서버이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Api server&lt;/p&gt;
&lt;p&gt;k8s(쿠버네티스)의 모든 구성 요소는 api server와 통신한다.&lt;/p&gt;
&lt;p&gt;etcd와 통신할 수 있는 유일한 요소이다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;etcd&lt;/p&gt;
&lt;p&gt;위에서 언급한 쿠버네티스가 바라는 오브젝트(pod, service, replicaset)들을 k-v 형태로 저장한다.&lt;/p&gt;
&lt;p&gt;etcd 인스턴스는 과반 투표를 하는 RAFT 알고리즘을 사용하기 때문에 홀수로 구성해야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;scheduler&lt;/p&gt;
&lt;p&gt;pod를 어느 node에 배치할지 판단하는 역할을 수행한다.&lt;/p&gt;
&lt;p&gt;스케줄될 수 있는 모든 노드의 목록과 affinity의 명세를 확인하고 우선순위를 지정해준다.&lt;/p&gt;
&lt;p&gt;node가 요청을 이행할 수 있는지, 리소스가 부족하지 않은지, node가 PodSpec의 Node Selector에 맞는 라벨을 가질 수 있는지, port가 이미 사용되고 있는지, 볼륨이 마운트 되는지 확인한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2. Worker Node&lt;/h4&gt;
&lt;p&gt;컨테이너가 배포될 장소로 master node로부터 파드를 관리하도록 명령받는 주체이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;기본 설정을 위한 구성요소&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;kubelet&lt;/p&gt;
&lt;p&gt;pod 내 컨테이너 실행을 직접 관리할 뿐만 아니라 마스터와 워커 노드 간의 통신 역할도 담당하는 에이전트이다.&lt;/p&gt;
&lt;p&gt;node 상태, Podspec에 맞춰 Pod들이 정상적으로 동작하는지 확인하고 cAdvisor를 통해 정보를 수집한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;kube-proxy&lt;/p&gt;
&lt;p&gt;가상 네트워크 관리&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DNS pod&lt;/p&gt;
&lt;p&gt;ip가 중복인 경우 각 service endpoint를 아는 dns pod가 worker node에 존재&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;서비스를 위한 구성요소&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Cluster&lt;/p&gt;
&lt;p&gt;컨테이너를 실행하기 위한 주체이다. 내부에 하나 이상의 master node, worker node가 존재해야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pod&lt;/p&gt;
&lt;p&gt;컨테이너의 집합으로 컨테이너 실행을 담당한다. 컨테이너 어플리케이션을 배포하기 위한 기본 단위로 컨테이너의 생성, 운영, 제거 등을 관리한다.&lt;/p&gt;
&lt;p&gt;보통 하나의 pod에 하나의 컨테이너(온전한 어플리케이션)만 실행한다. 하지만 설정 리로더나 로그 수집 등을 위한 추가 컨테이너가 포함될 수 있고 이를 사이드카 컨테이너라고 부른다. 이렇게 만들어진 하나의 포드가 완전한 애플리케이션으로 동작하는 것이다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Replicaset&lt;/p&gt;
&lt;p&gt;복제 담당으로 pod의 갯수가 부족할 경우 복제해서 생성하는 일을 담당한다. 정해진 수의 동일한 포드가 항상 실행되도록 관리한다. 노드 장애 등의 이유로 포드를 사용할 수 없으면 다른 노드에서 포드를 다시 생성한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deployment&lt;/p&gt;
&lt;p&gt;replicaset과 pod의 정보를 정의하는 오브젝트로 replicaset의 상위 오브젝트라 deployment가 생성되면 replicaset도 함께 생성된다.&lt;/p&gt;
&lt;p&gt;배포 담당으로 배포되고 있는상태를 확인하고 replicaset의 변경 사항을 저장하는 revision을 남겨 롤백할 수 있다.&lt;/p&gt;
&lt;p&gt;rolling update, scale out이 가능하다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Service&lt;/p&gt;
&lt;p&gt;deployment를 통해 생성된 포드에 접근하도록 도와주는 Endpoint이다. 여러 개의 포드에 쉽게 접근할 수 있도록 고유한 도메인 이름 부여하고 요청을 분산하는 로드밸런서 기능 수행한다.&lt;/p&gt;
&lt;p&gt;하나의 애플리케이션 묶음으로 Pod를 그룹핑하고 라벨로 분류해준다. Pod가 새로 생성되더라도 service를 통해 동일 도메인으로 접근할 수 있다.&lt;/p&gt;
&lt;p&gt;상황에 따라 맞는 type을 지정해 사용한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ClusterIP&lt;/p&gt;
&lt;p&gt;쿠버네티스 내부에서만 포드들에 접근할 때 사용하는 type&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;NodePort&lt;/p&gt;
&lt;p&gt;포드에 접근할 수 있는 포트를 클러스터의 모든 노드에 동일하게 개방해, 외부에서 포드에 접근 가능하도록 하는 type&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;LoadBalancer&lt;/p&gt;
&lt;p&gt;플랫폼에서 제공하는 로드 밸런서를 동적으로 프로비저닝해 포드에 연결하고 외부에서 접근 가능하도록 하는 type&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ingress&lt;/p&gt;
&lt;p&gt;Service의 NodePort + LoadBalancer를 합쳐논 요소이다. 외부에서 들어온 요청을 어떠한 서비스로 라우팅해주고, 같은 ip에 대해 다른 도메인 이름으로 요청이 들어왔을 때 가상 호스트 기반의 요청 처리, SSL/TLS 보안 연결 처리의 역할을 수행한다.&lt;/p&gt;
&lt;p&gt;외부의 요청을 대신 응답해주는 Reverse Proxy의 역할을 담당한다. Ingress를 이용해 각 Deployment에 대해 일일히 설정을 적용할 필요 없이 외부 요청에 대한 처리를 편리하게 관리할 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Network&lt;/h4&gt;
&lt;p&gt;Pod 내 컨테이너들은 하나의 veth0을 공유하며 외부에서 같은 ip로 접근해 port로 컨테이너 구분된다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;컨테이너를 생성하면 veth 생성되고 컨테이너 내 eth0과 연결한다.&lt;/li&gt;
&lt;li&gt;veth들은 docke0을 통해 컨테이너들간 통신 가능하다.&lt;/li&gt;
&lt;li&gt;컨테이너는 gateway인 docker0을 거쳐 외부와 통신한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/43775108/135275554-2b95e0db-415b-4163-9ae3-258215073902.png&quot;&gt;
&lt;p&gt;출처: &lt;a href=&quot;https://medium.com/google-cloud/understanding-kubernetes-networking-pods-7117dd28727&quot;&gt;https://medium.com/google-cloud/understanding-kubernetes-networking-pods-7117dd28727&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이렇게 쿠버네티스의 구성 요소에 대해서 간략히 알아보았다.&lt;/p&gt;
&lt;p&gt;쿠버네티스를 적용하면 편리한 면도 있지만, 프로젝트에 서비스 간 통신이 필요한지, 비용적인 여유가 되는지 등 다양한 고려가 필요하다.&lt;/p&gt;
&lt;p&gt;상황에 알맞게 적용할 수 있어야 한다.&lt;/p&gt;
&lt;h4&gt;출처&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://kubernetes.io/&quot;&gt;공식문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.heptio.com/core-kubernetes-jazz-improv-over-orchestration-a7903ea92ca&quot;&gt;Core Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/google-cloud/understanding-kubernetes-networking-pods-7117dd28727&quot;&gt;Understanding kubernetes networking&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Hash와 HashMap]]></title><description><![CDATA[서비스를 개발하거나 알고리즘, 자료구조를 공부하면서 Map, Set 등의 자료구조를 자주 이용했었다. Map 이나 Set 자료구조를 사용하면 자료의 양이 엄청 많아도 검색하는데 O(1) 만큼의 시간복잡도를 가지게 된다. 이렇게 빠른 검색을 위해 Map…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-11-05-hash-hashmap/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-11-05-hash-hashmap/</guid><pubDate>Fri, 05 Nov 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;서비스를 개발하거나 알고리즘, 자료구조를 공부하면서 Map, Set 등의 자료구조를 자주 이용했었다. Map 이나 Set 자료구조를 사용하면 자료의 양이 엄청 많아도 검색하는데 O(1) 만큼의 시간복잡도를 가지게 된다. 이렇게 빠른 검색을 위해 Map, Set을 구현하려고 보면 HashMap, HashSet 이라는 구현체를 통해 생성했을 것이다. 처음에는 그냥 당연히 Map과 Set 을 사용하려면 이렇게 써야 한다고 생각했었다. 하지만 계속해서 쓰다 보니 앞에 Hash가 의미하는 것이 뭔지가 궁금해졌다. 그렇다면 이번 글에서는 Hash의 개념과 Map에 대하여 살펴보자.&lt;/p&gt;
&lt;h2&gt;해싱이란?&lt;/h2&gt;
&lt;p&gt;해싱은 산술적인 연산을 이용하여 키가 있는 위치를 계산하여 찾아가는 검색 방식이다. 키값을 원소 위치로 변환하는 함수를 해시 함수(Hash Function)라 한다. 해시 함수에 의해 계산된 주소에 저장할 값을 저장한 표를 해시 테이블(Hash Table)이라 한다. 해시 테이블은 빠른 검색 속도를 제공하는데 이유는 내부적으로 버킷(배열)을 사용하여 데이터를 저장하기 때문이다. 해시 테이블은 각각의 key 값에 해시 함수를 적용해 배열의 고유한 인덱스를 생성한 후 인덱스를 이용해 값을 저장한다. 실제 값이 저장되는 장소를 버킷이라 한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63634505/140446636-2d26f699-3b5d-495e-8439-f2d8d29adbc0.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;어떤 수업을 듣는 10명의 학생에게 자리를 배정한다고 생각해보자. 이때 자리가 딱 10자리가 있고 10명의 학생은 각각 다른 이름을 가지고 있다. 그리고 좌석 번호를 생성하는 번호 생성기에 이름을 입력하면 좌석 번호가 나오게 된다. 이때 번호 생성기가 해시 함수의 역할을 한다. 그리고 좌석 번호와 학생 정보를 기록한 테이블은 해시 테이블이 된다.&lt;/p&gt;
&lt;h2&gt;해시 충돌&lt;/h2&gt;
&lt;p&gt;앞서 번호 생성기를 통하여 10명의 학생을 각각 자리에 앉혔다. 10명의 학생을 각각의 자리에 안내할 수 있었던 것은 사실 번호 생성기의 성능이 좋아 겹치지 않았다. 하지만 수업의 퀄리티가 좋아져 학생의 수가 갑자기 엄청나게 늘어났다. 기존의 번호 생성기는 0~20까지의 번호밖에 만들지 못했는데 학생의 수가 어느덧 30명이 되었다. 이제 필연적으로 겹치는 번호가 생성될 수밖에 없다. 그럼 이미 좌석 배정이 받은 학생이 있는 자리에 새로운 학생을 배정하게 되는데 이러한 경우가 해시 충돌 상황이다.&lt;/p&gt;
&lt;p&gt;이러한 해시 충돌을 해결하는 방법에는 크게 두 가지 방법이 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open Addressing&lt;/li&gt;
&lt;li&gt;Separate Chaining&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Open Addressing은 이미 자리에 누군가가 있으면 다른 곳으로 배정하는 방식이다. 다른 곳에 배정하는 방식에 따라 Linear Probing과 Quadratic Probing 등의 방법이 있다.&lt;/p&gt;
&lt;p&gt;Separate Chaining은 해시 테이블의 구조를 변경하여 하나 이상의 키값을 저장할 수 있도록 하는 방법이다. 이때 주로 사용하는 자료구조로 연결 리스트를 사용한다.&lt;/p&gt;
&lt;h2&gt;HashMap 에서의 Separate Chaining&lt;/h2&gt;
&lt;p&gt;HashMap에서 사용하는 충돌기법 방식은 Separate Chaining이다. Java 8 이전에의 HashMap에서는 단순히 연결 리스트를 사용하였다. 하지만 Java 8 이후부터는 데이터의 개수가 많아지면 연결 리스트가 아닌 트리 자료구조를 사용한다. 정확히 말하자면 레드 블랙 트리 자료구조이다. 그렇다면 데이터의 개수가 많아진다는 기준점은 무엇일까?&lt;/p&gt;
&lt;p&gt;하나의 해시 버킷에 8개의 키-값 쌍이 모이면 연결 리스트를 트리로 변경하게 된다. 또한 버킷의 데이터를 삭제하여 개수가 6개에 이르면 다시 연결 리스트로 변경한다. 그럼 왜 6과 7 또는 7과 8이 아닌 6과 8이 기준이겠냐는 의문이 생길 수 있다. 이유는 차이가 1일 경우 삽입/삭제가 반복되어 일어나면 불필요하게 트리와 연결 리스트로 변경하는 일이 발생하기 때문이다.&lt;/p&gt;
&lt;h2&gt;해시 버킷의 확장&lt;/h2&gt;
&lt;p&gt;앞서 버킷에 관해 설명했듯이 버킷은 결국 배열로 구성되어 있다. 우리가 HashMap을 구현할 때 따로 버킷의 개수를 지정 해주지 않았다면 기본값인 16으로 버킷의 개수가 지정된다. 해시 버킷의 개수가 일정 개수 이상이 되면 버킷의 개수를 2배로 늘리게 된다. 버킷을 늘리면 해시 충돌의 가능성도 낮아져 해시 충돌로 인한 문제를 해결할 수도 있다. 하지만 두 배로 증가할 때마다 새로운 Separate Chaining을 구성해야 하는 문제가 있다. 가장 좋은 것은 버킷의 개수를 예측할 수 있다면 지정하여 HashMap을 생성하는 것이다. 따라서 데이터의 개수가 예측 가능하다면 지정하여 생성자의 인자로 넣어주면 불필요한 Separate Chaining을 줄일 수 있다.&lt;/p&gt;
&lt;h3&gt;Load Factor&lt;/h3&gt;
&lt;p&gt;해시 버킷의 개수가 일정 개수 이상이 되면 버킷의 수를 두 배로 늘린다 했는데 그럼 일정 개수는 어떻게 정할까?  HashMap 클래스에 들어가 load factor 부분을 살펴보면 0.75f이라고 설정된 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63634505/140446706-681ff318-2a7c-418a-9028-e46a3e8810dd.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;이 의미는 데이터의 개수가 현재 버킷 개수의 0.75 즉 3/4 이 되었을 때 두 배로 확장한다는 뜻이다.&lt;/p&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;이번 글을 통하여 Hash와 HashMap에 대하여 알아보았다. 프로그래밍을 하는 사람이라면 배열이나 리스트만큼 자주 쓰는 자료구조인 Map일 것이다. 하지만 그 어떤 방식으로 Map이 구현되어 있는지는 관심 있게 보지 않으면 모르고 지나갔을 부분일 것 같다. HashMap은 해시 함수를 통하여 해시 테이블을 만들고 이를 기반으로 검색 시 빠른 속도를 낸다. 또한 해시 충돌을 방지하는 방법으로 open addressing과 separate chaining 방법이 있었다. 자바에서는 separate chaining을 이용하여 해시 충돌을 해결하였다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/831311&quot;&gt;Java HashMap은 어떻게 동작하는가?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[당신의 컴포넌트는 안녕하신가요]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-11-01-component-dependency/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-11-01-component-dependency/</guid><pubDate>Mon, 01 Nov 2021 12:00:00 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;❗ 원지혁님의 &lt;a href=&quot;https://www.youtube.com/watch?v=HYgKBvLr49c&quot;&gt;컴포넌트, 다시 생각하기&lt;/a&gt;에서 얻은 인사이트를 정리한 글임을 밝힙니다.&lt;/li&gt;
&lt;li&gt;❗ 필자의 생각도 많이 들어가 있으니 원본 영상도 함께 보는 것을 추천합니다!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;🎯 개요&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;당신의 컴포넌트는 안녕하신가요?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;조금 관심을 끌기 위한 멘트로 글을 시작했는데, 컴포넌트가 잘 지내는지 헬스 체크를 해보자는 의미였다. 잘 안 지낼 수도 있으므로… 🤣 아무튼, 이를 확인하기 위해 컴포넌트가 무엇인지부터 알아보도록 하자.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://reactjs.org/docs/components-and-props.html&quot;&gt;리액트 공식 홈페이지&lt;/a&gt;에서는 컴포넌트를 다음과 같이 표현하고 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.&lt;/p&gt;
&lt;p&gt;(컴포넌트는 UI를 독립적이고, 재사용 가능한 부분으로 분리해주며 각각을 고립시켜 생각할 수 있게 해줍니다.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;**‘독립적’, ‘재사용 가능하도록 분리’, ‘고립시켜 생각’**이라는 말들이 나오는데 우리가 만드는 컴포넌트가 완벽히 이러한 특징을 가지느냐고 자신에게 반문해보면 아마 아닐 것이다.&lt;/p&gt;
&lt;p&gt;사실 &lt;strong&gt;위의 설명은 컴포넌트의 최대 목표를 의미&lt;/strong&gt;하는 말이 아닐까 생각한다. 이러한 목표를 완벽하게 지키면 가장 좋겠지만, 개발하면서 위의 특징들을 못 지키는 사태가 빈번하다. 그래서 나는 이러한 특징들을 어느 정도 지키고 있는지 확인하는 것을 &lt;strong&gt;컴포넌트의 헬스 체크&lt;/strong&gt;라고 명명하고, 주기적으로 이러한 헬스 체크가 이루어져야 한다고 생각한다.&lt;/p&gt;
&lt;p&gt;그렇다면 헬스 체크에서 가장 중요한 개념은 무엇일까? 바로 &lt;code class=&quot;language-text&quot;&gt;의존성&lt;/code&gt;이다. 의존성을 잘 관리한 컴포넌트를 만들게 되면 유지보수 효율이 굉장히 올라가는데, 오늘은 &lt;strong&gt;컴포넌트 의존성&lt;/strong&gt;에 대해서 얘기해볼 생각이다.&lt;/p&gt;
&lt;h2&gt;🙌 의존성이란?&lt;/h2&gt;
&lt;p&gt;의존성을 네이버 국어사전에 검색해보면 다음과 같이 설명하고 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;다른 것에 의지하여 생활하거나 존재하는 성질.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;개발에서는 어떨까? 개발에서의 의존성이란 &lt;code class=&quot;language-text&quot;&gt;어떤 것과 다른 것이 변경에 있어서 서로 영향을 주고받는 관계&lt;/code&gt;를 의미한다.&lt;/p&gt;
&lt;p&gt;예시로 &lt;strong&gt;자전거 조립 회사&lt;/strong&gt;와 &lt;strong&gt;자전거 안장 제조 회사&lt;/strong&gt;를 생각해보면 좋을 것 같다.
조립 회사가 안장 제조 회사에서 만드는 A 안장을 사용하고 있다고 가정해보자.
당연히 조립 회사는 안장 제조 회사에 의존한다. 안장 제조 회사에서 A 안장의 소재를 바꾸기라도 한다면 당연하게도 조립된 자전거의 안장도 소재가 변경될 것이다.&lt;/p&gt;
&lt;p&gt;그렇다면 안장 제조 회사는 조립 회사에 의존되지 않을까? 아니다, 의존된다.
예를 들어 조립 회사가 안장 제조 회사에 A 안장의 소재를 바꾸어 A` 안장으로 바꾸어 달라는 요청을 한다.
당연히 될 줄 알았건만, 안장 제조 회사에서는 거절을 한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A 안장이 다른 조립 회사에서도 쓰고 있어서 그건 좀 곤란할 것 같아요 😥&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;그렇다. 우린 망했다.&lt;/strong&gt; 안장 제조 회사도 조립 회사에 의존되어 함부로 수정할 수 없는 처지가 된 것이다.
이처럼 의존성이란 한 방향으로 이루어지는 것이 아닌, &lt;code class=&quot;language-text&quot;&gt;양방향으로 영향을 주고받는 관계&lt;/code&gt;를 의미한다.&lt;/p&gt;
&lt;h2&gt;🤔 의존성 관리, 왜 중요한가요?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;영향, 변경, 수정&lt;/strong&gt;과 같은 얘기를 했는데, 약오르게 무언가 감이 잡힐 듯 말듯 한다. 🤔
의존성 관리가 중요한 이유는, 의존성 관리가 잘 되었을 때 예상치 못한 변경에 유연하게 대처할 수 있기 때문에 유지보수 효율이 굉장히 올라가기 때문이다.&lt;/p&gt;
&lt;p&gt;혹시 유저 정보에 블로그 주소라는 데이터가 하나 추가되었을 뿐인데 &lt;strong&gt;매우 많은 컴포넌트를 수정해야 하는 경우, 또는 해당 변경 사항이 생겼을 때 어떤 컴포넌트를 수정해야 하는지 분명하게 알 수 없는 경우&lt;/strong&gt;가 있었는지 한 번 생각해보자. 그것이 바로 &lt;strong&gt;의존성 관리를 해야할 때&lt;/strong&gt;라는 신호이다.&lt;/p&gt;
&lt;p&gt;그렇다면 의존성 관리는 어떻게 하는 걸까? 정답은 없지만, 의존성 관리의 가장 큰 줄기는 &lt;strong&gt;불필요한 의존성을 제거하거나 약화시키고, 불가피하게 강하게 의존되는 요소들은 가깝게 두는 것&lt;/strong&gt;에 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;아니 뜬구름 잡지 말고 좀 더 자세히 알려주세요 ㅠ 😭&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;다행히도 선배 개발자분들이 많은 고민을 하셨고 우리에게 인사이트를 주고 있다. 같이 따라가 보자.&lt;/p&gt;
&lt;h2&gt;📜 컴포넌트의 많은 의존성&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/140637449-6c7319a5-dbb2-4e6f-8258-4a54ea9febf7.png&quot; alt=&quot;image&quot;&gt;
(출처 - &lt;a href=&quot;https://www.youtube.com/watch?v=HYgKBvLr49c&amp;#x26;t=1285&quot;&gt;컴포넌트, 다시 생각하기&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;컴포넌트가 의존하는 것은 대체로 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 스타일&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;css-in-js의 경우 import하여 가져온 styles.ts 파일이라고 할 수 있다.
&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/140637015-66ca6154-9c98-444e-9278-4511ee549311.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 로직&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;custom hook들을 의미한다.
&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/140637285-82db10cf-df0d-4bdc-a4d2-adf13e4e8335.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 전역 상태&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;redux, react-query, recoil 등이 여기에 해당한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. props(상위 컴포넌트 의존성)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;props도 변경되면 상위 컴포넌트도 같이 변경되기 때문에 서로 의존한다고 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. 👑 서버 데이터 스키마&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;가장 중요하다고 생각한다. 전역 상태, 로직, 스타일 등의 변경은 많은 경우 서버 데이터 스키마의 변경에서부터 시작된다. 게시물 내용에 좋아요 개수가 추가된다든가 하는 경우가 있다.&lt;/p&gt;
&lt;h2&gt;😵 의존성 관리를 위한 4개의 원칙&lt;/h2&gt;
&lt;p&gt;이제 앞에서 살펴본 컴포넌트의 복잡한 의존성을 잘 관리할 수 있는 4개의 원칙에 대해서 알아보자.&lt;/p&gt;
&lt;p&gt;여기 소개된 4개의 원칙이 항상 정답은 아니므로 더 개선하기 위해서는 어떻게 할 수 있을지 열린 마음으로 바라보면 좋을 것 같다.&lt;/p&gt;
&lt;p&gt;스스로 드는 회초리가 가장 값진 법.
필자가 개발에 참여한 &lt;a href=&quot;https://nolto.app/&quot;&gt;놀토 - 놀러오세요 토이프로젝트&lt;/a&gt;라는 플랫폼의 나쁜 예시를 알아보고 개선하면서 원칙을 소개해보도록 하겠다.&lt;/p&gt;
&lt;h3&gt;1. 비슷한 관심사라면 함께 두기&lt;/h3&gt;
&lt;p&gt;비슷한 관심사, 즉 깊은 의존성을 지닌 것들끼리는 한 폴더에 같이 두는 것이 좋다. 그래야 어떤 컴포넌트를 수정할 때, 컴포넌트가 의존하는 &lt;strong&gt;파일을 찾느라 고생하지 않아도 된다.&lt;/strong&gt; 집중력이 분산되지 않고, 효율적으로 컴포넌트를 수정할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;👎 나쁜 예시&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/140651190-cd51ce43-5eff-4a52-abb9-b2a46c796fa3.png&quot; alt=&quot;image&quot;&gt;
OAuth 로그인을 처리하는 페이지 컴포넌트다. useOAuthLogin이라는 커스텀 훅은 OAuth에서만 쓰이고 있지만, 해당 훅 파일은 hooks 폴더에 따로 떨어져 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;👍 개선해보기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/140651374-cdec3c70-812b-487e-bacf-4b10d2c7a58a.png&quot; alt=&quot;image&quot;&gt;
의존성이 깊은 파일끼리 모아주기 위해 &lt;strong&gt;얼른 OAuth 폴더로 넣어주자.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;2. 데이터를 Id 기반으로 정리하기&lt;/h3&gt;
&lt;p&gt;클라이언트에서 데이터를 Id를 통해 전역적으로 관리하면 큰 이점이 있다. 바로 &lt;strong&gt;상위 컴포넌트에서 해당 데이터의 스키마를 알 필요가 없고, Id만 내려주면 하위 컴포넌트가 원하는 데이터에 바로 접근할 수 있다&lt;/strong&gt;는 점이다. 이렇게 되면 상위 컴포넌트는 데이터 스키마에 의존하지 않기 때문에, 데이터 스키마의 변경이 일어나도 상위 컴포넌트를 수정할 필요가 없다&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;👎 나쁜 예시&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/140653949-0a924ad1-bbab-429e-8fef-bebd42e0da33.png&quot; alt=&quot;image&quot;&gt;
예시로 인기 있는 토이 프로젝트를 나타내는 &lt;strong&gt;HotFeedsContent&lt;/strong&gt;와 &lt;strong&gt;LargeFeedCard&lt;/strong&gt; 컴포넌트를 확인해보자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/142862514-ddada9aa-be09-41f2-ad62-03f5e2fe0e0f.png&quot; alt=&quot;image&quot;&gt;
우선 컴포넌트를 모델 기준으로 나눈 것이 아니라 현재 UI 형태를 기준으로 나눈 것부터 문제다. &lt;strong&gt;Large라는 모호한 네이밍&lt;/strong&gt;으로 컴포넌트를 만들었기 때문에 해당 컴포넌트에서 데이터를 가져오기에 애매해졌다. 그래서 &lt;strong&gt;상위 컴포넌트에서 feed를 prop으로 받아오고 있다.&lt;/strong&gt; 이렇게 되면 &lt;strong&gt;상위 컴포넌트인 HotFeedsContent가 feed 데이터 스키마를 알고 있어야 하는 것&lt;/strong&gt;이므로 하위 컴포넌트와의 의존성이 강해진다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;👍 개선해보기&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;LargeFeedCard -&gt; HotFeedCard로 컴포넌트 네임을 수정하기
컴포넌트를 모델 관점에서 바라보기 위해서 네임을 수정&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;HotFeedCard의 prop으로 feedId를 받아오도록 수정
기존에는 feed를 그대로 상위 컴포넌트에서 주입하고 있었기 때문에 feed 데이터 스키마가 변경되면 상위 컴포넌트도 같이 변경해야 했다.
&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/140650710-5967e0fb-f79e-4dcb-a61f-3a78fa161c2e.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;feedId를 prop으로 받도록 수정하여 의존성을 느슨하게 만들어주었다. &lt;code class=&quot;language-text&quot;&gt;useData&lt;/code&gt;는 전역 상태 저장소에 접근할 수 있는 훅으로 가정했다.
&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/140650918-411c16fb-cc00-48e4-a446-e698e7aec982.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3. 의존성을 그대로 드러내기&lt;/h3&gt;
&lt;p&gt;의존성을 prop 구조에 그대로 드러내면 컴포넌트 의존 구조가 명확히 보이고 어떻게 의존성을 느슨하게 만들어야 하는지 눈에 보인다.
그래서 의존 관계를 prop 구조나 이름에 그대로 드러내는 것이 좋다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;👎 나쁜 예시&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/140653982-31186d45-88da-463d-ba0c-b8b9c99d7583.png&quot; alt=&quot;image&quot;&gt;
토이 프로젝트를 나타내는 &lt;strong&gt;RegularFeedCard 컴포넌트&lt;/strong&gt;를 확인해보자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/140639985-db29a690-4779-492f-a2e2-191ca580a424.png&quot; alt=&quot;image&quot;&gt;
author 같은 경우는 의존 구조가 명확하지만 thumbnailUrl은 다른 목적의 모델임에도 의존 관계가 표현되지 않고 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;👍 개선해보기&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;feed와 thumbnail의 의존 구조를 명확하게 표현해주기
thumbnail도 따로 의존 관계를 prop에 표현해 주었다.
&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/140648990-7b14491f-c077-46d6-8d11-9546a8efd78f.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;컴포넌트가 2개 이상의 모델에 의존하고 있는 것이 보인다면 Id 기반으로 정리하여 의존성을 약화시키자.
thumbnail의 의존 관계를 prop 구조에 표현해주니 RegularFeedCard 컴포넌트가 feed 외에도 thumbnail, author 등, 2개 이상의 모델에 의존되고 있다는 것이 보인다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/140674536-b3b45a36-cf2d-4948-8686-6ebb8aff6a35.png&quot; alt=&quot;image&quot;&gt;
하나의 모델에만 의존되게 하기 위해 2번 원칙인 &lt;strong&gt;Id 기반으로 정리&lt;/strong&gt;하고, 각 컴포넌트에서 필요한 데이터 스키마를 컴포넌트 내부에서 정의해준다.
이렇게 만들면 RegularFeedCard와 Thumbnail은 서로 데이터 스키마에 대해 알 필요가 없으므로 의존 관계를 느슨하게 만들 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4. 컴포넌트의 재사용 기준은 모델이다.&lt;/h3&gt;
&lt;p&gt;개발할 때 편리한 것보다 &lt;strong&gt;변경할 때 편리하기 위해서의 관점으로 컴포넌트를 나누는 것이 좋다.&lt;/strong&gt; 보통 모델(데이터의 목적)에 따라 변경이 자주 이루어지므로 모델이 같다면 재사용하고, 다르다면 컴포넌트를 분리하도록 하자. 모델이 다르지만 현재 UI가 같다고 컴포넌트를 재사용하게 되면, 추후 모델에 따라 다른 UI 변경점이 생길 때 곤란해진다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;👎 나쁜 예시&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/140652452-c6e8ea65-179c-44bd-ba8f-c80e4a15535b.png&quot; alt=&quot;image&quot;&gt;
진행 중인 프로젝트와 완성된 프로젝트의 UI가 같다는 이유 때문에, &lt;strong&gt;RegularFeedCard 컴포넌트로 재사용되고 있다.&lt;/strong&gt; 하지만 진행중 프로젝트와 완성된 프로젝트의 모델은 다르다. &lt;strong&gt;현재는 UI가 같지만, 모델이 다르기 때문에 충분히 다른 방식으로 변화할 가능성이 있다.&lt;/strong&gt; 예를 들어 완성된 프로젝트의 모델은 배포 url을 가지기 때문에 나중에는 &lt;code class=&quot;language-text&quot;&gt;서비스로 이동하기&lt;/code&gt;라는 버튼이 완성된 프로젝트 카드에만 추가될 수도 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;👍 개선해보기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;RegularFeedCard를 &lt;strong&gt;ProgressFeedCard, CompleteFeedCard 2개의 컴포넌트로 분리&lt;/strong&gt;한다.
이렇게 분리하게 되면 모델에 따라 카드의 형태가 달라진다 해도 쉽게 변경할 수 있을 것이다.&lt;/p&gt;
&lt;h2&gt;🎉 마무리 - 사실 이보다 더 중요한 것&lt;/h2&gt;
&lt;p&gt;사실 4개의 원칙보다도 &lt;strong&gt;본인의 경험에서 불편함을 찾는 자세가 굉장히 중요&lt;/strong&gt;하다고 생각한다. 4개의 원칙을 완벽하게 적용해도 분명히 어딘가 유지 보수하기 불편한 점이 있을 것이다. 거기서 스스로 개선까지 할 수 있으면 금상첨화지만, 불편함을 찾는 것만으로도 성장할 준비가 되어있다는 의미이기 때문에 괜찮은 경험이라고 생각한다. 그 상태에서 누군가 조금 이끌어주는 것만으로도 크게 성장할 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;나도 컴포넌트 기반으로 개발을 하면서 간혹 파일을 찾기 위해서 폴더를 굉장히 많이 뛰어넘는다든가, 하나의 수정 사항이 생길 때 여러 컴포넌트를 수정해야 하는 등 불편함을 많이 느끼고 있었는데 마침 좋은 영상을 접하게되어 큰 인사이트를 얻을 수 있었다.&lt;/p&gt;
&lt;p&gt;앞으로는 컴포넌트 개발을 하면서 컴포넌트 의존성을 도식화하는 습관을 지녀야겠다. 도식화하여 어떤 부분에서 의존성이 강한지 한눈에 파악하고, 어떻게 더 나은 방법으로 개선할 수 있을지 고민하는 사람이 되고 싶다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Ubuntu PPA 저장소 관리]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-11-01-ubuntu-manage-ppa/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-11-01-ubuntu-manage-ppa/</guid><pubDate>Mon, 01 Nov 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;서버를 구성할 때, 필요한 프로그램들을 공식 저장소를 통하여 보통 설치하게 됩니다.
하지만 저장소에 모든 프로그램 설치 데이터가 존재하진 않습니다.
다른 버전을 설치해야 할 때도 있을 것이고, 아예 등록되어 있지 않은 프로그램을 설치해야 할 때도 있습니다.
이번 주제는 서버용 배포판으로 많이 사용되는 우분투에서 어떻게 저장소들을 관리할 수 있는지에 대한 글입니다.&lt;/p&gt;
&lt;!-- end --&gt;
&lt;h2&gt;공식 저장소는 만능이 아니다.&lt;/h2&gt;
&lt;p&gt;Java, Nginx, redis는 일반적으로 WAS를 구성할 때 많이 사용하는 프로그램입니다.
동시에 우분투 기본 저장소를 통하여 설치를 할 수 있는 공통점이 있습니다.
&lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-09-13-linux-distribution/&quot;&gt;지난번 글&lt;/a&gt;에서는 공식 저장소를 통하여 Java를 설치하는 방법에 대해서 간단하게 정리했습니다.
서버를 운영하기 위해서는 Java 뿐만이 아니라 다양한 프로그램들이 필요합니다.
그렇기 때문에 의문이 드는 점이 있습니다. &lt;br&gt;&lt;br&gt;
먼저 공식 저장소에는 모든 프로그램이 등록되어있지 않다는 점입니다.
기능을 확인하고 안정성을 검증하는 과정을 모든 프로그램에 적용할 수가 없습니다.
우분투, 정확히는 데비안의 공식 저장소에서는 일부 프로그램들만 등록하여 사용하고 있습니다.
그래서 비교적 소규모의 커뮤니티나 개인이 개발한 프로그램들은 등록되어 있지 않습니다. &lt;br&gt;&lt;br&gt;
두 번째로는 공식 저장소에 등록된 프로그램들이 항상 최신 버전을 지원할까? 에 대한 의문입니다.
공식 저장소에서는 안정성을 중요시하기 때문에 충분한 검증을 거치고 난 다음에서야 프로그램이 등록됩니다.
해당 배포판의 채널을 stable에서 도전적인 프로그램 버전을 제공하는 채널로 바꾸어서 필요한 프로그램들을 설치할 수 있겠으나 추천하지 않습니다.
만약 개발자가 조금 더 최신의 기술을 원한다면 공식 저장소에 있는 버전이 아닌 다른 곳을 통하여 최신 버전을 설치를 해야 합니다.
&lt;br&gt;&lt;br&gt;
우분투의 기반이 되는 배포판인 데비안의 공식 저장소의 패키지는 &lt;a href=&quot;https://www.debian.org/distrib/packages#search_packages&quot;&gt;Debian package&lt;/a&gt;에서 확인할 수가 있습니다.
하지만 존재하지 않는 패키지나 stable 버전에서 지원하지 않는다면 위의 이유 때문에 공식 저장소를 통해서 설치하기가 매우 어려울 것입니다.
&lt;br&gt;&lt;br&gt;
이와 같은 문제점을 해결하려면 필요한 프로그램들이 저장되어 있는 주소를 직접 등록하는 방법을 사용해야만 합니다.
각각의 커뮤니티나 개인이 운영하면서 최신 혹은 새로운 프로그램을 관리하는 저장소를 PPA(Personal Package Archive)라고 합니다.
MariaDB 라는 RDBMS를 통해 PPA 저장소를 등록하고 설치하는 예시를 함께 보면서 알아가보도록 하겠습니다.&lt;/p&gt;
&lt;h2&gt;예시 : 최신 버전 MariaDB를 설치하고 싶을 때&lt;/h2&gt;
&lt;p&gt;MariaDB는 널리 쓰이는 RDBMS 중 하나로써 이미 데비안 저장소에 등록되어 있습니다.
저장소에서는 현재 10.5를 기본적으로 제공하고 있습니다.
하지만 2021년 8월 6일에 새로운 버전인 10.6 버전이 출시되었고, 우리는 10.6 버전에서만 제공하는 새로운 기능이 필요하다고 가정을 해보겠습니다.
기존의 저장소에서 다운을 시도하게 된다면 등록되어 있는 10.5 버전이 계속 설치가 될 것입니다.
소스 코드 파일을 내려받아서 이를 빌드해서 사용하는 방법도 있지만, 해당 방법은 패치가 일어날 때에도 똑같이 소스 코드 파일을 다운받아서 이를 다시 빌드해야하는 과정을 거쳐야 할 것입니다.
우리는 패키지 관리자에게 10.6 버전을 가지고 있는 저장소를 등록하여 저장소를 사용하도록 하겠습니다.&lt;/p&gt;
&lt;h2&gt;apt-key&lt;/h2&gt;
&lt;p&gt;MariaDB 재단에서 제공하는 문서는 해당 명령어를 입력을 하며 설치를 시작합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; apt-key adv --fetch-keys &lt;span class=&quot;token string&quot;&gt;&apos;https://mariadb.org/mariadb_release_signing_key.asc&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;//결과
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; Executing: /tmp/apt-key-gpghome.zHSJrMrTbv/gpg.1.sh --fetch-keys https://mariadb.org/mariadb_release_signing_key.asc
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; gpg: requesting key from &lt;span class=&quot;token string&quot;&gt;&apos;https://mariadb.org/mariadb_release_signing_key.asc&apos;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; gpg: key &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;%GPG KEY VALUE%&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; public key &lt;span class=&quot;token string&quot;&gt;&quot;MariaDB Signing Key &amp;lt;signing-key@mariadb.org&gt;&quot;&lt;/span&gt; imported
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; gpg: Total number processed: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; gpg:           	imported: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;저장소를 추가하기 전에 해당 명령어를 입력하게 됩니다.
apt-key는 apt가 해당 패키지를 인증할 때 사용하는 키를 관리하는 명령어입니다.
해당 명령어를 사용하게 된다면, MariaDB를 설치하기 위한 키가 추가가 되는 것입니다.
apt-key list 라는 명령어를 사용하면, 지금 우분투 혹은 데비안에서 사용되고 있는 key들을 모두 확인할 수가 있습니다.
여기서 GPG란 GNU Privacy Guard로 해당 키를 암호화해주는 프로그램입니다.&lt;/p&gt;
&lt;h2&gt;add-apt-respository&lt;/h2&gt;
&lt;p&gt;저장소를 추가하기 위한 키도 추가했습니다.
그 다음에는 저장소를 추가해야할 차례입니다.
MariaDB 10.6를 관리하는 저장소를 추가하기 위해서는 다음과 같은 명령어를 입력해야합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; add-apt-repository &lt;span class=&quot;token string&quot;&gt;&apos;deb [arch=amd64,arm64,ppc64el] https://mirror.yongbok.net/mariadb/repo/10.6/ubuntu focal main&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;// 추가된 출력
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; Get:2 https://mirror.yongbok.net/mariadb/repo/10.6/ubuntu focal InRelease &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7758&lt;/span&gt; B&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; Get:4 https://mirror.yongbok.net/mariadb/repo/10.6/ubuntu focal/main arm64 Packages &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;16.6&lt;/span&gt; kB&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;명령어의 각각의 구성들을 살펴보도록 하겠습니다. &lt;br&gt;&lt;br&gt;
먼저, 해당 파일은 deb라는 파일 형식으로 내려받겠다는 것을 알려줍니다.
각각의 리눅스 배포판들은 프로그램을 설치할 때, 패키지 관리자에게 호환되는 패키지가 존재합니다.
CentOS나 페도라 같은 레드헷 계열은 일반적으로 yum을 통해서 rpm이 설치가 되고, 저희가 사용하는 우분투 및 데비안 계열은 apt를 통해서 deb 형식의 패키지 파일을 읽어와 프로그램을 설치하게 됩니다. &lt;br&gt;&lt;br&gt;
그다음에는 해당 패키지를 내려받을 수 있는 Mirror Server를 등록해야 합니다.
한국에서는 yongbok.net 이라는 서버에서 패키지를 제공하고 있습니다. &lt;br&gt;&lt;br&gt;
마지막으로 focal main이라는 부분이 있습니다.
focal은 무엇일까요?
각각의 리눅스 배포판들도 버전이 존재합니다.
우분투에서는 2년마다 LTS(Long Term Support, 장기 지원 버전) 및 6개월마다 신 버전(상대적으로 실험적 요소들이 많음)을 내는데, 각각의 배포판 버전에 따라서도 조금씩 구성이 다를 것입니다.
버전마다 맞는 패키지를 등록하게 되는데, focal은 최근에 나온 LTS 버전인 20.04 버전의 이명입니다.
실제로 비교적 최신 버전인 21.04 버전을 조회하게 되면 다음과 같은 명령어로 다르게 입력을 하는 것을 권장하고 있습니다.
hirsute는 21.04 버전의 이명입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; add-apt-repository &lt;span class=&quot;token string&quot;&gt;&apos;deb [arch=amd64] https://mirror.yongbok.net/mariadb/repo/10.6/ubuntu hirsute main&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br&gt;
이렇게 사용할 PPA, 저장소를 등록하게 되면 해당 sudo apt -y update 시에 해당 저장소도 같이 업데이트 조회를 하는 것을 확인할 수가 있습니다.
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt; update&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; Hit:2 https://mirror.yongbok.net/mariadb/repo/10.6/ubuntu focal InRelease&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br&gt;
&lt;h2&gt;저장소 수정 및 삭제하기&lt;/h2&gt;
&lt;p&gt;한 번 등록하면 계속 업데이트를 받아오면서 사용할 수 있지만, 간혹가다 오타 때문에 저장소가 잘못 등록될 수가 있습니다.
혹은 저장소에서 더 이상의 업데이트를 제공하지 않을 때도 있습니다.
이를 해결하기 위해서는 /etc/apt/sources.list에 vim으로 접근하여 수정 및 삭제를 할 수가 있습니다.
해당 경로의 의미는 &lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-10-18-linux-file-directory-system/&quot;&gt;리눅스 파일시스템 경로 구조&lt;/a&gt;를 참조해주시길 바랍니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;vim&lt;/span&gt; /etc/apt/sources.list&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 해당 부분 삭제&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; deb &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;arch&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;amd64,ppc64el,arm64&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; https://mirror.yongbok.net/mariadb/repo/10.6/ubuntu focal main
&lt;span class=&quot;token comment&quot;&gt;# deb-src [arch=amd64,ppc64el,arm64] https://mirror.yongbok.net/mariadb/repo/10.6/ubuntu focal main&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 추가해준 MariaDB 저장소를 삭제해주었습니다.
이후에 sudo apt -y update를 실행하면, 제거된 저장소에 대해 더 이상 업데이트 조회를 하지 않습니다.
만약 PPA의 저장소명을 정확히 알고 있다면 해당 명령어를 통해서 삭제할 수가 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; add-apt-repository &lt;span class=&quot;token parameter variable&quot;&gt;--remove&lt;/span&gt; ppa:저장소명&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br&gt;
&lt;h2&gt;마치며&lt;/h2&gt;
&lt;p&gt;우리는 오픈 소스 세계에서 빠른 기술 성장 속도를 경험하고 있습니다.
또한, 리눅스 배포판들의 상당수도 오픈 소스로 운영되고 있습니다.
이 때문에 모든 오픈 소스의 변화들을 각각의 리눅스 배포판 저장소에서 검증하고 등록하기까지는 많은 시간이 필요합니다.
필요한 프로그램을 제공해주는 PPA를 직접 등록하고 관리하여 인스턴스에서 필요한 기능 및 성능을 구현하도록 합시다.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://linuxize.com/post/how-to-add-apt-repository-in-ubuntu/&quot;&gt;How To Add Apt Repository In Ubuntu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mariadb.org/download/?tab=repo-config&amp;#x26;distro=Mint+19&amp;#x26;ver=10.2&amp;#x26;r_mirror=yongbok&quot;&gt;MariaDB Foundation, Download MariaDB Server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[로드 밸런싱에 대해 알아보자!]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-11-07-load-balancing/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-11-07-load-balancing/</guid><pubDate>Wed, 27 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p style=&quot;font-family: sans-serif; text-align: center; color: #aaa; margin-bottom: 3em; font-size: 85%&quot;&gt;image origin: &lt;a href=&quot;https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.3/&quot;&gt;kubernetes-sigs.github.io&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;인터넷의 발달로 인해 데이터 통신이 활발해졌고, 이는 트래픽의 폭발적인 증가로 이어졌다. 방금 누른 페이스북 ‘좋아요’와 친구에게 카톡으로 전송한 동영상, 좋아하는 유튜버의 채널 시청까지 어느 것 하나 인터넷을 통하지 않은 것이 없을 정도이다.&lt;/p&gt;
&lt;p&gt;그 결과 아무리 성능이 뛰어난 서버라고 해도 단 한대의 서버로는 모든 트래픽을 감당해내기 어려워졌다. 이에 기업들은 서버를 추가로 구비하고 여러 대의 서버에 동일한 데이터를 저장해 수많은 트래픽을 효과적으로 분산하여 처리한다.&lt;/p&gt;
&lt;p&gt;그런데 단순히 다수의 서버를 구축해 운영한다고 해서 모든 클라이언트의 요청에 일관성 있게 응답할 수 있을까? 쏟아지는 트래픽을 여러 대의 서버로 분산시켜주는 기술이 없다면 한 곳의 서버에 모든 트래픽이 몰리는 상황이 발생할 것이다. 이때 필요한 기술이 바로 로드 밸런싱이다.&lt;/p&gt;
&lt;h2&gt;로드 밸런싱이란&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50273712/140634974-ff0038af-e9b6-48ef-a565-1d454f32377c.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;로드 밸런싱이란 말 그대로 서버가 처리해야 할 업무 혹은 요청(Load)을 여러 대의 서버로 나누어(Balancing) 처리하는 것을 의미한다. 한 대의 서버로 부하가 집중되지 않도록 트래픽을 관리해 각각의 서버가 최적의 퍼포먼스를 보일 수 있도록 하는 것이 목적이다.&lt;/p&gt;
&lt;p&gt;서비스의 규모가 커지고, 이용자 수가 늘어나게 되면 기존의 서버만으로는 원활한 서비스 동작이 불가능하게 되고, 이에 대처할 수 있는 방법은 크게 두 가지로 나뉜다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;기존의 서버 성능을 확장하는 Scale-up 방식&lt;/li&gt;
&lt;li&gt;기존의 서버와 동일하거나 낮은 성능의 서버를 증설하는 Scale-out 방식싱&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이때 Scale-out 방식을 통해 증가한 트래픽에 대처하기로 했다면, 여러 대의 서버로 트래픽을 균등하게 분산해주는 로드 밸런싱이 반드시 필요하다.&lt;/p&gt;
&lt;h2&gt;로드 밸런싱 기법&lt;/h2&gt;
&lt;p&gt;로드 밸런싱 기법은 여러 가지가 있다. 서버의 능력을 고려하여 분배해야 하기 때문에 서버의 상황에 맞춰 적절한 방법을 선택해야 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;라운드로빈 방식(Round Robin Method)&lt;/p&gt;
&lt;p&gt;서버에 들어온 요청을 순서대로 돌아가며 배정하는 방식이다. 클라이언트의 요청을 순서대로 분배하기 때문에 여러 대의 서버가 동일한 스펙을 갖고 있고, 서버와의 연결(세션)이 오래 지속되지 않는 경우에 활용하기 적합하다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;가중 라운드로빈 방식(Weighted Round Robin Method)&lt;/p&gt;
&lt;p&gt;각각의 서버마다 가중치를 매기고 가중치가 높은 서버에 클라이언트 요청을 우선적으로 배분한다. 주로 서버의 트래픽 처리 능력이 상이한 경우 사용되는 부하 분산 방식이다. 예를 들어 A라는 서버가 5라는 가중치를 갖고 B라는 서버가 2라는 가중치를 갖는다면, 로드 밸런서는 라운드로빈 방식으로 A 서버에 5개 B 서버에 2개의 요청을 전달한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;IP 해시 방식(IP Hash Method)&lt;/p&gt;
&lt;p&gt;클라이언트의 IP 주소를 특정 서버로 매핑하여 요청을 처리하는 방식이다. 사용자의 IP를 해싱해(Hashing, 임의의 길이를 지닌 데이터를 고정된 길이의 데이터로 매핑하는 것, 또는 그러한 함수) 로드를 분배하기 때문에 사용자가 항상 동일한 서버로 연결되는 것을 보장한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;최소 연결 방식(Least Connection Method)&lt;/p&gt;
&lt;p&gt;요청이 들어온 시점에 가장 적은 연결상태를 보이는 서버에 우선적으로 트래픽을 배분한다. 자주 세션이 길어지거나, 서버에 분배된 트래픽들이 일정하지 않은 경우에 적합한 방식이다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;최소 응답 시간 방식(Least Response Time Method)&lt;/p&gt;
&lt;p&gt;서버의 현재 연결 상태와 응답 시간(Response Time, 서버에 요청을 보내고 최초 응답을 받을 때까지 소요되는 시간)을 모두 고려하여 트래픽을 배분한다. 가장 적은 연결 상태와 가장 짧은 응답 시간을 보이는 서버에 우선적으로 로드를 배분하는 방식이다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;L4 로드 밸런싱과 L7 로드 밸런싱&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50273712/140638451-9d2b490d-d62e-4033-a122-808c6b05e9b2.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;로드 밸런싱에는 L4 로드 밸런싱과 L7 로드 밸런싱이 가장 많이 활용된다.&lt;/p&gt;
&lt;p&gt;먼저 L4와 L7이 무엇을 의미하는지부터 알아보자.&lt;/p&gt;
&lt;p&gt;L4, L7은 각각 Layer 4(전송 계층) 프로토콜과 Layer 7(응용 계층) 프로토콜의 헤더를 부하 분산에 이용하기 때문에 붙은 접두사이다. 모든 요청을 L4 혹은 L7 로드 밸런서가 받아 서버들에게 적절히 나누어 준다.&lt;/p&gt;
&lt;p&gt;L4 로드 밸런서는 네트워크 계층(IP, IPX)이나 전송 계층(TCP, UDP)의 정보(IP주소, 포트번호, MAC주소, 전송 프로토콜)를 바탕으로 로드를 분산한다.&lt;/p&gt;
&lt;p&gt;L7 로드 밸런서는 애플리케이션 계층(HTTP, FTP, SMTP)에서 로드를 분산하기 때문에 HTTP 헤더, 쿠키 등과 같은 사용자의 요청을 기준으로 특정 서버에 트래픽을 분산하는 것이 가능하다. 쉽게 말해 패킷의 내용을 확인하고 그 내용에 따라 로드를 특정 서버에 분배하는 것이 가능한 것이다. URL에 따라 부하를 분산시키거나, HTTP 헤더의 쿠키 값에 따라 부하를 분산하는 등 클라이언트의 요청을 보다 세분화해 서버에 전달할 수 있다.&lt;/p&gt;
&lt;p&gt;또한 L7 로드 밸런서의 경우 특정한 패턴을 지닌 바이러스를 감지해 네트워크를 보호할 수 있으며, DoS/DDoS와 같은 비정상적인 트래픽을 필터링할 수 있어 네트워크 보안 분야에서도 활용되고 있다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;로드 밸런서를 이용한 부하 분산을 통해 가용성 및 응답 시간을 최적화시킴으로써 안정적인 서비스를 구축해보자.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://server-talk.tistory.com/118&quot;&gt;https://server-talk.tistory.com/118&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://m.post.naver.com/viewer/postView.nhn?volumeNo=27046347&amp;#x26;memberNo=2521903&quot;&gt;https://m.post.naver.com/viewer/postView.nhn?volumeNo=27046347&amp;#x26;memberNo=2521903&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://aws-hyoh.tistory.com/entry/Server-Load-Balancing-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&quot;&gt;https://aws-hyoh.tistory.com/entry/Server-Load-Balancing-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SpringBoot 예외 처리에 관한 고찰]]></title><description><![CDATA[SpringBoot는 다양한 예외 처리 방법을 제공한다. 편리한 만큼 궁금증도 많이 들었던 예외처리 부분을 코드를 들춰보는 게 익숙해진 지금 파헤쳐보고자 한다.
ExceptionHandler…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-10-25-spring-exceptioin-handle/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-10-25-spring-exceptioin-handle/</guid><pubDate>Mon, 25 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SpringBoot는 다양한 예외 처리 방법을 제공한다. 편리한 만큼 궁금증도 많이 들었던 예외처리 부분을 코드를 들춰보는 게 익숙해진 지금 파헤쳐보고자 한다.
ExceptionHandler를 사용해 본 경험이 있는 독자에게 추천한다.&lt;/p&gt;
&lt;!-- end --&gt;
&lt;h2&gt;다양한 SpringBoot의 예외 처리 방법&lt;/h2&gt;
&lt;p&gt;SpringBoot에서 예외 처리를 하는 방법은 다양하다. 그중 자주 사용되는 방식의 3가지는 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Controller에 ExceptionHandler 정의&lt;/li&gt;
&lt;li&gt;ControllerAdvice에 ExceptionHandler 정의&lt;/li&gt;
&lt;li&gt;Exception에 ResponseStatus 어노테이션 추가&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이처럼 다양한 방식의 구현을 제공한다는 것은 장점이다. 하지만, 그만큼 궁금증도 든다. 어떤 방식이 가장 우선순위가 높은지, 각 구현 방식에는 어떤 차이가 있는지. 이번 글에서는
각각의 방법들이 어떻게 설정되고 동작하는지에 대해 알아보며 궁금증을 풀어나가고자 한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;DispatcherServlet 들여다보기&lt;/h2&gt;
&lt;p&gt;SpringBoot에서는 예외처리를 어떤 방식으로 진행할까? DispatcherServlet 클래스의 doDispatch 메소드에서 해당 부분을 확인할 수 있다. 밑에 코드로
확인해 보면, 너무나도 익숙한 try-catch 문으로 구현된 것으로 확인할 수 있다. 요청이 들어왔을 때, 해당하는 컨트롤러의 로직이 실행되고, 예외가 발생했을 때는
processDispatchResult 메소드에서 처리된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DispatcherServlet&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doDispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;ModelAndView&lt;/span&gt; mv &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; dispatchException &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;// Controller 로직 실행 (핸들러를 찾고, 해당 로직을 실행)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; ex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        dispatchException &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// Dispatch 결과 처리 (예외 처리도 여기서 진행)&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;processDispatchResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;dispatchException&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;예외 처리 방법 확인하기&lt;/h2&gt;
&lt;p&gt;위에서는 DispatcherServlet에서 try-catch 문으로 예외를 잡아주는 것을 확인했다. 그렇다면 처음 언급한 예외 처리 방식 3가지는 실제로 어떻게 동작할까? 이
&lt;a href=&quot;https://github.com/wannte/exceptionHandler-digging&quot;&gt;실습 저장소&lt;/a&gt; 에서 디버깅 포인트를 찍어가면 직접 확인해 보는 것을 추천한다.&lt;/p&gt;
&lt;p&gt;HandlerExceptionResolver.resolveExceptionHandler() 에서 어떤 ExceptionResolver를 택하는지 결정한다. 다음의
3가지의 ExceptionResolver가 존재한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ExceptionHandlerExceptionResolver&lt;/li&gt;
&lt;li&gt;ResponseStausExceptionResolver&lt;/li&gt;
&lt;li&gt;DefaultHandlerExceptionResolver&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이름에서 유추할 수 있듯이, ExceptionHandler로 정의한 경우에는 ExceptionHandlerExceptionResolver, ResponseStatus로 정의한 경우
ResponseStatusExceptionResolver로 처리한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HandlerExceptionResolverComposite&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ModelAndView&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resolveException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@Nullable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; ex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;resolvers &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HandlerExceptionResolver&lt;/span&gt; handlerExceptionResolver &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;resolvers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ModelAndView&lt;/span&gt; mav &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; handlerExceptionResolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolveException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mav &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; mav&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;1. Controller에 &lt;code class=&quot;language-text&quot;&gt;@ExceptionHandler&lt;/code&gt; 정의&lt;/h3&gt;
&lt;p&gt;ExceptionHandlerExceptionResolver내부에 exceptionHandlerCache에서 관리된다. 특이한 부분은
getExceptionHandlerMethod()를 호출했을 때 캐시를 조회해보고 없을 때만 캐시에 등록한다. 즉, Controller에 정의된 ExceptionHandler는 그
Exception이 발생할 때야 객체로 생성이 된다. 2번째부터는 exceptionHandlerCache에 저장된 HandlerMethodResolver를 바로 사용한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ExceptionHandlerExceptionResolver getExceptionHandlerMethod() 일부&lt;/span&gt;
handlerType&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;handlerMethod&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBeanType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;ExceptionHandlerMethodResolver&lt;/span&gt; resolver&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exceptionHandlerCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handlerType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolver&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  resolver&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExceptionHandlerMethodResolver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handlerType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exceptionHandlerCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handlerType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;resolver&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2. ControllerAdvice에 &lt;code class=&quot;language-text&quot;&gt;@ExceptionHandler&lt;/code&gt; 정의&lt;/h3&gt;
&lt;p&gt;ExceptionHandlerExceptionResolver 내부에 exceptionHandlerCache에서
관리된다. &lt;code class=&quot;language-text&quot;&gt;initExceptionHandlerAdviceCache&lt;/code&gt; 메소드를 통해 &lt;code class=&quot;language-text&quot;&gt;ExceptionHandlerExceptionResolver&lt;/code&gt; 가 초기에 생성한 시점에
캐시에 ControllerAdvice에 정의된 ExceptionHandler에 해당하는 HandlerMethodResolver가
등록된다. &lt;code class=&quot;language-text&quot;&gt;getExceptionHandlerMethod&lt;/code&gt; 에서 확인할 수 있듯이, Controller에서 정의한 ExceptionHandler를 먼저 조회하기 때문에 1번에
비해 우선순위에서 밀린다.&lt;/p&gt;
&lt;h3&gt;3. Exception에 ResponseStatus 어노테이션 추가&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ResponseStatusExceptionResolver&lt;/code&gt; 에서 예외 처리한다. HandlerExceptionResolverComposite.resolveException()
에서 resolver를 순회할 때, ExceptionHandlerExceptionResolver를 먼저 순회하기 때문에 1, 2번의 예외 처리 보다 우선순위에서 밀린다.&lt;/p&gt;
&lt;h3&gt;번외. ExceptionHandler에 ResponseStatus 어노테이션 추가&lt;/h3&gt;
&lt;p&gt;ResponseEntity를 활용하여 응답의 상태 코드를 설정할 수 있고, ExceptionHandler에 ResponseStatus를 추가하는 방법도 가능하다. 그렇다면 다음의
경우에는 어떤 상태 코드를 보낼까? 물론 이런 구현 자체를 피해야 하는 것이 당연하다고 생각한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@ExceptionHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ResponseStatusInAdviceException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ResponseStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;BAD_REQUEST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ErrorResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleResponseStatusInAdviceException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;notFound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;결과는 notFound(404)의 응답 코드를 가진다. ResponseStatus어노테이션은 HandlerMethod의 상태 코드를 정하는데,
ServletInvocableHandlerMethod.invokeAndHandle() 에서 ResponseEntity의 상태 코드로 덮여 씌워진다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;SpringBoot의 다양한 예외 처리 방법의 구현에 대해 알아보았다. 학습 테스트를 통해 쉽게 예외 처리의 우선순위를 확인할 수도 있지만,
직접 그 내부의 구현을 확인했을 때 보다 확신을 가지고 기능을 사용할 수 있을 것이라 생각한다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[브라우저 렌더링 과정 이해하기.]]></title><description><![CDATA[최근에 백엔드 팀원들과 CS공부를 하면서, 주소창에 을 입력했을 때 일어나는 일에 대해 공부하였다. 이때 백엔드 팀원이 받아온 HTML…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-10-24-browser-rendering/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-10-24-browser-rendering/</guid><pubDate>Sun, 24 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;최근에 백엔드 팀원들과 CS공부를 하면서, 주소창에 &lt;code class=&quot;language-text&quot;&gt;google.com&lt;/code&gt;을 입력했을 때 일어나는 일에 대해 공부하였다.&lt;/p&gt;
&lt;p&gt;이때 백엔드 팀원이 받아온 HTML 파일은 어떻게 브라우저에 그려지는지 물어보았는데, 명확하게 답을 하지못해서 내가 아직 브라우저 렌더링 과정을 정확하게 이해하지 못하고 있다는 걸 알게 되었다.&lt;/p&gt;
&lt;p&gt;그래서 이번 시간에는 브라우저 렌더링 과정에 대해서 공부하고 정리해보려고 한다.&lt;/p&gt;
&lt;h3&gt;목차&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;브라우저&lt;/li&gt;
&lt;li&gt;브라우저 렌더링 동작 과정
&lt;ul&gt;
&lt;li&gt;Parsing&lt;/li&gt;
&lt;li&gt;Style&lt;/li&gt;
&lt;li&gt;Layout/Reflow&lt;/li&gt;
&lt;li&gt;Painting&lt;/li&gt;
&lt;li&gt;Composite&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;마무리&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;브라우저&lt;/h2&gt;
&lt;p&gt;브라우저 렌더링 과정을 알아보기전에 브라우저가 무엇인지 먼저 알아보자. 우선 브라우저는 우리가 흔히 인터넷에 접속할 때 사용하는 Chrome, Safari, Firefox, Internet Explorer 등을 말한다.&lt;/p&gt;
&lt;p&gt;MDN에서는 브라우저에 대해 웹에서 페이지를 찾아서 보여주고, 사용자가 하이퍼링크를 통해 다른 페이지로 이동할 수 있도록 하는 프로그램이라고 설명하고 있다. 여기서 중요하다고 생각하는 부분은 찾아서 보여준다는 것이다.&lt;/p&gt;
&lt;p&gt;브라우저는 유저가 선택한 자원을 서버로 부터 받아와서 유저에게 보여준다. 이 자원은 페이지 외에도 이미지, 비디오 등의 컨텐츠들도 포함된다. 받아온 자원들을 렌더링 과정을 통해 유저에게 보여주게 된다.&lt;/p&gt;
&lt;h2&gt;브라우저 렌더링 동작 과정&lt;/h2&gt;
&lt;p&gt;렌더링의 기본적인 동작 과정은 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;HTML 파일과 CSS 파일을 파싱해서 각각 Tree를 만든다. (Parsing)&lt;/li&gt;
&lt;li&gt;두 Tree를 결합하여 Rendering Tree를 만든다. (Style)&lt;/li&gt;
&lt;li&gt;Rendering Tree에서 각 노드의 위치와 크기를 계산한다. (Layout)&lt;/li&gt;
&lt;li&gt;계산된 값을 이용해 각 노드를 화면상의 실제 픽셀로 변환하고, 레이어를 만든다. (Paint)&lt;/li&gt;
&lt;li&gt;레이어를 합성하여 실제 화면에 나타낸다. (Composite)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;각 단계를 좀 더 자세하게 알아보자.&lt;/p&gt;
&lt;h3&gt;Parsing&lt;/h3&gt;
&lt;p&gt;브라우저가 페이지를 렌더링하려면 가장 먼저 받아온 HTML 파일을 해석해야한다. Parsing 단계는 HTML 파일을 해석하여 DOM(Document Object Model) Tree를 구성하는 단계이다.&lt;/p&gt;
&lt;p&gt;파싱 중 HTML에 CSS가 포함되어 있다면 CSSOM(CSS Object Model) Tree 구성 작업도 함께 진행한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1746px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1d5973bb2abd4ea8580e2d6f9f286640/1805d/2021-10-24-browser-rendering-1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 25.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAABFUlEQVR42l2QS0+DUBCF+f+/w7gxbkxMXZioKxPjrrUWK1BKy6U8W3ophXLv54Vqok5mMa9zZs5YoOktLwrq+jDEsnDJl/dofe79zAgRsd/LP7X/ZvXlXLwQB88cOzDOJpwQTC/plEZ94/ZVi7cULMOC8tCRLEZsMwdlekops1ydCYvolci5JfbuyKIxZfZO4t3SNBX1ySxLXbLFjckb6kYjK4mYXxsVn0SBwS4eSWPHcIxQXYfVtdIEciDwgg2hWOFPLgjWa9x1RbaZkjpX5LuS2dzH9mIi74HIfzIXh8zsDwKRMX6bIpIS67f+Xp1IJbazYhUVQ97/UeuO9qTMdUd2smVi+4xnLsm2Gd7SnDTHphukfwEbEnqwwe5kuAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;image1&quot;
        title=&quot;&quot;
        src=&quot;/static/1d5973bb2abd4ea8580e2d6f9f286640/1805d/2021-10-24-browser-rendering-1.png&quot;
        srcset=&quot;/static/1d5973bb2abd4ea8580e2d6f9f286640/0eb09/2021-10-24-browser-rendering-1.png 500w,
/static/1d5973bb2abd4ea8580e2d6f9f286640/1263b/2021-10-24-browser-rendering-1.png 1000w,
/static/1d5973bb2abd4ea8580e2d6f9f286640/1805d/2021-10-24-browser-rendering-1.png 1746w&quot;
        sizes=&quot;(max-width: 1746px) 100vw, 1746px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Style&lt;/h3&gt;
&lt;p&gt;Style 단계에서는 Parsing 단계에서 생성된 DOM Tree와 CSSOM Tree를 매칭시켜서 Render Tree를 구성한다. Render Tree는 실제로 화면에 그려질 Tree이다.&lt;/p&gt;
&lt;p&gt;예를 들면 Render Tree를 구성할때 &lt;code class=&quot;language-text&quot;&gt;visibility: hidden&lt;/code&gt;은 요소가 공간을 차지하고, 보이지만 않기 때문에 Render Tree에 포함이 되지만, &lt;code class=&quot;language-text&quot;&gt;display: none&lt;/code&gt; 의 경우 Render Tree에서 제외된다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1308px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/812332bcab15fdc8d05543579dad9f5c/919e0/2021-10-24-browser-rendering-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 25%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA+ElEQVR42n2OS0vDQBSF+8/Fpf/HjXQhiDslFqlIYmxjS0MeTmYmk0xenXwmxUVF6IF7z+ae755FHMdEUcTszjlm1c2A1BYha0Q5cBxBKYPShnHkohZt22KtpZvcuRE3JTJpCXcph6QgEwptKoQQSKnpBmibks5muGP7Hziv8fdtqkuW3gPLp3se3zzyGvT+lvz1ilJuTjdzW/F1R7K6ppIfnOdnW5zTm36YoIJvo0iVJYhy/E1MLivWwR5/m9AdwcRL9i83mCIkzARe6PEceGzz5C/wXMNUxdQNle2xHZRVM017atjXB5piRd8ZYq1YR+/4u0+K2vADMtt9e8lspPAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;image2&quot;
        title=&quot;&quot;
        src=&quot;/static/812332bcab15fdc8d05543579dad9f5c/919e0/2021-10-24-browser-rendering-2.png&quot;
        srcset=&quot;/static/812332bcab15fdc8d05543579dad9f5c/0eb09/2021-10-24-browser-rendering-2.png 500w,
/static/812332bcab15fdc8d05543579dad9f5c/1263b/2021-10-24-browser-rendering-2.png 1000w,
/static/812332bcab15fdc8d05543579dad9f5c/919e0/2021-10-24-browser-rendering-2.png 1308w&quot;
        sizes=&quot;(max-width: 1308px) 100vw, 1308px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Layout&lt;/h3&gt;
&lt;p&gt;Layout 단계에서는 Render Tree를 화면에 어떻게 배치해야 할 것인지 노드의 정확한 위치와 크기를 계산한다.&lt;/p&gt;
&lt;p&gt;루트부터 노드를 순회하면서 노드의 정확한 크기와 위치를 계산하고 Render Tree에 반영한다. 만약 크기 값을 %로 지정하였다면, Layout 단계에서 % 값을 계산해서 픽셀 단위로 변환한다.&lt;/p&gt;
&lt;h3&gt;Paint&lt;/h3&gt;
&lt;p&gt;Paint 단계에서는 Layout 단계에서 계산된 값을 이용해 Render Tree의 각 노드를 화면상의 실제 픽셀로 변환한다. 이때 픽셀로 변환된 결과는 하나의 레이어가 아니라 여러 개의 레이어로 관리된다.&lt;/p&gt;
&lt;p&gt;당연한 말이지만 스타일이 복잡할수록 Paint 시간도 늘어난다. 예를 들어, 단색 배경의 경우 시간과 작업이 적게 필요하지만, 그림자 효과는 시간과 작업이 더 많이 필요하다.&lt;/p&gt;
&lt;h3&gt;Composite&lt;/h3&gt;
&lt;p&gt;Composite 단계에서는 Paint 단계에서 생성된 레이어를 합성하여 실제 화면에 나타낸다. 우리는 화면에서 웹 페이지를 볼 수 있다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;오늘은 브라우저 렌더링 동작 과정에 대해 정리하는 시간을 가져봤다.&lt;/p&gt;
&lt;p&gt;깊게 들여다보면 더 복잡한 과정들이 숨어있지만, 누군가 다시 나에게 브라우저 렌더링 동작 과정에 대해 물어본다면 어떻게 설명 할 수 있을지 생각하며 글을 작성해보았다.&lt;/p&gt;
&lt;p&gt;혹시 더 많은 내용이 궁금하면 &lt;a href=&quot;https://d2.naver.com/helloworld/59361&quot;&gt;naver d2 - 브라우저는 어떻게 동작하는가?&lt;/a&gt; 포스팅을 읽어보는 걸 추천한다.&lt;/p&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Glossary/Browser&quot;&gt;MDN - 브라우저&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%9B%B9_%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80&quot;&gt;wikipedia - 웹 브라우저&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/59361&quot;&gt;naver d2 - 브라우저는 어떻게 동작하는가?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ui.toast.com/fe-guide/ko_PERFORMANCE&quot;&gt;toast - 성능 최적화&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;이미지 출처&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction?hl=ko&quot;&gt;https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction?hl=ko&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[SVG viewBox를 알아보자]]></title><description><![CDATA[목차 SVG란 viewBox란 의미 기본 예시 위치 조정 예시 확대, 축소 예시 SVG란 SVG에 대해서 간단하게 알아보자. SVG란 Scalable Vector Graphics…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-10-24-svg-viewBox/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-10-24-svg-viewBox/</guid><pubDate>Sun, 24 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;목차&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;SVG란&lt;/li&gt;
&lt;li&gt;viewBox란
&lt;ul&gt;
&lt;li&gt;의미&lt;/li&gt;
&lt;li&gt;기본 예시&lt;/li&gt;
&lt;li&gt;위치 조정 예시&lt;/li&gt;
&lt;li&gt;확대, 축소 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;SVG란&lt;/h3&gt;
&lt;p&gt;SVG에 대해서 간단하게 알아보자. SVG란 &lt;strong&gt;S&lt;/strong&gt;calable &lt;strong&gt;V&lt;/strong&gt;ector &lt;strong&gt;G&lt;/strong&gt;raphics의 약자로 &lt;code class=&quot;language-text&quot;&gt;확장 가능한 벡터 그래픽&lt;/code&gt;, 다시 말해 &lt;code class=&quot;language-text&quot;&gt;크기를 확대하거나 축소할 수 있는, 수학적 함수를 이용해 도형이나 선을 그려서 표현한 그래픽&lt;/code&gt;이라고 할 수 있다. 여기서 &lt;code class=&quot;language-text&quot;&gt;수학적 함수를 이용해 도형이나 선을 그려서 표현한&lt;/code&gt;이라는 의미는 우리가 수학 시간에 배웠던 &lt;code class=&quot;language-text&quot;&gt;좌표평선 위의 그려져 있는 그래프&lt;/code&gt; 정도로 이해할 수 있다. SVG는 이런 방식을 차용함으로써, 우리가 흔히 알고 있는 &lt;code class=&quot;language-text&quot;&gt;jpg&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;png&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;gif&lt;/code&gt;와 다르게 확대해도 선명도가 떨어지지 않는다. 또한, 도형이나 선으로 구성되어 있어 파일의 용량이 상대적으로 작다. 하지만 &lt;code class=&quot;language-text&quot;&gt;svg&lt;/code&gt;를 구성하는 선들이 복잡해지는 경우, 다른 확장자(&lt;code class=&quot;language-text&quot;&gt;jpg&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;png&lt;/code&gt; 등)로 된 동일한 파일보다 그 용량이 더 커질 수 있고, 수학적 계산으로 로딩 시간이 길어질 수 있다는 단점이 있다.&lt;/p&gt;
&lt;h3&gt;viewBox란&lt;/h3&gt;
&lt;h5&gt;의미&lt;/h5&gt;
&lt;p&gt;viewBox는 svg 요소가 그려지는 영역에서, svg 요소의 크기를 확대 또는 축소 그리고 위치를 조정할 수 있는 속성이다. svg 요소를 viewBox 속성 없이도 화면에 그릴 수는 있다. 하지만, 화면의 크기가 달라져도 svg 요소의 크기는 고정되어 반응형에 취약한 모습을 보인다. 즉, viewBox 속성을 이용하면 화면에 크기에 따라 svg 요소의 크기가 자동으로 조절된다. 이런 이유로 svg 요소를 사용하면서 반응형 웹을 설계하기 위해서 viewBox는 필수 속성이다.&lt;/p&gt;
&lt;h5&gt;viewBox 속성 값&lt;/h5&gt;
&lt;p&gt;viewBox 속성의 값은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;viewBox=&quot;&amp;lt;min-x&gt; &amp;lt;min-y&gt; &amp;lt;width&gt; &amp;lt;height&gt;&quot;&lt;/code&gt;&lt;/p&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/40762111/138582965-1d57b799-4cee-42fa-83dc-fa9e38aa2564.png&quot; width=&quot;80%&quot; alt=&quot;viewBox 예시1&quot;&gt;
&lt;p&gt;위 그림처럼 &lt;code class=&quot;language-text&quot;&gt;min-x&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;min-y&lt;/code&gt;는 svg가 그려지는 영역의 시작점, 왼쪽 상단의 꼭짓점으로, &lt;code class=&quot;language-text&quot;&gt;width&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;height&lt;/code&gt;는 각각 영역의 가로, 세로 길이로 볼 수 있다.&lt;/p&gt;
&lt;p&gt;여기서 주의할 점은 &lt;code class=&quot;language-text&quot;&gt;width&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;height&lt;/code&gt;가 우리가 흔히 생각하는 &lt;code class=&quot;language-text&quot;&gt;px&lt;/code&gt; 단위가 아니라는 것이다. viewBox의 의미에서 말했던 것처럼 svg 요소들의 위치를 조정하거나, 요소들의 확대와 축소를 위한 일종의 좌표 평면이다. 그럼 예시를 통해서 좀 더 자세히 알아보겠다.&lt;/p&gt;
&lt;h5&gt;기본 예시&lt;/h5&gt;
&lt;p&gt;이해를 돕기 위해 svg 요소 중에 &lt;code class=&quot;language-text&quot;&gt;circle&lt;/code&gt;을 추가하여 컴포넌트 형태로 만들었다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div style&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;marginTop&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;50px&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;marginLeft&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;50px&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div style&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;backgroundColor&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;yellow&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;300px&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;300px&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;svg viewBox&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0 0 200 200&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;circle r&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt; fill&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;blue&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;svg&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; App&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;img width=&quot;100%&quot; alt=&quot;SVG viewBox-2&quot; src=&quot;https://user-images.githubusercontent.com/40762111/138591296-1cca74c3-136b-4f64-b6db-0cf36cedd3b0.png&quot;&gt;
&lt;p&gt;viewport에서 svg는 &lt;code class=&quot;language-text&quot;&gt;width&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;height&lt;/code&gt;가 각각 300px인 영역 안에 위치하고 있다. 하지만 svg의 viewBox는 &lt;code class=&quot;language-text&quot;&gt;&quot;0 0 200 200&quot;&lt;/code&gt;이므로, viewport의 &lt;code class=&quot;language-text&quot;&gt;width&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;height&lt;/code&gt; 300px의 길이는 svg viewBox 좌표평면 기준으로 200의 길이가 된다. svg의 요소 중에서 &lt;code class=&quot;language-text&quot;&gt;circle&lt;/code&gt;의 좌표 또한 svg의 viewBox의 좌표를 따르게 된다. &lt;code class=&quot;language-text&quot;&gt;circle&lt;/code&gt; 요소의 중심 좌표 속성 &lt;code class=&quot;language-text&quot;&gt;cx&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;cy&lt;/code&gt;가 설정되지 않은 상태이므로 현재 &lt;code class=&quot;language-text&quot;&gt;cx=&quot;0&quot;&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;cy=&quot;0&quot;&lt;/code&gt;이 적용된 상태이면서, 반지름 &lt;code class=&quot;language-text&quot;&gt;r=100&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;circle&lt;/code&gt; 요소가 만들어져 있는 것을 확인할 수 있다. 여기서 특징은 원 중심을 기준으로 제1, 3, 4 사분면(보라색 부분)은 svg viewBox 영역을 벗어났기 때문에 viewport에서 나타나지 않는다는 점이다.&lt;/p&gt;
&lt;h5&gt;위치 조정 예시&lt;/h5&gt;
&lt;p&gt;이번에는 다른 예시를 보자.&lt;/p&gt;
&lt;p&gt;일단 &lt;code class=&quot;language-text&quot;&gt;circle&lt;/code&gt; 요소에 &lt;code class=&quot;language-text&quot;&gt;cx=&quot;100&quot;&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;cy=&quot;100&quot;&lt;/code&gt;을 추가했다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div style&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;marginTop&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;50px&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;marginLeft&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;50px&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div style&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;backgroundColor&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;yellow&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;300px&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;300px&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;svg viewBox&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0 0 200 200&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;circle cx&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt; cy&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt; r&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt; fill&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;blue&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;svg&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; App&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;img width=&quot;100%&quot; alt=&quot;SVG viewBox-3&quot; src=&quot;https://user-images.githubusercontent.com/40762111/138591304-625a271a-d0b2-4b5e-813b-750dee8f68c0.png&quot;&gt;
&lt;p&gt;현재 상태에서 viewBox를 이용해서 어떻게 기본 예시와 같은 모양을 만들 수 있을까. 바로 &lt;code class=&quot;language-text&quot;&gt;min-x&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;min-y&lt;/code&gt;를 변경하면 된다.&lt;/p&gt;
&lt;img width=&quot;100%&quot; alt=&quot;SVG viewBox-4&quot; src=&quot;https://user-images.githubusercontent.com/40762111/138591309-32277e0e-626e-45f8-8117-a22f2f36825c.png&quot;&gt;
&lt;p&gt;viewBox의 &lt;code class=&quot;language-text&quot;&gt;min-x&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;min-y&lt;/code&gt;를 변경하면 viewport에서 보이는 영역(현재 예시에서의 &lt;code class=&quot;language-text&quot;&gt;width&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;height&lt;/code&gt; 300px 영역)이 동일하지만, svg에서 보여주는 영역은 변경된다. viewBox가 &lt;code class=&quot;language-text&quot;&gt;&quot;0 0 200 200&quot;&lt;/code&gt;부터 &lt;code class=&quot;language-text&quot;&gt;&quot;1 1 200 200&quot;&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;&quot;2 2 200 200&quot;&lt;/code&gt; 이런 식으로 &lt;code class=&quot;language-text&quot;&gt;&quot;100 100 200 200&quot;&lt;/code&gt;까지 변경되면, 아래 영상과 같이 된다. 영상은 위의 왼쪽 사진의 빨간 네모가 오른쪽 사진의 빨간 네모 위치로 이동한다고 생각하면 된다. 그런데, 빨간 네모는 실제로 화면에서 보이는 부분이므로 정지되어 있다. 즉, 빨간 네모 화면이 동남쪽으로 이동하는 것은 파란 원이 북서쪽으로 이동하는 것처럼 보이므로 영상과 같이 나타난다.&lt;/p&gt;
&lt;video width=&quot;50%&quot; autoplay loop&gt;
    &lt;source src=&quot;https://user-images.githubusercontent.com/40762111/138588688-3d925445-a45e-4f4c-9c2f-eaaf94c77087.mov&quot; type=&quot;video/mp4&quot;&gt;
&lt;/video&gt;
&lt;h5&gt;확대, 축소 예시&lt;/h5&gt;
&lt;p&gt;viewBox의 &lt;code class=&quot;language-text&quot;&gt;width&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;height&lt;/code&gt;를 변경하면 svg 요소들을 확대하거나 축소할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div style&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;marginTop&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;50px&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;marginLeft&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;50px&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div style&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;backgroundColor&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;yellow&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;300px&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;300px&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 이 부분만 변경 viewBox=&quot;0 0 100 100&quot; / viewBox=&quot;0 0 200 200&quot; / viewBox=&quot;0 0 300 300&quot;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;svg viewBox&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0 0 100 100&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;circle cx&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt; cy&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt; r&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt; fill&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;blue&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;svg&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; App&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;img width=&quot;1023&quot; alt=&quot;SVG viewBox-6&quot; src=&quot;https://user-images.githubusercontent.com/40762111/138591367-31e26466-f18d-4158-91b2-8033b77fa7bd.png&quot;&gt;
&lt;p&gt;viewBox의 속성값 중 &lt;code class=&quot;language-text&quot;&gt;width&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;height&lt;/code&gt;를 각각 100, 200, 300으로 변경했다. 이 값을 변경하더라도 viewport에서 보이는 영역(&lt;code class=&quot;language-text&quot;&gt;width&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;height&lt;/code&gt; 300px)은 동일하지만, svg 영역 안에서의 요소를 확대하거나 축소할 수 있다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Flyway]]></title><description><![CDATA[깃-들다 프로젝트를 진행하며 DB 형상관리를 위해 Flyway를 도입했다.
여기서 와 가 무엇일까?
이번 글에서 한번 알아보자. Flyway? Flyway 공식 문서에서는 Flyway를 다음과 같이 소개한다. Flyway is an open…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-10-23-flyway/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-10-23-flyway/</guid><pubDate>Sat, 23 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;깃-들다 프로젝트를 진행하며 DB 형상관리를 위해 Flyway를 도입했다.
여기서 &lt;code class=&quot;language-text&quot;&gt;DB 형상관리&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;Flyway&lt;/code&gt;가 무엇일까?
이번 글에서 한번 알아보자.&lt;br/&gt;&lt;/p&gt;
&lt;!-- end --&gt;
&lt;br/&gt;
&lt;h1&gt;Flyway?&lt;/h1&gt;
&lt;p&gt;Flyway 공식 문서에서는 Flyway를 다음과 같이 소개한다.&lt;br/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Flyway is an open-source database migration tool.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Flyway는 오픈소스 DB 마이그레이션 도구로, DB 형상관리를 위한 도구이다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;개발을 하며 한번 쯤은 형상관리 도구를 사용해본 경험이 있을 거다.
대표적으로 Git이 있다. 이는 소스코드 형상관리 도구이다.
결국 Flyway는 Git처럼 형상관리 도구인데, DB 형상관리를 할 때 쓰는 도구이다.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h1&gt;사용 이유&lt;/h1&gt;
&lt;p&gt;운영 환경에서는 &lt;code class=&quot;language-text&quot;&gt;spring.jpa.hibernate.ddl-auto&lt;/code&gt; 옵션을 create 또는 update로 할 수 없다.
그렇다면 DB 스키마에 변경이 생겼을 때, 직접 DB 서버에 접속해서 일일이 명령어를 쳐서 수정해야 한다.
하지만, 이렇게 하면 휴먼 에러가 발생할 가능성이 커진다. 오타를 낼 수도 있고, 실수로 명령어를 하나 빼먹을 수도 있다.
이때 Flyway와 같은 DB 형상관리 도구를 활용하면, 쉽고 안전하게 변경사항을 반영할 수 있다.
또, 스키마 버전 관리가 되기 때문에 어떤 변경사항이 있었는지 한눈에 볼 수 있다는 장점도 있다.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h1&gt;컨벤션&lt;/h1&gt;
&lt;p&gt;Flyway를 사용할 때, 스크립트의 위치와 네이밍을 조심해야 한다.
이들에 대한 컨벤션을 꼭 따라야 한다.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;위치&lt;/h2&gt;
&lt;p&gt;기본 위치는 &lt;code class=&quot;language-text&quot;&gt;main/resources/db/migration&lt;/code&gt;이다.
즉 resources 패키지에 &lt;code class=&quot;language-text&quot;&gt;db/migration&lt;/code&gt; 패키지를 만들고, 이 경로에 스크립트 파일을 추가하면 된다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;물론 커스텀 설정도 할 수 있다.
application.yml에서 &lt;code class=&quot;language-text&quot;&gt;spring.flyway.locations&lt;/code&gt; 옵션의 값을 &lt;code class=&quot;language-text&quot;&gt;classpath:db/migration/{다른_경로}&lt;/code&gt;와 같이 변경하면 된다.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;네이밍&lt;/h2&gt;
&lt;p&gt;Flyway 스크립트의 네이밍은 다음과 같은 규칙을 가진다.&lt;br/&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://user-images.githubusercontent.com/50176238/138549513-27e9a8a4-15e3-46ac-8c49-86cd35fae24a.png&quot;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;이미지 출처: &lt;a href=&quot;https://flywaydb.org/documentation/concepts/migrations#naming-1&quot;&gt;Flyway Documentation - Naming&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h3&gt;Prefix&lt;/h3&gt;
&lt;p&gt;V(Versioned), U(Undo), R(Repeatable)의 3가지 종류가 있다.
V는 현재 버전을 새로운 버전으로 업데이트하는 경우,
U는 현재 버전을 이전 버전으로 되돌리는 경우,
R은 버전에 관계 없이 매번 실행하는 경우에 사용한다.&lt;br/&gt;&lt;/p&gt;
&lt;h3&gt;Version&lt;/h3&gt;
&lt;p&gt;V와 U는 버전을 명시해야 하고, R은 버전을 명시하지 않는다.
R은 앞서 말했듯이 버전에 관련 없이 매번 실행되는 스크립트이기 때문이다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;새로 스크립트를 작성한다면, 스크립트의 버전을 이전보다 꼭 높게 적어야 한다.
예를 들어, 이전에 &lt;code class=&quot;language-text&quot;&gt;1.0&lt;/code&gt;였다면 이후에는 &lt;code class=&quot;language-text&quot;&gt;2.0&lt;/code&gt;과 같이 더 높은 숫자를 써야 한다.
이전보다 낮은 숫자를 쓰면, Flyway가 이를 무시하고 적용하지 않는다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;버전은 단순히 &lt;code class=&quot;language-text&quot;&gt;1&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;2&lt;/code&gt;처럼 적을 수도 있고, &lt;code class=&quot;language-text&quot;&gt;20211023&lt;/code&gt;처럼 날짜로 명시할 수도 있다.&lt;br/&gt;&lt;/p&gt;
&lt;h3&gt;Separator&lt;/h3&gt;
&lt;p&gt;무조건 &lt;code class=&quot;language-text&quot;&gt;__(언더바 2개)&lt;/code&gt;로 작성해야 한다.&lt;br/&gt;&lt;/p&gt;
&lt;h3&gt;Description&lt;/h3&gt;
&lt;p&gt;스크립트 내용에 맞게 자유롭게 적으면 된다.
단어 구분은 &lt;code class=&quot;language-text&quot;&gt;_(언더바 1개)&lt;/code&gt;로 한다.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h1&gt;적용 방법&lt;/h1&gt;
&lt;h2&gt;설정 파일&lt;/h2&gt;
&lt;h3&gt;build.gradle&lt;/h3&gt;
&lt;p&gt;먼저, Flyway 의존성을 추가한다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;dependencies &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	implementation &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.boot:spring-boot-starter-data-jpa&apos;&lt;/span&gt;
	implementation &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.boot:spring-boot-starter-web&apos;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 이 부분을 추가한다.&lt;/span&gt;
	implementation &lt;span class=&quot;token string&quot;&gt;&apos;org.flywaydb:flyway-core:6.4.2&apos;&lt;/span&gt;

	runtimeOnly &lt;span class=&quot;token string&quot;&gt;&apos;com.h2database:h2&apos;&lt;/span&gt;

	testImplementation &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.boot:spring-boot-starter-test&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h3&gt;application.yml&lt;/h3&gt;
&lt;p&gt;그리고 Flyway와 관련한 옵션을 추가한다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;spring.flyway.enabled&lt;/code&gt;는 기본 설정값이 true지만, 직관적으로 명시하기 위해 적어줬다.&lt;br/&gt;
&lt;code class=&quot;language-text&quot;&gt;spring.flyway.baseline-on-migration&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;변경 이력 테이블(버전 관리를 도와줌)&lt;/code&gt;을 생성하기 위해 작성했다.
변경 이력 테이블은 원래 자동으로 생성되나, 자동으로 생성되지 않는 경우도 있어 이를 대비하기 위해 넣어줬다.
Spring Boot 2 이상을 사용하는 경우 추가하면 된다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yml&quot;&gt;&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;spring&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# datasource는 각자에 맞게 작성한다.&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;datasource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; jdbc&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;h2&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;tcp&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//localhost/~/test;MODE=MySQL;DB_CLOSE_DELAY=&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;1;DB_CLOSE_ON_EXIT=FALSE
    &lt;span class=&quot;token key atrule&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; sa
    &lt;span class=&quot;token key atrule&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;jpa&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;show-sql&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;hibernate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;dialects&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; org.hibernate.dialect.MySQL57Dialect
        &lt;span class=&quot;token key atrule&quot;&gt;format_sql&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;generate-ddl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;hibernate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;ddl-auto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; validate
  &lt;span class=&quot;token comment&quot;&gt;# 이 부분을 추가한다.&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;flyway&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;baseline-on-migrate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h2&gt;V1 도입&lt;/h2&gt;
&lt;h3&gt;소스코드&lt;/h3&gt;
&lt;p&gt;간단하게 Member 엔티티를 구성했다.
필드(컬럼)으로 id, name이 있고 id는 auto_increment 설정이 돼있다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Member.java&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h3&gt;스크립트&lt;/h3&gt;
&lt;p&gt;Flyway 스크립트는 다음과 같이 작성했다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- V1__init.sql&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;drop&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;exists&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; member &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  id &lt;span class=&quot;token keyword&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;auto_increment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  name &lt;span class=&quot;token keyword&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h3&gt;결과&lt;/h3&gt;
&lt;p&gt;애플리케이션을 실행하고, h2에 접속하여 결과를 확인했다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;예상대로 Member 테이블이 생성됐다.
한편, &lt;code class=&quot;language-text&quot;&gt;flyway_schema_history&lt;/code&gt; 테이블도 생성됐다.
바로 이 테이블이 앞에서 말한 &lt;code class=&quot;language-text&quot;&gt;변경 이력 테이블&lt;/code&gt;이다.
DB 스키마의 변경사항을 이곳에 기록한다.&lt;br/&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
    &lt;img style=&quot;width: 340px; height: 100px&quot; src=&quot;https://user-images.githubusercontent.com/50176238/138552599-1cbdb51e-3e4e-49a7-9dcf-042aa984a1b2.png&quot;&gt;
&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;해당 테이블의 내용을 보고 싶다면, 아래 명령어를 통해 확인할 수 있다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;flyway_schema_history&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;이렇게 결과가 나타난다.
앞에서 작성한 V1 스크립트가 잘 기록됐다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50176238/138552652-7b487729-527f-4236-8ade-76f2f7f0bc79.png&quot; alt=&quot;flyway_schema_history_v1&quot;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;V2 도입&lt;/h2&gt;
&lt;h3&gt;소스코드&lt;/h3&gt;
&lt;p&gt;이번에는 Member 엔티티에 company 필드(컬럼)을 추가했다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Member.java&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; company&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h3&gt;스크립트&lt;/h3&gt;
&lt;p&gt;이어서 Flyway 스크립트를 다음과 같이 작성했다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- V2__add_company.sql&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; member &lt;span class=&quot;token keyword&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;column&lt;/span&gt; company &lt;span class=&quot;token keyword&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h3&gt;결과&lt;/h3&gt;
&lt;p&gt;애플리케이션을 다시 실행하고, 결과를 확인했다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Member 테이블에 변경사항이 잘 반영됐고, 변경 이력 테이블에서 버전 관리가 되고 있다.&lt;br/&gt;
Member 테이블의 1행 데이터는 V1에서 삽입했고, 2행 데이터는 V2에서 삽입했다.
따라서 1행 데이터의 company 필드값은 null이고, 2행 데이터의 company 필드값은 null이 아닌 것을 확인할 수 있다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50176238/138553829-324bbf6e-2383-4571-bab9-e8c6d0f21988.png&quot; alt=&quot;flyway_schema_history_v2&quot;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h1&gt;주의할 점&lt;/h1&gt;
&lt;p&gt;Flyway는 DB 형상관리를 편리하게 해주는 장점이 있지만, 조심할 점이 있다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;DB 스키마에 변경사항이 생겼을 때(e.g. 엔티티에 필드가 추가/삭제되는 경우), 이를 Flyway에게 알려주는 스크립트를 잊지 말고 작성해야 한다.
이를 깜빡하면, 애플리케이션이 실행되지 않는 불상사가 발생한다.
그러므로 꼭꼭 스크립트를 작성하자.
그리고 이를 통해 DB 형상관리를 조금 더 우아하게 해보자.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h1&gt;References&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=pxDlj5jA9z4&amp;#x26;t=634s&amp;#x26;ab_channel=%EC%9A%B0%EC%95%84%ED%95%9CTech&quot;&gt;[10분 테코톡] 🐶 코기의 Flyway&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flywaydb.org/documentation/&quot;&gt;Flyway Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/da-nyee/flyway-learning-test&quot;&gt;Flyway 학습 테스트&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[🔒 Java 로 동기화를 해보자!]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-10-23-java-synchronize/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-10-23-java-synchronize/</guid><pubDate>Sat, 23 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이 글에서는 상호배제를 통한 동기화를 알아볼 것이다.
동기화는 프로세스(스레드)가 수행되는 시점을 조절하여 서로가 알고 있는 정보가 일치하는 것인데, 쉽게 말해 프로세스 간 데이터가 일치하도록 하는 것이다. 이러한 동기화 메커니즘인 상호배제는 프로세스들이 필요로 하는 자원에 대해 배타적인 통제권을 요구하는 것이다. 쉽게 말해 하나의 프로세스가 공유자원을 사용할 때 다른 프로세스가 동일한 공유자원에 접근할 수 없도록 통제하는 것을 뜻한다. 상호배제 방법으로는 Mutex, Semaphore 방식이 사용된다. Java 에서는 Monitor 라는 도구를 통해 객체에 Lock 을 걸어 상호배제를 할 수 있다. Monitor 를 사용하면 어떤 점이 좋은지 알아보고 직접 사용해보자.&lt;/p&gt;
&lt;p&gt;먼저 Mutex, Semaphore 개념을 간단하게 살펴보자.&lt;/p&gt;
&lt;h2&gt;Mutex&lt;/h2&gt;
&lt;p&gt;여러 스레드를 실행하는 환경에서 자원에 대한 접근에 제한을 강제하기 위한 동기화 메커니즘이다. 특징을 살펴보면 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Boolean 타입의 Lock 변수를 사용한다. 따라서 1개의 공유자원에 대한 접근을 제한한다.&lt;/li&gt;
&lt;li&gt;공유자원을 사용 중인 스레드가 있을 때, 다른 스레드가 공유자원에 접근한다면 Blocking 후 대기 큐로 보낸다.&lt;/li&gt;
&lt;li&gt;Lock 을 건 스레드만 Lock 을 해제할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Semaphore&lt;/h2&gt;
&lt;p&gt;멀티프로그래밍 환경에서 다수의 프로세스나 스레드가 n 개의 공유 자원에 대한 접근을 제한하는 방법으로 사용되는 동기화 기법이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;세마포어 변수를 통해 wait, signal 을 관리한다. 세마포어 변수는 0 이상의 정수형 변수를 갖는다.&lt;/li&gt;
&lt;li&gt;n 개의 공유자원에 대한 접근을 제한할 수 있으며 이를 계수 세마포어라고 한다.&lt;/li&gt;
&lt;li&gt;접근 가능한 공유 자원의 수가 1개일 때는 이진 세마포어로 뮤텍스처럼 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;큐에 연결된 스레드를 깨우는 방식에 따라 강성 세마포어(큐에 연결된 스레드를 깨울 때 FIFO 정책), 약성 세마포어(큐에 연결된 스레드를 깨울 때 순서를 특별히 명시하지 않음)로 구분된다.&lt;/li&gt;
&lt;li&gt;Lock 을 걸지 않은 스레드도 Signal 을 보내 Lock 을 해제할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;동기화 메커니즘&lt;/h2&gt;
&lt;p&gt;Mutex, Semaphore 는 상호배제를 위한 동기화 개념이다. Framework, Library 에서는 이 개념을 바탕으로 구현된 상호배제 도구를 제공한다. 물론 C 언어 같은 언어는 제공하지 않기 때문에 직접 코드를 구현해야 하는 번거로움이 있다. 또한 Semaphore 는 완벽한 상호배제를 제공한다고 할 수 없기 때문에 이를 보완까지 해둔 Monitor 를 사용하면 훨씬 쉽게 동기화를 사용할 수 있다.&lt;/p&gt;
&lt;h2&gt;Monitor&lt;/h2&gt;
&lt;p&gt;임계 구역을 지켜내기 위한 방법인 상호 배제를 프로그램으로 구현한 것이다. 세마포어는 wait &amp;#x26; signal 연산 순서를 바꿔서 실행하거나 둘 중 하나라도 생략하면 상호배제를 위반하는 상황이나 교착 상태가 발생한다. wait &amp;#x26; signal 연산이 프로그램 전체에 구성되어 있으면 세마포어의 영향이 미치는 곳이 어딘지 파악하기 어렵기 때문에 세마포어를 사용하여 프로그램을 구현하기 어렵다. 이러한 단점을 극복하기 위해 모니터가 등장했다. 모니터는 프로그래밍 언어 수준에서 제공된다. 대표적으로 Java 에서 제공한다. 순차적으로 사용할  수 있는 공유 자원 혹은 공유 자원 그룹을 할당하는 데 사용된다. 모니터는 이진 세마포어만 가능하다. 모니터를 통해 프로세스가 자원에 접근하는 방식을 이미지로 보면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/49058669/138542983-2f7517e3-94e1-4086-8084-f11a4d2345cc.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;공유 자원에 점유 중인 프로세스(스레드)는 Lock 을 가지고 있다. 공유 자원을 점유 중인 프로세스(스레드)가 있는 상황에서 다른 프로세스(스레드)가 공유 자원에 접근하려고 하면 외부 모니터 준비 큐에서 진입을 &lt;code class=&quot;language-text&quot;&gt;wait&lt;/code&gt; 한다. Monitor 는 Semaphore 처럼 signal 연산을 보내는  것이 아니라 조건 변수를 사용하여 특정 조건에 대해 대기 큐에 signal 을 보내 작업을 시작시킨다.&lt;/p&gt;
&lt;h2&gt;Synchronized&lt;/h2&gt;
&lt;p&gt;Java 의 &lt;code class=&quot;language-text&quot;&gt;synchronized&lt;/code&gt; 키워드는 스레드 동기화를 할 때 사용하는 대표적인 기법이다. 자바의 모든 인스턴스는 Monitor 를 가지고 있으며(Object 내부) Monitor 를 통해 Thread 동기화를 수행한다. &lt;code class=&quot;language-text&quot;&gt;synchronized&lt;/code&gt; 키워드가 붙은 메서드를 사용하려면 Lock 을 가지고 있어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;synchronized&lt;/code&gt; 를 사용하는 방법으로는 메서드 앞에 키워드 명시, 인스턴스로 사용하기가 있다. 동기화가 필요한 메서드 앞에 &lt;code class=&quot;language-text&quot;&gt;synchronized&lt;/code&gt; 키워드만 붙여주면 편리하게 동기화를 적용할 수 있다. 인스턴스로 사용하려면 메서드 내부에서 &lt;code class=&quot;language-text&quot;&gt;synchronized (메서드) { 구현 }&lt;/code&gt; 으로 사용할 수 있다.&lt;/p&gt;
&lt;h2&gt;wait, notify&lt;/h2&gt;
&lt;p&gt;Monitor 에는 Condition Variable 이 있는데 이를 통해 &lt;code class=&quot;language-text&quot;&gt;wait()&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;notify()&lt;/code&gt; 메서드가 구현되어 있다.&lt;/p&gt;
&lt;p&gt;Lock 을 가진 스레드가 다른 스레드에 Lock 을 넘겨준 이후에 대기해야 한다면 &lt;code class=&quot;language-text&quot;&gt;wait()&lt;/code&gt; 를 사용하면 된다. 그리고 대기 중인 임의의 스레드를 깨우려면 &lt;code class=&quot;language-text&quot;&gt;notify()&lt;/code&gt; 를 통해 깨울 수 있다. 대기 중인 모든 스레드를 깨우려면 &lt;code class=&quot;language-text&quot;&gt;notifyAll()&lt;/code&gt; 을 통해 깨울 수 있는데, 이 경우에는 하나의 스레드만 Lock 을 획득하고 나머지 스레드는 다시 대기 상태에 들어간다.&lt;/p&gt;
&lt;h2&gt;동기화를 경험해 보자!&lt;/h2&gt;
&lt;p&gt;먼저 &lt;code class=&quot;language-text&quot;&gt;synchronized&lt;/code&gt; 를 이용한 메서드 동기화와 블럭 동기화를 살펴본다.&lt;/p&gt;
&lt;p&gt;이 브라우저의 특징을 살펴보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;최대 5개의 탭이 활성화될 수 있다.&lt;/li&gt;
&lt;li&gt;6개 이상의 탭이 동시에 켜지게 되면 브라우저가 강제 종료된다.&lt;/li&gt;
&lt;li&gt;새로운 탭이 켜지는 시간은 0~1초가 걸린다.&lt;/li&gt;
&lt;li&gt;새로운 탭이 켜지는 로딩시간 동안은 현재 활성화된 탭 수가 변경되지 않는다.
&lt;ul&gt;
&lt;li&gt;브라우저 로딩이 끝난 후(1초 후) 탭 수가 갱신된다는 뜻이다. (4개 -&gt; 새로운 탭 로딩(1초) -&gt; 1초 후 5개)&lt;/li&gt;
&lt;li&gt;즉, 로딩 중에 새로운 탭이 ‘아직 4개밖에 활성화되지 않았네?’ 라고 판단하고 새로운 탭이 또 켜질 수 있다는 뜻이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;비동기(결과를 주기 전에 다른 작업이 이루어지는 것)로 처리가 된다면 새로운 탭이 추가되기 위해 로딩 중인 상황에서 또 다른 탭이 추가되는 것을 시도한다면 곧이어 강제 종료가 되는 상황이 발생하는 것을 알 수 있다. 그 때문에 새로운 탭이 추가되는 상황에는 다른 탭이 추가되는 작업을 막을 필요가 있다.&lt;/p&gt;
&lt;h3&gt;세 개의 클래스를 준비한다.&lt;/h3&gt;
&lt;p&gt;동기화, 비동기화 상태에 따라 스레드가 어떻게 동작하는지 알아보는 실습이므로 아래 코드를 복사해서 사용하면 된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Computer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebBrowser&lt;/span&gt; webBrowser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebBrowser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt; threadA &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Google&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; webBrowser&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt; threadB &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Naver&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; webBrowser&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt; threadC &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Daum&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; webBrowser&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        threadA&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        threadB&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        threadC&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSite&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Runnable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; webSiteName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebBrowser&lt;/span&gt; webBrowser&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; webSiteName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebBrowser&lt;/span&gt; webBrowser&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;webSiteName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; webSiteName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;webBrowser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; webBrowser&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 메서드 블럭 동기화&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webBrowser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSpace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                webBrowser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createNewTab&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSiteName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebBrowser&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SPACE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; webSiteNames &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; maxWebCount&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebBrowser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; maxWebCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maxWebCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; maxWebCount&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 메서드 동기화&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createNewTab&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; webSiteName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;full&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSiteName &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; 사이트가 켜지는 중입니다...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            webSiteNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSiteName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;showRunningBrowser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;showRunningBrowser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSiteNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; maxWebCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UnsupportedOperationException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;현재 브라우저 탭이 6개 이상 활성화 되어 강제 종료합니다...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;┌───────────────────────────────────────────────────────────────────────────────┐&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;│ ◆ Wilder Web Browser                                                    - □ x │&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;└───────────────────────────────────────────────────────────────────────────────┘&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;StringBuilder&lt;/span&gt; browserNameLine &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StringBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;StringBuilder&lt;/span&gt; browserUnderLine &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StringBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSiteNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            browserNameLine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;│&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            browserUnderLine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;└&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; webSiteNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            browserNameLine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateWebSiteName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSiteNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;   │&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            browserUnderLine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;───────────────┘&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;browserNameLine&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;browserUnderLine&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateWebSiteName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;StringBuilder&lt;/span&gt; builder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StringBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; space &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; interval &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; space &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SPACE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;repeat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interval&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SPACE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;repeat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; interval &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hasSpace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; webSiteNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; maxWebCount&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;full&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSpace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Computer 클래스의 main 메서드를 실행해 본다. 위의 예시는 &lt;code class=&quot;language-text&quot;&gt;synchronized&lt;/code&gt; 키워드를 통해 간단하게 모니터를 사용한 것이다. 해당 코드를 실행해 보면 아래와 비슷한 결과를 얻을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/49058669/138544352-e9989200-7585-4756-917f-26264aee1288.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;여기서 핵심은 여러 스레드에서 새로운 탭을 추가하지만, 탭이 5개를 초과하여 생성되지 않는다는 것을 알 수 있다. &lt;code class=&quot;language-text&quot;&gt;synchonized&lt;/code&gt; 키워드를 제거하고 다시 실행해 보자. &lt;code class=&quot;language-text&quot;&gt;WebBrowser&lt;/code&gt; 클래스의 &lt;code class=&quot;language-text&quot;&gt;createNewTab&lt;/code&gt; 메서드와 &lt;code class=&quot;language-text&quot;&gt;WebSite&lt;/code&gt; 의 &lt;code class=&quot;language-text&quot;&gt;run&lt;/code&gt; 메서드의 &lt;code class=&quot;language-text&quot;&gt;synchronized&lt;/code&gt; 부분을 제거한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createNewTab&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; webSiteName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;full&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSiteName &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; 사이트가 켜지는 중입니다...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            webSiteNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSiteName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;showRunningBrowser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webBrowser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSpace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            webBrowser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createNewTab&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSiteName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같이 변경한 후 실행해 보면 스레드가 비동기로 처리되면서 6개 이상의 탭이 활성화되며 예외가 발생하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/49058669/138545250-2535617c-56b1-4aa6-b099-3b74c851eb77.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;위의 실습으로 동기화가 어떻게 진행되는지 알아보았다. 다음은 wait(), notify() 를 이용한 동기화를 해보자.&lt;/p&gt;
&lt;h2&gt;조건 변수를 통한 모니터 동기화&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;wait&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;notify&lt;/code&gt; 를 사용한다. 이전에 사용한 코드와 비슷하지만, 일부 변경 점이 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;3초마다 가장 먼저 켜진 웹 사이트가 꺼진다.&lt;/li&gt;
&lt;li&gt;웹 사이트가 꺼지면 대기 중이던 웹 사이트가 켜진다.&lt;/li&gt;
&lt;li&gt;웹 사이트가 5개가 켜지면 웹 사이트는 대기 상태에 들어간다.&lt;/li&gt;
&lt;li&gt;웹 사이트가 5개 이하가 켜져 있으면 대기 중이던 웹 사이트가 켜진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;아래 코드를 복사해서 실습해 보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Computer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebBrowser&lt;/span&gt; webBrowser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebBrowser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt; threadA &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Google&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; webBrowser&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt; threadB &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Naver&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; webBrowser&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt; threadC &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Daum&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; webBrowser&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt; closer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSiteCloser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webBrowser&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        closer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        threadA&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        threadB&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        threadC&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSite&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Runnable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; webSiteName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebBrowser&lt;/span&gt; webBrowser&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; webSiteName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebBrowser&lt;/span&gt; webBrowser&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;webSiteName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; webSiteName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;webBrowser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; webBrowser&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                webBrowser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createNewTab&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSiteName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebBrowser&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SPACE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; webSiteNames &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; webSiteIndexes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; maxWebCount&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; webSiteIndex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebBrowser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; maxWebCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maxWebCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; maxWebCount&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createNewTab&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; webSiteName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;full&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;wait&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSiteName &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; 사이트가 켜지는 중입니다...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            webSiteNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSiteName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            webSiteIndexes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;webSiteIndex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;showRunningBrowser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;showRunningBrowser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSiteNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; maxWebCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UnsupportedOperationException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;현재 브라우저 탭이 6개 이상 활성화 되어 강제 종료합니다...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;┌───────────────────────────────────────────────────────────────────────────────┐&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;│ ◆ Wilder Web Browser                                                    - □ x │&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;└───────────────────────────────────────────────────────────────────────────────┘&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;StringBuilder&lt;/span&gt; browserNameLine &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StringBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;StringBuilder&lt;/span&gt; browserUnderLine &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StringBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSiteNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            browserNameLine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;│&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            browserUnderLine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;└&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; webSiteNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            browserNameLine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateWebSiteName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSiteNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateWebSiteIndex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSiteIndexes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot; │&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            browserUnderLine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;───────────────┘&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;browserNameLine&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;browserUnderLine&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateWebSiteName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;StringBuilder&lt;/span&gt; builder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StringBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; space &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; interval &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; space &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SPACE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;repeat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interval&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SPACE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;repeat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; interval &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateWebSiteIndex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;index &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;00&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;index &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isNotEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; webSiteNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hasSpace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; webSiteNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; maxWebCount&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;full&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSpace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;removeAndNotify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isNotEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webSiteNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; 사이트가 종료됩니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            webSiteNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            webSiteIndexes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;showRunningBrowser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;notify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSiteCloser&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Runnable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebBrowser&lt;/span&gt; webBrowser&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSiteCloser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WebBrowser&lt;/span&gt; webBrowser&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;webBrowser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; webBrowser&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                webBrowser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeAndNotify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/49058669/138548124-00a19c52-8959-4b16-8055-b0c25b5ffd91.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;5개를 넘지 않는 선에서 새로운 탭이 빈자리가 생길 때마다 추가되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;이렇게 조건 변수를 이용하여 동기화를 해보는 경험까지 해봤다. 적절한 예시는 아닐 수 있지만, 이 글의 핵심은 어떻게, 어떤 방식으로 Java 에서 동기화를 처리할 수 있는지를 알아보는 시간이었다.&lt;/p&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;Java 에서 제공되는 모니터는 손쉽게 상호배제를 할 수 있도록 해준다. 대부분 멀티 스레드를 제공하기 때문에 스레드 세이프 하지 않은 환경에서는 동기화를 사용해야 한다. 예를 들면 웹 애플리케이션은 멀티 스레드를 사용한다. 이때 싱글톤 패톤(단 하나의 인스턴스만 만드는 패턴)을 사용한다면 처음 싱글톤을 생성할 때 스레드 세이프 하지 않다. 이 경우에 동기화가 필요하다. 이처럼 동기화가 필요한 상황이 종종 발생하기 때문에 이때 모니터를 활용하여 쉽게 동기화를 사용하면 된다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html&quot;&gt;Synchronized Method Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/7/docs/api/javax/management/monitor/Monitor.html&quot;&gt;Monitor Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://coding-start.tistory.com/201&quot;&gt;운영체제 - 상호배제와 동기화(뮤텍스,TAS,세마포어,모니터)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://javafactory.tistory.com/1535&quot;&gt;JAVA] WAIT()와 NOTIFY()&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[초심자를 위한 React Testing Library]]></title><description><![CDATA[React Testing Library(이하 RTL)는 구현 기반의 테스트 도구인 Enzyme의 대안으로 자리 잡은 테스트 도구입니다. 따라서 RTL…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-10-22-react-testing-library/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-10-22-react-testing-library/</guid><pubDate>Fri, 22 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;React Testing Library(이하 RTL)는 구현 기반의 테스트 도구인 Enzyme의 대안으로 자리 잡은 테스트 도구입니다. 따라서 RTL은 세부적인 구현사항보다는 실제 사용자 경험과 유사한 방식의 테스트를 작성할 것을 권고합니다. 예를 들어 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;div&gt;Hello World&amp;lt;/div&gt;&lt;/code&gt;라는 코드가 있다면, RTL은 &lt;code class=&quot;language-text&quot;&gt;div&lt;/code&gt; 태그를 사용하는지보다 &lt;code class=&quot;language-text&quot;&gt;Hello World&lt;/code&gt; 메시지가 브라우저에 노출이 되는지 파악하는 것을 더 중요하다고 봅니다. 이와 같이 구현보다 기능에 초점을 맞춘 테스트 방식은 신뢰도를 높임과 동시에 코드 리팩토링 시 테스트 코드 수정 빈도를 줄일 수 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The more your tests resemble the way your software is used, the more confidence they can give you. - RTL 공식문서&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;RTL은 이름 그대로 React 컴포넌트를 테스트 하기 위해 만들어진 도구이기 때문에, 기본적으로 CRA에 내장되어 있습니다. 만약 CRA를 사용하지 않고 개발 환경을 직접 설정한다면 &lt;code class=&quot;language-text&quot;&gt;npm install --save-dev @testing-library/react&lt;/code&gt; 명령어를 이용해 설치하면 됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;RTL을 처음 접할 때면 RTL을 Jest의 대안으로 혼동하는 경우가 더러 있는데요, 두 도구는 React 내에서 테스트를 진행할 때 같이 사용되기에 상호 보완 관계라고 볼 수 있습니다. (엄밀히 말하자면, RTL이 Jest를 포함하는 구조입니다.) 전반적으로 Jest를 통해 기능 테스트를 진행할 수는 있지만, React의 컴포넌트를 렌더링하고 테스트하기 위해서는 몇가지 기능이 더 필요하기 때문입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Jest - 자체적인 test runner와 test util 제공&lt;/li&gt;
&lt;li&gt;RTL - Jest + React 컴포넌트 test util 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그렇기에 리액트 내부의 테스트 코드 작성 방식은 Jest의 방식과 상당 부분 유사합니다.&lt;/p&gt;
&lt;h2&gt;테스트 코드 작성해보기&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Counter&lt;/code&gt; 컴포넌트를 테스트 하며 테스트 코드를 작성할 때 가장 기초가 되는 부분부터 알아보겠습니다. TDD는 테스트 코드부터 짜는 것이 정석이지만, 이해를 돕기 위해 앱 코드와 같이 진행하겠습니다. 우선은 &lt;code class=&quot;language-text&quot;&gt;Counter&lt;/code&gt; 컴포넌트의 테스트 항목부터 정의해보려고 하는 데요, 이렇게 요구 사항을 먼저 파악해두면 추후 테스트 코드 뿐만 아니라 컴포넌트가 제공할 기능을 더 명확한 단위로 구현할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;컴포넌트가 렌더링 되는지&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;+&lt;/code&gt; 버튼을 눌렀을 때 &lt;code class=&quot;language-text&quot;&gt;count&lt;/code&gt;가 증가하는지&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;-&lt;/code&gt; 버튼을 눌렀을 때 &lt;code class=&quot;language-text&quot;&gt;count&lt;/code&gt;가 감소하는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;요구사항을 도출 했으니 해당 기능을 단계 별로 테스트해보려 합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;코드 환경은 &lt;a href=&quot;https://create-react-app.dev/docs/getting-started/&quot;&gt;CRA&lt;/a&gt;로 구성했고 앱 코드와 테스트 코드는 모두 &lt;code class=&quot;language-text&quot;&gt;src&lt;/code&gt; 폴더 내에서 작성했습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Counter.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;react&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Counter&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setCount&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;decrement&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;increment&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;decrement&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;increment&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; Counter&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Counter.test.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; render &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@testing-library/react&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Counter &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./Counter&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Counter test&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;should render Counter&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Counter&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;test runner는 테스트 실행 시 &lt;code class=&quot;language-text&quot;&gt;*.test.js&lt;/code&gt; suffix를 가진 파일을 자동으로 탐색하며 테스트를 진행합니다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;describe&lt;/code&gt; - 같은 맥락의 테스트들을 그룹화합니다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;it&lt;/code&gt; - 개별 테스트를 수행합니다. &lt;a href=&quot;https://jestjs.io/docs/api#testname-fn-timeout&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;test&lt;/code&gt; 메서드와 동일합니다.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;render&lt;/code&gt; - 테스트를 위해 특정 컴포넌트를 jsdom에 렌더링합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;테스트를 단위 별로 끊어보면 대개 &lt;code class=&quot;language-text&quot;&gt;컴포넌트를 띄우고 → 특정 액션을 발생시킨 후 → 결과를 확인&lt;/code&gt;하는 패턴으로 이루어집니다. 이 패턴을 충족하기 위해 어떤 API들이 필요한지 차근차근 살펴 보겠습니다.&lt;/p&gt;
&lt;h3&gt;Query&lt;/h3&gt;
&lt;p&gt;렌더링 된 DOM 노드에 접근하여 엘리먼트를 가져오는 메서드입니다. 다양한 메서드가 존재하지만 사용 방법은 어렵지 않습니다. 예컨대 &lt;code class=&quot;language-text&quot;&gt;getAllByRole&lt;/code&gt; 메서드를 섹션 별로 나누어 보면&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;get&lt;/code&gt;(쿼리 타입)/&lt;code class=&quot;language-text&quot;&gt;All&lt;/code&gt;(타겟의 개수)/&lt;code class=&quot;language-text&quot;&gt;ByRole&lt;/code&gt;(타겟 유형)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 되는데요. 각 섹션에 들어갈 수 있는 타입은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;쿼리 타입&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;get&lt;/code&gt; - 동기적으로 처리되며 타겟을 찾지 못할 시 에러를 던집니다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;find&lt;/code&gt; - 비동기적으로 처리되며 타겟을 찾지 못할 시 에러를 던집니다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;query&lt;/code&gt; - 동기적으로 처리되며 타겟을 찾지 못할 시 &lt;code class=&quot;language-text&quot;&gt;null&lt;/code&gt;을 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;코드를 작성하다보면 이벤트, 데이터 fetching 등 비동기적인 처리가 필요하거나 해당 타겟을 찾지 못하더라도 에러를 던지지 않게끔 처리해야하는 경우가 있습니다. 때문에 각 경우에 맞게 쿼리 타입을 지정해줘야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;타겟의 개수&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;만약 다수의 엘리먼트가 탐색되는 상황이라면 뒤에 &lt;code class=&quot;language-text&quot;&gt;All&lt;/code&gt;을 붙입니다. (붙이지 않을 시 에러)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;타겟 유형 (우선 순위 별 배치)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;-ByRole&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;-ByLabelText&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;-ByPlaceholderText&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;-ByText&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;-ByDisplayValue&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;-ByAltText&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;-ByTitle&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;-ByTestId&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;다시 &lt;code class=&quot;language-text&quot;&gt;Counter&lt;/code&gt; 예시로 돌아가 이벤트 수행을 위한 타겟 엘리먼트를 얻어오겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; render&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; screen &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@testing-library/react&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Counter &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./Counter&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Counter test&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;should render Counter&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Counter&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 두 쿼리 모두 같은 element 탐색(문자열 대신 정규식 탐색도 가능)&lt;/span&gt;
    screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByRole&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;button&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;+&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;+&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;여기서 유의할 점은, 공식문서에서 &lt;code class=&quot;language-text&quot;&gt;getByRole&lt;/code&gt;을 권장한다고 해서 테스트를 위해 &lt;code class=&quot;language-text&quot;&gt;role&lt;/code&gt;을 억지로 선언할 필요는 없습니다. 기본적으로 몇몇 HTML 시맨틱 태그는 이미 implicit role을 가지고 있기 때문에 해당 role과 두번째 인자로 들어가는 옵션 객체를 통해 찾고자 하는 타입을 좁힐 수 있습니다. 만약 implicit role 파악이 어렵다면 &lt;a href=&quot;https://twitter.com/rwieruch/status/1260912349978013696&quot;&gt;RTL 에러 로그를 통해 현재 DOM 노드 내에서 사용 가능한 role을 제안&lt;/a&gt;받을 수 있습니다. 또 위에서 나열한 다른 메서드를 통해 타겟을 얻는 방법을 적절히 취사선택 하면 됩니다.&lt;/p&gt;
&lt;h3&gt;Action&lt;/h3&gt;
&lt;p&gt;사용자가 브라우저에서 이벤트를 발생시키는 것처럼, RTL은 얻어온 타겟을 이용해 이벤트를 발동시킬 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; fireEvent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; render&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; screen &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@testing-library/react&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; userEvent &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@testing-library/user-event&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;should render Counter&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Counter&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; target &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByRole&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;button&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;+&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 두 api 모두 이벤트 호출 용도로 사용됩니다.&lt;/span&gt;
  fireEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  userEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이벤트를 호출하는 두 api 중 되도록 &lt;a href=&quot;https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-using-testing-libraryuser-event&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;userEvent&lt;/code&gt;를 사용할 것을 권장&lt;/a&gt;하는 데요, 이 api는 내부적으로 &lt;code class=&quot;language-text&quot;&gt;fireEvent&lt;/code&gt;를 사용하며 실제 유저의 행동과 흡사한 기능을 추가로 제공하기 때문입니다.&lt;/p&gt;
&lt;h3&gt;Assertion&lt;/h3&gt;
&lt;p&gt;우리는 현재까지 &lt;code class=&quot;language-text&quot;&gt;1) Counter를 렌더링하고&lt;/code&gt; &lt;code class=&quot;language-text&quot;&gt;2) 타겟을 얻어 특정 이벤트를 발동&lt;/code&gt; 시켰습니다. 이로 인해 카운터의 값이 증가했으니 사용자는 브라우저에 &lt;code class=&quot;language-text&quot;&gt;1&lt;/code&gt;이라는 결과값이 노출 되기를 기대할 것입니다. 이때 &lt;code class=&quot;language-text&quot;&gt;expect&lt;/code&gt; 메서드에 타겟을 인자로 넘겨 사용할 수 있으며 유효성을 검증하는 방법들은 &lt;a href=&quot;https://jestjs.io/docs/expect&quot;&gt;여기&lt;/a&gt;서 확인할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; render&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; screen&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; waitFor &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@testing-library/react&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; userEvent &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@testing-library/user-event&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;should render Counter&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Counter&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; target &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByRole&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;button&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;+&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  userEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBeTruthy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Asynchronous Test&lt;/h3&gt;
&lt;p&gt;컴포넌트 내부의 동작은 항상 동기적으로만 이루어지는 것은 아닙니다. &lt;code class=&quot;language-text&quot;&gt;axios&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;fetch&lt;/code&gt;, 그 외의 Web API를 통해 다양한 비동기 상황을 접할 수 있는데요. 이번엔 가상으로 데이터를 불러오는 &lt;code class=&quot;language-text&quot;&gt;Fetch&lt;/code&gt; 컴포넌트와 그 테스트 코드를 작성해보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Fetch.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useEffect&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;react&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;mockFetch&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;resolve&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Person1&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;2&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Person2&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Fetch&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;loading&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setLoading&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setResult&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;loadResult&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fetchedResult &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mockFetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token function&quot;&gt;setResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fetchedResult&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;setLoading&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;loadResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;loading &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Loading&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;result &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ul&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;li&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;li&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;ul&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; Fetch&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Fetch.test.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@testing-library/jest-dom&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; render&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; screen &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@testing-library/react&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Fetch &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./Fetch&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Fetch&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;should load result&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Fetch&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Loading&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBeInTheDocument&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 1)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAllByRole&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;listitem&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toHaveLength&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 2)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;waitFor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAllByRole&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;listitem&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toHaveLength&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Fetch&lt;/code&gt; 컴포넌트는 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;div&gt;Loading&amp;lt;/div&gt;&lt;/code&gt;를 먼저 렌더링하고 데이터를 불러오면 해당 데이터를 렌더링합니다. 따라서 각 시점에 맞게 assertion을 작성해주어야 합니다. 데이터를 확인하는 방법은 위에서 작성한 것처럼 두가지의 방법을 택할 수 있지만, 가독성을 위해 &lt;code class=&quot;language-text&quot;&gt;find&lt;/code&gt; 메서드를 사용할 것을 권장하는 추세입니다. 사실 이 외에도 &lt;code class=&quot;language-text&quot;&gt;axios&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;fetch&lt;/code&gt;를 통한 api 요청 테스트를 위해 다양한 방법론들이 존재하지만 이 글에서 다루기엔 너무 글이 길어질 것 같아 하단 참조에 첨부하도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;프론트엔드 쪽에서 테스트를 할 때는 ‘백엔드와 api 통신이 성공적으로 이루어지는지’가 아닌 ‘api 응답 결과(대기, 성공, 실패)에 따라 컴포넌트가 어떻게 반응하는지’에 초점을 두고 테스트 전략을 짜는 것이 바람직합니다. 즉, 테스트의 성공 여부가 외부 환경에 의존성을 두지 않게끔 그 범위를 명확하게 가져가는 것이 좋습니다.&lt;/p&gt;
&lt;h3&gt;Summary&lt;/h3&gt;
&lt;p&gt;테스트 코드를 작성하다 보면 테스트를 어디까지 진행해야할지 의문이 들 때가 더러 있습니다. 수시로 바뀌는 기획으로 인해 테스트 자체가 무의미 해질 수도 있고, 때로는 테스트를 위한 테스트를 진행한다는 느낌을 받기 때문입니다. 그럼에도 불구하고 테스트의 중요성을 무시할 수는 없기에 스스로 점검하며 적절한 중간점을 찾으려는 자세가 필요합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;앱의 사용자 입장에서 반드시 확인해야 하는 항목은 무엇인지?&lt;/li&gt;
&lt;li&gt;단순히 커버리지를 높이기 위해 테스트를 진행하고 있는 건 아닌지?&lt;/li&gt;
&lt;li&gt;etc..&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이렇게 최소한의 원칙을 두고 테스트 코드를 작성한다면 사전에 불필요한 비용을 막을 수 있지 않을까 생각합니다. 😊&lt;/p&gt;
&lt;h3&gt;Reference&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.robinwieruch.de/react-testing-library&quot;&gt;How to use React Testing Library Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-using-testing-library-eslint-plugins&quot;&gt;Common mistakes with React Testing Library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://testing-library.com/docs/&quot;&gt;React Testing Library Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.danieljcafonso.com/how_to_test_api_calls&quot;&gt;How to test API calls?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.danieljcafonso.com/configuring_msw&quot;&gt;Configuring Mock Service Worker (MSW)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[refresh token 도입기]]></title><description><![CDATA[❗ SSR 상에서 refresh token을 도입하면서 느낀 것들을 작성한 글입니다. ❗ SSR에서 로그인이 어떻게 이루어지는지 궁금하시면 여기를 참고해주세요! 도입 계기 - 2시간이 지나면 로그인이 풀린다! 현재 진행하고 있는 놀토 앱은 access…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-10-20-refresh-token/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-10-20-refresh-token/</guid><pubDate>Wed, 20 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;❗ SSR 상에서 refresh token을 도입하면서 느낀 것들을 작성한 글입니다.&lt;/li&gt;
&lt;li&gt;❗ SSR에서 로그인이 어떻게 이루어지는지 궁금하시면 &lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-09-10-ssr/&quot;&gt;여기&lt;/a&gt;를 참고해주세요!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;도입 계기 - 2시간이 지나면 로그인이 풀린다!&lt;/h2&gt;
&lt;p&gt;현재 진행하고 있는 &lt;a href=&quot;https://nolto.app/&quot;&gt;놀토 앱&lt;/a&gt;은 access token 유효기간인 2시간을 지나면 무조건 로그아웃되고 있었다. 이 현상은 반드시 고쳐져야 할 이슈였다. 특히 글쓰기 기능에서 치명적이었는데, 글을 쓰면서 화면을 2시간 이상 유지하고 있다가 글 작성하기 버튼을 누르면 기존 글 내용이 전부 날아가기 때문이었다.&lt;/p&gt;
&lt;h3&gt;access token 만료기간을 2주로 늘리면 안 되나요?&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;access token은 JWT이므로 그 자체로 인증 정보를 모두 가지고 있어서 탈취되면 매우 위험한 상황이 발생할 수 있다.&lt;/code&gt; access token은 서버에 저장되지 않으므로(stateless) token이 탈취되면 서버에서는 막을 방법이 없다.&lt;/p&gt;
&lt;p&gt;만료 기간이 2주인 access token을 탈취한 해커는 2주 동안 정상 사용자 행세를 할 수 있어서 access token의 만료 기간은 짧은 것이 좋다.&lt;/p&gt;
&lt;h3&gt;XSS와 CSRF를 잘 막아두면 access token은 절대로 탈취될 일이 없지 않나요?&lt;/h3&gt;
&lt;p&gt;XSS와 CSRF는 웹에서 발생할 수 있는 가장 대표적인 취약점이다. XSS는 자바스크립트 코드를 사용자 브라우저에서 실행하여 악의적인 작업을 수행할 수 있는 공격이고, CSRF는 사용자 브라우저에서 어떤 api로의 요청을 강제함으로써 사용자가 의도하지 않은 작업을 수행하는 공격을 의미한다. 이런 문제를 잘 막아두면 access token이 탈취될 것이라는 가정을 할 필요가 없는 것이 아닐까?&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;하지만&lt;/code&gt; 항상 보안 취약점이 없다고 생각하는 것은 굉장히 위험한 생각이다. react를 사용하면 XSS 공격을 방지해주고, localStorage를 사용하면 CSRF 취약점을 막아주지만, 이것이 항상 통할 것으로 생각하면 안된다. 예를 들어 SSR 방식의 렌더링을 사용할 때, html의 script 태그로 서버의 데이터를 클라이언트로 내려줄 수가 있는데 이런 경우도 XSS 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;그래서 항상 우리가 예측할 수 없는 보안 취약점이 발생할 수 있으므로, 탈취되었을 때를 가정하고 최대한 덜 치명적인 방향으로 개발하는 습관을 가져야 한다.&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;해결책 - refresh token을 도입해보자!&lt;/h2&gt;
&lt;p&gt;refresh token은 access token을 재발급받을 수 있는 token이다. 이 token은 서버에 저장되기 때문에(stateful) refresh token이 해커에 의해 탈취당했다고 판단되었을 때 서버에서 refresh token을 삭제함으로써 강제 로그아웃을 시킬 수 있다.&lt;/p&gt;
&lt;p&gt;이런 특징을 이용해서 access token + refresh token의 조합을 구성하면 &lt;code class=&quot;language-text&quot;&gt;access token의 경제적인 장점&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;refresh token의 보안적인 장점&lt;/code&gt;을 둘 다 챙길 수 있다. access token은 보안 적으로 취약하니 2시간 정도로 짧게 가져가고, refresh token은 처리 비용이 많이 들기 때문에 2주 정도로 길게 가져가는 방식을 주로 사용한다.&lt;/p&gt;
&lt;h2&gt;도입 시나리오&lt;/h2&gt;
&lt;p&gt;refresh token을 프론트 서버의 session에 저장하여 프론트가 관리할 것인지, 백엔드 서버에 저장해 백엔드가 관리할 것인지가 가장 쟁점이었다.&lt;/p&gt;
&lt;p&gt;처음에 필자는 프론트 서버의 session에 저장하게 되면 브라우저에 refresh token을 직접 저장하지 않고, session id를 저장하면 되기 때문에 session으로 관리하는 것이 더 보안상 이점이 있을 것으로 생각했으나, 어차피 session id도 탈취당하면 위험한 것은 refresh token과 마찬가지이므로 둘 중 우월한 방법은 없다고 생각한다. 팀 상황에 맞게 2가지 시나리오 중 하나를 적용하면 좋을 듯하다.&lt;/p&gt;
&lt;h3&gt;시나리오 1) refresh token을 프론트 서버의 session에 저장하는 경우&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/138549716-eeaec98d-c246-42d8-b386-74fe75d4589a.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;refresh token과 access token을 프론트 서버의 session에 저장해두고, 클라이언트가 token이 저장된 session에 접근하기 위해 session id를 쿠키로 가지고 있는 방식이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;새로 고침 상황에서 백엔드 서버까지 요청이 전달되지 않아도 된다.&lt;/p&gt;
&lt;p&gt;프론트 서버 session에 refresh token과 access token이 사용자마다 저장되어 있기 때문에 access token을 얻기 위해 추가로 백엔드 서버로 요청을 보낼 필요가 없다. 요청 횟수에서 경제적이라 할 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;access token 재활용이 가능&lt;/p&gt;
&lt;p&gt;refresh token을 백엔드 서버에 저장하는 경우는 새로 고침 했을 때, access token 만료 기간이 한참 남았음에도 access token을 재발급받는다. 왜냐하면, 프론트 서버를 거쳐 백엔드 서버까지 이미 요청이 도달했기 때문에 access token을 재활용하는 것보다 재발급하는 것이 오히려 경제적일 수 있기 때문이다. 예를 들어 만료 기간이 10초 남은 access token을 재활용해봤자 금새 access token 재발급을 위해서 백엔드 서버로 요청을 보내야 한다. 그래서 새로 고침 시에 백엔드 서버에서 그냥 token을 재발급하는 것이 오히려 더 경제적이다.
&lt;code class=&quot;language-text&quot;&gt;하지만 프론트 서버의 session에 refresh token과 access token을 저장하는 경우는, token을 얻기 위한 요청이 백엔드 서버까지 전달될 필요가 없기 때문에 access token을 재활용함으로써 요청에 드는 비용을 절약할 수 있다.&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;사용자가 많아질 때 session 관리가 힘들다.&lt;/p&gt;
&lt;p&gt;사용자가 많아지게 되면 하나의 WAS를 여러개의 WAS로 나눠 사용량을 분배해야 한다고 한다. 그럼 session도 WAS마다 생겨나는데, session 정보를 요청하는 브라우저 입장에서는 이걸 모르니 WAS 앞 단인 웹서버에서 적절한 WAS의 session에 접근할 수 있도록 잘 연결해주어야 한다. 이 과정이 꽤 복잡하다고 하는데 프론트가 이를 관리하기에는 서버 지식이 깊게 필요하고, 프론트 issue도 많기 때문에 프론트가 서버 issue까지 관리하기가 힘들 것으로 판단된다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;시나리오 2) refresh token을 백엔드 서버에 저장하는 경우&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/138549736-cd90892d-05d1-4b58-9f9a-a087cd969856.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;refresh token을 백엔드 서버에 저장해두고 클라이언트는 refresh token에 대한 정보를 쿠키로 가지고 있는 방식이다. 새로고침했을 때 access token을 html 자원과 함께 받기 위해 반드시 백엔드 서버를 거쳐야한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;서버 issue를 백엔드 서버에서 대부분 처리하기 때문에 관리에 용이하다.&lt;/p&gt;
&lt;p&gt;사용자가 많아짐에 따라 생기는 서버 issue를 백엔드에서 모두 처리하기 때문에 관리 포인트가 프론트 서버, 백엔드 서버 둘로 나뉘지 않고 백엔드 서버 한 곳에서 처리된다. 따라서 서버 issue 관리에 용이하다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;새로 고침하면 항상 프론트 서버를 거쳐 백엔드 서버까지 요청이 전달된다.&lt;/p&gt;
&lt;p&gt;token이 프론트 서버에 따로 저장되지 않기 때문에 access token을 얻기 위해서는 항상 백엔드 서버까지 요청이 전달되어야 한다. 요청 횟수, 요청에 드는 간접비용 등을 고려해봤을 때 비효율적일 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;고려해보면 좋을 사항&lt;/h2&gt;
&lt;h3&gt;보안 - 정상적인 사용자의 ip가 아니라면 refresh token을 삭제하도록 하기&lt;/h3&gt;
&lt;p&gt;앞서 언급한 것처럼 refresh token은 서버에 저장된다. 그래서 보안적인 문제가 생기면 refresh token을 삭제함으로써 특정 사용자를 강제 로그아웃시킬 수 있다. 현재 놀토 앱은 최초 로그인 한 ip를 서버에 저장하고, refresh token을 통한 access token 갱신 요청이 다른 ip로부터 온다면 비정상적인 접근으로 판단하고 refresh token을 삭제하여 강제 로그아웃시킨다.&lt;/p&gt;
&lt;p&gt;그런데 이 방식도 UX적으로 단점이 있다. 요즘 항상 같은 곳에서만 컴퓨터를 사용하는 유저가 얼마나 되겠는가. 지금 글을 작성하는 시점에도 많은 사람이 카페에서 노트북을 사용하고 있는데 ip가 달라진다고 해서 강제로 로그아웃시킨다면 사용자 경험에 좋지 않을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/138549706-500fb0cc-c8d7-4c1c-a409-cfd1e1bf4277.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;네이버는 ip가 달라지는 경우 보안 알림을 사용자에게 띄워 준다. 여기서 “아니요” 를 선택한다면 로그아웃되는 구조로 보인다. 놀토의 다음 도전 과제는 이러한 방식이 아닐까? 언젠간 한번 도입해보고 싶은 방법이다.&lt;/p&gt;
&lt;h3&gt;UX - 사용 중에 access token이 만료된다면 조용히 재발급 받기&lt;/h3&gt;
&lt;p&gt;만약 어떤 사용자가 새로 고침하지 않고 화면을 2시간 이상 띄워놓는 경우를 가정해보자. 가장 흔한 경우는 글을 작성하는 경우가 있다. 이 경우에는 access token이 만료되었다는 사실을 사용자가 모르게 하는 것이 가장 자연스러운 경험일 것이다.&lt;/p&gt;
&lt;p&gt;필자가 생각한 방법은 2가지가 있다. 이 또한 장단이 있으니 상황에 따라 적절히 선택하면 될 듯하다.&lt;/p&gt;
&lt;h4&gt;방법 1) 요청 보낼 때 token 만료 에러가 발생하면 token을 새로 발급받기&lt;/h4&gt;
&lt;p&gt;인가가 필요한 요청을 보낼 때 token 만료 에러가 발생하면 token을 새로 발급받고, 새로 발급받은 token으로 다시 요청을 다시 보내는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;사용자가 인가가 필요한 작업을 요청할 때만 token이 재발급되어 경제적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;인가가 필요한 요청에 대한 에러처리, 재요청 로직이 추가로 요구된다.&lt;/p&gt;
&lt;p&gt;필자는 개인적으로 token 재발급 때문에 요청 로직이 너무 크게 변경되는 것이 부담스러워 일정 시간마다 새로운 token을 발급받는 방식을 사용했다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;token이 만료된 경우 인가가 필요한 요청을 결과적으로 2번 보내야한다.&lt;/p&gt;
&lt;p&gt;token이 만료된다면 첫번째 요청은 실패할 것이고, 새로운 token을 발급받아 두번째 요청을 보내야 한다. 큰 문제가 되지는 않을테지만 단점이라 할 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;방법 2) 일정 시간마다 새로운 access token을 발급 받기&lt;/h4&gt;
&lt;p&gt;access token 만료 기간이 2시간이라면 놀토에서는 1시간 55분 정도에 새로운 token을 발급받고 있다. 이 방법을 사용하면 요청 관련 로직을 수정하지 않아도 되어 무척 편하다. 사용자가 사용하지 않을 때에도 백그라운드에서 계속 새로운 token을 발급받는다는 단점이 있을 수 있지만, 이것도 브라우저 포커스가 되어있는지를 감지해서 최적화도 가능하므로 큰 문제가 되지 않을 것으로 생각한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;setTimeout으로 token 재발급 로직만 작성해주면 요청 관련 로직 수정이 필요 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;시간과 관련하여 token을 재발급 받기 때문에 특정 시간이 지나면 무조건 재발급 요청을 보내어 비효율적일 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;오늘은 refresh token을 도입하면서 겪었던 일들을 글로 작성해보았다. 꽤 오랫동안 보안적인 측면과 UX적인 측면에서 고민을 많이 해보았는데, 지금까지 대형 서비스에서 당연하게 느끼고 있던 인가 관련 처리가 이렇게나 복잡했다니 뒤에서 묵묵히 노력하는 개발자들의 노고가 느껴졌다.&lt;/p&gt;
&lt;p&gt;고생은 했지만 refresh token 도입이 끝나고 나니 여타 대형 서비스와 비슷한 사용자 경험을 놀토 앱이 줄 수 있다니 굉장히 만족스러웠다. 이렇게 사용자 경험적인 측면에서 보람을 느끼며 최근에는 인피니티 스크롤 최적화에도 눈길이 가는데 그것도 시간이 된다면 적용해보고 싶다. 역시 좋은 사용자 경험으로 가는 길에는 끝이 없다. 무던히 노력하자…! 😊&lt;/p&gt;</content:encoded></item><item><title><![CDATA[동기와 비동기 with webClient]]></title><description><![CDATA[‘여기서 만나’ 프로젝트를 진행하면서 초반에는 RestTemplate을 통하여 외부 API와 데이터를 주고받았다. 이후 WebClient가 비동기 방식으로 쓰여 더 좋다고 하여 WebClient로 변경했다. 그리고 이전 글에서 RestTemplate…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-10-20-synchronous-asynchronous/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-10-20-synchronous-asynchronous/</guid><pubDate>Wed, 20 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;‘여기서 만나’ 프로젝트를 진행하면서 초반에는 RestTemplate을 통하여 외부 API와 데이터를 주고받았다. 이후 WebClient가 비동기 방식으로 쓰여 더 좋다고 하여 WebClient로 변경했다. 그리고 이전 글에서 RestTemplate과 WebClient에 대한 비교를 했다. 아마도 “WebClient가 비동기라 동기로 진행되는 RestTemplate보다 훨씬 빠르고 더 좋아” 라는 글이였을 것이다. 하지만 프로젝트를 진행하면서 계속해서 리팩토링을 진행하고 동기, 비동기에 대한 개념을 공부해나가면서 우리 팀이 적용했던 방식은 비동기인 WebClient를 쓰면서 비동기로 동작하지 않도록 구현 하였다. 이번 글에서는 우리 팀이 중간지점을 찾는 ‘여기서 만나’의 핵심 로직을 구현하면서 진짜 비동기 방식으로 WebClient를 사용하는 방법에 관해 이야기 해보려한다. 이 글은 WebClient에 관한 이야기는 아니다. 비동기로 동작 하도록 하기 위해 고군분투 했던 우리 팀의 이야기이다. WebClient의 사용법이 궁금하다면 다른 글을 참고하면 좋을 것 같다.&lt;/p&gt;
&lt;h2&gt;동기와 비동기&lt;/h2&gt;
&lt;p&gt;동기와 비동기는 자주듣는 것 같은데 항상 헷갈린다. 동기와 비동기에 대해서는 JavaScript 미션을 하면서 처음 접했었다. 그때는 동기 비동기에 대한 이해도 없이 단순히 미션을 구현하는 데에만 집중했던 것 같다. 동기와 비동기는 호출한 결과의 완료 여부를 확인을 하는가 안 하는가에 따라 구분 할 수 있다. 동기 비동기에 대한 비유는 흔히 카페에서 주문하는 상황으로 표현이 된다.&lt;/p&gt;
&lt;h3&gt;동기&lt;/h3&gt;
&lt;p&gt;카페에서 커피를 주문한다고 생각해보자. 점원은 한 명 밖에 없는데 주문을 하고 나니 점원이 커피를 바로 줄 테니 기다려라고 한다. 뒤에 사람은 계속 오는데 커피가 나올 때 까지 뒤에 사람은 가만히 뒤에 서 있기만 해야 한다. 이 상황이 동기와 비슷한 상황이다. 동기란 결국 순서대로 실행된다는 것이다. 요청을 보내고 난 뒤 응답이 오기 전까지는 아무것도 할 수가 없다. 자바스크립트에서 모든 경우를 동기 방식으로 처리한다면 나는 웹 서비스를 이용 하고 싶지 않을 것 같다.&lt;/p&gt;
&lt;p&gt;이렇게 말하면 동기 방식이 별로 좋은 점이 없는 것 같다. 하지만 동기 방식은 장점이 많다. 일단 코드를 파악하기가 엄청 쉽다. 무엇보다도 순서대로 실행이 되기 때문에 디버깅도 쉽다. 멀티 스레드로 구현하지 않는 이상 비동기로 구현하는 것은 거의 힘들다.&lt;/p&gt;
&lt;h3&gt;비동기&lt;/h3&gt;
&lt;p&gt;이번에도 카페에서 커피를 주문한다고 생각해보자. 이번에도 커피를 주문하는데 주문을 하고 나니 점원이 진동벨을 건네주며 진동벨이 울리면 가지러 오라고 한다. 주문만 하고 앞에 사람이 빠져 바로 뒤에 기다리던 사람이 주문을 할 수 있다. 이러한 상황이 바로 비동기다.&lt;/p&gt;
&lt;p&gt;싱글스레드에서는 비동기에 대하여 고민하지 않아도 된다. 자바에서 다른 스레드없이 Main함수를 통하여 실행한다면 이는 항상 동기로 동작을 한다. 순수 자바로서만 구현을 한다면 멀티 스레드인 환경에서 동작할때 비동기로 동작이 되는데 이때는 동시성 문제와 동기 처리에 대해 고민해봐야한다. WebClient는 싱글 스레드와 비동기/ non-blocking 방식을 사용할 수 있다. 여기서 주의해야할 점이 “사용할 수 있다” 이다. 우리 팀이 잘못 사용했던 것처럼 비동기/ blocking방식으로 사용할 수도 있다.&lt;/p&gt;
&lt;h2&gt;비동기지만 비동기가 아닌(blocking)&lt;/h2&gt;
&lt;p&gt;중간지점을 찾는 로직을 구현하면서 WebClient를 사용했다. WebClient를 이용하면 외부 API로의 요청을 보내고 응답을 기다리지 않고 다음 요청을 처리한다. 우리가 진행한 프로젝트는 WebFlux 기반이 아닌 Srping MVC  기반으로 동작을 하는데 이때 WebFlux 기반인 WebClient의 반환값인 Mono나 Flux를 처리하기 위해서는 따로 처리를 해줘야 한다. 이 부분에서 비동기로 구현하였지만, 비동기 답게 쓰지 못했다.&lt;/p&gt;
&lt;p&gt;WebClient를 사용하면 당연히 비동기 방식으로 동작을 한다 생각하였다. 비동기니까 중간지점을 찾는 로직은 당연히 빠르겠다고 생각하였다. 또한 비동기니까 요청이 많아도 시간이 얼마 걸리지 않겠지 라고 생각했었다. 하지만 중간지점을 계산하는 로직은 거의 1분이 넘는 시간이 걸렸었다. 물론 계산해야 할 부분이 많아 API 요청이 많아지게 되어 느릴 수 있지만 그렇다고 서비스를 제공하기에는 너무 시간이 걸렸다. 이후 알게 되었지만 우리는 WebClient에 block을 걸어 사용하였고 이는 비동기/ blocking 방식으로 동작하여 비동기지만 시간이 아주 오래 걸리게 되었다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;APIUtilityResponse&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;receivedUtilityResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; categoryCode&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
		&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; webClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uriBuilder &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
            uriBuilder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;BASIC_URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queryParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;x&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queryParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;y&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queryParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;category_group_code&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; categoryCode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queryParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;radius&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;BASIC_DISTANCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queryParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;page&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queryParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sort&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;distance&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MediaType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;APPLICATION_JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;retrieve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bodyToFlux&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;APIUtilityResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findFirst&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GoodDayException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LocationExceptionSet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;KAKAO_SERVER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위 코드를 보면 toStream()을 사용한 것을 볼 수 있다. 위 코드에는 없지만 다른 곳에서는 block()을 아주 당연하게 적용하였다. toStream()이나 block()을 사용하게 되면 결국 결과가 반환될 때 까지 기다리게 되고 결과가 완전히 도착해야 다음 단계로 넘어가게 된다. 이는 비동기지만 blocking이라 할 수 있다.&lt;/p&gt;
&lt;h2&gt;비동기를 비동기 답게(non-blocking)&lt;/h2&gt;
&lt;p&gt;중간지점은 우리 서비스에 핵심 기능이다. 그런데 이 핵심 기능이 1분이나 걸린다는 것은 말도 안 되는일이었다. 문제는 WebClient의 Blocking이였다. WebFlux 기반의 프로젝트라면 WebClient가 반환한 Mono 나 Flux를 통하여 reactive 하게 사용하면 된다. 하지만 이전에도 말했듯이 우리 프로젝트는 Spring MVC 기반이었다. Mono, Flux 를 어쩔 수 없이 우리가 원하는 객체의 형태로 바꿔줘야 했다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;APIUtilityResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findNearbyStations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Points&lt;/span&gt; points&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;APIUtilityResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; nearbyStations &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    points&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPointRegistry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;point &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
        webClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uriBuilder &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
                uriBuilder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;BASIC_URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queryParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;x&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; point&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queryParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;y&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; point&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queryParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;category_group_code&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LocationCategory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SW8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queryParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;radius&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;BASIC_DISTANCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queryParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;page&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queryParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sort&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;distance&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MediaType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;APPLICATION_JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;retrieve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bodyToMono&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;APIUtilityResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; nearbyStations&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;point&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; nearbyStations&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위 코드를 보면 toStream()이나 block()이 없는 것을 볼 수 있다. toStream(), block() 이없다 해도 만약 subscribe() 가 없다면 webClient 요청이 전달되지 않는다. subscribe() 에서 요청이 전달되고 result에 결과 가 담겨 들어온다. nearbyStations에 담긴 value는 실제로 아직 값이 없는 시점이 있다. 정확한 시점을 알 수는 없지만 비동기로 요청을 보냈고 해당 로직을 수행하고 우리가 원하는 데이터가 반환되면 nearByStations에 value로 들어간다. 반환된 nearByStations 안에 APIUtilityResponse를 진짜 사용할 때 값이 있는지 없는지 확인하여 사용한다. 값이 들어오는 것을  기다리지 않고 요청만 보낸 후 다른일을 한다. 나중에 진짜 필요할 때 체크를 하고 사용하는 것이다.&lt;/p&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;동기였던 중간지점을 찾는 로직을 비동기 non-blocking 구조로 바꾸면서 1분이 걸리던 로직이 7&lt;del&gt;8초대로 줄어들었다. 우리 팀의 핵심 로직이 드디어 제대로 구실을 하는 순간이였다. 비동기에 많은 시간을 투자하였다. 이제 진짜 제대로 된 비동기인줄 알았는데 비동기가 아니였다는 걸 깨닫는 과정을 계속 반복하였었다. 이제는 진짜 비동기로 작동하는 것이면 좋겠다. 7&lt;/del&gt;8초대라면 진짜 비동기로 처리하고 있는 것으로 생각해본다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Spring Data Elasticsearch 설정 및 검색 기능 구현]]></title><description><![CDATA[실습 Repository에서 코드를 확인할 수 있습니다. 1. Elasticsearch Elasticsearch는 Apache Lucene 기반의 Java 오픈소스 분산형 RESTful…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-10-19-elasticsearch/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-10-19-elasticsearch/</guid><pubDate>Tue, 19 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/xlffm3/spring-data-elasticsearch-practice&quot;&gt;실습 Repository&lt;/a&gt;에서 코드를 확인할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1. Elasticsearch&lt;/h2&gt;
&lt;p&gt;Elasticsearch는 Apache Lucene 기반의 Java 오픈소스 분산형 RESTful 검색 및 분석 엔진입니다. 방대한 양의 데이터에 대해 실시간으로 저장과 검색 및 분석 등의 작업을 수행할 수 있습니다. 특히 정형 데이터, 비정형 데이터, 지리 데이터 등 모든 타입의 데이터를 처리할 수 있는데요. Elasticsearch는 &lt;strong&gt;JSON 문서(Document)로 데이터를 저장하기 때문입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Elasticsearch는 단독 검색을 위해 사용하거나, &lt;strong&gt;ELK(Elasticsearch &amp;#x26; Logstash &amp;#x26; Kibana) 스택&lt;/strong&gt;을 기반으로 사용합니다.&lt;/p&gt;
&lt;h3&gt;1.1. ELK&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/134549954-b93fedde-f86c-4a31-bae8-9fd02ae7d35f.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;본 글의 주제와는 벗어나지만 ELK에 대해서도 짧게나마 알아봅시다. ELK는 Elasticsearch + Logstash + Kibana를 같이 연동하여 사용한다는 의미입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Filebeat
&lt;ul&gt;
&lt;li&gt;로그를 생성하는 서버에 설치해 로그를 수집합니다.&lt;/li&gt;
&lt;li&gt;Logstash 서버로 로그를 전송합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Logstash
&lt;ul&gt;
&lt;li&gt;로그 및 트랜잭션 데이터를 수집과 집계 및 파싱하여 Elasticsearch로 전달합니다.&lt;/li&gt;
&lt;li&gt;정제 및 전처리를 담당합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Elasticsearch
&lt;ul&gt;
&lt;li&gt;Logstash로부터 전달받은 데이터를 저장하고, 검색 및 집계 등의 기능을 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Kibana
&lt;ul&gt;
&lt;li&gt;저장된 로그를 Elasticsearch의 빠른 검색을 통해 가져오며, 이를 시각화 및 모니터링하는 기능을 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2. RDB 비교&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/134550384-69294c02-20c5-4627-a832-ab0713c55e0b.png&quot; alt=&quot;image&quot;&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/134551520-b15876b6-9216-433c-a336-81a293dd828c.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;Elasticsearch는 데이터를 행렬 데이터로 저장하는 것이 아니라, **JSON 문서(Document)**로 직렬화된 복잡한 자료 구조를 저장하는 방식을 채택하고 있습니다. 따라서 기존 RDB에서 사용하던 용어를 그대로 사용하지 않습니다. 단, 그에 대응하는 적합한 용어들이 존재하니 이 글을 읽는데 어려움 없으시길 바라겠습니다. 😅&lt;/p&gt;
&lt;h3&gt;1.3. Inverted Index&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/134555327-82533784-030d-425b-8683-ee33a728b5fd.png&quot; alt=&quot;image&quot;&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/134555053-daa5f704-5f52-47dc-aa07-0de3bea33e78.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Elasticsearch는 특정 문장을 입력받으면, 파싱을 통해 문장을 단어 단위로 분리하여 저장합니다. 또한 대문자를 소문자로 치환하거나 유사어 체크 등의 추가 작업을 통해 텍스트를 저장합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Elasticsearch는 &lt;strong&gt;역 색인&lt;/strong&gt;이라고 하는 자료 구조를 사용하는데, 이는 전문 검색에 있어서 빠른 성능을 보장합니다. 책의 전반부에 위치한 일반적인 목차가 Index라면, 책 후반부에 키워드마다 내용을 찾아볼 수 있도록 돕는 목차가 Reverted Index입니다.&lt;/p&gt;
&lt;p&gt;역 색인은 각 Document에 등장하는 모든 고유한 단어들을 리스트업하고, 해당 단어들이 등장하는 Document들을 식별합니다. 색인은 최적화된 Document 컬렉션이며, 각 Document는 &lt;strong&gt;데이터를 포함하고 있는 Key-Value 쌍으로 이루어진 Field&lt;/strong&gt;의 컬렉션입니다.&lt;/p&gt;
&lt;p&gt;Elasticsearch는 모든 Field의 데이터를 인덱싱하는데, 인덱싱된 Field는 각각의 최적화된 자료구조를 사용합니다. 텍스트 형식의 Field는 Inverted Index에 저장되며, 숫자 혹은 지리 관련 Field는 BKD 트리에 저장됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/135963544-3a9c0ae0-89e8-464f-a4c2-ce3a9095003e.png&quot; alt=&quot;image&quot;&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/135963557-e18dac48-aac9-4c43-b4c5-7ce1fd3d4d68.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;RDB는 데이터 수정·삭제의 편의성과 속도 면에서 강점이 있지만 다양한 조건의 데이터를 검색하고 집계하는 데에는 구조적인 한계가 존재합니다. 특정 단어 검색시 ROW 개수만큼 확인을 반복하기 때문입니다. 반면 단어 기반으로 데이터를 저장하는 Elasticsearch는 특정 단어가 어디에 저장되어 있는지 이미 알고 있어 모든 Document를 검색할 필요가 없습니다.&lt;/p&gt;
&lt;p&gt;반면 수정과 삭제는 내부적으로 굉장히 많은 리소스가 소요되는 작업이라, RDBMS를 대체하기 어렵습니다.&lt;/p&gt;
&lt;h3&gt;1.4. Architecture&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/134556753-5133f373-7814-4c4f-8505-6dfbe9f47ed9.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;하나의 클러스터 내 복수 개의 노드를 사용하는 경우, 저장된 Document는 클러스터 전역으로 분배되기 때문에 어느 노드에서든 즉시 접근이 가능합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Cluster
&lt;ul&gt;
&lt;li&gt;최소 하나 이상의 노드로 이루어진 노드들의 집합을 의미합니다.&lt;/li&gt;
&lt;li&gt;서로 다른 클러스터는 데이터의 접근 및 교환을 할 수 없는 독립적인 시스템으로 유지됩니다.&lt;/li&gt;
&lt;li&gt;여러 대의 서버가 하나의 클러스터를 구성하거나, 하나의 서버에 여러 개의 클러스터가 존재할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Node
&lt;ul&gt;
&lt;li&gt;Elasticsearch를 구성하는 하나의 단위 프로세스를 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Shard
&lt;ul&gt;
&lt;li&gt;데이터를 분산해서 저장하는 방법을 의미합니다.&lt;/li&gt;
&lt;li&gt;Scale-Out을 위해 RDB의 Database에 해당하는 Index를 여러 Shard로 쪼갭니다.&lt;/li&gt;
&lt;li&gt;기본적으로 1개가 존재하며, 검색 성능 향상을 위해 클러스터의 Shard 개수를 조정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Replica
&lt;ul&gt;
&lt;li&gt;또 다른 형태의 Shard를 의미합니다.&lt;/li&gt;
&lt;li&gt;노드를 손실했을 경우, 데이터의 신뢰성을 위해 Shard를 복제하는 것입니다.&lt;/li&gt;
&lt;li&gt;따라서 Replica는 서로 다른 노드에 위치시킬 것을 권장하고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;추가적으로, Node는 &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html&quot;&gt;다양한 역할&lt;/a&gt;로 분류할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;대규모 클러스터에서 로드 밸런싱 역할을 하는 노드.&lt;/li&gt;
&lt;li&gt;데이터 변환 등 사전 처리 파이프라인 노드.&lt;/li&gt;
&lt;li&gt;색인된 데이터 CRUD 노드.&lt;/li&gt;
&lt;li&gt;메타 데이터 등 전체 클러스터를 제어하는 마스터 노드.
&lt;ul&gt;
&lt;li&gt;인덱스 생성과 삭제.&lt;/li&gt;
&lt;li&gt;데이터 입력시 샤딩 할당.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;단일 노드로 Elasticsearch를 구동할 수 있지만, 트래픽이 많아진다면 노드별로 서버를 분리하거나 작업 노드에 대해 Scale-Out 및 로드 밸런싱을 함으로써 성능을 향상시킬 수 있습니다.&lt;/p&gt;
&lt;h3&gt;1.5. 특징&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Scale out : Shard를 통해 규모가 수평적으로 늘어날 수 있습니다.&lt;/li&gt;
&lt;li&gt;고가용성 : Replica를 통해 데이터의 안정성을 보장하고, 단일 장애점을 극복합니다.&lt;/li&gt;
&lt;li&gt;Schema Free : Json 문서를 통해 데이터를 검색하므로, 스키마의 개념이 없습니다.&lt;/li&gt;
&lt;li&gt;RESTful : CRUD 작업은 RESTful API를 통해 수행되며, 각각이 HTTP의 PUT / GET / POST / DELETE 메서드에 대응됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;
&lt;h2&gt;2. Spring Data Elasticsearch&lt;/h2&gt;
&lt;p&gt;Spring Data Elasticsearch 프로젝트는 Elasticsearch 검색 엔진을 사용하는 솔루션 개발을 도와주는 모듈입니다. 다음 작업에 대해 높은 수준의 추상화를 템플릿으로 제공하고 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Document의 저장과 검색 및 정렬.&lt;/li&gt;
&lt;li&gt;Document를 Aggregate로 재구성.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Spring Data JPA가 Repository 인터페이스에 정의한 메서드 이름을 분석해서 JPQL을 자동으로 생성 및 실행해주는 것처럼, Spring Data Elasticsearch 또한 Repository 인터페이스에 메서드를 정의함으로써 쿼리를 표현할 수 있습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;3. Docker를 활용한 Elasticsearch 설치&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Shell&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; pull docker.elastic.co/elasticsearch/elasticsearch:7.10.0
$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9200&lt;/span&gt;:9200 &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9300&lt;/span&gt;:9300 &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;discovery.type=single-node&quot;&lt;/span&gt; docker.elastic.co/elasticsearch/elasticsearch:7.10.0&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다양한 설치 방법이 있지만 그 중 가장 간편한 Docker를 사용했습니다. 해당 예제는 단일 노드로 클러스터를 구성하는데, 클러스터를 멀티 노드로 구성하고 싶다면 &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html#docker-compose-file&quot;&gt;docker-compose를 활용하시면 됩니다.&lt;/a&gt;
9200 포트는 HTTP 클라이언트와 통신에 사용되며, 9300 포트는 노드들간 통신할 때 사용됩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;최신 공식 문서 기준 Spring Data Elasticsearch 4.2.x 버전이 Elasticsearch 7.10.0. 버전과 호환되기 때문에 7.10.0 버전으로 이미지를 받습니다.&lt;/li&gt;
&lt;li&gt;다른 버전을 사용하고 싶다면 &lt;a href=&quot;https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#new-features&quot;&gt;Srping Data Elasticsearch 프로젝트 버전 및 Elasticsearch 버전 호환 유무&lt;/a&gt;를 꼭 확인하도록 합시다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2&gt;4. Spring Boot 설정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;build.gradle&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;dependencies &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    implementation &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.boot:spring-boot-starter-data-JPA&apos;&lt;/span&gt;
    implementation &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.boot:spring-boot-starter-web&apos;&lt;/span&gt;
    implementation &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.data:spring-data-elasticsearch:4.2.2&apos;&lt;/span&gt;
    compileOnly &lt;span class=&quot;token string&quot;&gt;&apos;org.projectlombok:lombok&apos;&lt;/span&gt;
    runtimeOnly &lt;span class=&quot;token string&quot;&gt;&apos;com.h2database:h2&apos;&lt;/span&gt;
    annotationProcessor &lt;span class=&quot;token string&quot;&gt;&apos;org.projectlombok:lombok&apos;&lt;/span&gt;
    testImplementation &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.boot:spring-boot-starter-test&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;현재 사용 중인 Elasticsearch 버전과 호환되게 Spring Data Elasticsearch 4.2.x 버전으로 의존성을 받습니다.&lt;/p&gt;
&lt;h3&gt;4.1. Elasticsearch Clients 설정&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;ElasticsearchOperations.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ElasticsearchOperations&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DocumentOperations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SearchOperations&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Elasticsearch 관련 작업을 수행할 때 주로 ElasticsearchOperations 인터페이스의 구현체를 사용합니다. 해당 인터페이스는 DocumentOperations 및 SearchOperations 인터페이스를 확장하고 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AbstractElasticsearchConfiguration.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AbstractElasticsearchConfiguration&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ElasticsearchConfigurationSupport&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RestHighLevelClient&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;elasticsearchClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;elasticsearchOperations&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;elasticsearchTemplate&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ElasticsearchOperations&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;elasticsearchOperations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElasticsearchConverter&lt;/span&gt; elasticsearchConverter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token class-name&quot;&gt;RestHighLevelClient&lt;/span&gt; elasticsearchClient&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

		&lt;span class=&quot;token class-name&quot;&gt;ElasticsearchRestTemplate&lt;/span&gt; template &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ElasticsearchRestTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;elasticsearchClient&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; elasticsearchConverter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		template&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setRefreshPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;refreshPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; template&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;AbstractElasticsearchConfiguration 클래스가 ElasticsearchOperations을 Bean으로 등록하고 있습니다. 구현체는 ElasticsearchRestTemplate이네요. 해당 부분을 개발자가 커스텀하게 Bean 등록을 해주면 됩니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ElasticSearchConfig.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ElasticSearchConfig&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AbstractElasticsearchConfiguration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RestHighLevelClient&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;elasticsearchClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ClientConfiguration&lt;/span&gt; clientConfiguration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClientConfiguration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connectedTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;localhost:9200&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RestClients&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;clientConfiguration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Spring Data Elasticsearch는 ElasticsearchClient를 통해 Elasticsearch 노드 혹은 클러스터와 연결됩니다. &lt;strong&gt;ElasticsearchClient를 직접 사용할 수 있지만, 대게 더 추상화된 ElasticsearchOperations 혹은 ElasticsearchRepository를 사용합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ElasticsearchClient 구현체는 리액티브 지원 여부 등에 따라 종류가 다양한데, RestHighLevelClient가 일반적입니다. ElasticsearchOperations 구현체가 사용하는 RestHighLevelClient만 Bean으로 등록해주면 설정이 완료되었습니다.&lt;/p&gt;
&lt;h3&gt;4.2. Logging&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;application.properties&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;properties&quot;&gt;&lt;pre class=&quot;language-properties&quot;&gt;&lt;code class=&quot;language-properties&quot;&gt;&lt;span class=&quot;token key attr-name&quot;&gt;logging.level.org.springframework.data.elasticsearch.client.WIRE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;TRACE&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Spring Data Elasticsearch가 제대로 동작하는지 확인하고 싶다면, 위 로거를 등록합니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;5. 예제 코드&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;User.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Document&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;indexName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;users&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Embedded&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BasicProfile&lt;/span&gt; basicProfile&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; description&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BasicProfile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; description&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@PersistenceConstructor&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BasicProfile&lt;/span&gt; basicProfile&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;basicProfile &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; basicProfile&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// getter 생략&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;BasicProfile.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Embeddable&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BasicProfile&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nullable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; unique &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; description&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BasicProfile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BasicProfile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; description&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; description&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// getter 생략&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;과거의 Spring Data Elasticsearch는 Jackson 기반으로 데이터를 직렬화해 JSON으로 맵핑했는데, 현재는 Meta Object Mapping 방식을 사용합니다. JPA에서 @Entity 애너테이션을 통해 특정 클래스가 RDB에 저장될 테이블임을 명시한 것 처럼, @Document 애너테이션으로 해당 클래스가 ES DB에 맵핑될 클래스임을 표기합니다.&lt;/p&gt;
&lt;p&gt;또한 @PersistenceConstructor가 부착된 생성자통해 ES DB에 저장된 Document가 Aggregate로 재구성됩니다. 그 외 기타 애너테이션 및 설정들은 공식 문서를 확인하길 바랍니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ElasticsearchRepository.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@NoRepositoryBean&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ElasticsearchRepository&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ID&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PagingAndSortingRepository&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ID&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;token class-name&quot;&gt;Page&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;searchSimilar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; entity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@Nullable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; fields&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Spring Data JPA에서 사용자 정의 Repository 인터페이스를 정의할 때 JpaRepository 인터페이스를 확장한 것처럼, ElasticsearchRepository 인터페이스를 확장해 정의하면 됩니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;UserRepository.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserSearchRepository&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ElasticsearchRepository&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomUserSearchRepository&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findByBasicProfile_NameContains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Spring Data JPA처럼 메서드 이름을 기반으로 CRUD 명령 쿼리를 생성할 수 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ElasticSearchConfig.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableElasticsearchRepositories&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ElasticSearchConfig&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AbstractElasticsearchConfiguration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;@EnableElasticsearchRepositories 애너테이션을 부착해줍니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;CustomUserSearchRepositoryImpl.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomUserSearchRepositoryImpl&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomUserSearchRepository&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ElasticsearchOperations&lt;/span&gt; elasticsearchOperations&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;searchByName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Criteria&lt;/span&gt; criteria &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Criteria&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;basicProfile.name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Query&lt;/span&gt; query &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CriteriaQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;criteria&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPageable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;SearchHits&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; search &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; elasticsearchOperations&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;query&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; search&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SearchHit&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Collectors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;복잡한 쿼리를 직접 다뤄야 한다면 ElasticsearchOperations Bean을 주입받아 커스텀하게 쿼리를 작성할 수 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;UserController.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/api&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt; userService&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@PostMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/users&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestBody&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserRequest&lt;/span&gt; userRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;UserRequestDto&lt;/span&gt; userRequestDto &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserRequestDto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            userRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            userRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDescription&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userRequestDto&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;URI&lt;/span&gt; uri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;created&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/users/{name}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@PathVariable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; userResponses &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;searchByName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserResponse&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Collectors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userResponses&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;UserService.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Slf4j&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;readOnly &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserRepository&lt;/span&gt; userRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserSearchRepository&lt;/span&gt; userSearchRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserRequestDto&lt;/span&gt; userRequestDto&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userRequestDto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; userRequestDto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDescription&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; savedUser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        userSearchRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; savedUser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserResponseDto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;searchByName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// userSearchRepository.findByBasicProfile_NameContains(name) 가능&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; userSearchRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;searchByName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserResponseDto&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Collectors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;간단한 User 저장 및 검색 예제 코드를 작성했습니다. 자세한 코드는 글 상단에 위치한 &lt;a href=&quot;https://github.com/xlffm3/spring-data-elasticsearch-practice&quot;&gt;실습 Repository&lt;/a&gt; 링크에서 확인 가능합니다.&lt;/p&gt;
&lt;h3&gt;5.1. 트러블 슈팅&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Log&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;The bean &apos;userRepository&apos;, defined in com.example.elasticsearch.user.domain.UserRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration, could not be registered.
A bean with that name has already been defined in com.example.elasticsearch.user.domain.UserRepository defined in @EnableElasticsearchRepositories declared on ElasticsearchRepositoriesRegistrar.EnableElasticsearchRepositoriesConfiguration and overriding is disabled.

Action:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Spring Data Elasticsearch 단일 모듈을 사용할 때는 문제가 없는데, 예제 코드처럼 Spring Data JPA와 함께 사용하는 경우 ApplicationContext 로드에 실패합니다.&lt;/p&gt;
&lt;p&gt;User 클래스를 위해 생성한 JPA용 Repository와 Elasticsearch용 Repository 인터페이스 모두 &lt;code class=&quot;language-text&quot;&gt;PagingAndSortingRepository&amp;lt;T, ID&gt;&lt;/code&gt;를 확장하고 있습니다. 그 결과 @EnableAutoConfiguration을 기반으로 Bean을 주입하는 과정에서 Bean 중복 문제가 발생합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;application.properties&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;properties&quot;&gt;&lt;pre class=&quot;language-properties&quot;&gt;&lt;code class=&quot;language-properties&quot;&gt;&lt;span class=&quot;token key attr-name&quot;&gt;spring.main.allow-bean-definition-overriding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Bean을 오버라이딩할 수 있도록 상기 옵션을 추가해줍니다. 그럼에도 불구하고 다시 실행해보면 에러가 발생하는데요.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Trouble Annotations&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableJpaRepositories&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableElasticsearchRepositories&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;@EnableJpaRepositories 및 @EnableElasticsearchRepositories 등 두 애너테이션은 기본적으로 Repository 관련 클래스를 &lt;strong&gt;모두&lt;/strong&gt; 스캐닝하려고 시도합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Log&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;UnsupportedFragmentException: Repository com.example.elasticsearch.user.domain.UserRepository implements org.springframework.data.repository.query.QueryByExampleExecutor but ElasticsearchRepositoryFactory does not support Query by Example!&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;@EnableElasticsearchRepositories 애너테이션이 JpaRepository 인터페이스를 확장한 JPA 관련 클래스를 스캐닝하면 위와 같은 에러가 발생합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Log&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Caused by: org.springframework.data.mapping.PropertyReferenceException: No property searchSimilar found for type User!

혹은

Caused by: org.springframework.data.mapping.PropertyReferenceException: No property index found for type User!&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;반대로 @EnableJpaRepositories 애너테이션이 ElasticsearchRepository 인터페이스를 확장한 Elasticsearch 관련 클래스를 스캐닝하면 위와 같은 에러가 발생합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ElasticSearchConfig.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableElasticsearchRepositories&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;basePackageClasses &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserSearchRepository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ElasticSearchConfig&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AbstractElasticsearchConfiguration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;ElasticsearchApplication.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableJpaRepositories&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;excludeFilters &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@ComponentScan.Filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FilterType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;ASSIGNABLE_TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    classes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserSearchRepository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootApplication&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ElasticsearchApplication&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;SpringApplication&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElasticsearchApplication&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;따라서 @EnableElasticsearchRepositories 애너테이션은 Elasticsearch 관련 Repository 클래스만 스캐닝하도록 합니다. 반대로 @EnableJpaRepositories 애너테이션은 JPA 관련 Repository 클래스만 스캐닝하도록 합니다.&lt;/p&gt;
&lt;p&gt;@EnableXXX 관련 애너테이션을 잘 설정했다면 필요없는 &lt;code class=&quot;language-text&quot;&gt;spring.main.allow-bean-definition-overriding=true&lt;/code&gt; 옵션은 제거하셔도 됩니다.&lt;/p&gt;
&lt;h3&gt;5.2. 동작 확인&lt;/h3&gt;
&lt;p&gt;어플리케이션을 실행해보고 Elasticsearch 서버로의 데이터 쓰기 및 조회가 원활하게 동작하는지 확인해봅시다.&lt;/p&gt;
&lt;p&gt;RDB는 SQL 쿼리를 통해 CRUD 작업을 수행한다면, Elasticsearch는 &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current//docs.html&quot;&gt;RESTful API&lt;/a&gt; 쿼리를 통해 CRUD 작업을 수행합니다. Spring Data JPA가 자동으로 SQL 쿼리를 생성해 DB 서버로 요청을 보내는 것처럼, Spring Data Elasticsearch 또한 RESTful API 쿼리를 생성해 ES DB 서버로 요청을 보냅니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/137831507-5fd54393-e2ca-4ece-99cd-ef0c120ec350.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;jinhong&lt;/code&gt;이라는 문자열이 포함된 모든 User 검색에 성공했습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/137831649-b38a0587-b114-4575-b26b-0ac2351e69f9.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;RESTful한 GET 요청을 통해 ES DB에 저장된 Document를 조회한 모습입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/137831732-2ffed90b-9ea6-45ba-ba2e-f8502350975f.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;COUNT(*)&lt;/code&gt; SQL 쿼리처럼 몇 명의 유저가 저장되어있는지 카운팅할 수 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Query&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;POST /users/_delete_by_query

&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;query&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;range&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;gte&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;User ID가 1보다 크거나 같은 경우 삭제하겠다는 쿼리를 요청 본문에 담아 삭제를 진행할 수 있습니다. SQL 쿼리가 다양한 것처럼 Elasticsearch 또한 다양한 RESTful API 쿼리가 존재하니, 자세한 내용은 공식 문서를 참고해주세요.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;6. Trade-Off&lt;/h2&gt;
&lt;p&gt;행 기반으로 데이터를 저장하는 RDB에서 특정 키워드가 포함된 데이터를 조회할 때 LIKE 쿼리를 주로 사용합니다. 문제는 &lt;code class=&quot;language-text&quot;&gt;LIKE &apos;%jinhong%&apos;&lt;/code&gt;과 같이 &lt;code class=&quot;language-text&quot;&gt;jinhong&lt;/code&gt; 키워드가 들어간 데이터를 조회할 때, &lt;code class=&quot;language-text&quot;&gt;%&lt;/code&gt; 기호를 앞에 사용하는 경우 조회 성능 향상을 위한 인덱스 사용이 불가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/137668936-6fd70a7a-702a-4ec8-8a07-e1b245357824.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;2대의 조회 Slave DB(Replication)를 사용하는 웹 어플리케이션에서 특정 이름 키워드가 포함된 유저 정보를 조회하는 부하 테스트를 진행한 결과입니다. 평균 TPS가 38.9입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/137674715-51fcf017-29dd-4f60-bf16-3004f37e1079.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;키워드 검색 요청&lt;/strong&gt; 상황에서 RDB를 ES 검색 엔진으로 대체하면 성능 이점을 얻을 수 있습니다. ES를 도입한 다음 부하 테스트를 동일 환경에서 진행했습니다. ES의 경우 단 1대의 서버만으로도 평균 TPS가 82.2를 기록했습니다! 이는 2대의 서버를 사용하는 기존의 RDB 방식보다 성능이 2배 이상을 상회합니다.&lt;/p&gt;
&lt;p&gt;물론 Elasticsearch라는 별도의 DB를 사용함으로써 고려해야할 관리 포인트가 많아진다는 단점이 존재합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;기존 RDB에 저장된 데이터를 ES로 이관해야 합니다.&lt;/li&gt;
&lt;li&gt;업데이트 발생시 RDB와의 데이터 정합성을 제대로 유지해야합니다.&lt;/li&gt;
&lt;li&gt;ES는 루씬 쓰기 성능이 나쁜 만큼, 업데이트 방식에 대한 추가적인 의사결정이 필요합니다.
&lt;ul&gt;
&lt;li&gt;RDB - ES 간의 데이터 싱크를 실시간으로 맞출 것인지, 혹은 배치 방식을 사용할 것인지 등.&lt;/li&gt;
&lt;li&gt;실시간으로 데이터 싱크를 맞추면 ES 서버 트래픽이 증가하고 쓰기 작업을 수행하느라 많은 리소스를 사용하게 됩니다.&lt;/li&gt;
&lt;li&gt;Bulk API 기반의 배치 방식으로 데이터 싱크를 맞추면 Document의 반복 업데이트를 줄일 수 있으나, 데이터 싱크에 지연이 발생합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;고가용성을 위한 단일 장애점 극복을 위해 ES 클러스터링 및 샤딩 등 아키텍쳐에 대한 고려가 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.15/index.html&quot;&gt;Elasticsearch Guide Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#preface&quot;&gt;Spring Data Elasticsearch - Reference Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://steemit.com/elk/@modolee/elk-stack&quot;&gt;ELK Stack을 이용한 로그 관제 시스템 만들기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://victorydntmd.tistory.com/308&quot;&gt;🙈[Elasticsearch] 기본 개념잡기🐵&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/36252099/no-property-index-found-for-type-user&quot;&gt;No property index found for type User&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.samsungsds.com/kr/insights/elastic_data_modeling.html&quot;&gt;엘라스틱서치(Elasticsearch)에서 관계형 데이터 모델링하기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[리눅스 파일 시스템 경로 구조]]></title><description><![CDATA[작성한 프로그램을 서버를 통해서 배포 시에 리눅스를 많이 사용합니다.
이 때, 리눅스를 잘 활용하기 위해서 리눅스의 각각의 경로(directory…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-10-18-linux-file-directory-system/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-10-18-linux-file-directory-system/</guid><pubDate>Mon, 18 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;작성한 프로그램을 서버를 통해서 배포 시에 리눅스를 많이 사용합니다.
이 때, 리눅스를 잘 활용하기 위해서 리눅스의 각각의 경로(directory, 디렉터리)에 접근하여 작업할 때도 있습니다.
하지만 명령어를 따라치면서 어떤 의미를 가졌는지 쉽게 파악하기가 어렵습니다.
이를 이해하기 위한 리눅스 파일 시스템의 경로에 대해서 간단하게 훑어보는 리눅스 파일 시스템에 관한 글입니다.&lt;/p&gt;
&lt;!-- end --&gt;
&lt;h2&gt;배포할 때 만나는 케이스&lt;/h2&gt;
&lt;p&gt;우아한테크코스의 레벨 4가 마무리되며, 새로운 기능들을 추가하는 것보단 이전의 기능들을 안정감 있게 사용하는 것이 중요해졌습니다.
그래서 Java 코드를 새로 작성하는 것보다 인프라 관련 설정 및 이론을 공부하고 적용하는 비율이 상당히 높아졌습니다.
특히 서비스가 배포된 인스턴스의 리눅스 배포판인 우분투의 패키지 관리를 해주면서 해당 경로를 찾아가 기존의 필요없는 의존성 들을 직접 제거하게 되었습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; /etc/apt/
$ &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;vim&lt;/span&gt; source.list&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이때, 한 크루가 해당 경로가 어떤 파일을 의미하는지를 질문하게 되었고, 우분투에서 저장소에 관한 주소 및 별칭을 관리하는 곳이라고 답변하였습니다.
일반적으로 사용하는 Windows나 MacOS에 비해서 상당히 다른 파일 경로 구조로 되어 있고 이것을 터미널로 작업을 해야 하기 때문에 쉽게 이해할 수가 없는 부분이기도 합니다.
우분투의 의존성을 관리하는 파일은 왜 하필 저 경로에 위치하게 되었는가? 를 비롯한 이해하기 위한 지식을 알기 위해 이 글을 쓰게 되었습니다.&lt;/p&gt;
&lt;h2&gt;최상위 경로와 사용자 경로&lt;/h2&gt;
&lt;p&gt;먼저 우분투에서는 최상위 권한(“root”) 및 사용자를 분리하여 사용하고 있습니다.
그래서 일반 사용자가 사용하는 경로와 최상위 권한을 가진 경로가 분리되어 있습니다.
먼저 AWS의 EC2에 접속하게 된다면 일반 사용자가 사용하는 경로로 이동하게 됩니다.
해당 명령어를 입력하면 다음과 같은 결과가 나오게 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;// CLI 명령
$ &lt;span class=&quot;token builtin class-name&quot;&gt;pwd&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token builtin class-name&quot;&gt;pwd&lt;/span&gt;
/home/ubuntu
$ &lt;span class=&quot;token function&quot;&gt;ls&lt;/span&gt; 
&lt;span class=&quot;token number&quot;&gt;2021&lt;/span&gt;-see-you-there  amazon-cloudwatch-agent.deb  goodday  repo      script.sh                                           
Dockerfile          awslogs-agent-setup.py       log      scenario  target &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;일반적으로 작성한 프로그램을 배포하면 자동으로 이 경로에 파일을 옮기게 되며, 실행도 이곳에서 이루어집니다.
위의 결과에서도 저희 팀이 배포를 위해 사용한 파일들이 있는 경로와 스크립트 파일이 보입니다.
하지만 작성한 프로그램을 작동시키기 위한 Java 같은 언어와 MySQL과 같은 인프라는 어디서 설치되는지는 아직 궁금증이 해결되지 않았습니다.
윈도우의 경우에는 C 드라이브의 Program Files에 설치되어있습니다.
하지만 리눅스의 현재 경로에서는 해당 설정을 한 경로들이 존재하지 않습니다.
이를 확인하기 위해서 우리는 경로 중 가장 위에 있는 최상위 경로로 이동해주어야 합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;// CLI 명령
$ &lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; /
$ &lt;span class=&quot;token builtin class-name&quot;&gt;pwd&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;// 결과물
bin   dev  home initrd.img.old  lib64
media  opt   root  sbin  srv  tmp  var
vmlinuz.old  boot  etc  initrd.img  lib
lost+found  mnt    proc  run   snap  sys
usr  vmlinuz &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이전의 사용자가 정의한 프로그램과 폴더들이 있던 것과 달리 시스템 관련 상당히 많은 경로가 보입니다.
각각의 경로는 리눅스에서 중요한 의미가 있으며 대표적인 것들을 간략하게 알아가 보도록 하겠습니다.&lt;/p&gt;
&lt;h2&gt;각각의 디렉토리에 대한 정리&lt;/h2&gt;
&lt;h3&gt;bin&lt;/h3&gt;
&lt;p&gt;bin 경로는 실행 가능한 binary 파일들을 모아둔 장소입니다.
이 binary 파일들은 시스템과 사용자들을 위한 명령어들을 모아놓았습니다.
일반적으로 shell script에서 사용하는 “touch” , “echo”, “mkdir” 과 같은 명령어들이 이곳에서 정의되어 사용되고 있습니다.
아래는 해당 경로에 존재하는 폴더들의 캡쳐본입니다. CLI에서 사용하는 많은 명령어들을 볼 수가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 892px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ab863b0559cdbb58063a09ac9ffdda23/51f47/2021-10-18-linux-file-directory-bin.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.800000000000004%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABXklEQVR42j1SWZLcIAz1X7fR02JsY2C8Tsez9FQlP7lAjpD7XybQ0xUVHxIlvUXQ6A/BAHTgiJo48FoSZ/PQnROr4IXgAUINqp1uI3CtGn1jPokTkEsT4Uq6mUSTpN1HsDTo2rOX72EiiPF8mg4gQWPvHR/MmfhFdfJcuDbT1ThSAUVPvDOmJ7MjGk3+vKchilM0/nPkSejqLA/h183HjJkwEWfwCe5Z9wIk/2WT4XK0lIqGyuyhVK2WOhBdiMtwAs/gvfbLEiwGfLssZj3sZtJZuWjCfa9ZS8hoNzhH3dbLYEjEX7CZlzxK1wFPZjbuXoNNgUWa/nOSXsmRn8om1CtfdlCES6A3aEBcOw7y0PDcNg5XpVLx/DFw4CvRmfzfcykyfr+GddQ8pymN7dXpOdjdY3k8mAMMcgO0AjV6VPUVKFGbybWIS4/R/Vz3Peara+UgvhNvqMNWT/0Ij/gHgp8kcTjdZ24AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;bin&quot;
        title=&quot;&quot;
        src=&quot;/static/ab863b0559cdbb58063a09ac9ffdda23/51f47/2021-10-18-linux-file-directory-bin.png&quot;
        srcset=&quot;/static/ab863b0559cdbb58063a09ac9ffdda23/0eb09/2021-10-18-linux-file-directory-bin.png 500w,
/static/ab863b0559cdbb58063a09ac9ffdda23/51f47/2021-10-18-linux-file-directory-bin.png 892w&quot;
        sizes=&quot;(max-width: 892px) 100vw, 892px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;boot&lt;/h3&gt;
&lt;p&gt;부트 로더와 관련된 부분 설정들이 포함되어 있습니다.
부팅을 할 때 필요한 이미지와 정보들이 들어있습니다.
해당 인스턴스는 AWS에서 관리되어 관련 설정들이 포함되어 있습니다.
아래는 해당 경로에서 존재하는 폴더 및 파일들의 캡쳐본입니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 830px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9262ad6bff71a697c44b247b951127ba/026af/2021-10-18-linux-file-directory-boot.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAACCAIAAADXZGvcAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAWUlEQVR42j2MSQqAQAwEPWt1BtRxwx1x+f8HnXgQcujQ1ZVpxi7sQWOwPWhCO3ajDnLP2hzQghJzQO2VWmjI0m8n6guLVRii2kINGpLr49JmxY2nKyjd+N8LFY0ITukAMtYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;bin&quot;
        title=&quot;&quot;
        src=&quot;/static/9262ad6bff71a697c44b247b951127ba/026af/2021-10-18-linux-file-directory-boot.png&quot;
        srcset=&quot;/static/9262ad6bff71a697c44b247b951127ba/0eb09/2021-10-18-linux-file-directory-boot.png 500w,
/static/9262ad6bff71a697c44b247b951127ba/026af/2021-10-18-linux-file-directory-boot.png 830w&quot;
        sizes=&quot;(max-width: 830px) 100vw, 830px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;etc&lt;/h3&gt;
&lt;p&gt;현재 인스턴스 혹은 기기에서 사용되고 있는 프로그램들의 환경 설정들이 포함되어 있습니다.
작성한 프로그램을 사용할 때 수정할 수 있는 java 나 python에 관한 설정뿐만이 아니라 nginx와 같은 인프라 관련 프로그램
그리고 ssh와 같은 시스템 관련 설정까지 여기서 관리됩니다.
아래는 etc에서 존재하는 폴더 및 파일들의 캡쳐본입니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 933px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5aef61edb9f16308eec7573155d6f402/ec645/2021-10-18-linux-file-directory-etc.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 43.400000000000006%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABVElEQVR42i2RyZadMAxE3zKg0uABbMODZugMJ4v+/89rmUQrH7tuqSS/dGoSJgBEg+Yttr+siUmkJb0KKsBsbZXZoMBIkoqtJ0dx5CVzYu0nOJw6LGEWnbQU3RprAEH3bFfmynBhI/5kGHdYw+rPvdx1inIGlAHDoLVpebOau8qZeGEkIJDUJIu5+Ols2RP/g3lWuQICMIBX0T+Bi3dg3Se4yQ/XjBIXVkfGB/5Qzi7nBzbZomejkWwp4d5g7itWTkm1nxm8gA93wRP7o/luOMt/+B37FER9YXfzLC6SI8uSntgOs5zmW3zgusice1SfrUS9kttj8Nmi7NbvCeF9y1z7nL6XXMP2k4N6gxevCQtQiOKIKnwBb0IinISNiMkrrl+aD/9LolFysf0GC43jK9VfZpOKqKgWtd9qTS2qHWqfaot6xfmwMKkrxP+xpv1O9Qxx+gZoCiNwGY5fWgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;bin&quot;
        title=&quot;&quot;
        src=&quot;/static/5aef61edb9f16308eec7573155d6f402/ec645/2021-10-18-linux-file-directory-etc.png&quot;
        srcset=&quot;/static/5aef61edb9f16308eec7573155d6f402/0eb09/2021-10-18-linux-file-directory-etc.png 500w,
/static/5aef61edb9f16308eec7573155d6f402/ec645/2021-10-18-linux-file-directory-etc.png 933w&quot;
        sizes=&quot;(max-width: 933px) 100vw, 933px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;실제로 nginx을 환경을 수정한다는 것은 위의 etc 조건에 들어맞기 때문에 해당 경로에서 수정할 수 있습니다.
명령어를 관찰하면 해당 경로가 존재하는 것을 확인할 수가 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;vim&lt;/span&gt; /etc/nginx/nginx.conf&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br&gt;
&lt;h3&gt;home&lt;/h3&gt;
&lt;p&gt;각각의 사용자들을 위한 경로입니다.
처음 도입부에서 본 것처럼 리눅스 입장에서 일반 사용자들이 사용하는 파일들을 모아두게 됩니다.
home 경로 밑에는 해당 리눅스를 사용하는 사용자들 목록이 보이게 됩니다.
현재 AWS에서는 ubuntu라는 사용자만 존재함으로 하나의 사용자 경로만 존재합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;// home 경로에서 &lt;span class=&quot;token function&quot;&gt;ls&lt;/span&gt; 결과
ubuntu&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br&gt;
&lt;h3&gt;mnt&lt;/h3&gt;
&lt;p&gt;mnt는 mount의 약자 뜻을 가진 경로입니다.
리눅스 프로그램에 기기의 탈부착으로 임시로 연결된 경로가 생기면 해당 경로에서 확인할 수가 있습니다.
현재 프로젝트의 AWS에는 연결된 외부 장치나 시스템이 없으므로 아무런 경로가 나오지 않습니다.
하지만 시스템의 용량을 확장할 때나 WSL에서 윈도우 시스템을 인식할 때 해당 경로로 인식할 수가 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;//WSL에서의 윈도우 인식
///mnt 경로
c d wsl&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;여기서 C는 Windows에서의 C 드라이브를 의미합니다.
&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;root&lt;/h3&gt;
&lt;p&gt;최상위 권한을 가진 사용자를 위한 경로입니다.
최상위 경로와는 이름이 같지만 다른 엄연히 다른 의미가 있습니다.
오직 최상위 권한을 가지고 이 경로에 있는 파일들을 수정할 수가 있습니다.
그래서 ‘cd root’ 라는 명령어로 접근하려 하면 다음과 같은 접근 거부가 나오게 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;-bash: cd: root/: Permission denied&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br&gt;
&lt;h3&gt;usr&lt;/h3&gt;
&lt;p&gt;사용자가 이용하는 binary 파일, 라이브러리, 소스 코드 등이 포함되어 있는 경로입니다.
가장 최상위 경로인 / 에서 하위 경로를 바라보는 것과 비슷하게 usr 하위에도 각각의 역할에 맞게 경로들이 분리되어 있습니다.
/bin, /lib 등은 최상위 경로에서의 경로들이 가지는 의미를 /usr 경로 밑에서도 가지게 됩니다.
이외에도 직접적으로 소스에서 설치된 프로그램을 관리하는 /usr/local도 이곳에 존재합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;bin  config  games  include  lib  
libexec  &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt;  sbin  share  src &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;실제로 필자의 이전 글인 &lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-07-15-jvm-classloader/&quot;&gt;JVM에 관하여 - Part 2. ClassLoader&lt;/a&gt; 에서도 최상위의 /lib가 아닌 해당 경로의 /usr/lib 에서 자바 관련 파일들을 확인하기도 하였습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;var&lt;/h3&gt;
&lt;p&gt;리눅스 시스템 사용 중에 발생한 로그 및 데이터들이 저장되는 경로입니다.
쌓이는 데이터의 성격에 따라 /log, /tmp, /cache 등이 있으며 로그에 대한 경로를 설정해두지 않으면 리눅스 단에서의 로그는 이곳에 쌓이게 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;awslogs  backups  cache  crash  lib  &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt;  lock
log  mail  opt  run  snap  spool  tmp  www &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;앞에서 설명한 nginx의 환경 설정은 /etc로 들어가서 확인하게 되지만, 로그는 /var에 저장되기 때문에 해당 명령어를 통해서 로그에 접근합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; /var/log/nginx&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;마치며&lt;/h2&gt;
&lt;p&gt;지금까지 리눅스 파일 시스템에서의 각각의 경로를 확인해보고 어떤 의미를 가졌는지를 알아보았습니다.
이외에도 /proc, /run 등 중요한 의미가 있는 경로들도 존재합니다.
하지만 이 모든 경로의 의미를 알고 있어도 배포 중 필요한 설정들이 어디 있는지 확인하기는 매우 어렵습니다.
다만 리눅스 파일 시스템 기반 지식을 바탕으로 설정을 수정해주기 위해 해당 경로에 접근할 때, 무엇을 수정하는지 큰 틀을 파악할 수가 있습니다.
단순히 구조를 외우는 것이 아닌, 큰 의미를 파악하는 데 있어서 많은 도움이 되길 바랍니다.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.thegeekstuff.com/2010/09/linux-file-system-structure/&quot;&gt;The Geek Stuff - Linux Directory Structure (File System Structure) Explained With Examples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[SpringBoot AutoConfiguration을 대하는 자세]]></title><description><![CDATA[SpringBoot의 AutoConfiguration은 정말 편리하게 의존성만 추가해주면 관련된 기능들이 모두 설정된다. 편리한 만큼 이를 커스터마이징하는 데는 주의가 필요하다.
이 글에서는 Spring AutoConfiguration…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-10-14-springboot-autoconfiguration/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-10-14-springboot-autoconfiguration/</guid><pubDate>Thu, 14 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SpringBoot의 AutoConfiguration은 정말 편리하게 의존성만 추가해주면 관련된 기능들이 모두 설정된다. 편리한 만큼 이를 커스터마이징하는 데는 주의가 필요하다.
이 글에서는 Spring AutoConfiguration의 간단한 원리를 알아보고 어떤 관점으로 바라봐야 하는지 제시한다.&lt;/p&gt;
&lt;!-- end --&gt;
&lt;h2&gt;@SpringBootApplication&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootConfiguration&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableAutoConfiguration&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ComponentScan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;excludeFilters &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FilterType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;CUSTOM&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; classes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TypeExcludeFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FilterType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;CUSTOM&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; classes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AutoConfigurationExcludeFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SpringBootApplication&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@SpringBootApplication&lt;/code&gt; 은 위의 3가지 어노테이션을 합친 것이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@SpringBootConfiguration : @Configuration과 같은 기능을 한다.&lt;/li&gt;
&lt;li&gt;@EnableAutoConfiguration : classpath에 있는 &lt;code class=&quot;language-text&quot;&gt;/resource/META-INF/spring.factories&lt;/code&gt; 중
EnableAutoConfiguration 부분에 정의된 Configuration들을 자동으로 등록한다. 이때, 모든 경우가 적용되는 것이 아니라,
AutoConfiguration의 등록 조건을 만족하는 경우에만 등록된다. &lt;code class=&quot;language-text&quot;&gt;--debug&lt;/code&gt; 옵션을 가지고 jar파일을 실행시키면 조건 만족 여부와 함께, 어떤 Bean들이
등록되는지 확인해 볼 수 있다.&lt;/li&gt;
&lt;li&gt;@ComponentScan : base-package가 정의되지 않으면 해당 어노테이션이 붙은 classpath 하위의 &lt;code class=&quot;language-text&quot;&gt;@Component&lt;/code&gt; 어노테이션을 스캔해서,
Bean으로 등록한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이때, 개발자가 정의한 Component들이 먼저 스캔 돼서 Bean으로 등록된 후에 AutoConfiguration의 Bean들이 등록된다. 해당 등록 부분을 확인하고
싶으면 &lt;code class=&quot;language-text&quot;&gt;ConfigurationClassParser&lt;/code&gt; 클래스의 &lt;code class=&quot;language-text&quot;&gt;doProcessConfigurationClass&lt;/code&gt; 메소드를 디버깅을 해보면 확인해 볼 수 있다.&lt;/p&gt;
&lt;p&gt;추가로 IntelliJ에서는 별도의 설정 없이 Actuator의 기능을 제공해서, 어떤 Bean들이 등록됐는지 다음과 같이 쉽게 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1134px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7303f092fa43c70f472e4e6586ee6a03/3d024/2021-10-14-springboot-autoconfiguration-0.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 36.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABbUlEQVR42jWQy3KjMBBF+aTsJoxjMBjMGyQDwrxssGemahb5/+VJYyeLUy11q67ubctLcmJtyM4Dqe7xk4oPL8Tee9iOx+9vdo4v94B3JxRe1XZfvHrb+YQVRwn2KeM9yHmzXd5kkHT/yJq/xPUfAnUnVA/8YiEqc1QZo1RGkUeksU+aHMnSUAiIoiNWphR7P3y6sPcHfrkyuPynnT8ppR7yVVhw0gU9tDwW4dYJLffri+lSMl8K+SzCqrTmcAz4cA7sXU/iHul6zX2qWCfFY1asY8VkUs7CbW5YJmFumeVd12ToIqCuxKW4tcI4w/FFcBNzD+y8gMoY5rGjv7QMvWEYDKoqUSZnvW5iDfebYZV6HTRjV9DVMWUWYG3OdsJT0PkRlBjDJtbQd2dqnT/3VIibcWwZpb9h2oq2LqnP+TNuHIvgM+YP34JGHD1kN+u2n17R6hNGJ+Iw4ypRr5MwNsxDzaUtMU3O0FWUecQXbD8KULP1u5sAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Intellij&quot;
        title=&quot;&quot;
        src=&quot;/static/7303f092fa43c70f472e4e6586ee6a03/3d024/2021-10-14-springboot-autoconfiguration-0.png&quot;
        srcset=&quot;/static/7303f092fa43c70f472e4e6586ee6a03/0eb09/2021-10-14-springboot-autoconfiguration-0.png 500w,
/static/7303f092fa43c70f472e4e6586ee6a03/1263b/2021-10-14-springboot-autoconfiguration-0.png 1000w,
/static/7303f092fa43c70f472e4e6586ee6a03/3d024/2021-10-14-springboot-autoconfiguration-0.png 1134w&quot;
        sizes=&quot;(max-width: 1134px) 100vw, 1134px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Springboot AutoConfiguration&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 836px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ce2abef55181fd3b937d5f564013b4b5/5c9dd/2021-10-14-springboot-autoconfiguration-1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 37.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABHklEQVR42l2RW3bDIAxEvZ2El3gEg52m5/SjSd3sfzsTCUic9mPOCAyjKzzVJWI9JyxrhCcNMgqeFezw93q4NRrHo4L6L6UwXa4Zn9uMr3vBconIyaJkhzI7zMPbuhAqS9Ype1CwLAfX3MJ5C20MpvI9o94q6laRzrEdokTwJwLFUXOArF/iPWMNtNZDqkmxpuVWsP5WnO8L8iW1w5ED4szKhDT3OpWxN2ofCIeD6qOrXYOQx93KILTwTBaERAhjd2kke2E4caB1BobH/BPYCLedUC48SeRi89xDUgkv4k4auBn1Z+I3lPCpMuHy08c+CaHQnHqAT641aC7hI/jZQL5Zckymd8J6fQv8iK9xg/yIEbQHdtrmI1jqJ50EPgDwL/5+wb5REwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;spring.factories&quot;
        title=&quot;&quot;
        src=&quot;/static/ce2abef55181fd3b937d5f564013b4b5/5c9dd/2021-10-14-springboot-autoconfiguration-1.png&quot;
        srcset=&quot;/static/ce2abef55181fd3b937d5f564013b4b5/0eb09/2021-10-14-springboot-autoconfiguration-1.png 500w,
/static/ce2abef55181fd3b937d5f564013b4b5/5c9dd/2021-10-14-springboot-autoconfiguration-1.png 836w&quot;
        sizes=&quot;(max-width: 836px) 100vw, 836px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Springboot의 아무 의존성이나 추가하면, spring-boot-autoconfigure가 설정이 되고, 그
내부에 있는 &lt;code class=&quot;language-text&quot;&gt;/resource/META-INF/spring.factories&lt;/code&gt; 를 확인하면 위와 같이 설정이 되어있다. 이 중 FlywayAutoConfiguration을 보며 어떤
방식으로 AutoConfiguration이 진행되는지 알아보자.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 871px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8bd4204f5857ad7f2ebbc692f2e72889/ca38a/2021-10-14-springboot-autoconfiguration-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 26.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA20lEQVR42mWQSZLFIAxDcx7AkISAmQKp6vufSW3CX/SweGUwIGRtiQnM+iWyB0cGh4DrOuGdgbMEpTX0X5RaaPWrv41BeIZC74Q+GL1mNE5omXFN4YNwiLAxBP2yBAwRyFrQrEb20psfb60RWtMYIji6Rb0Z9/MFzhWpFdSaZC2uY0A8d8S5zgmldbT+yP0h00UUb2FJBC9v4L2Tg45SK3LtQnsv5nqjcpAHCcfucFotjpar6dAYs+oPl1uJkqGIRn8iprzgDE4FQfK0+wE3kfF2az5Zqf+ZfjL8BgsAnoMEP6b7AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;FlywayAutoConfiguration&quot;
        title=&quot;&quot;
        src=&quot;/static/8bd4204f5857ad7f2ebbc692f2e72889/ca38a/2021-10-14-springboot-autoconfiguration-2.png&quot;
        srcset=&quot;/static/8bd4204f5857ad7f2ebbc692f2e72889/0eb09/2021-10-14-springboot-autoconfiguration-2.png 500w,
/static/8bd4204f5857ad7f2ebbc692f2e72889/ca38a/2021-10-14-springboot-autoconfiguration-2.png 871w&quot;
        sizes=&quot;(max-width: 871px) 100vw, 871px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Configuration(proxyBeanMethods = false)&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;대부분의 AutoConfiguration 설정의 경우 &lt;code class=&quot;language-text&quot;&gt;proxyBeanMethods = false&lt;/code&gt;로 설정되어 있다. 기본 설정은 &lt;code class=&quot;language-text&quot;&gt;proxyBeanMethods = true&lt;/code&gt;
인데, CGLIB을 통해 런타임에 동적으로 Configuration 객체의 프록시를 생성해준다. 필자가 이해한 바로는 아래의 설정을 진행하는 데 필요하다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Config&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;SomeBean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;someBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SomeBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;NothingBean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;nothingBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NothingBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;someBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt;에서는 메소드 호출로 내부에서 정의한 Bean을 바로 사용할 수 있다. 기본 설정을 사용하면 Configuration의 Proxy가 생성되고,
Proxy는 그 내부의 Bean이 한 번만 생성되도록 관리한다. 즉, 기본 설정에서는 SomeBean은 한 번만 생성되고, &lt;code class=&quot;language-text&quot;&gt;proxyBeanMethod = false&lt;/code&gt;를
사용하면, NothingBean을 생성하는 시점에서 또 다른 SomeBean이 생성되어 총 두 번 생성이 된다. 하지만, 이런 방식은 모든 Configuration에서는 필수적이지
않으며, Proxy를 생성하는 부분에서 성능적인 이슈가 있다고 한다. 자세한 부분은 다음
&lt;a href=&quot;https://github.com/spring-projects/spring-boot/issues/9068&quot;&gt;SpringBoot 이슈&lt;/a&gt; 를 참고하길 바란다.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@Conditional-#&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Spring4부터 제공하는 Conditional 어노테이션은, AutoConfiguration의 구성에 자주 사용된다. 조건에 따라 해당 자동 설정이
적용 여부를 결정한다. 대표적으로 몇 가지를 적으면 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@ConditionalOnClass: 해당 Class가 classpath에 존재할 때 조건을 만족한다. 위의 경우 Flyway에 대한 dependency를 추가해주지 않았기 때문에, 빨간색으로 표시가 되었고, FlywayAutoConfiguration이 동작하지 않게&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@ConditionalOnMissingBean: 특정 Bean이 사전에 정의되지 않을 때 조건을 만족한다.&lt;/li&gt;
&lt;li&gt;@ConditionalOnProperty: 특정 property를 명시해주고 일치할 때 조건을 만족한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@AutoConfigurationBefore&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;@AutoConfigurationAfter&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;AutoConfiguration의 등록 순서를 결정한다. FlywayAutoConfiguration 같은 경우에는 DatasourceAutoConfiguration,
JdbcTemplateAutoConfiguraion, HibernateJpaAutoConfiguration 이후에 설정이 진행된다.&lt;/p&gt;
&lt;p&gt;실제로 직접 AutoConfiguration을 만들어서 라이브러리도 배포할 수도 있다. 해당 부분은 잘
정리한 &lt;a href=&quot;https://donghyeon.dev/spring/2020/08/01/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8%EC%9D%98-AutoConfiguration%EC%9D%98-%EC%9B%90%EB%A6%AC-%EB%B0%8F-%EB%A7%8C%EB%93%A4%EC%96%B4-%EB%B3%B4%EA%B8%B0/&quot;&gt;이 블로그&lt;/a&gt;
를 참고하길 바란다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;AutoConfiguration을 대하는 자세 (feat.DB Replication)&lt;/h2&gt;
&lt;p&gt;SpringBoot에서 DB Replication 설정을 위해 구글링을 해보면 다음과 비슷한 형태의 코드들을 많이 발견할 수 있다. 이 예시를 조금 더 SpringBoot스럽게 코드 수정 작업을 진행해보고자 한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableAutoConfiguration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exclude &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;DataSourceAutoConfiguration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableTransactionManagement&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableJpaRepositories&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;basePackages &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.example&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataBaseConfig&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@ConfigurationProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prefix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;spring.datasource.master&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSource&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;masterDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSourceBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HikariDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@ConfigurationProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prefix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;spring.datasource.slave&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSource&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;slaveDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSourceBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HikariDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSource&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;routingDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Qualifier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;masterDataSource&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSource&lt;/span&gt; master&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                      &lt;span class=&quot;token annotation punctuation&quot;&gt;@Qualifier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;slaveDataSource&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSource&lt;/span&gt; slave&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;ReplicationRoutingDataSource&lt;/span&gt; routingDataSource &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReplicationRoutingDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; sources &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    sources&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DATASOURCE_KEY_MASTER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; master&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    sources&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DATASOURCE_KEY_SLAVE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; slave&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    routingDataSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setTargetDataSources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sources&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    routingDataSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setDefaultTargetDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;master&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; routingDataSource&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Primary&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSource&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;dataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Qualifier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;routingDataSource&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSource&lt;/span&gt; routingDataSource&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LazyConnectionDataSourceProxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;routingDataSource&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReplicationRoutingDataSource&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AbstractRoutingDataSource&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DATASOURCE_KEY_MASTER&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;master&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DATASOURCE_KEY_SLAVE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;slave&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Logger&lt;/span&gt; logger &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoggerFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLogger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ReplicationRoutingDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;determineCurrentLookupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TransactionSynchronizationManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isCurrentTransactionReadOnly&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DATASOURCE_KEY_SLAVE&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DATASOURCE_KEY_MASTER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위 코드를 간단히 설명하면 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DB Replication는 Read를 Slave DB로, CUD(Create, Update, Delete)의 작업은 Master DB에 접근해야 한다. DB
Replication의 개념이 궁금한
분은 &lt;a href=&quot;https://mariadb.com/kb/en/replication-overview/&quot;&gt;MariaDB Replication Overview&lt;/a&gt; 를 참고하길 바란다.&lt;/li&gt;
&lt;li&gt;master/slave DataSource를 설정한다.&lt;/li&gt;
&lt;li&gt;ReplicationRoutingDataSource에서 &lt;code class=&quot;language-text&quot;&gt;@Transactional&lt;/code&gt;의 readOnly 여부에 따라 연결하는 DataSource를 결정한다.&lt;/li&gt;
&lt;li&gt;기존의 DataSource는 Transaction이 시작되는 시점에서 DataSource를 결정하지만 &lt;code class=&quot;language-text&quot;&gt;LazyConnectionDataSourceProxy&lt;/code&gt;를 사용하여 그
DataSource 결정을 실제 Statement가 요청되었을 때로 늦춘다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;위의 구현에서 의문이 드는 점&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;DataSource를 커스터마이징한다고 하면, 무조건 DataSourceAutoConfigration을 exclude 해야 하나? 필자의 견해는 &lt;code class=&quot;language-text&quot;&gt;No&lt;/code&gt;
이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Auto-configuration is non-invasive&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;SpringBoot 공식문서에서는 위와 같이 설명한다. AutoConfiguration은 침투적이지 않다, 즉 &lt;code class=&quot;language-text&quot;&gt;@Conditional-*&lt;/code&gt;의 어노테이션을
사용해서 충분히 AutoConfiguration을 exclude하지 않아도 된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// DataSourceAutoConfiguration 클래스&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;proxyBeanMethods &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Conditional&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;EmbeddedDatabaseCondition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ConditionalOnMissingBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;DataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XADataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;EmbeddedDataSourceConfiguration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EmbeddedDatabaseConfiguration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;DataSourceAutoConfiguration 에서는 @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
설정을 통해, DataSource클래스의 Bean이 이미 존재한다면, 새로운 DataSource Bean을 생성하지 않는다.&lt;/p&gt;
&lt;p&gt;추가적으로 DataSourceAutoConfiguration의 코드를 보게 되면, &lt;code class=&quot;language-text&quot;&gt;DataSourceInitializationConfiguration&lt;/code&gt; 이
deprecated(&lt;code class=&quot;language-text&quot;&gt;SqlInitializationAutoConfiguration&lt;/code&gt; 로 이전한 것으로 보임)돼서 별도의 설정이 진행되지 않지만, 다른
AutoConfiguration에서는 별도의 설정이 진행될 가능성이 있다. AutoConfigration은 직접 들어가서 확인해보기 전까지는 그 작업을 예단하면 안 된다.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;@EnableTransactionalManager, @EnableJpaRepositories&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;해당 설정은 &lt;code class=&quot;language-text&quot;&gt;TransactionAutoConfiguration&lt;/code&gt;에서 자동으로 설정이 된다. 그렇기에 불필요한
설정이다. &lt;code class=&quot;language-text&quot;&gt;JpaRepositoriesAutoConfiguration&lt;/code&gt; 와 &lt;code class=&quot;language-text&quot;&gt;@EnableJpaRepositories&lt;/code&gt;는 효과가 동일하기 때문에 삭제해도 된다.&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;실제로 잘 동작하는가?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;해당하는 설정을 진행했을 때, 특이한 상황을 발견했다. 여러 개의 트랜잭션으로 구성된 api콜의 경우 맨 처음 트랜잭션의 readOnly 여부에 따라 master/slave DB가
결정된다. 예를 들어 &lt;code class=&quot;language-text&quot;&gt;@Transactional(readOnly=true)&lt;/code&gt;의 트랜잭션을 DB에 쓰이는 것을 확인할 수 있었다. 각각의 트랜잭션별로 readOnly 검증하고
있다고 생각했는데, 실제로는 그렇게 동작하지 않았다. 그 이유는 &lt;code class=&quot;language-text&quot;&gt;HibernateJpaAutoConfiguration&lt;/code&gt; 설정에서 자체적으로 OSIV(Open Session In
View)설정이 true로 설정되어 있기 때문이다. OSIV의 자세한 내용이 궁금한
분은 &lt;a href=&quot;http://pds19.egloos.com/pds/201106/28/18/Open_Session_In_View_Pattern.pdf&quot;&gt;조영호님 OSIV 정리 글&lt;/a&gt; 을
참고하기를 바란다. 트랜잭션별로 DataSource를 설정해주기 위해서는 &lt;code class=&quot;language-text&quot;&gt;jpa.open-in-view=false&lt;/code&gt;설정을 application.properties에 추가해줘야
한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;주의 사항&lt;/p&gt;
&lt;p&gt;Spring의 많은 AutoConfiguration Setting들은 한 개의 DataSource의 사용을 가정하는 것으로 보인다. &lt;code class=&quot;language-text&quot;&gt;HibernateJpaAutoConfiguration&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;JdbcTemplateAutoConfiguration&lt;/code&gt; , &lt;code class=&quot;language-text&quot;&gt;SqlInitializationAutoConfiguration&lt;/code&gt; 에서 모두 &lt;code class=&quot;language-text&quot;&gt;@ConditionalOnSingleCandidate(DataSource.class)&lt;/code&gt; 의 설정이 요구된다. 여러 개의 DataSource를 사용하게 되었을 때에는 &lt;code class=&quot;language-text&quot;&gt;@Primary&lt;/code&gt; 어노테이션을 붙여주어야 한다. 그렇지 않게 되면, 실행된 것으로 예상된 동작들이 작동하지 않는 것을 확인할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Flyway의 의존성을 추가해줬을 때 FlywayAutoConfigration으로 인한 순환 참조의 문제 발생&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;어찌 보면 당연한 문제다. 지금의 DataSource는 &lt;code class=&quot;language-text&quot;&gt;@Transactional&lt;/code&gt; 의 readOnly 여부에 따라 dataSource가 결정되는 부분인데, DB
Migration을 담당하는 Flyway는 여러 DB 중 어떤 DB를 스스로 선택해서 Migration해줘야 할까? FlywayAutoConfiguration의 설정을
보면 &lt;code class=&quot;language-text&quot;&gt;@FlywayDataSource&lt;/code&gt;라는 어노테이션을 활용해서 Flyway가 관리하는 DataSource를 지정해줄 수 있다. 설정하지 않으면 여러 DataSource일
때는 &lt;code class=&quot;language-text&quot;&gt;@Primary&lt;/code&gt; 가 붙은 DataSource를, 하나일 때는 해당 DataSource가 관리된다.&lt;/p&gt;
&lt;p&gt;현재 시점에서는 &lt;a href=&quot;https://github.com/spring-projects/spring-boot/issues/15732&quot;&gt;Spring Boot 이슈&lt;/a&gt; 에서 확인 할 수 있듯이
여러 DataSource에 대한 별도의 Solution이 없는 것으로 보인다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;수정 후 코드&lt;/h3&gt;
&lt;p&gt;위의 고려 사항을 반영한 코드는 다음과 같다. &lt;code class=&quot;language-text&quot;&gt;jpa.open-in-view=false&lt;/code&gt; 옵션도 추가해야 한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;proxyBeanMethods &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSourceConfig&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@FlywayDataSource&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@ConfigurationProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prefix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;spring.datasource.master&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSource&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;masterDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSourceBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HikariDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@ConfigurationProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prefix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;spring.datasource.slave&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSource&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;slaveDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSourceBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HikariDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSource&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;routingDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Qualifier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;masterDataSource&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSource&lt;/span&gt; masterDataSource&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                      &lt;span class=&quot;token annotation punctuation&quot;&gt;@Qualifier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;slaveDataSource&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSource&lt;/span&gt; slaveDataSource&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;ReplicationRoutingDataSource&lt;/span&gt; routingDataSource &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReplicationRoutingDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; sources &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    sources&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DATASOURCE_KEY_MASTER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; masterDataSource&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    sources&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DATASOURCE_KEY_SLAVE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; slaveDataSource&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    routingDataSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setTargetDataSources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sources&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    routingDataSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setDefaultTargetDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;masterDataSource&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; routingDataSource&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Primary&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSource&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;dataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Qualifier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;routingDataSource&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataSource&lt;/span&gt; routingDataSource&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LazyConnectionDataSourceProxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;routingDataSource&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;해당 부분을 이슈로 작업했던
흐름은 &lt;a href=&quot;https://github.com/woowacourse-teams/2021-gpu-is-mine/issues/399&quot;&gt;GPU-IS-MINE DB Replication 이슈&lt;/a&gt;
에서 확인해 볼 수 있다.&lt;/p&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;SpringBoot의 AutoConfiguration은 편리하다. 하지만, 예측하지 못하는 경우가 발생하기도 하고, 이는 바로 확인하기가 쉽지 않다.
AutoConfiguration의 작동 방식을 잘 이해했을 때, 코드는 더 간결해지고 SpringBoot를 SpringBoot답게 쓸 수 있다고 생각한다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[커버링 인덱스]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-10-12-covering-index/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-10-12-covering-index/</guid><pubDate>Tue, 12 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;조회 성능 개선 미션을 진행하며 &lt;code class=&quot;language-text&quot;&gt;커버링 인덱스&lt;/code&gt;를 알게 됐다.
처음 보는 단어여서 이게 어떤 인덱스일까 궁금했고, 바로 찾아보고 이해했다.
그러면서 한번 내용을 글로 정리하면 좋을 것 같다는 생각을 했다.
그래서 이번 기회에 학습 테스트를 하며 해당 개념을 잡고가려 한다.&lt;br/&gt;&lt;/p&gt;
&lt;!-- end --&gt;
&lt;br/&gt;
&lt;h1&gt;커버링 인덱스?&lt;/h1&gt;
&lt;p&gt;&lt;sup&gt;*&lt;/sup&gt;인덱스는 데이터를 효율적으로 탐색하는 방법이다.
MySQL의 경우 인덱스 안에 있는 데이터를 사용할 수 있다.
이를 잘 이용하면 실 데이터까지 접근할 필요가 없다.
즉, 테이블에 액세스하지 않아도 된다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;커버링 인덱스&lt;/code&gt;는 쿼리를 충족하는데 필요한 모든 데이터를 갖는 인덱스를 뜻한다.
&lt;code class=&quot;language-text&quot;&gt;SELECT / WHERE / GROUP BY / ORDER BY&lt;/code&gt; 등에 활용되는 모든 컬럼이 인덱스의 구성 요소인 경우를 말한다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;커버링 인덱스를 잘 쓰면(특히, 대용량 데이터 처리 시), 조회 성능을 상당 부분 높일 수 있다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;sup&gt;*&lt;/sup&gt;인덱스에 대해 기초를 쌓고 싶다면, 테코블에 있는 아래 글을 참고해보자.&lt;br/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-09-18-db-index/&quot;&gt;DB Index 입문&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h1&gt;기본 지식&lt;/h1&gt;
&lt;h2&gt;실행 계획&lt;/h2&gt;
&lt;p&gt;쿼리의 실행 계획을 확인하는 방법은 2가지가 있다.
아래 쿼리를 이용해서 어떤 방법인지 알아보자.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; programmer
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; member_id &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h3&gt;Workbench 아이콘&lt;/h3&gt;
&lt;p&gt;MySQL Workbench를 사용하면, 쿼리문을 작성하는 화면 상단에서 아래와 같이 여러 아이콘을 볼 수 있다.
이중 &lt;code class=&quot;language-text&quot;&gt;⚡️ + 🔍&lt;/code&gt;를 클릭하면 실행 계획을 확인할 수 있다.(왼쪽에서 5번째에 위치한 아이콘이다.)&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50176238/136898561-30f19651-9a98-48e0-a6e1-bad76b5cb270.png&quot; alt=&quot;workbench_icon1&quot;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;⚡️ + 🔍&lt;/code&gt;를 클릭해서 확인하면, 이렇게 실행 계획이 나타난다.
해당 쿼리는 Index Range Scan을 했음을 알 수 있다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50176238/136899046-82fbc92d-edc8-4447-9619-b664b2f5311b.png&quot; alt=&quot;workbench_icon2&quot;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h3&gt;EXPLAIN&lt;/h3&gt;
&lt;p&gt;쿼리의 맨 앞에 &lt;code class=&quot;language-text&quot;&gt;EXPLAIN&lt;/code&gt;을 붙여 실행하면, 아이콘보다 더 상세한 실행 계획을 확인할 수 있다.
이는 SQL 쿼리로 보는 방식이다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;EXPLAIN&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; programmer
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; member_id &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;SQL 쿼리로 확인하면, 아래처럼 실행 계획이 나타난다.
각 항목에 관한 내용은 바로 이어서 설명한다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50176238/136902808-bddf6277-dc8f-4b54-be81-9ded54fe38c1.png&quot; alt=&quot;explain&quot;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;b&gt;id&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;SQL문이 실행되는 순서를 의미한다.
2개 행의 id가 같다면, 이는 조인된 것이다.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;b&gt;select_type&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;SELECT문의 유형을 의미한다.&lt;br/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SIMPLE: 단순한 SELECT문&lt;/li&gt;
&lt;li&gt;PRIMARY: 외부쿼리 또는 UNION이 포함되는 경우 1번째 SELECT문&lt;/li&gt;
&lt;li&gt;SUBQUERY: SELECT / WHERE에 작성된 서브쿼리&lt;/li&gt;
&lt;li&gt;DERIVED: FROM에 작성된 서브쿼리&lt;/li&gt;
&lt;li&gt;UNION: UNION 또는 UNION ALL로 합쳐진 SELECT문&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;&lt;b&gt;type&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;system: 0개 또는 1개의 데이터만 테이블에 존재하는 경우&lt;/li&gt;
&lt;li&gt;const: 단 1개의 데이터만 조회하는 경우&lt;/li&gt;
&lt;li&gt;eq_ref: 조인이 될 때, 드리븐 테이블의 PK 또는 고유 인덱스로 단 1개의 데이터만 조회하는 경우&lt;/li&gt;
&lt;li&gt;ref: eq_ref와 같지만, 2개 이상의 데이터를 조회하는 경우&lt;/li&gt;
&lt;li&gt;index: &lt;sup&gt;*&lt;/sup&gt;Index Full Scan&lt;/li&gt;
&lt;li&gt;range: &lt;sup&gt;*&lt;/sup&gt;Index Range Scan&lt;/li&gt;
&lt;li&gt;all: &lt;sup&gt;*&lt;/sup&gt;Table Full Scan&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;sup&gt;*&lt;/sup&gt;Index Full Scan, Index Range Scan, Table Full Scan이 무엇인지 잘 모르겠다면, 아래 글을 참고해보자.&lt;br/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://imnkj.tistory.com/49&quot;&gt;[SQL 튜닝] Table Full Scan VS Index Range Scan VS Index Full Scan&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br/&gt;
&lt;p&gt;&lt;b&gt;key&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;옵티마이저가 실제로 선택한 인덱스를 의미한다.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;b&gt;rows&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;SQL문을 수행하기 위해 접근한 데이터의 모든 행 수를 의미한다.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;b&gt;extra&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Distinct: 중복을 제거하는 경우&lt;/li&gt;
&lt;li&gt;Using where: WHERE로 필터링한 경우&lt;/li&gt;
&lt;li&gt;Using temporary: 데이터 중간 결과를 위해 임시 테이블을 생성한 경우 (보통 &lt;code class=&quot;language-text&quot;&gt;DISTINCT / GROUP BY / ORDER BY&lt;/code&gt;가 포함되면 임시 테이블 생성)&lt;/li&gt;
&lt;li&gt;Using index: &lt;code class=&quot;language-text&quot;&gt;커버링 인덱스&lt;/code&gt;를 사용한 경우&lt;/li&gt;
&lt;li&gt;Using filesort: 데이터를 정렬한 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;실행 계획의 &lt;code class=&quot;language-text&quot;&gt;extra&lt;/code&gt;에 &lt;code class=&quot;language-text&quot;&gt;Using index&lt;/code&gt;가 나타나면, 이는 커버링 인덱스를 활용한 것으로 해석할 수 있다.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;Non-clustered Key와 Clustered Key&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;대상&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;제한&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Non-clustered Key&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;일반적인 인덱스&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;테이블에 여러 개 생성 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Clustered Key&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;(1) PK&lt;br/&gt;(2) PK가 없을 땐, Unique Key&lt;br/&gt;(3) PK가 없고 Unique Key도 없을 땐,&lt;br/&gt;6 bytes의 Hidden Key 생성&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;테이블당 1개만 생성 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;br/&gt;
&lt;p&gt;Non-clustered Key와 Clustered Key를 통한 탐색은 다음과 같이 진행된다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50176238/136989175-03758ad2-5660-47e2-ac20-6ef04c2d781c.png&quot; alt=&quot;non-clustered-key-and-clustered-key&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;이미지 출처: &lt;a href=&quot;https://jojoldu.tistory.com/476&quot;&gt;1. 커버링 인덱스 (기본 지식 / WHERE / GROUP BY)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br/&gt;
&lt;p&gt;현재 age와 PK에 인덱스가 걸려있다. 따라서 Non-clustered Key는 age에, Clustered Key는 PK에 인덱스가 있다.
그리고 Non-clustered Key는 age 순으로, Clustered Key는 PK 순으로 정렬된다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Non-clustered Key에는 인덱스 컬럼(age)의 값들과 PK의 값들이 있고, Clustered Key는 테이블의 실제 레코드 위치를 알고 있다.
MySQL에서는 Non-clustered Key에 Clustered Key가 항상 포함되어 있다. Non-clustered Key엔 데이터 블록의 위치가 없기 때문이다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;그러므로 인덱스 조건에 부합하는 WHERE가 있더라도,
SELECT 문에 인덱스에 포함되어 있는 컬럼 외의 다른 컬럼 값이 필요할 때는 Non-clustered Key에 있는 Clustered Key 값으로 데이터 블록을 찾는 과정이 필요하다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;그렇지만, PK를 사용하는 경우에는 테이블 액세스 시간이 없어져서 필요로 하는 데이터에 더 빠르게 접근할 수 있다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;결국 &lt;code class=&quot;language-text&quot;&gt;커버링 인덱스&lt;/code&gt;는 이미지의 &lt;code class=&quot;language-text&quot;&gt;2. 실제 데이터 접근&lt;/code&gt; 과정 없이, 인덱스에 존재하는 컬럼 값으로만 쿼리를 완성하는 것을 얘기한다.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h1&gt;적용 조건&lt;/h1&gt;
&lt;p&gt;학습 테스트에서는 아래의 member 테이블을 활용했다.
테이블의 컬럼은 다음과 같고, 전체 레코드는 96206개가 있다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50176238/136998203-cea92b70-4ab6-474a-a936-a87b970a8866.png&quot; alt=&quot;member_table&quot;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;학습 테스트를 시작하기 앞서 (email)으로 인덱스를 추가했다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INDEX&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;idx_member_email&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;email&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h2&gt;SELECT + WHERE&lt;/h2&gt;
&lt;p&gt;먼저, 아래 쿼리의 실행 계획을 확인했다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; member
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; email &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;probitanima8@gmail.com&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50176238/137000538-48493036-80cb-4d8d-a32a-755be7aed65f.png&quot; alt=&quot;select_where_explain1&quot;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;그렇다면, SELECT 문을 &lt;code class=&quot;language-text&quot;&gt;*&lt;/code&gt;가 아닌 &lt;code class=&quot;language-text&quot;&gt;email&lt;/code&gt;로 변경하면 어떤 일이 일어날까?&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; email
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; member
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; email &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;probitanima8@gmail.com&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50176238/137000717-c4132a2a-e0db-41de-b4e5-d20f11bb0f5e.png&quot; alt=&quot;select_where_explain2&quot;&gt;&lt;/p&gt;
&lt;p&gt;실행 계획의 &lt;code class=&quot;language-text&quot;&gt;extra&lt;/code&gt;를 보면, 커버링 인덱스가 사용됐다. 즉, 이 쿼리는 인덱스에 포함되어 있는 컬럼만으로 쿼리 생성이 가능하다.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;WHERE + GROUP BY&lt;/h2&gt;
&lt;p&gt;우선, GROUP BY는 아래 조건에서 인덱스가 적용된다.&lt;br/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;인덱스 컬럼과 GROUP BY에 명시하는 컬럼의 순서는 동일해야 된다.&lt;/li&gt;
&lt;li&gt;인덱스 컬럼 중 뒤에 있는 컬럼은 GROUP BY에 명시하지 않아도 된다.&lt;/li&gt;
&lt;li&gt;인덱스 컬럼 중 앞에 있는 컬럼은 GROUP BY에 명시해야 된다.&lt;/li&gt;
&lt;li&gt;인덱스에 없는 컬럼을 GROUP BY에 명시하면 안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;--- 인덱스가 (a, b, c)인 경우&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; b              &lt;span class=&quot;token comment&quot;&gt;--- 인덱스 적용 X&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; a           &lt;span class=&quot;token comment&quot;&gt;--- 인덱스 적용 X&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b        &lt;span class=&quot;token comment&quot;&gt;--- 인덱스 적용 X&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; a              &lt;span class=&quot;token comment&quot;&gt;--- 인덱스 적용 O&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b           &lt;span class=&quot;token comment&quot;&gt;--- 인덱스 적용 O&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; c        &lt;span class=&quot;token comment&quot;&gt;--- 인덱스 적용 O&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; c           &lt;span class=&quot;token comment&quot;&gt;--- 인덱스 적용 X&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; d     &lt;span class=&quot;token comment&quot;&gt;--- 인덱스 적용 X&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h3&gt;WHERE가 동등 비교일 때&lt;/h3&gt;
&lt;p&gt;WHERE + GROUP BY가 함께 사용되면, WHERE에 있는 컬럼은 GROUP BY에 없어도 된다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;--- 인덱스가 (a, b, c)인 경우&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; c           &lt;span class=&quot;token comment&quot;&gt;--- 인덱스 적용 O&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; b &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;dani&apos;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; c              &lt;span class=&quot;token comment&quot;&gt;--- 인덱스 적용 O&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h3&gt;WHERE가 동등 비교가 아닐 때&lt;/h3&gt;
&lt;p&gt;학습 테스트를 하기 앞서 (email, id, age)로 인덱스를 추가했다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INDEX&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;idx_member_email_id_age&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;email&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; age&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;그리고 아래 쿼리의 실행 계획과 수행 시간을 확인했다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; member
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; email &lt;span class=&quot;token operator&quot;&gt;LIKE&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;probitanima1%&apos;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; age&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;실행 계획의 &lt;code class=&quot;language-text&quot;&gt;extra&lt;/code&gt;에 &lt;code class=&quot;language-text&quot;&gt;Using where&lt;/code&gt;가 나타났다. 이는 WHERE로 필터링한 것으로 해석할 수 있다.
수행 시간은 &lt;code class=&quot;language-text&quot;&gt;0.250sec&lt;/code&gt;이 소요됐다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;해당 시간이 긴 건지 짧은 건지 판단하기 어려웠다. 그래서 커버링 인덱스를 사용하는 테스트를 이어 진행했다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50176238/137096460-d8427f15-0096-4d15-8139-4cb33ac3951e.png&quot; alt=&quot;where_group_by_explain1&quot;&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/50176238/137097814-8c5b6141-fce0-470f-81eb-3c39ebe3fab9.png&quot; alt=&quot;where_group_by_time1&quot;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;이번에는 아래 쿼리의 실행 계획과 수행 시간을 확인했다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; email&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; age
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; member
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; email &lt;span class=&quot;token operator&quot;&gt;LIKE&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;probitanima1%&apos;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; age&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;실행 계획의 &lt;code class=&quot;language-text&quot;&gt;extra&lt;/code&gt;에 &lt;code class=&quot;language-text&quot;&gt;Using index&lt;/code&gt;가 나타났다. 이는 커버링 인덱스를 이용한 것으로 해석할 수 있다.
수행 시간은 &lt;code class=&quot;language-text&quot;&gt;0.70sec&lt;/code&gt;이 소요됐다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;이전 테스트와 비교하여 수행 시간이 약 3.5배 정도 단축됐다.
커버링 인덱스를 적용하니 결과를 상대적으로 빠르게 얻을 수 있었다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50176238/137096924-3bb21258-af08-41d7-8da6-497f11cf6539.png&quot; alt=&quot;where_group_by_explain2&quot;&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/50176238/137097925-52a771bd-7f8d-48e0-812e-a1b079779a2c.png&quot; alt=&quot;where_group_by_time2&quot;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h1&gt;결론&lt;/h1&gt;
&lt;p&gt;조회 쿼리를 작성할 때, &lt;code class=&quot;language-text&quot;&gt;커버링 인덱스&lt;/code&gt;를 적절하게 활용하면 성능 개선에 도움을 줄 수 있다.
다만, 인덱스 적용 조건을 잘 숙지해야 한다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;이번 글에서 실험한 상황 외에도 다양한 곳에서 커버링 인덱스를 사용할 수 있다.
WHERE + ORDER BY와 같은 케이스에 대한 내용은 바로 아래 레퍼런스에 첨부했다.
관심이 있는 사람은 참고해서 추가로 학습하면 좋을 것 같다.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h1&gt;References&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;우아한테크코스 강의 자료&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jojoldu.tistory.com/476&quot;&gt;1. 커버링 인덱스 (기본 지식 / WHERE / GROUP BY)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jojoldu.tistory.com/481&quot;&gt;2. 커버링 인덱스 (WHERE + ORDER BY / GROUP BY + ORDER BY )&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gywn.net/2012/04/mysql-covering-index/&quot;&gt;MySQL에서 커버링 인덱스로 쿼리 성능을 높여보자!!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Scale-up과 Scale-out에 대해 알아보자!]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-10-12-scale-up-scale-out/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-10-12-scale-up-scale-out/</guid><pubDate>Tue, 12 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p style=&quot;font-family: sans-serif; text-align: center; color: #aaa; margin-bottom: 3em; font-size: 85%&quot;&gt;image origin: &lt;a href=&quot;http://www.bemax.co.kr/community/info?mod=document&amp;uid=4&quot;&gt;bemax.co.kr&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;서비스를 제작하고, 성공적으로 배포까지 마쳤다.&lt;/p&gt;
&lt;p&gt;운 좋게도 점점 많은 유저가 유입되기 시작했고, 수많은 유저가 동시접속하는 서비스가 되었다.&lt;/p&gt;
&lt;p&gt;기쁨도 잠시, 문제가 생겼다. 기존에 사용하던 서버가 한계에 도달했다. 인프라를 확장해야 할 필요성을 느끼게 된 것이다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 인프라를 업그레이드하는 방법인 &lt;code class=&quot;language-text&quot;&gt;스케일 업(Scale-up)&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;스케일 아웃(Scale-out)&lt;/code&gt;에 대해 알아볼 것이다.&lt;/p&gt;
&lt;h2&gt;스케일 업(Scale-up)&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50273712/136900399-b4615b3a-8afa-4950-9513-9ce35748588f.png&quot; alt=&quot;scaleup drawio (1)&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;스케일 업(Scale-up)&lt;/code&gt;은 쉽게 말하면 기존의 서버를 보다 높은 사양으로 업그레이드하는 것을 말한다. 하드웨어적인 예를 들면, 성능이나 용량 증강을 목적으로 하나의 서버에 디스크를 추가하거나 CPU나 메모리를 업그레이드시키는 것을 말한다.&lt;/p&gt;
&lt;p&gt;이처럼 하나의 서버의 능력을 증강하기 때문에 &lt;code class=&quot;language-text&quot;&gt;수직 스케일링(vertical scaling)&lt;/code&gt;이라고도 한다.&lt;/p&gt;
&lt;p&gt;소프트웨어적인 예로는 AWS의 EC2 인스턴스 사양을 micro에서 small, small에서 medium 등으로 높이는 것으로 생각하면 된다.&lt;/p&gt;
&lt;h2&gt;스케일 아웃(Scale-out)&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50273712/136903171-2fa97983-a678-4fda-8128-ff007ab95c84.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;스케일 아웃(Scale-out)&lt;/code&gt;은 장비를 추가해서 확장하는 방식을 말한다.&lt;/p&gt;
&lt;p&gt;기존 서버만으로 용량이나 성능의 한계에 도달했을 때, 비슷한 사양의 서버를 추가로 연결해 처리할 수 있는 데이터 용량이 증가할 뿐만 아니라 기존 서버의 부하를 분담해 성능 향상의 효과를 기대할 수 있다.&lt;/p&gt;
&lt;p&gt;서버를 추가로 확장하기 때문에 &lt;code class=&quot;language-text&quot;&gt;수평 스케일링(horizontal scaling)&lt;/code&gt;이라고도 불린다.&lt;/p&gt;
&lt;p&gt;클라우드 서비스에서는 자원 사용량을 모니터링하여 자동으로 서버를 증설(Scale Out)하는 Auto Scaling 기능도 있다.&lt;/p&gt;
&lt;h2&gt;스케일 업(Scale-up)과 스케일 아웃(Scale-out)의 장단점 비교&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50273712/136903809-d755fb78-678f-4c70-a5ed-90099fb37882.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;스케일 업&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;스케일 업 아키텍처에서는 추가적인 네트워크 연결 없이 용량을 증강할 수 있다. 스케일 아웃보다 관리 비용이나 운영 이슈가 적고, 사양만 올리면 되는 것이기 때문에 비교적 쉽다.&lt;/p&gt;
&lt;p&gt;다만 성능 향상에 한계가 있으며 성능 향상에 따른 비용부담이 크고, 서버 한 대가 부담하는 양이 많아서 자연재해 등의 다양한 이유로 서버에 문제가 생기면 큰 타격을 입게 된다.&lt;/p&gt;
&lt;p&gt;또한 기존의 서버를 교체함으로써 성능을 올릴 때에는 서비스를 이용할 수 없는 다운타임이 불가피하다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;스케일 아웃&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;스케일 아웃 아키텍처의 가장 큰 장점 중 하나는 확장의 유연성에 있다.&lt;/p&gt;
&lt;p&gt;스케일 업 시스템을 구축한 상황에서는 향후 확장 가능성에 대비해 서버를 현재 필요한 만큼보다 더 많은 용량이나 성능을 확보해놓는 경우가 많다.&lt;/p&gt;
&lt;p&gt;그러나 이런 경우 예상했던 정도와 요구되는 정도가 다르거나, 확장의 필요성이 없어졌을 경우 추가로 확보해놓은 만큼의 손해가 발생하게 된다.&lt;/p&gt;
&lt;p&gt;스케일 아웃 방식으로 시스템을 구축한 상황에서는 서버를 필요한 만큼만 도입해 놓고, 장기적인 용량 증가 추이를 예측할 필요 없이 그때그때 필요한 만큼 서버를 추가해 용량과 성능을 확장(pay-as-you-grow)할 수 있게 된다.&lt;/p&gt;
&lt;p&gt;다만, 스케일 아웃도 장점만 있는 것은 아니다. 무엇보다 여러 노드를 연결해 병렬 컴퓨팅 환경을 구성하고 유지하려면 아키텍처에 대한 높은 이해도가 요구된다.
서버의 수가 늘어날수록 관리가 힘들어지는 부분이 있고, 아키텍처의 설계 단계에서부터 고려되어야 할 필요가 있기 때문이다.&lt;/p&gt;
&lt;p&gt;여러 노드에 부하를 균등하게 분산시키기 위해 로드 밸런싱(load balancing)이 필요하고, 노드를 확장할수록 문제 발생의 잠재 원인 또한 추가한 만큼 늘어나게 된다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;인프라 확장 방식으로 스케일 업과 스케일 아웃에 대해 알아보았다.&lt;/p&gt;
&lt;p&gt;글만 봤을 땐 스케일 아웃이 스케일 업보다 뛰어나 보이지만, 무조건 뛰어나다고 단언할 수는 없다.&lt;/p&gt;
&lt;p&gt;서비스를 제공하는 어플리케이션의 종류나 스토리지의 용도 등에 따라 선택할 수 있을 뿐이다.&lt;/p&gt;
&lt;p&gt;빅데이터의 데이터 마이닝이나 검색엔진 데이터 분석 처리 등을 대표하는 OLAP(Online Analytical Processing) 어플리케이션 환경에서는 대량의 데이터 처리와 복잡한 쿼리가 이루어지기 때문에 스케일아웃 구성이 더 효율적이다.&lt;/p&gt;
&lt;p&gt;반면 온라인 금융거래와 같이 워크플로우 기반의 빠르고 정확하면서 단순한 처리가 필요한 OLTP(Online Transaction Processing) 환경에서는 고성능의 스케일 업 방식이 적합하다고 한다.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://tech.gluesys.com/blog/2020/02/17/storage_3_intro.html&quot;&gt;https://tech.gluesys.com/blog/2020/02/17/storage_3_intro.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ojava.tistory.com/184&quot;&gt;https://ojava.tistory.com/184&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://jins-dev.tistory.com/entry/Scaling-%EC%9D%98-%EC%A2%85%EB%A5%98%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A0%95%EB%A6%AC&quot;&gt;https://jins-dev.tistory.com/entry/Scaling-%EC%9D%98-%EC%A2%85%EB%A5%98%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A0%95%EB%A6%AC&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[CI/CD가 뭔가요? 실전편]]></title><description><![CDATA[지난번 CI/CD가 뭔가요? - 이론편에서 CI/CD에 대해서 알아보았다. 이번 글에서는 jenkins를 이용해 하나의 CI/CD…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-10-10-jenkins/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-10-10-jenkins/</guid><pubDate>Sun, 10 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;지난번 &lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-08-14-ci-cd/&quot;&gt;CI/CD가 뭔가요? - 이론편&lt;/a&gt;에서 CI/CD에 대해서 알아보았다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 jenkins를 이용해 하나의 CI/CD 환경을 구축하는 방법에 대해 알아보는 시간을 갖도록 하겠다.&lt;/p&gt;
&lt;p&gt;개선 근로에서 구현한 &lt;a href=&quot;https://github.com/dusdn1702/tecoble-match-reviewer&quot;&gt;리뷰 매칭 프로그램&lt;/a&gt; 을 젠킨스를 통해 master branch에 merge되면 새롭게 배포하는 환경을 구축하도록 하자.&lt;/p&gt;
&lt;h2&gt;Jenkins 환경 구축하기&lt;/h2&gt;
&lt;p&gt;우선 jenkins를 위한 서버가 필요하다.&lt;/p&gt;
&lt;p&gt;해당 서버에 자신의 운영체제에 맞는 jenkins를 설치하도록 하자.&lt;/p&gt;
&lt;p&gt;이 글에서는 어느 운영체제에서도 설치할 수 있도록 docker image로 jenkins를 설치하고 관리하도록 하겠다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;docker image 받기&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;docker pull jenkins/jenkins:lts&lt;/code&gt; jenkins 프로세스를 위한 컨테이너를 만들기 위해 image 정보를 받아온다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;docker images&lt;/code&gt; 로 설치받은 이미지들을 확인하자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/43775108/136723125-b5bb9949-a473-4ce3-973a-92d8adbf3d1b.png&quot; alt=&quot;스크린샷 2021-10-11 오전 11 02 57&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;docker container 실행&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;docker run -d -p 8080:8080 --name jenkins jenkins/jenkins&lt;/code&gt; docker를 이용해 8080포트에 jenkins라는 이름의 jenkins 프로세스를 실행한다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;docker ps&lt;/code&gt; 로 동작 중인 컨테이너들을 확인하자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/43775108/136723731-2e74648e-464d-4f09-b755-c5294795770b.png&quot; alt=&quot;스크린샷 2021-10-11 오전 11 12 12&quot;&gt;&lt;/p&gt;
&lt;p&gt;이제 해당 서버의 8080 포트로 접근을 하면 아래와 같이 jenkins에 로그인하라는 문구를 확인할 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/43775108/136801794-f60b8f3c-2dd3-438d-b8df-aa907b047551.png&quot; alt=&quot;스크린샷 2021-10-11 오전 11 13 58&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;docker exec -it jenkins /bin/bash&lt;/code&gt;로 우리가 생성한 컨테이너에 접근하도록 하자.&lt;/p&gt;
&lt;p&gt;컨테이너에 접속한 뒤 &lt;code class=&quot;language-text&quot;&gt;cat /var/jenkins_home/secrets/initialAdminPassword&lt;/code&gt;를 입력한 뒤에 사진의 폴더 속 내용을 조회하면 jenkins에 접근할 수 있는 비밀번호를 확인할 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;로그인하고 나면, Customize Jenkins 페이지가 보이는데, 플러그인 설치에 관한 내용이 보이는데 &lt;code class=&quot;language-text&quot;&gt;Install Suggested Plugins&lt;/code&gt;를 선택하도록 하자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/43775108/136724139-aeddf397-3f21-40e4-a476-8686cc0320a4.png&quot; alt=&quot;스크린샷 2021-10-11 오전 11 18 09&quot;&gt;&lt;/p&gt;
&lt;p&gt;이후 설치가 끝나면 계정 생성 페이지가 나오는데, 계정을 생성하고 나면 아래와 같은 화면을 마주할 수 있다.&lt;/p&gt;
&lt;p&gt;이러면 설치는 모두 끝났다. 이제 jenkins에 프로젝트를 연결해보도록 하자!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/43775108/136724755-88d16817-e467-47b2-9391-97a4014d657e.png&quot; alt=&quot;스크린샷 2021-10-11 오전 11 26 48&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Jenkins를 이용한 CI/CD 환경 구축하기&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;플러그인 확인&lt;/p&gt;
&lt;p&gt;Jenkins에서 shell script를 통한 작업 생성 시에는 &lt;strong&gt;Post build task Plugin&lt;/strong&gt;, gradle 빌드 시 &lt;strong&gt;Gradle Plugin&lt;/strong&gt;, maven 빌드 시 &lt;strong&gt;Maven Plugin&lt;/strong&gt;과 같은 플러그인이 필요하다.&lt;/p&gt;
&lt;p&gt;Jenkins 관리 -&gt; Plugin 관리 -&gt; 설치된 플러그인 목록에 해당되는 플러그인이 존재하지 않는 경우, 설치 가능 탭에서 설치해주도록 하자.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;작업 생성&lt;/p&gt;
&lt;p&gt;새로운 item 탭에 들어가면 다음과 같은 화면을 조회할 수 있을 것이다.&lt;/p&gt;
&lt;img width=&quot;1440&quot; alt=&quot;스크린샷 2021-10-11 오전 11 36 36&quot; src=&quot;https://user-images.githubusercontent.com/43775108/136725453-f65b2ff7-ae5a-4b85-96c2-7465f877cda1.png&quot;&gt;
&lt;p&gt;build를 하면서 보다 복잡한 shell script 실행이 필요한 경우 &lt;strong&gt;pipeline&lt;/strong&gt;을 통한 구축을 하는 것이 좋다.&lt;/p&gt;
&lt;p&gt;지금은 간단한 환경 구축이니 &lt;strong&gt;freestyle&lt;/strong&gt;로 작업을 만들어보도록 하자.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Github Project  등록&lt;/p&gt;
&lt;img width=&quot;1430&quot; alt=&quot;스크린샷 2021-10-11 오전 11 40 20&quot; src=&quot;https://user-images.githubusercontent.com/43775108/136725727-342073cd-9279-47b9-a955-faa759d7d4a4.png&quot;&gt;
&lt;p&gt;위와 같이 General -&gt; Github project에 체크하고 저장소 url을 입력한다.&lt;/p&gt;
&lt;img width=&quot;1433&quot; alt=&quot;스크린샷 2021-10-11 오전 11 43 30&quot; src=&quot;https://user-images.githubusercontent.com/43775108/136725983-a0168c20-031b-40cf-9a84-3d2a6bbd4cc2.png&quot;&gt;
&lt;p&gt;소스 코드 관리 -&gt; Git 체크하고 Repository URL을 다시 한번 입력해준다.&lt;/p&gt;
&lt;p&gt;저장소를 사용하려면 자격 증명이 필요하다. 토큰을 등록해주기 위해 Github 페이지에서 &lt;code class=&quot;language-text&quot;&gt;settings -&gt; Developer Settings -&gt; Personal access tokens -&gt; Generate Access token&lt;/code&gt;으로 접근할 범위를 아래와 같이 선택하고 토큰을 생성하자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/43775108/136726172-25524ffa-bc20-4cb2-af07-ff0ab89ea851.png&quot; alt=&quot;스크린샷 2021-10-11 오전 11 45 58&quot;&gt;&lt;/p&gt;
&lt;p&gt;만들어진 access token을 잘 갖고 있다가 &lt;code class=&quot;language-text&quot;&gt;Credentials&lt;/code&gt;에 Add를 클릭해 자격 증명을 해주자.&lt;/p&gt;
&lt;img width=&quot;1428&quot; alt=&quot;스크린샷 2021-10-11 오전 11 49 22&quot; src=&quot;https://user-images.githubusercontent.com/43775108/136726407-5ce76559-171f-48b1-97de-f9e5befab98a.png&quot;&gt;
&lt;p&gt;Kind는 &lt;code class=&quot;language-text&quot;&gt;Username with password&lt;/code&gt;를 선택하고 &lt;code class=&quot;language-text&quot;&gt;Username&lt;/code&gt;에는 자신의 github 아이디, &lt;code class=&quot;language-text&quot;&gt;password&lt;/code&gt;에는 발급받은 토큰을 입력해주도록 한다.&lt;/p&gt;
&lt;p&gt;Credential 추가 후에는 소스코드관리 &lt;code class=&quot;language-text&quot;&gt;Credentials&lt;/code&gt; 탭에서 생성한 Credential을 입력하도록 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build trigger 추가&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Branches to build&lt;/code&gt;에는 빌드 할 브랜치명을 선택해주고, &lt;code class=&quot;language-text&quot;&gt;빌드 유발&lt;/code&gt;에는 Github hook trigger for GITScm polling을 선택해준다. 이것은 Github의 webhook으로부터 빌드를 유발받겠다는 의미이다.&lt;/p&gt;
&lt;img width=&quot;1429&quot; alt=&quot;스크린샷 2021-10-11 오전 11 53 04&quot; src=&quot;https://user-images.githubusercontent.com/43775108/136726689-b65a2429-e3c4-4208-9bc6-cc8599118b21.png&quot;&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build 추가&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Build -&gt; invoke Gradle script&lt;/code&gt;를 선택하고 Tasks에 수행할 빌드를 적어준다.&lt;/p&gt;
&lt;p&gt;이때, gradle wrapper를 사용하는 경우 아래와 같이 작성한다.&lt;/p&gt;
&lt;img width=&quot;1433&quot; alt=&quot;스크린샷 2021-10-11 오후 9 40 36&quot; src=&quot;https://user-images.githubusercontent.com/43775108/136791522-41893512-f4f7-4037-9c1b-ee66cc046b55.png&quot;&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build 후 조치&lt;/p&gt;
&lt;p&gt;Log text에는 로깅 내용이 일치하는지 확인을 위한 용도로 사용한다.&lt;/p&gt;
&lt;p&gt;Script에는 빌드 성공 후 배포할 스크립트를 적는다.&lt;/p&gt;
&lt;img width=&quot;1435&quot; alt=&quot;스크린샷 2021-10-11 오후 8 46 03&quot; src=&quot;https://user-images.githubusercontent.com/43775108/136784859-50c18cb5-bae3-483b-93ed-5e8cf637aabc.png&quot;&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;저장&lt;/p&gt;
&lt;p&gt;Apply 와 저장을 클릭해준다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이제 기본적인 환경 구축은 끝났다! 원하는대로 잘 동작하는지 확인해보도록 하자.&lt;/p&gt;
&lt;p&gt;하지만 이게 웬일! build now를 클릭하고 console output을 확인해보면 아래와 같이 SUCCESS이지만, 서버 상에 배포가 되지 않았다.&lt;/p&gt;
&lt;img width=&quot;1079&quot; alt=&quot;스크린샷 2021-10-11 오후 9 41 07&quot; src=&quot;https://user-images.githubusercontent.com/43775108/136791564-8f233bb1-9274-4e58-9fb2-af49db17d2a2.png&quot;&gt;
&lt;p&gt;이것은 docker로 jenkins를 구축했기 때문이다. 우리는 배포 서버에 해당 동작이 수행되기를 원했지만, 실제로는 jenkins를 담당하는 CI/CD 서버에서 동작이 수행된 것이다.&lt;/p&gt;
&lt;p&gt;이럴 경우 배포 서버에 jenkins의 &lt;code class=&quot;language-text&quot;&gt;publish over SSH&lt;/code&gt; 기능을 이용해 jar 파일을 전송하고 실행하면 원하는 대로 배포 서버와 CI/CD 서버를 따로 구축할 수 있다.&lt;/p&gt;
&lt;h2&gt;Send publish over SSH&lt;/h2&gt;
&lt;p&gt;우선 publish over SSH 플러그인 설치가 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Jenkins 관리 -&gt; 시스템 설정&lt;/code&gt;에서 다음과 같은 설정을 하도록 하자.&lt;/p&gt;
&lt;img width=&quot;1426&quot; alt=&quot;스크린샷 2021-10-11 오후 10 29 04&quot; src=&quot;https://user-images.githubusercontent.com/43775108/136798380-b1dfc9f4-b1a4-4493-847b-8c5814f66e76.png&quot;&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Key&lt;/code&gt;에 SSH 통신을 위한 자신의 pem키&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Hostname&lt;/code&gt;에 file을 보낼 ip 주소&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Username&lt;/code&gt;에 자신의 서버 주인&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Remote Directory&lt;/code&gt;에 기본 위치를 등록하도록 하자.&lt;/p&gt;
&lt;img width=&quot;1421&quot; alt=&quot;스크린샷 2021-10-11 오후 10 32 38&quot; src=&quot;https://user-images.githubusercontent.com/43775108/136798854-d1d14ba5-7dbd-47e3-b0fb-7f510272b933.png&quot;&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Name&lt;/code&gt;에 자신이 등록한 이름&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Source files&lt;/code&gt;에 jenkins에서 build한 jar 파일의 위치&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Remove prefix&lt;/code&gt;에 제거할 주소&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Remote directory&lt;/code&gt;에 file을 저장할 위치&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Exec command&lt;/code&gt;에 file을 실행할 명령어를 입력하도록 하자.&lt;/p&gt;
&lt;p&gt;이렇게 하면 모든 준비는 끝났다.&lt;/p&gt;
&lt;p&gt;Build Now를 하면 아래와 같이 Transferred 1 files 와 함께 모든 실행이 성공한 것을 볼 수 있을 것이다.&lt;/p&gt;
&lt;img width=&quot;1159&quot; alt=&quot;스크린샷 2021-10-11 오후 10 35 13&quot; src=&quot;https://user-images.githubusercontent.com/43775108/136799229-f508746c-7534-4455-9505-68e7c02428f4.png&quot;&gt;
&lt;p&gt;github repository에서 GitHub webhook을 등록하고 나면 이제 push 될 때마다 jenkins가 실행되는 것을 볼 수 있을 것이다.&lt;/p&gt;
&lt;h2&gt;마치며&lt;/h2&gt;
&lt;p&gt;이렇게 Jenkins를 이용한 CI/CD 환경 구축이 모두 끝이 났다.&lt;/p&gt;
&lt;p&gt;상황에 따라 추가적인 설정이 필요할 수도, 필요 없을 수도 있지만 이 글을 통해서 CI/CD가 어떤 식으로 동작하는지 흐름이라도 이해할 수 있었기를 바란다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[리액트 쿼리로 에러처리하기]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-10-01-react-query-error-handling/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-10-01-react-query-error-handling/</guid><pubDate>Mon, 04 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;현재 팀 프로젝트를 진행하고 있는 놀토 팀에서는 &lt;code class=&quot;language-text&quot;&gt;리액트 쿼리 + 에러 바운더리&lt;/code&gt;를 통해서 에러 처리를 하고 있다. 에러 처리를 하면서 겪었던 어려움이 머리 속에 정리가 잘 안돼있다고 느껴서 글을 작성하게 되었다.&lt;/p&gt;
&lt;p&gt;같은 환경으로 에러 처리를 구현하려고 한다면 글이 참고가 될 것이다.&lt;/p&gt;
&lt;h2&gt;알면 좋은 용어&lt;/h2&gt;
&lt;h3&gt;QueryCache&lt;/h3&gt;
&lt;p&gt;react query의 저장소 메커니즘. react query의 모든 데이터를 저장한다.
일반적으로 개발자가 직접 QueryCache에 접근할 일은 거의 없고 특정 cache에 접근하기 위해서는 QueryClient를 사용한다.&lt;/p&gt;
&lt;h3&gt;Observer&lt;/h3&gt;
&lt;p&gt;리액트 쿼리에서 하나의 캐시 entry를 감시하는 객체를 의미한다. 캐시 entry의 무언가가 변경되면 Observer가 알 수 있다.&lt;/p&gt;
&lt;p&gt;Observer를 생성하기 가장 쉬운 방법은 &lt;code class=&quot;language-text&quot;&gt;useQuery()&lt;/code&gt; 함수를 호출하는 것이다.&lt;/p&gt;
&lt;p&gt;useQuery() 함수를 사용하는 컴포넌트가 리렌더링 될 때마다 useQuery()를 호출하기 때문에 매번 새로운 Observer를 생성한다.
즉, &lt;strong&gt;여러번 리렌더링되면 하나의 캐시 entry에 대해 여러 개의 Observer를 가질 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;리액트 쿼리로 에러 처리를 할 때 가장 먼저 발견하게 되는 것은 useQuery의 &lt;code class=&quot;language-text&quot;&gt;onError&lt;/code&gt; 옵션이다. onError 옵션으로 에러 핸들러를 넘겨 리액트 쿼리가 처리하도록 하는 것이다.&lt;/p&gt;
&lt;p&gt;그러나 onError가 처음에는 잘 동작하는 것처럼 보일지도 모르지만, 곧 이상함을 느끼게 된다. &lt;strong&gt;하나의 네트워크 요청에 대한 오류가 발생했을 뿐인데 onError로 등록한 에러 핸들러가 여러 번 호출&lt;/strong&gt;되는 현상이 발생한다.&lt;/p&gt;
&lt;p&gt;오늘은 필자가 겪었던 문제와 해결 방법을 같이 따라가면서 상황에 따라 어떤 에러 처리 방식이 적절한지 알아보자.&lt;/p&gt;
&lt;h2&gt;1. 리액트 쿼리의 onError 활용하기&lt;/h2&gt;
&lt;p&gt;네트워크 요청에 대한 응답을 받아와 캐시에 저장하는 &lt;code class=&quot;language-text&quot;&gt;useQuery()&lt;/code&gt;에는 onError 옵션이 있다. 이 옵션으로 에러 핸들러를 넘기면 네트워크 요청이 실패했을 때 자동으로 콜백을 실행 시켜준다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getRecentFeeds&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; axios&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/feeds/recent&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;recentFeeds&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; getRecentFeeds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;onError&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;에러 발생했지롱&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위처럼 코드를 작성하면 에러가 발생했을 때 onError에 등록된 에러 핸들러가 자동으로 실행된다.&lt;/p&gt;
&lt;h3&gt;문제점&lt;/h3&gt;
&lt;p&gt;onError를 사용하면 에러 처리가 끝난 것인가?
사실 &lt;strong&gt;onError로 에러 처리를 하면 의도대로 동작하지 않는 경우가 많다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;예를 들어 useQuery()를 사용하는 RecentFeedsContent 컴포넌트가 있다고 생각해보자. 처음 useQuery 훅을 사용하게 되면 어떤 요청을 보내고 pending 상태가 될 것이다. 이 상태에서 상위 컴포넌트에 의해 RecentFeedsContent가 4번 리렌더링 된다면 해당 useQuery 요청이 실패했을 때 onError 콜백은 4번 호출된다.
스낵바로 사용자에게 알리는 콜백 코드를 작성했다면 스낵바가 4번 동작하는 것이다.&lt;/p&gt;
&lt;h3&gt;원인&lt;/h3&gt;
&lt;p&gt;에러 콜백 호출 횟수가 컴포넌트의 리렌더링 횟수를 따라가는 이유는 무엇일까?&lt;/p&gt;
&lt;p&gt;그 이유는 useQuery()가 호출될 때마다 리액트 쿼리는 새로운 &lt;code class=&quot;language-text&quot;&gt;Observer&lt;/code&gt;를 만들고 onError는 Observer level에서 동작하기 때문이다.&lt;/p&gt;
&lt;h2&gt;2. useEffect 활용하기&lt;/h2&gt;
&lt;p&gt;앞서 onError의 문제점을 살펴봤다. 그럼 이 문제를 어떻게 해결할 수 있을까?&lt;/p&gt;
&lt;p&gt;아주 간단한 방법으로 useEffect를 사용하는 방법이 있다. useQuery를 훅으로 감싸고, 훅 내에서 useQuery의 isError가 true로 변하는 시점에만 에러 핸들러를 호출하는 것이다.&lt;/p&gt;
&lt;p&gt;예를 들면 다음과 같은 방식이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//useRecentFeeds.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useRecentFeeds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;errorHandler&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; query &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; useQuery&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Feed&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Error&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;recentFeeds&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; getRecentFeeds&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isError&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;errorHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isError&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; query&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;//RecentFeedsContent.tsx&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;RecentFeedsContent&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; recentFeeds&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useRecentFeeds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;이제 한 번만 에러 핸들러 실행!!&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 구현하면 query.isError가 true로 변할 때만 에러 핸들러가 실행된다.&lt;/p&gt;
&lt;h3&gt;문제점&lt;/h3&gt;
&lt;p&gt;대부분은 useEffect를 사용하면 문제가 해결될 것이다. 에러 처리 끝…! 이라고 하고 싶었으나 아직 문제가 남았다. &lt;strong&gt;만약 &lt;code class=&quot;language-text&quot;&gt;에러 바운더리&lt;/code&gt;를 사용하고 있다면 문제가 생길 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;예를 들면 다음과 같은 상황이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useRecentFeeds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;errorHandler&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; query &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; useQuery&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Feed&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Error&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;recentFeeds&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; getRecentFeeds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;useErrorBoundary&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 에러 바운더리 사용, 쿼리 훅마다 설정도 가능하지만&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// QueryClient를 이용해서 전역적으로 설정도 가능하다.&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ----- 여기부터는 실행되지 않는다. -----&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isError&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;errorHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isError&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; query&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;에러 바운더리를 사용하면 useQuery() 함수까지만 실행되고 이후에 작성된 useEffect 함수는 실행되지 않는다. &lt;strong&gt;당연히 에러 핸들러도 실행되지 않는다.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;원인&lt;/h3&gt;
&lt;p&gt;그 이유는 에러 바운더리를 사용한다는 의미가 에러가 발생했을 때 외부로 에러를 그대로 전파하고, 외부의 에러 바운더리 컴포넌트가 이것을 처리하겠다는 의미이기 때문이다.&lt;/p&gt;
&lt;p&gt;그래서 useQuery() 함수에서 에러를 throw하므로 useQuery 이후에 오는 모든 코드는 평가되지 않는다.&lt;/p&gt;
&lt;h2&gt;3. 커스텀 에러 클래스 정의하기&lt;/h2&gt;
&lt;p&gt;우리 팀은 이 문제를 커스텀 에러 클래스를 정의함으로써 해결했다. 커스텀 에러 인스턴스를 생성할 때 에러 핸들러를 등록하고, 에러 바운더리에서 핸들러를 실행하도록 한 것이다. 이런 방식을 사용하면 컴포넌트 level에서 에러 핸들러를 정의할 수 있으면서 핸들러도 단 한 번만 실행된다.&lt;/p&gt;
&lt;p&gt;예를 들면 다음과 같은 방식이다.&lt;/p&gt;
&lt;h3&gt;1️⃣ 커스텀 에러 클래스 정의&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// CustomError.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomError&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; string&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;errorHandler&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ErrorHandler&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;message&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; string&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; errorHandler&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ErrorHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// new 연산자로 호출된 생성자의 이름&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;errorHandler &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; errorHandler&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;executeErrorHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;errorHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;errorHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 에러 핸들러는 핸들링 대상 에러를 핸들러로 받음.&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;에러 인스턴스를 생성할 때 에러 핸들러도 같이 등록할 수 있게 하였다. 등록된 에러 핸들러는 이후에 에러 바운더리 컴포넌트가 &lt;code class=&quot;language-text&quot;&gt;executeErrorHandler()&lt;/code&gt;메서드를 호출함으로써 처리될 것이다.&lt;/p&gt;
&lt;h3&gt;2️⃣ useQuery 커스텀 훅 수정&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getRecentFeeds&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;errorHandler&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; axios&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/feeds/recent&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;recent 피드에서 에러발생!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; errorHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 여기서 throw된 에러는 에러 바운더리 컴포넌트까지 전파됨&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useRecentFeeds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;errorHandler&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; useQuery&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Feed&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; CustomError&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;recentFeeds&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRecentFeeds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;errorHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 인자로 errorHandler를 전달.&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;useErrorBoundary&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;생략된 부분이 많지만 가장 중요한 코드만을 담았다. axios에서 throw된 에러를 잡아 커스텀 에러로 재가공해서 외부로 전파하였다.&lt;/p&gt;
&lt;h3&gt;3️⃣ 에러 바운더리에서 커스텀 에러 처리&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ErrorBoundary&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Component&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Props&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; State&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; State &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;hasError&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getDerivedStateFromError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; State &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;hasError&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;componentDidCatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token literal-property property&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;errorInfo&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ErrorInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Uncaught error in Error Boundary:&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; errorInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;executeErrorHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1번 과정에서 커스텀 에러 클래스에 정의했던 에러 핸들러를 실행&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hasError&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fallback&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;CustomError 인스턴스를 생성할 때 넣어준 에러 핸들러를 에러 바운더리에서 처리해준다.
에러 바운더리가 에러를 처리하게 되면 바운더리가 감싸고 있는 컴포넌트들을 errorFallback 컴포넌트로 대체하므로 에러 핸들러는 무조건 한 번만 실행된다.&lt;/p&gt;
&lt;h3&gt;4️⃣ 최종 결과&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//App.tsx&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ErrorBoundary fallback&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;에러났어요&lt;span class=&quot;token operator&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;RecentFeedsContent &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;ErrorBoundary&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;//RecentFeedsContent.tsx&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;RecentFeedsContent&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; recentFeeds &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useRecentFeeds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;에러 처리 완료 with 에러 바운더리&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 에러 바운더리를 사용하면서도 에러 핸들러가 한 번만 실행되는 구조가 만들어졌다.
조금 구현이 복잡해 보이지만 사용하는 입장에서는 위와 같이 간단하므로 큰 문제가 없을 것으로 보인다.&lt;/p&gt;
&lt;p&gt;이렇게 구현하니 다음과 같은 장점을 느낄 수 있었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;useQuery 커스텀 훅을 사용하는 컴포넌트에서 에러 핸들러를 정의할 수 있어 자유도가 높다.
같은 커스텀 훅을 사용하더라도 컴포넌트마다 다른 에러 처리 방법을 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;에러 바운더리의 선언적 에러 처리라는 이점을 그대로 가져갈 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;오늘은 리액트 쿼리로 에러 처리할 때 만나볼 수 있는 문제점을 같이 들여다봤다.
무조건 에러 처리를 커스텀 에러 클래스를 정의해서 처리할 필요는 없고, 상황에 따라 적절한 방법을 사용하면 된다.&lt;/p&gt;
&lt;p&gt;우리 놀토 팀은 Observer 관련 문제인지도 몰랐고 리액트 쿼리와 에러 바운더리를 같이 사용하고 있었기 때문에 굉장히 고통받았는데
혹시 우리와 같은 문제를 겪고 있다면 좋은 길잡이가 되었기를 바라면서 글을 맺는다.&lt;/p&gt;
&lt;h2&gt;Thanks to&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tkdodo.eu/blog/react-query-error-handling#the-onerror-callback&quot;&gt;React Query Error Handling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;고통받았던 놀토 팀원(&lt;a href=&quot;https://github.com/zigsong&quot;&gt;zigsong&lt;/a&gt;, &lt;a href=&quot;https://github.com/0307kwon&quot;&gt;0307kwon&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[전략패턴과 커맨드패턴]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-10-04-strategy-command-pattern/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-10-04-strategy-command-pattern/</guid><pubDate>Mon, 04 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;인터페이스를 구현하여 사용하는 두 가지 패턴인 전략 패턴과 커맨드 패턴.
자주 사용하지만, 이 둘의 차이점은 무엇일까?
알쏭달쏭한 전략 패턴과 커맨드 패턴의 차이점을 알아보는 전략 패턴과 커맨드 패턴 글입니다.&lt;/p&gt;
&lt;!-- end --&gt;
&lt;h2&gt;학습 로그 정리 중에 만난 복병&lt;/h2&gt;
&lt;p&gt;지난 우아한테크코스 레벨 2 때는 크루들끼리 레벨 1 때 배운 내용을 학습 로그를 통하여 돌아보는 내용을 가지게 됩니다.
자바 및 객체에 대한 것이 주 내용을 이루게 됩니다.
객체를 공부하다 보니 대표적인 디자인 패턴도 몇 가지 나오게 되었습니다.
특히 미션에서 전략 패턴 및 커맨드 패턴을 사용하였기 때문에 해당 내용도 정리하였는데요.
하지만 한 크루의 “그럼 와이비, 전략 패턴과 커맨드 패턴의 차이점은 무엇인가요?”라는 질문에 시원하게 차이점을 답변하지 못하였던 경험이 있습니다.
인터페이스에 의존하도록 만들고, 이를 이용하여 유연성을 가지도록 구현하는 점이 상당히 비슷하게 느껴졌기 때문입니다.
이를 돌이켜보며, 같은 인터페이스를 구현하여 사용하는 디자인 패턴이지만 서로 다른 전략 패턴과 커맨드 패턴의 차이점을 예시와 함께 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h2&gt;전략 패턴과 예시&lt;/h2&gt;
&lt;p&gt;와이비는 오늘도 일확천금의 꿈을 꿈꾸며 복권을 사러 가게 되었습니다.
하지만 와이비의 집과 복권 판매점의 거리가 상당히 멀어서 어떻게 가야 할 것인가에 대해서 고민을 하게 되었습니다.
이때, 와이비가 갈 방법은 버스나 자전거를 사용하는 방법이 있습니다.
이 두 방법을 유연하게 선택하여 코드로 구현할 방법은 무엇일까요? &lt;br&gt;
이에 대한 해결책이 바로 전략 패턴입니다.
전략 패턴은 프로그램이 진행되면서 캡슐화된 로직을 선택할 수 있게 하는 디자인 패턴입니다.
로직 실행은 인터페이스에 의존을 시키고 인터페이스를 구현한 로직들을 전달해줌으로써 분기처리 없이 유연성을 갖출 수가 있습니다.
위의 예시를 전략 패턴을 적용하여 구현해보도록 하겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PeopleWithMovement&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Transportation&lt;/span&gt; transportation&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PeopleWithMovement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Transportation&lt;/span&gt; transportation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;transportation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; transportation&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; end&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        transportation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;start&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; end&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;changeTransporation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Transportation&lt;/span&gt; transportation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;transportation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; transportation&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;먼저 전략을 행할 주체를 작성을 하였습니다.
여기서 전략은 Transporation이라는 인터페이스로 분리되어 캡슐화되어 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Transportation&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; end&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Bicycle&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Transportation&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; end&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;출발점 : &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; start &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;에서 목적지 : &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; end &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;까지 `자전거`로 이동합니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Bus&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Transportation&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; end&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;출발점 : &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; start &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;에서 목적지 : &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; end &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;까지 `버스`로 이동합니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Transporation 인터페이스를 구현한 두 가지 전략 Bus와 Bicycle 코드도 작성하게 되었습니다.
이렇게 전략을 캡슐화를 함으로써, 예시 코드처럼 Bus 및 Bicycle을 선택하여 실행할 수 있으며, 중간에 전략을 바꿔서 진행하는 것도 가능합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Main&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Bicycle&lt;/span&gt; bicycle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Bicycle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Bus&lt;/span&gt; bus &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Bus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;PeopleWithMovement&lt;/span&gt; whybeFirst &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PeopleWithMovement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bicycle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        whybeFirst&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;시작점&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;끝점&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;PeopleWithMovement&lt;/span&gt; whybeSecond &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PeopleWithMovement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bus&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        whybeSecond&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;시작점&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;끝점&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;PeopleWithMovement&lt;/span&gt; whybeChangeMovement &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PeopleWithMovement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bicycle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        whybeChangeMovement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;시작점&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;중간지점&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        whybeChangeMovement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;changeTransporation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bus&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        whybeChangeMovement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;중간지점&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;끝점&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;출발점 &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; 시작점에서 목적지 &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; 끝점까지 &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;자전거&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;로 이동합니다.
출발점 &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; 시작점에서 목적지 &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; 끝점까지 &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;버스&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;로 이동합니다.
출발점 &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; 시작점에서 목적지 &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; 중간지점까지 &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;자전거&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;로 이동합니다.
출발점 &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; 중간지점에서 목적지 &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; 끝점까지 &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;버스&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;로 이동합니다.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;커맨드 패턴과 예시&lt;/h2&gt;
&lt;p&gt;이렇게 와이비는 복권 판매점에 도착하게 되었습니다.
복권 판매점에서는 여러 가지 종류의 복권을 판매합니다.
와이비는 그 중 즉석 복권과 번호식 복권을 사게 되었습니다.
즉석 복권은 바로 당첨 여부를 확인할 수가 있지만, 번호식 복권의 경우에는 당첨을 확인하려면 당첨 번호가 필요합니다.
이동수단을 고를 때보다 조금 더 까다로워지게 되었습니다.
이를 코드로 유연하게 구현하려면 어떻게 해야 할까요? &lt;br&gt;
답은 커맨드 패턴에 있습니다.
커맨드 패턴은 요청을 홀로 처리할 수 있도록 요청을 수행하는 여러 인자를 함께 패키징하여 나중에 처리할 수 있도록 만들어주는 행동 중심 디자인 패턴입니다.
커맨드를 실행시키는 객체는 커맨드 내부의 요소에 대해서 숨김으로써 코드의 유연성을 가질 수 있습니다.
앞서 언급한 각각의 복권은 당첨을 확인할 수 있는 조건이 다릅니다.
먼저 즉석 복권을 긁는 행위를 캡슐화한 코드를 작성하도록 하겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PeopleWithLottery&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LotteryCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; lotteryCommands&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PeopleWithLottery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LotteryCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; lotteryCommands&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lotteryCommands &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lotteryCommands&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;addLotteryCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LotteryCommand&lt;/span&gt; lotteryCommand&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        lotteryCommands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lotteryCommand&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scratchAllLottery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; lotteryCommands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;LotteryCommand&lt;/span&gt; lotteryCommand &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lotteryCommands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            lotteryCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;scratch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//초기화&lt;/span&gt;
        lotteryCommands &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LinkedList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LotteryCommand&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scratch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InstantScratch&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LotteryCommand&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InstantLottery&lt;/span&gt; instantLottery&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; account &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InstantScratch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InstantLottery&lt;/span&gt; instantLottery&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt; account&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;instantLottery &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; instantLottery&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;account &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scratch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;//instantLottery의 당첨을 확인하고 account에 돈을 집어 넣는 로직&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InstantLottery&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; win&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InstantLottery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; win&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;win &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; win&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isWin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; win&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; balance&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;putMoney&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        balance &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; money&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위의 코드를 바탕으로 프로그램을 구성하면 다음과 같습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Main&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;PeopleWithLottery&lt;/span&gt; whybe &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PeopleWithLottery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LinkedList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt; 와이비통장 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        
        &lt;span class=&quot;token comment&quot;&gt;//즉석복권 구입&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;//즉석복권 생성 로직 &lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;InstantLottery&lt;/span&gt; instantLottery &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InstantLottery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;당첨여부&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;//즉석복권긁기행위 객체 생성 및 커맨드 목록에 추가&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;InstantScratch&lt;/span&gt; 즉석복권긁기커맨드 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InstantScratch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;즉석복권&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 와이비통장&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            whybe&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addLotteryCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;즉석복권긁기커맨드&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        
        whybe&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;scratchAllLotery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;해당 코드에서 볼 수 있다시피 당첨을 확인하고 통장에 돈을 추가하는 것을 캡슐화하게 되었습니다.
여기서 알 수 있는 점은 복권을 긁는 행위를 호출하는 객체(People), 명령을 담당하는 객체(LotteryCommand) 그리고 명령 수행으로 인하여 영향을 받는 객체(MyAccount)가 모두 다른 것을 알 수가 있습니다.
각각의 객체들을 분리함으로써 행위의 구성 요소들을 독립적으로 쓸 수 있다는 점입니다. &lt;br&gt; &lt;br&gt;
그 다음 예시를 통해 조금 더 자세히 알아보도록 하겠습니다.
와이비는 영이와의 내기를 통해서 번호식 복권의 당첨금은 영이의 통장으로 넣어주기로 하였습니다.
하지만 번호식 복권같은 경우에는 당첨을 확인하기 위해서는 당첨 번호가 필요합니다.
이를 반영한 번호식 복권의 코드 및 다른 사람의 통장에 대한 로직은 다음과 같이 작성될 수 있을 것입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberScratch&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LotteryCommand&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; winners&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberLottery&lt;/span&gt; numberLottery&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt; account&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberScratch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; winners&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberLottery&lt;/span&gt; numberLottery&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt; account&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;winners &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; winners&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;numberLottery &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; numberLottery&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;account &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; account&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scratch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// winners와 numberLottery를 비교하여 당첨금을 계산하고 &lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// account에 해당 금액을 입금하는 로직&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;   
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberLottery&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; numbers&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberLottery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; numbers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;numbers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; numbers&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rank&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; winners&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 당첨 번호와 비교하여 자신의 등수를 계산하는 로직&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위의 코드를 바탕으로 프로그램을 구성하면 다음과 같습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Main&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;PeopleWithLottery&lt;/span&gt; whybe &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PeopleWithLottery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LinkedList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt; 영이통장 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        
        &lt;span class=&quot;token comment&quot;&gt;//즉석복권 구입&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;//번호식 복권 생성 로직 &lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;NumberLottery&lt;/span&gt; 번호식복권 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberLottery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;선택한 번호&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;//번호식복권긁기행위 객체 생성 및 커맨드 목록에 추가&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;NumberScratch&lt;/span&gt; 번호식복권긁기커맨드 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberScratch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;이번주 당첨번호&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 번호식복권&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 영이통장&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            whybe&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addLotteryCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;번호식복권긁기커맨드&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        whybe&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;scratchAllLottery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;예시에서는 기준이 되는 당첨 번호를 비교하여 해당하는 금액을 영이의 통장으로 돈을 넣어주는 코드를 완성하게 되었습니다.
행위를 캡슐화하였기 때문에 복권을 긁는 행위를 호출하는 객체인 PeopleWithLottery와 명령 수행으로 인하여 영향을 받는 객체에 큰 변화를 거치지 않고도 로직을 수행할 수 있게 되었습니다.
그렇다면 같은 인터페이스에 의존하여 유연성을 갖도록 구현하는 전략 패턴과의 차이는 무엇일까요?&lt;/p&gt;
&lt;h2&gt;“어떻게” 와 “무엇” 의 차이&lt;/h2&gt;
&lt;p&gt;전략 패턴은 먼저 &lt;strong&gt;어떻게&lt;/strong&gt; 라는 측면에 집중하게 됩니다.
하고자 하는 것은 이미 정해져 있고, 방법을 어떻게 할지에 대한 유연성을 고려하며 구현합니다.
인터페이스의 메소드에 직접적으로 의존을 하게 되어서, 해당 메소드의 parameter들에 강하게 영향을 받습니다.
이 때문에 위의 복권예시같은 로직을 수행하여 다른 인자가 필요하게 된다면 오버로딩을 해주어야하는데, 전략 패턴의 가치가 퇴색이 될 것입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 만약 버스를 타는데에는 요금이 필요하게 된다면?&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Transportation&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; end&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; end&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Bicycle&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Transportation&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; end&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;출발점 : &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; start &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;에서 목적지 : &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; end &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;까지 자전거로 이동합니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; end&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;버스가 아닙니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Bus&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Transportation&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
     &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; end&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;자전거가 아닙니다&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; end&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;버스 요금으로 &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;만큼 지불했습니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;출발점 : &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; start &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;에서 목적지 : &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; end &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;까지 버스로 이동합니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;하지만 커맨드 패턴은 &lt;strong&gt;무엇&lt;/strong&gt;을 초점을 두게 됩니다.
어떻게 할지에 대한 방법은 외부에서 정의하며 주입을 해주며, 그것을 실행하는 것이 중요하기 때문입니다.
그래서 즉석 복권 혹은 번호식 복권 중 &lt;strong&gt;무엇&lt;/strong&gt;을 긁을지에 대해서 구현할 때에는 전략 패턴보다는 커맨드 패턴이 조금 더 적합하다고 볼 수가 있습니다.
위의 예시에서는 커맨드 패턴은&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;무엇을 할지를 선택하면 해당 행위를 하기 위한 필요한 변수들을 같이 캡슐화하여 제공받기 때문에 유연하다.&lt;/li&gt;
&lt;li&gt;Receiver(Account)도 같이 제공을 받기 때문에 행위에 따른 영향을 받는 객체도 조건에 따라 다르게 설정할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;의 장점을 알 수가 있습니다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;결과물이 비슷해 보이지만, 다른 방향의 두 패턴 탐구를 통해서 차이점 및 각각의 장점을 볼 수가 있었습니다.
하지만 무조건 커맨드 패턴이 전략 패턴보다 유리한 것일까요? &lt;br&gt;
그렇지 않습니다. 위의 코드에서도 볼 수 있다시피 커맨드 패턴을 구현하려면 각각의 행위를 캡슐화를 해야 하기 때문에 비약적으로 코드의 양이 상승하게 됩니다.
디자인 패턴에는 항상 옳은 것이 없는 것 같습니다. 각각의 로직 및 환경에 적합한 코드를 작성하는 것이 중요하다고 할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/4834979/difference-between-strategy-pattern-and-command-pattern&quot;&gt;Difference between Strategy Pattern and Command Pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://refactoring.guru/design-patterns/strategy&quot;&gt;Strategy Pattern by Refactoring Guru&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://refactoring.guru/design-patterns/command&quot;&gt;Command Pattern by Refactoring Guru&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/37773648/what-does-the-client-do-in-command-pattern&quot;&gt;What does the client do in Command Pattern?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[TCP/IP]]></title><description><![CDATA[TCP/IP TCP TCP는 Transmission Control Protocol…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-10-02-tcp-ip/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-10-02-tcp-ip/</guid><pubDate>Sat, 02 Oct 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;TCP/IP&lt;/h2&gt;
&lt;h3&gt;TCP&lt;/h3&gt;
&lt;p&gt;TCP는 Transmission Control Protocol의 약어로서 인터넷상의 데이터를 주고받는 과정을 제어하는 통신 표준을 의미합니다. 이 프로토콜은 데이터를 교환하는 기기 간 데이터의 무결성(정확성)과 전달의 신뢰성을 보장하기 때문에 거의 모든 애플리케이션 수준의 프로토콜(FTP, SSH, HTTP 등)에서 사용하고 있습니다.&lt;/p&gt;
&lt;h3&gt;IP&lt;/h3&gt;
&lt;p&gt;Internet Protocol이라고 하며 인터넷을 통해 데이터를 전송하기 위해 지켜야 하는 규칙입니다. 이 프로토콜은 &lt;code class=&quot;language-text&quot;&gt;183.101.195.193&lt;/code&gt;과 같은 IP 주소를 통해 데이터를 전달할 위치를 파악합니다. 쉽게 생각해 우리가 편지를 부칠 때 주소를 적어야 하는 것처럼, 컴퓨터에서 데이터를 전송할 때 전송할 목적지가 필요한데 그 역할을 해주는 매개체가 바로 IP 주소인 셈입니다.&lt;/p&gt;
&lt;p&gt;데이터를 전송할 때는 하나의 완전한 데이터를 바로 보내지 않고 분할 작업을 거치게 되는데, 이때 분할되어 작아진 데이터 단위를 패킷(Packet)이라고 합니다. 여기서 데이터를 왜 패킷 단위로 나누어야 할까요? 그것은 전송 과정이 실패했을 때 처음부터 다시 데이터를 보내는 건 소모적이고 데이터가 완전히 전송될 때까지 다른 데이터가 대기해야 할 수도 있기 때문입니다. 이러한 이유로 패킷은 송신 측에서 분할 전송 후에 수신 측에서 완전한 데이터로 재결합하여 사용합니다.&lt;/p&gt;
&lt;p&gt;참고 - &lt;a href=&quot;https://www.cloudflare.com/learning/network-layer/what-is-a-packet/&quot;&gt;What is a packet?&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;TCP vs IP&lt;/h3&gt;
&lt;p&gt;TCP와 IP는 각기 다른 목적을 가진 프로토콜이지만 인터넷 통신을 위해서는 대부분 두 프로토콜을 함께 사용하기 때문에 흔히 TCP/IP라고 부릅니다. 즉, TCP는 IP에 대한 대체가 아니라 보완을 목적으로 두는 프로토콜입니다. 만약 IP를 통해서만 데이터를 주고받는다면 원치 않는 이슈를 맞닥뜨릴 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;비연결성 - 패킷을 받을 대상을 확인하지 않고 패킷을 전달&lt;/li&gt;
&lt;li&gt;비신뢰성 - 패킷이 유실되거나 잘못된 순서로 전달&lt;/li&gt;
&lt;li&gt;IP 주소의 한계 - 동일 IP 내에서 구동 중인 프로그램이 두 개 이상일 때 패킷의 목적지 구분 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TCP는 위와 같은 문제를 사전에 방지하고자 3 Way-Handshake나 PORT와 같은 메커니즘을 주요한 특징으로 삼고 있습니다. 그리고 우리는 이러한 메커니즘을 잘 알아야 TCP가 어떻게 IP를 보완하는지 알 수 있으므로 두 메커니즘의 특징을 좀 더 살펴보도록 하겠습니다.&lt;/p&gt;
&lt;h3&gt;3 Way-Handshake&lt;/h3&gt;
&lt;p&gt;데이터를 보내는 기기와 데이터를 받는 기기 간에 데이터 교환이 이루어지기 전에, 기본적으로 두 기기는 서로 동기화되어야 합니다. 따라서 TCP는 송, 수신 측에서 데이터를 주고받기 전에 논리적인 접속을 확립합니다. 이러한 접속을 커넥션이라고 하며, 이는 양측 모두 데이터를 전송할 준비가 되었다는 것을 보장합니다.&lt;/p&gt;
&lt;p&gt;커넥션을 확립하기 위해서는 우선 동기화 목적의 패킷을 교환해야 하는데, 해당 패킷이 담고 있는 정보 중에 비교적 중요한 정보는 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SYN(synchronize sequence numbers; 일련번호를 일치시키다)&lt;/li&gt;
&lt;li&gt;ACK(acknowledgement; 응답을 확인하다)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;두 개의 정보는 비트로 이루어져 있으며 &lt;a href=&quot;https://www.omnisecu.com/tcpip/tcp-header.php&quot;&gt;TCP 세그먼트 헤더&lt;/a&gt;에서 관리 됩니다. 이 정보들은 패킷을 전달하는 과정에서 flag 역할을 하고 TCP는 이러한 정보들을 바탕으로 ‘3 Way’라는 이름 그대로 3번의 통신을 거쳐 데이터 전송을 위한 사전 작업을 진행합니다.&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 458px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/d5738c4897bd5f4c945fc213f7e3b191/cf9ad/2021-10-02-three-way-handshake.jpg&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 60.480349344978166%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wgARCAAMABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAUGBwn/xAAWAQEBAQAAAAAAAAAAAAAAAAADAgT/2gAMAwEAAhADEAAAAeu1W0dahzcqBp//xAAZEAADAQEBAAAAAAAAAAAAAAACAwQFBgH/2gAIAQEAAQUCg6Bmj5G0Qdzu7dpwqx5xa/PkWCRelf8A/8QAHxEAAgIDAAIDAAAAAAAAAAAAAQIDEQQSIQBBEyJh/9oACAEDAQE/AcZJkiRJpfmm+20uuotnJFAelBCDl0LNt3yLDyUjRZcxpJAKd9QuzeyBXB3n55//xAAhEQACAgICAQUAAAAAAAAAAAABAgMEBRETIgASITJBQv/aAAgBAgEBPwHITVJrUs1SsalTpx1uQysFjiRWLu2yXkZWlbXX1OQoCaUWsrjpbE0lbEpXrs5MMJmdzHH+VZ99m18j9nevbz//xAAmEAACAgIABQMFAAAAAAAAAAACAwEEERIABRMiMRQhIzJCUXGB/9oACAEBAAY/AuYLeMPrBf6FYy5jXpMYKWWgYzsVXwtTkCBDuZ9y857uIMVBXUW4kc8+GwJRiOn8HybNj7AjXWS9znPBWpASWdgvTlLiYR1+kklkcwIwJFtORiMR/eA2OWK2aU1prcuWgjMjYbJivRSzqGepGUMiTkB32jeC6iErruX7rapa9gzIwWIYDF9wxrOQmYj6dSxPELVccsI8AtHLgCP0IURGPz488f/EABkQAQEBAQEBAAAAAAAAAAAAAAEhEQAxQf/aAAgBAQABPyGySX7pfYDlao73Zl9uek+rbBjiuw3+ZET9wOb3cnRpqTVqhxq8CTaPdk8q8F8btla3A6spL3//2gAMAwEAAgADAAAAEEwP/8QAFhEBAQEAAAAAAAAAAAAAAAAAAREh/9oACAEDAQE/EDb1wGB5wvjDRFnkWE2UFaQNQFqN/8QAFxEBAQEBAAAAAAAAAAAAAAAAAREAIf/aAAgBAgEBPxCTs3RIRlygnvtypTG4IBgGIKgKb//EABcQAQEBAQAAAAAAAAAAAAAAAAERACH/2gAIAQEAAT8QXvWLEmExnB38TivKCuMXbhCyX6qrSpg5ojN4KD3TrlnvqvGpCqk6ErlDwyZwm05qBCHMimew/9k=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;3 way handshake&quot; title=&quot;&quot; src=&quot;/static/d5738c4897bd5f4c945fc213f7e3b191/cf9ad/2021-10-02-three-way-handshake.jpg&quot; srcset=&quot;/static/d5738c4897bd5f4c945fc213f7e3b191/cf9ad/2021-10-02-three-way-handshake.jpg 458w&quot; sizes=&quot;(max-width: 458px) 100vw, 458px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br /&gt;
&lt;blockquote&gt;
&lt;p&gt;ISN(Initial Sequence Number), ACK NO(Acknowledgement Number), SEQ NO(Sequence Number)는 TCP를 통해 전송되는 다른 데이터와의 충돌을 방지하기 위한 일종의 구분자로 생각하시면 됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Client → Server: &lt;code class=&quot;language-text&quot;&gt;SYN = 1, ACK = 0&lt;/code&gt;과 함께 &lt;code class=&quot;language-text&quot;&gt;ISN&lt;/code&gt;을 무작위로 설정하여 Server로 전달&lt;/li&gt;
&lt;li&gt;Server → Client: &lt;code class=&quot;language-text&quot;&gt;ACK NO&lt;/code&gt;에 클라이언트 &lt;code class=&quot;language-text&quot;&gt;ISN + 1&lt;/code&gt;을 할당하고 &lt;code class=&quot;language-text&quot;&gt;SYN = 1, ACK = 1&lt;/code&gt;과 함께 새로운 &lt;code class=&quot;language-text&quot;&gt;ISN&lt;/code&gt;을 무작위로 설정하여 Client로 전달&lt;/li&gt;
&lt;li&gt;Client → Server: &lt;code class=&quot;language-text&quot;&gt;SEQ NO&lt;/code&gt;에 서버 &lt;code class=&quot;language-text&quot;&gt;ACK NO&lt;/code&gt; 할당, &lt;code class=&quot;language-text&quot;&gt;ACK NO&lt;/code&gt;에 서버 &lt;code class=&quot;language-text&quot;&gt;ISN + 1&lt;/code&gt;을 할당 후 &lt;code class=&quot;language-text&quot;&gt;SYN = 0, ACK = 1&lt;/code&gt;과 함께 Server로 전달&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이러한 과정을 모두 마치게 되면 커넥션이 확립되고 두 컴퓨터는 인증된 &lt;code class=&quot;language-text&quot;&gt;SEQ NO&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;ACK NO&lt;/code&gt;를 통해 데이터를 전송할 수 있습니다. 이제 TCP의 커넥션을 통해 패킷이 유실되거나 잘못된 순서로 전달되어도 패킷 재전송, 재구성이 가능하기에 컴퓨터 간 더욱 안정적으로 데이터를 교환하게 되는 것입니다.&lt;/p&gt;
&lt;h3&gt;PORT&lt;/h3&gt;
&lt;p&gt;Port라는 단어를 직역하자면 ‘항구’라고 합니다. 말 그대로 데이터가 실제로 도달해야 하는 어느 지점을 의미하는 것인데요, IP 주소를 통해 컴퓨터의 위치를 파악했다면 그 컴퓨터에서 구동되고 있는 애플리케이션의 주소를 파악하는 것은 포트의 역할이라고 할 수 있습니다. 예컨대 HTTP는 80번, HTTPS는 443번, SMTP는 25번으로 송신 측에서 전달된 데이터의 최종 행방은 이러한 포트 번호와 일치하는 애플리케이션이 됩니다. 그리고 Port 또한 위에서 언급한 TCP 세그먼트 헤더에서 관리됩니다.&lt;/p&gt;
&lt;h3&gt;TCP/IP Model&lt;/h3&gt;
&lt;p&gt;TCP/IP Model은 OSI 7 Layers Model을 참고하여 구축된 모델입니다. 이 글에서는 OSI 7 Layers Model은 깊게 다루진 않을 것입니다. 다만 이해를 돕기 위해 Layer Model에 대해 간략히 설명하자면, ‘네트워크상에서 정보 교환을 위한 여러 과정을 모듈화하여 계층으로 나눈 구조’라고 얘기할 수 있겠습니다. 그렇기에 각 계층은 서로 독립적인 동시에 상호 유기적인 성격을 띱니다. (OSI 7 Layers Model에 대해 좀 더 자세히 알고 싶다면 이 &lt;a href=&quot;https://www.youtube.com/watch?v=1pfTxp25MA8&quot;&gt;영상&lt;/a&gt;을 보시는 것을 추천드립니다. 😊)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;현재 사용되고있는 TCP/IP Model은 기존 TCP/IP Model과 약간은 다릅니다. 기존 모델은 계층을 4개로 나누는 반면, 현재의 모델은 계층을 5개로 분리하기 때문입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 700px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/76b4df259f1a2a718eaaa4515727c550/a27c6/2021-10-11-tcp-model.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 80%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEhElEQVR42m2R609bZRzHz1xi/AuWTF9pjPpGF2NijEanmbvAakcpPaUHenp6oYVTboVtCHQc2p4zeq69IBDuhTnQQsuGMG7alsNlhG1lmi2+cckSX5iwNWVosvXm4zk4k73wl3zz/PL95vt5zskDaTThw14v97HH0/WliyRP0zT/BSQNQRAvyWcwOPC65B1nGP97L/iH5L23d/wIzfPHKYr6JBwOHwYAHPhQpLf5SKS/7egEbX/1itB2VPb+C9mu9s8DDNnY46NKRgnilRcvC/XxrwVot5WhnJV9vPsYJHd07gULQsf1sirp9Qrd5bixglxS1XKL7+g8SzY9eb3RQk27TV2zFxByqV5HLn4owyqoZRgh5+oxz7QTI6+3Gj2zX8PEggVSeeKgfmBHUhLU9N4u2AfuAw0VT+K+mE3D3gFKUix85V4FSo+Y0/ruAh2dYGVguVd8WEpvA4V79SA/R66DEokFlXbeyGHcag5lEjkjl8iY/ZtASy5vG6j5KoTdAFb/RtYorOVNwlpG79sG6s5FWgZqyJXfTP6bwOxbz0pZHuPX8+c6FnKQilgEBlYEJn5NklgwB7aB1rOcNHsXbHrhFrD4NwsYvwGMwmYOC+4A2L1y8IVaKvbQHLwtATcKGLcOMGETqFwrADrpXErB9FrK1L2VMnbffKQkE88Urh/jFm4JK3L99PR0Z+xRkSuWKuqM7Ra743k1ueKSgaVU7O6ZzvhfZ57nxe5EqqhjOQV57QTCVEuyOhHa1q5jagiUrXUVfUPwb8o7h3fomOrnGU5gXJP7fRkoNJLnaLxDz+GSX916wPDanQi0WnIW3LEYwI7dCpJ4VeG+tIuakmSsCrPdRTQgWW0uJHGrnOfuozqQKC89+GURVj382YyCpN1aSMpdqZdQKQAUPqtDr5Wh6DxqQxcMuP4HNWaKlugVUYv9rWvKCtMcUqWfR2vROT2un1GhlhmN4QMZeF1tLp1VVRrn9NX6eWMtOg8b0bCyEoV2+4q5/XEdtxeSNAqzT0bUwcf9Ssfvo5aPngwqgrK3F9JyeyMwkx5U9uwOqBUycLe/pH1/DPbvjenYvRENtx/ScDIL2g98ms6Ow+nsJJbOTGKpZyPK7J/dJ1b/GECwZ30nMpkr5anM1cp05lvk8dO+k3+nes54ZOB+z6lfMiHV08ykIZWZQCVGWXo/8Fka4ho4ra+W1nI1l7WBOi8s1NEVgcbAqWGi9w3eziB8TZdGwCmYryE1Qh1XwTv4YzKw+0K3wl/PIALuhX1SNygzJBbUig3n2fPTeaElmmeap7JsUxQ4zaO3+IthWxceBRQeLvQQNwDdMJ1j6mYBVTtx8CgdVWMPmKYI8LXNZNnmqTztmMq3Goby0CXDMOAcYRBoiQJf81SBa5gGLkvo1yFf+G0vPgyzjcNlwsUrZYxjVO21DyK8Y+RdGei2hh4IjggItkQLvvNTgKn/HlzChgHUYR4SvQ0Tor8tInY7Iwlv/eSWyxaavAfuvQz9O4eg/xkPPjbZVXd1K9AaWQ20R0W26TvRaR4S/wGr7pIuU0z9JAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;tcp-model&quot; title=&quot;&quot; src=&quot;/static/76b4df259f1a2a718eaaa4515727c550/a27c6/2021-10-11-tcp-model.png&quot; srcset=&quot;/static/76b4df259f1a2a718eaaa4515727c550/0eb09/2021-10-11-tcp-model.png 500w,
/static/76b4df259f1a2a718eaaa4515727c550/a27c6/2021-10-11-tcp-model.png 700w&quot; sizes=&quot;(max-width: 700px) 100vw, 700px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br /&gt;
&lt;p&gt;각 계층에 대한 설명 (Updated TCP/IP Model 기준)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Application Layer
&lt;ul&gt;
&lt;li&gt;high-level 프로토콜 담당&lt;/li&gt;
&lt;li&gt;애플리케이션의 데이터 전송&lt;/li&gt;
&lt;li&gt;HTTP, SMTP, FTP&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Transport Layer
&lt;ul&gt;
&lt;li&gt;신뢰성, 흐름 제어, 데이터 수정 담당&lt;/li&gt;
&lt;li&gt;TCP, UDP&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Network Layer
&lt;ul&gt;
&lt;li&gt;네트워크를 통해 패킷 전송&lt;/li&gt;
&lt;li&gt;Internet Protocol - IP 주소 정의, 라우팅&lt;/li&gt;
&lt;li&gt;IP, ARP, ICMP&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Data Link Layer
&lt;ul&gt;
&lt;li&gt;네트워크 계층으로부터 받은 패킷 헤더에 MAC 주소 추가 (프레임 생성)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Physical Layer
&lt;ul&gt;
&lt;li&gt;모델의 최하단 계층으로써 비트 단위의 실질적인 데이터 전송 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.net-usb.com/usb-over-ip/what-is-tcp-ip/&quot;&gt;대표 이미지 출처&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.fortinet.com/resources/cyberglossary/tcp-ip&quot;&gt;What is a Transmission Control Protocol TCP/IP Model?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.omnisecu.com/tcpip/tcp-three-way-handshake.php&quot;&gt;3 Way-Handshake&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.computernetworkingnotes.com/ccna-study-guide/similarities-and-differences-between-osi-and-tcp-ip-model.html&quot;&gt;TCP/IP vs OSI Model: What’s the Difference?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://microchipdeveloper.com/tcpip:tcp-ip-five-layer-model&quot;&gt;TCP/IP Five-Layer Software Model Overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Java 8 에서 왜 함수형 프로그래밍이 도입되었을까?]]></title><description><![CDATA[우선 당신에게 질문을 던져본다. 객체지향 프로그래밍과 함수형 프로그래밍은 상호 배제 관계에 있다고 생각하는가? 객체지향과 함수형 프로그래밍 Java…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-09-30-java8-functional-programming/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-09-30-java8-functional-programming/</guid><pubDate>Thu, 30 Sep 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;우선 당신에게 질문을 던져본다. 객체지향 프로그래밍과 함수형 프로그래밍은 상호 배제 관계에 있다고 생각하는가?&lt;/p&gt;
&lt;h2&gt;객체지향과 함수형 프로그래밍&lt;/h2&gt;
&lt;p&gt;Java 혹은 다른 객체지향 프로그래밍 언어로 처음 프로그래밍하던 시절이 생각나는가? 그 당시에는 객체지향 이라는 의미를 이해하지 못한 채 절차 지향적인 코드를 구현한 경험이 있을 것이다. 우리는 &lt;b&gt;지향&lt;/b&gt;이라는 단어의 의미를 생각해야 한다. &lt;b&gt;지향&lt;/b&gt;은 하나의 프로그래밍 패러다임이다. 객체지향 패러다임을 따른다고 해서 반드시 모든 부분에서 객체지향 프로그래밍을 &lt;b&gt;해야만 한다는 것이 아니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;위키 백과에 나온 설명에 따르면 &lt;b&gt;객체지향&lt;/b&gt; 프로그래밍이란 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위인 &lt;b&gt;객체의 모임&lt;/b&gt;으로 파악하고자 하는 것이다. 그러면 함수형 프로그래밍은 어떤 것일까? 함수형 프로그래밍은 수학적 &lt;b&gt;함수의 계산을 통해 자료를 처리&lt;/b&gt;하고 &lt;b&gt;상태와 가변 데이터를 멀리&lt;/b&gt;하는 프로그래밍 패러다임 중 하나다.&lt;/p&gt;
&lt;p&gt;Java 는 객체지향 프로그래밍 언어다. 그러나 Java 는 함수형 프로그래밍과 비동기 논블로킹의 기능 도입하기 위해 Java 8 부터 Optional 과 람다식, NodeJS 처럼 비동기 논블로킹 방식을 도입하게 된다. 물론 Java 8 은 순수한 함수형 언어는 아니다. JavaScript, C# 의 함수형 언어의 특성을 접목할 수 있도록 변신시켰다.&lt;/p&gt;
&lt;p&gt;함수형 프로그래밍이 어떤 장점을 가지고 있어서 Java 는 함수형 프로그래밍을 도입 했을까? 이번 글에서는 함수형 프로그래밍의 도입 이유와 Null 에 대해서 살펴보자.&lt;/p&gt;
&lt;h2&gt;주목 받고있는 함수형 프로그래밍&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Java 의 함수형 인터페이스&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Consumer&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; print &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

print&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;안녕하세요!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;함수형 프로그래밍은 이미 오래전에 고안된(1958년) 프로그래밍 패러다임이지만 주목받지 못하고 오랜 시간이 흘렀다. 하지만 21세기 들어 함수형 프로그래밍이 주목받기 시작하는데 어떤 이유에서 주목받기 시작한 것일까?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;공짜 점심은 끝났다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;공짜 점심이란 세상에 공짜는 없다는 말이 있듯이 공짜로 점심을 얻어먹은 것으로 생각할 수 있지만, 그 시간 동안 할 수 있었던 다른 일(기회비용)을 못하게 된다는 뜻을 가진다. 2000 년대 초반까지는 CPU 의 클럭 증가, 실행 시간 최적화, Cache 의 크기 증가를 통해 소프웨어의 싱글 프로세스, 싱글 스레드의 속도가 공짜로 증가했다고 보는 말이다. 그러나 전력 소모 및 발열 문제 등으로 단일코어를 통한 CPU 클럭 증가는 현실적으로 향상시키기 어려운 상황에 돌입하게 되었다. 그래서 이를 대안으로 코어 수를 늘려서 CPU 의 성능을 향상하게 된다.&lt;/p&gt;
&lt;h3&gt;불변이다.&lt;/h3&gt;
&lt;p&gt;불변이 아닌 변수는 예상치 못한 상황에 예상치 못한 값으로 변경될 가능성이 있다. 함수형 프로그램은 대입문이 없기 때문에 기본적으로 한 번 값이 변수에 할당되고 나면 이후에 값이 변경되지 않는다. 부수효과(Side Effect)가 발생하지 않기 때문에 표현 식을 언제 어느 때 실행하더라도 문제가 발생하지 않으며 이는 프로그래머가 신경 쓸 부분을 줄여주는 셈이다. 이러한 프로그램을 &lt;b&gt;참조 투명성&lt;/b&gt;을 가졌다고 말한다. 참조 투명성을 지녔기 때문에  함수형 프로그래밍은 멀티코어 프로세스에서 &lt;b&gt;교착상태에 빠지지 않는다&lt;/b&gt;는 장점이 있다. 함수형 프로그래밍은 동시성 프로그래밍에서 강력한 프로그래밍 패러다임으로 작용한다.&lt;/p&gt;
&lt;h3&gt;명확하다.&lt;/h3&gt;
&lt;p&gt;함수가 하는 일은 명확하게 정의되기 때문에 코드를 읽을 때 이해하기 쉬워진다는 장점이 있다. 또한 함수를 값으로 사용할 수 있다. 이해하기 쉬운 표현 식에 의해 함수의 결합을 통한 고차함수를 사용할 때도 간결하게 표현할 수 있다. 익명 함수를 사용할 수 있다는 점도 한몫한다.&lt;/p&gt;
&lt;p&gt;함수형 프로그래밍에서 고안한 Java8 의 StreamAPI 를 통해 왜 명확한 지 살펴보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;        &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; values &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;95&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;68&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; values&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;number &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; number &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;distinct&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sorted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;compare&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Collectors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같은 형태를 한글로 해석해 보면 아래와 같다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;        &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; 숫자가_담긴_리스트 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;95&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;68&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; 연산_결과 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; 숫자가_담긴_리스트&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;스트림으로_변경&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;필터링&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;숫자 &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt; 미만 숫자&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;중복제거&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;정렬&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt; 타입의 compare 메서드 방식으로&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;스트림_연산_결과를_합침&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Collectors&lt;/span&gt; 의 toList 메서드로 &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt; 결과를 반환&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Null&lt;/h2&gt;
&lt;p&gt;Null 이라는 개념은 참조형 자료에 사용하기 위해 만들어졌다. 간단한 예시로 휴지를 전부 사용하고 휴지심만 남아 있다면 그것은 0 이라고 볼 수 있고, 휴지심 자체가 없는 것을 Null 이라고 볼 수 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;10억 불짜리 실수&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;토니 호어가 만든 Null Reference 의 개념을 위의 표현으로 회고한 것이 유명하다. Null 이 도입되면서 개발자는 직접 Null Pointer 를 체크해서 사용한다.&lt;/p&gt;
&lt;p&gt;Java 로 개발을 하다 보면 예상치 못한 오류를 맞이한다. 대부분의 상황에서 &lt;code class=&quot;language-text&quot;&gt;NullPointerException&lt;/code&gt; 을 마주하게 된다. 자바는 객체지향 프로그래밍의 참조형 자료에 Null 이라는 개념을 허용하고 있기 때문에 발생하는 문제다. 이러한 지긋지긋한 Null 문제를 해결하기 위해 Java 는 Optional 을 도입했다. Optional 은 함수형 프로그래밍에서 고안 해낸 개념으로 참조 값이 Null 일 수도 있음을 알려주고 이를 통해 &lt;code class=&quot;language-text&quot;&gt;NullPointerException&lt;/code&gt; 을 방지한다. Optional 에 대해 알고 싶다면 &lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-06-20-optional-vs-null/&quot;&gt;여기&lt;/a&gt;를 참고하면 도움이 된다.&lt;/p&gt;
&lt;h2&gt;Java 8&lt;/h2&gt;
&lt;p&gt;함수형 프로그래밍과 관련하여 도입된 기능을 살펴보고 싶다면 해당 링크를 참조해보길 권장한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2020-07-17-Functional-Interface/&quot;&gt;Functional Interface&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-05-23-stream-api-basic/&quot;&gt;StreamAPI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;시대의 흐름에 의해 프로그래밍의 생태계가 변하고 있다. 더 이상 싱글 코어로는 한계에 다다르게 되면서 멀티코어 CPU 가 대중화 되고 있다. Java 8 은 시대 흐름에 맞게 병렬 프로세싱을 활용하고자 했고, 그로 인해 기존 Java 에서는 구현하기 어려웠던 부분을 Java 8 을 기점으로 함수형 프로그래밍과 비동기 논블로킹 방식을 도입을 통해 해결했다. 미래 사회에서 함수형 프로그래밍은 더욱 주목받을 것으로 보이기 때문에 개발자라면 함수형 프로그래밍을 익힌다면 큰 도움이 되리라 생각한다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html&quot;&gt;Functional Interface Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mangsby.com/blog/programming/fp-vs-oop/&quot;&gt;함수형 프로그래밍 vs 객체지향 프로그래밍&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zdnet.co.kr/view/?no=20170908145636&quot;&gt;자바는 더 함수형으로 업그레이드돼야 한다.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://ruaa.me/why-functional-matters/&quot;&gt;왜 함수형 프로그래밍이 좋을까?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://friday.fun25.co.kr/blog/?p=266&quot;&gt;JAVA 8 정리 개요&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[http와 https에 대해 알아보자]]></title><description><![CDATA[최근 프로젝트를 EC2로 배포를 하였는데, 가 적용되어있지 않아서, ‘주의 요함’, ‘이 사이트는 보안 연결(HTTPS…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-09-25-https/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-09-25-https/</guid><pubDate>Sat, 25 Sep 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;최근 프로젝트를 EC2로 배포를 하였는데, &lt;code class=&quot;language-text&quot;&gt;https&lt;/code&gt;가 적용되어있지 않아서, ‘주의 요함’, ‘이 사이트는 보안 연결(HTTPS)이 사용되지 않았습니다.’ 라는 문구를 볼 수 있었다. 그래서 &lt;code class=&quot;language-text&quot;&gt;https&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;http&lt;/code&gt;와 어떤 차이가 있고, 어떻게 보안 문제를 해결하였는지 궁금해졌다. 그래서 이번 시간에는 &lt;code class=&quot;language-text&quot;&gt;http&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;https&lt;/code&gt;의 차이를 알아보고자 한다.&lt;/p&gt;
&lt;h2&gt;HTTP(HyperText Tranfer Protocol)&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;http&lt;/code&gt;는 WWW 상에서 정보를 주고 받는 프로토콜이다. 프로토콜은 컴퓨터나 원거리 통신 장비 사이에서 메시지를 주고 받는 양식과 규칙의 체계를 뜻하는데, &lt;code class=&quot;language-text&quot;&gt;http&lt;/code&gt;는 웹브라우저(Client)와 서버(Server)간의 웹페이지같은 자원을 주고 받을 때 쓰는 통신 규약이라고 생각하면 된다.&lt;/p&gt;
&lt;p&gt;그래서 &lt;code class=&quot;language-text&quot;&gt;http&lt;/code&gt;를 사용해서 클라이언트인 웹브라우저는 서버에 웹페이지나 이미지 정보를 요청하고, 서버는 이 요청에 대한 정보를 응답하게 된다.&lt;/p&gt;
&lt;p&gt;그런데 이때 누군가 네트워크에서 신호를 가로채면 내용이 노출되는 보안 문제가 발생할 수 있다. 이를 해결해주는 프로토콜이 바로 &lt;code class=&quot;language-text&quot;&gt;https&lt;/code&gt;이다.&lt;/p&gt;
&lt;h2&gt;HTTPS(HyperText Tranfer Protocol over Secure Socket Layer)&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;https&lt;/code&gt;는 정보를 암호화하는 SSL(Secure Socket Layer)*을 이용한 프로토콜이다. 여기에서 핵심은 보안 문제를 해결하기 위해 암호화 한다는 것인데, &lt;code class=&quot;language-text&quot;&gt;https&lt;/code&gt;의 암호화에 대해 이해하기 위해서는 먼저 대칭키와 공개키에 대해 알아야한다.&lt;/p&gt;
&lt;p&gt;* SSL(Secure Socket Layer) - 컴퓨터 네트워크에 통신 보안을 제공하기 위해 설계된 암호 규약으로 SSL는 과거의 명칭이고, 현재는 TLS(Transport Layer Security - 전송 계층 보안)라고 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h3&gt;대칭키와 공개키&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;대칭키 암호화&lt;/code&gt;란 암호화와 복호화 과정에서 같은 암호키(대칭키)를 사용하는 알고리즘 방식이다. 주고 받는 정보에 대해서 같은 암호키를 사용하기 때문에 빠르다는 장점이 있지만, 암호키가 노출되면 암호화된 내용을 해독 가능하기 때문에 보안 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;공개키 암호화&lt;/code&gt;란 암호키를 하나만 가지는 대칭키 암호화와 달리, 암호화와 복호화에 사용하는 암호키를 분리한 알고리즘 방식으로 비대칭키라고도 불린다. 암호키는 외부에 공개되며 암호화에 사용되는 공개키와 자신만이 가지며 복호화에 사용되는 개인키로 이루어져있다. 공개키 암호화의 진행 과정은 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A가 ‘B의 공개키’로 암호화한 정보를 B에게 보낸다.&lt;/li&gt;
&lt;li&gt;B는 ‘자신의 개인키’로 정보를 복호화해서 확인한다.&lt;/li&gt;
&lt;li&gt;B는 다시 ‘A의 공개키’로 암호화한 정보를 A에게 보낸다.&lt;/li&gt;
&lt;li&gt;A는 ‘자신의 개인키’로 정보를 복호화해서 확인한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;대칭키 암호화의 보안에 대한 단점을 보완하였다. 하지만 공개키 암호화방식에서는 더 복잡한 암호화와 복호화는 과정을 거치게 되는데, 매번 이 과정을 반복하게 되면 시간이 오래걸린다는 단점이 있다. 그래서 이 두 가지 방식을 혼합히여 사용할 수도 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A가 ‘B의 공개키’로 ‘대칭키’를 암호화해서 B에게 보낸다.&lt;/li&gt;
&lt;li&gt;B는 ‘자신의 개인키’로 이를 복호화해서 ‘대칭키’를 얻는다.&lt;/li&gt;
&lt;li&gt;B는 ‘대칭키’로 정보를 암호화해서 A에게 보낸다.&lt;/li&gt;
&lt;li&gt;A는 ‘대칭키’로 정보를 복호화해서 확인한다.&lt;/li&gt;
&lt;li&gt;앞으로 이 대칭키로 암호화와 복호화해서 정보를 주고 받는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 방식은 SSL 암호화의 기본적인 과정으로 &lt;code class=&quot;language-text&quot;&gt;https&lt;/code&gt;의 암호화에 사용되는 방식이다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그럼 이제 &lt;code class=&quot;language-text&quot;&gt;https&lt;/code&gt;가 통신하는 과정에 대해서 알아보자.&lt;/p&gt;
&lt;p&gt;먼저, &lt;code class=&quot;language-text&quot;&gt;https&lt;/code&gt;를 적용하기 위해서 공개키와 개인키를 만든다. 그 다음에 신뢰할 수 있는 CA*에 ‘내 서버의 공개키’를 맡긴다.&lt;/p&gt;
&lt;p&gt;* ‘CA(Certificate Authority)’ - 공개키를 저장해주는 신뢰성이 검증된 민간 기업이다. CA로는 코모도 등이 있다.&lt;/p&gt;
&lt;p&gt;CA에도 공개키와 개인키가 있다. CA는 CA의 이름과 ‘내 서버의 공개키’, 공개키의 암호화 방법 등의 정보를 담은 인증서를 만들고, 해당 인증서를 ‘CA의 개인키’로 암호화해서 내 서버에게 제공한다.&lt;/p&gt;
&lt;p&gt;이제 내 서버에서는 클라이언트에서 첫 요청이 오면 이 암호화된 인증서를 준다. 클라이언트는 세계적으로 신뢰할 수 있는 ‘CA의 공개키’를 가지고 있다. CA 리스트에서 인증서에 적혀있는 이름과 같은 CA의 공개키를 찾아서 인증서를 해독하고 ‘내 서버의 공개키’를 얻는다.&lt;/p&gt;
&lt;p&gt;이제 클라이언트는 ‘대칭키’를 만들어서 ‘내 서버의 공개키’를 사용해서 암호화하고, 서버로 보내게 된다. 그러면 서버에서는 ‘내 서버의 개인키’로 이를 복호화해서 ‘대칭키’를 얻는다. 이후에는 클라이언트와 서버가 통신할 때 이 대칭키를 사용해서 정보를 암호화하고 복호화해서 통신하게 된다.&lt;/p&gt;
&lt;p&gt;이러한 과정을 통해 우리는 &lt;code class=&quot;language-text&quot;&gt;https&lt;/code&gt;로 더 안전하게 정보를 주고 받을 수 있게 된다. 하지만 신뢰할 수 있는 CA가 아니라 자체적으로 인증서를 발급하거나, 신뢰할 수 없는 CA를 통해서 인증서를 발급받을 수도 있기 때문에 항상 안전한 것은 아니다. 이 경우에도 브라우저는 ‘주의 요함’, ‘이 사이트는 보안 연결(HTTPS)이 사용되지 않았습니다.’ 알림을 준다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;이번 시간에는 항상 당연하게 사용해왔지만, 잘 알지못했던 &lt;code class=&quot;language-text&quot;&gt;https&lt;/code&gt;에 대해서 알아보았다. 혹시 나처럼 &lt;code class=&quot;language-text&quot;&gt;https&lt;/code&gt;에 대해 잘 몰랐던 사람들에게 도움이 되었으먼 한다. 혹시 &lt;code class=&quot;language-text&quot;&gt;https&lt;/code&gt;가 통신하는 과정에 대해서 조금 더 자세히 알고싶다면 참고 자료에 생활 코딩 블로그 글을 읽어보는 걸 추천한다.&lt;/p&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;h3&gt;위키백과&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/HTTP&quot;&gt;HTTP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/HTTPS&quot;&gt;HTTPS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%8C%80%EC%B9%AD_%ED%82%A4_%EC%95%94%ED%98%B8&quot;&gt;대칭키 암호&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EA%B3%B5%EA%B0%9C_%ED%82%A4_%EC%95%94%ED%98%B8_%EB%B0%A9%EC%8B%9D&quot;&gt;공개키 암호&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%A0%84%EC%86%A1_%EA%B3%84%EC%B8%B5_%EB%B3%B4%EC%95%88&quot;&gt;전송 계층 보안&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;블로그&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://opentutorials.org/course/228/4894&quot;&gt;생활코딩 - HTTPS와 SSL 인증서&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[우테코 프로젝트에서 Github Actions를 이용한 배포 자동화]]></title><description><![CDATA[팀 프로젝트 백중원에서 프론트앤드 배포 인프라로 AWS의 S3와 CloudFront를 사용하고 있다. 하지만 배포 과정에서 자동화가 적용되지 않은 상태라, 실제 서비스에 사용되는  브랜치나  브랜치가 최신화될 때 매번 수동으로 S…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-09-23-continuous-deployment-with-github-actions/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-09-23-continuous-deployment-with-github-actions/</guid><pubDate>Thu, 23 Sep 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://github.com/woowacourse-teams/2021-cvi&quot;&gt;팀 프로젝트 백중원&lt;/a&gt;에서 프론트앤드 배포 인프라로 AWS의 S3와 CloudFront를 사용하고 있다. 하지만 배포 과정에서 자동화가 적용되지 않은 상태라, 실제 서비스에 사용되는 &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt; 브랜치나 &lt;code class=&quot;language-text&quot;&gt;develop&lt;/code&gt; 브랜치가 최신화될 때 매번 수동으로 S3에 객체를 업로드하고 있었다. 객체 자체를 업로드하는 과정이 복잡한 것은 아니지만, 매번 최신 객체가 업로드되어 있는지 확인해야 하고 이로 인해 추가적인 커뮤니케이션의 비용이 든다. 따라서 Github Actions를 사용하여 배포 자동화를 적용하게 되었다.&lt;/p&gt;
&lt;p&gt;배포 자동화가 이루어지면 다음과 같은 플로우로 배포가 진행된다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40762111/134311751-60db6b90-76e1-4ef3-81dd-90ac4c73bb2a.png&quot; alt=&quot;frontend-deployment-flow&quot;&gt;&lt;/p&gt;
&lt;p&gt;이번 글에서는 5, 6번과 관련된 부분을 다룰 예정이다. 이해를 돕기 위해서 과정 전체를 대략적으로 설명한 뒤, 각 부분에 필요한 과정을 나열하겠다.&lt;/p&gt;
&lt;h2&gt;목차&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;전반적인 설명&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;세부 과정&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;자격 증명 정보 발급&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;자격 증명 정보(새로 만든 계정)와 리전 정보(팀 계정)를 프로젝트 Repository Secrets 등록&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;S3 버킷 ACL(엑세스 제어 목록) 피부여자 추가&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;S3 버킷 객체 소유권 변경&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Github Actions workflow 작성&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;전반적인 설명&lt;/h2&gt;
&lt;p&gt;최상단에 있는 배포 플로우 이미지의 6번을 보면, 5번에서 Github Actions를 사용하여 AWS S3에 객체를 업로드한다. Github Actions는 명령어 동작 시점(&lt;code class=&quot;language-text&quot;&gt;push&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;pull_request&lt;/code&gt; 등)과 명령어가 담긴 &lt;code class=&quot;language-text&quot;&gt;.yml&lt;/code&gt; 파일을 읽어 동작되는데, 우리는 S3 버킷에 객체를 업로드하기 위해, &lt;code class=&quot;language-text&quot;&gt;.yml&lt;/code&gt; 파일에 AWS 명령어를 입력할 예정이다.&lt;/p&gt;
&lt;p&gt;하지만 그전에, AWS 명령어가 동작하기 위해서는 크게 나눠서 두 가지 작업이 필요하다. 첫 번째는 계정 및 S3 버킷의 정보가 Github Actions에서 사용될 수 있도록 프로젝트에 등록하는 것이고, 두 번째는 계정의 접근 문제를 해결하는 것이다.&lt;/p&gt;
&lt;p&gt;먼저 첫 번째 과정이다. 세부적으로는 자격 증명 정보(&lt;code class=&quot;language-text&quot;&gt;access-key-id&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;secret-access-key&lt;/code&gt;)를 발급받아, 리전 정보(&lt;code class=&quot;language-text&quot;&gt;region&lt;/code&gt;)와 함께 프로젝트 Repository Secrets에 등록하는 것이다. 이 정보들은 AWS SDK와 AWS CLI에서 확인되어 AWS 명령어가 동작하기 위한 자격 증명과 지역을 결정하는 데 사용된다. 그런데, 사진을 보면 자격 증명 정보는 새로 만든 계정에서 가져오고, 리전 정보는 우테코 팀 계정에서 가져오는지 궁금증이 생길 것이다. 이유는 우테코에서 제공해 주는 팀 계정으로 자격 증명 정보를 발급받을 권한이 없기 때문이다. 자격 증명 정보가 다른 곳에서 사용된다면 보안상의 문제가 발생할 수 있어 우테코에서는 해당 기능을 제한하고 있다. 이런 이유로 새로운 계정이 필요하다. (우테코에서 미션을 위해 개인적으로 제공해 주었던 계정도 자격 증명 정보를 발급받을 수 없다.)&lt;/p&gt;
&lt;p&gt;첫 번째 과정을 요약하면, 새로운 계정의 자격 증명 정보(&lt;code class=&quot;language-text&quot;&gt;access-key-id&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;secret-access-key&lt;/code&gt;)를 발급받아(아래 사진의 2번 과정), 우테코 팀 계정의 리전 정보와 함께 프로젝트 Repository Secrets에 등록하는 것(아래 사진의 3번 과정)이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40762111/134863861-498cbcb0-9d0e-4b39-9ab1-bf14dd97638a.png&quot; alt=&quot;Github Actions를 이용한 배포 자동화 설정 2&quot;&gt;&lt;/p&gt;
&lt;p&gt;다음으로 두 번째, 계정의 접근 문제 해결 과정이다. Github Actions에서 AWS 명령어를 동작시키기 위해 사용된 정보 중 자격 증명 정보(&lt;code class=&quot;language-text&quot;&gt;access-key-id&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;secret-access-key&lt;/code&gt;)는 새로 만든 계정의 정보인데, S3 버킷을 소유하고 있는 계정은 우테코에서 제공된 팀 계정이다. 즉, S3 버킷에 객체를 업로드하는 계정과 S3 버킷을 소유한 계정이 다르다. 이런 이유로 새로 만든 계정은 팀 계정의 S3 버킷에 접근할 수 없다. 새로운 계정이 팀 계정의 S3 버킷에 접근하여 객체를 업로드하기 위해서는 팀 계정에서 생성한 S3 버킷의 ACL(액세스 제어 목록)의 피부여자에 새로 만든 계정을 추가해야 한다(위 사진의 4번 과정). 이렇게 하면 새로 만든 계정에서 팀 계정이 만든 S3 버킷에 접근하여 객체를 업로드할 수 있다. (참고로 리전 정보(&lt;code class=&quot;language-text&quot;&gt;region&lt;/code&gt;)는 S3 버킷 자체의 정보이므로 접근 문제와 관련이 없다)&lt;/p&gt;
&lt;p&gt;계정의 접근 문제 해결과 관련해, 한 가지 더 세부적인 과정이 필요하다. 이는 S3 버킷의 특성에서 비롯되는데, 기본적으로 S3 버킷의 객체 소유권은 버킷에 객체를 업로드한 계정이 갖게 된다는 점이다. 즉, 새로 만든 계정에서 S3 버킷에 객체를 업로드하면, 해당 계정이 그 객체의 소유권을 갖게 된다. 팀 계정이 S3 버킷을 생성한 버킷의 소유자임에도 불구하고 해당 버킷의 객체 소유권을 새로 만든 계정이 갖게 되면, 팀 계정의 CloudFront에서 S3 버킷의 객체에 소유권이 없어 접근하지 못하게 된다. 이런 상황을 방지하기 위해서, 팀 계정의 S3 버킷의 권한 속성에서 객체 소유권을 &lt;code class=&quot;language-text&quot;&gt;버킷 소유자 선호&lt;/code&gt;로 변경하는 과정이 필요하다.&lt;/p&gt;
&lt;p&gt;두 번째 과정을 요약하자면 계정의 S3 접근 문제를 해결하기 위해 S3 버킷의 ACL(액세스 제어 목록)에 피부여자를 추가하고(아래 사진의 4번 과정), 객체 소유권을 변경한 것(아래 사진의 5번 과정)이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40762111/134863864-1ad260b6-a4a9-4734-84d0-de14b31cbcf2.png&quot; alt=&quot;Github Actions를 이용한 배포 자동화 설정 3&quot;&gt;&lt;/p&gt;
&lt;p&gt;이제 첫 번째 과정과 두 번째 과정의 세부적인 부분을 이미지와 함께 알아보겠다.&lt;/p&gt;
&lt;h2&gt;세부 과정&lt;/h2&gt;
&lt;h3&gt;자격 증명 정보 발급&lt;/h3&gt;
&lt;p&gt;해당 단계는 위 사진을 기준으로 &lt;code class=&quot;language-text&quot;&gt;1. AWS 계정 생성&lt;/code&gt; 과정을 마친 이후 &lt;code class=&quot;language-text&quot;&gt;2. 자격 증명 발급&lt;/code&gt;이다. 앞으로의 과정에서 AWS 관리 콘솔에 새로 만든 계정과 우테코 팀 계정을 동시에 로그인해야 하므로, 둘 중 하나의 계정은 시크릿 모드를 이용해 로그인하는 것을 추천한다.&lt;/p&gt;
&lt;p&gt;자격 증명을 발급받기 위해 &lt;strong&gt;새로 생성한 계정&lt;/strong&gt;에서 로그인 후, 메뉴에서 &lt;code class=&quot;language-text&quot;&gt;내 보안 자격 증명&lt;/code&gt;을 누른다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40762111/134385627-e51f6d16-6a6b-4259-ac36-a9ecf4f01c35.png&quot; alt=&quot;스크린샷 2021-09-23 오전 1 36 58&quot;&gt;&lt;/p&gt;
&lt;p&gt;나타나는 &lt;code class=&quot;language-text&quot;&gt;보안 자격 증명&lt;/code&gt; 페이지에서 &lt;code class=&quot;language-text&quot;&gt;액세스 키(액세스 키 ID 및 비밀 액세스 키)&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;새 액세스 키 만들기&lt;/code&gt;를 눌러 자격 증명을 발급받는다. 참고로 나타나는 보안 액세스 키는 한 번 밖에 확인할 수 없으므로 따로 보관하거나 &lt;code class=&quot;language-text&quot;&gt;키 파일 다운로드&lt;/code&gt;로 파일을 다운로드해 보관한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40762111/134386454-bf37c846-4c87-444d-a399-f541bc0b93e9.png&quot; alt=&quot;스크린샷 2021-09-23 오전 1 44 55&quot;&gt;&lt;/p&gt;
&lt;h3&gt;자격 증명 정보(새로 만든 계정)와 리전 정보(팀 계정)를 프로젝트 Repository Secrets에 등록&lt;/h3&gt;
&lt;p&gt;이번 단계는 방금 발급받은 자격 증명 정보와 우테코 팀 계정에서 만든 S3 버킷의 리전 정보를 프로젝트 Repository Secrets에 등록하는 과정이다. 자격 증명 정보는 바로 이전에 단계에서 발급받았으므로 제외하고, S3 버킷의 리전 정보를 확인하겠다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;우테코 팀 계정&lt;/strong&gt;의 S3 메뉴로 들어가 설정하고자 하는 버킷을 눌러 상세 페이지로 이동한다. 버킷의 &lt;code class=&quot;language-text&quot;&gt;속성&lt;/code&gt; 메뉴에서 &lt;code class=&quot;language-text&quot;&gt;버킷 개요&lt;/code&gt; 부분을 보면, 리전 정보를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40762111/134389491-bd17f42b-d05c-468c-a958-b09a1b10beba.png&quot; alt=&quot;스크린샷 2021-09-23 오전 2 05 45&quot;&gt;&lt;/p&gt;
&lt;p&gt;이제 깃허브 프로젝트 페이지로 이동하여, &lt;code class=&quot;language-text&quot;&gt;Settings&lt;/code&gt; - &lt;code class=&quot;language-text&quot;&gt;Secrets&lt;/code&gt; - &lt;code class=&quot;language-text&quot;&gt;New repository secret&lt;/code&gt;을 눌러 자격 증명 정보와 리전 정보 등록한다. &lt;code class=&quot;language-text&quot;&gt;Name&lt;/code&gt;은 임의로 만들어도 상관없고, &lt;code class=&quot;language-text&quot;&gt;Value&lt;/code&gt;는 발급받은 자격 증명 정보를 입력한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40762111/134390167-29f2f822-5fbd-4896-8491-e8d4e09c955b.png&quot; alt=&quot;스크린샷 2021-09-23 오전 2 10 02&quot;&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40762111/134391668-3ec3c86a-781d-4f5a-ac35-4fc8ed31f435.png&quot; alt=&quot;스크린샷 2021-09-23 오전 2 21 45&quot;&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40762111/134391665-7e8f5f22-9d09-4001-b797-0e85bfbebfe3.png&quot; alt=&quot;스크린샷 2021-09-23 오전 2 22 11&quot;&gt;&lt;/p&gt;
&lt;h3&gt;S3 버킷의 ACL(엑세스 제어 목록)에 피부여자 추가&lt;/h3&gt;
&lt;p&gt;다음은 새로 만든 계정이 우테코 팀 계정의 S3의 버킷에 객체를 업로드하기 위해서 접근할 수 있도록 하는 과정이다. S3의 버킷의 ACL에 피부여자로 등록하기 위해서는 AWS 계정의 ID가 필요하다. 그래서 이전 단계에서 자격 증명을 발급받았던 것처럼 &lt;strong&gt;새로 만든 계정&lt;/strong&gt;에서 메뉴의 &lt;code class=&quot;language-text&quot;&gt;내 보안 자격 증명&lt;/code&gt;을 눌러 접근한 뒤 &lt;code class=&quot;language-text&quot;&gt;계정 ID&lt;/code&gt; 탭을 눌러 &lt;code class=&quot;language-text&quot;&gt;정규 사용자 ID&lt;/code&gt;를 확인한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40762111/134394748-25489249-f6b4-44ac-9bd5-f8bc5a6e64db.png&quot; alt=&quot;스크린샷 2021-09-23 오전 2 43 43&quot;&gt;&lt;/p&gt;
&lt;p&gt;이제 다시 &lt;strong&gt;우테코 팀 계정&lt;/strong&gt;의 S3 버킷의 상세 페이지로 돌아가, 메뉴 중에 &lt;code class=&quot;language-text&quot;&gt;권한&lt;/code&gt;을 눌러 아래로 스크롤 해 &lt;code class=&quot;language-text&quot;&gt;ACL(액세스 제어 목록)&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;편집&lt;/code&gt;을 누른다. 이후 &lt;code class=&quot;language-text&quot;&gt;피부여자 추가&lt;/code&gt; 버튼을 눌러, 새로 만든 계정의 &lt;code class=&quot;language-text&quot;&gt;정규 사용자 ID&lt;/code&gt;를 입력하고 객체의 나열, 쓰기 / 버킷 ACL의 읽기, 쓰기를 모두 체크하고 저장한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40762111/134394153-b2be8b27-c5f9-4e6e-b065-9a2b88fbfa33.png&quot; alt=&quot;스크린샷 2021-09-23 오전 2 39 27&quot;&gt;&lt;/p&gt;
&lt;h3&gt;S3 버킷의 객체 소유권 변경&lt;/h3&gt;
&lt;p&gt;설정의 마지막으로 S3 버킷의 객체 소유권을 변경하는 단계이다. 새로 만든 계정이 S3의 버킷에 객체를 업로드해도, 객체의 소유권을 우테코 팀 계정이 갖기 위해 필요한 과정이다. 위의 과정과 비슷하게 &lt;strong&gt;우테코 팀 계정&lt;/strong&gt;의 S3 버킷 상세 페이지에서 &lt;code class=&quot;language-text&quot;&gt;권한&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;객체 소유권&lt;/code&gt; - &lt;code class=&quot;language-text&quot;&gt;편집&lt;/code&gt;을 누른다. 이후 &lt;code class=&quot;language-text&quot;&gt;버킷 소유자 선호&lt;/code&gt;를 누른 후 저장한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40762111/134395846-ef8398eb-a712-4281-bb6c-3f87ad7fb50c.png&quot; alt=&quot;스크린샷 2021-09-23 오전 2 51 55&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Github Actions workflow 작성&lt;/h2&gt;
&lt;p&gt;workflow는 프로젝트 repository 내에서도 작성할 수 있고, 따로 폴더와 파일을 만들어 작성할 수도 있다. 프로젝트 repository 내에서 작성하는 경우 메뉴에서 &lt;code class=&quot;language-text&quot;&gt;Actions&lt;/code&gt;를 누른 후, 왼쪽의 &lt;code class=&quot;language-text&quot;&gt;New workflow&lt;/code&gt; - 아래로 스크롤을 내려 &lt;code class=&quot;language-text&quot;&gt;Manual workflow&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;Set up this workflow&lt;/code&gt;를 눌러 작성할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40762111/134396850-5d7b8068-05de-480c-b21f-a9654cee7db2.png&quot; alt=&quot;스크린샷 2021-09-23 오전 2 58 28&quot;&gt;&lt;/p&gt;
&lt;p&gt;예시로 작성한 workflow는 다음과 같다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# .github/workflows/front-dev-deploy.yml&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; front&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dev&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;deploy

&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 적용될 액션&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; develop &lt;span class=&quot;token comment&quot;&gt;# 적용될 브랜치&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;frontend/**&apos;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# workflow에서 적용될 path&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;defaults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;working-directory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ./frontend &lt;span class=&quot;token comment&quot;&gt;# workflow에서 default working directory&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;deploy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest &lt;span class=&quot;token comment&quot;&gt;# 인스턴스 OS&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Checkout source code
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v2 &lt;span class=&quot;token comment&quot;&gt;# 워크플로에서 액세스할 수 있도록 에서 저장소를 체크아웃&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install Dependencies
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; yarn

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Build
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; yarn build

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; S3 Deploy
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; aws s3 sync ./dist s3&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//2021&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;cvi&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dev/ &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;acl bucket&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;owner&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;full&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;control &lt;span class=&quot;token comment&quot;&gt;# s3 이름 2021-cvi-dev&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.AWS_ACCESS_KEY_ID &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.AWS_SECRET_ACCESS_KEY &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;AWS_REGION&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.AWS_REGION &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Github actions가 &lt;code class=&quot;language-text&quot;&gt;develop&lt;/code&gt; 브랜치에 &lt;code class=&quot;language-text&quot;&gt;push&lt;/code&gt;되는 경우 동작하도록 workflow를 작성했다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40762111/134417159-38ad59a0-c625-4ed8-be04-ed409ca9c4fd.png&quot; alt=&quot;스크린샷 2021-09-23 오전 5 28 56&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;develop&lt;/code&gt;에 &lt;code class=&quot;language-text&quot;&gt;push&lt;/code&gt; 이벤트가 발생하니 Github Actions가 잘 동작한다.&lt;/p&gt;
&lt;p&gt;AWS에서 S3 버킷의 객체도 확인해 보니, 새롭게 업로드된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40762111/134417146-9f5725db-a55a-47b0-9006-67db188e66ee.png&quot; alt=&quot;스크린샷 2021-09-23 오전 5 27 45&quot;&gt;&lt;/p&gt;
&lt;h2&gt;참조&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/aws-actions/configure-aws-credentials&quot;&gt;https://github.com/aws-actions/configure-aws-credentials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/about-object-ownership.html&quot;&gt;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/about-object-ownership.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://yung-developer.tistory.com/111&quot;&gt;https://yung-developer.tistory.com/111&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[HTTP/2 프로토콜 NGINX 적용하기]]></title><description><![CDATA[HTTP/2 프로토콜의 특징과 NGINX에서의 간단한 설정 방법에 대해 알아본다. HTTP/2를 알아보기 전에, 그 이전 프로토콜인 HTTP/1.x를 먼저 알아보자. 대부분의 서버(NGINX, apache, SpringBoot…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-09-20-http2/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-09-20-http2/</guid><pubDate>Mon, 20 Sep 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;HTTP/2 프로토콜의 특징과 NGINX에서의 간단한 설정 방법에 대해 알아본다.&lt;/p&gt;
&lt;!-- end --&gt;
&lt;p&gt;HTTP/2를 알아보기 전에, 그 이전 프로토콜인 HTTP/1.x를 먼저 알아보자. 대부분의 서버(NGINX, apache, SpringBoot …등)는 기본설정으로 HTTP/1.1 이 적용되어 있다.&lt;/p&gt;
&lt;h2&gt;HTTP/1.x&lt;/h2&gt;
&lt;h3&gt;HTTP/1.0&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;HTTP/1.0&lt;/code&gt;에서는 기본적으로 단기 커넥션(Short-lived connections)을 제공한다. 요청마다 커넥션을 매번 새롭게 생성하고 응답이 도착하고 연결을 닫는다. 매번 TCP 핸드 셰이크의 오버헤드가 발생한다. 하나의 커넥션 안에서, 요청에 대한 응답이 오고 난 다음에야 새로운 요청을 보낼 수 있는 구조이다.&lt;/p&gt;
&lt;h3&gt;HTTP/1.1&lt;/h3&gt;
&lt;p&gt;HTTP/1.0의 다음 버전인 &lt;code class=&quot;language-text&quot;&gt;HTTP/1.1&lt;/code&gt; 에서는 영속적인 커넥션과 HTTP 파이프라이닝 기능이 추가되었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;영속적인 커넥션(Persistent Connection)
매번 연결을 성립하는 대신, 연결을 유지한 채 요청을 주고받는 방식이다. &lt;code class=&quot;language-text&quot;&gt;keep-alive&lt;/code&gt; header를 사용해서 연결이 얼마나 오랫동안 유지하는지 설정할 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;HTTP 파이프라이닝(HTTP Pipelining)
요청-응답이 한 호흡으로 데이터를 주고받는 것이 아닌, 여러 요청을 보내고, 각각의 응답을 기다리는 형태이다. 오류가 많아 현재 대부분의 브라우저에서는 활성화되지 않은 기능이다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;가장 대표적인 HTTP/1.1의 문제점은 HTTP HOL 블로킹(Head Of Line Blocking) 문제다. 간단하게 설명하면, 요청(Line)의 앞부분(head)에서 지연이 발생하게 되었을 때, 전체적인 성능 저하 현상이 발생하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1012px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e48ed44d2d693b0947c2ffef01a2cfde/9d874/2021-09-20-http2-0.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.19999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABDrAAAQ6wFQlOh8AAADG0lEQVR42n1TX0xSURg/IAjiv0yd5R9MI1N0oCN98IWXWuaubZnMrRfa2tiw+dCcW1u1u9pqVvLi0oVoKNXaKHRGog8WSoNEbSIgKlwVLzf/BE6dWuuhTvcYuKats333fPd8f873+33fASUl5ekqlU7sc33L2g3CdI2mT1RdffUUAIBxrqoqp6troJAkYUYoBDOam7tLKiurs5GttvZagcFgLZqZ/H7CNbF5sqlJUyaVSo8BCGHsqG2p/cOQR2cZ9mk/2ZZ0c+6dYjoIOMdWc21Wv3Z4mNCMmIlO++hKa8D7KxPZXK5gqWWYeGEx+55aRohOx2TwPvwCecjGoCUKhFdDQ0NsY2NjOtLpG1nhY+QDlEpl0vWwTSKRsOkNCRfZz2IYv76+PhWYzVLW9PS9wsXFpoKpqdun5+YeFRuHVBkoSCgURkskGK+iooJDZ2fhCoz3pE4WF76Mi+MKnkqlitHKs7nyljf5sm43H2xukkf9/s/b3rlR6Jm2/aQCDrizRVxAQXK5nIsqkclkUXoaBf/my6T4B/bkyGX0xowgm83DUpxFVWmIw+jB/vnLvb2u2iHTfE3/4OKVZ0YCNYUtFMqiUXUIOg4AK1c9kZijcaahBAsLBjFF9TykKMOdQMBwd3vDdGPF8Tj2EIfgLeSlt1JZfACSUBUYhvFobjhSHGfphcI4dV5eCu2VEAqZMv1+fY3b3XVx0tl5aX194LzXa+KAtTVzHEUZNSRp1Pp8hs6VFZPux85HMcqtUCjYkYagFRKUJazmFKUdaFhkMfeEhpzocHjsNuv49PiYY9LpnHUtkF+lyIMk+wTLyz1agnjVPjPzvGNra6BlF9r2GqZWq9kQ4iyz+Y+gfwBwZhiyfh+yPrUuboQvOY50m14VA6E1G8L3e7Kx0ZdDB6MxAahR/6xQrVawPR5dEc2JcBmJvePM+i1lFvLAD8NiRDp7ICFjPyHiMBg0tvj9r9toaC2h0Ls2CO2FEQ5pSvY5DOvM/1YY/sSEJ55DC3o+kcqYYrH4CD2LieXl5fFI/3v2RCJRLDorzc9PFggEKBb8BsC5YlABD6C1AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;multi-HTTP1_x_Connections&quot;
        title=&quot;&quot;
        src=&quot;/static/e48ed44d2d693b0947c2ffef01a2cfde/9d874/2021-09-20-http2-0.png&quot;
        srcset=&quot;/static/e48ed44d2d693b0947c2ffef01a2cfde/0eb09/2021-09-20-http2-0.png 500w,
/static/e48ed44d2d693b0947c2ffef01a2cfde/1263b/2021-09-20-http2-0.png 1000w,
/static/e48ed44d2d693b0947c2ffef01a2cfde/9d874/2021-09-20-http2-0.png 1012w&quot;
        sizes=&quot;(max-width: 1012px) 100vw, 1012px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;HTTP/1.x에서 성능 개선을 위해 클라이언트가 여러 병렬 요청을 수행하려는 경우, 여러 TCP 연결이 사용되어야 하며, 브라우저는 각각 최대 연결 개수를 정하고 있다. 크롬의 경우 최대 6개의 연결을 제공하기 때문에, 병렬적으로 요청을 받아올 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;도메인 별로 커넥션 수의 제약이 있기 때문에, 도메인을 샤딩하여 더 많은 커넥션을 활용하여 성능을 향상할 수도 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;HTTP/2&lt;/h2&gt;
&lt;p&gt;기존 HTTP 표준을 대체하는 것이 아니라 확장한 것으로, API의 작동 방식은 동일하게 유지한 채, 밑단의 전달 방식을 수정함으로써 성능 개선을 진행하였다. HTTP/2가 제공하는 특징 몇 가지를 차례대로 살펴보겠다. 더욱 자세한 설명은 &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/http2?hl=ko&quot;&gt;구글 HTTP/2 문서&lt;/a&gt; 를 참고하기 바란다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;바이너리 프레이밍 계층(Binary Framing)&lt;/p&gt;
&lt;p&gt;기존의 텍스트로 구성되었던 부분(줄 바꿈으로 RequestLine, Header, Body를 구분)을 HEADERS, DATA 프레임 둘로 구분하고 바이너리 형식으로 인코딩된다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/ae09920e853bee0b21be83f8e770ba01/2021-09-20-http2-1.svg&quot; alt=&quot;http2_init.svg&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;요청 및 응답 다중화&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;HTTP/1.x에서는 다중 커넥션을 활용하여 병렬 요청을 진행했지만, HTTP/2는 하나의 커넥션에서 위에서 정의한 프레임을 스트리밍해 줌으로써 요청과 응답의 다중화를 진행한다. 이를 통해 기존의 HTTP/1.1에서 응답이 오지 않았을 때, 요청을 보내지 못하는 HOL 블로킹 문제를 해결할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/47ba5b32e42cf5a06c3741d29ef9b94a/2021-09-20-http2-2.svg&quot; alt=&quot;http2.svg&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서버 푸시&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;기존의 1대1 요청 응답 방식과는 다르게, 하나의 요청에 대해 여러 응답을 보내줄 수 있다. 클라이언트가 html 요청을 보냈을 때, 이를 구성하는 css, js, image 파일들도 함께 보내줄 수도 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;헤더 압축&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;HTTP/2 에서는 헤더 전송의 오버헤드를 줄이기 위해, 요청과 응답 헤더 데이터를 압축한다. 압축하는 방식으로 HPACK을 사용하는데, 자세한 설명은 &lt;a href=&quot;https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-header-compression&quot;&gt;여기&lt;/a&gt; 를 참고하기 바란다.&lt;/p&gt;
&lt;h2&gt;NGINX 에서 HTTP/2 설정하기&lt;/h2&gt;
&lt;p&gt;HTTP/2 같은 경우, SSL/TLS 설정이 필수라고 명시하고 있지는 않지만, 대부분의 브라우저에서 SSL/TLS 설정이 되어있는 경우만 제공한다고 한다. NGINX에서도 SSL/TLS 설정을 해주어야 한다. 실제 nginx.conf을 다음과 같이 구성하면 된다. 서버 푸시에 자세한 설명은 &lt;a href=&quot;https://www.nginx.com/blog/nginx-1-13-9-http2-server-push/&quot;&gt;공식문서&lt;/a&gt; 참고하기 바란다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;server {
    # ssl설정을 해주고, http2를 적어주면 쉽게 설정할 수 있다.
    listen 443 ssl http2;

    ssl_certificate ssl/certificate.pem;
    ssl_certificate_key ssl/key.pem;

    root /var/www/html;

    # index.html에 요청이 왔을 때, style.css, main.js, image.jpg 파일도 함께 서버 푸시된다.
    location = /index.html {
        http2_push /style.css;
        http2_push /main.js;
        http2_push /image.jpg;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;NGINX에서는 upstream HTTP/2를 제공하지 않는다&lt;/h2&gt;
&lt;p&gt;HTTP/2가 HTTP/1.x의 단점을 보완하여 만들었기 때문에, 항상 HTTP/2 프로토콜을 사용하는 것이 좋아 보인다. 하지만, NGINX 에서는 클라이언트 사이드에서의 HTTP/2 통신만을 제공하고 있다.&lt;/p&gt;
&lt;p&gt;NGINX를 로드밸런서, Spring서버를 백엔드 서버로 두는 다음의 상황을 생각해 보자. 기존에는 모두 HTTP/1.1 프로토콜 방식으로 클라이언트-프록시, 프록시-백엔드 사이의 통신을 하고 있는 상태이다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1302px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/363c9f604f557fdf2bcc74e29711ff66/3b5a6/2021-09-20-http2-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 34.400000000000006%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABIklEQVR42p2Ry07CQBSG+7A+hIkxJrpRF8qCROMlwYWJiS9gjImy8IZKKoSwAC9AlUCx0BbaAp35nFZRROLCf3Jm5lwy55/za0xAShnbCEIKoqWiU/Mjf2RatAkh+A3J35ie18ad/mDAc6VKEARf3Y9KJyyeLfNoPcWxas3Acd34HoqQilXmvn5NoZHF8dtohmGg63pcMOy2sRs1AtuEoIdez7N+nmQjs8PaRZKh5+CaL3Fedt8UAY+D3Cbz6Rm2b1Ypm0U0V3VrNpsf81JF0rcJe5ai61NWrGaPF1g6XWHrNkXYtZCejVA1qDPo9zjM75K4mmPvLkHJLPz88qQ4ESKWqew+Hb/zObnv2YViyEOriP56Sa6eoe21Jh6U/xNiXJR3Ky8UI6mLxlIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;HTTP/1.1 NGINX&quot;
        title=&quot;&quot;
        src=&quot;/static/363c9f604f557fdf2bcc74e29711ff66/3b5a6/2021-09-20-http2-3.png&quot;
        srcset=&quot;/static/363c9f604f557fdf2bcc74e29711ff66/0eb09/2021-09-20-http2-3.png 500w,
/static/363c9f604f557fdf2bcc74e29711ff66/1263b/2021-09-20-http2-3.png 1000w,
/static/363c9f604f557fdf2bcc74e29711ff66/3b5a6/2021-09-20-http2-3.png 1302w&quot;
        sizes=&quot;(max-width: 1302px) 100vw, 1302px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;클라이언트-로드밸런서를 HTTP/2&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1301px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/527f770be3beca736a51d8bd83c6db9d/14a3e/2021-09-20-http2-4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 34.400000000000006%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABIElEQVR42q2RS0vDQBSF81vFjeiPEAR1owvdSAsW3FUoXVoouqgggkHEVBexajX4aE3tI22SziOfyViFihQX3mG4d5hzz5x7xmJGJJNl6iThL2FNEaRNURRPNQ/iAa7fQGppznEcI6X8vleJMlkn2mxLCMEwCIwOKcY07xqE4TBl13SjHrmzAvPlRYpOKXsR77FJx2+ntUQpyUnzkNz5OvtOnvuOi+V5HrZtI6RC91/RvWdE9wmCd+qta5aP1qi4VVZrm/Q6D5BiVL+FSjFKRJSuCqwcL7Frb+C82NMjZ6qSiXdmPBmzfbrDXHmBg5uqUZjZ8WWI1ILabYW9iy2Kl3ncdv2TcJbhmXdvIz/153dMJENCMWQ0DpBK/FD4D7/8AWtnFOpY8chCAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;HTTP/2 NGINX&quot;
        title=&quot;&quot;
        src=&quot;/static/527f770be3beca736a51d8bd83c6db9d/14a3e/2021-09-20-http2-4.png&quot;
        srcset=&quot;/static/527f770be3beca736a51d8bd83c6db9d/0eb09/2021-09-20-http2-4.png 500w,
/static/527f770be3beca736a51d8bd83c6db9d/1263b/2021-09-20-http2-4.png 1000w,
/static/527f770be3beca736a51d8bd83c6db9d/14a3e/2021-09-20-http2-4.png 1301w&quot;
        sizes=&quot;(max-width: 1301px) 100vw, 1301px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;앞서 설명한 설정 방식을 통해 쉽게 구조를 수정할 수 있다.&lt;/p&gt;
&lt;p&gt;많은 경우 로드밸런서와 백엔드 서버를 같은 프라이빗 네트워크에 두고 서비스를 구성한다. 프라이빗 네트워크는 지연 시간이 짧기 때문에, 성능의 주요 결정 요인은 클라이언트와 로드밸런서 사이의 지연 시간이다.&lt;/p&gt;
&lt;p&gt;위의 측면에서 볼 때, 지연 시간이 오래 걸리는 클라이언트와 로드밸런서 사이에 HTTP/2 통신을 하게 되었을 때, 기대되는 성능 향상은 클 것이다.&lt;/p&gt;
&lt;h3&gt;HTTP/2 → HTTP/2으로 연결해야 최고의 성능이지 않을까?&lt;/h3&gt;
&lt;p&gt;HTTP/2는 하나의 커넥션에서 요청과 응답을 스트리밍의 방식으로 연결한다. 이 연결은 End-to-End 가 아닌 각각의 연결에 대해서 적용된다. 모든 연결에 대해서 HTTP/2 프로토콜을 이용하는 것이 응답을 기다리지 않고 요청을 보내기 때문에 가장 최적의 성능일 것이다. 하지만, 로드밸런서와 백엔드 서버 사이에서의 아래의 이유로 HTTP/1.1로 연결해도 충분히 빠를 수 있기에 NGINX 에서는 upstream HTTP/2 통신 방식은 제공하고 있지 않는다고 한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;프라이빗 네트워크인 경우 지연시간이 짧다.&lt;/li&gt;
&lt;li&gt;NGINX는 기본적으로 512개의 커넥션을 제공하고 있다. 브라우저의 6개의 커넥션 제한과는 다르게 더 많은 커넥션을 연결할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;keep-alive&lt;/code&gt; 의 장점을 이용할 수 있다. 로드밸런서의 경우 통신하는 서버의 수가 제한되기 때문에, keep-alive 상태의 커넥션을 재사용하는 경우 많다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;HTTP/2 프로토콜의 특징과 NGINX 에서 설정하는 방법에 대해서 알아보았다. 설정은 매우 간편하지만, HTTP/2의 특징을 안 상태로 프로토콜의 개선이 각자의 상황에 효과적일지는 고민해 봤으면 좋겠다.&lt;/p&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/http2?hl=ko&quot;&gt;Google Developer HTTP/2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Connection_management_in_HTTP_1.x&quot;&gt;Mozila Developer Connection_management_in_HTTP_1.x&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/41637076/http2-with-node-js-behind-nginx-proxy&quot;&gt;StackOverflow http2-with-node-js-behind-nginx-proxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.linkedin.com/pulse/why-does-your-browser-limit-number-concurrent-ishwar-rimal/&quot;&gt;Why does your browser limit the number of concurrent network calls?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[메시지 큐에 대해 알아보자!]]></title><description><![CDATA[메시지 큐란 메시지 큐(Message Queue)는 프로세스 또는 프로그램 간에 데이터를 교환할 때 사용하는 통신 방법 중에 하나로, 메시지 지향 미들웨어(Message Oriented Middleware:MOM…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-09-19-message-queue/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-09-19-message-queue/</guid><pubDate>Sun, 19 Sep 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p style=&quot;font-family: sans-serif; text-align: center; color: #aaa; margin-bottom: 3em; font-size: 85%&quot;&gt;image origin: &lt;a href=&quot;https://blog.naver.com/kkumbo1003/222419008116&quot;&gt;blog.naver.com/kkumbo1003&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;메시지 큐란&lt;/h2&gt;
&lt;p&gt;메시지 큐(Message Queue)는 프로세스 또는 프로그램 간에 데이터를 교환할 때 사용하는 통신 방법 중에 하나로, 메시지 지향 미들웨어(Message Oriented Middleware:MOM)를 구현한 시스템을 의미한다. 메시지 지향 미들웨어란 비동기 메시지를 사용하는 응용 프로그램들 사이에서 데이터를 송수신하는 것을 의미한다.
여기서 메시지란 요청, 응답, 오류 메시지 혹은 단순한 정보 등의 작은 데이터가 될 수 있다.&lt;/p&gt;
&lt;p&gt;그림을 통해 쉽게 이해해보자. 메시지 큐는 메시지를 임시로 저장하는 간단한 버퍼라고 생각하면 된다. 메시지를 전송 및 수신하기 위해 중간에 메시지 큐를 두는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50273712/133918874-2664898b-a080-4df6-bd21-fad7a204029d.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;메시지 전송 시 생산자(Producer)로 취급되는 컴포넌트가 메시지를 메시지 큐에 추가한다. 해당 메시지는 소비자(Consumer)로 취급되는 또 다른 컴포넌트가 메시지를 검색하고 이를 사용해 어떤 작업을 수행할 때까지 메시지 큐에 저장된다. 각 메시지는 하나의 소비자에 의해 한 번만 처리될 수 있는데, 이러한 이유로 메시지 큐를 이용하는 방식을 일대일 통신이라고 부른다.&lt;/p&gt;
&lt;h2&gt;메시지 큐를 사용하는 경우는?&lt;/h2&gt;
&lt;p&gt;일반적인 클라이언트-서버 구조에서는 사용자가 요청을 하면 서버는 그에 대한 처리를 한 후 클라이언트에게 응답을 한다. 간단한 서버 구조에서는 굳이 메시지 큐를 사용할 필요가 없다. 사용자가 응답을 기다려야 하는 HTTP 요청을 바로 처리하지 않고 중간에 메시지 큐를 두는 경우는 썩 바람직하지 않아 보인다. 또한 메시지 큐를 적용하려면 다양한 메시지 큐 중에서 시스템의 목적에 맞는 것을 선정해야 한다. 이후 선정된 메시지 큐의 사용 방법도 익혀야 하며, 지원하는 다양한 옵션 중에 시스템이 추구하는 목적에 맞는 옵션을 찾아 설정해야 한다. 상상만 해도 번거로운 작업들이 수반되는데, 왜 메시지 큐를 사용하며, 어떤 경우에 도입하는 걸까?&lt;/p&gt;
&lt;p&gt;메시지 큐는 소비자(Consumer)가 실제로 메시지를 어느 시점에 가져가서 처리하는 지는 보장하지 않는다. 언젠가는 큐에 넣어둔 메시지가 소비되어 처리될 것이라고 믿는 것이다. 이러한 비동기적 특성 때문에 메시지 큐는 실패하면 치명적인 핵심 작업보다는 어플리케이션의 부가적인 기능에 사용하는 것이 적합하다.&lt;/p&gt;
&lt;p&gt;실제로 어떤 작업들에 메시지 큐를 사용하는지 예시를 통해 살펴보자.&lt;/p&gt;
&lt;h3&gt;이메일 전송&lt;/h3&gt;
&lt;p&gt;어떤 웹 사이트의 비밀번호를 잊어버려서 이메일을 통해 임시 비밀번호를 받거나, 새로운 회원가입을 위한 인증 코드를 받아본 경험이 있을 것이다.
우리는 이러한 상황들에서 이메일이 즉각적으로 수신되기를 기대하지는 않는다. 아무리 성격이 급한 사람이라도 몇 분 안에 오겠거니 생각할 것이다. 어느 정도의 응답 지연이 허용되며, 어플리케이션의 핵심 기능은 아닌 경우이므로 메시지 큐는 이런 경우 도움이 될 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50273712/135778817-0d97c716-6a04-4dae-b632-84da95b6292d.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;비밀번호 재설정을 위해 이메일을 발급하는 서비스, 회원가입을 위해 이메일을 발급하는 서비스 등은 메시지(이메일)를 큐에 넣을 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;이메일 전송 전용 서비스는 이메일이 어느 서비스로부터 생산되었는지와는 관계없이, 메시지 큐의 메시지를 하나씩 소비하고, 그저 이메일이 전송되어야 할 곳으로 이메일을 전송한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;이와 같은 접근 방식은 메시지 큐에 들어오는 메시지 수가 너무 많아지는 경우 이메일 전송 전용 서비스 인스턴스를 더 둠으로써 확장할 수 있으므로 확장성이 뛰어나다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;블로그 포스팅&lt;/h3&gt;
&lt;p&gt;모든 블로그 사용자가 웹에 최적화되어 있거나, 용량이 작은 이미지만 업로드하진 않을 것이다. 블로그 사용자가 게시글에 업로드한 이미지의 용량이 매우 큰 경우를 생각해보자. 블로그 서비스의 응답 시간을 저해하지 않으면서 사용자들에게 유연성을 제공하는 방법으로, 사용자가 업로드한 모든 이미지를 게시 과정에서 즉각 처리하는 것이 아닌, 사후처리하며 최적화하는 방법이 있다. 사용자 경험에 약간의 영향을 미칠 수는 있지만, 최적화는 응용 프로그램에서 가장 중요한 것은 아니며 작업을 즉시 수행할 필요도 없다. 메시지 큐는 이러한 상황에서도 사용될 수 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;사용자가 고용량의 이미지가 포함된 블로그 포스팅을 한다.&lt;/li&gt;
&lt;li&gt;이미지는 저장소에 전송된다.&lt;/li&gt;
&lt;li&gt;업로드된 이미지에 대한 정보가 포함된 메시지를 이미지 최적화 서비스의 메시지 큐에 담는다.&lt;/li&gt;
&lt;li&gt;이미지 최적화 서비스는 저장소에서 이미지를 가져와 최적화하고, 2번에서 저장해놨던 이미지를 대체한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;메시지 큐의 이점&lt;/h2&gt;
&lt;p&gt;메시지 큐를 사용함으로써 얻을 수 있는 이점은 다음과 같다.&lt;/p&gt;
&lt;h3&gt;비동기(Asynchronous)&lt;/h3&gt;
&lt;p&gt;메시지 큐는 생산된 메시지의 저장, 전송에 대해 동기화 처리를 진행하지 않고, 큐에 넣어 두기 때문에 나중에 처리할 수 있다.
여기서, 기존 동기화 방식은 많은 메시지(데이터)가 전송될 경우 병목이 생길 수 있고, 뒤에 들어오는 요청에 대한 응답이 지연될 것이다.&lt;/p&gt;
&lt;h3&gt;낮은 결합도(Decoupling)&lt;/h3&gt;
&lt;p&gt;생산자 서비스와 소비자 서비스가 독립적으로 행동하게 됨으로써 서비스 간 결합도가 낮아진다.&lt;/p&gt;
&lt;h3&gt;확장성(Scalable)&lt;/h3&gt;
&lt;p&gt;생산자 서비스 혹은 소비자 서비스를 원하는 대로 확장할 수 있기 때문에 확장성이 좋다.&lt;/p&gt;
&lt;h3&gt;탄력성(Resilience)&lt;/h3&gt;
&lt;p&gt;소비자 서비스가 다운되더라도 어플리케이션이 중단되는 것은 아니다. 메시지는 메시지 큐에 남아 있다. 소비자 서비스가 다시 시작될 때마다 추가 설정이나 작업을 수행하지 않고도 메시지 처리를 시작할 수 있다.&lt;/p&gt;
&lt;h3&gt;보장성(Guarantees)&lt;/h3&gt;
&lt;p&gt;메시지 큐는 큐에 보관되는 모든 메시지가 결국 소비자 서비스에게 전달된다는 일반적인 보장을 제공한다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;결국은 서버가 사용자에게 얼마나 빠르고 안정적으로 정보를 전달할 수 있는 지에 초점을 맞춘 기술이라고 생각한다.&lt;/p&gt;
&lt;p&gt;모든 어플리케이션에 메시지 큐가 필요한 것은 아니지만, 서버의 뒷단에서 실행되는 일부 작업을 메시지 큐에 맡김으로써 웹 어플리케이션의 성능을 향상시키는 것에 대해 생각해보는 것도 좋을 것이다.&lt;/p&gt;
&lt;p&gt;또한 메시지 큐에도 RabbitMQ, Kafka, ActiveMQ 등과 같은 다양한 오픈소스 시스템이 존재하는데, 각 시스템별 비교 분석 후 현재 도입하고자 하는 서비스에 적합한 시스템을 선정하는 것이 좋다.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://aws.amazon.com/message-queue/&quot;&gt;https://aws.amazon.com/message-queue/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.icelancer.com/2016/12/message-queue.html&quot;&gt;https://www.icelancer.com/2016/12/message-queue.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://lion-king.tistory.com/entry/Message%ED%81%90-%EB%A9%94%EC%8B%9C%EC%A7%80&quot;&gt;https://lion-king.tistory.com/entry/Message큐-메시지&lt;/a&gt; 큐-what-is&lt;/p&gt;</content:encoded></item><item><title><![CDATA[DB Index 입문]]></title><description><![CDATA[1. Index란? Index는 DB 분야에 있어서 테이블에 대한 동작의 속도를 높여주는 자료 구조를 일컫는다. Index는 테이블 내…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-09-18-db-index/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-09-18-db-index/</guid><pubDate>Sat, 18 Sep 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. Index란?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Index는 DB 분야에 있어서 테이블에 대한 동작의 속도를 높여주는 자료 구조를 일컫는다. Index는 테이블 내 1개의 컬럼, 혹은 여러 개의 컬럼을 이용하여 생성될 수 있다. 고속의 검색 동작뿐만 아니라 레코드 접근과 관련 효율적인 순서 매김 동작에 대한 기초를 제공한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;DB Index는 흔히 &lt;strong&gt;책의 목차&lt;/strong&gt;에 비유됩니다. 책에 목차가 없다고 가정해봅시다. 독자가 해당 책에서 특정 내용을 찾으려고 한다면 어떤 행동을 취하게 될까요? 책의 첫 페이지부터 시작해서, 원하는 내용이 나올 때까지 모든 페이지를 일일이 찾아 읽을 것입니다. 책의 분량이 적거나 찾고자 하는 내용이 책의 전반부에 위치한다면, 검색에 소요되는 시간이 비교적 짧을 수 있습니다. 그러나 책의 분량이 많거나 찾고자 하는 내용이 책의 후반부에 위치한다면, 검색에 오랜 시간이 걸립니다.&lt;/p&gt;
&lt;p&gt;반대로 책에 목차가 존재한다면, 책의 분량이 많더라도 독자는 찾고자 하는 특정 내용이 몇 페이지에 있는지 바로 알 수 있습니다. 이처럼 목차는 검색에 소요되는 시간을 비약적으로 줄여줍니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SQL&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;USER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; COMPANY_ID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ?&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;DB의 특정 테이블에서 원하는 데이터들을 조회할 때, 조건절에 사용하는 컬럼의 Index가 없다면 어떻게 될까요? 책의 사례와 유사하게, 원하는 데이터의 위치를 특정할 힌트가 없다보니 테이블 전체를 탐색(Full Scan)하게 됩니다. 테이블에 데이터의 양이 많아질수록 검색에 소요되는 시간이 길어집니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/133879642-a98c1b23-1c8d-4aa7-917b-fe569bc4d375.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SQL&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INDEX&lt;/span&gt; USER_COMPANY_INDEX &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;COMPANY_ID&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#2개 이상의 컬럼을 사용해서 인덱스를 생성할 수도 있다.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Index는 데이터의 주소값을 저장하는 별도의 특별한 자료 구조입니다. USER 테이블의 &lt;code class=&quot;language-text&quot;&gt;COMPANY_ID&lt;/code&gt; 컬럼에 대한 Index가 존재한다면, 예시 쿼리를 수행할 때 테이블 전체를 탐색하지 않고 해당 Index를 바탕으로 원하는 데이터의 위치를 빠르게 검색합니다. Index는 테이블에 있는 하나 이상의 컬럼으로 생성이 가능합니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;2. 성능 테스트&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;User.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//@Table(indexes = {@Index(name = &quot;i_user&quot;, columnList = &quot;name&quot;)})&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;unique &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nullable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;IndexTest.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootTest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webEnvironment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebEnvironment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NONE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IndexTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserRepository&lt;/span&gt; userRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;index_test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// given&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1_000_000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; randomName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;UUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;randomName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            userRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        userRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;tester&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;StopWatch&lt;/span&gt; stopWatch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StopWatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// when&lt;/span&gt;
        stopWatch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        userRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findByName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;tester&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        stopWatch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// then&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stopWatch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;prettyPrint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;100만건의 데이터가 존재할 때, 원하는 데이터를 조회하는데 걸리는 시간을 측정하는 테스트 예제입니다.&lt;/li&gt;
&lt;li&gt;테스트 환경에 따라 검색 성능이 적게는 2배에서 많게는 4배 차이가 났습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;img width=&quot;462&quot; alt=&quot;스크린샷 2021-09-18 오후 5 28 03&quot; src=&quot;https://user-images.githubusercontent.com/56240505/133882483-9f9e0e91-86b8-4efc-9241-59b5778e9039.png&quot;&gt;
&lt;ul&gt;
&lt;li&gt;Index 적용 전에는 약 0.22초가 걸립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;img width=&quot;457&quot; alt=&quot;스크린샷 2021-09-18 오후 5 12 57&quot; src=&quot;https://user-images.githubusercontent.com/56240505/133882540-b17b1f07-6d5b-4e92-8ad4-d2d9ca1d2d76.png&quot;&gt;
&lt;ul&gt;
&lt;li&gt;Index 적용 이후에는 약 0.12초가 걸립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2&gt;3. Index 자료 구조&lt;/h2&gt;
&lt;p&gt;DB Index에 적합한 자료 구조로는 크게 Hash Table, B-Tree, B+Tree 등이 있습니다.&lt;/p&gt;
&lt;h3&gt;3.1. Hash Table&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/133892261-f95fd474-7955-4f8f-81f8-457d71ac6324.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;해시 테이블은 Key-Value로 이루어진 데이터를 저장하는데 특화된 자료 구조입니다. 해시 테이블 기반의 DB Index는 특정 컬럼의 값과 데이터의 위치를 Key-Value로 사용합니다.&lt;/p&gt;
&lt;p&gt;해시 테이블은 내부에 &lt;strong&gt;버켓&lt;/strong&gt;이라고 하는 배열이 존재합니다. 해시 함수를 통해 Key를 고유한 해시 값으로 변환시키는데, 이를 버켓 배열의 인덱스로 사용하며 해당 인덱스에 Value를 저장합니다. Key 값으로 Value가 저장되어 있는 위치(주소)를 바로 산출할 수 있기 때문에, 해시 테이블의 평균적인 시간 복잡도는 O(1)입니다. 하지만 해시 함수를 제대로 정의하지 않으면 해시 함수를 통해 산출한 해시 값이 중복되는 &lt;a href=&quot;https://preamtree.tistory.com/20&quot;&gt;해시 충돌&lt;/a&gt;이 발생합니다. 너무 많은 해시 충돌이 발생하면 검색 성능이 하락해 시간 복잡도가 O(N)에 수렴할 수 있습니다.&lt;/p&gt;
&lt;p&gt;아울러 Index 자료 구조로 해시 테이블을 사용하는 경우는 매우 제한적입니다. 해시 함수는 Key가 조금이라도 다르면 완전히 다른 해시 값을 생성합니다. 이러한 해시 테이블을 사용하는 Index의 경우 WHERE 조건의 등호(=) 연산에는 효율이 좋지만, 부등호 연산(&gt;, &amp;#x3C;)은 부적합합니다. 해시 테이블은 내부 데이터들이 정렬되어 있지 않아 탐색이 효율적이지 않습니다.&lt;/p&gt;
&lt;h3&gt;3.2. B-Tree&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/133893917-705097ca-4f0b-4f6a-8540-435988282643.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/133895971-588e05fd-a013-4a39-9e89-86efd1b4d5c5.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;B-Tree란 자식 노드가 2개 이상인 트리를 의미합니다. 이진검색 트리처럼 각 Key의 왼쪽 자식은 항상 Key보다 작은 값을, 오른쪽 자식은 큰 값을 가집니다. B-Tree 기반의 DB Index는 특정 컬럼의 값(Key)에 해당하는 노드에 데이터의 위치(Value)를 저장합니다. B-Tree 자료 구조의 상세한 내부 동작 원리는 &lt;a href=&quot;https://velog.io/@emplam27/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EA%B7%B8%EB%A6%BC%EC%9C%BC%EB%A1%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-B-Tree&quot;&gt;자료구조 - 그림으로 알아보는 B-Tree&lt;/a&gt;을 참조하길 바랍니다.&lt;/p&gt;
&lt;p&gt;B-Tree의 Key-Value 값들은 항상 Key를 기준으로 오름차순 정렬입니다. 이로 인해 부등호 연산(&gt;, &amp;#x3C;)에 대해 해시 테이블보다 효율적인 데이터 탐색이 가능합니다. 또한 B-Tree는 균형 트리(Balanced Tree)로서, 최상위 루트 노드에서 리프 노드까지의 거리가 모두 동일하기 때문에 평균 시간 복잡도는 O(logN)입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/133894623-ea20089e-8b18-49af-b361-6fae2223e6bb.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;그러나 Index가 적용된 테이블에 데이터 갱신(INSERT, UPDATE, DELETE)이 반복되다보면, 트리의 균형이 깨지면서 성능이 악화됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/133896121-8fb5241c-ab39-4180-a751-51ee455bf02c.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;또한 DB Index 컬럼은 부등호(&gt;, &amp;#x3C;)를 이용한 순차 검색 연산이 자주 발생합니다. B-Tree가 해시 테이블보다 부등호를 이용한 검색 연산 성능이 좋지만, 순차 검색의 경우 중위 순회를 하기 때문에 효율이 좋지 않습니다. 예시의 경우 7-&gt;3-&gt;8-&gt;1-&gt;9-&gt;4-&gt;10-&gt;0-&gt;11-&gt;5-&gt;2-&gt;6 순으로 조회하는 등 상당히 많은 노드를 확인해야 합니다.&lt;/p&gt;
&lt;p&gt;이러한 연유로 MySQL 엔진인 InnoDB는 B-Tree를 확장 및 개선한 B+Tree를 Index의 자료 구조로 사용합니다.&lt;/p&gt;
&lt;h3&gt;3.3. B+Tree&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/133894968-99fda4ba-a422-448c-a19d-fa8fc03fa364.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;B+Tree는 B-Tree를 확장 및 개선한 자료 구조로서, 말단의 리프 노드에만 데이터의 위치(Value)를 관리합니다. 중간 브랜치 노드에 Value가 없어서 B-Tree보다 메모리를 덜 차지하는 만큼, 노드의 메모리에 더 많은 Key를 저장할 수 있습니다. 아울러 하나의 노드에 더 많은 Key를 저장하는 만큼 트리의 높이가 더 낮아집니다.&lt;/p&gt;
&lt;p&gt;또한 말단의 리프 노드들끼리는 LinkedList 구조로 서로를 참조하고 있습니다. 따라서 부등호(&gt;, &amp;#x3C;)를 이용한 순차 검색 연산을 하는 경우, 많은 노드를 방문해야 하는 B-Tree에 비해 B+Tree는 말단 리프 노드를 저장한 LinkedList를 한 번만 탐색하는 등 속도 이점이 있습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;4. 고려 사항&lt;/h2&gt;
&lt;p&gt;Index를 사용하면 조회 성능이 우수해진다는 것을 알았습니다. 하지만 무턱대고 컬럼에 Index를 적용하는 것은 오히려 성능이 저하되는 역효과가 발생할 수 있습니다.&lt;/p&gt;
&lt;p&gt;Index는 항상 최신 상태로 정렬되기 위해, 데이터 갱신(INSERT, UPDATE, DELETE) 작업에 대해 추가적인 연산이 발생합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;INSERT : 새로운 데이터에 대한 인덱스가 추가된다.&lt;/li&gt;
&lt;li&gt;DELETE : 삭제하는 데이터의 인덱스를 제거한다.&lt;/li&gt;
&lt;li&gt;UPDATE : 기존의 인덱스를 제거하고, 갱신된 데이터에 대해 인덱스를 추가한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;앞서 살펴본 Index 트리 자료 구조는 값이 추가 혹은 삭제될 때마다, 트리 균형을 위해 트리 구조의 재분배 및 합병 등 복잡한 연산이 수반됩니다. 따라서 데이터 갱신보다는 조회에 주로 사용되는 컬럼에 Index를 생성하는 것이 유리합니다.&lt;/p&gt;
&lt;h3&gt;4.1. Index 대상 컬럼 선정&lt;/h3&gt;
&lt;p&gt;일반적으로 Cardinality가 높은 컬럼을 우선적으로 인덱싱하는 것이 검색 성능에 유리합니다. Cardinality란 특정 데이터 집합의 유니크(Unique)한 값의 개수를 의미합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;남-여 등 2가지 값만 존재하는 성별 컬럼은 &lt;strong&gt;중복도가 높으며 카디널리티가 낮습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;개인마다 고유한 값이 존재하는 주민번호 컬럼은 &lt;strong&gt;중복도가 낮으며 카디널리티가 높습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cardinality 높은 컬럼의 경우, Index를 통해 데이터를 더 많이 필터링할 수 있기 때문입니다.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://chartio.com/learn/databases/how-does-indexing-work/&quot;&gt;How Does Indexing Work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%9D%B8%EB%8D%B1%EC%8A%A4_(%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4)&quot;&gt;위키백과 - 인덱스 (데이터베이스)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/B%2B_%ED%8A%B8%EB%A6%AC&quot;&gt;위키백과 - B+ 트리&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.btechsmartclass.com/data_structures/b-trees.html&quot;&gt;B - Tree Datastructure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mangkyu.tistory.com/96&quot;&gt;[Database] 인덱스(index)란?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://junhyunny.github.io/information/data-structure/db-index-data-structure/&quot;&gt;DB 인덱스(INDEX) 자료구조&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@emplam27/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EA%B7%B8%EB%A6%BC%EC%9C%BC%EB%A1%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-B-Tree&quot;&gt;[자료구조] 그림으로 알아보는 B-Tree&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@jewelrykim/Binary-Search-Tree%EC%97%90%EC%84%9C-BTree%EA%B9%8C%EC%A7%80Database-Index-%EC%B6%94%EA%B0%80&quot;&gt;Binary Search Tree에서 B+Tree까지(Database Index 추가)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://yurimkoo.github.io/db/2020/03/14/db-index.html&quot;&gt;DB 인덱스를 효과적으로 설정하는 방법 - 고려해야 할 4가지&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Replication과 Clustering]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-09-18-replication_clustering/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-09-18-replication_clustering/</guid><pubDate>Sat, 18 Sep 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;프로젝트를 진행하면서 서버를 늘리게 되었다. 서비스 이용자가 당장 많아서 적용한 것은 아니었지만 미래에 많은 사용자가 이용하기를 바라는 마음과 그때 허겁지겁 대비하는 것보다는 미리 대비하자는 차원에서 적용하였다. 하지만 서버는 늘렸는데 과연 효율적으로 동작을 할 까라는 의문이 들었다. 결국, 서버는 데이터베이스와 통신을 하여 정보를 읽어오거나 수정하는 작업을 거치는데 우리 서비스의 데이터베이스는 하나뿐이었다. 과연 서버를 확장한 것 만으로 효과가 있을 것인가란 의문이 들었고 우리 팀은 데이터베이스도 확장하기로 하였다.&lt;/p&gt;
&lt;p&gt;데이터베이스를 확장하는 데는 여러 가지 방법이 있었다. 이번 글에서는 데이터베이스 Clustering과 Replication에 대해 알아보자.&lt;/p&gt;
&lt;h2&gt;일반적인 데이터베이스 구조&lt;/h2&gt;
&lt;p&gt;우선 일반적인 데이터베이스의 구조를 보자. 처음으로 데이터베이스를 구성하게 되면 아래와 같은 구조로 데이터베이스를 만들었을 것이다. 데이터베이스 서버와 스토리지가 하나씩 구성되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63634505/133868099-4176692c-f042-47ec-af6c-a3ee969f2786.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;이렇게 구성된 데이터베이스는 그림만 봐도 서버가 제대로 동작하지 않으면 먹통이 된다는 것을 알 수 있다. 이러한 문제점을 해결하기 위해 가장 먼저 떠오르는 방법은 서버를 하나 더 늘리는 것이다. 이렇게 서버를 하나가 아닌 여러개로 구성하는 것을 Clustering이라 한다.&lt;/p&gt;
&lt;h2&gt;Clustering 이란?&lt;/h2&gt;
&lt;p&gt;앞서 본 데이터베이스 구조의 문제, 데이터베이스 서버가 동작하지 않게 되면 전체 서비스가 동작할 수 없는 점을 해결하기 위해 Clustering을 통하여 서버를 한 대가 아닌 두 대로 구성하게 된다. 서버가 두대인 상황을 간략하게 표현하면 아래와 같은 구조다. 이때 DB서버는 서로 다른 인스턴스테서 동작한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63634505/133868115-03263a5c-d337-4707-a2b6-afcc43c6f552.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;이 경우 같은 데이터베이스 서버 두 대를 하나로 묶어 운영하고 있다. 이때 두 서버의 상태가 모두 Active인 상태다. 이렇게 되면 서버 한 대가 죽게 되더라도 하나의 서버가 동작하고 있어서 서비스에 큰 문제가 발생하지 않는다. 다른 서버가 동작하는 동안 복구를 하여 서비스의 중단이 없도록 할 수 있다. 또한 이렇게 구성함으로써 하나의 데이터베이스 서버에 가해지던 부하가 두 개로 나눠져 지므로 CPU와 Memory도 부하가 줄어들게 된다.&lt;/p&gt;
&lt;h3&gt;Active &amp;#x26; Stand-By&lt;/h3&gt;
&lt;p&gt;이렇게 구성하면 무조건 좋은점만 있을까? 여러 개의 서버가 하나의 스토리지를 공유함으로써 병목현상이 발생할 수 있다. 이러한 문제점을 해결하기 위해 하나의 서버는 Active 상태 다른 하나는 Stand-by 상태로 두는 방법이 있다. Active 상태의 서버에 문제가 생겼을 때 Fail over를 하여 Stand-by 서버를 Active로 전환하여 문제에 대응 할 수 있다. 비용적인 부분에서는 별다른 차이가 없을 수 있지만 한 대로 서버가 운영되기 때문에 병목현상을 해결 할 수있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63634505/133868138-68c71901-79fb-4502-8429-f450fd6eaedf.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;하지만 Active &amp;#x26; Stand-by에도 문제점이 있다. Fail over가 발생하는 시간 동안에는 서비스가 중단될 수 밖에 없다. 또한 결론적으로 한 대로 운영하기 때문에 효율은 Active &amp;#x26; Active의 1/2 정도가 된다.&lt;/p&gt;
&lt;p&gt;서버를 하나 더 둠으로써 문제를 해결하고 싶지만 뭔가 찜찜한 기분이 든다. 아마 서버는 늘리지만, 스토리지는 그대로 두는 점 일 것이다. 그래서 이번에는 스토리지를 여러개 가지는 Replication에 대하여 알아보자.&lt;/p&gt;
&lt;h2&gt;Replication 이란?&lt;/h2&gt;
&lt;p&gt;Replication은 말 그대로 복제라는 의미다. 여기서는 데이터베이스 스토리지를 복제하는 것을 의미한다. Clustering은 단순히 데이터베이스 서버를 확장한 것이라면 Replication은 데이터베이스 서버와 스토리지 모두를 확장하게 된다. 이때 단순히 확장만 하는 것이 아니라 메인으로 사용할 Master 서버와 이를 복제한 Slave 서버로 구성하게 된다. 아래 그림은 Replication을 통해 확장한 구조를 나타낸다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63634505/133868152-6a82b34f-1994-484b-805e-cd0b2f9c4d5b.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;Master와 Slave로 구성된 구조를 자세히 살펴보면 Master 서버에는 INSERT, UPDATE, DELETE 작업이 전달되고 Slave 서버에는 SELECT 작업이 전달되는 것을 볼 수 있다. Slave는 결국 Master 서버에서 복제된 데이터이기 때문에 데이터의 조작이 발생할 수 있는 INSERT, UPDATE, DELETE 작업은 Master로 전달이 되고 조회만을 하는 SELECT 작업은 Slave 서버를 통하여 진행하게 된다. 그림에서는 Slave 서버가 하나뿐이지만 서비스에 맞게 Slave 서버를 여러 개 가져갈 수 있다. 데이터베이스에서 발생하는 대부분의 쿼리는 조회인 SELECT인데 이러한 것을 Slave 서버를 통해 분산하여 처리할 수 있으니 좀 더 성능 향상을 가져갈 수 있다.&lt;/p&gt;
&lt;h3&gt;Replication의 단점&lt;/h3&gt;
&lt;p&gt;Replication을 사용한다고 좋은점만 있는 것은 아니다. 각각의 서로 다른 서버로 운영하다보니 버전을 관리해야한다. 이때 Master와 Slave의 데이터베이스 버전을 동일하게 맞춰주는 것이 좋다. 버전이 다를 경우 적어도 Slave 서버가 상위 버전이어야 한다. 또 한가지의 단점으로는 Master에서 Slave로 비동기 방식으로 데이터를 동기화 하기 때문에 일관성있는 데이터를 얻지 못할 수 있다. 동기방식으로 Replication을 할 수 있지만 이럴 경우 속도가 느려진다는 문제점이 있다. 마지막으로는 Master 서버가 다운이 될경우 복구 및 대처가 까다롭다는 단점이 있다.&lt;/p&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;이번 글에서는 데이터베이스를 확장하기 위한 방법으로 Clustering과 Replication에 대해 알아보았다. Clustering은 데이터베이스 서버만을 늘려 처리하는 것이고 Replication은 서버와 스토리지 모두 복제하여 구성하는 방법이었다. 이때 Replication은 Master와 Slave로 구성하여 SELECT 쿼리는 Slave에서 나머지 INSERT, UPDATE, DELETE는 Master에서 진행하였다. Clusterting과 Replication 중 어느 것이 좋다고 말할 수는 없는 것 같다. 무엇보다도 데이터베이스를 무조건 확장하는 것은 최대한 피해야 할 것이다. 각자의 서비스에 맞춰서 적절하게 선택한다면 좀 더 성능이 좋은 서비스를 제공할 수 있을 것 같다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://jordy-torvalds.tistory.com/94&quot;&gt;https://jordy-torvalds.tistory.com/94&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mangkyu.tistory.com/97&quot;&gt;https://mangkyu.tistory.com/97&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bryceyangs.github.io/study/2021/06/01/Database-Sharding-&amp;#x26;-Replication-&amp;#x26;-Clustering/&quot;&gt;https://bryceyangs.github.io/study/2021/06/01/Database-Sharding-&amp;#x26;-Replication-&amp;#x26;-Clustering/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[🧶 Java 에서 스레드 풀(Thread Pool) 을 사용해 보자]]></title><description><![CDATA[프로세스 내에서 스레드의 생성 및 수거가 빈번하게 발생한다면 메모리 할당에 소모되는 비용이 많이 들지 않을까? 이에 대한 해답으로 스레드 풀에 대해 알아보고 Java 에서 어떻게 사용하면 되는지 알아보자. 스레드 풀(Thread Pool…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-09-18-java-thread-pool/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-09-18-java-thread-pool/</guid><pubDate>Fri, 17 Sep 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;프로세스 내에서 스레드의 생성 및 수거가 빈번하게 발생한다면 메모리 할당에 소모되는 비용이 많이 들지 않을까? 이에 대한 해답으로 스레드 풀에 대해 알아보고 Java 에서 어떻게 사용하면 되는지 알아보자.&lt;/p&gt;
&lt;h2&gt;스레드 풀(Thread Pool)&lt;/h2&gt;
&lt;p&gt;스레드 제어 문제를 해결할 방법으로 스레드 풀을 사용한다. 스레드 풀은 매번 생성 및 수거 요청이 올 때 스레드를 생성하고 수거하는 것이 아닌, 스레드 사용자가 설정해둔 개수만큼 미리 생성해두는 것이다.&lt;/p&gt;
&lt;h3&gt;스레드(Thread)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;어떤 프로그램 내에서 실행되는 흐름의 단위&lt;/li&gt;
&lt;li&gt;특히 프로세스 내에서 실행되는 흐름의 단위&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;풀(Pool)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;필요할 때마다 개체를 할당하고 파괴하는 대신, 사용 준비된 상태로 초기화된 개체 집합&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;스레드는 동일한 메모리 영역에서 생성 및 관리가 이루어지지만, 생성하거나 수거할 때 커널 오브젝트를 동반하는 리소스이므로 생성 비용이 크게 발생한다. 스레드를 제어할 수 없는 상태에서 스레드를 무차별적으로 생성하면 리소스가 빠르게 소진되는 상황이 발생할 수 있다. 그러면 어떻게 하면 효율적으로 스레드를 제어할 수 있을까?
스레드 풀의 동작 방식은 간단하게 보면 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;병렬 작업의 형태로 동시 코드를 작성한다.&lt;/li&gt;
&lt;li&gt;실행을 위해 스레드 풀의 인스턴스에 제출한다.&lt;/li&gt;
&lt;li&gt;제출한 인스턴스에서 실행하기 위해 재사용되는 여러 스레드를 제어한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;스레드 풀을 사용하면 비용적인 측면이나 컨텍스트 스위치가 발생하는 상황에서 딜레이를 줄일 수 있다는 장점이 있다. 그렇다면 단점은 어떤 것이 있을까?&lt;/p&gt;
&lt;p&gt;단점은 스레드 풀에 너무 많은 양의 스레드를 만들어둔다면 메모리 낭비가 심해질 수 있다는 점이다. 그 때문에 얼만큼의 스레드가 필요할지 예측하고 할당해서 사용하는 것이 스레드 풀을 현명하게 사용하는 것이라고 할 수 있다.&lt;/p&gt;
&lt;h2&gt;Java 에서 스레드 풀 사용하기&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ExecutorService&lt;/code&gt; 인터페이스의 구현 객체를 정적 팩토리 메서드로 제공하는 &lt;code class=&quot;language-text&quot;&gt;Executors&lt;/code&gt; 클래스의 세 가지 메소드 중 하나를 이용하여 스레드 풀을 쉽게 생성할 수 있다. 자세한 메서드 사용법은 &lt;a href=&quot;https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html&quot;&gt;이곳&lt;/a&gt;에서 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ThreadPoolExecutor&lt;/code&gt; 에서 사용되는 파라미터로는 &lt;code class=&quot;language-text&quot;&gt;corePoolSize(생성할 개수), maximumPoolSize(생성할 최대 개수), keepAliveTime(유지 시간)&lt;/code&gt; 이 있다. 스레드 풀에서 스레드를 생성할 때 &lt;code class=&quot;language-text&quot;&gt;corePoolSize&lt;/code&gt; 의 파라미터만큼 코어 스레드를 생성한다. 그리고 새로운 작업이 들어올 때 모든 코어 스레드가 사용 중이고 내부 큐가 가득 차면 스레드 풀의 최대 크기가 &lt;code class=&quot;language-text&quot;&gt;maximumPoolSize&lt;/code&gt; 만큼 커질 수 있다. 만약 현재 스레드 풀이 &lt;code class=&quot;language-text&quot;&gt;corePoolSize&lt;/code&gt; 보다 많은 스레드를 가지고 있다면, 초과한 스레드에 대해서 &lt;code class=&quot;language-text&quot;&gt;keepAliveTime&lt;/code&gt; 파라미터값보다 오랫동안 할 일이 없으면 제거된다. 이것은 자원의 낭비를 감소 시켜 효율적으로 스레드 풀을 관리할 수 있게 된다.&lt;/p&gt;
&lt;p&gt;스레드 풀에 작업요청을 하는 방식은 &lt;code class=&quot;language-text&quot;&gt;execute( )&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;submit( )&lt;/code&gt; 방식이 있다. &lt;code class=&quot;language-text&quot;&gt;execute&lt;/code&gt; 방식은 작업 처리 중에 예외가 발생하면 해당 &lt;b&gt;스레드가 종료&lt;/b&gt;되고 스레드 풀에서 제거한 뒤, 새로운 스레드를 생성하여 다른 작업을 처리한다. 또한 &lt;b&gt;처리결과를 반환하지 않는다.&lt;/b&gt; 반대로 &lt;code class=&quot;language-text&quot;&gt;submit&lt;/code&gt; 은 작업 처리 중에 예외가 발생하더라도 &lt;b&gt;스레드가 종료되지 않고&lt;/b&gt; 다음 작업에 사용된다. 또한 &lt;b&gt;처리 결과를 &lt;code class=&quot;language-text&quot;&gt;Future&amp;lt;?&gt;&lt;/code&gt; 로 반환&lt;/b&gt;한다. 따라서 스레드 풀을 사용할 때 &lt;code class=&quot;language-text&quot;&gt;submit&lt;/code&gt; 을 사용하는 점이 더 바람직하다.&lt;/p&gt;
&lt;p&gt;테스트 클래스를 하나 생성하여 테스트를 진행한다.&lt;/p&gt;
&lt;p&gt;우선 &lt;code class=&quot;language-text&quot;&gt;MyCounter&lt;/code&gt; 클래스를 하나 만든다. 해당 클래스는 &lt;code class=&quot;language-text&quot;&gt;increment&lt;/code&gt; 메서드를 통해 &lt;code class=&quot;language-text&quot;&gt;count&lt;/code&gt; 를 증가시킨 후 1초의 대기시간을 갖는다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyCounter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; count&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;increment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; temp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; count&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            count &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; temp &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; count&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;첫 번째 방법은 &lt;code class=&quot;language-text&quot;&gt;Executors.newFixedThreadPool(int nThreads)&lt;/code&gt; 에 대해 알아본다.&lt;/p&gt;
&lt;p&gt;파라미터로 제공되는 n 개 만큼 스레드 풀을 생성한다. 보통 일정량의 업무가 발생할 때 사용한다. 다음 예시는 18개의 스레드가 필요한 Task 를 제공하고 5개의 스레드 풀로 처리하는 과정을 확인해 보는 테스트다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token annotation punctuation&quot;&gt;@DisplayName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;FixedThreadPool 을 생성한다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;testCounterWithConcurrencyFixed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; numberOfThreads &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ExecutorService&lt;/span&gt; service &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Executors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newFixedThreadPool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;CountDownLatch&lt;/span&gt; latch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CountDownLatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;numberOfThreads&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;MyCounter&lt;/span&gt; counter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyCounter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;iterateThread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;numberOfThreads&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; service&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; latch&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; counter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; service&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPoolSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;iterateThread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; numberOfThreads&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExecutorService&lt;/span&gt; service&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CountDownLatch&lt;/span&gt; latch&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyCounter&lt;/span&gt; counter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; numberOfThreads&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;submit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                counter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;increment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                latch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;countDown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        latch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/49058669/133976949-a4dd1594-0b04-4013-86c5-d2f8fb6c688a.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;실행 결과를 살펴보면 한 스레드에서 작업을 처리하는 데 1초가 걸리도록 설정이 되어있다. 그 때문에 5개의 작업을 3번, 3개의 작업을 1번 하게 되면서 총 작업 시간은 (3+1)초 + 로직 실행 시간(300ms)이 된다. 따라서 스레드 풀에 생성된 &lt;b&gt;스레드 개수만큼 작업을 처리&lt;/b&gt;하는 모습을 확인할 수 있다. 또한, 스레드 풀의 크기가 5개로 유지되는 모습을 확인할 수 있다. 해당 작업에서 Count 가 여러 스레드에서 동시에 호출되는데 이 경우 비동기로 처리되기 때문에 실제 Count 의 결과(&lt;code class=&quot;language-text&quot;&gt;counter.getCount()&lt;/code&gt;)는 100보다 작은 값이 반환되며 실행할 때마다 랜덤한 결과를 반환하는 모습을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;두 번째 방법은 &lt;code class=&quot;language-text&quot;&gt;Executors.newCachedThreadPool()&lt;/code&gt; 에 대해 알아본다.&lt;/p&gt;
&lt;p&gt;초기 스레드 개수가 0개로 설정되며 스레드 개수보다 많은 양의 작업의 요청되면 &lt;b&gt;새로운 스레드를 생성하여 작업을 처리&lt;/b&gt;한다. 작업이 끝난 스레드가 60초 이상 새로운 작업요청이 없으면 스레드를 종료하고 스레드 풀에서 제거된다. 다음 예시는 18개의 스레드가 필요한 Task 를 제공하고 5개의 스레드 풀로 처리하는 과정을 확인해 보는 테스트다. 위에 생성해둔 코드에 이어서 작성해서 테스트를 진행하면 된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token annotation punctuation&quot;&gt;@DisplayName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;CachedThreadPool 을 생성한다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;testCounterWithConcurrencyCached&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; numberOfThreads &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ExecutorService&lt;/span&gt; service &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Executors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newCachedThreadPool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;CountDownLatch&lt;/span&gt; latch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CountDownLatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;numberOfThreads&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;MyCounter&lt;/span&gt; counter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyCounter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;iterateThread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;numberOfThreads&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; service&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; latch&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; counter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;counter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;numberOfThreads&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; service&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPoolSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;60000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 60초 후 생성된 스레드가 제거되는지 확인한다.&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; service&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPoolSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/49058669/133979857-aea7e9c2-050c-433c-b445-f83b32d45b4e.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;세 번째 방법은 &lt;code class=&quot;language-text&quot;&gt;Executors.newScheduledThreadPool(int corePoolSize)&lt;/code&gt; 에 대해 알아본다.&lt;/p&gt;
&lt;p&gt;스레드를 &lt;b&gt;일정시간이 흐르고 난 뒤 실행&lt;/b&gt;시키도록 하는 스케줄링 스레드 기능이다. 해당 기능을 테스트해 보기 위해서는 테스트 코드가 아닌 메인에서 실행해봐야 한다. 따라서 &lt;code class=&quot;language-text&quot;&gt;Sample&lt;/code&gt; 클래스를 만들고 실습을 해본다. corePoolSize 는 생성할 corePool 의 크기를 지정해주는 부분인데 &lt;code class=&quot;language-text&quot;&gt;Executors.newScheduledThreadPool(0)&lt;/code&gt; 을 하더라도 실행에는 문제가 없어 보인다. 다만 JDK 8 버전 이하에서 발견된 &lt;a href=&quot;https://bugs.openjdk.java.net/browse/JDK-8129861&quot;&gt;버그&lt;/a&gt;로 단일 코어 가상 머신에서 CPU 를 100% 사용하는 버그가 있기 때문에 파라미터로 1 이상으로 설정한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Sample&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ScheduledExecutorService&lt;/span&gt; service &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Executors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newScheduledThreadPool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;첫 번째 작업이 실행됩니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;schedule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;두 번째 작업이 실행됩니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeUnit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SECONDS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;세 번째 작업이 실행됩니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;shutdown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/49058669/133980017-e1cc4860-0fc2-4e88-872c-6853887db442.png&quot; alt=&quot;image&quot;&gt;
스케줄에서 설정한 5초가 흐른 후 &lt;code class=&quot;language-text&quot;&gt;두 번째 작업이 실행됩니다.&lt;/code&gt; 가 실행되는 모습을 확인할 수 있다.&lt;/p&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;자바에서 사용하게 될 스레드 풀에 대해 간단하게 알아봤다. 얼마만큼의 스레드가 주기적으로 소모될지, 스레드를 추가로 생성할지, 작업 후 반환 값을 받을지에 대한 분석을 토대로 상황에 적합한 Thread 및 ThreadPool 을 적용한다면 멀티 코어 프로그래밍에 큰 도움이 된다고 생각한다. 다만, 적절하지 않게 사용할 경우 오히려 사용하지 않은 것보다 못할 수 있기 때문에 주의해야 한다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html&quot;&gt;Class Executor Oracle Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/thread-pool-java-and-guava&quot;&gt;Introduction to Thread Pools in Java&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://limkydev.tistory.com/55&quot;&gt;[Java] Thread Pool(스레드 풀)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;#x26;blogId=2feelus&amp;#x26;logNo=220728222140&quot;&gt;Java Thread Pool (자바 쓰레드 풀)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[리눅스 배포판이란?]]></title><description><![CDATA[Ubuntu , Fedora , Manjaro , Centos … AWS…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-09-13-linux-distribution/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-09-13-linux-distribution/</guid><pubDate>Mon, 13 Sep 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Ubuntu&lt;/em&gt; , &lt;em&gt;Fedora&lt;/em&gt; , &lt;em&gt;Manjaro&lt;/em&gt; , &lt;em&gt;Centos&lt;/em&gt; … &lt;em&gt;AWS&lt;/em&gt; 인스턴스 설정에서 만나는, 운영체제에 대한 것들은 도대체 무엇일까?
이번 글은 배포 시에 마주치는 리눅스 배포판들과 리눅스의 특징들에 대해 다룬 글, “리눅스 배포판이란?” 입니다.&lt;/p&gt;
&lt;!-- end --&gt;
&lt;h2&gt;배포 미션 중 마주친 다양한 배포판&lt;/h2&gt;
&lt;p&gt;우아한테크코스에서는 단순하게 프로그램을 만드는 것뿐 만이 아니라 실제로 서비스를 배포하고 이를 운영 및 유지 보수를 하면서 미션을 진행하고 있습니다.
이 때문에 클라우드를 활용하여 프로그램을 배포하는 과정은 필수적입니다.
필자가 속한 팀도 서비스 완성 이후에 배포를 위해 AWS의 EC2를 활용하기로 하였습니다.
EC2를 위한 사양을 고르고 다음 페이지에서는 처음 보는 로고들을 보게 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1196px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ce0147dc571ac22415da9aba04ed55f9/ee2d9/2021-09-13-linux-distributions-on-aws.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 39.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAAsTAAALEwEAmpwYAAABHUlEQVR42lWRzW6DMBCE/f4P0EOlXvsQPaVVK1U9cCltBEEhpZBAiH+wDa6xId14lSj5DtbIy3g8hlgtjWBS667rpJRCCBT6vKMDSinQUinvbGf8w/P2NRUkS5Nl/Hmg9BDYBzjnF7MKyDNutL2dolVdtpLQerf//aGcM8YopeBs2xbMYOgC6EENtwKzNP7xrfzIOFnnebJK+wCkwQq2YRhsYBxHFM451JP32rj35W7TSFJUZbZZ0wAEYgg0x3DMhxXOxdGgVc2Gu6d88d2SqqyKohABmBljMBajrsHN03Wcm+fxOHti4Vt5KgOvBWmQf90ZWsAcTsRS0MrZP967+8X2JREkjuMoii4/CcCnNgHv/XTLcZ7NOH3lTcPUPwK2wYfbapy1AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;AWS에서의 리눅스 배포판&quot;
        title=&quot;&quot;
        src=&quot;/static/ce0147dc571ac22415da9aba04ed55f9/ee2d9/2021-09-13-linux-distributions-on-aws.png&quot;
        srcset=&quot;/static/ce0147dc571ac22415da9aba04ed55f9/0eb09/2021-09-13-linux-distributions-on-aws.png 500w,
/static/ce0147dc571ac22415da9aba04ed55f9/1263b/2021-09-13-linux-distributions-on-aws.png 1000w,
/static/ce0147dc571ac22415da9aba04ed55f9/ee2d9/2021-09-13-linux-distributions-on-aws.png 1196w&quot;
        sizes=&quot;(max-width: 1196px) 100vw, 1196px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;익숙한 &lt;em&gt;Windows&lt;/em&gt; 로고뿐만이 아니라 처음 보게 되는 로고가 많아서 당황스러웠습니다.
서버에서는 리눅스라는 특수한 운영체제가 쓰이는 것은 알고 있었는데, 정작 운영체제 선택창에서는 펭귄 로고 리눅스가 아닌 다양한 로고들이 보였습니다.
이것들은 무엇일까요?
저와 함께 리눅스 및 리눅스 배포판에 대해서 배워가보도록 하겠습니다.&lt;/p&gt;
&lt;h2&gt;리눅스(&lt;em&gt;Linux&lt;/em&gt;) 란&lt;/h2&gt;
&lt;p&gt;리눅스는 사실 커널이라는 운영체제의 일종입니다.
운영체제란 컴퓨터 시스템 중앙에서 시스템을 구성하는 자원들을 관리하며 사용자와 소통하며 시스템을 작동시키는 구성 요소입니다.
일반적으로 운영체제는 &lt;em&gt;Windows&lt;/em&gt; 나 &lt;em&gt;MacOS&lt;/em&gt; 처럼 프로그램을 실행시켜주는 틀이라고 알려져있습니다.
하지만 운영체제는 하드웨어와 소통하며 CPU와 같은 자원 사용을 효율적으로 사용할 수 있게 도와주는 역할도 하고 있습니다.
후자의 역할을 수행하기 위해서 메모리 위에 항상 상주하며 작동해야 하는 운영체제의 구성요소가 있습니다.
그것을 바로 커널이라고 부릅니다. &lt;br&gt;
커널은 운영체제의 역할 중에서 프로그램들과 하드웨어 사이를 소통시키고 제어해주는 역할을 하고 있습니다.
또한, 메모리 관리, 저장 공간 관리, 업무 관리 등도 하고 있습니다.
운영체제를 위한 커널에도 여러 종류가 있는데, 이 중 리눅스(&lt;em&gt;Linux&lt;/em&gt;) 라는 커널을 선택하는 운영체제들이 있습니다.
이를 통상적으로 저희는 운영체제로서의 리눅스라고 부릅니다. &lt;br&gt;
하지만 리눅스 커널 혼자서만은 운영체제의 역할을 모두 해낼 수가 없습니다.
이를 해결하기 위해서 자유 소프트웨어 재단에서 만든 &lt;em&gt;GNU&lt;/em&gt; 와 함께 사용하며, &lt;em&gt;GNU/Linux&lt;/em&gt; 라는 형태로 사용하게 됩니다.
실제로 운영체제로서 리눅스 라고 부르는 것은 단순한 커널뿐만이 아니라 여러 프로그램을 묶어서 배포하는 &lt;em&gt;GNU/Linux&lt;/em&gt; 에서 기능이 추가된 것이라고 볼 수가 있습니다.
이렇듯 리눅스 커널은 서버에서 많이 선택되는 &lt;em&gt;Ubuntu&lt;/em&gt; , &lt;em&gt;Centos&lt;/em&gt; , &lt;em&gt;RHEL&lt;/em&gt; 뿐만이 아니라 가까운 곳에서 찾아볼 수 있는 핸드폰의 운영체제인 &lt;em&gt;Android&lt;/em&gt; 도 리눅스 커널을 채택하여 사용하고 있습니다.&lt;/p&gt;
&lt;h2&gt;배포판이란&lt;/h2&gt;
&lt;p&gt;위의 파트에서는 리눅스가 정확히 어떤 부분을 지칭하는지를 배웠습니다.
하지만 운영체제에서는 커널 이외의 다른 부분도 존재합니다.
단순히 우리가 지칭하는 리눅스는 일반 사용자가 많이 사용하는 &lt;em&gt;Windows&lt;/em&gt; , &lt;em&gt;MacOS&lt;/em&gt; 정도의 편의성에서는 못 미치는 상태입니다.
그 뿐만 아니라 &lt;em&gt;Ubuntu Server&lt;/em&gt; 나 &lt;em&gt;RHEL&lt;/em&gt; 에서 제공하는 서버 관련 low level의 기능들조차 제공해주지 못합니다.
순수하게 리눅스 커널 자체만을 사용하는 것이 아닌 커널과 함께 해당 운영체제 목적에 맞는 여러 프로그램을 패키징하여 제공하게 됩니다. &lt;br&gt;
이를 바로 &lt;em&gt;Distribution&lt;/em&gt; , 배포판이라고 합니다.
앞서 &lt;em&gt;AWS&lt;/em&gt; 의 운영체제 선택 창에서 보았던 &lt;em&gt;Window Server&lt;/em&gt; 를 제외한 많은 운영체제가 바로 리눅스 커널을 사용하되, 목적에 따라서 다양한 프로그램들을 함께 제공하는 배포판의 종류들입니다.
배포판에 따라서 서버 운영에 특화된 것, 일반 사용자에게 특화된 것, IoT와 같은 소형 장치들에 특화된 것들이 나뉘어서 제공되며, 같은 서버 특화라도 같이 제공되는 프로그램이 다른 것을 알 수가 있습니다.&lt;/p&gt;
&lt;h2&gt;저장소에서 프로그램을 가져오는 방법&lt;/h2&gt;
&lt;p&gt;저희 팀은 &lt;em&gt;Ubuntu&lt;/em&gt; 를 선택하여 미션을 진행하게 되었습니다.
&lt;em&gt;Ubuntu&lt;/em&gt; 에서 서버 운영에 필요한 &lt;em&gt;Java&lt;/em&gt; 를 설치하기 위해서는 다음과 같은 명령어를 입력해주어만 했습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt; update &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt; upgrade
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; default-jdk&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;하지만 만약 다른 배포판인 &lt;em&gt;Centos&lt;/em&gt; 에서 &lt;em&gt;Java&lt;/em&gt; 를 설치하기 위해서는 다음과 같은 명령어를 입력해주어야만 합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; yum &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; java-1.8.0-openjdk&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;“sudo” 는 최상위 사용자의 권한으로 실행한다는 것을 표현하는 명령어입니다.
하지만 왜 같은 리눅스인데 하나는 &lt;em&gt;apt&lt;/em&gt; 라는 명령어를 쓰고, 하나는 &lt;em&gt;yum&lt;/em&gt; 이라는 명령어를 쓰게 될까요?
또한 뒤의 &lt;em&gt;apt&lt;/em&gt; 는 무엇이고, 업데이트 및 업그레이드는 무엇을 의미하는 것일까요?
그것은 리눅스 배포판의 특성과 관련이 있습니다. &lt;br&gt;
각각의 배포판에서는 운영체제 및 프로그램을 쉽게 관리할 수 있도록 저장소(&lt;em&gt;repository&lt;/em&gt;)를 운영하고 있습니다.
저장소 및 저장소를 통해서 설치된 프로그램의 버전 및 의존성을 관리해주는 패키지 매니저에 접근하는 명령어들은 배포판마다 각각 다릅니다.
&lt;em&gt;Ubuntu&lt;/em&gt; 를 비롯한 배포판들은 &lt;em&gt;Debian&lt;/em&gt; 계열에 기반을 두고 있으므로 &lt;em&gt;apt&lt;/em&gt; 명령어를 통해서, &lt;em&gt;Centos&lt;/em&gt; 를 비롯한 배포판들은 &lt;em&gt;Fedora&lt;/em&gt; 계열에 기반을 두고 있으므로 &lt;em&gt;yum&lt;/em&gt; 명령어를 통해서 각각의 리눅스 배포판의 저장소에 접근하여 해당 저장소 정보를 받아오게 됩니다.
이렇게 저장소와 패키지 관리자 명령어로 리눅스 배포판의 종류를 나누기도 합니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 487px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f007babf755ff4c301d24541efe16bcb/6a170/2021-09-13-linux-distributions-hiearchy.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.82546201232032%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABZ0lEQVR42m1STUsDMRDdv9O/4l8QPPXoSdDi0UJBzx4Kggc/QK2CiiAWpcWCUKtVa62IQmuxG4V216Tb3ew2H2u222azag5J5mXeZN7MaL6yOOfU9fz/Fo98IlCTNLHD2is4KcH62y+v8PW0cLd9eGUiWyKaDEv6tlF5st+BedvgCpOx4F6tt6ZmlmeXduZWjiSoReEpFX+6n13mDdVEKWNi388/TM9vJlNrydS6BDVViocGDuyPmTyWc6vTOz6/3zq41L/MWNqcDH3iEUp7pgEAQAjRMC1JH1kY25R6oZKoYPhi18ouCLbj2H2EBPJ8c72RWYz+FbWEEGPsui4cOURk0n7xaqVACaUDayAuHw+NfGY19LCc7mMhBztmaDrNpp5OE8NQWjWSCIegaZUZZ6pavd07y2WxoY/J1WolkbCKRdkqoYqK8xvrZbBHmBsEmwgLNHIanxjux6odTRL/O1xq2wNjYv4AtSluquThKxAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;리눅스 배포판 간단 계보도&quot;
        title=&quot;&quot;
        src=&quot;/static/f007babf755ff4c301d24541efe16bcb/6a170/2021-09-13-linux-distributions-hiearchy.png&quot;
        srcset=&quot;/static/f007babf755ff4c301d24541efe16bcb/6a170/2021-09-13-linux-distributions-hiearchy.png 487w&quot;
        sizes=&quot;(max-width: 487px) 100vw, 487px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;배포판들은 운영체제의 역사와 마찬가지로 필요에 따라서 분리되면서 특화되어왔습니다.
그 중 가장 유명한 것으로는 서버 시장에서 널리 쓰이고 있는 &lt;em&gt;fedora&lt;/em&gt; 계열 및 &lt;em&gt;Ubuntu&lt;/em&gt; 와 &lt;em&gt;Mint&lt;/em&gt; 와 같은 데스크톱 유저들을 많이 확보한 &lt;em&gt;debian&lt;/em&gt; 계열이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 400px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2a9932eaf5c30ef0ab44a039c600f704/d9f49/2021-09-13-linux-repository.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 87.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAIAAABSJhvpAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAChUlEQVR42pVT21LTUBTt3/nmi7/gD+iMTzz5IgOiaEFwRmCsFRzUsYJYbgKFpiWCgPSSNG0qza1NW5te0qRpknPiOTm1VccZx53VM2uSWXuvrJ0GPL8c0O87xq8AwPH+VQH0MyztC/+azr/8nF/2sXLMvciKWxBC9BRCQOBBdPXalUuzbmIp9MVqK3+QmjnKPj0cYT7OPEPzfbHfAgB0crlcaOI2t7tP7mBxtVWIMws0F07mnie5ECIJdglxF9joUVr4+K2a4JS9us51O17p6tzzyr5r6E9ucsgzK39ipO2UsJERNy+v1o9zIdu1ml25oMTzAs2U4m2zbNvOboK5Ujq+I28wGVml2MUEu4gsICCezIX6NrZtGt2Li1SBFxAPR5LXb07fmXznOO4gsHq7uJ+eiWXmY5k5goP0LMUu2G5vEJgHXRe/806CG5t6uxKhSBYBsqeiSjPSHisf+IghrjSY4UqgX4QD1x3ygPc/RXoAP/mB2AUwK+l0oX3CY5zybTrfKtVMkgr4uWg8rm85JRaa+khcVHvjEXFibYDJNfFeRJjdUiwbDPeM1orD2w5rYzeMN8GRmJGNyTXhwQdxCkNCuL8uBqOyZUO+0omeSlRW3btQ+Hrfev/k+61r+tJdz++IxRmpO7ejrCZr4SMV4RVVXdivzG7JlgOL5c72mXLGN6lsrVg1PLNlp46g3hh9JGjy46i8TFVXE9VQrIJbHKrzO4rec4ntr+cngoD3DBxglFu23hvZzkrGeERAVskLk/NRVCZiFJWmNU0TC0yl1qAvTbFCosdiRbOCm/LDDWl6QyInahQ6VB0A/1gV6Fm21v7tL4mqoTtlrV9pDoA4GTvc71/5Dzo8jRQMmi5EAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;리눅스 저장소에서 프로그램 다운로드&quot;
        title=&quot;&quot;
        src=&quot;/static/2a9932eaf5c30ef0ab44a039c600f704/d9f49/2021-09-13-linux-repository.png&quot;
        srcset=&quot;/static/2a9932eaf5c30ef0ab44a039c600f704/d9f49/2021-09-13-linux-repository.png 400w&quot;
        sizes=&quot;(max-width: 400px) 100vw, 400px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ubuntu&lt;/em&gt; 기준으로 apt update는 저장소에서 어떤 패키지들이 업데이트되어있는지에 대해서 정보를 받아오고, apt upgrade를 통해서 이전의 패키지 버전을 삭제하고 새 패키지를 다운받아 오게 됩니다.
명령어 한 번으로 패키지 간들의 의존성을 정리해주고, 버전을 최신 상태로 업그레이드할 수 있습니다.
각각의 저장소들은 기본으로 선택된 안정 버전뿐만이 아니라 베타 버전 등에 대한 옵션을 제공함으로써 조금 더 최신 버전의 패키지들을 선택해서 사용할 수 있습니다.
만약 원하는 프로그램이 공식 저장소에 등록이 되어 있지 않으면 ppa와 같은 개인이 운영하는 저장소를 컴퓨터에 등록하여 프로그램들을 받아서 사용할 수 있습니다.
저장소에 등록된 버전들을 확인 및 업데이트하고 난 다음에 원하는 프로그램을 저장소에서 다운을 받을 수가 있습니다.
마지막 명령어인 sudo apt -y install default-jdk 는 &lt;em&gt;debian&lt;/em&gt; 의 공식 저장소에서 기본적인 자바 개발 환경을 다운받는 명령어입니다.&lt;/p&gt;
&lt;h2&gt;부록 리눅스, 유닉스, MacOs, And Window&lt;/h2&gt;
&lt;p&gt;많은 서버 개발자들은 &lt;em&gt;Windows&lt;/em&gt; 에서 개발하는 것보다는 &lt;em&gt;MacOS&lt;/em&gt; 에서 개발하는 것을 선호해왔습니다.
리눅스는 마이너하기 때문에 사용하지 않지만 왜 &lt;em&gt;MacOS&lt;/em&gt; 가 조금 더 선호되는 것일까요?
많은 이유가 있겠습니다만 그 이유 중 하나는 운영체제의 역사와 관련되어 있습니다&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 371px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c4cee7737630a89fa735259f04379976/d86da/2021-09-13-os-history.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 94.60916442048519%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAIAAAAf7rriAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACGElEQVR42pVTS2sTURSefyDuBBf9C2606j8oIrqXKlh0oyj4IFXEKEIJ2QimxWxscVERRN1lYcuYGBUkgSyKzCtkMplM6nSa1Mx75ubeeDJ3bENqgjlc5g7n3u+c73z3HKb/30bIqIchkW9b0zRV4ziO538SgieE8EKsWwhHgZherwfbm/cfFp8+fPDocSr13PO8KE9sCKEQDe4UZWduTTn7sn58SXTDQQKmH4HZ29dvHbucmF9YfTW3t6eDR1XVer3eaDSq1eq2boBnvfL7yBP+9Ip8crnmIxKBMUb9/o+rVxaPXlg6d/HZ/Vl99xscOI7tOI7rut1u13EHXDYle3ZFPrUsz6QkN/wLhv373ZsvZs7cubTw7uOJHV2KaP+7csNGW7/8HqbgyAJCAoKDkPj+YVFHND8Iyhw+jdbY14KFMRkHnmRBELCf877vTwem7WB2d+/dOC8IPPxjjJnhJ923cWBBEDhua3XtNfUww8dkrFjUj8RP81831r+8vQaJY9qdTqfVaimKAl/oimazGYbhPg7oEYxpBGRJmsJ7pnpQcy6Xy2az6XQ6kUhkMplkMgkUIEqtVoMmE0XRMIyY2hCNGCzLcqFQgEssy5ZKpXK5DKratg29ZVlWu92GPqOF+Z5rGDuu69AwzIgkEwz4A5F8Pi9JEh2nWO1BYRPVpmCY2WKxWKlUqCjTNQlMq2maUAVNMB14pLo/NsEkF7aCZL8AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;운영체제의 역사&quot;
        title=&quot;&quot;
        src=&quot;/static/c4cee7737630a89fa735259f04379976/d86da/2021-09-13-os-history.png&quot;
        srcset=&quot;/static/c4cee7737630a89fa735259f04379976/d86da/2021-09-13-os-history.png 371w&quot;
        sizes=&quot;(max-width: 371px) 100vw, 371px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;운영체제는 발전을 거듭하며 목적 및 필요에 따라서 갈라져서 발전해왔습니다.
유닉스 (&lt;em&gt;UNIX&lt;/em&gt;)는 그 중 리눅스와 &lt;em&gt;MacOS&lt;/em&gt; 에 많은 영향을 준 운영체제입니다.
이 둘은 같은 운영체제에서 파생 및 영향을 받게 됨으로써 시스템 구조나 명령어 상에서 많은 유사점을 가지게 되었습니다.
하지만, &lt;em&gt;Windows&lt;/em&gt; 는 &lt;em&gt;MS-DOS&lt;/em&gt; 부터 유닉스와는 다른 방향으로 발전을 해왔기 때문에 상대적으로 리눅스와 &lt;em&gt;MacOS&lt;/em&gt; 와는 비슷한 점이 상대적으로 줄어들게 되었습니다.
이 때문에 서버의 대부분은 리눅스 환경에서 운영이 되며 개발자들도 유사한 환경에서 개발을 진행하는 것이 유리했습니다.
그렇기에 조금 더 유사한 점이 많은 &lt;em&gt;MacOS&lt;/em&gt; 의 특징이 개발자들에게 조금 더 매력적으로 다가오게 되었습니다.
하지만 최근에는 &lt;em&gt;Windows&lt;/em&gt; 에서도 하위 시스템으로 리눅스를 둠으로써 (&lt;em&gt;WSL&lt;/em&gt;) , &lt;em&gt;Windows&lt;/em&gt; 에서의 서버 개발자들의 접근성이 개선되고 있습니다.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/kernel-in-operating-system/&quot;&gt;Kernel in Opearting System&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/kernel-in-operating-system/&quot;&gt;Introduction to Linux Operating System : What is the Linux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.debian.org/releases/buster/amd64/ch01s02.en.html&quot;&gt;What is GNU/Linux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.networkworld.com/article/3305810/how-to-list-repositories-on-linux.html#:~:text=A%20Linux%20repository%20is%20a,software%20packages%20on%20Linux%20systems.&quot;&gt;How to list repositories on linux&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[비동기 처리 시 race condition 고려하기]]></title><description><![CDATA[비동기 처리 시 race condition…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-09-12-race-condition-handling/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-09-12-race-condition-handling/</guid><pubDate>Sun, 12 Sep 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;비동기 처리 시 race condition 고려하기&lt;/h2&gt;
&lt;p&gt;프로그래밍을 할 때 비동기 처리는 여간 까다로운 게 아닙니다. 저마다의 어려움이 있겠지만, 대표적으로 그 이유를 ‘낮은 예측성’에 들 수 있는데요. 아무리 잘 작성된 비동기 코드라 할 지라도, 동기 코드에 비해 추론이 힘들 뿐더러 일관된 응답 처리 또한 쉽지 않기 때문입니다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 비동기 처리를 어렵게 하는 요소 중 하나인 &lt;strong&gt;race condition&lt;/strong&gt;과 이에 대한 대안책을 함께 알아보겠습니다.&lt;/p&gt;
&lt;h3&gt;Race condition&lt;/h3&gt;
&lt;p&gt;경합 조건이라고도 하며 이것은 꽤 여러 군데에서 통용되는 개념입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;경합 조건은 시스템의 실질적인 동작이 다른 통제 불가능한 이벤트의 순서나 타이밍에 따라 달라지는 전자 장치, 소프트웨어 또는 기타 시스템의 조건이다. - &lt;a href=&quot;https://en.wikipedia.org/wiki/Race_condition&quot;&gt;Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;조금 더 쉽게 풀어보자면, 하나의 처리를 위해 다수의 작업이 (거의) 동시에 발생하는 경우 처리를 제어하는 입장에서 무엇이 먼저 완료될 지 예측하기 힘들다는 이야기입니다. 비동기 맥락에서 경합 조건은 주로 &lt;strong&gt;요청 순서와 응답 순서가 반드시 같지는 않음&lt;/strong&gt;을 의미하는 용어로 사용됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetchA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;//&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;A: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;response&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetchB&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;//&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;B: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;response&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;fetchA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;fetchB&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같이 &lt;code class=&quot;language-text&quot;&gt;fetchA()&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;fetchB()&lt;/code&gt;를 호출해도 콘솔에 A 응답, B 응답이 순서대로 출력된다는 것을 보장할 수는 없습니다. 때문에 이와 같은 특성이 비동기 상황을 보다 세심하게 처리해야 하는 이유입니다.&lt;/p&gt;
&lt;p&gt;구체적인 이해를 돕기 위해 사용자 시나리오를 가정해보겠습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;인풋에 값을 입력하면&lt;/li&gt;
&lt;li&gt;입력된 값에 따라 실시간으로 데이터를 가져온다.&lt;/li&gt;
&lt;li&gt;가져온 데이터를 화면에 보여준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;혹은&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;화면에 여러 버튼이 있고&lt;/li&gt;
&lt;li&gt;버튼을 클릭할 때마다 데이터를 가져온다.&lt;/li&gt;
&lt;li&gt;가져온 데이터를 화면에 보여준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;프로젝트에서 검색이나 필터를 도입한다면 흔히 생각해볼 수 있는 시나리오입니다. 하지만 만약 이러한 상황에서 경합 조건이 발생한다면, 어떤 문제가 생길 수 있을까요? 첫 번째 예시를 코드로 옮겨 확인해보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 1 ~ 2초 사이의 통신이 발생하는 네트워크 상황을 가정&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;delayRandomly&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; timer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ceil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;resolve&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; $input &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;input&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; requestIndex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// input이 바뀔 때마다 데이터를 가져와 콘솔에 출력&lt;/span&gt;
$input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;input&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;requestIndex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;delayRandomly&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;요청 &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;index&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;img width=&quot;400&quot; src=&quot;/3571ff03e3b4bd24c2a8b40af6cdfdac/2021-09-23-race-condition1.gif&quot; alt=&quot;경합 조건 예시 1&quot;&gt;
&lt;p&gt;마지막 응답의 결과는 매번 바뀔 수 있기에 그대로 화면에 띄워주게 되면 결국 사용자는 기대와 다른 결과를 얻게 됩니다.&lt;/p&gt;
&lt;p&gt;대개 반복되는 이벤트의 최적화를 위해 &lt;a href=&quot;https://webclub.tistory.com/607&quot;&gt;Debounce, Throttle&lt;/a&gt;을 활용하지만 이는 앞서 설명한 문제의 실질적인 대안이라고 할 수는 없습니다. 이 기술은 빈번하게 발생되는 동일 이벤트의 출력량을 조절할 뿐, 비동기적으로 가져올 데이터 간의 경합 조건을 해결해주지는 않기 때문입니다.&lt;/p&gt;
&lt;p&gt;근본적인 문제를 해결하는 데 초점을 맞춰본다면 아마 다음과 같은 방법을 고민해보지 않을까 싶습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이전 요청은 모두 무시하고 마지막 요청에 대한 응답만 처리해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이와 같은 처리가 가능하도록 특정 시점의 요청 자체를 끊어버릴 수 있는 방법이 있다면 어떨까요? 그것을 가능하게 하는 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/AbortController&quot;&gt;Web API&lt;/a&gt;가 바로 &lt;code class=&quot;language-text&quot;&gt;AbortController&lt;/code&gt;입니다.&lt;/p&gt;
&lt;h3&gt;AbortController&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;AbortController&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;fetch&lt;/code&gt; 등의 웹 요청을 취소할 수 있도록 고안된 인터페이스이며 두 개의 주요 프로퍼티를 가집니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;AbortController.signal&lt;/code&gt;: 읽기 전용 프로퍼티이며 요청을 취소할 수 있도록 사용되는 인스턴스 객체입니다. 컨트롤러와 웹 요청을 이어주는 매개체라고 생각하면 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;AbortController.abort()&lt;/code&gt;: &lt;code class=&quot;language-text&quot;&gt;signal&lt;/code&gt;로 연결된 요청이 완료되기 전에 취소할 수 있는 메서드입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;사용법은 꽤 간단합니다. 우선 다음과 같이 컨트롤러를 생성해줍니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; controller &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AbortController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그 다음 취소될 수 있는 요청을 보낼 때 &lt;code class=&quot;language-text&quot;&gt;signal&lt;/code&gt; 옵션을 지정해줄 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;signal&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;signal&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 원하는 시점에 해당 요청을 취소해야할 때 컨트롤러의 &lt;code class=&quot;language-text&quot;&gt;abort()&lt;/code&gt;를 호출해주면 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;abort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;아까의 예시 코드에 &lt;code class=&quot;language-text&quot;&gt;AbortController&lt;/code&gt;를 적용해보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; currentRequest &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; requestIndex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

$input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;input&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;currentRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    currentRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;abort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  currentRequest &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AbortController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;requestIndex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;signal&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; currentRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;signal&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;delayRandomly&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;요청 &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;index&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;AbortError&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;img width=&quot;400&quot; src=&quot;/8dfa8c63a915afe524038ba7b8619717/2021-09-23-race-condition2.gif&quot; alt=&quot;경합 조건 예시 2&quot;&gt;
&lt;p&gt;코드를 다시 정리해보면,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;이벤트 호출 시 &lt;code class=&quot;language-text&quot;&gt;AbortController&lt;/code&gt; 생성 후 저장&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;fetch&lt;/code&gt; 요청을 보낼 때 저장된 컨트롤러의 &lt;code class=&quot;language-text&quot;&gt;signal&lt;/code&gt;을 옵션에 기입&lt;/li&gt;
&lt;li&gt;input이 바뀔 때마다 이전에 저장된 컨트롤러가 있다면 연결된 요청은 취소&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이전 요청이 다음 요청 전까지 응답에 대한 처리를 모두 마쳤다면 &lt;code class=&quot;language-text&quot;&gt;abort()&lt;/code&gt;가 호출되어도 별다른 동작을 하지 않습니다. 반면 요청 사이에 간격이 좁아 이전 요청이 여전히 수행 중이라면 바로 취소되고 다음 요청의 수행만 유지합니다. 이로써 다수의 요청 간에 경합 조건을 발생시킬 여지를 없앨 수 있게 되는 것입니다.&lt;/p&gt;
&lt;p&gt;하나의 요청이 취소되면 &lt;code class=&quot;language-text&quot;&gt;AbortError&lt;/code&gt;를 던집니다. 이것은 &lt;code class=&quot;language-text&quot;&gt;error.name&lt;/code&gt;에 맞게 적절한 예외처리를 해주면 됩니다.&lt;/p&gt;
&lt;h3&gt;Summary&lt;/h3&gt;
&lt;p&gt;사용자를 위한 UX를 고려하며 비동기 처리를 할 때, 단지 &lt;code class=&quot;language-text&quot;&gt;AbortController&lt;/code&gt;를 사용하는 것만으로는 완벽한 대안이 될 수 없습니다. Debounce, Throttle을 활용해 불필요한 이벤트의 반복을 줄이고, &lt;code class=&quot;language-text&quot;&gt;AbortController&lt;/code&gt;를 통해 응답 간의 경합 조건을 줄여준다면 관련된 비동기 처리의 안정성을 더욱 높일 수 있을 것입니다.&lt;/p&gt;
&lt;h3&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.theodo.com/2019/09/handle-race-conditions-in-nodejs-using-mutex/&quot;&gt;대표 이미지 출처&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://javascript.info/fetch-abort#:~:text=AbortController%20is%20a%20simple%20object,possible%20to%20abort%20the%20fetch%20.&quot;&gt;Fetch: Abort - Modern Javascript Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/sebastienlorber/handling-api-request-race-conditions-in-react-4j5b&quot;&gt;Handling API request race conditions in React&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[CSR 앱에서 SSR + CSR 환경으로 이주하기]]></title><description><![CDATA[❗ CSR에 SSR을 적용하는데 어려움을 겪는 분들을 위해 작성했습니다. 혹시 틀린 부분이 있다면 말씀해주세요! ❗ SSR과 refresh token에 대한 사전 지식이 있으면 글을 이해하기 조금 더 편합니다. ❗ 웹서버와 WAS…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-09-10-ssr/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-09-10-ssr/</guid><pubDate>Fri, 10 Sep 2021 12:00:00 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;❗ CSR에 SSR을 적용하는데 어려움을 겪는 분들을 위해 작성했습니다. 혹시 틀린 부분이 있다면 말씀해주세요!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;❗ SSR과 refresh token에 대한 사전 지식이 있으면 글을 이해하기 조금 더 편합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;❗ 웹서버와 WAS가 무엇인지 궁금하다면 &lt;a href=&quot;https://www.youtube.com/watch?v=Zimhvf2B7Es&amp;#x26;t=1s&quot;&gt;여기&lt;/a&gt;를 참고해주세요!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;최근 진행하고 있는 프로젝트가 CSR 환경에 SSR을 도입한 경험을 공유하고자 한다.
오늘 내용은 상세한 구현보다도 리액트 사용을 전제로 한 전체적인 흐름을 소개하려고 한다.&lt;/p&gt;
&lt;h2&gt;👋 CSR에 SSR을 도입하게 된 계기&lt;/h2&gt;
&lt;p&gt;앱이 커지게 되면 CSR의 한계에 점점 부딪히게 된다. 개인적인 생각에, 앱이 커지게 되면 SSR은 필연적인 것이 아닌가 생각한다.
CSR에 SSR을 일부 적용하게 되면 CSR의 단점을 SSR이 보완해주어 앱의 완성도가 높아진다.
추상적인 말로만 풀어낸 것 같은데 구체적으로 풀면 다음과 같다.&lt;/p&gt;
&lt;h3&gt;프론트엔드 개발자가 서버 쪽 코드도 개발할 수 있어 자유도가 높아진다.&lt;/h3&gt;
&lt;p&gt;SSR을 쓴다는 것은 프론트 서버를 따로 둔다는 것과 같은 말이기 때문에 프론트에서 서버 쪽 자원도 관리 할 수 있게 된다.
예를 들어 refresh token이 탈취당하는 경우 access token을 계속 만들 수 있기 때문에 서버에 저장하는 것이 보안에 더 좋은데,
프론트 서버를 두게 되면 refresh token을 프론트 개발자가 관리할 수 있다. 물론 refresh token에 대한 관리를 백엔드에서도 할 수 있겠지만 프론트의 토큰 전략이 바뀔 때마다 백엔드와 논의를 거쳐야 한다는 단점이 있다.&lt;/p&gt;
&lt;h3&gt;SEO(검색 엔진 최적화)가 향상된다.&lt;/h3&gt;
&lt;p&gt;뻔한 얘기겠지만 SEO가 향상된다. 하지만 하이퍼 링크로 문서에서 다른 문서로 이동한다는 웹의 특징을 고려해봤을 때 SEO는 굉장히 중요한 부분이라고 할 수 있다.
구글 크롤러는 자바스크립트를 해석할 수 있어서 CSR 페이지도 탐색할 수 있지만 네이버나 다음 같은 사이트에서는 아직 자바스크립트 해석 기능을 지원하지 않기 때문에 검색 엔진에 잡히지 않는다.
하지만 SSR을 통해서 각 라우팅 포인트에 대한 html 문서를 정의해두면 자바스크립트를 해석할 필요가 없으므로 구글 이외의 검색 엔진에서도 최적화가 가능하다.&lt;/p&gt;
&lt;h3&gt;페이지의 초기 렌더링이 CSR보다 빨라진다.&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/133872438-a140deb8-6df0-4cf7-aafc-d947195fb8fe.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;초기 렌더링이 CSR보다 빨라지는 이유는, CSR은 첫 html을 빈 화면으로 받고 추가적인 js 파일을 가져와서 화면에 그리는 과정까지 완료되어야 첫 화면이 사용자에게 보여지지만 SSR은 일부 완성된 html 문서로 가져오기 때문에 사용자에게 첫 화면을 보여주기까지의 시간이 크게 단축되기 때문이다. 페이지 일부가 보이는 것이지만 그것만으로도 사용자 경험은 크게 나아진다.&lt;/p&gt;
&lt;h2&gt;🔬 CSR + SSR 환경 구조&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/133872411-a75e8376-5433-4e0e-b330-185ae8b6e909.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;전체적인 구조는 다음과 같다. 도메인 네임을 통해 NginX 웹서버에 접근하면 NginX는 EC2 내부의 다른 포트에서 동작하고 있는 express 서버에 접근한다. express 서버는 일부가 완성된 html 문서를 NginX로 다시 되돌려주고 브라우저에게 그대로 전달한다. 이렇게 NginX를 한번 거쳐서 express 같은 WAS와 연결하는 방식을 &lt;code class=&quot;language-text&quot;&gt;reverse proxy&lt;/code&gt;라고 한다. reverse proxy를 사용하는 이유는 여러 가지가 있지만, 외부 사용자들에게 WAS가 어떤 포트에서 동작하는지 숨겨 보안 적으로 강화하기 위한 목적이 있다.&lt;/p&gt;
&lt;p&gt;이런 흐름으로 사용자는 어느 정도 화면이 만들어진 html 문서를 처음 응답으로 받아볼 수가 있는데, 비어있는 body를 첫 html 응답으로 가져오는 CSR보다는 훨씬 빠른 화면 로딩 속도를 보여준다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; indexFile &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;../client/index.html&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ReactDOMServer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;renderToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;App &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;indexFile&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;utf8&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;err&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Something went wrong:&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Oops, better luck next time!&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&amp;lt;div id=&quot;root&quot;&gt;&amp;lt;/div&gt;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;div id=&quot;root&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;app&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;/div&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;리액트를 사용한다면 개발자는 express.js에서 대략 위와 같은 코드를 작성하는데 &lt;code class=&quot;language-text&quot;&gt;ReactDOMServer.renderToString&lt;/code&gt;을 사용한다는 점을 빼면 CSR에서 렌더링하던 방식과 크게 다른 것은 없다. &lt;code class=&quot;language-text&quot;&gt;renderToString&lt;/code&gt;은 리액트가 컴포넌트 구조를 파악하고 HTML string으로 바꿔주는 동작을 한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/133872451-4d2b17f2-f6f2-4f5b-82c1-39542fd28723.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;이렇게 작업을 마치면 개발자 도구에서 html을 받아온 결과가 일부 완성된 것을 확인할 수 있다.
첫 html을 받아온 이 시점 이후부터는 더는 SSR이 아닌 CSR로 바뀐다. 페이지 이동도 SPA처럼 동작하는데 이게 어떻게 가능한 것일까?&lt;/p&gt;
&lt;p&gt;일단 우리는 CSR + SSR의 환경이기 때문에 첫 html을 받아온 후에도 CSR 환경을 동작시키기 위한 javascript를 불러오는 script 태그가 여전히 들어가 있는 상태다. 그래서 html을 받아온 후 js파일을 가져와 CSR로 동작시키기 때문에 SSR에서 CSR로의 변경이 가능한 것이다.&lt;/p&gt;
&lt;p&gt;그럼 또 다른 의문이 들 것이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;이렇게 되면 SSR에 의해서 일부 렌더링 된 화면을 CSR에 의해 다시 그리는 것이 아닌가? 엄청나게 비효율적일 것 같은데?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;그 의문은 &lt;code class=&quot;language-text&quot;&gt;ReactDOM.hydrate()&lt;/code&gt;가 풀어준다. 우리가 이전에 CSR로 렌더링할 때 쓰던 &lt;code class=&quot;language-text&quot;&gt;ReactDOM.render()&lt;/code&gt; 메서드를 hydrate로 바꿔주기만 하면 리액트는 ‘SSR에 의해서 일부는 렌더링 되어서 오는구나! 나는 나머지 부분만 채워주면 되겠네?‘라고 생각한 뒤 이벤트 리스너만 등록하거나 렌더링되지 않은 나머지 부분을 추가 렌더링해 주는 등 효율적으로 동작한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//index.js&lt;/span&gt;
ReactDOM&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hydrate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StrictMode&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;BrowserRouter&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;App &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;BrowserRouter&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StrictMode&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;root&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;🙋‍♂️ 로그인된 상태로 화면을 가져올 순 없을까?&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/133872457-9040e402-f18c-4401-aceb-b6e1f35cb8db.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;이미 SSR에 성공했지만 약간 문제가 있다. 이미 로그인되어있는 사용자는 첫 화면으로 로그인되어있는 화면을 볼 수 있어야 할 것이다. 하지만 현재 상태는 로그인 여부와는 상관없이 무조건 비회원 화면만을 응답으로 보내주고 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/133872460-435daede-a36e-48f4-8af2-0690c24d3471.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;로그인 상태일 때 위와 같은 화면을 서버에서 내려줄 순 없을까? 결론부터 말하면, 가능하다.
먼저 로그아웃 상태에서 로그인하는 경우부터 알아보자.&lt;/p&gt;
&lt;h3&gt;로그아웃 상태에서 로그인&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/133872418-260cb94e-1f8b-424a-bcc3-c7c4f044405a.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;로그아웃 상태에서 처음 로그인하는 상황을 떠올려보자. 필자는 보안을 위해 refresh token을 브라우저에 저장하지 않고 서버의 세션에 저장하고 브라우저는 access token만을 private 변수로 가지고 있는 상황을 가정했다.&lt;/p&gt;
&lt;p&gt;브라우저는 백엔드 서버로 로그인 요청을 보내고 access token과 refresh token을 받아온다. 여기서 access token은 사용자 권한이 필요한 다음 요청에서 사용해야 하니 브라우저에서 가지고 있어야 할 것이다. 문제는 누군가 내가 로그인한 상태를 기억하고 있어야 다음 웹페이지 접근 시 로그인 상태가 풀리지 않는다는 것인데, 이 역할을 프론트 서버가 하게 된다.&lt;/p&gt;
&lt;p&gt;브라우저는 프론트 서버로 refresh token, access token 그리고 유저 정보를 보내고 프론트 서버는 이를 받아 세션에 저장한 후 세션 id를 브라우저에게 돌려준다. 브라우저는 이 세션 id를 만료 기간 전까지 쿠키로 가지고 있게 되고, 다음 웹페이지 접근 시 사용하게 된다.&lt;/p&gt;
&lt;h3&gt;로그인 상태에서 웹페이지 진입&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/133872421-7f992db7-86d0-470c-89dc-d95c7fd09521.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;이번에는 로그인 상태에서 새로 고침을 통해 웹페이지 진입을 한다고 생각해보자. 일단 &lt;code class=&quot;language-text&quot;&gt;로그인 상태&lt;/code&gt;라는 것은 브라우저가 세션 id를 쿠키로 가지고 있다는 의미와 같다. 그렇다면 쿠키 특성상 웹페이지 진입 시 자동으로 html 요청에 쿠키가 담겨 프론트 서버로 넘어가게 될 것이고 express에서는 세션 id를 통해서 사용자를 식별할 수 있다. 식별된 사용자의 정보에 따라 첫 html을 구성하고, access token도 브라우저에게 넘겨 줄 수 있다.&lt;/p&gt;
&lt;p&gt;access token을 넘겨주는 이유는 초기 웹페이지 진입 시 브라우저는 백엔드 서버와 통신에 필요한 access token은 가지고 있지 않기 때문이다. 브라우저가 프론트 서버를 거치지 않고 직접 백엔드 서버와 통신하기 위해서는 access token이 꼭 필요하다.&lt;/p&gt;
&lt;p&gt;여기서 html 문서에 어떻게 access token과 같은 정보를 담아 보낼 수 있는지 의문이 들 것이다.
이 문제는 html 문서에 script 태그를 포함하는 것으로 해결한다. 예를 들면 다음과 같은 방법이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/133872472-eb8267bc-51d9-4f73-b7d4-348537ba4316.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;전달하는 정보가 객체면 &lt;code class=&quot;language-text&quot;&gt;JSON.stringify()&lt;/code&gt;를 사용해 전달한다. 이러한 방법을 좀 더 편하게 사용할 수 있도록 리덕스나 리액트 쿼리 같은 상태 관리 라이브러리도 api 지원을 하고 있다. 리액트 쿼리 같은 경우에, 미리 프론트 서버에서 백엔드 서버로부터 데이터를 가져온 후 script 태그로 포함해 브라우저에서는 리액트 쿼리가 백엔드 서버로 추가 요청을 보내지 않는 것도 가능하다. (&lt;a href=&quot;https://react-query.tanstack.com/guides/ssr#using-other-frameworks-or-custom-ssr-frameworks&quot;&gt;참고&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;🎯 주의할 것은 이 방법이 XSS 공격에 취약할 수 있다는 사실이다. 하지만 간단한 해결법도 있으니 &lt;a href=&quot;https://medium.com/node-security/the-most-common-xss-vulnerability-in-react-js-applications-2bdffbcc1fa0&quot;&gt;여기&lt;/a&gt;를 참고하여 XSS 공격을 방지할 수 있도록 하자. 요약하자면 script 태그에 정보를 담을 때 &lt;code class=&quot;language-text&quot;&gt;JSON.stringify()&lt;/code&gt;로 정보를 담아서는 안 되고 &lt;a href=&quot;https://github.com/yahoo/serialize-javascript&quot;&gt;serialize-javascript&lt;/a&gt;와 같은 라이브러리를 사용해서 담아야 한다는 것이다. 그 이유는 우리가 JSON.stringify()로 브라우저에 넘기는 정보가 악의적인 스크립트 태그를 가지고 있을 수도 있기 때문이다. 예를 들면 다음과 같다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
  window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__userInJSON__ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string-property property&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;-&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;여기까지만&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;의도한&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;대로&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;실행&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&amp;lt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;\&quot;요건 몰랐지\&quot;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&quot;}
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 경우 유저네임에 악의적인 태그를 작성한 사용자에 의해 &lt;code class=&quot;language-text&quot;&gt;alert(&quot;요건 몰랐지&quot;)&lt;/code&gt; 메서드가 호출된다. 이 방법을 통해 민감한 작업을 수행한다면… 생각만 해도 아찔하다.&lt;/p&gt;
&lt;h3&gt;결과&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/133872483-af5ef395-7325-4496-b4de-fc5e519b2c83.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;결과적으로는 위와 같이 로그인 상태의 html을 첫 응답으로 받아볼 수 있다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;오늘은 CSR에 SSR을 도입하는 흐름을 같이 따라가 봤다. 처음에 공부했을 때는 도대체 어떤 흐름인가 혼란도 많이 왔었고 특히 로그인 부분에서 많이 헤맸는데 이제 어느 정도 감이 잡히는 것 같다.&lt;/p&gt;
&lt;p&gt;CSR + SSR 환경을 도입하면서 느낀 점은 굉장히 매력적인 구조라는 것이다. 특히 서버와 클라이언트 모두 하나의 프로젝트에서 javascript 언어로 개발할 수 있다는 것이었는데, 생산성이 굉장히 좋다고 느꼈다. 좀 더 개발을 해봐야 알겠지만, 앱이 커지게 되면 필연적으로 SSR은 도입해야 할 기술이라 느꼈다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Lh5CrFJQSz4&quot;&gt;React.js 개발자를 위한 SSR 앱 개발 및 배포하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hoon-k.tistory.com/5&quot;&gt;웹서버 vs WAS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://darrengwon.tistory.com/546&quot;&gt;Nginx를 리버스 프록시로 사용해보자 (EC2 + Nginx + express)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://thewidlarzgroup.com/nextjs-auth/&quot;&gt;NextJS SSR - JWT (Access/Refresh Token) Authentication with external Backend&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ykss.netlify.app/web/storage_session_cookie/&quot;&gt;웹 스토리지 vs 쿠키 vs 세션&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[멀티 모듈 적용하기 with Gradle]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-09-06-multi-module/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-09-06-multi-module/</guid><pubDate>Mon, 06 Sep 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이번 글에서는 프로젝트를 구성하는 데 있어 멀티 모듈 활용했을 때의 장점과 간단한 설정 방법을 알아본다. 멀티 모듈의 개념을 처음 접하는 사람들이 읽어보기를 추천한다.&lt;/p&gt;
&lt;!-- end --&gt;
&lt;h2&gt;멀티 모듈이란?&lt;/h2&gt;
&lt;p&gt;오라클 자바 문서에서는, 모듈을 패키지의 한 단계 위의 집합체이며, 관련된 패키지와 리소스들의 재사용할 수 있는 그룹이라고 정의한다.&lt;/p&gt;
&lt;p&gt;운영하던 서비스를 관리자 서버(admin)와, 사용자 서버(web)를 분리하여 운영할 필요성이 있다고 해보자. 하나의 모듈로 구현을 진행한다면, 각각 별도의 프로젝트를 만들고 많은 부분의 코드를 &lt;code class=&quot;language-text&quot;&gt;복사+붙여넣기&lt;/code&gt;를 통해 구현할 것이다. Java 빌드 도구의 멀티 모듈을 사용하면, 공통된 구현(common)을 공유하는 방식으로 코드를 작성할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;왜 멀티 모듈을 사용하는가?&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;코드의 중복을 줄일 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;공통된 로직이 있는 여러 서비스를 운영하게 되었을 때, 공통된 부분을 모듈화하고, 이 의존성을 추가하여 공유할 수 있다. 물론, executions 플러그인을 사용하여, 하나의 프로젝트 안에서 여러 개의 실행 jar 파일을 만들어낼 수도 있지만, &lt;a href=&quot;https://maven.apache.org/guides/mini/guide-using-one-source-directory.html#producing-multiple-unique-jars-from-a-single-source-directory&quot;&gt;Maven 공식 문서&lt;/a&gt; 에서는 하나의 Source 디렉토리로부터 여러 jar 파일을 생성하는 것을 지양한다고 말하고 그 해결방식으로 Module의 사용을 제시한다.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;각 모듈의 기능을 파악하기 쉬워진다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;공통의 기능은 의존성 주입으로, 모듈별로 기능을 분리하여 작성하기 때문에, 코드의 이해가 쉬워진다.&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;빌드를 쉽게 진행할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;./gradlew :moduleName:build&lt;/code&gt;의 명령어를 통해 빌드를 쉽게 진행할 수 있다. 멀티프로젝트의 경우에는 별도로 빌드를 진행하는 반면에 루트 프로젝트에서 각각의 모듈을 빌드할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;멀티 모듈 간단 설정 방법&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://spring.io/guides/gs/multi-module/&quot;&gt;Spring 공식문서&lt;/a&gt; 에서 작성된 예시를 활용하여 설명을 이어나간다. 공통된 부분을 &lt;code class=&quot;language-text&quot;&gt;library&lt;/code&gt; 모듈, 이를 활용하는 부분 &lt;code class=&quot;language-text&quot;&gt;application&lt;/code&gt; 모듈로 구성된 간단한 프로젝트의 설정은 다음의 방식으로 이루어진다.&lt;/p&gt;
&lt;h3&gt;프로젝트 내부에 여러 프로젝트 생성하기&lt;/h3&gt;
&lt;p&gt;IntelliJ에서 여러 프로젝트를 생성하는 방법은 간단하다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Gradle 프로젝트를 생성한다.&lt;/li&gt;
&lt;li&gt;File &gt; Project Structure 탭에서 모듈을 쉽게 추가할 수가 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 895px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/04b458c1b3cd181b7b42359291ba020a/b002d/2021-09-06-multi-module.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.39999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAACGklEQVR42o2TWY/TMBSF84OomGm6ZXE223GcxVnassyAhBAgkHgCIR5m/gC/+nDttqgZeODhk50o91zf4xPP99fw/Q1Wyxusl8/h3yyIZ7RfYHW7AAs3kAVDKQXCIIS/9LHyVzN8YrNao9zcwpOqgW4GlP09mhefocb34MMH6OMXyPEjBO3l9Alc9yhkSai/yAleVohYCi8aH1C/+wXx6gHq7gH6zSPY9BO77jt2xvKN+IGY92BJCpYVM9KcuzUhdmEML9ZvUR+/Iq1bVFoiSRiiKABjIQlEp5UFKISA0jVkpSGVRqkUhKqQcUGC3BHGCTxedejHl+DGoB33aPs9uKoRJjmitECUFIhp3eUageywEwYhb9CbDt0woqKD5FxSwxJb8tjLpUHdv0bZdCQ2YNgfoFtDnaUjpw/tSCHLEBBhkrn9ZVz3jRUkHzdWkBU1CRyQtxNqM8AME/pp74RrEr4IxmlG5CeS0z6i9YJ93gYReZhVSEWHQmu0w0Ae1a5zWgjX/Y/IteA/sE234VmQFS3FwjhP7Nj2ZNabedF/C2okRUO3fBpZ1Q1dSoWiVOdRzwXnout3TwVdbCIn2CIpGxp5Iu+OFI/GmXxdbEMtKTYlYX29+HYtGMQMXiYaZKIlD3uY6QAzTs7HpyNy9zcoJ5zk85PPRk5tMClzqa7Q0e2W1Sm8uZDzU9i4UHAvsOaeuHNhFxR0GzUr/hv30dCIUq25PQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;multi-module&quot;
        title=&quot;&quot;
        src=&quot;/static/04b458c1b3cd181b7b42359291ba020a/b002d/2021-09-06-multi-module.png&quot;
        srcset=&quot;/static/04b458c1b3cd181b7b42359291ba020a/0eb09/2021-09-06-multi-module.png 500w,
/static/04b458c1b3cd181b7b42359291ba020a/b002d/2021-09-06-multi-module.png 895w&quot;
        sizes=&quot;(max-width: 895px) 100vw, 895px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 452px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/50b4b7fb282914488635bc79ded08e58/80d88/2021-09-06-multi-module-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 91.5929203539823%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsTAAALEwEAmpwYAAABp0lEQVR42p1USW7cMBCc1wQRd3GnRFJyAsdB8v/3VJqcAL4OfWhogVDq2viQSoNzDi7pqsxzxj0Xz/eL8ziOAh0K1PUX6nyHah+Q9RcE/UiILwBaa2H2HTEGHKWgt4acM3q/UGuFVBKMsdcB3QC0DjEEAo04zwO1VYzNC/3gvi9orV8Gfey0nSJ63kgoom5ChiGA8V4bA0MjxOt6PnKKkMbBHG9TO0amsO/faKMNnLZaoTsBB1XtEmz9CZF/QPgCYRO4kBNw2RTn3HTTGw0TK3T/DV3fyQxFUqgluv9jcyDQlheJ34cZOeKiayOHW+vTrCWXcyYjnCTKDLZzKM+xbYxmm7OsYU4Jg3Zv55yjZNqskbv6i02hrBmfSbePqZ8kgwT/3JKtAnrnoSlzPpOWpSGWA5aeU8o0kT5aiw5Vb6fqWbjdoKSAt7tPM3rvVL2GYZqUcrXLFil4uHQixPysYYjkfpwVHPF5uXqD3uhyocOBpws8nKSbxMa2T6c5Wwv2aIV3FjqeUP0PZGyQFOjVUE/AEWrvPZ0y54xL70/d7vtGoWNsNYf/ALBeLH4lEbTkAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;multi-module-tree&quot;
        title=&quot;&quot;
        src=&quot;/static/50b4b7fb282914488635bc79ded08e58/80d88/2021-09-06-multi-module-2.png&quot;
        srcset=&quot;/static/50b4b7fb282914488635bc79ded08e58/80d88/2021-09-06-multi-module-2.png 452w&quot;
        sizes=&quot;(max-width: 452px) 100vw, 452px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;프로젝트를 생성하면 위와 같은 구조를 가진다.&lt;/p&gt;
&lt;h3&gt;Root Project (setting.gradle)&lt;/h3&gt;
&lt;p&gt;하위 프로젝트(library, application)를 setting.gradle에서 설정을 진행해준다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;rootProject&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;multi-module&apos;&lt;/span&gt;

include &lt;span class=&quot;token string&quot;&gt;&apos;library&apos;&lt;/span&gt;
include &lt;span class=&quot;token string&quot;&gt;&apos;application&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Library 모듈 (build.gradle)&lt;/h3&gt;
&lt;p&gt;기본적으로 gradle build시 실행 가능한 jar 파일을 만드는데, Library는 이를 자체적으로 실행을 시키지 않기 때문에 아래의 설정을 추가해준다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;plugins &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	id &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.boot&apos;&lt;/span&gt; version &lt;span class=&quot;token string&quot;&gt;&apos;2.5.2&apos;&lt;/span&gt;
	id &lt;span class=&quot;token string&quot;&gt;&apos;io.spring.dependency-management&apos;&lt;/span&gt; version &lt;span class=&quot;token string&quot;&gt;&apos;1.0.11.RELEASE&apos;&lt;/span&gt;
	id &lt;span class=&quot;token string&quot;&gt;&apos;java&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

group &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;com.example&apos;&lt;/span&gt;
version &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;0.0.1-SNAPSHOT&apos;&lt;/span&gt;
sourceCompatibility &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1.8&apos;&lt;/span&gt;

repositories &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;mavenCentral&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 설정 부분&lt;/span&gt;
bootJar &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	enabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

jar &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	enabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//&lt;/span&gt;

dependencies &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	implementation &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.boot:spring-boot-starter&apos;&lt;/span&gt;
	testImplementation &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.boot:spring-boot-starter-test&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Application 모듈 (build.gradle)&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;project(:projectPath)&lt;/code&gt; 의 방식으로 정의한 모듈의 의존성을 추가하여 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;gradle의 ProjectApi의 정의된 project() 메소드로 특정 경로의 있는 프로젝트의 의존성을 추가할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;plugins &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	id &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.boot&apos;&lt;/span&gt; version &lt;span class=&quot;token string&quot;&gt;&apos;2.5.2&apos;&lt;/span&gt;
	id &lt;span class=&quot;token string&quot;&gt;&apos;io.spring.dependency-management&apos;&lt;/span&gt; version &lt;span class=&quot;token string&quot;&gt;&apos;1.0.11.RELEASE&apos;&lt;/span&gt;
	id &lt;span class=&quot;token string&quot;&gt;&apos;java&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

group &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;com.example&apos;&lt;/span&gt;
version &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;0.0.1-SNAPSHOT&apos;&lt;/span&gt;
sourceCompatibility &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1.8&apos;&lt;/span&gt;

repositories &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;mavenCentral&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

dependencies &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	implementation &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.boot:spring-boot-starter-actuator&apos;&lt;/span&gt;
	implementation &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.boot:spring-boot-starter-web&apos;&lt;/span&gt;
  
  &lt;span class=&quot;token comment&quot;&gt;// 설정 부분&lt;/span&gt;
	implementation &lt;span class=&quot;token function&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;:library&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	testImplementation &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.boot:spring-boot-starter-test&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;간단하게 멀티 모듈이 어떤 것인지와 그 장점, 아주 쉬운 설정 방법을 알아보았다. 공통으로 정의한 기능이라는 것은 수정이 발생했을 때, 의존성을 가지고 있는 모든 서비스에 영향을 주기 때문에, 발생할 수 있는 위험도 많다. 프로젝트의 상황을 고려하여, 멀티 모듈의 도입을 시도해 보기 바란다.&lt;/p&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.oracle.com/kr/corporate/features/understanding-java-9-modules.html&quot;&gt;ORACLE - understanding-java-9-modules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://spring.io/guides/gs/multi-module&quot;&gt;Spring Docs - multi-module&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[웹 소켓에 대해 알아보자! - 실전 편]]></title><description><![CDATA[웹 소켓에 대해 알아보자! - 실전 편 이번 글에서는 저번에 작성했던 웹 소켓 이론 편에 이어서, 스프링 환경에서 웹 소켓을 사용하는 법에 대해 다루려고 한다. STOMP 구현하기에 앞서, 우리는 새로운 프로토콜을 도입할 것이다. 바로 STOMP…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-09-05-web-socket-practice/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-09-05-web-socket-practice/</guid><pubDate>Sun, 05 Sep 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p style=&quot;font-family: sans-serif; text-align: center; color: #aaa; margin-bottom: 3em; font-size: 85%&quot;&gt;image origin: &lt;a href=&quot;https://unsplash.com/photos/vT9zeLCOpps&quot;&gt;unsplash.com&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;웹 소켓에 대해 알아보자! - 실전 편&lt;/h2&gt;
&lt;p&gt;이번 글에서는 저번에 작성했던 &lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-08-14-web-socket/&quot;&gt;웹 소켓 이론 편&lt;/a&gt;에 이어서, 스프링 환경에서 웹 소켓을 사용하는 법에 대해 다루려고 한다.&lt;/p&gt;
&lt;h3&gt;STOMP&lt;/h3&gt;
&lt;p&gt;구현하기에 앞서, 우리는 새로운 프로토콜을 도입할 것이다. 바로 STOMP이다.&lt;/p&gt;
&lt;p&gt;STOMP는 Simple Text Oriented Messaging Protocol의 약자이다. WebSocket 프로토콜은 Text 또는 Binary 두 가지 유형의 메시지 타입은 정의하지만 메시지의 내용에 대해서는 정의하지 않는다.
즉, WebSocket만 사용해서 구현하게 되면 해당 메시지가 어떤 요청인지, 어떤 포맷으로 오는지, 메시지 통신 과정을 어떻게 처리해야 하는지 정해져 있지 않아 일일이 구현해야 한다.
따라서 STOMP라는 프로토콜을 서브 프로토콜로 사용한다. STOMP는 클라이언트와 서버가 서로 통신하는 데 있어 메시지의 형식, 유형, 내용 등을 정의해주는 프로토콜이라고 할 수 있다.
STOMP를 사용하게 되면 단순한 Binary, Text가 아닌 규격을 갖춘 메시지를 보낼 수 있다.
스프링은 spring-websocket 모듈을 통해서 STOMP를 제공하고 있다.&lt;/p&gt;
&lt;p&gt;STOMP의 형식은 HTTP와 닮았다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;COMMAND&lt;/span&gt;
header1&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;value1
header2&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;value2

&lt;span class=&quot;token class-name&quot;&gt;Body&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;@&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;클라이언트는 메시지를 전송하기 위해 COMMAND로 SEND 또는 SUBSCRIBE 명령을 사용하며, header와 value로 메시지의 수신 대상과 메시지에 대한 정보를 설명할 수 있다. 기존의 WebSocket만으로는 표현할 수 없는 형식이다.
이를 통해 STOMP 프로토콜은 Publisher(송신자)-Subscriber(수신자)를 지정하고, 메시지 브로커를 통해 특정 사용자에게만 메시지를 전송하는 기능 등을 가능하게 한다.
메시지 브로커는 Publisher로부터 전달받은 메시지를 Subscriber로 전달해주는 중간 역할을 수행한다고 생각하면 된다.
메시지 브로커의 동작 방식은 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;각각 A, B, C 라는 유저가 차례로 5번방에 입장한다.&lt;/li&gt;
&lt;li&gt;A가 5번방에서 채팅을 전송한다.&lt;/li&gt;
&lt;li&gt;5번방 메시지 브로커(중재자)가 메세지를 받는다.&lt;/li&gt;
&lt;li&gt;5번방 메시지 브로커가 5번방 구독자들(A, B, C)에게 메세지를 전송한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;지금부터 5번방에 유저들이 참가하여 메시지를 주고받는 상황을 단계별로 살펴보자.&lt;/p&gt;
&lt;p&gt;유저들은 채팅방에 입장함과 동시에 다음과 같이 5번 채팅방에 대해 구독(SUBSCRIBE)을 하게 되고, 메시지 브로커는 클라이언트의 SUBSCRIBE 정보를 자체적으로 메모리에 유지한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;SUBSCRIBE&lt;/span&gt;
destination&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;subscribe&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;chat&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;room&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다음과 같이 어떤 유저가 메시지를 보내면, 메시지 브로커는 SUBSCRIBE 중인 다른 유저들에게 메시지를 전달한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;SEND&lt;/span&gt;
content&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;application&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;json
destination&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;publish&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;chat

&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;chatRoomId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;MESSAGE&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;writer&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;clientB&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;STOMP 기반 구현&lt;/h3&gt;
&lt;p&gt;위에서 말한 시나리오대로 구현해보자. 먼저, WebSocket 관련 설정 클래스를 추가해야 한다.
간단하게 요약하면 WebSocket 연결을 요청할 주소와 SUBSCRIBE, PUBLISH를 요청할 주소를 설정해주는 것이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableWebSocketMessageBroker&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSocketConfig&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSocketMessageBrokerConfigurer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;configureMessageBroker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MessageBrokerRegistry&lt;/span&gt; registry&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      registry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enableSimpleBroker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/subscribe&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      registry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setApplicationDestinationPrefixes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/publish&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;registerStompEndpoints&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StompEndpointRegistry&lt;/span&gt; registry&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      registry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEndpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/ws-connection&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAllowedOrigins&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;chrome-extension://ggnhohnkfcpcanfekomdkjffnfcjnjam&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withSockJS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@EnableWebSocketMessageBroker&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;메시지 브로커가 지원하는 ‘WebSocket 메시지 처리’를 활성화한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;configureMessageBroker()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;메모리 기반의 Simple Message Broker를 활성화한다. 메시지 브로커는 &lt;code class=&quot;language-text&quot;&gt;&quot;/subscribe&quot;&lt;/code&gt;으로 시작하는 주소의 Subscriber들에게 메시지를 전달하는 역할을 한다. 이때, 클라이언트가 서버로 메시지 보낼 때 붙여야 하는 prefix를 지정한다. 예제에서는 &lt;code class=&quot;language-text&quot;&gt;&quot;/publish&quot;&lt;/code&gt;로 지정하였다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;registerStompEndpoints()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;기존의 WebSocket 설정과 마찬가지로 HandShake와 통신을 담당할 EndPoint를 지정한다. 클라이언트에서 서버로 WebSocket 연결을 하고 싶을 때, &lt;code class=&quot;language-text&quot;&gt;&quot;/ws-connection&quot;&lt;/code&gt;으로 요청을 보내도록 하였다.&lt;/p&gt;
&lt;p&gt;송신자와 수신자, 채팅방 번호 그리고 메시지 내용을 담고 있는 ChatRequest DTO는 다음과 같다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Getter&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@NoArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChatRequest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/**
     * 송신자 id
     */&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@NotNull&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; senderId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 수신자 id
     */&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@NotNull&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; receiverId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 채팅방 id
     */&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@NotNull&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; roomId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 메시지 내용
     */&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@NotBlank&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChatRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; senderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; receiverId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; roomId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;senderId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; senderId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;receiverId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; receiverId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;roomId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; roomId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Controller는 다음과 같다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChattingController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChattingService&lt;/span&gt; chattingService&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpMessagingTemplate&lt;/span&gt; simpMessagingTemplate&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChattingController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ChattingService&lt;/span&gt; chattingService&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpMessagingTemplate&lt;/span&gt; simpMessagingTemplate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;chattingService &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; chattingService&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;simpMessagingTemplate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; simpMessagingTemplate&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@MessageMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/messages&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Valid&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChatRequest&lt;/span&gt; chatRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      chattingService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;chatRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      simpMessagingTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;convertAndSend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/subscribe/rooms/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; chatRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRoomId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; chatRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;SimpleMessagingTemplate&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@EnableWebSocketMessageBroker&lt;/code&gt;를 통해서 등록되는 &lt;code class=&quot;language-text&quot;&gt;Bean&lt;/code&gt;이다. Broker로 메시지를 전달한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@MessageMapping&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Client가 SEND를 할 수 있는 경로이다. &lt;code class=&quot;language-text&quot;&gt;WebSocketConfig&lt;/code&gt;에서 등록한 &lt;code class=&quot;language-text&quot;&gt;applicationDestinationPrfixes&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;@MessageMapping&lt;/code&gt;의 경로가 합쳐진다.(&lt;code class=&quot;language-text&quot;&gt;/publish/messages&lt;/code&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;chat()&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;클라이언트에서 &lt;code class=&quot;language-text&quot;&gt;/publish/messages&lt;/code&gt; url로 메시지를 보내면, ChatRequest의 채팅방 id를 이용하여 해당 방을 구독 중인 사용자들에게 메시지를 전달하도록 하는 메서드이다.&lt;/p&gt;
&lt;p&gt;클라이언트에서는 다음과 같이 연결하면 된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; socket &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSocket&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/ws-connection&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    stompClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Stomp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;over&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;socket&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    stompClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;setConnected&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        stompClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/subscribe/rooms/5&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;greeting&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;greeting&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;메시지 전송 예시는 다음과 같다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    stompClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/publish/messages&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token string-property property&quot;&gt;&apos;message&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;#name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string-property property&quot;&gt;&apos;senderId&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string-property property&quot;&gt;&apos;receiverId&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string-property property&quot;&gt;&apos;roomId&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;결론&lt;/h3&gt;
&lt;p&gt;WebSocket에 STOMP를 활용한 스프링 환경의 채팅 기능 구현에 대해 알아보았다.&lt;/p&gt;
&lt;p&gt;메모리 기반의 STOMP 메시지 브로커 대신 Kafka, RabbitMQ, ActiveMQ 등의 메시징 시스템을 이용할 수도 있으니 추가로 학습하고 적용하는 것도 좋을 것이다.&lt;/p&gt;
&lt;h3&gt;Reference&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/spring-guides/gs-messaging-stomp-websocket&quot;&gt;https://github.com/spring-guides/gs-messaging-stomp-websocket&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#websocket&quot;&gt;https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#websocket&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://supawer0728.github.io/2018/03/30/spring-websocket/&quot;&gt;https://supawer0728.github.io/2018/03/30/spring-websocket/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sup2is.github.io/2019/06/05/websocket-1.html&quot;&gt;https://sup2is.github.io/2019/06/05/websocket-1.html&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[관계형 DB에서 계층적인 데이터 관리하기]]></title><description><![CDATA[1. 계층형 댓글 구현 image 우아한테크코스 레벨 3 팀 프로젝트에서 SNS 성격의 웹 어플리케이션을 개발하게 되었습니다. SNS 기능 요구사항 중 특정 댓글에 대한 대댓글 작성이라는 다소 까다로운 요구사항이 존재했는데요. 관계형 DB…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-09-03-hierarchy-data-with-rdb/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-09-03-hierarchy-data-with-rdb/</guid><pubDate>Sat, 04 Sep 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. 계층형 댓글 구현&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/132088157-28e0f111-2036-4fa0-9c19-39e8c3543570.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;우아한테크코스 레벨 3 팀 프로젝트에서 SNS 성격의 웹 어플리케이션을 개발하게 되었습니다. SNS 기능 요구사항 중 &lt;strong&gt;특정 댓글에 대한 대댓글 작성&lt;/strong&gt;이라는 다소 까다로운 요구사항이 존재했는데요. 관계형 DB에서 계층적인 데이터를 관리하는 방법에 대해 호기심이 생겨 토이 프로젝트를 진행하게 되었습니다.&lt;/p&gt;
&lt;p&gt;최대 99 Depth까지를 허용하는 계층형 댓글 게시판을 구현하는데, 관계형 DB의 스키마 설계가 가장 큰 난관이었습니다. 처음에는 DB 스키마를 고려하지 않고 객체 지향적인 JPA 코드를 먼저 작성해보았습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;2. JPA 스켈레톤 코드&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/132110264-5af23016-38d9-4d43-94a5-931c3ed4cdd8.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;계층형 댓글은 다소 특별한 노출 기준을 가지고 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하위 대댓글이 없는 동일한 Depth의 댓글 A와 B가 존재한다면, 작성 일자 오름차순으로 정렬한다.&lt;/li&gt;
&lt;li&gt;특정한 댓글보다 상단에 위치한 댓글에 추가적인 하위 대댓글이 달리는 경우, 작성 일자보다 Depth를 우선적으로 고려하여 정렬한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;기준이 잘 이해되지 않는다면 위 사진 속 댓글들의 날짜와 Depth 및 순서를 눈여겨봐 주시기를 바랍니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Comment.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ManyToOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fetch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FetchType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;LAZY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@JoinColumn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;root_comment_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; rootComment&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ManyToOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fetch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FetchType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;LAZY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@JoinColumn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;parent_comment_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; parentComment&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@OneToMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fetch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FetchType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;LAZY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mappedBy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;parentComment&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cascade &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PERSIST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; childComments &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nullable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; groupOrder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nullable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; depth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 중략&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;addChildComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; childComment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;depth &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        childComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rootComment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rootComment&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        childComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parentComment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        childComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;depth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;depth &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        childComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;groupOrder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        childComments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;childComment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;계층적인 댓글은 특정한 최상위 댓글 및 직속 부모 댓글이 존재한다는 점에 착안하여 연관 관계를 위와 같이 구성해보았습니다. 또한 다소 번거로운 댓글 정렬 조건 때문에, 추가적으로 특정한 최상위 댓글 그룹 내에 속한 댓글의 순서를 &lt;code class=&quot;language-text&quot;&gt;groupOrder&lt;/code&gt;라는 별도의 필드로 표현했습니다.&lt;/p&gt;
&lt;p&gt;특정 댓글에 대댓글을 작성하는 경우 다음과 같은 로직이 진행됩니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;CommentService.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;replyComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CommentReplyRequestDto&lt;/span&gt; commentReplyRequestDto&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findActiveUserById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;commentReplyRequestDto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUserId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserNotFoundException&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; post &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; postRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findActivePostById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;commentReplyRequestDto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPostId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PostNotFoundException&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; parentComment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; commentRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;commentReplyRequestDto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCommentId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CommentNotFoundException&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; childComment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;commentReplyRequestDto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    parentComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addChildComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;childComment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    commentRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;adjustGroupOrders&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;childComment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;대댓글을 달고자 하는 부모 Comment를 조회한다.&lt;/li&gt;
&lt;li&gt;자식 Comment를 생성하고 &lt;code class=&quot;language-text&quot;&gt;parentComment.addChildComment(childComment)&lt;/code&gt;를 호출한다.
&lt;ul&gt;
&lt;li&gt;자식 Comment의 계층 구조를 부모 Comment 기반으로 설정한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@OneToMany&lt;/code&gt;로 잡힌 컬렉션에 자식 Comment를 추가한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;새로운 자식 Comment가 영속화되기 전에, 해당 자식 Comment가 속한 그룹 내 댓글 순서들이 재정렬되어야 한다.
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;commentRepository.adjustGroupOrders(childComment)&lt;/code&gt;를 호출하여 같은 댓글 그룹 내에서, 새롭게 삽입될 자식 Comment보다 groupOrder가 크거나 같은 경우 groupOrder를 +1을 하여 순서를 재조정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Cascade.PERSISTS&lt;/code&gt; 옵션을 통해 최종적으로 자식 Comment가 영속화된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;대댓글을 삭제할 때도 추가와 비슷한 로직이 수행됩니다.&lt;/p&gt;
&lt;h3&gt;2.1. 문제점&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Comment.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;addChildComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; childComment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;depth &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    childComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rootComment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rootComment&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    childComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parentComment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    childComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;depth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;depth &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    childComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;groupOrder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    childComments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;childComment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;새롭게 추가될 자식 Comment의 그룹 내 순서는 어떻게 정해질까요? &lt;code class=&quot;language-text&quot;&gt;부모 Comment에 달린 하위 Comment들의 groupOrder 중 최댓값 + 1&lt;/code&gt;이 될 것입니다. 그런데 이 부분을 어플리케이션 코드에서 매끄럽게 처리하는 것이 다소 번거롭습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;A
ㄴ A1
   ㄴ A2
   ㄴ A3
      ㄴ A4
         ㄴ ... //무수히 많은 하위 계층 댓글이 존재
ㄴ A5
ㄴ A6&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A1 댓글에 대댓글을 다는 경우를 생각해봅시다. 조회하게 될 부모 Comment는 A1가 됩니다. 부모 Comment A1은 어떻게 하위 Comment들의 groupOrder 최댓값을 찾을 수 있을 까요? 현재 코드에서는 다음과 같은 방법을 고려할 수 있습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@OneToMany&lt;/code&gt;로 잡힌 자식 Comment 컬렉션에서 groupOrder가 가장 높은 자식 Comment를 찾는다.&lt;/li&gt;
&lt;li&gt;해당 자식 Comment가 하위 대댓글 자식 Comment를 보유하는 경우 1번 로직을 반복한다.&lt;/li&gt;
&lt;li&gt;최종적으로 탐색한 Comment가 하위 대댓글 자식이 없는 경우, 해당 Comment가 댓글 그룹 내에서 groupOrder가 가장 크다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;만약 A1 하위에 존재하는 대댓글이 많거나 계층이 너무 깊게 뻗어있는 경우 groupOrder를 탐색하는데 많은 시간이 소요될 것입니다. A1이 Root Comment가 아니기 때문에 groupOrder 최댓값을 찾을 수 있는 쿼리를 작성하는 것 또한 매우 복잡합니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;3. 관계형 DB와 계층적인 데이터&lt;/h2&gt;
&lt;p&gt;관계형 DB는 관계형 모델을 기초로 설계되어 있습니다. 반면에 웹 어플리케이션은 주로 객체 지향 언어로 작성되며 내부적으로 이진트리와 같은 계층적인 데이터 구조를 자주 활용합니다. 따라서 웹 어플리케이션에서 사용하는 그래프 구조의 데이터를 관계형 DB에 저장하는 것이 어렵습니다.&lt;/p&gt;
&lt;h3&gt;3.1. 전략&lt;/h3&gt;
&lt;p&gt;관계형 DB에 그래프를 표현하는 전략은 다음과 같이 4가지가 존재합니다. 각각의 전략에 대한 세부 내용은 &lt;a href=&quot;https://ibocon.tistory.com/203?category=846768&quot;&gt;관련 글&lt;/a&gt;을 참고하시길 바랍니다. 이번 글에서는 제가 토이 프로젝트를 진행하면서 사용한 Nested Set 방법에 관해 설명하고자 합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adjacency list&lt;/li&gt;
&lt;li&gt;Nested set&lt;/li&gt;
&lt;li&gt;Path enumeration&lt;/li&gt;
&lt;li&gt;Closure table&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2&gt;4. 중첩 세트 모델(The Nested Set Model)&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/132088882-53a900e6-ad28-40d2-8733-e7bb3f247c81.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;중첩 세트 모델(The Nested Set Model)이란 계층적인 데이터 구조를 집합 구조로 생각하고 스키마를 설계하는 전략입니다. Modified Preorder Tree Traversal Algorithm(수정된 전위 트리 순회 알고리즘, 원문 표기)이라고 일컫는데요.&lt;/p&gt;
&lt;p&gt;LEFT 필드와 RIGHT 필드로 레코드가 포함하는 범위를 결정하게 됩니다. 부모는 1 부터 N까지를 포함하고 자녀들은 각자의 범위를 부모 범위 내에서 결정하게 됩니다. 중간에 노드를 삽입 혹은 삭제할 때마다 관련 노드들의 값을 적절히 수정해주면 됩니다.&lt;/p&gt;
&lt;p&gt;이해가 잘 안 되시는 독자분들을 위해 조금 더 쉽게 풀어서 설명해보겠습니다. 특정한 게시물에 A, B, C, D, E라는 대댓글이 아닌 Root 댓글이 달리는 경우를 가정해보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;A
B
C
D
E&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A, B, C, D, E는 서로 독립적인 댓글 그룹의 Root 댓글입니다. 이 때, Root 댓글의 LEFT 및 RIGHT 필드 초기값은 1과 2로 고정입니다. LEFT 및 RIGHT 필드는 특정한 댓글 그룹 내에 속한 댓글의 계층 및 순서를 알기 위해 사용할뿐, 서로 다른 그룹의 댓글간 상호 독립적입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;A
B
ㄴB1
C
D
ㄴD1
E&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;B와 D에 대댓글이 달린 경우입니다. 이 때, A, C, E의 LEFT 및 RIGHT 필드는 여전히 1과 2로 고정입니다. A, C, E가 속한 댓글 계층에는 전혀 변화가 발생하지 않았기 때문입니다. 반면 B와 D가 속한 댓글 그룹의 중첩 세트 모델은 어떻게 변할까요?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;1 [] 4
  ㅣ
2 [] 3&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;B와 D의 LEFT 및 RIGHT 필드는 1과 4로 수정됩니다. 그리고 대댓글인 B1 및 D1의 LEFT 및 RIGHT 필드는 각각 2와 3이 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;B
ㄴB1
 ㄴa
 ㄴb
 ㄴc
 ㄴd
ㄴB2
 ㄴe
ㄴB3
 ㄴf
 ㄴg
 ㄴh&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이번에는 위와 같은 계층형 댓글을 중첩 세트 모델로 표현해보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;                                   1 [] 24
                                     ㅣ
          2 [] 11                 12 [] 15                 16 [] 23
            ㅣ                        ㅣ                       ㅣ
3 [] 4 5 [] 6 7 [] 8 9 [] 10      13 [] 14        17 [] 18 19 [] 20 21 [] 22&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;첨부된 그림을 참고하면 계층적인 데이터를 중첩 세트 모델로 쉽게 변환할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;B
ㄴB1
 ㄴa
 ㄴb
 ㄴc
 ㄴd
ㄴB2
 ㄴe
  ㄴe2
ㄴB3
 ㄴf
 ㄴg
 ㄴh&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이번에는 B2에 달린 e라는 대댓글에 신규 대댓글이 추가되는 경우를 상정해보겠습니다. 중첩 세트 모델은 어떻게 변화할까요?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;                                   1 [] 26
                                     ㅣ
          2 [] 11                 12 [] 17                 18 [] 25
            ㅣ                        ㅣ                       ㅣ
3 [] 4 5 [] 6 7 [] 8 9 [] 10      13 [] 16        19 [] 20 21 [] 22 23 [] 24
                                     ㅣ
                                  14 [] 15&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;신규 노드가 추가되면 후위에 있는 관련된 노드들의 LEFT 및 RIGHT 필드의 값이 함께 조정되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Comment.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ManyToOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fetch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FetchType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;LAZY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@JoinColumn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;root_comment_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; rootComment&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ManyToOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fetch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FetchType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;LAZY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@JoinColumn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;parent_comment_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; parentComment&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nullable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; leftNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nullable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; rightNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nullable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; depth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 중략&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateChildComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; childComment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;depth &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        childComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rootComment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rootComment&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        childComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parentComment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        childComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;depth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;depth &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        childComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;leftNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rightNode&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        childComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rightNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rightNode &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;사진을 참고하면 다음과 같은 내용을 알 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;대댓글이 아닌 게시물에 달리는 Root Comment의 경우 무조건 leftNode 및 rightNode는 1과 2로 고정이다.
&lt;ul&gt;
&lt;li&gt;leftNode 및 rightNode는 동일한 댓글 그룹 내에서의 순서 및 계층을 표현하는 값일뿐, 서로 다른 댓글 그룹 사이에서는 독립적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;rightNode - leftNode == 1&lt;/code&gt;이면 하위 자식 Comment가 존재하지 않는다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;rightNode - leftNode != 1&lt;/code&gt;이면 하위 자식 Comment가 존재한다.&lt;/li&gt;
&lt;li&gt;leftNode 및 rightNode는 기존 JPA 스켈레톤 코드의 groupOrder와 일맥상통한다.
&lt;ul&gt;
&lt;li&gt;어떤 특정한 노드에 하위 노드를 삽입하면, 해당 하위 노드의 leftNode는 상위 노드의 rightNode가 되고 rightNode는 상위 노드의 rightNode + 1이 된다.&lt;/li&gt;
&lt;li&gt;부모 Comment 하나만 조회해도 가장 마지막에 삽입된 Comment의 순서 정보(leftNode, rightNode)를 알 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;CustomCommentRepositoryImpl.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;adjustHierarchyOrders&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; newComment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    jpaQueryFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;QCOMMENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;QCOMMENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;leftNode&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;QCOMMENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;leftNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;QCOMMENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;leftNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;goe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRightNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;QCOMMENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rootComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRootComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    jpaQueryFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;QCOMMENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;QCOMMENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rightNode&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;QCOMMENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rightNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;QCOMMENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rightNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;goe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLeftNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;QCOMMENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rootComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRootComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이러한 규칙성을 바탕으로 새롭게 댓글을 삽입했을 때, 같은 그룹 내 댓글의 순서를 재조정하는 것도 어렵지 않습니다. 새롭게 삽입될 newComment의 leftNode 및 rightNode 값을 알았다면 다음 값들을 수정하면 됩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;같은 그룹 내 댓글 중, leftNode 값이 새로 삽입될 댓글의 rightNode보다 크거나 같으면 leftNode의 값을 2씩 증가시킨다.&lt;/li&gt;
&lt;li&gt;같은 그룹 내 댓글들 중, rightNode 값이 새로 삽입될 댓글의 leftNode보다 크거나 같으면 rightNode의 값을 2씩 증가시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;CustomCommentRepositoryImpl.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findCommentsOrderByHierarchy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;selectCommentInnerFetchJoinUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;QCOMMENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orderBy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;QCOMMENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rootComment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token constant&quot;&gt;QCOMMENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;leftNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getOffset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPageSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;또한 댓글을 예시 사진처럼 조회하기 위해서는 &lt;code class=&quot;language-text&quot;&gt;orderBy&lt;/code&gt; 조건을 최상위 Root Comment ID 및 leftNode 값을 각각 오름차순으로 조회하면 됩니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;4. 마치며&lt;/h2&gt;
&lt;p&gt;중첩 세트 모델을 통해 계층형 데이터를 RDB로 쉽게 관리할 수 있습니다. 이번 글에서는 설명하지 않았지만, 특정 부모 댓글의 대댓글을 조회 혹은 삭제하는 쿼리는 아래와 같이 단순합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SQL&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COMMENT&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; ROOT_COMMENT &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ? &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; LFT &lt;span class=&quot;token operator&quot;&gt;BETWEEN&lt;/span&gt; ${부모 &lt;span class=&quot;token keyword&quot;&gt;Comment&lt;/span&gt; lft} &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; ${부모 &lt;span class=&quot;token keyword&quot;&gt;Comment&lt;/span&gt; rgt}&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COMMENT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; IS_DELETED &lt;span class=&quot;token boolean&quot;&gt;TRUE&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; ROOT_COMMENT &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ? &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; LFT &lt;span class=&quot;token operator&quot;&gt;BETWEEN&lt;/span&gt; ${부모 &lt;span class=&quot;token keyword&quot;&gt;Comment&lt;/span&gt; lft} &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; ${부모 &lt;span class=&quot;token keyword&quot;&gt;Comment&lt;/span&gt; rgt}&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그러나 여러 한계점 또한 존재합니다. 빈번하게 새로운 대댓글 노드를 중간에 삽입 혹은 삭제할 때 성능이 탁월하지 않습니다. 삽입 혹은 삭제가 발생할 때마다 같은 그룹 내 다른 노드들의 lft 및 rgt 필드의 값들 또한 함께 수정되기 때문입니다. 아울러 데이터를 추가할 때 lft 및 rgt 범위가 무결성을 위반하지 않도록 신경을 써야 합니다.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/&quot;&gt;Managing Hierarchical Data in MySQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/4048151/what-are-the-options-for-storing-hierarchical-data-in-a-relational-database&quot;&gt;What are the options for storing hierarchical data in a relational database?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ibocon.tistory.com/206&quot;&gt;[Spring] RDB 에서 계층적인 데이터 구조 관리 전략 - Nested set&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[📊 로그 시각화는 왜 중요할까?]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-09-01-log-visualization/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-09-01-log-visualization/</guid><pubDate>Wed, 01 Sep 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;로깅을 통해 개발자는 개발 과정 혹은 개발 후에 발생할 수 있는 예상치 못한 애플리케이션의 문제를 진단할 수 있고, 다양한 정보를 수집할 수 있다. 단순히 로그를 확인한다는 것은 추상적인 행위로 보인다. 기록을 남기는 것이 아닌 구체적으로 어떤 정보를 어떻게 확인하고 관리하는지가 로깅의 핵심이라고 생각한다.&lt;/p&gt;
&lt;p&gt;로그는 시간의 경과에 따라 수집된 데이터의 한 종류로 볼 수 있다. 그러면 데이터 시각화의 장점은 곧 로그 시각화의 장점으로 볼 수 있다. 로그 시각화는 어떤 장점이 있을까?&lt;/p&gt;
&lt;h3&gt;👀 방대한 데이터를 빠르게 확인할 수 있다.&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/49058669/131712577-62733209-5e1a-45ad-a1e1-7b0d8c15967b.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/49058669/131712385-81416d25-bb53-4758-8a14-a614709c24ac.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;한 달간 발생한 에러에 대한 로그를 각각 수치화와 그래프화로 나타낸 것이다. 과연 어떤 것이 더 빠르게 이해하고 많은 정보분석이 가능한가? 일반적으로 그래프로 표현한 아래쪽이 더 빠르고 넓은 범위에 대해 이해하기 쉽다. 원하는 기간을 특정해서 로그를 분석할 때도 시각화를 한다면 훨씬 빠르고 정확하게 이해가 가능하다. 빅데이터의 활용에서 시각화가 강조되는 이유도 여기에 있다.&lt;/p&gt;
&lt;h3&gt;💡 전문 지식이 없어도 쉽게 분석된 데이터를 이해할 수 있다.&lt;/h3&gt;
&lt;p&gt;인간은 감각기관을 통해 획득하는 정보의 80% 이상이 시각을 통해서 얻는 지극히 시각적인 동물이다. 그만큼 시각화의 효과는 크며 개발자 입장에서도 시각화를 해둔 편이 이해가 더 쉽다. 하지만 자칫 잘못된 표현 때문에 &lt;code class=&quot;language-text&quot;&gt;시각에 의해 발생하는 오류&lt;/code&gt;를 범하는 상황이 발생할  수 있으니 유의해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/49058669/131712634-4dc19c0c-20ba-4c88-b402-ed0c2398c9b6.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;위의 예시를 살펴보자. 개발자로서는 어떤 예외로 인해 에러가 발생했는지 이해할 수 있다. 그러나 개발자가 아닌 사람이 해당 데이터를 분석한다면 이해하는데 사전지식이 필요하다. 그러나 아래 예시처럼 시각화해두면 사전지식이 없어도 이해할 수 있다는 장점이 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/49058669/131712385-81416d25-bb53-4758-8a14-a614709c24ac.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;누구나 쉽게 데이터 인사이트를 찾을 수 있다는 것은 &lt;b&gt;데이터 활용범위가 넓어지고 새로운 가치 창출의 범위가 확대&lt;/b&gt;된다고 생각할 수 있다.&lt;/p&gt;
&lt;h3&gt;📝 정확한 데이터 분석 결과를 도출할 수 있다.&lt;/h3&gt;
&lt;p&gt;일반적으로 데이터를 시각화한다면 분석의 용도보다는 남에게 보여주기 위한 용도로 사용된다고 생각하기 쉽다. 하지만 데이터 시각화는 단순히 분석 결과를 전달하기 위한 목적 외에도 정확한 분석을 위한 데이터 탐색 방법으로 활용된다. 예를 들면 평균, 표준편차, 상관관계가 &lt;b&gt;동일한 요약 통계를 가진 데이터셋&lt;/b&gt;을 가지고 산점도로 시각화를 한다면 &lt;b&gt;시각적 패턴이 명확히 다른 것&lt;/b&gt;을 알 수 있다. 아래 &lt;a href=&quot;https://www.autodesk.com/research/publications/same-stats-different-graphs&quot;&gt;이미지&lt;/a&gt;는 1973년 F.J.Anscombe가 개발한 Anscomes’ Quarter 다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/49058669/131712705-3cd3f70b-67e7-4dc7-8cc2-63165cbae08d.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;h3&gt;👍 효과적인 데이터 인사이트 공유로 의사결정을 진행할 수 있다.&lt;/h3&gt;
&lt;p&gt;보고서를 제출하거나 발표를 할 때 설명과 함께 전달되는 문서의 시각화 차트는 메시지 전달을 효과적으로 뒷받침하는 근거 자료가 된다. 듣는이가 쉽게 이해할 수 있도록 도와주고, 공감을 얻을 수 있다.&lt;/p&gt;
&lt;p&gt;다양한 로그 데이터를 한곳에서 살펴보려면 대시보드를 활용할 수 있다. 대시보드는 다수의 조직 구성원이 대시보드를 공유하며 각기 다른 관점에서 데이터를 탐색하고 인사이트를 도출할 수 있다. 시각화 차트로 구성된다는 것은 데이터 분석 기술을 가진 개발자 이외의 사람도 쉽게 데이터 활용을 할 수 있게 한다. 각자 이해한 내용을 바탕으로 데이터를 근거로 한 중요한 의사결정으로 이어질 수 있다.&lt;/p&gt;
&lt;h3&gt;✍️ 다양한 분야에 데이터를 활용할 수 있다.&lt;/h3&gt;
&lt;p&gt;특히 로그 시각화는 기술적인 로그뿐만 아니라 사용자 정보 로그에서도 그 진가를 발휘한다. 이를테면 단순 문자열로만 저장되면 쉽게 인지하지 못하는 부분들(프로덕트 사용자들의 실시간 관심사)을 보고 빠르게 의사결정을 진행함으로써, 사용자에게 더 많은 관심사 노출을 유도할 수 있다.&lt;/p&gt;
&lt;p&gt;이런 다양한 장점이 존재하기 때문에 로그 시각화를 도입하는 것은 큰 도움이 된다고 생각한다. 그렇다면 로그 시각화는 어떻게 하는 것일까?&lt;/p&gt;
&lt;h3&gt;로그 시각화 도구&lt;/h3&gt;
&lt;p&gt;많은 양의 로그를 처리하기 위해서는 &lt;code class=&quot;language-text&quot;&gt;중앙형 집중 로깅 방식&lt;/code&gt;을 통해 로그를 저장한다. 이 과정에서 주로 Kibana 혹은 Grafana 가 사용된다. 두 시각화 도구 다 Elasticsearch 를 통해 시각화를 사용할 수 있으니 간단하게 Elasticsearch 가 어떤 것인지부터 알아본다.&lt;/p&gt;
&lt;h4&gt;Elasticsearch (로그 저장 및 검색)&lt;/h4&gt;
&lt;p&gt;텍스트, 숫자, 위치 기반 정보, 정형 및 비정형 데이터 등 모든 유형의 데이터를 위한 무료 검색 및 분석 엔진이다. 분산형과 개방형을 특징으로 한다. Elastic Stack 의 핵심 구성 요소다. Elasticsearch 로 흘러들어온 원시 데이터를 서로 관련 있는 문서끼리 색인하여 복잡한 쿼리에 대해 요약 &lt;b&gt;검색&lt;/b&gt; 할 수 있도록 제공한다. 관계형 데이터베이스가 document 중심이라면 Elasticsearch 는 텍스트 중심이라고 볼 수 있다. 관계형 데이터베이스를 베이스로 검색에 필요한 부분을 Elasticsearch 로 진행하는 것이 보통이다.&lt;/p&gt;
&lt;h4&gt;Kibana&lt;/h4&gt;
&lt;p&gt;세계에서 가장 인기 있는 오픈 소스 로그 분석 플랫폼인 Elastic 에서 제공하는 ELK(Elasticsearch Logstash Kibana) 의 K(Kibana)다. 데이터 쿼리 및 분석이 핵심이다. 다양한 방법을 사용해 근본 원인 분석 및 진단을 위해 데이터 내의 특정 이벤트 또는 문자열에 대해 Elasticsearch 에서 인덱싱된 데이터 검색을 제공한다. 이러한 쿼리를 기반으로 차트, 테이블, 지표 등의 다양한 시각화 방식을 통해 데이터를 시각화 할 수 있다. 문제 해결, 포렌식, 개발, 보안 등의 지원은 Kibana 만 제공한다.&lt;/p&gt;
&lt;h4&gt;Grafana&lt;/h4&gt;
&lt;p&gt;다양한 데이터 저장소에서 사용할 수 있는 무료 오픈 소스 시각화 도구다. 일반적으로 Graphite, InfluxDB, Prometheuse, Elasticsearch 와 함께 사용된다. Kibana 가 제공하지 않았던 메트릭에 대한 지원을 제공하려고 했기 때문에 CPU Memory, Disk Input/Output, 사용률 등의 메트릭스 지표를 시각화하는데 특화되어 있다.&lt;/p&gt;
&lt;p&gt;위의 두 시각화 도구는 사용 목적에 따라 차이점이 생긴다. 따라서 본인이 시각화하고자 하는 로그에 대해 정의를 내리고, 그것에 가장 적합한 시각화 도구를 적용하는 것이 좋다.&lt;/p&gt;
&lt;h3&gt;결론&lt;/h3&gt;
&lt;p&gt;개발자는 로깅과 언제나 함께한다. 그렇다면 로깅을 분석하고 활용해 나가는 것은 개발자의 숙제라고 생각한다. &lt;code class=&quot;language-text&quot;&gt;아는 것이 힘&lt;/code&gt;이라는 말이 있듯이 정확한 정보를 빠르게 알아가는 것은 더욱이 21세기에 매우 중요하다. 수집한 로그를 분석하고 나아가서 시각화까지 하는 연습을 하는 것은 분명 개발자와 주변 사람들에게 큰 도움이 되리라 생각한다.&lt;/p&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.openads.co.kr/content/contentDetail?contsId=2235&quot;&gt;당신도 데이터 시각화를 해야 하는 이유 5가지&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://42place.innovationacademy.kr/archives/978&quot;&gt;로그가 뭔지 알아? ELK로 만들어보다.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://logz.io/blog/grafana-vs-kibana/&quot;&gt;Grafana vs. Kibana: The Key Differences to Know&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Redis 알아보기]]></title><description><![CDATA[이글은 Redis에 대해 들어봤거나 어렴풋이 알고 있는 독자들을 대상으로 작성하였습니다. 우테코 레벨 3 에서 프로젝트를 진행하면서 Redis를 적용해보게 되었다. Redis…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-09-01-redis/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-09-01-redis/</guid><pubDate>Wed, 01 Sep 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이글은 Redis에 대해 들어봤거나 어렴풋이 알고 있는 독자들을 대상으로 작성하였습니다.&lt;/p&gt;
&lt;p&gt;우테코 레벨 3 에서 프로젝트를 진행하면서 Redis를 적용해보게 되었다. Redis라는 단어를 많이 들어봤고 어렴풋이 캐싱을 통하여 속도를 빠르게 해준다는 것을 들어봤었다. 하지만 어떻게 쓰고 왜 속도가 빠른지에 대해서는 알지 못했었다. 이번 글을 통하여 Redis에 대해 좀 더 깊게 알아보자. Redis를 알기 전에 캐시의 개념을 먼저 알아야 한다.&lt;/p&gt;
&lt;h2&gt;캐시는 무엇일까?&lt;/h2&gt;
&lt;p&gt;캐시라는 것이 무엇일까? 개발하면서 무언가 수정을 하고 다시 새로 고침을 눌렀을 때 수정한 부분이 전혀 반영되지 않아서 당황한 경험이 있을 것이다. 이때 우리는 강력 새로 고침을 통해 캐시를 비워주곤 했다. 이처럼 캐시는 요청된 결과를 미리 저장해두어서 빠르게 데이터를 전달하기 위해서 사용한다. CPU에만 캐시가 있다고 오해를 할 수 있는데 캐시는 좀 더 빠른 속도를 위하여 다른 저장공간에 캐싱해둔다면 모두 캐시라고 할 수 있다. 예를 들면 웹 서버에서 빠르게 정보를 로딩하기 위해 DB가 아닌 다른 장소(ex. Browser Caches)에 저장하는 것도 캐시이다.&lt;/p&gt;
&lt;p&gt;웹 서비스의 구조를 간략하게 보면 아래와 같을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63634505/131674382-eeee191f-70a9-47a2-ba10-22650a700970.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;DB에는 수많은 데이터가 저장되어 있고 이러한 것들은 클라이언트가 요청하면 꺼내 클라이언트에게 전달해준다. 이때 수많은 클라이언트의 요청으로 데이터를 전달하는데 많은 시간이 걸려 클라이언트가 불만을 표출 했다고 생각해보자. 문제를 해결하기 위해 조사를 해보니 클라이언트가 요청하는 데이터의 80%는 DB 데이터의 일부인 20% 만 차지할 뿐 이였다. 그래서 이를 해결하기 위해 캐시를 도입하였다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63634505/131674277-f0f6036e-7115-423e-855f-96284e2abc25.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;이제 웹서버는 DB에 데이터를 확인하기 전에 캐시를 먼저 조회하여 있다면 캐시에서 정보를 가져오게 된다. 캐시는 일반적으로 조회속도가 DB에서 조회하는 것보다 빠르기 때문에 캐시에 데이터가 있다면 기존의 속도 보다 더 빠르게 요청을 처리할 수 있다.&lt;/p&gt;
&lt;h2&gt;프로젝트에서 캐시 도입 배경&lt;/h2&gt;
&lt;p&gt;레벨 3 기간동안 진행한 프로젝트는 여러 사람의 만나기 좋은 중간지점을 찾아주는 서비스였다. 이때 각 사용자마다 위치정보를 통해 중간값을 찾은 후 중간 위치의 근처의 역정보들을 활용하여 해당 역까지 걸리는 교통정보를 활용하여 모든 사람에게 공평한 위치를 찾아주는 로직을 구현하였다. 서울에는 수많은 역이 있다 보니 로직을 수행하는 데 많은 시간이 걸렸고 처음 로직을 완성 하였을 때 1분 이상의 시간이 걸렸다. 사용자 입장에서는 단순히 중간 지점을 찾기 위해 서비스를 이용하고 있는데 이런 서비스가 1분 이상이 걸린다면 우리의 서비스를 이용할 사람은 아무도 없다는 생각이 들었다. 그래서 우리 팀은 캐시를 통해 문제를 해결해보기로 하였다.&lt;/p&gt;
&lt;h2&gt;Redis란?&lt;/h2&gt;
&lt;p&gt;Redis는 Remote Dictionary Server의 약자다. “key-value” 구조로 데이터를 저장하고 관리하기 위한 비 관계형 데이터베이스로 모든 데이터를 메모리에서 처리하는 메모리 기반 DB이다. 메모리를 통해 사용하기 때문에 일반적인 DB보다 빠른 성능을 보여준다.&lt;/p&gt;
&lt;h3&gt;Redis의 적용&lt;/h3&gt;
&lt;p&gt;프로젝트에서 경로를 찾아올 때 캐시에서 이야기했던 것처럼 Redis에서 존재하면 그 값을 반환하였고 아니라면 로직을 처리하고 그 결과를 Redis에 저장하였다. 아래는 실제 Redis를 적용한 코드이다. Redis에 먼저 조회를 하여 데이터가 없으면 로직을 계산하고 Redis에 데이터를 저장하였다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PathTransferResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calculatedPathTransferResults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Points&lt;/span&gt; points&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;UtilityResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Point&lt;/span&gt; target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; points&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPoints&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parallelStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;source&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PathTransferResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                source&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                pathResultRedisRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orElseGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;saveRedisCachePathResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;source&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPlaceName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Collectors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PathResult&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;saveRedisCachePathResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Point&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Point&lt;/span&gt; target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; placeName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;PathResult&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;minPathResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;source&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; placeName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        pathResultRedisRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Redis는 마치 JPA를 사용하는 것처럼 사용할 수 있었다. 아래는 RedisRepository를 나타낸다. 인터페이스를 구현하는 클래스를 만들지 않아도 &lt;code class=&quot;language-text&quot;&gt;pathResultRedisRepository.save(result)&lt;/code&gt; 와 같이 사용할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PathResultRedisRepository&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CrudRepository&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PathResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이번에는 간단하게 Redis를 사용하기 위해 Bean으로 등록하는 코드를 살펴보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableRedisRepositories&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RedisConfig&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;${redis.host}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; redisHost&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;${redis.port}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; redisPort&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RedisConnectionFactory&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;redisConnectionFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LettuceConnectionFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;redisHost&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; redisPort&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RedisTemplate&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;redisTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;RedisTemplate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; redisTemplate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RedisTemplate&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        redisTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setConnectionFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;redisConnectionFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; redisTemplate&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Redis의 데이터 구조&lt;/h2&gt;
&lt;p&gt;Redis는 관계형 데이터베이스가 아니다. INT, DATETIME 등 일반적으로 관계형 데이터베이스에서 사용할 수 있었던 데이터 타입은 사용할 수 없다. 오직 String, Hash, List, Set 타입 만을 사용할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;문자열(String)&lt;/li&gt;
&lt;li&gt;해시(Hash)&lt;/li&gt;
&lt;li&gt;리스트(List)&lt;/li&gt;
&lt;li&gt;셋(Set)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Redis 스키마&lt;/h2&gt;
&lt;p&gt;Redis는 관계형 데이터베이스가 아닌 key-value를 활용한 비 관계형 데이터베이스이다. 따라서 일반적인 데이터베이스와의 스키마 구조와는 다르다. &lt;code class=&quot;language-text&quot;&gt;:&lt;/code&gt; 을 이용하여 데이터를 구분하여 스키마를 구성한다.&lt;/p&gt;
&lt;p&gt;사용자의 이메일, 닉네임, 최근 접속 시간을 저장한다고 생각해보자. ”&lt;a href=&quot;mailto:redis@redis.com&quot;&gt;redis@redis.com&lt;/a&gt;”, “redis”, “Sep-02-2021 22:30:10” 라는 데이터를 저장하려고 한다면 아래와 같이 저장하면 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;user:userid:email:&lt;a href=&quot;mailto:redis@redis.com&quot;&gt;redis@redis.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;user:userid:nickname:redis&lt;/li&gt;
&lt;li&gt;user:userid:lastlogin:Sep-02-2021 22:30:10&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위와 같은 스키마로 구성하여 사용자의 아이디만 알면 이메일, 닉네임 등을 알 수 있다.&lt;/p&gt;
&lt;h2&gt;Redis와 Memcached&lt;/h2&gt;
&lt;p&gt;결론부터 말하면 우리팀은 Redis를 통하여 문제를 해결하였다. 1분 이상 걸리던 것을 10초대로 줄일 수 있었고 다음에 멀티 스레드를 통하여 시간을 더욱더 단축할 수 있었다. 그렇다면 왜 Redis를 사용하였을까? 우리가 선택할 수 있는 선택지들중 대표적인 Memcached와 Redis를 비교해보자.&lt;/p&gt;
&lt;h3&gt;Redis&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;“key-value” 구조의 인 메모리 비 관계형 데이터베이스&lt;/li&gt;
&lt;li&gt;메모리와 디스크에 저장되어 영속성을 지원한다&lt;/li&gt;
&lt;li&gt;쓰기 성능 증대를 위한 샤딩을 지원&lt;/li&gt;
&lt;li&gt;String, Set, Sorted Set, Hash, List의 데이터 형식을 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Memcached&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;데이터가 메모리에만 저장된다. 데이터는 프로세스가 종료되면 사라진다&lt;/li&gt;
&lt;li&gt;문자열만 지원&lt;/li&gt;
&lt;li&gt;만료일을 지정하여 만료되면 데이터가 사라짐&lt;/li&gt;
&lt;li&gt;처리속도가 빠르다&lt;/li&gt;
&lt;li&gt;저장소 메모리 재사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;사실 두 가지의 특징을 비교 하였을 때 우리 서비스에서 꼭 Redis를 사용해야한다 라고 단정 지을만한 이유는 없었다. 하지만 아래와 같은 차이점으로 우리 팀은 Redis를 선택하였다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Memcached 보다 훨씬 많은 관련 문서&lt;/li&gt;
&lt;li&gt;영속성을 줄 수 있다&lt;/li&gt;
&lt;li&gt;Replica 를 통한 master, slave구조를 구현 가능&lt;/li&gt;
&lt;li&gt;다양한 자료타입&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;아직 서비스의 이용자가 많지 않은 시점에서는 당장 필요하다고는 할 수 없다. 하지만 우리의 서비스 이용자가 계속 늘어난다면 위와 같은 사항들은 많은 도움이 될 것이라 판단하였다.&lt;/p&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;이번 글은 프로젝트를 진행하면서 캐시에 대한 필요성을 느꼈고 Redis를 통하여 문제를 해결하는 것을 적었다. Redis는 JPA를 사용하는 것과 매우 비슷한 느낌을 받았고 매우 쉽게 적용을 할 수 있다는 느낌을 받았다. 독자 여러분들도 무엇인가 데이터를 더 빠르게 반환하고 싶다면 Redis를 한번 고려해보면 좋을 것 같다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://brunch.co.kr/@skykamja24/575&quot;&gt;https://brunch.co.kr/@skykamja24/575&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jyejye9201.medium.com/%EB%A0%88%EB%94%94%EC%8A%A4-redis-%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-2b7af75fa818&quot;&gt;https://jyejye9201.medium.com/레디스-redis-란-무엇인가-2b7af75fa818&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chrisjune-13837.medium.com/redis-vs-memcached-10e796ddd717&quot;&gt;https://chrisjune-13837.medium.com/redis-vs-memcached-10e796ddd717&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[JVM에 관하여 - Part 4, Garbage Collection 기초]]></title><description><![CDATA[Java 로 작성된 코드는 어떻게 돌아가는 걸까? 해당 물음에 답을 찾기 위한 JVM 시리즈 4편, JVM 의 구성요소 중 GC 에 관한 글입니다.
이번 글에서는 JVM 의 GC 에 대한 기초 지식에 대해서 알아봅시다. JVM…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-30-jvm-gc/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-30-jvm-gc/</guid><pubDate>Mon, 30 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Java&lt;/em&gt; 로 작성된 코드는 어떻게 돌아가는 걸까? 해당 물음에 답을 찾기 위한 &lt;em&gt;JVM&lt;/em&gt; 시리즈 4편, &lt;em&gt;JVM&lt;/em&gt; 의 구성요소 중 &lt;em&gt;GC&lt;/em&gt; 에 관한 글입니다.
이번 글에서는 &lt;em&gt;JVM&lt;/em&gt; 의 &lt;em&gt;GC&lt;/em&gt; 에 대한 기초 지식에 대해서 알아봅시다.&lt;/p&gt;
&lt;!-- end --&gt;
&lt;h2&gt;&lt;em&gt;JVM&lt;/em&gt; 에서의 메모리 관리&lt;/h2&gt;
&lt;p&gt;지난 글에서는 &lt;em&gt;JVM&lt;/em&gt; 에서의 메모리가 어떻게 구성되는지 알아보았습니다.
&lt;em&gt;JVM&lt;/em&gt; 안에서 모두 공유되는 영역인 &lt;em&gt;Heap&lt;/em&gt; 부터 각각의 메소드의 작업별로 할당되는 &lt;em&gt;Stack&lt;/em&gt; 까지 다양한 영역이 존재합니다.
프로그램을 실행하며 메소드에서 작업 진행 도중 새로운 데이터들이 메모리에 추가될 가능성이 높습니다.
그렇다면 &lt;em&gt;Java&lt;/em&gt; 로 구성된 프로그램을 계속 작동시켜 놓으면 메모리가 계속 증가만 할까요?
이 문제점에 해답을 주는 것이 바로 &lt;em&gt;Garbage Collection&lt;/em&gt; , &lt;em&gt;GC&lt;/em&gt; 입니다.&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;Garbage Collection&lt;/em&gt; 및 &lt;em&gt;Garbage Collection&lt;/em&gt; 의 대상&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Garbage Collection&lt;/em&gt; , &lt;em&gt;GC&lt;/em&gt; 는 &lt;em&gt;JVM&lt;/em&gt; 상에서 더 이상 사용되지 않는 데이터가 할당되어있는 메모리를 해제시켜주는 장치입니다.
&lt;em&gt;JVM&lt;/em&gt; 에서 자동으로 동작하기 때문에 &lt;em&gt;Java&lt;/em&gt; 는 특별한 경우가 아니면 메모리 관리를 개발자가 직접 해줄 필요가 없습니다.
&lt;em&gt;GC&lt;/em&gt; 가 주로 동작하는 대상은 &lt;em&gt;Heap&lt;/em&gt; 영역 내의 객체 중에서 참조되지 않은 데이터입니다.
예시로 코드와 함께 학습해보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Main&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt; person &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;곧 참조되지 않음&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        person &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;b&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;참조가 유지됨&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//GC 발생 가정 시점&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;//Person은 &quot;name&quot;과 &quot;description&quot;을 필드로 가지며, 생성자에서 차례대로 주입받게 됨.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위에서 person은 처음에 “a” 라는 이름을 가진 객체를 참조하게 됩니다.
하지만 곧 이어 person이 “b” 를 참조하게 되며 “a”는 다음 줄에서 참조를 잃어버리게 됩니다.
&lt;em&gt;main&lt;/em&gt; 함수가 종료되기 직전, person이 가르키는 객체가 “b”를 가르킨 직후에 &lt;em&gt;GC&lt;/em&gt; 가 이루어진다고 가정해봅시다.
“b” 는 참조가 유지된 상태이기 때문에 &lt;em&gt;GC&lt;/em&gt; 의 대상이 아니지만, “a” 는 &lt;em&gt;GC&lt;/em&gt; 의 대상이 됩니다.
위의 경우처럼 참조하는 대상을 바꾸거나 메소드가 끝나서 &lt;em&gt;Stack&lt;/em&gt; 이 &lt;em&gt;pop&lt;/em&gt; 이 되면 참조되지 않는 객체들이 생겨납니다. &lt;br&gt;
참조되고 있는지에 대한 개념을 &lt;em&gt;reachability&lt;/em&gt; 라고 하고, 유효한 참조를 &lt;em&gt;reachable&lt;/em&gt; , 참조되지 않으면 &lt;em&gt;unreachable&lt;/em&gt; 이라고 합니다.
그리고 &lt;em&gt;GC&lt;/em&gt; 는 &lt;em&gt;unreachable&lt;/em&gt; 한 객체들을 &lt;em&gt;garbage&lt;/em&gt; 라고 인식하게 됩니다.
다음은 &lt;em&gt;Heap&lt;/em&gt; 영역에서 &lt;em&gt;reachable&lt;/em&gt; , &lt;em&gt;unreachable&lt;/em&gt; 한 개념들을 도식화하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1234px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b8df49672801f142e8f81175a1bf8169/649dd/2021-08-30-jvm-reachability.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 59.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAA7DAAAOwwHHb6hkAAACQ0lEQVR42o1T2W7aQBTl/7+oiqq8RGlaICGYzWBsjI333cYGs+T03OGhfalUS6NhhuN7z3I9uFyvqJojmrbDsTvhyL05tgjjDFGUIM1yFGWNJM2QZQUi7jnPx4749oGvmxbd6Qx5BllR4nTuUdYNoiTlizna7qwKOq4Hfb2FNl9hOlvA3Nk4+CEbdigr4mPi2UTel6bn/oJBlORI8xJ+mGCztbC1dsiKCmH0ALteiKVuQDcsdRZcXtTw/AhrwyR+z2INvCBGrwqSiWE57O5g/DnjmsN2fRimA2vvweR/k+kC2kKH7fjYmHt1v97uMHyf4kNbYud4LOzier2J5IoM6Q2ZPr2ZcP1EnXPeZ2S+5cvLjQmdTAJKVPe0SVS9fh4QJoXCRUmG/kKGMT0LCZyttnh7X6nfLcORkDpKSCwb2s8RvNUaDX3rzmfln6j4mFsIGFxH/IEWXC5XDKSrCoT763iNPYG3+x243XCtamyGOmJasfwxQ08W+PpCT2li0VTTWChW6Tr0WjEU82uOjXRdiTSmWnMcLjw7owms6RLdfA1jsoA7nqr79tSrMF5+aSq0Owm4XvRHsiTkk7qw9dlAZKgVxtjbLizbgc3d48gI7sCkPf73PNzBsEOlThgqyTIGLvWnHAkp/vdKaLaMkoyHzuURmzKQsqlRHRsUVUUcAywreho8Cnanx1D/a8kQN+rrkb2ltARPzwt8+z7DbOmSSMbBX8DeO48vBf/7MAx5xMPs9R3pywjt7kC/GwRBwM8yVwV/A6TZgDg5oVOHAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;JVM reachability 구조&quot;
        title=&quot;&quot;
        src=&quot;/static/b8df49672801f142e8f81175a1bf8169/649dd/2021-08-30-jvm-reachability.png&quot;
        srcset=&quot;/static/b8df49672801f142e8f81175a1bf8169/0eb09/2021-08-30-jvm-reachability.png 500w,
/static/b8df49672801f142e8f81175a1bf8169/1263b/2021-08-30-jvm-reachability.png 1000w,
/static/b8df49672801f142e8f81175a1bf8169/649dd/2021-08-30-jvm-reachability.png 1234w&quot;
        sizes=&quot;(max-width: 1234px) 100vw, 1234px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;그림에서 보았던 것처럼 &lt;em&gt;Heap&lt;/em&gt; 영역 내부의 객체들은 &lt;em&gt;Method Area&lt;/em&gt; , &lt;em&gt;Stack&lt;/em&gt; , &lt;em&gt;Native Stack&lt;/em&gt; 에서 참조되면 &lt;em&gt;reachable&lt;/em&gt; 로 판정합니다.
이렇게 &lt;em&gt;reachable&lt;/em&gt; 로 인식되게 만들어주는 &lt;em&gt;JVM Runtime Area&lt;/em&gt; 들을 &lt;em&gt;root set&lt;/em&gt; 이라고 합니다.
또한 &lt;em&gt;reachable&lt;/em&gt; 이 참조하고 있는 다른 객체 역시 &lt;em&gt;reachable&lt;/em&gt; 이 됩니다.
하지만 &lt;em&gt;root set&lt;/em&gt; 의해 참조되고 있지 않은 객체들은 &lt;em&gt;unreachable&lt;/em&gt; 로 판정이 되어, &lt;em&gt;GC&lt;/em&gt; 의 대상이 됩니다.
예시 코드에서는 “a” 가 &lt;em&gt;unreachable&lt;/em&gt; , “b” 가 &lt;em&gt;reachable&lt;/em&gt; 상태입니다.
그래서 &lt;em&gt;unreachable&lt;/em&gt; 상태인 “a” 를 상대로 &lt;em&gt;GC&lt;/em&gt; 가 이루어집니다.&lt;/p&gt;
&lt;h2&gt;Stop-The-World&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;GC&lt;/em&gt; 실행 알고리즘을 공부하기 전에 추가로 알아야 할 개념이 있습니다.
바로 &lt;em&gt;Stop-The-World&lt;/em&gt; 라는 개념입니다.
&lt;em&gt;JVM&lt;/em&gt; 은 &lt;em&gt;GC&lt;/em&gt; 를 통해 &lt;em&gt;JVM&lt;/em&gt; 에서의 여유 메모리를 확보할 수가 있습니다.
이 때문에 &lt;em&gt;GC&lt;/em&gt; 를 자주 실행시키면, 여유 메모리를 최대한 확보하여 성능이 좋아지리라 추측할 수도 있습니다.
하지만 빈번한 &lt;em&gt;GC&lt;/em&gt; 는 프로그램의 성능을 저하하게 됩니다.
&lt;em&gt;GC&lt;/em&gt; 가 일어나면 &lt;em&gt;GC&lt;/em&gt; 를 담당하는 쓰레드를 제외한 모든 쓰레드들은 작동이 일시적으로 정지되게 됩니다.
이를 &lt;em&gt;Stop-The-World&lt;/em&gt; 현상이라고 합니다.
모든 쓰레드가 정지되기 때문에 더 이상 작업이 실행되지 않고, 성능이 저하됩니다.
그래서 적절한 빈도의 &lt;em&gt;GC&lt;/em&gt; 가 실행되도록 하여, &lt;em&gt;Stop-The-World&lt;/em&gt; 시간을 줄여 쓰레드가 정지되는 시간을 줄이는 것이 중요합니다.&lt;/p&gt;
&lt;h2&gt;Algorithm&lt;/h2&gt;
&lt;h3&gt;&lt;em&gt;Weak Generational Hypothesis&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;GC&lt;/em&gt; 를 성공적으로 수행하는 &lt;em&gt;Algorithm&lt;/em&gt; 을 설계하기 위해서는 몇 가지 가설이 필요합니다.
그 중 대표적인 것이 &lt;em&gt;Weak Generational Hypothesis&lt;/em&gt; 입니다.
이 가설은 대부분의 객체는 빠르게 &lt;em&gt;unreachable&lt;/em&gt; 한 상태로 전환이 된다고 보고 있습니다.
또한, 저번 시간에 보았던 &lt;em&gt;Heap&lt;/em&gt; 의 메모리 영역 기준으로 오래된 영역에서 최신 영역으로의 참조 방향은 적게 존재한다고 가정합니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 548px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b1a4301290039e7cba3fa0afe9d34b39/9079b/2021-08-30-jvm-heap.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 115.19999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAYAAAALHW+jAAAACXBIWXMAAA7DAAAOwwHHb6hkAAADMUlEQVR42q1VaW/aQBDN//8xVVVV6pGkrdJUSlJ6QLGNb2Mbgw3G2AZMuF5nBkwhROqXrPTkYXd2d+bNm+UCLzwuttutGJO8RBAl6A2GJ+gnKZJRJni6xgj7CebV4t+BtaFbXWSTEmU5Q15OMSnIns4Q9Qfo6AYc10OW58hpPi+m4jOdzjGkiyw3kDM4uIudAZncbDaHm+rIy7KEZZnwPPdkvR6LxSPs4wPrjfFoDNfvoRv2YTk+WoqOh8ZvQeNnS3D3/af8brY7MJ2u+PIezqYO7HBgQakKZ/EIQa8Pw/bQVnXcPfzA+4/XePfhiuwG/igdGJYLnw5j34j2cJRnHBp0Y5rlchtjNl9glGbEYYxBPBT4YSTFm84q4bEgHhPKzPF65xyalOZyucR6vcaKwCObTKAoKmzHgU6FMQwTVVWBc1qvN9gSp7P5/LQodcrRICE+IgThQPhRNJPS04nLDlrEmXz3dlszYNpd+L0YbjeSzE4i5LFarUVPFfExnc0RJ0M4jotms4Xb22+4+XqL32RbtkPpJyIp9mUcV/8QIevQ7fYkQp8Qksi7QZ8KFMOjyAVBhJAy4QZgH16z3VD8nnC4FQ7rm+pLsiyDqiqiQdM04LnuszrkvWccOhQdV8vf67DNHKqG6PGPuoOimiQlQ8A8c5QcIUvn7MCSeIv2OuRDddNB41cLnz7f4PWbt3j1+g0ur7+QsH9B0y1KM9rrcETqWB1zuDNMKn08HGM0nmA8Kagoqeiuz/qjzaw3XhvQIek4l8oyIlr3nuXwSS9PSIPNZhNBEEDTNOJSJU0qAtbifznkKtrEId9me4G0nmY4UCm9DqWvGTaUjiXQqfVYzF3i0CIOOeozHbLyq2qnq7woEPZ6ULUO7u+/4/LqGh8vqZfvH6gwGkmLHgSiRXRIe+qgTnQ4IM5GR9xwzzKX+eF9pN6l94/tjNeyglqzwJD6nX0Pr40Y/HzRgcc38eB3kPnTdZ2+xGG7jXGanmiVO4xf87OU42EqkWV5sY8ulzmf0vMD4tYPxY6T0e5FIh+OkCufEs45pApXjzsOF4fvEo/LGisBz7Gf+Oy/J7380v96fwGxBs20wS/79QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;JVM reachability 구조&quot;
        title=&quot;&quot;
        src=&quot;/static/b1a4301290039e7cba3fa0afe9d34b39/9079b/2021-08-30-jvm-heap.png&quot;
        srcset=&quot;/static/b1a4301290039e7cba3fa0afe9d34b39/0eb09/2021-08-30-jvm-heap.png 500w,
/static/b1a4301290039e7cba3fa0afe9d34b39/9079b/2021-08-30-jvm-heap.png 548w&quot;
        sizes=&quot;(max-width: 548px) 100vw, 548px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;실제로 위의 가설의 경우 여러 관찰을 통해서 높은 경향성을 가지는 것으로 증명이 되었습니다.
&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/generations.html&quot;&gt;해당 링크&lt;/a&gt; 는 &lt;em&gt;Oracle&lt;/em&gt; 사의 &lt;em&gt;Java 8&lt;/em&gt; 에서 관찰 자료입니다.
대부분의 객체가 빠르게 소멸하는 것을 알 수가 있습니다.
이 가설을 바탕으로 하여, 가장 기본적인 알고리즘 두 개에 대해서 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h3&gt;Mark And Sweep Algorithm&lt;/h3&gt;
&lt;p&gt;가장 기본적인 알고리즘입니다. &lt;em&gt;root set&lt;/em&gt; 으로부터 출발하여, 참조되는 객체들에 대해서 마크를 하게 됩니다.
이 단계를 &lt;em&gt;Mark Phase&lt;/em&gt; 라고 합니다. 이후에는 마크되지 않은 객체들을 추적하여 삭제를 합니다.
삭제하는 단계를 &lt;em&gt;Sweep Phase&lt;/em&gt; 라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1270px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ba1ff3a6b48c50c915488b3b4985ed20/6b882/2021-08-30-jvm-mark-and-sweep.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACMklEQVR42p2S3U/TYBTG9297qTHxwhsvuTARMoNB8UJjJAESBQkakMC+VwZjW7t2XT/Xdv0a3c/TMbnXN3lyvt4+b895TiWMYizHw/MDXMfHd2f4nkBygVjX9sjznCTNsKXulvcEsyB8RCDwZwFxklIxTIdao8tVs4t53Ue/uMISP2j3GFzUUC+bxHLZEZKOcstlvY2uTyiyhDyZk8cRWRITR5GQhlRMy6XVuaYhcIcak1oDp9VleqfSbrTpdW8Iwvmqg+ubAfVmh6llw3IB9znaWEdRehimhScPVxxpcagaqLqJqU2wVB1bMzDEH0luILV5nBBGc9TxZIWp7TBPEyLJ6xMb3bAwLY9yfJXlcsmSh1OIX4gt4zL/cB5sGRdFsfKt6YyTI4VfJ9KVE+K6Lv3+LZG0XeFfzvqRTIRyTi9xz+rEMjJbCPXxmDiWP2R0xvn+AVsHE8yjHbLj13w6VPh62KT//TOb+zpOkFMsEs7rA97sqVwpGpZnclibsnus0+qN2flmcGeUhJ09DqpVnm8PGe2+JP74hI3t32y+O6X5YYNn1T4TJ6XII/ZPWjzd6vHtZ4e7XpPqlw6v3iv8OOvw4m2X+u1MCGPZwekU1UpIbY3CHmBYoQw5YG4bjCSfLwrp9h4/iBlL7AWCMJVahmalIkbC0AhFpPz/ZpjOY0ZdBU3pyo76stiyo+0mvu8JYanyCusP1v7f+FHsteBlXNwvyEKPNHBZZDKOYilkMxEl4Q8cdzUrf0hmQwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;GC Mark and Sweep&quot;
        title=&quot;&quot;
        src=&quot;/static/ba1ff3a6b48c50c915488b3b4985ed20/6b882/2021-08-30-jvm-mark-and-sweep.png&quot;
        srcset=&quot;/static/ba1ff3a6b48c50c915488b3b4985ed20/0eb09/2021-08-30-jvm-mark-and-sweep.png 500w,
/static/ba1ff3a6b48c50c915488b3b4985ed20/1263b/2021-08-30-jvm-mark-and-sweep.png 1000w,
/static/ba1ff3a6b48c50c915488b3b4985ed20/6b882/2021-08-30-jvm-mark-and-sweep.png 1270w&quot;
        sizes=&quot;(max-width: 1270px) 100vw, 1270px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;다음과 같이 메모리를 해제하는 것을 알 수가 있습니다. 하지만 그림에서도 볼 수 있다시피 메모리가 &lt;em&gt;Fragmentation&lt;/em&gt;, 단편화되는 단점이 있습니다.
메모리에서의 단편화는 정렬되지 않은 조각으로 나뉘어져, 절대적인 크기는 충분하지만 추가적으로 메모리 할당이 되기 힘든 상태를 의미합니다.
이를 해결하기 위한 것이 바로 &lt;em&gt;Mark And Compact Algorithm&lt;/em&gt; 입니다.&lt;/p&gt;
&lt;h3&gt;Mark And Compact Algorithm&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Mark And Sweep Algorithm&lt;/em&gt; 처럼 참조되는 객체들에 대해서 마크를 하고, 참조되지 않으면 삭제를 합니다.
이후에 메모리를 정리하여, 메모리 단편화를 해결할 수 있도록 합니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/36fc84790098c4d7cc25ec9b05912eef/ce661/2021-08-30-jvm-mark-and-compact.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 131.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAENElEQVR42q2VCW8TVxSF+c8VrVqpYgkhFIGgNFQVJYIEhyy205AQWsoSmsQ2NpBAvIw9432Jd49tHLA9tj/uTAIObUjUqk86eiNfz3nnnnvnvhP8z+sEgwFG+g3tiIs3oTyeoI47WCcTDtPXNmhrHgbaOtmwgitQl3iNzVCRnsSKET8bwSavA1m6OWWPcDDo8+7Jderz3zEx4+P8dJRz03HW52boOL+h7jyF4TyJe97GWVuSsWmN63e3aEtsa/4mp6ez/Dq5xtsN25Dw/doEjcVz2BY2ueJIcNmRwrO4QHf5DI3lCxjLp/At2rlkT3PVEWfC6acjse17Ni46dpia89Dy2vcJ+6Lw8Tj63LfcnPEyKgpHbHHWZu/Stp9Et39P1/4VG7NTnL6TYNSmMj69RcfxNZui8JQovGEqXLcNPewG/uSdZ5ZVd5RFV0FQRvM9R97A2LwHW3YSr7wseXWWPSUeuZMY3hmSz1dxuKo8XVdohzf2CQ9ZRrdLtVojk8uTTGeIJ1Kk0ml2cjn0Wg3DMI6o8qFrQM/o0Go2hCRLPKpRzO/Qft9i0O9a8S8SmqFatUo+X8C1XeLxS8GrMkq8LGoqltK3DR0tXePpVo3VrQprr0uUS0XUZJGHL0qsvylQ0/WPVR4Qj8fZ9gf5eUFl9I7KyJTK0moYLRIiEAjJHuT+M4WzkyrnJX51PoIqsUcuhTO3Na7NKySS6SFhOpUipES4tRLjqj3GlfkYD10a8ZhKOKKSkP2RW+PyXIwfJX7jXkxsUPnLp3FpLs7NJZV0JjckTCQS+ANBfvlNZcy2p/L+s6HCqBpiRRSayi9I/JpdRVMVHrvDjExqjNsVkqlPCsVDqVyhUOR5oMzqZpmngvABD5viYSyj8+x1VVARr8tUyiWiqRJPxG/3dhFdr3+5yj2jK3+oks3tEI3GUJQwiXiMUnFHyGv0e/+6bczO6NGRNqmLykI+S6VSodXaxej1j542Zsrr2xWWXDs88ORZ2ccDT4E/vCV+95WlLYS0WBJb8uSkuf+OTCZjHWgR9vsDbj9IMToV4eJdlR+m93DB2iPW79edYas4SkigKP9AMBgknd4vSl8kzj7JSqtEGV+I8ZNziHHnXpvcWola7aOqhyMss9NU+knhxEqSs7fD0jIRadwhxgQjkxHGHUOFoUMQCASGCk0Pt2MNXP6qNY0P4oWi8zKs44/pVuu8beo06jVppaoFXT63RqNBvV6XgrWOqbKs950+jZZhodIwKNfN595wKsnU2d3dpdls0m63h4SmjwfRG+xNk2Suic9fZDNYknukxMuAORB0Bvtxs7JmymZRUvL5Hjm+rFWNQ8INKZ/AKye4GRRCn8jMCpsF+awoR6Vs+mK++NEz89n0zMpKrg4TZsp+v59isXg8odHriY8d2gfQ7XY/z0XSNw/uSOxYwv9y0X8Ax7pgo59lXPgAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;GC Mark and Sweep&quot;
        title=&quot;&quot;
        src=&quot;/static/36fc84790098c4d7cc25ec9b05912eef/ce661/2021-08-30-jvm-mark-and-compact.png&quot;
        srcset=&quot;/static/36fc84790098c4d7cc25ec9b05912eef/0eb09/2021-08-30-jvm-mark-and-compact.png 500w,
/static/36fc84790098c4d7cc25ec9b05912eef/ce661/2021-08-30-jvm-mark-and-compact.png 626w&quot;
        sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;많은 &lt;em&gt;GC&lt;/em&gt; 방식들이 이 &lt;em&gt;Algorithm&lt;/em&gt; 을 바탕으로 하여 구현되고 있습니다.&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;Major GC&lt;/em&gt; 와 &lt;em&gt;Minor GC&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;위의 &lt;em&gt;Oracle&lt;/em&gt; 사에서 자료에서는 그래프에 &lt;em&gt;Minor Collections&lt;/em&gt; 와 &lt;em&gt;Major Collections&lt;/em&gt; 라는 단어가 나옵니다.
&lt;em&gt;GC&lt;/em&gt; 는 일어나는 시점에 따라서 크게 &lt;em&gt;Minor GC&lt;/em&gt; 그리고 &lt;em&gt;Major GC&lt;/em&gt; 로 나눌 수가 있습니다.
&lt;em&gt;Minor GC&lt;/em&gt; 는 &lt;em&gt;JVM&lt;/em&gt; 의 &lt;em&gt;Young&lt;/em&gt; 영역에서 일어나는 &lt;em&gt;GC&lt;/em&gt; 입니다.
&lt;em&gt;Young&lt;/em&gt; 에 위치한 각각의 영역이 가득 차게 되어 더 이상 새로운 객체를 생성할 수 없을 때 발생합니다.
실행 과정은 마크된 영역이 다음 영역으로 복사가 되면서 이루어집니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 979px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/40426a80379c37beefc7e9de73af5593/1d5a8/2021-08-30-jvm-minor-gc.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 72.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACx0lEQVR42pWTWY/bVBSA87t5ACFVqH2BBwoMKlAK7UyHaYtKpTIDGkpnS6GtmmXieEvGdhI7E2dxbMdL7OTj2pkKieWhRzq6V1f25+8en1PJ85xFHJOkS5IkJRL71SrnbeRZysy1WeUZ/4zlcsncmxL6c+I4Ks8qk6lH1+jTaquo+gWKbtHSLxnNYqpKyO7JhJ1nNnunU87kgEXoC4jHzAs5emPz9Fhn/7TLQdVkOI6ojAXQsGwa5xKq1kHr9mmoQxYpdI4PeHlvi/qjO7za3kI72ifNcuIopGdP+GRH4qM7TW58f86HX9epa9PCcI7RdwSwjVIATRvVdEv9XvUXWntbyD99i/TgS/ovfiVdZswmLs5ozscCeO12nevfNQWw9hboMeoY6G8a9FoybtekJjsYTsDu8YT37/a4vmfzwd0+94/GJFEg6hUzDyJ+PtV5/EzmyXOFHw/bWI5HZSoMA1nDOqni/PGauayL+viloXm6T+uHz5EffyNMv6B3dlCeXzoOi0WIeaHT0WS6ukJHbRP4AjiZePhql74wHAlDT+ynwrqIpHqPxYP3iJ5cK9fsz13COGXsjii6Q1U12m0ZWVaQpDaeVxjO5vjnCsbhEfbxC+aSytQL8BcZDw91Prvf4NajFp/u1Dk4s8SVw7K9sixD0zQBk1EURYCvgEUNhx0TpdbEaCsMdJNqvY9uR+jPn1LbuUnz4VfUtm9ycbJfmq/Xa9I0FYZqCSqgkiRtgEXbdKwBr+tNWrKK3O1hDsabF+XfoHobXm5vVu13YjEA7qVTNvVg0Mc0TSzLKjMMQypL0QbueEZhWsDHk1n5cAn8j/TjNeMgZyYGI1peTdNqVRoXda3wjuF0L7DEjzBbEtPhpRjTVVnLRqOxufKmJv/O/4tkNiR2LSLXJFuI24jJCQIfwzDEGry7YZavWV5lvvr7y0mSlFf+C77yBGuh2cYkAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Minor GC&quot;
        title=&quot;&quot;
        src=&quot;/static/40426a80379c37beefc7e9de73af5593/1d5a8/2021-08-30-jvm-minor-gc.png&quot;
        srcset=&quot;/static/40426a80379c37beefc7e9de73af5593/0eb09/2021-08-30-jvm-minor-gc.png 500w,
/static/40426a80379c37beefc7e9de73af5593/1d5a8/2021-08-30-jvm-minor-gc.png 979w&quot;
        sizes=&quot;(max-width: 979px) 100vw, 979px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이 때, 마크가 된 영역만 복사되기 때문에 삭제 과정은 이루어지지 않습니다.
&lt;em&gt;Major GC&lt;/em&gt; 에 비해서 상대적으로 시간이 짧아서, &lt;em&gt;Stop-The-World&lt;/em&gt; 가 이루어지지 않는다고 알려져 있습니다.
실제로는 &lt;em&gt;Stop-The-World&lt;/em&gt; 가 이루어지며, &lt;em&gt;GC&lt;/em&gt; 를 담당하는 쓰레드를 제외하고 모든 쓰레드가 멈추게 됩니다.
하지만 이 시간이 무시가 가능할 정도로 짧게 이루어지기 때문에 &lt;em&gt;Stop-The-World&lt;/em&gt; 가 이루어지지 않는다고 간주하곤 합니다. &lt;br&gt;
이와 다르게 &lt;em&gt;Major GC&lt;/em&gt; 는 &lt;em&gt;Old&lt;/em&gt; 영역에서 이루어지게 됩니다.
상당히 긴 시간 &lt;em&gt;Stop-The-World&lt;/em&gt; 가 이루어지며, 이는 &lt;em&gt;Java&lt;/em&gt; 프로그램에 영향을 줍니다.
이를 해결하기 위해서 &lt;em&gt;Major GC&lt;/em&gt; 에서 우리가 앞에서 다룬 &lt;em&gt;Mark And Compact Algorithm&lt;/em&gt; 을 기반한 여러 &lt;em&gt;GC&lt;/em&gt; 방식들이 선택 및 적용됩니다.&lt;/p&gt;
&lt;h2&gt;마치며&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;JVM&lt;/em&gt; 의 메모리 영역, 그중에서도 &lt;em&gt;Heap&lt;/em&gt; 메모리를 정리해주는 &lt;em&gt;GC&lt;/em&gt; 의 기초에 대해서 살펴보게 되었습니다.
실제로 &lt;em&gt;GC&lt;/em&gt; 를 선택해서 사용할 때에는 &lt;em&gt;Java&lt;/em&gt; 프로그램 실행 시&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;java&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-XX:+UseParallelGC&lt;/span&gt; SampleProgram &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;와 같이 &lt;em&gt;Serial GC&lt;/em&gt; , &lt;em&gt;Parallel GC&lt;/em&gt; 등 &lt;em&gt;GC&lt;/em&gt; 방식을 선택해서 사용할 수가 있습니다.
오늘 다룬 기초적인 지식을 바탕으로 하여, 각자의 &lt;em&gt;Java&lt;/em&gt; 프로그램에 맞는 &lt;em&gt;GC&lt;/em&gt; 방식을 채택하는게 좋습니다.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/329631&quot;&gt;Naver D2 Java Reference와 GC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/generations.html&quot;&gt;Oracle Java 8 - 3 generation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://plumbr.io/handbook/garbage-collection-in-java&quot;&gt;Plumbr Garbage Collection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dzone.com/articles/minor-gc-vs-major-gc-vs-full&quot;&gt;Dzone Minor GC vs Major GC vs Full GC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[정적 웹 사이트에 대해 알아보자.]]></title><description><![CDATA[최근 S…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-30-static-web-page/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-30-static-web-page/</guid><pubDate>Mon, 30 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;최근 S3를 통한 배포를 실습해보면서, &lt;code class=&quot;language-text&quot;&gt;정적 웹 사이트 호스팅&lt;/code&gt;을 하였다. 그러면서 &lt;code class=&quot;language-text&quot;&gt;정적&lt;/code&gt;이라는 단어에 의문이 들었다.&lt;/p&gt;
&lt;p&gt;*호스팅 - 서버 컴퓨터의 전체 또는 일부를 임대해주는 서비스&lt;/p&gt;
&lt;p&gt;대개 정적 웹 사이트는 ‘모든 상황에서 모든 사용자에게 동일한 정보를 표시하는 웹사이트’, 동적 웹 사이트는 ‘상황에 따라 사용자 별로 다른 정보을 보여주는 웹 사이트’이다.&lt;/p&gt;
&lt;p&gt;이번에 프로젝트로 만든 서비스는 사용자가 정보를 입력하면, 입력한 정보에 따라 다른 결과를 보여주는 동적 웹 사이트이다.&lt;/p&gt;
&lt;p&gt;그런데 어떻게 S3 서버를 통해 &lt;code class=&quot;language-text&quot;&gt;정적 웹 사이트 호스팅&lt;/code&gt;가 가능한걸까?&lt;/p&gt;
&lt;h2&gt;정적 웹 사이트와 동적 웹 사이트&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;정적 웹 사이트&lt;/code&gt;는 앞에서 말한 것 처럼 모든 상황에서 모든 사용자에게 동일한 정보를 표시하는 웹사이트이다.&lt;/p&gt;
&lt;p&gt;사용자(혹은 클라이언트)의 요청에 따라 서버에 저장된 파일(HTML, CSS, JavaScript, Image 등)을 그대로 전달해준다. 서버의 데이터가 변경되지 않으면 항상 동일한 페이지가 나타나는 것이다.&lt;/p&gt;
&lt;p&gt;정적 웹 사이트는 저장된 정보를 바로 전송해주기 때문에 속도가 빠르지만, 저장된 정보만 보여줄 수 있기 때문에 서비스가 한정적이다.&lt;/p&gt;
&lt;p&gt;정적 웹 사이트는 클라이언트의 요청을 받아서 정적인 정보만 제공하면 되기 때문에 *웹 서버만 있으면 된다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1500px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/44bbef753fa5a99e8742e5c0c9ce0b36/6ffd1/2021-08-30-static-web-page1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 32.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAAA0ElEQVR42o2RSQqFQAxEPa6n8QZeQVx4BMGtLsSFtqDiBA4gOA/1SaA3H8EuaJpA8lJJNLzovm9c14XnefhRTKqqClmWYV1XHMfxVgoNHyIgaZ5nhGGINE2R5zniOP4GymLf92HbNoZhwLIsEEIgiiKGGoYBx3HQNA1PoQT0PA+WZaGuayRJAtd1EQQBtm2DruswTRNd1+E8TzXgNE08FjmQ++v7HmVZcgP6ybXMV97hvu98iHEc2REdhOKiKNC2rfpRZGcCUiHtkhy+jfgP/AFA6Boqj8EXBwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;정적 웹 사이트&quot;
        title=&quot;&quot;
        src=&quot;/static/44bbef753fa5a99e8742e5c0c9ce0b36/6ffd1/2021-08-30-static-web-page1.png&quot;
        srcset=&quot;/static/44bbef753fa5a99e8742e5c0c9ce0b36/0eb09/2021-08-30-static-web-page1.png 500w,
/static/44bbef753fa5a99e8742e5c0c9ce0b36/1263b/2021-08-30-static-web-page1.png 1000w,
/static/44bbef753fa5a99e8742e5c0c9ce0b36/6ffd1/2021-08-30-static-web-page1.png 1500w&quot;
        sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;동적 웹 사이트&lt;/code&gt;는 앞에서 말한 것 처럼 상황에 따라 사용자 별로 다른 정보을 보여주는 웹 사이트이다.&lt;/p&gt;
&lt;p&gt;사용자(혹은 클라이언트)의 요청에 따라 서버는 데이터를 처리하여 사용자에 맞는 페이지를 생성하여 전달해준다. 요청에 맞는 필요한 데이터를 가져오고 처리하기 때문에 상황에 따라 다른 페이지가 나타나는 것이다.&lt;/p&gt;
&lt;p&gt;동적 웹 사이트는 데이터를 조합하고 생성하여 제공할 수 있어서 다양한 서비스가 가능하다. 하지만 데이터를 처리하는 시간이 필요하기 때문에 비교적 속도가 느리다.&lt;/p&gt;
&lt;p&gt;동적 웹 사이트는 웹 서버 외에도 웹 서버로부터 요청을 받아서 데이터를 처리하여 동적인 정보를 제공하는 *웹 애플리케이션 서버가 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1500px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/31ea3a5b7b999a37410895a70a7c53bf/6ffd1/2021-08-30-static-web-page2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 32.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAAA9klEQVR42o1RyaqDQBDMf/oXfl5AxIPeRCHRi/uGiorx4L7Uoxsm5PDCewXFzPTQNdU1N3zgPE8m4bouJp2jKEJd11iWBeM4Mqdp4pVqn7jhC0iM8Hq9YFkWfN9HkiQoy5JJe6LjOL8LkoCqqrjf7/x6URQsFAQB0jSFLMt4Pp8YhoHvyHGWZexePM6C4rCuKzRNg6Io6Psetm2zILmgJkmSYJomi+V5jqqquB7H8Tumt0Mh2rYtN4g8CV3XsRMamfb7vnN2hmFA13U8Ho//ZUg4joPzcl0XYRiyWxqfnHmex6TaV0HxswLzPPNITdPwiNu24S/8AHMWEm4ONGBrAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;동적 웹 사이트&quot;
        title=&quot;&quot;
        src=&quot;/static/31ea3a5b7b999a37410895a70a7c53bf/6ffd1/2021-08-30-static-web-page2.png&quot;
        srcset=&quot;/static/31ea3a5b7b999a37410895a70a7c53bf/0eb09/2021-08-30-static-web-page2.png 500w,
/static/31ea3a5b7b999a37410895a70a7c53bf/1263b/2021-08-30-static-web-page2.png 1000w,
/static/31ea3a5b7b999a37410895a70a7c53bf/6ffd1/2021-08-30-static-web-page2.png 1500w&quot;
        sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;*웹 서버 - 클라이언트의 요청을 받아 정적인 정보를 클라이언트에 제공한다. 동적인 정보를 요구하는 요청이라면 웹 애플리케이션 서버에 처리를 요청한다. 정적인 정보를 처리해주기 때문에 웹 애플리케이션 서버의 부담을 줄여주는 역할도 한다.&lt;/p&gt;
&lt;p&gt;*웹 애플리케이션 서버 - 웹 서버로부터 요청을 받아서 데이터를 처리하여 동적인 정보를 웹 서버에 제공한다. 일반적으로 DB 조회나 다양한 로직을 처리하는 기능을 담당한다.&lt;/p&gt;
&lt;h2&gt;정적 웹 사이트 호스팅&lt;/h2&gt;
&lt;p&gt;그렇다면 프론트엔드 서버로 S3 정적 웹 사이트 호스팅이 가능한 이유는 무엇일까?&lt;/p&gt;
&lt;p&gt;그 이유는 정적 웹 사이트와 동적 웹 사이트의 서버 구조를 보면 알 수 있다.&lt;/p&gt;
&lt;p&gt;앞에서 설명한 것처럼 정적 웹 사이트는 클라이언트의 요청을 받아서 정적인 정보만 제공하면 되기 때문에 웹 서버만으로 배포가 가능하다.&lt;/p&gt;
&lt;p&gt;반면 동적 웹 사이트는 클라이언트의 요청에 따라서 정적인 정보를 제공하는 웹 서버와 동적인 정보를 제공하는 웹 애플리케이션 서버가 필요하다.&lt;/p&gt;
&lt;p&gt;그런데 여기서 주목할 부분은 동적 웹 사이트에서도 웹 서버가 필요하다는 부분이다. 즉, 동적 웹 사이트는 정적 웹 사이트에 동적인 정보를 추가적으로 제공하는 웹 사이트인 것이다.&lt;/p&gt;
&lt;p&gt;이제 결론을 지어보자면, S3 정적 웹 사이트를 호스팅 한다는 말은 서버로부터 HTML, CSS, JavaScript 등의 정적인 정보를 받아올 수 있다는 의미이다.&lt;/p&gt;
&lt;p&gt;이는 웹 서버의 역할을 할 수 있다는 것이고, 웹 서버의 역할은 동적 웹 사이트에서도 동일하다.&lt;/p&gt;
&lt;p&gt;일반적으로 웹 서버는 프론트엔드 서버이기 때문에, 동적 웹 사이트인 이번 프로젝트에서 S3 정적 웹 사이트 호스팅으로 프론트엔드 서버 배포가 가능했던 것이다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;이번 시간은 프론트엔드 서버를 배포하면서 헷갈렸던 정적 웹 사이트와 동적 웹 사이트에 대해서 정리할 수 있는 시간이었다.&lt;/p&gt;
&lt;p&gt;개발 용어 중에는 다른 단어이지만 비슷한 의미를 갖는 용어들도 많고, 비슷한 단어이지만 다른 의미를 갖는 용어들도 많아서 헷갈리게 되는 것 같다.&lt;/p&gt;
&lt;p&gt;혹시 나처럼 위 용어들에 대해 헷갈렸던 분들이 있다면 도움이 되었으면 한다.&lt;/p&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;h3&gt;위키백과&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%A0%95%EC%A0%81_%EC%9B%B9_%ED%8E%98%EC%9D%B4%EC%A7%80&quot;&gt;정적 웹 페이지&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%8F%99%EC%A0%81_%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80&quot;&gt;동적 웹 페이지&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%9B%B9_%EC%84%9C%EB%B2%84&quot;&gt;Web Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%9B%B9_%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98_%EC%84%9C%EB%B2%84&quot;&gt;WAS(Web Application Server)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;유튜브&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=C06xRvXIAUk&quot;&gt;얄팍한 코딩사전 - 정적 웹은 뭐고 동적 웹은 뭔가요?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;기타&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/WebsiteHosting.html&quot;&gt;Amazon S3 - 정적 웹 사이트 호스팅&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://business.adobe.com/kr/glossary/dynamic-web-page.html#q1&quot;&gt;Adobe - 동적 웹 페이지&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[웹팩 Asset Modules]]></title><description><![CDATA[이 글은 webpack에 대한 기본적인 이해가 필요한 글입니다. 웹 애플리케이션을 제작하면서 HTML, CSS, JS와 더불어 아이콘, 사진, 비디오 등 다양한 Asset을 추가하게 된다. Asset Modules…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-30-webpack-asset-modules/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-30-webpack-asset-modules/</guid><pubDate>Mon, 30 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이 글은 webpack에 대한 기본적인 이해가 필요한 글입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;웹 애플리케이션을 제작하면서 HTML, CSS, JS와 더불어 아이콘, 사진, 비디오 등 다양한 Asset을 추가하게 된다. Asset Modules은 로더를 추가하지 않아도 이러한 asset 파일들을 사용할 수 있도록 도와주는 모듈이다. 쉽게 말해서 Asset Modules는 Asset 파일들을 처리하는 방식들을 모아놓은 모듈이고, 정의하는 방식에 따라 브라우저가 한 번에 다운로드하는 파일의 개수, 파일의 용량을 결정한다.&lt;/p&gt;
&lt;p&gt;Asset Modules의 종류로는 총 4가지, &lt;code class=&quot;language-text&quot;&gt;asset/resource&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;asset/inline&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;asset/source&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;asset&lt;/code&gt;이 있다.&lt;/p&gt;
&lt;h2&gt;목차&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;asset/resource&lt;/li&gt;
&lt;li&gt;asset/inline&lt;/li&gt;
&lt;li&gt;asset/source&lt;/li&gt;
&lt;li&gt;asset&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;asset/resource&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;asset/resource&lt;/code&gt; 모듈은 별도의 파일로 내보내고 URL을 추출한다. 다시 말해서 빌드 후 asset 파일을 출력 디렉터리로 내보내고, 해당 경로를 번들에 추가한다. (webpack 5 이전에는 &lt;code class=&quot;language-text&quot;&gt;file-loader&lt;/code&gt;가 해당 역할을 했다)&lt;/p&gt;
&lt;p&gt;파일을 출력 디렉터리로 내보낼 때 &lt;code class=&quot;language-text&quot;&gt;asset/resource&lt;/code&gt; 모듈은 기본적으로 &lt;code class=&quot;language-text&quot;&gt;[hash][ext][query]&lt;/code&gt; 파일명을 사용하는데, &lt;code class=&quot;language-text&quot;&gt;assetModuleFilename: &apos;static(예시)/[name][ext]&lt;/code&gt; 옵션을 이용하면 출력 디렉터리 안에 폴더&lt;code class=&quot;language-text&quot;&gt;static&lt;/code&gt; 폴더를 생성하고 그 안에 기존 파일 이름과 동일한 이름으로 asset 파일을 내보낼 수 있다. &lt;code class=&quot;language-text&quot;&gt;Rule.generator.filename&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;assetModuleFilename&lt;/code&gt;의 기능과 동일하지만, &lt;code class=&quot;language-text&quot;&gt;asset/resource&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;asset&lt;/code&gt; 모듈에서만 동작한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// webpack.config.js&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 빌드 후 생기는 폴더 안에 images라는 폴더를 만들고 그 안에 기존 에셋 파일 이름과 동일한 이름으로 파일을 내보낸다&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./src/index.js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;main.js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;dist&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;assetModuleFilename&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;images/[name][ext]&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;\.png&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;asset/resource&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;\.jpg&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;asset.resource&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;generator&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token literal-property property&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;static/[name][ext]&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/index.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; mainImage &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./assets/main.png&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; subMainImage &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./assets/subMain.jpg&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

img1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mainImage&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
img2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; subMainImage&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;.png&lt;/code&gt; 파일은 &lt;code class=&quot;language-text&quot;&gt;asset/resource&lt;/code&gt; 모듈의 사용과 &lt;code class=&quot;language-text&quot;&gt;output&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;assetModuleFilename: &apos;images/[name][ext]&apos;&lt;/code&gt;의 설정으로 출력 디렉터리(&lt;code class=&quot;language-text&quot;&gt;dist&lt;/code&gt;) 안 새로운 폴더(&lt;code class=&quot;language-text&quot;&gt;images&lt;/code&gt;)에 별도의 파일로 생성된다. 또한, 파일의 이름은 기존의 이름 그대로 생성된다. &lt;code class=&quot;language-text&quot;&gt;.jpg&lt;/code&gt; 파일은 &lt;code class=&quot;language-text&quot;&gt;asset/resource&lt;/code&gt; 모듈의 사용과 &lt;code class=&quot;language-text&quot;&gt;Rule.generator.filename: &apos;static/[name][ext]&lt;/code&gt;의 설정으로 출력 디렉터리(&lt;code class=&quot;language-text&quot;&gt;dist&lt;/code&gt;) 안 새로운 폴더(&lt;code class=&quot;language-text&quot;&gt;static&lt;/code&gt;)에 기존의 이름 그대로의 파일이 생성된다. 참고로, 위와 같이 &lt;code class=&quot;language-text&quot;&gt;output&lt;/code&gt;에서의 &lt;code class=&quot;language-text&quot;&gt;assetModuleFilename&lt;/code&gt; 옵션과 &lt;code class=&quot;language-text&quot;&gt;module&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;rules&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;Rule.generator.filename&lt;/code&gt;을 동시에 사용하는 경우, &lt;code class=&quot;language-text&quot;&gt;Rule.generator.filename&lt;/code&gt;이 우선된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;// 출력 파일의 폴더 구조

dist
├── main.js
├── images
│   └── main.png
└── static
    └── subMain.jpg&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;asset/inline&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;asset/inline&lt;/code&gt; 모듈은 asset의 data URI를 내보낸다. 즉, 파일이 data URI 형식으로 번들에 삽입된다. (webpack 5 이전에는 &lt;code class=&quot;language-text&quot;&gt;url-loader&lt;/code&gt;가 해당 역할을 했다)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// webpack.config.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;path&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./src/index.js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;main.js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;dist&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;\.svg&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;asset/inline&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/index.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; metroMap &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./images/metro.svg&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

block&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;background &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;url(&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;metroMap&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;//  build 후 번들에 삽입되는 형태 - url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDo...vc3ZnPgo=)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;모든 &lt;code class=&quot;language-text&quot;&gt;.svg&lt;/code&gt; 파일은 data URI 형태로 번들(&lt;code class=&quot;language-text&quot;&gt;main.js&lt;/code&gt;)에 삽입된다.&lt;/p&gt;
&lt;h3&gt;Custom data URI generator&lt;/h3&gt;
&lt;p&gt;기본적으로 webpack에서 내보낸 data URI는 Base64 알고리즘을 사용하여 인코딩 된 파일 콘텐츠를 의미하는데, 파일 인코딩을 위한 커스텀 함수를 지정하여 &lt;a href=&quot;https://github.com/tigt/mini-svg-data-uri&quot;&gt;mini-svg-data-uri&lt;/a&gt;와 같은 커스텀 인코딩 알고리즘도 사용할 수 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Base64: 화면에 표시되는 ASCII 문자들만 사용하여 일련의 문자열로 바꾸는 인코딩 방식&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// webpack.config.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;path&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; svgToMiniDataURI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;mini-svg-data-uri&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./src/index.js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;main.js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;dist&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;\.svg&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;asset/inline&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;       generator&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;         &lt;span class=&quot;token function-variable function&quot;&gt;dataUrl&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;           content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;           &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;svgToMiniDataURI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;       &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;asset/source&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;asset/source&lt;/code&gt; 모듈은 asset의 소스 코드를 그대로 내보낸다. (webpack 5 이전에는 &lt;code class=&quot;language-text&quot;&gt;raw-loader&lt;/code&gt;가 해당 역할을 했다)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// webpack.config.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;path&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./src/index.js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;main.js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;dist&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;\.txt&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;asset/source&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/example.txt&lt;/span&gt;

Hello Webpack&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/index.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; exampleText &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./example.txt&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

block&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; exampleText&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// &apos;Hello world&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;모든 &lt;code class=&quot;language-text&quot;&gt;.txt&lt;/code&gt; 파일은 있는 그대로(여기서는 텍스트의 형태로) 번들(&lt;code class=&quot;language-text&quot;&gt;main.js&lt;/code&gt;)에 삽입된다.&lt;/p&gt;
&lt;h3&gt;asset&lt;/h3&gt;
&lt;p&gt;이 모듈은 기본 조건에 따라서 &lt;code class=&quot;language-text&quot;&gt;resource&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;inline&lt;/code&gt; 모듈 중에서 자동으로 선택한다. 용량 크기가 &lt;code class=&quot;language-text&quot;&gt;8KB(default)&lt;/code&gt; 보다 작으면 &lt;code class=&quot;language-text&quot;&gt;inline&lt;/code&gt; 모듈로 처리되고(번들에 삽입), 그렇지 않으면 &lt;code class=&quot;language-text&quot;&gt;resource&lt;/code&gt; 모듈로 처리된다.(별도의 파일로 분리) (webpack 5 이전에는 &lt;code class=&quot;language-text&quot;&gt;url-loader&lt;/code&gt;가 해당 역할을 했다)&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://webpack.kr/configuration/module/#ruleparserdataurlcondition&quot;&gt;Rule.paser.dataUrlCondition.maxSize&lt;/a&gt; 옵션을 설정해서 용량 조건을 변경할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// webpack.config.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;path&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./src/index.js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;main.js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;dist&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;\.txt&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;asset&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token literal-property property&quot;&gt;dataUrlCondition&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;maxSize&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 기준으로 4kb 로 변경&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;참조&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://webpack.kr/guides/asset-modules/&quot;&gt;webpack - Asset Modules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/smelukov/webpack-5-asset-modules-2o3h&quot;&gt;webpack 5 - Asset Modules / 공식 문서 링크 블로그&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[이벤트 루프, 넌 누구냐]]></title><description><![CDATA[최근 requestAnimationFrame(이하 rAF)과 setTimeout을 공부하면서 브라우저의 싱글 스레드 동작이 정확히 어떻게 이루어지는지 잘 모른다는 생각이 들었다.
이번 기회에 이벤트 루프의 동작을 공부하고 추가로 rAF…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-28-event-loop/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-28-event-loop/</guid><pubDate>Sat, 28 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;최근 requestAnimationFrame(이하 rAF)과 setTimeout을 공부하면서 브라우저의 싱글 스레드 동작이 정확히 어떻게 이루어지는지 잘 모른다는 생각이 들었다.
이번 기회에 이벤트 루프의 동작을 공부하고 추가로 rAF와 setTimeout의 차이점까지 알아보자.&lt;/p&gt;
&lt;h2&gt;들어가기 전에&lt;/h2&gt;
&lt;p&gt;들어가기 전에 용어 정리를 해두면 좋을 것 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;프로세스
&lt;ul&gt;
&lt;li&gt;운영체제가 프로그램의 실행을 위해 프로그램에 메모리를 할당하는 단위&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스레드
&lt;ul&gt;
&lt;li&gt;프로세스가 할당받은 메모리를 실행하는 단위. 하나의 프로세스가 여러 스레드로 나뉠 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;task queue
&lt;ul&gt;
&lt;li&gt;콜 스택에 들어가기 전에 setTimeout, 사용자 이벤트 콜백 등이 저장되는 큐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;microtask queue
&lt;ul&gt;
&lt;li&gt;Promise.then 콜백이 저장되는 큐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;이벤트 루프, 넌 누구냐&lt;/h2&gt;
&lt;p&gt;브라우저 메인 스레드 동작 타이밍을 관리하는 관리자라고 할 수 있다.
여기서 메인 스레드란 자바스크립트 코드 실행이나 브라우저 렌더링을 맡는 등 브라우저의 주된 동작이 수행되는 곳이다.&lt;/p&gt;
&lt;h2&gt;이벤트 루프가 왜 중요한가요?&lt;/h2&gt;
&lt;p&gt;관리자라고 하니까 중요한 것 같기는 한데 감이 오지 않을 수 있다.
브라우저 동작 특징을 알아보면서 이벤트 루프가 중요한 이유를 알아보자.&lt;/p&gt;
&lt;h3&gt;1. 브라우저 동작의 대부분이 메인 스레드에서 싱글 스레드로 실행된다.&lt;/h3&gt;
&lt;p&gt;웹 api에서 제공하는 비동기 함수들(ex. fetch, setTimeout)과 워커 종류를 제외한 대부분의 자바스크립트 코드가 메인 스레드라는 곳에서 실행된다. 또한 브라우저 화면을 그리는 렌더링 작업도 이곳에서 실행된다. 이처럼 브라우저의 주요한 동작들이 메인 스레드라는 하나의 싱글 스레드로 동작한다.&lt;/p&gt;
&lt;p&gt;여기서 메인 스레드가 싱글 스레드로 동작하는 것이 중요한 이유는 싱글 스레드에서 하나의 작업을 하고 있다면 다른 작업은 지연시키기 때문이다.
예를 들어 싱글 스레드의 동작은 한 사람이 여러 가지 일을 동시에 처리할 수 없는 것과 비슷하다. &lt;code class=&quot;language-text&quot;&gt;게임&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;식사&lt;/code&gt;라는 2개의 작업이 수행되어야 할 때 &lt;code class=&quot;language-text&quot;&gt;게임&lt;/code&gt;을 하고 있다면 &lt;code class=&quot;language-text&quot;&gt;식사&lt;/code&gt; 작업은 지연될 수밖에 없다. 둘 중 하나의 작업이 굉장히 길어진다면 다른 작업은 시작조차 못 할 것이다.&lt;/p&gt;
&lt;p&gt;메인 스레드에서도 마찬가지로, 개발자가 만약 자바스크립트 코드에 무한 루프 코드를 작성한다면 해당 작업이 다른 모든 작업을 지연시키고 메인 스레드에서 무한히 동작할 것이다. 결국에는 브라우저가 먹통이 되는 현상이 발생한다. 사용자 이벤트도 메인 스레드에서 동작하기 때문에 키보드 입력이나 마우스 클릭도 동작하지 않는다. 그러므로 싱글 스레드의 작업 관리는 매우 중요하다고 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://media.giphy.com/media/rzgDhQtB1GHM8djEa7/giphy.gif?cid=790b761157d6fcdd39c301704bb8588f03cb14930a9b239e&amp;#x26;rid=giphy.gif&amp;#x26;ct=g&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;F12로 개발자 도구를 열어 &lt;code class=&quot;language-text&quot;&gt;while(1) {}&lt;/code&gt; 을 입력해보자. 브라우저가 먹통이 되는 현상을 경험할 수 있다.(주의 - 프로세스 강제종료 해야 합니다.)
하지만 다른 브라우저 탭은 잘 동작함을 알 수 있는데 이는 탭마다 다른 프로세스로 동작하기 때문이다.&lt;/p&gt;
&lt;h3&gt;2. 메인 스레드는 이벤트 루프에 의해 관리된다.&lt;/h3&gt;
&lt;p&gt;앞서 언급한 것처럼, 메인 스레드와 같은 싱글 스레드에서 하나의 작업이 오랫동안 실행되어서도 안 되고, 여러 작업 중 어떤 작업을 우선으로 동작시킬 것인가 결정하는 것도 매우 중요하다. 또한, 작업 간 전환 속도를 빠르게 하여 한 번에 하나의 작업씩 수행하지만 마치 동시에 수행하는 것처럼 동작해야 한다. (&lt;a href=&quot;https://www.charlezz.com/?p=44646&quot;&gt;참고 - 동시성과 병렬성&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;이러한 섬세한 컨트롤을 누군가 해주어야 할 텐데 누가 이런 역할을 할까? 바로 이벤트 루프가 메인 스레드의 동작을 관리한다.
그러므로 이벤트 루프의 동작을 정확히 이해하는 것이 브라우저 실행 전반을 이해하는 데 있어 매우 중요한 것이다.&lt;/p&gt;
&lt;h2&gt;이벤트 루프의 동작&lt;/h2&gt;
&lt;p&gt;이벤트 루프가 왜 중요한지는 알 것 같다. 그럼 이벤트 루프는 어떠한 우선순위로 task를 실행할까? 이벤트 루프의 동작을 같이 알아보자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://media.giphy.com/media/41qMCw6byUsNjqeie0/giphy.gif?cid=790b76110b521df01c236df35e6f56e004135ad84c50bd9e&amp;#x26;rid=giphy.gif&amp;#x26;ct=g&quot; alt=&quot;image&quot;&gt;
(그림 1 - &lt;a href=&quot;https://www.youtube.com/watch?v=cCOL7MC4Pl0&quot;&gt;Jake Archibald: 루프 속 - JSConf.Asia&lt;/a&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;그림 상
&lt;ul&gt;
&lt;li&gt;T : task queue&lt;/li&gt;
&lt;li&gt;rAF : requestAnimationFrame&lt;/li&gt;
&lt;li&gt;S : Style (렌더 트리 생성)&lt;/li&gt;
&lt;li&gt;L : Layout&lt;/li&gt;
&lt;li&gt;P : Paint&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;가장 잘 와 닿았던 그림이다. 중앙을 기준으로 왼쪽 path가 task queue, 오른쪽 path가 브라우저 렌더링이라고 생각하면 된다.
(microtask는 그림 어디에서나 실행될 수 있다고 한다.)
이 그림을 보면서 이벤트 루프의 동작 과정을 따라가 보자.&lt;/p&gt;
&lt;h3&gt;1. 초기 콜 스택에 쌓여있는 task를 모두 처리&lt;/h3&gt;
&lt;p&gt;처음 html을 가져오고 script 태그를 만나는 순간을 생각해보자. 브라우저 렌더링 과정에 들어가기 전에 동작을 잠깐 멈추고 자바스크립트 코드를 읽기 시작한다. 자바스크립트 코드가 DOM 트리를 수정할 수 있기 때문이다. 이 과정에서 코드들이 콜 스택에 올라가 동작을 수행하게 되고 Promise나 setTimeout과 같은 비동기 관련 콜백들이 queue에 등록된다.&lt;/p&gt;
&lt;h3&gt;2. Promise.then 콜백이 microtask queue에 등록되어 있다면 실행&lt;/h3&gt;
&lt;p&gt;처음 콜 스택에 있는 코드들이 모두 실행되고 난 후부터는 Promise가 가장 높은 우선순위를 차지한다. Promise.then 콜백이 등록된 microtask queue는 특징이 하나 있는데, queue에 등록된 모든 콜백이 처리될 때까지 계속 수행한다는 것이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;infinityThen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;infinityThen&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;infinityThen&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;궁금하다면 개발자 도구에 위 코드를 넣어보자. Promise가 resolve 될 때 Promise.then 콜백을 재귀적으로 등록하면 앞서 예시와 비슷하게 브라우저는 먹통이 되어버린다. Promise를 처리하느라 바쁜 나머지 다른 작업을 아무것도 못 하게 되는 것이다.&lt;/p&gt;
&lt;h3&gt;3. 화면 갱신이 필요하다면 렌더링 파이프라인으로 이동 (requestAnimationFrame(rAF))&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;화면 갱신이 필요하다면&lt;/code&gt; 이라고 했는데 이는 이벤트 루프가 판단하는 것이다. 사용자가 스크롤 이동을 했거나, 어떤 요소를 클릭했거나 등등 화면을 갱신해야 될 필요가 있다면 렌더링 파이프라인으로 이동한다. (그림 1에서 오른쪽 path)&lt;/p&gt;
&lt;p&gt;여기서 자바스크립트 코드 상에 rAF api를 사용하면 이벤트 루프가 모니터 주사율(Hz)에 맞춰 렌더링 파이프라인으로 들어가려고 노력한다. (싱글 스레드이기 때문에 반드시 주기가 지켜지는 것은 아님) 보통은 모니터 주사율이 60Hz기 때문에 화면 갱신 속도도 60fps이고 이는 1프레임이 16ms 정도 나와야 함을 의미한다. 여기서 1프레임이란, 렌더링 파이프라인에 진입했을 때부터 다음 파이프라인 진입까지를 의미한다.&lt;/p&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/131212408-7e079272-136d-4e2e-be6f-607e57b1799b.png&quot; style=&quot;width:200px&quot;&gt;
rAF를 동작시켰을 때 1프레임이 16ms로 지켜지는 모습.
&lt;h3&gt;4. task queue에 있는 콜백을 하나씩 실행. (setTimeout)&lt;/h3&gt;
&lt;p&gt;이제 드디어 setTimeout과 사용자 이벤트 콜백의 시간이다. 그런데 queue에 있는 콜백을 &lt;code class=&quot;language-text&quot;&gt;하나씩&lt;/code&gt; 실행한다는 것은 무슨 뜻일까.
Promise와는 다르게 task queue는 콜백을 하나 실행하고 이벤트 루프를 놓아주어 다른 동작을 수행할 수 있도록 한다는 것이다. 앞서 Promise의 예시와 같이 재귀적으로 setTimeout 콜백을 task queue에 집어넣어도, 브라우저는 정상 작동한다.&lt;/p&gt;
&lt;h2&gt;requestAnimationFrame vs setTimeout&lt;/h2&gt;
&lt;p&gt;지금까지 이벤트 루프를 알아보았다. 생각보다 복잡한 일을 하는 녀석인데 마지막으로 rAF와 setTimeout의 동작 차이를 알아보며 마무리하도록 하자.&lt;/p&gt;
&lt;p&gt;rAF와 setTimeout의 가장 큰 차이점은 1프레임 당 호출이 보장되느냐 되지 않느냐의 차이가 있다.
흔히 웹에서 애니메이션을 보여주기 위해 setTimeout 대신 rAF 사용을 권장한다. 그 이유는 애니메이션을 위해 setTimeout을 16ms마다 동작하도록 코드를 작성하여도, 다른 task에 의해서 지연될 가능성이 있어 1프레임 당 1번의 호출이 보장되지 않기 때문이다.&lt;/p&gt;
&lt;p&gt;그림 1을 보면 setTimeout은 task queue에 올라가 동작하고, rAF는 렌더링 파이프라인과 붙어 동작하는 것을 확인할 수 있다. 이런 구조 상 rAF는 무조건 1프레임 당 1번의 호출이 보장되고 setTimeout은 지연되어 2프레임 당 1번, 또는 3프레임 당 1번 호출될 가능성도 있다. 이런 현상은 버벅거리는 애니메이션을 제공하여 사용자 경험을 떨어뜨린다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=cCOL7MC4Pl0&quot;&gt;Jake Archibald: 루프 속 - JSConf.Asia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@francesco_rizzi/javascript-main-thread-dissected-43c85fce7e23&quot;&gt;JavaScript main thread. Dissected&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Web Event 다루기]]></title><description><![CDATA[Web Event…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-26-web-event/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-26-web-event/</guid><pubDate>Thu, 26 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Web Event 다루기&lt;/h2&gt;
&lt;p&gt;웹 개발에서 프론트엔드 영역이 이토록 발전할 수 있었던 가장 주된 이유는 웹의 특성이 단순한 ‘페이지’에서 ‘애플리케이션’으로 진화했다는 사실일 겁니다. 그리고 이 말은 곧, 좋은 웹의 기준을 판단할 때 사용자의 행동에 따라 웹이 얼마나 자연스럽게 반응할 수 있는지가 중요함을 나타냅니다.&lt;/p&gt;
&lt;p&gt;자연스러운 인터랙션을 위해서는 UI, UX, 성능 등 다양한 요소를 고려해야겠지만 무엇보다 인터랙션 과정의 시작을 알리는 이벤트를 빼놓지 않을 수 없습니다. 그럼 지금부터 이벤트에 대해 함께 알아볼까요?&lt;/p&gt;
&lt;h3&gt;Event&lt;/h3&gt;
&lt;p&gt;이벤트는 말 그대로 &lt;strong&gt;행동 혹은 사건&lt;/strong&gt;입니다. 이것을 프로그래밍 맥락에서 유추해보면, 코드 내에서 발생하는 행동이라고 볼 수 있겠습니다. 웹을 사용하면서 클릭을 하거나 키보드를 입력해야 할 때, 더 나아가 모바일 환경에서 터치할 때 일어나는 모든 일들은 각기 다른 이벤트입니다. 이제 이벤트에 대한 개념은 얼추 파악을 마쳤으니 빠르게 이벤트를 다루는 방법으로 넘어가겠습니다.&lt;/p&gt;
&lt;h3&gt;Event Listener&lt;/h3&gt;
&lt;p&gt;이벤트를 코드로 다룰 때는 특정 요소에 이벤트를 등록하는 과정을 거칩니다. 이 과정을 좀 더 쉽게 풀어보면, “사용자가 a 행동을 하는 순간에 그에 맞는 브라우저의 행동을 보여줘”라고 할 수 있겠네요. 브라우저의 행동은 우리가 흔히 작성하는 함수를 예상할 수 있습니다. 하지만 사용자가 무슨 행동을 할지, 이벤트를 무슨 요소에 등록할지는 어떻게 알려줄 수 있을까요?&lt;/p&gt;
&lt;p&gt;이러한 요구를 충족하기 위해 출현한 개념이 바로 &lt;strong&gt;이벤트 리스너&lt;/strong&gt;입니다. 무언가를 리스닝(listening)한다는 것은 쉽게 말해 대기를 하고 있다고 이해하는 것이 편합니다. 개발자는 이벤트 리스너를 통해 브라우저 내의 &lt;code class=&quot;language-text&quot;&gt;특정 요소&lt;/code&gt;에 &lt;code class=&quot;language-text&quot;&gt;특정 행동을 등록&lt;/code&gt;해놓고 사용자가 &lt;code class=&quot;language-text&quot;&gt;특정 행동을 할 때까지 기다리도록&lt;/code&gt; 명령할 수 있습니다.&lt;/p&gt;
&lt;p&gt;이벤트를 등록하는 방법은&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTML 태그에 직접 등록하는 &lt;code class=&quot;language-text&quot;&gt;Inline event&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;JS에서 DOM을 가져와 등록하는 &lt;code class=&quot;language-text&quot;&gt;Event Handler&lt;/code&gt; (e.g. &lt;code class=&quot;language-text&quot;&gt;button.onClick&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;JS에서 DOM을 가져와 등록하는 &lt;code class=&quot;language-text&quot;&gt;EventTarget.addEventListener&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위와 같이 크게 세 가지로 나눌 수 있습니다. 이번 글에서는 마지막 방법인 &lt;code class=&quot;language-text&quot;&gt;EventTarget.addEventListener&lt;/code&gt;를 예시로 들어 설명하겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;button&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Click me&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; button &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;#button&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  button&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;click&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;버튼이 클릭되었음&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;예시로 작성한 코드는 다음과 같은 흐름으로 실행됩니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;#button&lt;/code&gt;이라는 id를 가진 HTML의 요소를 가져와서&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;addEventListener&lt;/code&gt;를 선언해 이벤트를 대기할 것이라고 알려준다.&lt;/li&gt;
&lt;li&gt;이벤트의 타입은 &lt;code class=&quot;language-text&quot;&gt;&apos;click&apos;&lt;/code&gt;이 될 것이고&lt;/li&gt;
&lt;li&gt;해당 이벤트가 발생하면 그에 따른 브라우저 동작은 두 번째 인자로 들어간 함수의 실행이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;두 번째 인자로 들어간 함수는 이벤트 리스너의 관점에서 &lt;strong&gt;이벤트 핸들러&lt;/strong&gt;가 됩니다. 용어만 달라졌을 뿐 함수라는 사실은 변함이 없는데요, 단지 이벤트로 일어날 행동을 정의한 함수를 의미합니다.&lt;/p&gt;
&lt;p&gt;한가지 염두에 두어야 할 점은, &lt;code class=&quot;language-text&quot;&gt;addEventListener&lt;/code&gt;는 &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API&quot;&gt;Web API&lt;/a&gt; (Web에서 제공하는 인터페이스 e.g. &lt;code class=&quot;language-text&quot;&gt;fetch&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;setTimeout&lt;/code&gt; etc.) 로 취급된다는 것입니다. 스크립트 파일 내에서 JS 문법과 같이 작성하기 때문에 착각하기 쉽지만, 이 부분을 혼동하는 일은 없길 바랍니다.&lt;/p&gt;
&lt;h3&gt;Event Object&lt;/h3&gt;
&lt;p&gt;이벤트 핸들러로 정의되어 호출된 함수는 하나의 인자를 받는 것이 원칙입니다. 여기서 넘어간 인자의 정체는 바로 &lt;strong&gt;이벤트 객체&lt;/strong&gt;입니다. 이벤트 객체는 발생한 이벤트에 대한 전반적인 정보를 담고 있는 객체입니다. 대부분은 &lt;code class=&quot;language-text&quot;&gt;e&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;event&lt;/code&gt;라는 이름으로 선언되지만 사실 아무 이름이나 들어가도 상관은 없습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; button &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;#button&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

button&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;click&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// PointerEvent { ... }&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 객체 안을 들여다보면 이벤트가 등록된 요소나 타겟 요소, 현재 좌표 등을 알 수 있으므로 이를 통해 더욱 세밀한 이벤트의 작성이 가능해집니다.&lt;/p&gt;
&lt;p&gt;그건 그렇고 여기서 하나의 의문점을 짚고 넘어가야 할 것 같습니다. &lt;code class=&quot;language-text&quot;&gt;event&lt;/code&gt;라는 인자를 선언만 해주었지, 어디서도 이벤트 객체를 넘겨주겠다는 코드를 작성하진 않은 것 같은데 이것은 당최 어떻게 이벤트 핸들러로 넘어가는 걸까요? 이 메커니즘을 이해하기 위해서는 이벤트 객체가 어디서부터 출발하여 전달되는지 알아야 합니다.&lt;/p&gt;
&lt;h3&gt;Event Flow&lt;/h3&gt;
&lt;p&gt;하나의 웹 페이지는 &lt;code class=&quot;language-text&quot;&gt;Window&lt;/code&gt;를 최상위로 가진 계층적인 구조를 생성합니다. 이벤트 객체는 이 구조와 이벤트의 Phase에 따라 결정된 &lt;strong&gt;전파 경로&lt;/strong&gt;(Propagation path)를 기준으로 흘러가게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img width=&quot;400&quot; src=&quot;/a97db545084746db37078b6ce4c91fe6/2021-08-26-eventflow.svg&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;이벤트의 Phase는 총 3단계로 나누어볼 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Capture&lt;/code&gt; Phase: 이벤트 객체가 &lt;code class=&quot;language-text&quot;&gt;Window&lt;/code&gt;로부터 시작하여 타겟의 부모 요소에 도달하는 단계&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Target&lt;/code&gt; Phase: 이벤트 객체가 타겟에 도달하는 단계.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Bubbling&lt;/code&gt; Phase: &lt;code class=&quot;language-text&quot;&gt;Capture&lt;/code&gt; Phase의 역순. 타겟의 부모 요소부터 출발해 &lt;code class=&quot;language-text&quot;&gt;Window&lt;/code&gt; 요소에 도달하는 단계&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이벤트 객체의 전파 경로 상에서 등록된 이벤트 핸들러가 있다면, 해당 이벤트 핸들러는 호출되고 이벤트 객체를 인자로 받게 됩니다. 그것이 바로 모든 이벤트 핸들러에서 이벤트 객체를 확인할 수 있는 이유입니다.&lt;/p&gt;
&lt;p&gt;현대의 브라우저들은 &lt;code class=&quot;language-text&quot;&gt;Bubbling&lt;/code&gt; Phase를 기본 동작으로 설정해놓았다고 하네요. 특수한 경우에서 &lt;code class=&quot;language-text&quot;&gt;Capture&lt;/code&gt; Phase를 사용해야 한다면, &lt;code class=&quot;language-text&quot;&gt;EventTarget.addEventListener&lt;/code&gt;의 세 번째 인자인 &lt;code class=&quot;language-text&quot;&gt;useCapture&lt;/code&gt;를 &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;로 설정해주면 됩니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In modern browsers, by default, all event handlers are registered for the bubbling phase. - &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling_and_capture&quot;&gt;MDN&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;&lt;code class=&quot;language-text&quot;&gt;currentTarget&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;target&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;앞서 설명한 이벤트 객체에는 이벤트와 관련된 요소에 접근할 수 있는 프로퍼티를 제공합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;currentTarget&lt;/code&gt;: 이벤트 리스너를 등록한 요소. 이벤트 핸들러의 &lt;code class=&quot;language-text&quot;&gt;this&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;target&lt;/code&gt;: 이벤트 전파 경로의 최하위 요소.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 두 프로퍼티가 항상 같은 요소를 가리키고 있다고는 보장할 수 없습니다. 이벤트를 등록한 요소에 자식 요소가 존재하는 경우가 그렇습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ul&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;li&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;child&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;child&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;li&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;ul&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; parent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;parent&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  parent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;click&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTarget&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// &amp;lt;ul id=&quot;parent&quot;&gt;&amp;lt;/ul&gt;&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// &amp;lt;li id=&quot;child&quot;&gt;child&amp;lt;/li&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;초반에는 조금 헷갈리겠지만, 이벤트의 흐름과 앞서 말한 두 타겟의 차이점을 잘 알아두면 &lt;a href=&quot;https://javascript.info/event-delegation&quot;&gt;이벤트 위임&lt;/a&gt;과 같은 숙련된 테크닉을 사용할 수도 있습니다.&lt;/p&gt;
&lt;h3&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.web-event.be/&quot;&gt;대표 이미지 출처&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/DOM-Level-3-Events/#current-event-target&quot;&gt;https://www.w3.org/TR/DOM-Level-3-Events/#current-event-target&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jbee.io/web/about-event-in-the-web/&quot;&gt;https://jbee.io/web/about-event-in-the-web/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[다양한 이미지 타입]]></title><description><![CDATA[누구나 쉽게 읽을 수 있는 글입니다. 0. Intro 어느 조사에 따르면 이미지 파일은 6.…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-17-various-image-types/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-17-various-image-types/</guid><pubDate>Tue, 17 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;누구나 쉽게 읽을 수 있는 글입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;0. Intro&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://w3techs.com/technologies/overview/image_format&quot;&gt;어느 조사&lt;/a&gt;에 따르면 이미지 파일은 6.3퍼센트만을 제외하고 모든 웹 사이트에서 사용된다. 그만큼 이미지는 사용자에게 친숙하고 당연히 있어야 할 것처럼 여겨진다. 이런 이미지 파일은 그 목적에 따라 그 용도가 다양하다. 어떤 이미지 파일을 사용하는지에 따라 품질이 더 높을 수 있고, 파일의 용량이 커질 수 있으며, 이미지의 품질에 상관없이 크기(scale)를 자유롭게 조절할 수도 있기 때문이다. 그럼 대표적인 이미지 파일 종류로 JPG, PNG, GIF, SVG에 대해서 알아보겠다.&lt;/p&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;JPG&lt;/li&gt;
&lt;li&gt;PNG&lt;/li&gt;
&lt;li&gt;GIF&lt;/li&gt;
&lt;li&gt;SVG&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1. JPG - Joint Photographic Experts Group(JPEG)&lt;/h2&gt;
&lt;p&gt;먼저 제목부터 의문이 생길 수 있을텐데, JPG와 JPEG에 대해서 간단히 설명하겠다. JPG와 JPEG의 관계를 알기 위해서는 지금은 거의 사용되지 않은 도스(DOS)를 조금 살펴보아야 한다. 도스는 윈도우가 보편화되기 전에 주로 사용되던 프로그램으로 일종의 운영체제이다. 도스에서는 파일명을 최대 8자, 확장자를 최대 3자까지 밖에 사용할 수 없어 기존의 4자리로 된 JPEG를 줄여 JPG로 사용한 게 시초가 되었다. 하지만, 윈도우 macOS 등 다양한 운영체제가 개발되면서 파일명과 확장자를 포함하여 최대 255자까지 사용할 수 있게 되면서, JPEG를 굳이 JPG로 표기할 필요가 없게 되었다.&lt;/p&gt;
&lt;p&gt;다시 돌아가서 JPG 파일에 대해서 알아보겠다. JPG 파일은 기본적으로 raster-based 이미지(웹, 인쇄 용도로 사용되는 이미지)이다. JPG 형식은 디지털 카메라 파일의 기본 형식인데, 압축과 대부분의 브라우저를 지원하는 이유로 웹에서 널리 사용되고 있는 형식이다.&lt;/p&gt;
&lt;p&gt;이런 파일 형식은 파일의 크기가 작고, 품질 손실이 거의 없는 사진 파일을 저장하는데 가장 적합하다. JPG는 *손실 압축을 사용하므로 다시 저장할 때마다 품질이 저하된다.&lt;/p&gt;
&lt;p&gt;JPG 파일을 올바르게 사용하기 위해서는 올바른 크기와 해상도로 저장해야 한다. 그렇지 않으면 흔히 ‘깨져 보이는’ 픽셀화 또는 이미지가 늘어져 보이기 때문이다. 이런 이유로 소셜 미디어 플랫폼 등에서는 이미지의 크기를 사용하여 해상도를 제어한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;*손실 압축이란, 이미지를 저장할 때 데이터를 없애 더 작은 사이즈로 압축하는 방식을 이른다. 그 결과 더 낮은 해상도를 갖게 되지만, 파일 크기와 다운로드 속도가 필수적인 온라인에서는 이상적이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;2. PNG - Portable Network Graphic&lt;/h2&gt;
&lt;p&gt;PNG는 웹 사용에 표준이다. 이런 형식은 픽셀 기반이며, 이미지의 크기를 키울 경우, 픽셀화가 일어난다. JPG와 비슷하게, 최종 사용에 적합한 크기로 내보내야(export)한다.&lt;/p&gt;
&lt;p&gt;하지만 JPG와 다르게, PNG 파일은 투명한 배경을 지원하며 손실없는 압축으로 그래픽(사진 아님)용 JPG보다 전반적으로 고품질의 이미지를 유지할 수 있다. 즉, 파일을 저장해도 품질이 저하되지 않는다.&lt;/p&gt;
&lt;p&gt;PNG는 로고, 아이콘, 간단한 일러스트처럼 선명하게 유지해야 하는 색상 간 전환과 색상이 적은 그래픽에 사용하는 것이 더 좋다. 또한, 웹 사이트와 소셜 미디어에서 PNG를 사용하여 디지털 및 모바일 디스플레이에서 더 선명한 이미지를 만들 수 있다. 이런 이유로 고품질을 유지하면서 파일 크기를 최대한 낮게 설정하여 PNG 파일을 사용하는 것은 웹 최적화에 유리하다.&lt;/p&gt;
&lt;p&gt;참고로 PNG-24는 PNG-8보다 선명하지만 더 많은 색상으로 이미지를 저장하기 때문에 파일 크기가 훨씬 커질 수 있으므로 사용에 주의가 필요하다.&lt;/p&gt;
&lt;h2&gt;3. GIF - Graphics Interchange Format&lt;/h2&gt;
&lt;p&gt;GIF는 모든 주요 웹 브라우저와 대부분의 이미지 편집기에서 지원되는 유일한 애니메이션 이미지 형식이다. GIF는 투명도와 애니메이션을 지원하고 파일을 줄이기 위해서 높은 압축률로 압축될 수 있다.&lt;/p&gt;
&lt;p&gt;GIF는 색상이 거의 필요없는 애니메이션 그래픽에서 자주 사용되는 무손실 raster 형식이다. GIF는 오직 256가지 색만 지원하기 때문에, 사진을 저장하는 경우는 권장하지 않는다.&lt;/p&gt;
&lt;p&gt;다른 애니메이션 이미지 파일 형식으로 APNG(Animated Portable Network Graphics), WebP, MNG(Multiple-Image Network Graphics), FLIF(Free Lossless Image Format) 등이 있지만, 널리 사용되지는 않는다.&lt;/p&gt;
&lt;h2&gt;4. SVG - Scalable Vector Graphic&lt;/h2&gt;
&lt;p&gt;SVG는 World Wide Web Consortium(W3C)에서 개발한 2차원 그래픽을 설명하기 위한 백터 기반의 확장 가능한 마크업 언어(XML)이다.&lt;/p&gt;
&lt;p&gt;SVG 파일 형식의 가장 큰 특징은 다른 파일 형식과 다르게 픽셀 형식을 사용하지 않는다는 것이다. 픽셀 형식을 사용하지 않아서, 이미지의 크기를 키우는 경우 이미지가 꺠져 흐릿하게 보이는 픽셀화가 일어나지 않는다.&lt;/p&gt;
&lt;p&gt;SVG 파일은 PNG 및 JPG에 비해 가장 선명한 품질의 그래픽을 제공하기 때문에 로고, 아이콘 및 간단한 일러스트레이션에 적합하다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://w3techs.com/technologies/overview/image_format&quot;&gt;https://w3techs.com/technologies/overview/image_format&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://designpowers.com/blog/image-file-formats&quot;&gt;https://designpowers.com/blog/image-file-formats&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;#x26;blogId=acromedia&amp;#x26;logNo=220278860944&quot;&gt;https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;#x26;blogId=acromedia&amp;#x26;logNo=220278860944&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[JPA CascadeType.REMOVE vs orphanRemoval = true]]></title><description><![CDATA[JPA…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-15-jpa-cascadetype-remove-vs-orphanremoval-true/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-15-jpa-cascadetype-remove-vs-orphanremoval-true/</guid><pubDate>Sun, 15 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;JPA를 공부하다 보면 바로 이해하기 쉽지 않은 개념들을 몇 개 마주친다.
필자는 연관관계 매핑, 영속성 전이, 고아 객체 등이 특히 어려웠다.
이때 &lt;code class=&quot;language-text&quot;&gt;CascadeType.REMOVE&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;orphanRemoval = true&lt;/code&gt;가 유독 헷갈렸는데, 직접 학습 테스트를 작성하며 이해했다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;이번 글에서는 영속성 전이(REMOVE)와 고아 객체를 학습 테스트를 통해 비교하여 살펴본다.
최종적으로 독자들이 둘의 차이를 이해하는 것을 목표로 한다.&lt;br/&gt;&lt;/p&gt;
&lt;!-- end --&gt;
&lt;br/&gt;
&lt;h2&gt;엔티티 기본 세팅&lt;/h2&gt;
&lt;p&gt;Team과 Member 엔티티를 바탕으로 두 개념의 공통점과 차이점을 알아본다.
Team은 @OneToMany, Member는 @ManyToOne으로 양방향 매핑을 했다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Team.java&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@OneToMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mappedBy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;team&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fetch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FetchType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;LAZY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; members &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Member.java&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ManyToOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fetch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FetchType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;LAZY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@JoinColumn&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; team&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;학습 테스트를 조금 더 편하게 작성하기 위해 Team에 연관관계 편의 메소드 &lt;code class=&quot;language-text&quot;&gt;addMember()&lt;/code&gt;를 추가했다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Team.java&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;addMember&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        members&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setTeam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h2&gt;학습 테스트 기본 세팅&lt;/h2&gt;
&lt;p&gt;학습 테스트는 &lt;code class=&quot;language-text&quot;&gt;@DataJpaTest&lt;/code&gt;로 진행했다.
테스트에 필요한 TeamRepository와 MemberRepository를 각각 DI했다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// JpaLearningTest.java&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@DataJpaTest&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JpaLearningTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TeamRepository&lt;/span&gt; teamRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MemberRepository&lt;/span&gt; memberRepository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h2&gt;CascadeType.REMOVE&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;CascadeType.REMOVE&lt;/code&gt;는 부모 엔티티가 삭제되면 자식 엔티티도 삭제된다. 즉, 부모가 자식의 삭제 생명 주기를 관리한다.
만약 &lt;code class=&quot;language-text&quot;&gt;CascadeType.PERSIST&lt;/code&gt;도 함께 사용하면, 부모가 자식의 전체 생명 주기를 관리하게 된다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;한편, 이 옵션의 경우에는 부모 엔티티가 자식 엔티티와의 관계를 제거해도 자식 엔티티는 삭제되지 않고 그대로 남아있다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;학습 테스트를 위해 Team 엔티티에 영속성 전이 옵션을 추가한다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Team.java&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@OneToMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        mappedBy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;team&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        fetch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FetchType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;LAZY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        cascade &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CascadeType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;ALL&lt;/span&gt;   &lt;span class=&quot;token comment&quot;&gt;// { CascadeType.PERSIST, CascadeType.REMOVE }와 동일하다.&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; members &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;먼저, 부모 엔티티를 삭제하는 경우를 살펴본다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// JpaLearningTest.java&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@DisplayName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;CascadeType.REMOVE - 부모 엔티티(Team)을 삭제하는 경우&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cascadeType_Remove_InCaseOfTeamRemoval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; team &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    team&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addMember&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    team&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addMember&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    teamRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;team&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// when&lt;/span&gt;
    teamRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;team&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; teams &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; teamRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; members &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; memberRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;teams&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;members&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;delete 쿼리가 총 3번 나가는 걸 확인할 수 있다.
즉, Team(부모)가 삭제될 때 Member(자식)도 영속성 전이 옵션으로 인해 함께 삭제된다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// DML&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    insert 
    into
        team
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
    values
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    insert 
    into
        member
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; team_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
    values
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    insert 
    into
        member
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; team_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
    values
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    delete 
    from
        member 
    where
        id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    delete 
    from
        member 
    where
        id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    delete 
    from
        team 
    where
        id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;다음으로, 부모 엔티티에서 자식 엔티티를 제거하는 경우를 알아본다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// JpaLearningTest.java&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@DisplayName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;CascadeType.REMOVE - 부모 엔티티(Team)에서 자식 엔티티(Member)를 제거하는 경우&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cascadeType_Remove_InCaseOfMemberRemovalFromTeam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; team &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    team&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addMember&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    team&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addMember&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    teamRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;team&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// when&lt;/span&gt;
    team&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMembers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; teams &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; teamRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; members &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; memberRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;teams&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;members&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;delete 쿼리가 전혀 나가지 않는다. 영속성 전이 삭제 옵션은 부모와 자식의 관계가 끊어졌다 해서 자식을 삭제하지 않기 때문이다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// DML&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    insert 
    into
        team
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
    values
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    insert 
    into
        member
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; team_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
    values
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    insert 
    into
        member
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; team_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
    values
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h2&gt;orphanRemoval = true&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;orphanRemoval = true&lt;/code&gt; 또한 부모 엔티티가 삭제되면 자식 엔티티도 삭제된다.
따라서 &lt;code class=&quot;language-text&quot;&gt;CascadeType.PERSIST&lt;/code&gt;를 함께 사용하면, 이때도 부모가 자식의 전체 생명 주기를 관리하게 된다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;한편, 이 옵션의 경우에는 부모 엔티티가 자식 엔티티의 관계를 제거하면 자식은 고아로 취급되어 그대로 사라진다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;이번에는 학습 테스트를 위해 Team 엔티티에 고아 객체 옵션을 추가한다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Team.java&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@OneToMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        mappedBy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;team&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        fetch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FetchType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;LAZY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        cascade &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CascadeType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PERSIST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        orphanRemoval &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; members &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;이전과 동일하게 부모 엔티티를 삭제하는 경우를 살펴본다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// JpaLearningTest.java&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@DisplayName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;orphanRemoval = true - 부모 엔티티(Team)을 삭제하는 경우&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;orphanRemoval_True_InCaseOfTeamRemoval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; team &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    team&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addMember&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    team&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addMember&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    teamRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;team&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// when&lt;/span&gt;
    teamRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;team&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; teams &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; teamRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; members &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; memberRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;teams&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;members&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;이때도 delete 쿼리가 총 3번 나가는 걸 확인할 수 있다.
즉, Team(부모)가 삭제될 때 Member(자식)도 고아 객체 옵션으로 인해 같이 삭제된다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// DML&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    insert 
    into
        team
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
    values
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    insert 
    into
        member
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; team_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
    values
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    insert 
    into
        member
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; team_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
    values
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    delete 
    from
        member 
    where
        id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    delete 
    from
        member 
    where
        id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    delete 
    from
        team 
    where
        id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;학습 테스트로 부모 엔티티를 삭제할 때는 &lt;code class=&quot;language-text&quot;&gt;CascadeType.REMOVE&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;orphanRemoval = true&lt;/code&gt;가 동일하게 동작하는 것을 이해했다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;그렇다면, 부모 엔티티에서 자식 엔티티를 제거할 때는 어떤 결과를 나타낼까?&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// JpaLearningTest.java&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@DisplayName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;orphanRemoval = true - 부모 엔티티(Team)에서 자식 엔티티(Member)를 제거하는 경우&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;orphanRemoval_True_InCaseOfMemberRemovalFromTeam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; team &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    team&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addMember&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    team&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addMember&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    teamRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;team&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// when&lt;/span&gt;
    team&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMembers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; teams &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; teamRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; members &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; memberRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;teams&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;members&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;이전과는 다르게 delete 쿼리가 1번 나간다. 고아 객체 옵션은 부모와 자식의 관계가 끊어지면 자식을 고아로 취급하고 자식을 삭제하기 때문이다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// DML&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    insert 
    into
        team
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
    values
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    insert 
    into
        member
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; team_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
    values
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    insert 
    into
        member
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; team_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
    values
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    select
        team0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id as id1_1_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        team0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name as name2_1_ 
    from
        team team0_

&lt;span class=&quot;token class-name&quot;&gt;Hibernate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
    delete 
    from
        member 
    where
        id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h2&gt;비교 결과&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;부모 엔티티 삭제
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;CascadeType.REMOVE&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;orphanRemoval = true&lt;/code&gt;는 부모 엔티티를 삭제하면 자식 엔티티도 삭제한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;부모 엔티티에서 자식 엔티티 제거
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;CascadeType.REMOVE&lt;/code&gt;는 자식 엔티티가 그대로 남아있는 반면, &lt;code class=&quot;language-text&quot;&gt;orphanRemoval = true&lt;/code&gt;는 자식 엔티티를 제거한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2&gt;주의점&lt;/h2&gt;
&lt;p&gt;두 케이스 모두 자식 엔티티에 딱 하나의 부모 엔티티가 연관되어 있는 경우에만 사용해야 한다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;예를 들어 Member(자식)을 Team(부모)도 알고 Parent(부모)도 알고 있다면, &lt;code class=&quot;language-text&quot;&gt;CascadeType.REMOVE&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;orphanRemoval = true&lt;/code&gt;를 조심할 필요가 있다.
자식 엔티티를 삭제할 상황이 아닌데도 어느 한쪽의 부모 엔티티를 삭제했거나 부모 엔티티로부터 제거됐다고 자식이 삭제되는 불상사가 일어날 수 있기 때문이다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;그러므로 &lt;code class=&quot;language-text&quot;&gt;@OneToMany&lt;/code&gt;에서 활용할 때 주의를 기울이고, @ManyToMany에서는 활용을 지양하자.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.inflearn.com/course/ORM-JPA-Basic/dashboard&quot;&gt;자바 ORM 표준 JPA 프로그래밍 - 기본편&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/da-nyee/jpa-learning-test&quot;&gt;JPA 학습 테스트&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Pageable을 이용한 Pagination을 처리하는 다양한 방법]]></title><description><![CDATA[Spring Data JPA에서 Pageable 를 활용한 Pagination 의 개념과 방법을 알아본다. Pageable을 활용한 Pagination…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-15-pageable/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-15-pageable/</guid><pubDate>Sun, 15 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Spring Data JPA에서 &lt;strong&gt;Pageable&lt;/strong&gt; 를 활용한 &lt;strong&gt;Pagination&lt;/strong&gt; 의 개념과 방법을 알아본다.&lt;/p&gt;
&lt;!-- end --&gt;
&lt;h2&gt;Pageable을 활용한 Pagination이 무엇인가?&lt;/h2&gt;
&lt;p&gt;많은 게시판은 모든 글을 한 번에 보여주지 않고 페이지를 나눠 쪽수별로 제공한다. 정렬 방식 또한 설정하여, 보고 싶은 정보의 우선순위를 설정할 수도 있다. 이처럼 정렬 방식과 페이지의 크기, 그리고 몇 번째 페이지인지의 요청에 따라 정보를 전달해주는 것이 &lt;strong&gt;Pagination&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;p&gt;이를 개발자가 직접 구현해서 사용할 수도 있으나, JPA에서는 이를 편하게 사용할 수 있도록 &lt;strong&gt;Pageable&lt;/strong&gt; 이라는 객체를 제공한다. ‘page=3&amp;#x26;size=10&amp;#x26;sort=id,DESC’ 형식의 QueryParameter를 추가로 요청을 보내게 되면, 쉽게 원하는 형식의 데이터들을 얻을 수 있다. 이 예시는 id의 내림차순으로 정렬한, 1쪽 10개의 글의 구성의 3번째 페이지의 정보를 요청 보내는 것이다.&lt;/p&gt;
&lt;h2&gt;Pageable JPA에서 사용하기&lt;/h2&gt;
&lt;p&gt;Pageable을 사용한 간단한 UserRespository, UserController를 만들어 보면 다음과 같다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserRepository&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JpaRepository&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Job&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findByLastname&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; lastName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Controller&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/users&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findByLastName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestParam&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; lastName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 생략&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;    
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같은 방식으로 Pageable 객체를 인수로 넘겨줌으로써 JpaRepository로 부터 원하는 Page만큼의 User 목록을 반환받을 수 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;GET /users?lastName=kim&amp;#x26;page=3&amp;#x26;size=10&amp;#x26;sort=id,DESC&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Controller에서는 Pageable 객체를 인수로 설정한 후 위와 같은 요청이 들어오게 되면, ‘page=3&amp;#x26;size=10&amp;#x26;sort=id,DESC’ 해당하는 Pageable 객체를 자동으로 만들어준다.&lt;/p&gt;
&lt;p&gt;별다른 수정 없이 Respository로 Pageable을 넘겨주면 되기 때문에, 매우 편리하게 Pagination 처리를 할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Page&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findByLastname&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; lastName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findByLastname&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; lastName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findByLastname&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; lastName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;JPA에서는 반환 값으로 &lt;strong&gt;List&lt;/strong&gt;, &lt;strong&gt;Slice&lt;/strong&gt;, &lt;strong&gt;Page&lt;/strong&gt; 로 다양하게 제공하고 있다. &lt;strong&gt;Page&lt;/strong&gt; 구현체의 경우에는 전체 Page의 크기를 알아야 하므로, 필요한  Page의 요청과 함께, 전체 페이지 수를 계산하는 count 쿼리가 별도로 실행된다. &lt;strong&gt;Slice&lt;/strong&gt; 구현체의 경우에는 전후의 Slice가 존재하는지 여부에 대한 정보를 가지고 있다. 각 특성을 익혀 본인의 상황에 적합한 자료형을 사용하는 것을 추천한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;spring.jpa.show-sql=true&lt;/code&gt; 옵션을 설정 파일에 추가하면 Page를 반환하는 메소드 실행 시, 실제로 count 명령어가 실행되는 것을 확인 할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;내부 구현 들여다보기&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PageableHandlerMethodArgumentResolver&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PageableHandlerMethodArgumentResolverSupport&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PageableArgumentResolver&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;supportsParameter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MethodParameter&lt;/span&gt; parameter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parameter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getParameterType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resolveArgument&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//생략&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Controller에서 별도의 어노테이션 없이 어떻게 ‘page=3&amp;#x26;size=10&amp;#x26;sort=firstName,ASC’ 같은 로직이 Pageable 객체로 전환되었는지 궁금증이 생긴다.&lt;/p&gt;
&lt;p&gt;이는 &lt;strong&gt;org.springframework.data.web&lt;/strong&gt; 패키지의 안에 정의된 ArgumentResolver인  &lt;code class=&quot;language-text&quot;&gt;PageableHandlerMethodArgumentResolver&lt;/code&gt;에 정의되어 있다. &lt;code class=&quot;language-text&quot;&gt;Pageable&lt;/code&gt;의 parameter가 왔을 때, &lt;code class=&quot;language-text&quot;&gt;resolveArgument()&lt;/code&gt; 로직이 실행되도록 정의가 되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;그렇다면, Pagination의 정보가 없는 &lt;code class=&quot;language-text&quot;&gt;GET ~/users?lastName=kim&lt;/code&gt; 의 요청만을 보냈을 때 어떤 형태의 반환 값을 가질까?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;실제 테스트를 해보면, 정렬되지 않은 20개씩 분리된 페이지 중 첫 페이지를 반환하는 것을 확인할 수 있다. 내부 구현을 들여다보면, &lt;code class=&quot;language-text&quot;&gt;PageableHandlerMethodArgumentResolverSupport&lt;/code&gt; 에 정의된  &lt;code class=&quot;language-text&quot;&gt;fallbackPageable&lt;/code&gt; 의 형식으로 반환하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;그렇다면 또 궁금증이 생긴다. &lt;em&gt;기본 설정을 수정하려면 어떤 방식을 사용할 수 있을까?&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;@PageDefault 어노테이션&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;PageableHandlerMethodArgumentResolverSupport&lt;/code&gt; 에 정의된 &lt;code class=&quot;language-text&quot;&gt;getDefaultFromAnnotationOrFallback()&lt;/code&gt; 메소드에서 확인할 수 있다.  &lt;code class=&quot;language-text&quot;&gt;@PageDefault&lt;/code&gt;로 어노테이션이 붙어 있는 경우에는 &lt;code class=&quot;language-text&quot;&gt;fallbackPageable&lt;/code&gt;이 아닌, &lt;code class=&quot;language-text&quot;&gt;@PageDefault&lt;/code&gt;어노테이션에 설정대로 사용자에게 보내준다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Controller&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/users&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findByLastName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestParam&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; lastName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                           &lt;span class=&quot;token annotation punctuation&quot;&gt;@PageDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;size&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sort&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; direction &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Sort&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Direction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DESC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 생략&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;    
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같은 방식으로 기존의 Pageable 객체 앞에 &lt;code class=&quot;language-text&quot;&gt;@PageDefault&lt;/code&gt; 어노테이션을 붙여주고 괄호 안에 기본값 설정을 진행한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;DefaultPage vs FallbackPage&lt;/p&gt;
&lt;p&gt;혼동될만한 두 개념을 구분하여 설명하고자 한다. DefaultPage의 경우, 개발자가 정한 기본 Page의 형식이다. 별도로 어노테이션을 통해 설정해주지 않으면 FallbackPage의 설정으로 실행된다. Fallback의 경우 적합한 방식이 없는 경우, 만일을 대비해 만들어 둔 설정이다. &lt;code class=&quot;language-text&quot;&gt;PageableHandlerMethodArgumentResolverSupport&lt;/code&gt;에는 FallbackPage만 설정되어 있다. DefaultPage는 &lt;code class=&quot;language-text&quot;&gt;@PageDefault&lt;/code&gt; 어노테이션으로 설정한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;지금까지 &lt;strong&gt;org.springframework.data.web&lt;/strong&gt; 에 정의된 &lt;code class=&quot;language-text&quot;&gt;PageableHandlerMethodArgumentResolver&lt;/code&gt; 를 살펴보았다. &lt;em&gt;그렇다면, SpringBoot의 어디에서 이를 Bean으로 등록하고 사용하고 있을까?&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;SpringDataWebConfiguration&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;proxyBeanMethods&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SpringDataWebConfiguration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PageableHandlerMethodArgumentResolverCustomizer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; pageableResolverCustomizer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PageableHandlerMethodArgumentResolver&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pageableResolver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;PageableHandlerMethodArgumentResolver&lt;/span&gt; pageableResolver &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PageableHandlerMethodArgumentResolver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sortResolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;customizePageableResolver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageableResolver&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; pageableResolver&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;customizePageableResolver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PageableHandlerMethodArgumentResolver&lt;/span&gt; pageableResolver&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    pageableResolverCustomizer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ifPresent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;c &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;customize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageableResolver&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;    
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;SpringDataWebConfiguration에서 &lt;code class=&quot;language-text&quot;&gt;PageableHandlerMethodArgumentResolver&lt;/code&gt; Bean을 등록한다. &lt;strong&gt;spring-boot-starter-web&lt;/strong&gt;의 의존성을 추가하면 자동으로 등록된다.&lt;/p&gt;
&lt;p&gt;내부적으로 &lt;code class=&quot;language-text&quot;&gt;pageableResolverCustomizer&lt;/code&gt;가 존재하는 경우 이를 적용한다.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;그렇다면 PageableHandlerMethodArgumentResolverCustomizer Bean은 또 어디서 정의될까?&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;SpringDataWebAutoConfiguration&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;proxyBeanMethods&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SpringDataWebAutoConfiguration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@ConditionalOnMissingBean&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PageableHandlerMethodArgumentResolverCustomizer&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pageableCustomizer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolver&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPageable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      resolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPageParameterName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPageParameter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      resolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSizeParameterName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSizeParameter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      resolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setOneIndexedParameters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isOneIndexedParameters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      resolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPrefix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPrefix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      resolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setQualifierDelimiter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getQualifierDelimiter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      resolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFallbackPageable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PageRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDefaultPageSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      resolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setMaxPageSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMaxPageSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;    
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;SpringDataWebAutoConfiguration 설정 파일에서 &lt;code class=&quot;language-text&quot;&gt;PageableHandlerMethodArgumentResolverCustomizer&lt;/code&gt; Bean을 등록을 관리한다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@ConditionalOnMissingBean&lt;/code&gt;설정의 경우, 해당 Bean이 기존의 정의되지 않은 경우에만, 등록되도록 작업 되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;SpringDataWebProperties&lt;/code&gt; 클래스 내부적으로 기본값이 설정되어 있고(하나의 Page의 크기 = 20) 이는 &lt;code class=&quot;language-text&quot;&gt;@ConfigurationProperties(&quot;spring.data.web&quot;)&lt;/code&gt;를 가르키고 있기 때문에, &lt;em&gt;application-properties&lt;/em&gt;에 별도로 ‘spring.data.web.pageable.default-page-size=100’과 같은 설정을 추가해주면, 이 설정으로 변경할 수 있다.&lt;/p&gt;
&lt;h2&gt;CustomPageableConfiguration 적용하기&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomPageableConfiguration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PageableHandlerMethodArgumentResolverCustomizer&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;customize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; p &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFallbackPageable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PageRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;MAX_VALUE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;별도의 CustomPageableConfiguration을 만들어서 &lt;code class=&quot;language-text&quot;&gt;PageableHandlerMethodArgumentResolverCustomizer&lt;/code&gt; Bean을 미리 정의하게 되면, &lt;code class=&quot;language-text&quot;&gt;SpringDataWebAutoConfiguration&lt;/code&gt;에서는 추가적인 Bean생성이 되지 않게 된다.&lt;/p&gt;
&lt;p&gt;Auto-configuration 설정들은 User-defined Bean들이 등록하고 난 다음에 적용되기 때문이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;위의 설정들 중 우선순위는?&lt;/h2&gt;
&lt;p&gt;다소 복잡하게 설명이 되었지만, 페이지네이션 관련 설정을 제외한 요청을 보냈을 때, Page의 설정을 수정하는 방법은 다음과 같은 3가지 정도이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@PageDefault&lt;/code&gt;어노테이션 사용. → DefaultPage를 정의&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;application-properties&lt;/code&gt;에 별도로 ‘spring.data.web.pageable.default-page-size=100’과 같은 설정을 추가 → Bean 등록 시 FallbackPage의 설정을 수정(이와 별개로 Bean을 구성하는 다른 설정들도 수정 가능)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;CustomPageableConfiguration&lt;/code&gt; 에서 설정 진행.→ 2번에서는 application-properties에서 설정을 진행했다면, 동일한 설정을 java 코드에서 진행한다. → 마찬가지로 Bean등록시 FallbackPage의 설정을 수정(이와 별개로 Bean을 구성하는 다른 설정들도 수정 가능)&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;그렇다면, 동시엔 이 3가지 설정의 적용된다면, 어떤 설정이 우선순위를 가질까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;첫 번째 우선순위를 지니는 것은 1. &lt;code class=&quot;language-text&quot;&gt;@PageDefault&lt;/code&gt;어노테이션의 사용이다. 2, 3번의 경우 FallbackPage의 설정을 결정하는 것인데 반해, 1번의 설정은 DefaultPage를 설정하기 때문이다. 내부 구현에서는 DefaultPage가 설정되어 있으면, 그 설정을 따른다.&lt;/p&gt;
&lt;p&gt;2, 3번의 대결에서는 3번이 우선순위를 가진다. &lt;code class=&quot;language-text&quot;&gt;CustomPageableConfiguration&lt;/code&gt; 에서 &lt;code class=&quot;language-text&quot;&gt;PageableHandlerMethodArgumentResolverCustomizer&lt;/code&gt; Bean을 생성하게 되면 &lt;code class=&quot;language-text&quot;&gt;@ConditionalOnMissingBean&lt;/code&gt;설정에 따라 2번 로직은 실행되지 않게 된다.&lt;/p&gt;
&lt;p&gt;우선순위만의 결론을 내리자면 1 -&gt; 3 -&gt; 2의 순이 되겠다. 개인적으로 추천하는 방식은, DefaultPage와 FallbackPage의 특징을 잘 살려 각각 정의하고 사용하는 것을 추천한다. 2, 3번 설정 중 어떤 방식을 택하는지는 개인의 취향의 문제라 남겨두겠다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;지금까지 Pageable의 작동 방식과, 이를 상황에 맞게 사용하는 방법을 알아보았다. 이 글이, 이번 경우에 한정된 것이 아니라 Springboot를 사용하게 되었을 때 자동으로 설정되는 수많은 설정을 커스터마이징 할 수 있는 기반이 되었으면 한다.&lt;/p&gt;
&lt;h3&gt;참고 자료&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-data/jpa/docs/2.0.6.RELEASE/reference/html/&quot;&gt;Spring Data Jpa&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/web/config/SpringDataWebConfiguration.html&quot;&gt;Spring Docs SpringDataWebConfiguration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/data/web/SpringDataWebAutoConfiguration.html&quot;&gt;Spring Docs SpringDataWebAutoConfiguration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[react에서의 에러 처리]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-13-react-error-handling/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-13-react-error-handling/</guid><pubDate>Sat, 14 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;🔥 아직 고민할 점이 많은 문제입니다. 다양한 의견 남겨주세요&lt;/li&gt;
&lt;li&gt;🔥 에러 처리에 대해서 개인적인 생각들을 담았습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;🚫 에러, 어떻게 처리해야하나&lt;/h2&gt;
&lt;p&gt;이전부터 프론트 개발을 하면서 계속 고민했던 부분이다.
팀 프로젝트를 할 때 명확한 기준없이 에러 처리를 해왔었는데 에러 처리 코드가 일관성이 떨어졌을 뿐만 아니라 에러 관련 코드가 산발적으로 흩어져 리팩터링할 때 어떤 파일을 보아야 하는 지 헤매는 경우가 많았다.&lt;/p&gt;
&lt;p&gt;이러다보니 일관된 에러 처리 코드에 관한 갈증이 높아졌다.
최근 새롭게 팀 프로젝트를 하게 되었는데 이번에야말로 에러에 대한 기준을 세워야겠다고 마음먹었고 실제 적용했던 내용을 글로 작성하게 되었다.&lt;/p&gt;
&lt;p&gt;처음으로 깊게 고민해본 것이라 부족한 점이 많을 것이다. 비판적으로 봐주시길 바란다.&lt;/p&gt;
&lt;h2&gt;🎯 에러 처리. 무엇이 중요할까&lt;/h2&gt;
&lt;p&gt;나는 에러 처리 방법을 정하기에 앞서, 에러 처리에서 &lt;code class=&quot;language-text&quot;&gt;무엇이 중요한가?&lt;/code&gt; 를 정의하는 것이 우선 되어야 한다고 생각한다.
무엇이 중요한가에 대해 모두가 생각이 조금씩 다르겠지만 나는 다음과 같이 생각한다.&lt;/p&gt;
&lt;h3&gt;1. 에러가 발생해도 사용자가 앱을 사용하는데 최대한 지장이 없어야 한다.&lt;/h3&gt;
&lt;p&gt;에러가 발생해도 중대한 에러가 아닌 이상 최대한 지장이 없어야 한다.
예를 들어 사용자 프로필 정보를 가져오는 페이지가 있다고 생각해보자.
프로필 정보를 불러오던 중, 사용자의 나이 정보만을 불러오지 못했을 때 페이지 전체가 에러 페이지로 대체된다면 어떨까?
나이 정보 외에는 사용자의 정보를 보여줄 수 있었지만, 사용자는 아무런 정보를 보지 못할 것이다.
사용자가 정상적으로 앱을 이용할 수 있음에도 이용하지 못하도록 강제했기 때문에 사용자 경험을 떨어뜨린다.&lt;/p&gt;
&lt;h3&gt;2. 사용자는 에러 발생 사실을 인지할 수 있어야 한다.&lt;/h3&gt;
&lt;p&gt;어떠한 방식으로든 사용자는 에러 발생 사실을 인지할 수 있어야 한다고 생각한다.
예를 들어 상품의 정보를 보여주는 페이지가 있다고 생각해보자.
상품 정보를 대부분 정상적으로 불러왔지만, 상품의 제조 일자를 알려주는 부분만을 받아오지 못했을 때
아무런 알림이나 UI적인 표현 없이 조용히 실패한다면 어떨까?
앱을 이전부터 써왔던 사용자라면 제조 일자를 불러오지 못했다는 사실을 알 테지만
앱을 처음 접한 사용자는 원래부터 제조 일자는 표시되지 않는 것으로 인식할 것이다.
에러를 어떻게 사용자에게 알려야 하는지는 에러의 중요도에 따라서 다르다. 후에 다뤄보도록 하겠다.&lt;/p&gt;
&lt;h2&gt;🤔 에러 처리 방법에 대한 고민&lt;/h2&gt;
&lt;p&gt;에러 처리에서 중요한 점을 정의했다. 이를 바탕으로 에러 처리 방법을 생각해보자.&lt;/p&gt;
&lt;h3&gt;에러 전파를 최대한 막기&lt;/h3&gt;
&lt;p&gt;javascript에는 이벤트 버블링이라는 개념이 있다. 웹상에서 어떤 이벤트(ex 클릭)가 발생했을 때, 이벤트가 발생한 element가 가장 먼저 이벤트 처리에 대한 권한을 가진다. 해당 element는 이벤트를 처리한 후에 상위 element로 이벤트를 전파한다.&lt;/p&gt;
&lt;p&gt;에러 전파도 이벤트 버블링과 비슷하다고 생각했다. 우리가 에러 처리를 할 때 자주 사용하는 try catch와 throw 구문은
에러가 발생한 곳에서 에러 처리에 대한 권한을 가장 먼저 갖고, 처리할 수 없다면 다른 곳으로 처리를 위임한다.&lt;/p&gt;
&lt;p&gt;이벤트 버블링과 에러 전파는 큰 차이점이 있는데, 이벤트 버블링은 이벤트를 처리하고도 다른 곳으로 전파되지만
에러는 최대한 빨리 처리하고 다른 곳으로의 전파를 막아야 한다는 점이다.
나는 이런 생각을 바탕으로 에러가 전파되는 3가지의 레이어를 정의했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;에러가 최초 발생한 함수 (보통 fetch를 사용하는 네트워크 요청 관련 함수)&lt;/li&gt;
&lt;li&gt;1번 함수의 반환값을 표현하는 컴포넌트&lt;/li&gt;
&lt;li&gt;2번 컴포넌트를 감싸는 &lt;a href=&quot;https://ko.reactjs.org/docs/error-boundaries.html&quot;&gt;에러 바운더리&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위의 구조를 적용해봤을 때 에러가 최초 발생한 함수에서 에러 전파를 끊는 경우는 아직 없었다.
1번에서 모든 에러 처리를 하고 전파를 끊기에는 상황에 따른 유연성이 너무 부족했기 때문이다.
그래서 우리는 1번을 &lt;code class=&quot;language-text&quot;&gt;console.error()&lt;/code&gt; 정도의 역할을 맡도록 하고 에러를 2번으로 전파하였다.
2번에서 해당 에러에 대한 모든 처리가 가능하다면 3번으로 에러 전파를 하지 않고 끊어냈다.
마지막 3번 에러 바운더리로 에러가 전파될 경우 에러 폴백 컴포넌트를 대신 렌더링하여 에러 전파를 무조건 끊어낸다.&lt;/p&gt;
&lt;p&gt;말로 하는 것보다 직접 보는 것이 훨씬 낫다. 위 구조를 바탕으로 만든 예제를 같이 보자.&lt;/p&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/my-error-handling-manner-w2569?fontsize=14&amp;hidenavigation=1&amp;theme=dark&quot;
  style=&quot;width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;&quot;
  title=&quot;my-error-handling-manner&quot;
  allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot;
  sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;
&gt;&lt;/iframe&gt;
&lt;p&gt;실제 팀 프로젝트에서는 react-query를 사용하고 있지만, react-query를 사용하지 않고 유사한 방식으로 구현해보았다.
큰 어려움 없이 구현할 수 있었는데, react-query를 사용하지 않는 프로젝트에도 위의 구조를 충분히 적용할 수 있음을 알 수 있었다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; userProfile &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useUserProfile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;onError&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;alert : &apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;useErrorBoundary&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;예제에서 가장 핵심적인 부분은 위 코드라고 생각한다.
네트워크 요청으로 데이터를 받아오기 위해서 훅을 사용하고 있고,
컴포넌트에서 훅 에러 처리를 위해 &lt;code class=&quot;language-text&quot;&gt;onError&lt;/code&gt; 인자를 넘기고 있다.
그리고 에러 바운더리로 에러 전파가 필요하다면 &lt;code class=&quot;language-text&quot;&gt;useErrorBoundary&lt;/code&gt;를 true로 설정해준다.&lt;/p&gt;
&lt;h3&gt;중요도에 따라 에러 종류 정의하기&lt;/h3&gt;
&lt;p&gt;먼저 나는 에러의 중요도를 &lt;code class=&quot;language-text&quot;&gt;사용자에게 얼마나 적극적으로 알려야 하는가&lt;/code&gt;로 정의했다.
중요도가 높을수록 사용자에게 적극적으로 알려야하는 것이다. 나는 이러한 중요도에 따라 에러를 3가지로 분류했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;에러가 발생한 컴포넌트만 에러 화면으로 대체하는 방식 (小 에러)&lt;/li&gt;
&lt;li&gt;alert, snackbar 등 사용자에게 강제로 보여주는 방식 (中 에러)&lt;/li&gt;
&lt;li&gt;1번과 2번을 모두 사용하는 방식(大 에러)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;앞서 언급한 에러 전파 구조에 이와 같은 분류를 적용했을 때,
컴포넌트에서는 中 에러만을 처리하고 에러 바운더리에서는 小 에러만을 처리하는 구조가 만들어졌다.
大 에러를 처리하기 위해서는 컴포넌트에서 中 에러를 처리하고 에러 바운더리로 throw만 하면 된다.&lt;/p&gt;
&lt;p&gt;이러한 구조를 적용했을 때 아무런 기준 없이 에러를 처리하던 기존 방식보다 훨씬 에러를 분류하기도 좋았고 코드를 작성하기에도 명료했다.&lt;/p&gt;
&lt;h2&gt;📦 번외. 에러 메세지 매핑&lt;/h2&gt;
&lt;p&gt;여기서 추가로 고민해보면 좋을 사항이 있다. 백엔드로부터 받아온 에러 메세지를 어떻게 사용자에게 보여줄 것인지에 관한 내용이다.
백엔드로부터 받아온 에러 메세지를 그대로 사용자에게 보여줄 수도 있겠지만 이렇게 하는 경우 프론트에서 사용자에게 보여줄 메세지를 변경하려면 백엔드 팀원에게 부탁해야 할 것이다. 이 상황은 어색하게 느껴진다. 사용자와 맞닿아 있는 곳은 프론트이기 때문에 사용자를 위한 메세지는 프론트에서 관리하고, 프론트와 맞닿아 있는 곳은 백엔드이기 때문에 백엔드에서 보내주는 메세지는 프론트 개발자를 위한 메세지로 관리하는 것이 더 자연스러울 것이다.
그래서 나는 에러 응답에 에러 메세지와 에러 코드를 담아 보내도록 백엔드와 협의하였다.
예를 들어 다음과 같이 관리했다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/129390012-c0ab333e-3ed3-4960-8ad8-dbe396512523.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;이를 토대로 백엔드에서 에러가 발생했을 시 프론트로 다음과 같은 에러 응답을 보낸다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;errorCode&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;feed-001&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;errorMessage&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;존재하지 않는 피드입니다.&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;받아온 에러 메세지는 &lt;code class=&quot;language-text&quot;&gt;console.error()&lt;/code&gt;로 출력하고 에러 코드를 프론트 내의 에러 메세지 객체와 매핑하여
사용자를 위한 에러 메세지도 프론트에서 관리할 수 있도록 하였다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;이번 글에서는 &lt;code class=&quot;language-text&quot;&gt;react에서 어떻게 에러 처리를 하는 것이 좋을지&lt;/code&gt;에 대한 주제를 다뤄보았다.
글을 작성하면서 느낀 것은 어떤 문제에 대한 해결책을 찾기 위해서는 일단 문제에 대한 상세한 정의가 중요함을 느꼈다.
에러 처리 방법을 고민하기 전에 에러 처리의 정의를 내려야 하는 것처럼 말이다.&lt;/p&gt;
&lt;p&gt;에러 처리에서 아직 부족한 점이 많지만, 처음부터 완벽할 순 없다.
앞으로 여러 팀에서 일을 해보고 경험을 쌓으면서 좀 더 효율적인 에러 처리 방법을 찾을 수 있다면 좋겠다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://jbee.io/react/error-declarative-handling-1/&quot;&gt;리액트에서 에러와 로딩을 선언적으로 다루기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://rinae.dev/posts/how-to-handle-errors-1&quot;&gt;에러 처리를 어떻게 하면 좋을까? - 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.digitalocean.com/community/tutorials/react-error-boundaries&quot;&gt;A Simple Guide to Error Boundaries in React&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[CI/CD가 뭔가요? - 이론편]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-14-ci-cd/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-14-ci-cd/</guid><pubDate>Sat, 14 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;우리는 여태까지 어떻게 하면 더 좋은 코드를 만들어낼 수 있을지에 대해서 고민해왔다.&lt;/p&gt;
&lt;p&gt;하지만 궁극적으로 우리가 하고 싶은 것은 사용자에게 우리가 멋드러지게 만든 코드를 결과물로 만들어 보여주는 것이다.&lt;/p&gt;
&lt;p&gt;어떻게 하면 사용자에게 우리의 코드를 보여줄 수 있을까?&lt;/p&gt;
&lt;br&gt;
&lt;ol&gt;
&lt;li&gt;컴파일&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;첫번째로 우리가 만든 코드를 &lt;strong&gt;컴파일&lt;/strong&gt; 한다.&lt;/p&gt;
&lt;p&gt;컴파일이란 우리가 만든 프로그래밍 언어를 기계가 이해할 수 있는 기계의 언어로 번역하는 것이다.&lt;/p&gt;
&lt;p&gt;우리가 사용한 java, c와 같은 프로그래밍 언어는 기계가 이해할 수 없다. 이렇게 개발자의 편의를 위해 개발자의 언어로 작성한 프로그래밍 언어를 &lt;code class=&quot;language-text&quot;&gt;컴파일러&lt;/code&gt;가 컴파일 해 기계가 이해할 수 있는 언어로 번역해준다.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;빌드&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;다음은 컴파일된 기계의 언어를 사용자에게 보여주기 위해 &lt;strong&gt;빌드&lt;/strong&gt;하여 완성된 상품, 소프트웨어 가공물로 만든다.&lt;/p&gt;
&lt;p&gt;java에서는 &lt;code class=&quot;language-text&quot;&gt;maven, gradle&lt;/code&gt;과 같은 빌드 도구를 이용하면 컴파일과 함께 소스코드 파일을 &lt;code class=&quot;language-text&quot;&gt;.jar, .war&lt;/code&gt; 와 같은 산출물로 변환하는 빌드도 함께 할 수 있다.&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;배포&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이렇게 만들어진 산출물을 각각의 서버에서 동작하도록 하여 상품을 사용자들에게 공개하는 것이 &lt;strong&gt;배포&lt;/strong&gt;이다.  최종적으로 만들어진 상품을 배포해 사용자에게 사용하게 하는 것이 우리의 목적이자 개발을 하는 이유가 되는 것이다.&lt;/p&gt;
&lt;p&gt;프로젝트를 개발하고 배포를 진행할 때 많은 이들이 CI/CD에 대해 언급하곤 한다. 이 때 말하는 CI/CD란 무엇이고 왜 적용해야할까?&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;CI/CD&lt;/h3&gt;
&lt;p&gt;사용자에게 우리가 만들어낸 프로젝트를 배포했는데 어떠한 동작이 올바르게 동작하지 않아 문제가 발생했다고 가정해보자.&lt;/p&gt;
&lt;p&gt;개발자들에게는 비상이 걸릴 것이다. 급한 프로젝트일수록 더욱 빠르게 문제를 수정해야만 한다. 수정을 했으면 다시 컴파일, 빌드, 배포하는 과정을 통해 수정된 코드가 제대로 동작하는지 테스트하고 검증할 필요가 있다. 이 과정들은 시간도 많이 걸리고 실수하기도 쉽다.&lt;/p&gt;
&lt;p&gt;수정된 코드에 문제가 다시 생기면 또 다시 과정을 반복해야한다. 이를 위해서 CI/CD가 생겨났다.&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;CI (Continuous Integration)&lt;/h3&gt;
&lt;p&gt;CI는 &lt;strong&gt;지속적 통합&lt;/strong&gt;이라는 뜻으로 개발을 진행하면서도 품질을 관리할 수 있도록 하는 것으로 여러 명이 하나의 코드에 대해서 수정을 진행해도 지속적으로 통합하면서 관리할 수 있음을 의미한다.&lt;/p&gt;
&lt;p&gt;CI가 나오기 전까지는 위와 같이 개발을 끝마치고 배포가 되어야만 코드에 오류는 없는지, 올바르게 동작하는지를 검증하며 코드 품질을 관리할 수 있었다.&lt;/p&gt;
&lt;p&gt;CI를 적용하게 되면 각자의 개발자가 자신의 구현해야 할 기능을 구현하면 된다. 이후 완성이 되면 main 브랜치와 통합하고 코드가 잘 빌드되는지 보고, 올바르게 동작하는지 테스트하며 코드에 버그가 있다면 해결한다.&lt;/p&gt;
&lt;p&gt;하지만 개발자가 직접 코드를 병합하고 빌드, 테스트를 검증하는 것은 시간이 많이 소요될 뿐만 아니라 귀찮고 그 양도 프로젝트의 크기가 커질수록 많아질 수밖에 없다.&lt;/p&gt;
&lt;p&gt;이를 &lt;strong&gt;자동화&lt;/strong&gt;하면 개발자가 빌드와 테스트를 직접 하지 않고도 수정한 코드를 브랜치에 병합하기만 하면 자동으로 빌드와 테스트를 검증할 수 있다.&lt;/p&gt;
&lt;p&gt;개발자가 단위별로 구현한 부분을 병합할 때마다 자동화된 빌드와 테스트가 &lt;strong&gt;트리거&lt;/strong&gt;되어 실행된다. 결과를 통해 우리는 어떤 부분에서 문제가 있는지 배포 전에 확인할 수 있고, 배포가 완성된 후에야 버그를 수정할 수 있던 기존의 문제를 빠르고 정확하게 해결할 수 있다.&lt;/p&gt;
&lt;p&gt;결과적으로 버그를 신속하게 찾아 해결할 수 있을 뿐 아니라, 소프트웨어 품질을 개선하고 새로운 소프트웨어 업데이트를 검증하고 릴리즈하는데에 걸리는 시간을 단축할 수도 있다.&lt;/p&gt;
&lt;p&gt;요약하자면 CI의 간단한 순서는 아래와 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;개발자가 구현한 코드를 기존 코드와 병합한다.&lt;/li&gt;
&lt;li&gt;병합된 코드가 올바르게 동작하고 빌드되는지 검증한다.&lt;/li&gt;
&lt;li&gt;테스트 결과 문제가 있다면 수정하고 다시 1로 돌아간다. 문제가 없다면 배포를 진행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;
&lt;h3&gt;CD(Continuous Deployment)&lt;/h3&gt;
&lt;p&gt;이제 지속적 통합을 거친 코드에 대해서 신뢰할 수 있고 바로 배포할 수 있다.&lt;/p&gt;
&lt;p&gt;CD는 &lt;strong&gt;지속적 배포&lt;/strong&gt;로 소프트웨어가 항상 신뢰 가능한 수준에서 배포될 수 있도록 관리하자는 개념으로 &lt;strong&gt;지속적 제공&lt;/strong&gt;(Continuous Delivery)로 사용되기도 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;지속적 제공&lt;/strong&gt;은 CI를 통해서 새로운 소스코드의 빌드와 테스트 병합까지 성공적으로 진행되었다면, 빌드와 테스트를 거쳐 github과 같은 저장소에 업로드하는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;지속적 배포&lt;/strong&gt;는 이렇게 성공적으로 병합된 내역을 저장소뿐만 아니라 사용자가 사용할 수 있는 배포환경까지 릴리즈하는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;지속적 배포에서는 지속적 통합을 통해 빌드한 소스코드를 테스트 가능한 알파나 베타 버전으로 만든다. 이 버전에서 테스트를 수행해 문제가 발생하면 수정한 뒤 정식 버전으로 배포를 진행한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/43775108/129401468-6f2ea859-5a9f-4a55-8d56-ffde5e8ca4ea.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;대표적인 CI/CD의 방법으로는 &lt;strong&gt;Travis&lt;/strong&gt;와 &lt;strong&gt;Jenkins&lt;/strong&gt;가 있다.&lt;/p&gt;
&lt;p&gt;오늘은 간단히 Jenkins 서버를 구축하는 순서에 대해서 알아보고, 다음 글에서 이와 관련한 실습을 이어가도록 하겠다.&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;Jenkins 구축하기&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;자신의 서버에 알맞게 Jenkins를 설치한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;설치된 Jenkins에 접속해 기본적인 설정을 한다.&lt;/p&gt;
&lt;p&gt;플러그인을 설치하고 user를 등록한다. 접속할 url도 지정한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Jenkins 관리에서 자신의 프로젝트에 알맞는 설정을 한다. 다양한 plugin을 추가할 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;새로운 job을 추가해 Jenkins에게 부여하고 싶은 일을 지정한다. 여기서 다양한 파이프라인을 줄 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;참고&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.redhat.com/ko/topics/devops/what-is-ci-cd&quot;&gt;RedHat&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[docker 이해하기]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-14-docker/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-14-docker/</guid><pubDate>Sat, 14 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이 글은 도커를 처음 사용해보거나 아직 도커에 익숙하지 않은 독자들을 대상으로 작성하였습니다.&lt;/p&gt;
&lt;h2&gt;도커란?&lt;/h2&gt;
&lt;p&gt;도커는 애플리케이션을 신속하게 구축, 테스트 및 배포할 수 있는 소프트웨어 플랫폼이다. 소프트웨어를 컨테이너라는 표준화된 유닛으로 패키징하며, 컨테이너는 라이브러리, 시스템 도구, 코드 등 소프트웨어 실행에 필요한 모든 것이 포함되어 있다. 즉, 도커는 컨테이너 환경에서 독립적으로 애플리케이션을 실행할 수 있도록 컨테이너를 만들고 관리하는 것을 도와주는 도구이다. 도커를 통해 애플리케이션을 실행하면 독립적인 환경에서 일관된 결과를 보장한다. 도커의 핵심 개념은 이미지와 컨테이너다. 그럼 먼저 컨테이너를 알아보자.&lt;/p&gt;
&lt;h3&gt;컨테이너&lt;/h3&gt;
&lt;p&gt;컨테이너는 격리된 공간에서 프로세스가 동작하는 기술이다. 기존의 가상화 방식인 OS 가상화가 아닌 프로세스를 격리하는 방식으로 동작한다. 리눅스에서 프로세스를 격리하는 방식을 리눅스 컨테이너라고 한다. 단순히 프로세스를 격리하기 때문에 가볍고 빠르다. 또한 CPU나 메모리는 프로세스가 필요한 만큼만 추가 사용하여서 성능적으로 거의 손실이 없다.
아래 그림은 도커(왼쪽)와 가상머신(오른쪽)을 나타낸 것이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63634505/129429515-b639f8c7-d123-46b6-b303-3a3313819be3.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;컨테이너는 다음과 같은 특징이 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서버에 여러 컨테이너를 실행하면 독립적으로 실행되어 VM(Virtual Machine) 을 사용하는 느낌을 준다.&lt;/li&gt;
&lt;li&gt;실행 중인 컨테이너에 접속하여 명령어를 입력할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;apt-get&lt;/code&gt; 이나 &lt;code class=&quot;language-text&quot;&gt;yum&lt;/code&gt; 등 운영체제에서 사용하는 패키지 매니저를 통해 설치할 수 있고 사용자도 추가하고 프로세스를 백그라운드로 실행할 수 있다.&lt;/li&gt;
&lt;li&gt;CPU나 메모리 사용량을 제한할 수 있다&lt;/li&gt;
&lt;li&gt;호스트의 특정 포트와 연결하거나 호스트의 특정 디렉토리를 내부 디렉토리인 것처럼 사용 가능&lt;/li&gt;
&lt;li&gt;새로운 컨테이너를 만드는데 1~2초로 매우 빠르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;이미지&lt;/h3&gt;
&lt;p&gt;컨테이너 실행에 필요한 파일과 설정을 포함하고 있는 것으로 상태값을 가지지 않고 변하지 않는다. 컨테이너는 이미지를 실행한 상태이다. 추가되거나 변하는 값은 컨테이너에 저장된다. 같은 이미지에서 여러 개의 컨테이너를 만들 수 있고 컨테이너의 상태가 바뀌거나 삭제되어도 이미지는 변하지 않고 그대로 남아있다. 도커 이미지는 Docker hub에 등록하거나 Docker Registry 저장소를 직접 만들어 관리할 수 있다.&lt;/p&gt;
&lt;p&gt;이미지는 다음과 같은 특징이 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;도커 이미지의 용량은 보통 수백 MB ~ 수 GB이지만 가상머신에 비하면 작은 용량이다&lt;/li&gt;
&lt;li&gt;상태값을 가지지 않고 변하지 않는다&lt;/li&gt;
&lt;li&gt;하나의 이미지를 통해 여러 컨테이너를 생성할 수 있고, 컨테이너를 삭제해도 이미지는 변하지 않는다&lt;/li&gt;
&lt;li&gt;이미지들은 Docker Hub를 통해 버전 관리 및 배포가 가능&lt;/li&gt;
&lt;li&gt;도커는 Dockerfile이라는 파일로 이미지를 만든다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Dockerfile&lt;/h3&gt;
&lt;p&gt;도커 이미지를 만들기 위해 Dockerfile이라는 파일에 DSL(Domain Specific Language) 언어를 이용해 이미지를 생성할 수 있다. 단순 텍스트 파일로 일반적으로 소스와 함께 관리한다. 서버에서 프로그램을 설치하려고 할 때 Dockerfile 을 통하여 관리하면 된다. Dockerfile에서 사용할 수 있는 키워드는 20개 정도 있다. 여기서 중요한 건 &lt;code class=&quot;language-text&quot;&gt;FROM&lt;/code&gt; 과 &lt;code class=&quot;language-text&quot;&gt;RUN&lt;/code&gt; 이다. &lt;code class=&quot;language-text&quot;&gt;FROM&lt;/code&gt; 과 &lt;code class=&quot;language-text&quot;&gt;RUN&lt;/code&gt; 으로 이미지를 만들 수 있다.&lt;/p&gt;
&lt;p&gt;여기서는 Dockerfile의 기본이 되는 &lt;code class=&quot;language-text&quot;&gt;FROM&lt;/code&gt; 과 &lt;code class=&quot;language-text&quot;&gt;RUN&lt;/code&gt; 에 대해 알아보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FROM&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;FROM &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;image&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;:&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;tag&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
FROM ubuntu:16.04&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;베이스 이미지를 지정한다. 반드시 베이스 이미지를 지정해야 하며 어떠한 이미지도 베이스 이미지가 될 수 있다. tag는 버전을 지정하는 것으로 가능하면 구체적인 버전을 지정하는 것이 좋다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RUN&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;RUN &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;command&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
RUN bundle &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;가장 많이 사용하는 구문 중 하나로 말 그대로 명령어를 실행한다. 내부적으로 &lt;code class=&quot;language-text&quot;&gt;/bin/sh -c&lt;/code&gt; 뒤에 명령어를 실행하는 방식&lt;/p&gt;
&lt;h2&gt;도커 설치하기&lt;/h2&gt;
&lt;p&gt;도커는 리눅스 컨테이너 기술이라 mac이나 windows에서는 가상머신에 설치가 된다. 여기서는 mac이나 window가 아닌 리눅스에서 설치하는 방법을 알아보자.&lt;/p&gt;
&lt;h3&gt;Linux&lt;/h3&gt;
&lt;p&gt;리눅스에서는 자동 설치 스크립트를 활용하여 도커를 설치한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-fsSL&lt;/span&gt; https://get.docker.com/ &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sh&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;도커 명령어&lt;/h2&gt;
&lt;p&gt;도커를 실행하는 명령어는 다음과 같다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;docker&quot;&gt;&lt;pre class=&quot;language-docker&quot;&gt;&lt;code class=&quot;language-docker&quot;&gt;docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG..]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;자주 사용하는 옵션&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63634505/129429541-eb7b464b-c371-4ee2-861f-44e00a2414ec.png&quot; alt=&quot;image&quot;&gt;
&lt;code class=&quot;language-text&quot;&gt;run&lt;/code&gt; 명령어를 사용하면 사용할 이미지가 저장되어 있는지 확인하고 없으면 다운로드를 한 후 컨테이너를 생성하고 시작 한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;docker&quot;&gt;&lt;pre class=&quot;language-docker&quot;&gt;&lt;code class=&quot;language-docker&quot;&gt;docker run --rm -it ubuntu:16.04 /bin/bash&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;컨테이너 내부에 들어가기 위해 뒤에 &lt;code class=&quot;language-text&quot;&gt;/bin/bash&lt;/code&gt; 를 붙이고 키보드 입력을 위해 &lt;code class=&quot;language-text&quot;&gt;-it&lt;/code&gt; 옵션을 준다. 추가로 프로세스가 종료되면 컨테이너가 자동 삭제 되도록  &lt;code class=&quot;language-text&quot;&gt;--rm&lt;/code&gt; 옵션도 추가했다.&lt;/p&gt;
&lt;h2&gt;컨테이너 명령어&lt;/h2&gt;
&lt;h3&gt;컨테이너 목록 확인하기(ps)&lt;/h3&gt;
&lt;p&gt;컨테이너 목록을 확인하는 명령은 다음과 같다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;docker&quot;&gt;&lt;pre class=&quot;language-docker&quot;&gt;&lt;code class=&quot;language-docker&quot;&gt;docker ps [OPTIONS]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ps&lt;/code&gt; 명령어는 실행 중인 컨테이너 목록을 보여준다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;-a&lt;/code&gt; 옵션은 종료된 컨테이너까지 목록으로 보여준다. 컨테이너는 종료되어도 삭제되지 않고 남아있다.&lt;/p&gt;
&lt;h3&gt;컨테이너 중지하기(stop)&lt;/h3&gt;
&lt;p&gt;실행 중인 컨테이너를 중지하는 명령어, 실행 중인 컨테이너를 하나 또는 그 이상을 중지할 수 있다.(띄어쓰기로 구분)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;docker&quot;&gt;&lt;pre class=&quot;language-docker&quot;&gt;&lt;code class=&quot;language-docker&quot;&gt;docker stop [OPTIONS] CONTAINER [CONTAINER...]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;도커 ID의 길이는 64자리이다. 명령어의 인자로 전달할 때는 전부 입력하지 않아도 된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;컨테이너 제거하기(rm)&lt;/h3&gt;
&lt;p&gt;종료된 컨테이너를 제거하는 명령어, 종료된 컨테이너를 하나 또는 여러 개 삭제할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;docker&quot;&gt;&lt;pre class=&quot;language-docker&quot;&gt;&lt;code class=&quot;language-docker&quot;&gt;docker rm [OPTIONS] CONTAINER [CONTAINER...]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;docker rm -v $(docker ps -a -q -f status=exited)&lt;/code&gt; 명령어를 입력하면 중지된 컨테이너 ID를 가져와서 한 번에 삭제한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;컨테이너 로그 보기(logs)&lt;/h3&gt;
&lt;p&gt;컨테이너가 정상적으로 동작하는지 확인하는 좋은 방법. 로그를 확인하는 방법&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;docker&quot;&gt;&lt;pre class=&quot;language-docker&quot;&gt;&lt;code class=&quot;language-docker&quot;&gt;docker logs [OPTIONS] CONTAINER&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;--tail&lt;/code&gt; 마지막 줄부터 몇 줄을 출력할지 설정할 수 있다. ( ex, &lt;code class=&quot;language-text&quot;&gt;--tail 10&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;-f&lt;/code&gt; 실시간으로 생성되는 로그를 볼 수 있다.&lt;/p&gt;
&lt;h3&gt;컨테이너 명령어 실행하기(exec)&lt;/h3&gt;
&lt;p&gt;실행중인 컨테이너에 들어가보거나 컨테이너 파일을 실행하고 싶을때 사용한다. 컨테이너 명령어를 실행하는 방법&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;docker&quot;&gt;&lt;pre class=&quot;language-docker&quot;&gt;&lt;code class=&quot;language-docker&quot;&gt;docker exec [OPTIONS] CONTAINER COMMAND [ARG..]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;이미지 명령어&lt;/h2&gt;
&lt;h3&gt;이미지 목록 확인하기(images)&lt;/h3&gt;
&lt;p&gt;도커가 다운로드한 이미지 목록을 보는 명령어&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;docker&quot;&gt;&lt;pre class=&quot;language-docker&quot;&gt;&lt;code class=&quot;language-docker&quot;&gt;docker images [OPTIONS] [REPOSITORY[:TAG]]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;이미지 다운로드 하기(pull)&lt;/h3&gt;
&lt;p&gt;이미지를 다운로드 하는 명령어&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;docker&quot;&gt;&lt;pre class=&quot;language-docker&quot;&gt;&lt;code class=&quot;language-docker&quot;&gt;docker pull [OPTIONS] NAME[:TAG|@DIGEST]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;run&lt;/code&gt; 명령어를 입력하면 이미자가 없을 때 자동 다운이 된다. &lt;code class=&quot;language-text&quot;&gt;pull&lt;/code&gt; 명령어는 최신버전 다운로드를 할 경우 사용한다.&lt;/p&gt;
&lt;h3&gt;이미지 삭제하기(rmi)&lt;/h3&gt;
&lt;p&gt;이미지를 삭제하는 방법(컨테이너가 실행중인 이미지는 삭제되지 않는다.)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;docker&quot;&gt;&lt;pre class=&quot;language-docker&quot;&gt;&lt;code class=&quot;language-docker&quot;&gt;docker rmi [OPTIONS] IMAGE [IMAGE...]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;도커란 무엇이고 도커의 이미지와 컨테이너가 무엇인지 이번 글에서 알아보았다. 또한 리눅스에서 도커를 설치하는 방법과 컨테이너를 실행, 중지, 제거 및 목록확인 하는 방법 이미지를 다운로드, 삭제하는 방법들을 알아보았다. 아직 이 정도로는 실질적으로 도커를 제대로 활용하기는 어려울 것이다. 하지만 도커를 처음 접하는 독자들에게 조금이나마 보탬이 되었으면 한다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.docker.com/resources/what-container&quot;&gt;docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://subicura.com/2017/01/19/docker-guide-for-beginners-1.html&quot;&gt;초보를 위한 도커 안내서&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[웹 소켓에 대해 알아보자! - 이론 편]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-14-web-socket/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-14-web-socket/</guid><pubDate>Sat, 14 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p style=&quot;font-family: sans-serif; text-align: center; color: #aaa; margin-bottom: 3em; font-size: 85%&quot;&gt;image origin: &lt;a href=&quot;https://unsplash.com/photos/vT9zeLCOpps&quot;&gt;unsplash.com&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;웹 소켓에 대해 알아보자! - 이론 편&lt;/h2&gt;
&lt;p&gt;실시간 알림, 실시간 채팅 등 실시간이라는 키워드가 들어가는 기능들을 위해서는 대부분 웹 소켓 기술이 필요하다.&lt;/p&gt;
&lt;p&gt;이 글에서는 다음과 같은 주제를 다룬다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;웹 소켓이 무엇인지&lt;/li&gt;
&lt;li&gt;웹 소켓의 탄생 배경&lt;/li&gt;
&lt;li&gt;웹 소켓의 동작 방식&lt;/li&gt;
&lt;li&gt;웹 소켓과 HTTP의 차이점&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;웹 소켓이란?&lt;/h3&gt;
&lt;p&gt;HTML5 표준 기술로, HTTP 환경에서 클라이언트와 서버 사이에 하나의 TCP 연결을 통해 실시간으로 &lt;strong&gt;전이중 통신&lt;/strong&gt;을 가능하게 하는 &lt;strong&gt;프로토콜&lt;/strong&gt;이다.
여기서 전이중 통신이란, 일방적인 송신 또는 수신만이 가능한 단방향 통신과 달리 가정에서의 전화와 같이 양방향으로 송신과 수신이 가능한 것을 말한다.
양방향 통신이 아닌 단방향 통신의 예로는 텔레비전 방송, 라디오를 들 수 있는데, 데이터를 수신만 할 수 있고, TV나 라디오를 통해 데이터를 보낼 수 없다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;웹 소켓 기술이 없을 때는 &lt;code class=&quot;language-text&quot;&gt;Polling&lt;/code&gt;이나 &lt;code class=&quot;language-text&quot;&gt;Long polling&lt;/code&gt; 등의 방식으로 실시간은 아니지만 그에 준하게끔 구현하여 해결했다.&lt;/p&gt;
&lt;p&gt;지금은 웹 소켓의 등장으로 클라이언트와 서버간의 실시간 통신이 가능하게 되었다.&lt;/p&gt;
&lt;p&gt;따라서 SNS, 멀티 플레이어 게임, 구글 공유 문서 등 실시간 웹 어플리케이션 구현을 위해 웹 소켓을 사용하고 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;웹 소켓의 탄생 배경&lt;/h3&gt;
&lt;p&gt;초기 웹의 탄생 목적은 문서 전달과 하이퍼링크를 통한 문서 연결이었다.&lt;/p&gt;
&lt;p&gt;웹을 위한 HTTP 프로토콜은 이러한 목적에 매우 부합하는 모델이다.&lt;/p&gt;
&lt;p&gt;그러나 시대가 변하고 환경이 발전할 수록 웹에게 동적인 표현과 뛰어난 상호작용이 요구되었고 이로 인해 여러 새로운 기술이 탄생되었다.&lt;/p&gt;
&lt;p&gt;그 중 실시간 양방향 통신을 위한 스펙이 바로 웹 소켓인 것이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;웹 소켓의 동작 방식&lt;/h3&gt;
&lt;p&gt;웹 소켓은 전이중 통신이므로, 연속적인 데이터 전송의 신뢰성을 보장하기 위해 Handshake 과정을 진행한다.&lt;/p&gt;
&lt;p&gt;기존의 다른 TCP 기반의 프로토콜은 TCP layer에서의 Handshake를 통해 연결을 수립하는 반면, 웹 소켓은 HTTP 요청 기반으로 Handshake 과정을 거쳐 연결을 수립한다.&lt;/p&gt;
&lt;p&gt;웹 소켓은 연결을 수립하기 위해 &lt;code class=&quot;language-text&quot;&gt;Upgrade 헤더&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;Connection 헤더&lt;/code&gt;를 포함하는 HTTP 요청을 보낸다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50273712/129447412-30e32809-b1fe-4e95-9d7a-85553f3ab92b.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;통상적인 상태 코드 200 대신, 웹 소켓 서버의 응답은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50273712/129448362-0ada9130-1181-4d3b-ac4b-33bd4130b00d.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;101 Switching Protocols&lt;/code&gt;: Handshake 요청 내용을 기반으로 다음부터 WebSocket으로 통신할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Sec-WebSocket-Accept&lt;/code&gt;: 보안을 위한 응답 키&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Handshake 과정을 통해 연결이 수립되면 응용 프로그램 계층 프로토콜이 HTTP에서 웹 소켓으로 업그레이드가 된다.&lt;/p&gt;
&lt;p&gt;업그레이드가 되면 HTTP는 사용되지 않고, 웹 소켓 연결이 닫힐 때까지 두 끝 점에서 웹 소켓 프로토콜을 사용하여 데이터를 주고 받게 된다.&lt;/p&gt;
&lt;p&gt;웹 소켓 연결은 주로 새로고침이나 창 닫기 등의 이벤트 발생 시 닫힌다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;웹 소켓 vs HTTP&lt;/h3&gt;
&lt;p&gt;웹 소켓이 HTTP 요청으로 시작되며 HTTP에서 동작하지만, 두 프로토콜은 분명히 다르게 동작한다.&lt;/p&gt;
&lt;p&gt;웹 소켓과 HTTP의 차이점은 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;HTTP&lt;/strong&gt;는 클라이언트와 서버간 접속을 유지하지 않으며 요청과 응답 형태로 단방향 통신만 가능하다.
따라서 서버에서 클라이언트로의 요청은 불가능하다.
또한 요청-응답이 완료되면 수립했던 연결이 닫힌다.
&lt;strong&gt;웹 소켓&lt;/strong&gt;은 클라이언트와 서버간 접속이 유지되며 요청과 응답 개념이 아닌 서로 데이터를 주고 받는 형식이다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;REST한 방식의 &lt;strong&gt;HTTP&lt;/strong&gt; 통신에서는 많은 URI와 Http Method를 통해 웹 어플리케이션과 상호작용하지만,
&lt;strong&gt;웹 소켓&lt;/strong&gt;은 초기 연결 수립을 위한 오직 하나의 URL만 존재하며, 모든 메시지는 초기에 연결된 TCP 연결로만 통신한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;간단한 예제&lt;/h3&gt;
&lt;p&gt;웹 소켓 서버를 만드는 것은 &lt;code class=&quot;language-text&quot;&gt;WebSocketHandler&lt;/code&gt;를 구현하는 것으로부터 출발한다. 대개 &lt;code class=&quot;language-text&quot;&gt;TextWebsocketHandler&lt;/code&gt;나 &lt;code class=&quot;language-text&quot;&gt;BinaryWebSocketHandler&lt;/code&gt;를 상속받아 구현한다.
예제에서는 &lt;code class=&quot;language-text&quot;&gt;TextWebSocketHandler&lt;/code&gt;를 상속받아 구현한다. 추후 콘솔에서 메시지를 확인해보기 위해 메시지를 출력하기로 한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyHandler&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TextWebSocketHandler&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleTextMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WebSocketSession&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TextMessage&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPayload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;afterConnectionEstablished&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WebSocketSession&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;afterConnectionEstablished:&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;afterConnectionClosed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WebSocketSession&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CloseStatus&lt;/span&gt; status&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;afterConnectionClosed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; status&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;handleTextMessage&lt;/code&gt; 메서드는 웹 소켓 연결이 수립된 후, 해당 연결을 이용하는 세션과 메시지에 대해 다룬다.
&lt;code class=&quot;language-text&quot;&gt;afterConnectionEstablished&lt;/code&gt; 메서드는 연결이 수립된 직후의 행위를 지정한다.
&lt;code class=&quot;language-text&quot;&gt;afterConnectionClosed&lt;/code&gt; 메서드는 연결이 닫힌 직후의 행위를 지정한다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;이후, 구현한 웹 소켓 핸들러를 등록해주는 Config 클래스를 만들자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableWebSocket&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PureWebSocketConfig&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSocketConfigurer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;registerWebSocketHandlers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WebSocketHandlerRegistry&lt;/span&gt; registry&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        registry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;myHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/myHandler&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebSocketHandler&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;myHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;WebSocketHandlerRegistry&lt;/code&gt;에 &lt;code class=&quot;language-text&quot;&gt;WebSocketHandler&lt;/code&gt;의 구현체를 등록했다.
등록된 &lt;code class=&quot;language-text&quot;&gt;Handler&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;&quot;/myHandler&quot;&lt;/code&gt; 엔드 포인트로 Handshake를 완료한 후 맺어진 연결을 관리한다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;2021년 5월부터 Postman은 웹 소켓 요청을 지원한다. Postman으로 웹 소켓을 연결해 메세지를 보내고, IntelliJ 콘솔에서도 확인해보자.&lt;/p&gt;
&lt;p&gt;첫 화면에서 new 버튼을 클릭한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50273712/129508126-e1c034d4-5858-45bf-8081-c0f81d83b959.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;WebSocket Request를 클릭한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50273712/129508281-445ccbed-8396-4165-80e9-1078c5d48c99.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;웹 소켓 연결을 맺기 위해 위에서 등록했던 핸들러의 경로를 입력한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50273712/129508612-5400af8a-9d86-4063-8b8a-f740df7ad2f4.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;연결 성공 후 메시지를 보내면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50273712/129508714-7ae100a1-17df-4b47-880d-4dea746a4143.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;IntelliJ 콘솔에서는 다음과 같이 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50273712/129508834-a7152d85-0125-44af-a35e-051a0340803d.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;h3&gt;결론&lt;/h3&gt;
&lt;p&gt;실시간 웹 어플리케이션을 만들기 위해 필요한 기술인 웹 소켓의 개념에 대해 알아보았다.&lt;/p&gt;
&lt;p&gt;다음 글에서는 Spring Boot 환경에서 채팅 기능을 구현해볼 것이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Reference&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#websocket&quot;&gt;https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#websocket&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://asfirstalways.tistory.com/85&quot;&gt;https://asfirstalways.tistory.com/85&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://m.mkexdev.net/98&quot;&gt;https://m.mkexdev.net/98&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[프론트엔드 테스트 자동화 해보기.]]></title><description><![CDATA[우테코는 프로젝트 중 여름이 막바지에 접어드는 요즘, 우아한테크코스에서는 크루들이 모여 진행하는 프로젝트 기간인 레벨…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-13-test-automation/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-13-test-automation/</guid><pubDate>Fri, 13 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;우테코는 프로젝트 중&lt;/h2&gt;
&lt;p&gt;여름이 막바지에 접어드는 요즘, 우아한테크코스에서는 크루들이 모여 진행하는 프로젝트 기간인 레벨3도 끝나가고 있습니다. 코스는 프로젝트만 하라고 마냥 내버려두는게 아니라 일정기간마다 프로젝트에 적용해야 되는 과제를 내줍니다.  성능 측정을 통해 성능 개선하기, 배포 자동화하기, 사용성 테스트 진행하기 등등.&lt;/p&gt;
&lt;p&gt;그 중에서 프론트엔드 개발환경에서 테스트를 자동화해보라는 과제가 나왔습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;…예?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이전에 접해보지 않은 생소한 과제에 잠시 당황 했었는데요. 테스트러너를 동작시킬 때 —watch 옵션을 주라는 건지.. 팀원과 잠시 고민에 빠졌습니다.  곧 이런 테스트 러너를 직접 동작시키지 않고, 프로그래밍을 진행하면서 자연스럽게 테스트가 동작하도록 환경을 구성하라는 뜻으로 이해하게 됐습니다.&lt;/p&gt;
&lt;h2&gt;테스트 자동화 방법&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/129481126-6a61326f-5e5d-4e8d-8021-07b0ba08a1fb.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;Git을 활용해 진행하는 프로그래밍 루틴에 테스트를 녹이는 방법은 어디에서 진행하든 가능했습니다. 코드작성 -&gt; 프로그램 배포 과정 중 테스트가 어디에 위치하든 간에 테스트가 실패한다면, 다음 단계로 나아가지 못하게 하는 것이 키포인트였습니다.&lt;/p&gt;
&lt;p&gt;프로젝트에서는 이미 적용 중인 테스트 자동화 방법이 있었습니다.&lt;/p&gt;
&lt;h3&gt;1. Netlify: Deploy Preview&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/129477970-72ac1437-3e99-4e0e-9917-d3ead07d50c0.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;프로젝트를 정식으로 배포하기 전, 빠른 테스트를 위해서 netlify를 이용해 간이로 배포를 진행했습니다.  netlify에서는 netlify site를  Github Repository와 연결하면 강력한 기능을 제공합니다. 레포지토리의 웹어플리케이션에 대한 CI/CD를 클릭 몇 번만에 셋업해주는데요. 감시하고 있는 브랜치에 커밋이 올라오면 새롭게 netlify가 배포를 하게 됩니다. 이와 관련해서 Deploy Preview라는 기능도 제공하는데, repository에 PR이 등록되면 해당 PR에 대해 netlify 자체적으로 배포 + 테스트를 진행합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/129478241-f572fb42-f64a-412b-a4a2-cff710008c87.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;배포가 성공적으로 끝나면, netilfy github app이 PR의 코멘트에 위와 같은 메세지를 남기게 되고,  Browse the preview란의 url을 클릭하면 pr코드를 실제 배포된 환경에서 테스트해볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/129478325-28828199-eb26-4951-8501-54f0e093cde3.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;배포가 실패하면 위 메세지가 등장하고, PR을 merge할 때 경고문이 뜨게 됩니다.&lt;/p&gt;
&lt;p&gt;위 기능은 프로젝트 초기 빠르게 기능을 구현하는 단계에 많은 도움이 되었습니다. 새로 추가되는 기능이 오류가 나는 코드인지 별도의 테스트코드를 작성하지 않고도, 별도로 CI/CD 파이프라인을 구축하지 않고도 netlify가 알아서 모두 해결해준 것이죠.&lt;/p&gt;
&lt;p&gt;하지만 netlify는 서비스 로직이 어떻게 동작해야 하는지는 알지 못했기에, 팀에서 작성한 테스트코드를 자동화 해야했습니다.&lt;/p&gt;
&lt;h3&gt;2. husky&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/129478578-ee0df4a2-1af5-446b-8796-cdddc490f80e.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;첫 번째로 적용한 방법은 &lt;a href=&quot;https://typicode.github.io/husky/#/&quot;&gt;husky&lt;/a&gt;입니다. husky는 git commit hook을 이용해서 commit이 실행될 때마다 테스트, 코드 린팅, 커밋메시지 린팅 등을 실행시켜줍니다.&lt;/p&gt;
&lt;p&gt;husky를 이용해서 로컬에서 commit이 일어날 때마다 테스트가 동작해 테스트가 성공하는 경우에만 commit이 이뤄지도록 세팅했습니다.&lt;/p&gt;
&lt;p&gt;사용법은 정말 간단합니다.  아래와 같습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;npx husky-init &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# husky 설치&lt;/span&gt;
npx husky &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; .husky/pre-commit &lt;span class=&quot;token string&quot;&gt;&quot;yarn test&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# husky add &amp;lt;file&gt; [cmd] 입니다. file에는 git hook이름이 따라옵니다. [cmd]에는 실행할 쉘 커맨드를 입력합니다.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;husky로 이용할 수 있는 hook은 다양합니다. &lt;a href=&quot;https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks&quot;&gt;git 공식문서&lt;/a&gt; 링크를 남겨놓았으니 필요한 훅을 이용해보세요.&lt;/p&gt;
&lt;p&gt;저희는 git hook 중 push hook을 이용할 수도 있지만, 피드백은 빠르면 빠를수록 좋다는 판단으로 테스트 진행단위를 commit으로 잡았습니다.&lt;/p&gt;
&lt;h3&gt;3. 배포시에는 어떻게 할까?&lt;/h3&gt;
&lt;p&gt;이제 commit 단위에서 테스트자동화를 이뤘지만, 고민이 하나 남았습니다. Github Actions로 CI/CD파이프라인을 구축해놓은 상황에서 workflow가 실행 중일때 테스트를 해서 바로 배포할 앱이 무결한지 체크해야하는 상황이었습니다. 사실 이 경우는 생각보다 간단하게 해결할 수 있습니다. 파이프라인 상에서 테스트를 실행 시켜주면 되기 때문입니다. 테스트가 실패하게 된다면 프로그램 종료코드가 에러번호를 담고 종료될 것이므로 job은 실패합니다. 사실 위에서 소개한 방법들이 이 방법으로 테스트가 실패했을 때 커밋을 실패시키고, preview 배포도 중단시킨 것입니다. 프로세스 종료코드가 에러번호를 담을 수만 있다면 workflow yaml파일이든, dockerfile이든, 빌드 npm script이든 어디든 가능합니다.&lt;/p&gt;
&lt;p&gt;팀은 빌드 npm script에 테스트를 우선 수행하기로 했습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;build&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jest src &amp;amp;&amp;amp; webpack --mode=production --node-env=production&quot;&lt;/span&gt;,&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;amp;&amp;amp;&lt;/code&gt;연산자를 통해 &lt;code class=&quot;language-text&quot;&gt;jest src&lt;/code&gt;명령어를 먼저 실행시킵니다. &lt;code class=&quot;language-text&quot;&gt;&amp;amp;&amp;amp;&lt;/code&gt;연산자는 먼저 실행된 명령어가 오류코드를 담고 종료되면 이후 명령어를 실행시키지 않음으로 테스트가 실패한다면 빌드를 진행시키지 않을 수 있습니다.&lt;/p&gt;
&lt;p&gt;빌드 npm script자체가 테스트를 하도록 수정하게 되면, 이 빌드 npm script는 workflow yaml파일이든 dockerfile에서든 빌드가 필요한 모든 곳에서 실행될 명령어이기에 테스트를 강제할 수 있다는 것입니다.&lt;/p&gt;
&lt;h3&gt;4.  github action PR 테스트&lt;/h3&gt;
&lt;p&gt;github action은 &lt;a href=&quot;https://docs.github.com/en/actions/reference/events-that-trigger-workflows&quot;&gt;github의 이벤트를 활용할 수 있습니다&lt;/a&gt;. 그 말인즉슨, 처음에 소개해드렸던 netlify deploy-preview처럼 PR상에서 테스트를 진행할 수 있다는 것입니다.  마켓에 올라와있는 &lt;a href=&quot;https://github.com/marketplace/actions/github-action-tester&quot;&gt;액션&lt;/a&gt;을 이용해서 예제를 만들어보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; workflow&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;example

&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; pull_request &lt;span class=&quot;token comment&quot;&gt;# on에 pull_request를 적으면 jobs에 기재된 명령어들이 PR이 올라올 때마다 실행됩니다.&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Run tests
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; checkout PR			&lt;span class=&quot;token comment&quot;&gt;# 1.해당 PR에 checkout합니다&lt;/span&gt;
       &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@master
	&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; yarn install 		    &lt;span class=&quot;token comment&quot;&gt;# 2. 패키지를 설치합니다.&lt;/span&gt;
	   &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; yarn
	&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Test				   &lt;span class=&quot;token comment&quot;&gt;# 3. 테스트러너를 실행하고, 테스트가 실패/성공에 따라 해당 PR에 코멘트를 남깁니다.&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; skx/github&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;tester@master
      &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; yarn test&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;깃헙에서 스크립트 액션을 제공해주기 때문에, 기존의 액션을 이용하지 않고 직접 액션을 작성할 수도 있습니다.&lt;/p&gt;
&lt;h2&gt;자동화를 마치고 나서&lt;/h2&gt;
&lt;p&gt;테스트를 자동화하게 되니 로컬에서 구현한 기능들을 보다 안전하게 기존 코드와 통합할 수 있게 되었습니다. 물론 우리는 코드를 실행시켜서 직접 테스트 해야하고 주의를 계속 기울여야 합니다. UI와 관련된 에러가 발생할 수 있는 변수가 다분한 프론트엔드 영역에서는  더욱 그렇습니다. 자동화는 깜빡하고 넘어갈 수 있는 부분을 대신 한 번 체크해준 것일뿐입니다.  당연한 말이지만 테스트코드를 더욱 열심히 짜야 효과가 있는 작업입니다. 우리 모두 테스트를 작성하고, 테스트가 주는 혜택을 누려봅시다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[PWA에 대해 알아보자]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-11-progressive-web-app/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-11-progressive-web-app/</guid><pubDate>Wed, 11 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이번에 프로젝트로 웹 애플리케이션를 개발하면서 &lt;code class=&quot;language-text&quot;&gt;PWA(Progressive Web App)&lt;/code&gt; 라는 개념에 대해 접하게 되었다.&lt;/p&gt;
&lt;p&gt;웹 애플리케이션, 프론트엔드를 공부하는 입장에서 처음 접하는 용어에 대한 호기심이 생겼고, 이번 시간을 통해 &lt;code class=&quot;language-text&quot;&gt;PWA&lt;/code&gt; 에 대해 함께 알아보려고 한다.&lt;/p&gt;
&lt;h2&gt;모바일 앱&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;PWA&lt;/code&gt; 에 대해 알아보기 전에 &lt;code class=&quot;language-text&quot;&gt;PWA&lt;/code&gt; 가 등장하기 전의 모바일 앱에 대해 이해할 필요가 있었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;네이티브 앱(Native App)&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;네이티브 앱&lt;/code&gt;은 모바일 기기에서 가장 흔하게 사용하는 형태의 앱으로 안드로이드의 플레이 스토어 혹은 ios의 앱 스토어에서 다운받아서 사용하는 앱을 생각하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;네이티브 앱&lt;/code&gt;은 각 OS에 최적화되어 동작하며, OS의 자원을 최대한 활용할 수 있다는 장점이 있다. 하지만 각 OS에 맞는 환경에서 개발을 해야하기 때문에 더 많은 시간과 비용이 필요하고, 앱을 사용하기 위해서 설치를 해야한다는 집입 장벽이 존재한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;모바일 웹 앱(Mobile Web App)&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;모바일 웹 앱&lt;/code&gt;은 모바일 기기에 설치된 웹 브라우저를 기반으로 동작하는 앱으로 모바일을 통해 접근한 웹 사이트라고 생각하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;모바일 웹 앱&lt;/code&gt;은 별도의 설치 없이 대부분의 기기에서 사용이 가능하며 시간과 비용적인 측면에서 장점이 있다. 오프라인 상태에서는 서비스 이용이 제한되며, 브라우저 상에서 동작하기 때문에 OS의 자원을 활용하는 데에도 제약이 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;하이브리드 앱(Hybrid App)&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;하이브리드 앱&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;네이티브 앱&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;모바일 웹 앱&lt;/code&gt;의 장점을 살리면서 단점을 보완하고자 두 가지 방식을 결합한 형태의 앱이다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;하이브리드 앱&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;모바일 웹 앱&lt;/code&gt; 기술을 통해 개발을 하기 때문에 시간과 비용적인 측면에서 &lt;code class=&quot;language-text&quot;&gt;네이티브 앱&lt;/code&gt;보다 좋을 수 있지만, 여전히 &lt;code class=&quot;language-text&quot;&gt;네이티브 앱&lt;/code&gt;만큼 성능이 최적화 되거나 OS의 자원을 활용하는 데에는 제약이 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;PWA(Progressive Web App)&lt;/h2&gt;
&lt;p&gt;그럼 이제 &lt;code class=&quot;language-text&quot;&gt;PWA&lt;/code&gt; 에 대해 알아보자. &lt;code class=&quot;language-text&quot;&gt;PWA&lt;/code&gt; 는 &lt;code class=&quot;language-text&quot;&gt;Google I/O 2016&lt;/code&gt; 에서 소개된 개념으로 &lt;code class=&quot;language-text&quot;&gt;모바일 웹 앱&lt;/code&gt;을 마치 &lt;code class=&quot;language-text&quot;&gt;네이티브 앱&lt;/code&gt;스럽게 만들고자하는 방법론이다. 이 설명만 보았을 때는 &lt;code class=&quot;language-text&quot;&gt;하이브리드 앱&lt;/code&gt;와 비슷하게 느껴진다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;MDN&lt;/code&gt; 에서는 &lt;code class=&quot;language-text&quot;&gt;PWA&lt;/code&gt; 는 새로운 기술이 아닌 웹 앱을 개발하는 새로운 방법론이라는 것을 여러 차례에 걸쳐 말하고 있다. 기존의 기술들을 활용하여 &lt;code class=&quot;language-text&quot;&gt;모바일 웹 앱&lt;/code&gt;을 &lt;code class=&quot;language-text&quot;&gt;네이티브 앱&lt;/code&gt;처럼 사용할 수 있도록하는 것이다.&lt;/p&gt;
&lt;p&gt;반면 &lt;code class=&quot;language-text&quot;&gt;하이브리드 앱&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;모바일 웹 앱&lt;/code&gt; 기술로 앱을 개발하고, 프레임워크를 사용해서 네이티브 앱으로 패키징을 해주어야한다는 차이가 있다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;PWA&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;모바일 웹 앱&lt;/code&gt;과 구분되기 위한 몇 가지 원칙있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;발견 가능, 컨텐츠를 검색 엔진을 통해 찾을 수 있다.&lt;/li&gt;
&lt;li&gt;설치 가능, 홈 화면에서 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;연결 가능, 간단하게 URL을 전송해 공유할 수 있다.&lt;/li&gt;
&lt;li&gt;네트워크 독립적, 오프라인이나 불안한 네트워크 연결에서 동작한다.&lt;/li&gt;
&lt;li&gt;점진적, 최신 브라우저의 모든 기능은 사용할 수 없지만 이전 브라우저의 기능은 여전히 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;재 참여 가능, 알림을 보낼 수 있다.&lt;/li&gt;
&lt;li&gt;반응형, 모든 기기의 화면이나 브라우저에서 사용할 수 있다&lt;/li&gt;
&lt;li&gt;안전, 민감한 데이터에 접근하려는 모든 제3자로부터 안전하다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;설치를 통해 홈 화면의 아이콘을 탭하여 브라우저를 사용하여 이동이 가능하고 오프라인에서 동작할 수 있어야한다. 또한 푸시 메시지를 통해 사용자에게 알림을 제공해야한다.&lt;/p&gt;
&lt;p&gt;이러한 기능은 확실히 &lt;code class=&quot;language-text&quot;&gt;모바일 웹 앱&lt;/code&gt;보다는 &lt;code class=&quot;language-text&quot;&gt;네이티브 앱&lt;/code&gt;에서 많이 볼 수 있던 기능으로 서비스를 편리하게 사용하는데 도움을 주는 기능이다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;PWA&lt;/code&gt;에서 이를 가능하게 해주는 것은 서비스 워커와 웹 앱 매니페스트이다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;서비스 워커&lt;/code&gt;는 캐싱을 통해 오프라인 상태에서도 사용자가 &lt;code class=&quot;language-text&quot;&gt;모바일 웹 앱&lt;/code&gt;을 사용할 수 있도록 도와주는 역할을 한다. &lt;code class=&quot;language-text&quot;&gt;웹 앱 매니페스트&lt;/code&gt;는 사용자의 홈 화면에 설치할 수 있는 웹사이트를 구성하는 역할을 한다. &lt;code class=&quot;language-text&quot;&gt;모바일 웹 앱&lt;/code&gt;의 정보를 제공하여, 홈 화면 설치를 통한 더 빠른 접근을 가능하게 한다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;이번에 모바일 앱의 종류와 &lt;code class=&quot;language-text&quot;&gt;PWA&lt;/code&gt;에 대해 공부하면서, 모바일 앱은 성능적인 측면 뿐만아니라 사용자의 관점에서 사용성을 고려하며 발전해 나가고 있다는 걸 알 수 있었다.&lt;/p&gt;
&lt;h2&gt;참고 링크&lt;/h2&gt;
&lt;h3&gt;위키백과&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%A0%88%EC%8B%9C%EB%B8%8C_%EC%9B%B9_%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98&quot;&gt;PWA(Progressive web applications)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;MDN&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/Progressive_web_apps/Introduction&quot;&gt;PWA(Progressive web applications)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/Service_Worker_API&quot;&gt;Service Worker&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/Manifest&quot;&gt;Web app manifests&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;유튜브&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=FEBkne7Nyu4&quot;&gt;드림코딩 by 엘리 - PWA (Progressive Web App) 개념과 따라만들기&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=FEBkne7Nyu4&quot;&gt;얄팍한 코딩사전 - PWA가 뭔가요? (+모바일 앱의 종류)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[JVM에 관하여 - Part 3, Run-Time Data Area]]></title><description><![CDATA[Java 로 작성된 코드는 어떻게 돌아가는 걸까? 해당 물음에 답을 찾기 위한 JVM 시리즈 3편, JVM 의 구성요소 중 Run-Time Data Area 에 관한 글입니다.
이번 글에서는 JVM…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-09-jvm-memory/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-09-jvm-memory/</guid><pubDate>Mon, 09 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Java&lt;/em&gt; 로 작성된 코드는 어떻게 돌아가는 걸까? 해당 물음에 답을 찾기 위한 &lt;em&gt;JVM&lt;/em&gt; 시리즈 3편, &lt;em&gt;JVM&lt;/em&gt; 의 구성요소 중 &lt;em&gt;Run-Time Data Area&lt;/em&gt; 에 관한 글입니다.
이번 글에서는 &lt;em&gt;JVM&lt;/em&gt; 의 메모리 영역에서는 어떤 일이 일어나는지에 대해서 알아봅시다.&lt;/p&gt;
&lt;h2&gt;클래스 파일이 &lt;em&gt;JVM&lt;/em&gt; 에 탑재된 이후&lt;/h2&gt;
&lt;p&gt;지난 글에서는 클래스 파일들을 어떻게 &lt;em&gt;JVM&lt;/em&gt; 에 탑재하고 초기화가 되는지에 대해서 알아보았습니다.
이렇게 탑재하는 클래스 파일들은 &lt;em&gt;JVM&lt;/em&gt; 에서 어떤 영역을 차지하고 있는 것일까요?
&lt;em&gt;JVM&lt;/em&gt; 의 &lt;em&gt;Run-Time Data Area&lt;/em&gt; 에는 크게 &lt;em&gt;Method Area&lt;/em&gt; , &lt;em&gt;Heap&lt;/em&gt; , &lt;em&gt;Java Stacks&lt;/em&gt; , &lt;em&gt;PC registers&lt;/em&gt; 그리고 &lt;em&gt;Native Method Stacks&lt;/em&gt; 가 존재합니다.
각각의 영역이 어떤 역할을 하는지에 대해서 알아봅시다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1116px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a0b18cc999920474a1852901e1e46ebf/6f641/2021-08-09-jvm-runtime-data-area-structure.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 53.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAB1ElEQVR42m1TiY6iUBDk//9td0ZRURG5kfsQUBG1tvq92c2YHZIK2qmurj4wXq8XHo8H8dR46t/DeEXTntF239GhbjpM04znX/6/vCfkMab7/SuxR0Vy1bSo+f+UFfCCENu9A2tnK+xsB64foao7NCxQ1o3iSm577vGkqHG53jSh7bF3PJibLeK0QHcetHBeMbbDktAFO5z7EUGUwlxvcXBDJSjO7/cZxm2a4EUnZEUFWwRJik8ZEopKPIhPdHlgMRcR424Qs0hJwZMq7ni+4ibsSEZn3G4TwkQEcqytHX59LNhWiJTOgjjD0QuxWK6wMNdKJIhTpFmJw9HD788lNlubBgryS8xKUByGJEaJSt4fXM4uRsQiRz9mYsC2fNgUOHoRHSbKtRtEKuZ4AfkJHZZfDqc7W8lhcfjL1VbPi237dCP4NDdciIOVtWd8x0I5RULFMdeaa9lHFFWDeabglS3LTGQJsqlW3hyybFHQDxflPkkznIeRsV5Bc3uVV9Xtu6BsVc5Fgu9o1fm4fgA/jNXiJF7W71yJ50WtBWUpMtSSpyMC3yExP0zUlmUJMUfzE08WmJe1Om7jxeseL1dtvx/+g7Q8Xm7qy5GWf+J0zBVj8qX8AVFeN9q/54FiAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;JVM Run-Time Data Area 구조&quot;
        title=&quot;&quot;
        src=&quot;/static/a0b18cc999920474a1852901e1e46ebf/6f641/2021-08-09-jvm-runtime-data-area-structure.png&quot;
        srcset=&quot;/static/a0b18cc999920474a1852901e1e46ebf/0eb09/2021-08-09-jvm-runtime-data-area-structure.png 500w,
/static/a0b18cc999920474a1852901e1e46ebf/1263b/2021-08-09-jvm-runtime-data-area-structure.png 1000w,
/static/a0b18cc999920474a1852901e1e46ebf/6f641/2021-08-09-jvm-runtime-data-area-structure.png 1116w&quot;
        sizes=&quot;(max-width: 1116px) 100vw, 1116px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;Method Area&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Method Area&lt;/em&gt; 에는 인스턴스 생성을 위한 객체 구조, 생성자, 필드 등이 저장됩니다.
&lt;em&gt;Runtime Constant Pool&lt;/em&gt; 과 &lt;em&gt;static&lt;/em&gt; 변수, 그리고 메소드 데이터와 같은 &lt;em&gt;Class&lt;/em&gt; 데이터들도 이곳에서 관리가 됩니다.
이 영역은 &lt;em&gt;JVM&lt;/em&gt; 당 하나만 생성이 됩니다.
인스턴스 생성에 필요한 정보도 존재하기 때문에 &lt;em&gt;JVM&lt;/em&gt; 의 모든 &lt;em&gt;Thread&lt;/em&gt; 들이 &lt;em&gt;Method Area&lt;/em&gt; 을 공유하게 됩니다.
&lt;em&gt;JVM&lt;/em&gt; 의 다른 메모리 영역에서 해당 정보에 대한 요청이 오면, 실제 물리 메모리 주소로 변환해서 전달해줍니다.
기초 역할을 하므로 &lt;em&gt;JVM&lt;/em&gt; 구동 시작 시에 생성이 되며, 종료 시까지 유지되는 공통 영역입니다.&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;Heap&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Heap&lt;/em&gt; 영역은 코드 실행을 위한 &lt;em&gt;Java&lt;/em&gt; 로 구성된 객체 및 &lt;em&gt;JRE&lt;/em&gt; 클래스들이 탑재됩니다.
이곳에서는 문자열에 대한 정보를 가진 &lt;em&gt;String Pool&lt;/em&gt; 뿐만이 아니라 실제 데이터를 가진 인스턴스, 배열 등이 저장이 됩니다.
&lt;em&gt;JVM&lt;/em&gt; 당 역시 하나만 생성이 되고, 해당 영역이 가진 데이터는 모든 &lt;em&gt;Java Stack&lt;/em&gt; 영역에서 참조되어, &lt;em&gt;Thread&lt;/em&gt; 간 공유가 됩니다.
&lt;em&gt;Heap&lt;/em&gt; 영역이 가득 차게 되면 &lt;em&gt;OutOfMemoryError&lt;/em&gt; 를 발생시키게 됩니다.
다음은 인스턴스의 영역을 가득 차게 만들어서 해당 &lt;em&gt;Heap&lt;/em&gt; 영역에서의 &lt;em&gt;Error&lt;/em&gt; 발생시키는 코드입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Heap&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Heap 메모리 오류&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; num &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; nums &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LinkedList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                nums&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;num&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                num &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; num &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;num &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Heap 메모리 오류
Exception &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; thread &lt;span class=&quot;token string&quot;&gt;&quot;main&quot;&lt;/span&gt; java.lang.OutOfMemoryError: Java heap space
        at Heap.main&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Heap.java:16&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Heap&lt;/em&gt; 에서는 참조되지 않는 인스턴스와 배열에 대한 정보 또한 얻을 수 있기 때문에 &lt;em&gt;GC&lt;/em&gt; 의 주 대상이기도 합니다.
이때, 인스턴스가 생성된 후 시간에 따라서 다음과 같이 5가지 부분으로 나눌 수가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 548px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b1a4301290039e7cba3fa0afe9d34b39/9079b/2021-08-09-jvm-heap-structure.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 115.19999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAYAAAALHW+jAAAACXBIWXMAAA7DAAAOwwHHb6hkAAADMUlEQVR42q1VaW/aQBDN//8xVVVV6pGkrdJUSlJ6QLGNb2Mbgw3G2AZMuF5nBkwhROqXrPTkYXd2d+bNm+UCLzwuttutGJO8RBAl6A2GJ+gnKZJRJni6xgj7CebV4t+BtaFbXWSTEmU5Q15OMSnIns4Q9Qfo6AYc10OW58hpPi+m4jOdzjGkiyw3kDM4uIudAZncbDaHm+rIy7KEZZnwPPdkvR6LxSPs4wPrjfFoDNfvoRv2YTk+WoqOh8ZvQeNnS3D3/af8brY7MJ2u+PIezqYO7HBgQakKZ/EIQa8Pw/bQVnXcPfzA+4/XePfhiuwG/igdGJYLnw5j34j2cJRnHBp0Y5rlchtjNl9glGbEYYxBPBT4YSTFm84q4bEgHhPKzPF65xyalOZyucR6vcaKwCObTKAoKmzHgU6FMQwTVVWBc1qvN9gSp7P5/LQodcrRICE+IgThQPhRNJPS04nLDlrEmXz3dlszYNpd+L0YbjeSzE4i5LFarUVPFfExnc0RJ0M4jotms4Xb22+4+XqL32RbtkPpJyIp9mUcV/8QIevQ7fYkQp8Qksi7QZ8KFMOjyAVBhJAy4QZgH16z3VD8nnC4FQ7rm+pLsiyDqiqiQdM04LnuszrkvWccOhQdV8vf67DNHKqG6PGPuoOimiQlQ8A8c5QcIUvn7MCSeIv2OuRDddNB41cLnz7f4PWbt3j1+g0ur7+QsH9B0y1KM9rrcETqWB1zuDNMKn08HGM0nmA8Kagoqeiuz/qjzaw3XhvQIek4l8oyIlr3nuXwSS9PSIPNZhNBEEDTNOJSJU0qAtbifznkKtrEId9me4G0nmY4UCm9DqWvGTaUjiXQqfVYzF3i0CIOOeozHbLyq2qnq7woEPZ6ULUO7u+/4/LqGh8vqZfvH6gwGkmLHgSiRXRIe+qgTnQ4IM5GR9xwzzKX+eF9pN6l94/tjNeyglqzwJD6nX0Pr40Y/HzRgcc38eB3kPnTdZ2+xGG7jXGanmiVO4xf87OU42EqkWV5sY8ulzmf0vMD4tYPxY6T0e5FIh+OkCufEs45pApXjzsOF4fvEo/LGisBz7Gf+Oy/J7380v96fwGxBs20wS/79QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;JVM Heap Structure 구조&quot;
        title=&quot;&quot;
        src=&quot;/static/b1a4301290039e7cba3fa0afe9d34b39/9079b/2021-08-09-jvm-heap-structure.png&quot;
        srcset=&quot;/static/b1a4301290039e7cba3fa0afe9d34b39/0eb09/2021-08-09-jvm-heap-structure.png 500w,
/static/b1a4301290039e7cba3fa0afe9d34b39/9079b/2021-08-09-jvm-heap-structure.png 548w&quot;
        sizes=&quot;(max-width: 548px) 100vw, 548px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Eden&lt;/em&gt;, &lt;em&gt;Survivor0&lt;/em&gt;, &lt;em&gt;Survivor1&lt;/em&gt;, &lt;em&gt;Old&lt;/em&gt; , &lt;em&gt;Perm&lt;/em&gt; 으로 나누어지게 됩니다.
&lt;em&gt;Young Gen&lt;/em&gt; 이라고 불리는 비교적 신생 데이터 부분은 &lt;em&gt;Eden&lt;/em&gt; , &lt;em&gt;Survivor0&lt;/em&gt; , &lt;em&gt;Survivor1&lt;/em&gt; 입니다.
&lt;em&gt;Eden&lt;/em&gt; 에는 &lt;em&gt;new&lt;/em&gt; 를 통해 새롭게 생성된 인스턴스가 위치하며, 이후에는 &lt;em&gt;Survivor&lt;/em&gt; 로 이동하게 됩니다.
이곳에서도 참조되지 않는 인스턴스와 배열 대상으로 &lt;em&gt;Minor GC&lt;/em&gt; 가 일어납니다.
하지만 가장 주요하게 &lt;em&gt;GC&lt;/em&gt; 가 일어나는 부분은 그 이후의 부분인 &lt;em&gt;Old&lt;/em&gt; 부분입니다.
&lt;em&gt;Perm&lt;/em&gt; 의 경우에는 클래스의 메타 정보 및 &lt;em&gt;static&lt;/em&gt; 변수를 저장하고 있었습니다.
&lt;em&gt;Java 8&lt;/em&gt; 버전 이후로 &lt;em&gt;Native&lt;/em&gt; 영역에 존재하는 &lt;em&gt;Metaspace&lt;/em&gt; 라는 영역으로 대체되었습니다.
정확한 내용은 후에 &lt;em&gt;GC&lt;/em&gt; 파트에서 다루도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;또한, 각 &lt;em&gt;Thread&lt;/em&gt; 별로 메모리를 할당받는 &lt;em&gt;Java Stack&lt;/em&gt; 영역과 달리 조금은 속도가 느린 점이 있습니다.
그리고 앞서 언급했듯이 모든 &lt;em&gt;Thread&lt;/em&gt; 들이 해당 영역인 &lt;em&gt;Heap&lt;/em&gt; 을 공유하여 &lt;em&gt;Java&lt;/em&gt; 의 동시성 문제가 발생하게 됩니다.
각각의 &lt;em&gt;Thread&lt;/em&gt; 메모리가 따로 관리되는 것과 달리 이 부분은 &lt;em&gt;Thread&lt;/em&gt; 에 의해서 공유가 되기 때문에 &lt;em&gt;Thread Safe&lt;/em&gt; 하지 않습니다.
이 때문에 해당 영역에 있는 객체나 인스턴스를 사용하게 되면, &lt;em&gt;synchronized&lt;/em&gt; 블록을 사용하는 방법 등을 비롯하여 동시성을 지켜주는 방법을 사용해야 합니다.&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;Java Stacks&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;각 &lt;em&gt;Thread&lt;/em&gt; 별로 따로 할당되는 영역입니다. &lt;em&gt;Heap&lt;/em&gt; 메모리 영역보다 비교적 빠르다는 장점이 있습니다.
또한, 각각의 &lt;em&gt;Thread&lt;/em&gt; 별로 메모리를 따로 할당하기 때문에 동시성 문제에서 자유롭다는 점도 있습니다.
각 &lt;em&gt;Thread&lt;/em&gt; 들은 메소드를 호출할 때마다 &lt;em&gt;Frame&lt;/em&gt; 이라는 단위를 추가(&lt;em&gt;push&lt;/em&gt;)하게 됩니다.
메소드가 마무리되며 결과를 반환하면 해당 &lt;em&gt;Frame&lt;/em&gt; 은 &lt;em&gt;Stack&lt;/em&gt; 으로부터 제거(&lt;em&gt;pop&lt;/em&gt;)가 됩니다.
&lt;em&gt;Frame&lt;/em&gt; 은 메소드에 대한 정보를 가지고 있는 &lt;em&gt;Local Variable&lt;/em&gt;, &lt;em&gt;Operand Stack&lt;/em&gt; 그리고 &lt;em&gt;Constant Pool Reference&lt;/em&gt; 로 구성이 되어 있습니다.
&lt;em&gt;Local Variable&lt;/em&gt; 은 메소드 안의 지역 변수들을 가지고 있습니다.
&lt;em&gt;Operand Stack&lt;/em&gt; 은 메소드 내 연산을 위해서, 바이트 코드 명령문들이 들어있는 공간입니다.
&lt;em&gt;Constant Pool Reference&lt;/em&gt; 는 &lt;em&gt;Constant Pool&lt;/em&gt; 참조를 위한 공간입니다.
이렇게 구성된 &lt;em&gt;Java Stack&lt;/em&gt; 은 메소드가 호출될 때마다 &lt;em&gt;Frame&lt;/em&gt; 이 쌓이게 됩니다.&lt;/p&gt;
&lt;p&gt;다음은 &lt;em&gt;Frame&lt;/em&gt; 가 쌓인 구조 및 &lt;em&gt;Heap&lt;/em&gt; 영역과의 참조를 보여주는 코드 및 그림입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Main&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;whybe&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt; person &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        person &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildPerson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildPerson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1896px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4a7393ea46dfdcefbfea50b92f3cffa8/c7953/2021-08-09-jvm-reference-structure.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.60000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACBUlEQVR42j1TB7ajMAzk/nfcX9IIYONucGN27LxdEz2MEcoUabouoLbrf5TacH884XxEG++AXK4R/X0uCUeKyDVhFQt+bt9I5cSRP2dTrhd8POHCgVIa1k1ikwrHeeA8PLSWcM4gpYDAvGWfoYNgSKxqxiJfUF7AhB3CvDEl/rNznmHR17IwaVmQjx3tXOHUDdE+cKUNnnmP9Y5Uz5EbooexGhcar4rNzJg6DRczrD8RjgKxWyxCE3F/TlhWCaks9xkHn+/LHYFowTIpJxYNo2BpGat5YSqkvBvPQoba1EE99o/PxHuANhqW6LsE3h+Ytye1OodeuWbkVsa+F9zthunMF2Fb7LtEaw3fX39oygMlaTTSNPIHXt9xZcHCDvP6HIj6KqXiPPP4ri/lxAehC2lQ9qQ+rzvemxpIQySi94pN7ESb4Hn23qhhoYG1y2QgaVo43EAo7dJNaRDKUzcDf9RP8ZCH89YHtkbXkLRDhHEJr/cX6Wu2UB2GSCWGNKVRb/3EFM8GYyyU2gfs2+0Hv7+/SFHRWUnKNzpNmnlnC1m8tgfqlUn7GigT+7JSR3bpp23O1IgqD7o+FiKQeLw2aOrqgyfdDUKKsTcusu9mCL0MA5ST0J7Be9dv0yx4cVQqx+HfpPTJ4Y9Tco1J6ft+NqaGeTHGQdV5C7kLvNm3PZaVDW8U/gKywFBT7tPuTQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;JVM Reference Structure 구조&quot;
        title=&quot;&quot;
        src=&quot;/static/4a7393ea46dfdcefbfea50b92f3cffa8/c7953/2021-08-09-jvm-reference-structure.png&quot;
        srcset=&quot;/static/4a7393ea46dfdcefbfea50b92f3cffa8/0eb09/2021-08-09-jvm-reference-structure.png 500w,
/static/4a7393ea46dfdcefbfea50b92f3cffa8/1263b/2021-08-09-jvm-reference-structure.png 1000w,
/static/4a7393ea46dfdcefbfea50b92f3cffa8/c7953/2021-08-09-jvm-reference-structure.png 1896w&quot;
        sizes=&quot;(max-width: 1896px) 100vw, 1896px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위 그림에서 알 수 있듯이 지역 변수에서 값으로 가지는 부분, 그리고 참조로 가지는 부분이 나누어져 있습니다.
이후 각 &lt;em&gt;Frame&lt;/em&gt; 의 연산이 끝나게 되면, 결과 값을 호출한 상위 &lt;em&gt;Frame&lt;/em&gt; 에 반환해주게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Java Stack&lt;/em&gt; 영역이 가득 차게 되면 &lt;em&gt;StackOverflowError&lt;/em&gt; 를 발생시키게 됩니다.
다만 &lt;em&gt;JVM&lt;/em&gt; 의 전체적인 메모리가 부족하면 &lt;em&gt;OutOfMemoryError&lt;/em&gt; 가 발생하기도 합니다.&lt;br&gt;
아래 코드에서 재귀 함수를 무한으로 호출시켜 &lt;em&gt;Stack&lt;/em&gt; 영역에서의 &lt;em&gt;Error&lt;/em&gt; 를 일으켜보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Stack&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Stack 메모리 오류&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; num &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; num&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        num &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; num&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;num&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Stack 메모리 오류
java.lang.StackOverflowError&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;&lt;em&gt;Native Method Stacks&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Java&lt;/em&gt; 로 작성된 프로그램을 실행하면서, 순수하게 &lt;em&gt;Java&lt;/em&gt; 로 구성된 코드만을 사용할 수 없는 시스템의 자원이나 &lt;em&gt;API&lt;/em&gt; 가 존재합니다.
다른 프로그래밍 언어로 작성된 메소드들을 &lt;em&gt;Native Method&lt;/em&gt; 라고 합니다.
&lt;em&gt;Native Method Stacks&lt;/em&gt; 는 &lt;em&gt;Java&lt;/em&gt; 로 작성되지 않은 메소드를 다루는 영역입니다. &lt;em&gt;C Stacks&lt;/em&gt; 라고 불리기도 합니다.
앞의 &lt;em&gt;Java Stacks&lt;/em&gt; 영역과 비슷하게 &lt;em&gt;Native Method&lt;/em&gt; 가 실행될 경우 &lt;em&gt;Stack&lt;/em&gt; 에 해당 메서드가 쌓이게 됩니다.
각각의 &lt;em&gt;Thread&lt;/em&gt; 들이 생성되면 &lt;em&gt;Native Method Stacks&lt;/em&gt; 도 동일하게 생성이 됩니다.&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;PC(Program Counter) Registers&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Java&lt;/em&gt; 에서 &lt;em&gt;Thread&lt;/em&gt; 는 각자의 메소드를 실행하게 됩니다.
이때, &lt;em&gt;Thread&lt;/em&gt; 별로 동시에 실행하는 환경이 보장되어야 하므로 최근에 실행 중인 &lt;em&gt;JVM&lt;/em&gt; 에서는 명령어 주소값을 저장할 공간이 필요합니다.
이 부분을 &lt;em&gt;PC Registers&lt;/em&gt; 영역이 관리하여 추적해주게 됩니다.  &lt;em&gt;Thread&lt;/em&gt; 들은 각각 자신만의 &lt;em&gt;PC Registers&lt;/em&gt; 를 가지고 있습니다.
만약 실행했던 메소드가 네이티브하다면 &lt;em&gt;undefined&lt;/em&gt; 가 기록이 됩니다.
실행했던 메소드가 네이티브하지 않다면, &lt;em&gt;PC Registers&lt;/em&gt; 는 &lt;em&gt;JVM&lt;/em&gt; 에서 사용된 명령의 주소 값을 저장하게 됩니다.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/java-stack-heap&quot;&gt;java-stack-heap Baeldung&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/java-memory-management/&quot;&gt;Java Memory Management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ibm.com/docs/en/i/7.2?topic=languages-native-methods-java-native-interface&quot;&gt;Native Methods and the Java Native Interface, IBM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ibm.com/docs/en/i/7.2?topic=interface-getting-started-java-native-methods&quot;&gt;Getting started with Java native methods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html&quot;&gt;Java SE 8 document, Oracle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Spring Boot에 QueryDSL을 사용해보자]]></title><description><![CDATA[1. QueryDSL PostRepository.java Spring Data JPA가 기본적으로 제공해주는 CRUD 메서드 및 쿼리 메서드 기능을 사용하더라도, 원하는 조건의 데이터를 수집하기 위해서는 필연적으로 JPQL…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-08-basic-querydsl/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-08-basic-querydsl/</guid><pubDate>Sun, 08 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. QueryDSL&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;PostRepository.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;select p from Post p join fetch p.user u &quot;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;where u in &quot;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;(select t from Follow f inner join f.target t on f.source = :user) &quot;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;or u = :user &quot;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;order by p .createdAt desc&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findAllAssociatedPostsByUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Spring Data JPA가 기본적으로 제공해주는 CRUD 메서드 및 쿼리 메서드 기능을 사용하더라도, 원하는 조건의 데이터를 수집하기 위해서는 필연적으로 JPQL을 작성하게 됩니다. 간단한 로직을 작성하는데 큰 문제는 없으나, 복잡한 로직의 경우 개행이 포함된 쿼리 문자열이 상당히 길어집니다. JPQL 문자열에 오타 혹은 문법적인 오류가 존재하는 경우, 정적 쿼리라면 어플리케이션 로딩 시점에 이를 발견할 수 있으나 그 외는 런타임 시점에서 에러가 발생합니다.&lt;/p&gt;
&lt;p&gt;이러한 문제를 어느 정도 해소하는데 기여하는 프레임워크가 바로 QueryDSL입니다. QueryDSL은 정적 타입을 이용해서 SQL 등의 쿼리를 생성해주는 프레임워크입니다. QueryDSL의 장점은 다음과 같습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;문자가 아닌 코드로 쿼리를 작성함으로써, 컴파일 시점에 문법 오류를 쉽게 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;자동 완성 등 IDE의 도움을 받을 수 있다.&lt;/li&gt;
&lt;li&gt;동적인 쿼리 작성이 편리하다.&lt;/li&gt;
&lt;li&gt;쿼리 작성 시 제약 조건 등을 메서드 추출을 통해 재사용할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;물론 QueryDSL을 사용하기 위해서는 다소 번거로운 Gradle 설정 및 사용법 등을 익혀야한다는 단점이 존재합니다. 하지만 JPQL이 익숙한 독자님들은 QueryDSL을 이해하는데 큰 어려움이 없을 것으로 예상됩니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;2. 설정&lt;/h2&gt;
&lt;p&gt;사실 QueryDSL을 적용하면서 가장 까다로운 부분이 설정이었습니다. 공식 문서에는 Gradle에 대한 내용이 누락되어 있으며, 실제로 QueryDSL 설정 방법은 Gradle 및 IntelliJ 버전에 따라 상이하기 때문입니다. 따라서 제가 사용하는 방법이 다른 환경에서는 잘 동작하지 않을 수 있습니다. 😭 Groovy 문법이 익숙하지 않다면 설정 파일을 완벽하게 이해할 필요는 없어보입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;build.gradle&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;gradle&quot;&gt;&lt;pre class=&quot;language-gradle&quot;&gt;&lt;code class=&quot;language-gradle&quot;&gt;buildscript &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    ext &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        queryDslVersion &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;4.4.0&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;plugins&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    id &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.boot&apos;&lt;/span&gt; version &lt;span class=&quot;token string&quot;&gt;&apos;2.5.3&apos;&lt;/span&gt;
    id &lt;span class=&quot;token string&quot;&gt;&apos;io.spring.dependency-management&apos;&lt;/span&gt; version &lt;span class=&quot;token string&quot;&gt;&apos;1.0.11.RELEASE&apos;&lt;/span&gt;
    id &lt;span class=&quot;token string&quot;&gt;&apos;java&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

group &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;com.learning&apos;&lt;/span&gt;
version &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;0.0.1-SNAPSHOT&apos;&lt;/span&gt;
sourceCompatibility &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1.8&apos;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;repositories&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;mavenCentral&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;dependencies&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;implementation&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.boot:spring-boot-starter-data-jpa&apos;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;implementation&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.boot:spring-boot-starter-web&apos;&lt;/span&gt;
    runtimeOnly &lt;span class=&quot;token string&quot;&gt;&apos;com.h2database:h2&apos;&lt;/span&gt;
    testImplementation &lt;span class=&quot;token string&quot;&gt;&apos;org.springframework.boot:spring-boot-starter-test&apos;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// QueryDSL&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;implementation&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.querydsl:querydsl-jpa:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;queryDslVersion&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;annotationProcessor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;javax.persistence:javax.persistence-api&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;javax.annotation:javax.annotation-api&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.querydsl:querydsl-apt:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;queryDslVersion&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:jpa&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// QueryDSL&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;sourceSets&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    main &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        java &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            srcDirs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;projectDir&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/src/main/java&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;projectDir&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/build/generated&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

test &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;useJUnitPlatform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;기본적으로 QueryDSL은 프로젝트 내의 @Entity 어노테이션을 선언한 클래스를 탐색하고, &lt;code class=&quot;language-text&quot;&gt;JPAAnnotationProcessor&lt;/code&gt;를 사용해 Q 클래스를 생성합니다.
&lt;ul&gt;
&lt;li&gt;Q 클래스는 후술합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;querydsl-apt&lt;/code&gt;가 @Entity 및 @Id 등의 애너테이션을 알 수 있도록, &lt;code class=&quot;language-text&quot;&gt;javax.persistence&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;javax.annotation&lt;/code&gt;을 annotationProcessor에 함께 추가합니다.
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;annotationProcessor&lt;/code&gt;는 Java 컴파일러 플러그인으로서, 컴파일 단계에서 어노테이션을 분석 및 처리함으로써 추가적인 파일을 생성할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;개발 환경에서 생성된 Q 클래스를 사용할 수 있도록 generated 디렉토리를 sourceSet에 추가합니다.
&lt;ul&gt;
&lt;li&gt;IDE의 개발 코드에서 생성된 Q 클래스 파일에 접근할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.1. Q 클래스&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/128705967-f62b8478-4255-4caf-8f72-4b379f410277.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;설정 및 빌드를 마친 이후, 다음과 같이 Java 파일을 컴파일합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/128706176-45990a1e-15a4-4936-bb8a-7571a8fb320f.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;$projectDir/build/generated&lt;/code&gt; 디렉토리 하위에 Entity로 등록한 클래스들이 Q라는 접두사가 붙은 형태로 생성되었습니다.&lt;/li&gt;
&lt;li&gt;이러한 클래스들을 Q 클래스 혹은 Q(쿼리) 타입이라고 합니다.
&lt;ul&gt;
&lt;li&gt;QueryDSL로 쿼리를 작성할 때, Q 클래스를 사용함으로써 쿼리를 Type-Safe하게 작성할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Gradle 설정을 통해 &lt;code class=&quot;language-text&quot;&gt;$projectDir/src/main/java&lt;/code&gt;의 프로덕션 코드에서 Q 클래스를 import해 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2&gt;3. QueryDSL 간단 예제&lt;/h2&gt;
&lt;p&gt;hi라는 내용을 포함하며 댓글이 1개 이상인 Post를 ID 내림차순으로 조회하는 로직이 존재한다고 가정해봅시다. 정적 쿼리가 아닌 EntityManager를 통해 JPQL을 작성하는 경우 코드는 다음과 같습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PostRepositoryTest.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@DisplayName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;hi 내용을 포함하며 댓글이 1개 이상인 Post를 조회한다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;jpa_findPostsByMyCriteria_Three&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;EntityManager&lt;/span&gt; entityManager &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; testEntityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntityManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; posts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;select p from Post p where p.content like &apos;%hi%&apos; and p.comments.size &gt; 0 order by p.id desc&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getResultList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;posts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;정적 쿼리가 아닌 관계로 문법 오류가 발생하면 어플리케이션 로딩 시점에서 감지하지 못하고, 런타임 에러가 발생합니다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Post.class&lt;/code&gt; 지네릭 타입을 메서드 파라미터로 제공하지 않으면, 로 타입의 리스트가 반환됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;PostRepositoryTest.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@DisplayName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;hi 내용을 포함하며 댓글이 1개 이상인 Post를 ID 내림차순으로 조회한다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;queryDsl_findPostsByMyCriteria_Three&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;EntityManager&lt;/span&gt; entityManager &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; testEntityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntityManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;JPAQuery&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; query &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JPAQuery&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entityManager&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;QPost&lt;/span&gt; qPost &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;QPost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;p&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; posts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;qPost&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;qPost&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;hi&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;qPost&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;comments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orderBy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;qPost&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;posts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;반면 QueryDSL은 각종 풍부한 체이닝 메서드와 유틸리티 메서드 및 정적 타입(Q 클래스)을 기반으로 직관적으로 쿼리를 작성합니다.&lt;/li&gt;
&lt;li&gt;JPQL을 사용해본 독자님이라면 코드가 상당히 직관적임을 느끼실 겁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;PostRepositoryTest.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@DisplayName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;QueryDsl을 통해 Post 조회시 Comment를 Fetch Join한다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;queryDsl_FetchJoinComments_Success&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;EntityManager&lt;/span&gt; entityManager &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; testEntityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntityManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;JPAQuery&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; query &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JPAQuery&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entityManager&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;QPost&lt;/span&gt; qPost &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;QPost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;p&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;QComment&lt;/span&gt; qComment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;QComment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;c&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; posts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;distinct&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;qPost&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;leftJoin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;qPost&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;comments&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; qComment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetchJoin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;posts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;또한 &lt;code class=&quot;language-text&quot;&gt;fetchJoin()&lt;/code&gt; 등 직관적인 체이닝 메서드를 통해 간단하게 Fetch Join을 적용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2&gt;4. Repository에서 QueryDSL 사용하기&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;PostRepository.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostRepository&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JpaRepository&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;select p from Post p join fetch p.comments&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findAllInnerFetchJoin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;select distinct p from Post p join fetch p.comments&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findAllInnerFetchJoinWithDistinct&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;현재 PostRepository가 사용 중인 정적 쿼리(JPQL)들을 QueryDSL로 교체해봅시다. Spring Data JPA는 JpaRepository를 상속한 Repository 클래스(예, PostRepository)에서 Custom Repository 기능을 사용할 수 있도록 하는 기능을 제공합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;QueryDslConfig.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;QueryDslConfig&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@PersistenceContext&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EntityManager&lt;/span&gt; entityManager&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JPAQueryFactory&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;jpaQueryFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JPAQueryFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entityManager&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;먼저, JPAQueryFactory를 Bean으로 등록하여 프로젝트 전역에서 QueryDSL을 작성할 수 있도록 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;PostCustomRepository.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostCustomRepository&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findAllInnerFetchJoin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findAllInnerFetchJoinWithDistinct&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;기존의 PostRepository 인터페이스의 &lt;code class=&quot;language-text&quot;&gt;findAllInnerFetchJoin()&lt;/code&gt; 및 &lt;code class=&quot;language-text&quot;&gt;findAllInnerFetchJoinWithDistinct()&lt;/code&gt; 메서드를 삭제하고, 동일한 메서드 시그니처를 새로운 커스텀 인터페이스에 정의합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;PostCustomRepositoryImpl.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token import static&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;learning&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jpa&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;QPost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token static&quot;&gt;post&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@Repository&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostCustomRepositoryImpl&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostCustomRepository&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JPAQueryFactory&lt;/span&gt; jpaQueryFactory&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostCustomRepositoryImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;JPAQueryFactory&lt;/span&gt; jpaQueryFactory&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jpaQueryFactory &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; jpaQueryFactory&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findAllInnerFetchJoin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; jpaQueryFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;selectFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;innerJoin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;comments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetchJoin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findAllInnerFetchJoinWithDistinct&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; jpaQueryFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;selectFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;distinct&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;innerJoin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;comments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetchJoin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;    
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;커스텀 인터페이스를 구현하는 클래스에 QueryDSL 쿼리를 작성합니다.
&lt;ul&gt;
&lt;li&gt;이 때, 해당 구현 클래스 이름은 반드시 &lt;code class=&quot;language-text&quot;&gt;Impl&lt;/code&gt;로 끝나야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이전 테스트 코드에서는 QPost와 같은 Q 타입 인스턴스를 직접 생성해서 사용했습니다.
&lt;ul&gt;
&lt;li&gt;이번에는 QPost를 static import함으로써, QPost에 미리 정의된 Q 타입 인스턴스 상수를 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/128717061-49ecb966-e075-4ae4-9e6e-38922e9d4228.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;실제 QPost 클래스 내부에는 Q 타입의 인스턴스 상수가 미리 정의되어 있음을 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;PostRepository.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostRepository&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JpaRepository&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostCustomRepository&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/*
    삭제된 JPQL 정적 쿼리 메서드
    @Query(&quot;select p from Post p join fetch p.comments&quot;)
    List&amp;lt;Post&gt; findAllInnerFetchJoin();

    @Query(&quot;select distinct p from Post p join fetch p.comments&quot;)
    List&amp;lt;Post&gt; findAllInnerFetchJoinWithDistinct();    
     */&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;JpaRepository를 상속하는 PostRepository가 PostCustomRepository 인터페이스를 상속하도록 합니다.
&lt;ul&gt;
&lt;li&gt;PostCustomRepositoryImpl에 작성된 QueryDSL 코드를 PostRepository가 자동으로 사용할 수 있게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2&gt;5. 기타&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;QueryRepository.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Repository&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;QueryRepository&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JPAQueryFactory&lt;/span&gt; jpaQueryFactory&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;QueryRepository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;JPAQueryFactory&lt;/span&gt; jpaQueryFactory&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jpaQueryFactory &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; jpaQueryFactory&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findAllPostsInnerFetchJoin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; jpaQueryFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;selectFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;innerJoin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;comments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetchJoin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Orphan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findALlOrphans&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; jpaQueryFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;selectFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;orphan&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;distinct&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;leftJoin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;orphan&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetchJoin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;orphan&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;abc&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;원한다면 특정 엔티티 타입에 구애받지 않는 자신만의 QueryDSL 관련 Repository를 정의해 사용할 수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/intro-to-querydsl&quot;&gt;Intro to Querydsl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://n1tjrgns.tistory.com/275&quot;&gt;SpringBoot + queryDsl Setting 총정리&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.gradle.org/incremental-compiler-avoidance#about-annotation-processors&quot;&gt;Incremental Compilation, the Java Library Plugin, and other performance features in Gradle 3.4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://javachoi.tistory.com/397&quot;&gt;간단한 QueryDSL 초기 설정 in Gradle 6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://honeymon.io/tech/2020/07/09/gradle-annotation-processor-with-querydsl.html&quot;&gt;[gradle] 그레이들 Annotation processor 와 Querydsl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jojoldu.tistory.com/372&quot;&gt;Spring Boot Data Jpa 프로젝트에 Querydsl 적용하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/tech2flew/spring-data-jpa-querydsl-integration-part-1-1aaebe6208c8&quot;&gt;이미지 출처&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Logback 으로 쉽고 편리하게 로그 관리를 해볼까요? ⚙️]]></title><description><![CDATA[Spring Boot…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-07-logback-tutorial/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-07-logback-tutorial/</guid><pubDate>Sat, 07 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Spring Boot 를 통해 프로젝트를 개발할 때, 로깅을 적용하려는 초심자분들을 위한 글입니다.&lt;/p&gt;
&lt;h2&gt;로깅을 하는 이유?&lt;/h2&gt;
&lt;p&gt;로깅이란 시스템이 동작할 때 시스템의 상태 및 동작 정보를 시간 경과에 따라 기록하는 것을 의미한다. 로깅을 통해 개발자는 개발 과정 혹은 개발 후에 발생할 수 있는 예상치 못한 애플리케이션의 문제를 진달할 수 있고, 다양한 정보를 수집할 수 있다. 사용자 로그의 경우 분석 데이터로 활용할 수 있다. 하지만 로깅을 하는 단계에서 적절한 수준의 로그 기록 기준을 잡지 못하면 방대한 양의 로그 파일이 생성되는 문제를 겪거나, 의미 있는 로그를 쌓지 못하는 경우가 발생할 수 있다. 결국 &lt;b&gt;효율적으로 로깅을 하는 방법을 이해하는 것&lt;/b&gt;이 중요하다.&lt;/p&gt;
&lt;h2&gt;스프링에서 로깅을 하는 방법&lt;/h2&gt;
&lt;p&gt;초기의 스프링은 JCL(Jakarta Commons Logging)을 사용해서 로깅을 구현했다. 요즘에는 대표적으로 &lt;b&gt;Log4j&lt;/b&gt; 와 &lt;b&gt;Logback&lt;/b&gt; 으로 스프링 부트의 로그 구현체를 사용한다. Log4j는 가장 오래된 프레임워크이며 Apache 의 Java 기반 Logging Framework 다. xml, properties 파일로 로깅 환경을 구성하고, 콘솔 및 파일 출력의 형태로 로깅을 할 수 있게 도와준다. 로그 레벨의 경우는 6단계로 구성되어있다. Logback 은 log4j 이후에 출시된 Java 기반 Logging Framework 중 하나로 가장 널리 사용되고 있다. SLF4j 의 구현체이며 Spring Boot 환경이라면 별도의 dependency 추가 없이 기본적으로 포함되어 있다.&lt;/p&gt;
&lt;h2&gt;Logging 구현체 선택하기&lt;/h2&gt;
&lt;p&gt;JCL 을 사용하면 기본적인 인터페이스인 Log 와 Log 객체 생성을 담당하는 LogFactory 만 구현하면 언제든지 로깅 구현체 교체가 가능하니 선택은 자유롭다. 이번 시간에는 SLF4J 의 구현체 Logback 을 사용해서 로깅을 구현한다. SLF4J 는 JCL 의 가진 문제를 해결하기 위해 클래스 로더 대신에 컴파일 시점에서 구현체를 선택하도록 변경시키기 위해 도입된 것이다. Logback 은 log4j 에 비해 향상된 필터링 정책, 기능, 로그 레벨 변경 등에 대해 서버를 재시작할 필요 없이 자동 리로딩을 지원한다는 장점이 있다.&lt;/p&gt;
&lt;h2&gt;로그 찍어보기&lt;/h2&gt;
&lt;p&gt;우선 프로젝트를 생성하고 간단한 로그를 찍어보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;slf4j&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Logger&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;slf4j&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LoggerFactory&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;web&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;annotation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RestController&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;web&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;annotation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GetMapping&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LogController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Logger&lt;/span&gt; logger &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoggerFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLogger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;LoggerController 의 로그&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/log&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;로깅 발생!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위의 클래스를 추가해준 뒤 SpringApplication 을 실행한다. 그리고 localhost:8080/log 에 접속한다.&lt;/p&gt;
&lt;p&gt;그 뒤 콘솔에 찍힌 로그를 확인한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2021&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;08&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;07&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7491&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;http&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;nio&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8080&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;exec&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INFO&lt;/span&gt;  &lt;span class=&quot;token class-name&quot;&gt;LoggerController&lt;/span&gt; 의 로그 &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; 로깅 발생&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Info 레벨의 로그로 “LoggerController 의 로그 - 로깅 발생!” 이 콘솔에 출력되는 것을 확인할 수 있다. 그렇다면 이렇게 콘솔에 출력된 로그를 파일로 남기는 방법을 알아보자.&lt;/p&gt;
&lt;h2&gt;로그 레벨 관련&lt;/h2&gt;
&lt;p&gt;Logback 은 5단계의 로그 레벨을 가진다.&lt;br&gt; 심각도 수준은 Error &gt; Warn &gt; Info &gt; Debug &gt; Trace 이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;⛔️ Error : 예상하지 못한 심각한 문제가 발생하는 경우, 즉시 조취를 취해야 할 수준의 레벨&lt;/li&gt;
&lt;li&gt;⚠ ️Warn : 로직 상 유효성 확인, 예상 가능한 문제로 인한 예외 처리, 당장 서비스 운영에는 영향이 없지만 주의해야 할 부분&lt;/li&gt;
&lt;li&gt;✅ Info : 운영에 참고할만한 사항, 중요한 비즈니스 프로세스가 완료됨&lt;/li&gt;
&lt;li&gt;⚙️ Debug : 개발 단계에서 사용하며, SQL 로깅을 할 수 있음&lt;/li&gt;
&lt;li&gt;📝 Trace : 모든 레벨에 대한 로깅이 추적되므로 개발 단계에서 사용함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Debug 와 Trace 레벨은 많은 양의 로그가 쌓이므로 자칫 운영 단계에서 해당 레벨의 로깅을 할 경우 용량 감당이 안 될 수 있다. 그렇기 때문에 중요하지 않은 정보는 Debug 이하로 설정하고 로깅을 하지 않는 편이 좋다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Debug, Trace 레벨의 로깅은 개발 단계에서만 사용하고 배포 단계에서는 사용하지 말자&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;에러 레벨에 따라 로깅을 하게 되면 어떻게 처리되는지 살펴보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;beans&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;factory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;annotation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Autowired&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;web&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;annotation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GetMapping&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;web&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;annotation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RestController&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LogController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LogService&lt;/span&gt; logService&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/log&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        logService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;slf4j&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Logger&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;slf4j&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LoggerFactory&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stereotype&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Service&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LogService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Logger&lt;/span&gt; logger &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoggerFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLogger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSimpleName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Trace&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Debug&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Info&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;warn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Warn&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Error&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위에 나온 두 개의 클래스 파일을 생성하고 SpringApplication 을 실행한다. 그리고 localhost:8080/log 에 접속하고 콘솔에 출력된 내용을 살펴본다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2021&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;08&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;07&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;19&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;09&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;17317&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;http&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;nio&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8080&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;exec&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;INFO&lt;/span&gt;  &lt;span class=&quot;token class-name&quot;&gt;LogService&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Info&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2021&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;08&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;07&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;19&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;09&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;17317&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;http&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;nio&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8080&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;exec&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;WARN&lt;/span&gt;  &lt;span class=&quot;token class-name&quot;&gt;LogService&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Warn&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2021&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;08&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;07&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;19&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;09&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;17317&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;http&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;nio&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8080&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;exec&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ERROR&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LogService&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이것을 통해 로그는 별다른 설정을 하지 않아도 기본 설정된 로그 레벨(INFO)에 맞춰 상위 로그로 출력되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;h2&gt;로그 파일 작성하기&lt;/h2&gt;
&lt;p&gt;콘솔 로그의 수준을 변경하는 방법은 application.yml 과 logback-spring.xml 에서 설정하는 방법이 있다. application.yml 은 설정하는 난이도가 비교적 쉽지만, 실제 제품에 사용하기엔 한계가 있고 세부적인 설정이 불편하기 때문에 logback-spring.xml 로 관리하는 편이 더 좋다고 생각한다. 따라서 logback-spring.xml 로 로그를 기록해보자.&lt;/p&gt;
&lt;p&gt;logback-spring.xml 은 콘솔, 파일, DB 등 로그를 출력하는 방법을 지정하는 appender 와 출력할 곳을 정하는 logger 로 나눌 수 있다. 아래 예시를 통해 간단하게 로그를 남기는 연습을 해보자.&lt;/p&gt;
&lt;p&gt;경로는 &lt;code class=&quot;language-text&quot;&gt;src/main/resources/logback-spring.xml&lt;/code&gt;이다. 해당 예시문은 특정 클래스에서 발생한 특정 레벨의 예외를 기록하는 방법과 특정 라이브러리의 기능을 기록하는 방법에 대한 예시를 다룬다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;configuration 설정을 시작한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token prolog&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;configuration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 이 곳에 추가할 기능을 넣는다. --&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;configuration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;appender(어디에 출력할 지)&lt;/code&gt;에서 콘솔에 출력되는 형식을 지정한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;appender&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;STDOUT&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ch.qos.logback.core.ConsoleAppender&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;layout&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ch.qos.logback.classic.PatternLayout&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Pattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;[%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{36} - %msg%n&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;Pattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;layout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;appender&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;pattern&lt;/code&gt; 에서 지정한 방식대로 시간과 레벨 등의 설정이 되고난 후 콘솔에 메세지를 출력한다. 해당 &lt;code class=&quot;language-text&quot;&gt;append&lt;/code&gt; 설정은 &lt;code class=&quot;language-text&quot;&gt;STDOUT&lt;/code&gt; 이라는 변수명으로 저장해뒀다고 생각하면 된다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Info 레벨의 이름을 가진 로그를 저장할 방식을 지정한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;appender&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;INFO_LOG&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ch.qos.logback.core.rolling.RollingFileAppender&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;./logs/info.log&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 파일을 저장할 경로를 정한다 --&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;filter&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ch.qos.logback.classic.filter.LevelFilter&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;level&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;INFO&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;level&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;onMatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;ACCEPT&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;onMatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 해당 레벨만 기록한다. --&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;onMismatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;DENY&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;onMismatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 다른 수준의 레벨은 기록하지 않는다.(상위 레벨도 기록 안함), 상위 수준의 레벨에 대한 기록을 원하면 ACCEPT 로 하면 기록된다. --&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 레벨별 필터링이 필요없을 경우 filter class 관련된 부분을 삭제하면 됨--&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;encoder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;pattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;[%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - %msg%n&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;pattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 해당 패턴 네이밍으로 현재 로그가 기록됨 --&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;encoder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;rollingPolicy&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ch.qos.logback.core.rolling.TimeBasedRollingPolicy&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;fileNamePattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;./was-logs/info.%d{yyyy-MM-dd}.%i.log.gz&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;fileNamePattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 해당 패턴 네이밍으로 이전 파일이 기록됨 --&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;timeBasedFileNamingAndTriggeringPolicy&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;maxFileSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;100MB&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;maxFileSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 한 파일의 최대 용량 --&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;timeBasedFileNamingAndTriggeringPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;maxHistory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;180&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;maxHistory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 한 파일의 최대 저장 기한 --&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;rollingPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;appender&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;INFO_LOG&lt;/code&gt;라는 변수명에 여러 가지 설정을 한다. 우선 파일을 저장할 경로와 파일명을 지정한다. &lt;code class=&quot;language-text&quot;&gt;INFO level&lt;/code&gt; 의 에러만 기록한다. 기록한 로그를 &lt;code class=&quot;language-text&quot;&gt;pattern&lt;/code&gt; 에서 지정한 방식대로 인코딩한다. 인코딩한 로그기록을 앞서 지정한 파일명에 기록하는 것이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 이러한 형태로 인코딩 된다.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2021&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;08&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;07&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3093&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;main&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEBUG&lt;/span&gt; org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hibernate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; drop table &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; exists user &lt;span class=&quot;token constant&quot;&gt;CASCADE&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;파일의 크기가 &lt;code class=&quot;language-text&quot;&gt;100MB&lt;/code&gt; 혹은 &lt;code class=&quot;language-text&quot;&gt;180일&lt;/code&gt;이 넘을 경우 앞서 인코딩한 파일을 &lt;code class=&quot;language-text&quot;&gt;fileNamePattern&lt;/code&gt; 에 맞게 &lt;code class=&quot;language-text&quot;&gt;.gz&lt;/code&gt; 로 따로 저장한다는 설정이다.&lt;/p&gt;
&lt;p&gt;비슷한 방식으로 &lt;code class=&quot;language-text&quot;&gt;WARN_LOG&lt;/code&gt; 도 만들어준다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Info 레벨의 로그를 콘솔에 출력한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;root&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;INFO&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;appender-ref&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;STDOUT&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;root&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;로거가 발생될 때, 설정해둔 대상을 파일에 기록한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;logger&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;LogController&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;additivity&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;false&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 콘솔에 출력된 LogController 에 대해서 아래 작업을 실행한다.--&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;level&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;DEBUG&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- DEBUG 레벨 이상에서만 실행한다. --&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;appender-ref&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;INFO_LOG&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;appender-ref&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;WARN_LOG&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;logger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;b&gt;LogController 라는 이름의 로거가 발생할 때 아래 조건을 실행&lt;/b&gt;한다. Debug 이상의 레벨에 대해서 각각 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;appender-ref ref=&quot;INFO_LOG&quot; /&gt;&lt;/code&gt; 와 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;appender-ref ref=&quot;WARN_LOG&quot; /&gt;&lt;/code&gt; 의 변수에 할당해둔 명령이 실행된다.&lt;/p&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;
&lt;p&gt;예외에 나온 내용 외에도 콘솔에 특정 단어를 감지해 처리할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2021&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;08&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;07&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5723&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SpringApplicationShutdownHook&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEBUG&lt;/span&gt; org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hibernate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; drop table &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; exists user &lt;span class=&quot;token constant&quot;&gt;CASCADE&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다음과 같이 출력되는 콘솔 로그를 감지하려면 아래처럼 하면 로그를 쌓을 수 있게 되는 것이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;logger&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;org.hibernate.SQL&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;additivity&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;false&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;level&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;DEBUG&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;appender-ref&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;DEBUG_LOG&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;logger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;위에서 한 모든 작업을 한 파일로 만들면 다음과 같다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token prolog&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;configuration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;property&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;LOGS_ABSOLUTE_PATH&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;./logs&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;

  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;appender&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;STDOUT&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ch.qos.logback.core.ConsoleAppender&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;layout&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ch.qos.logback.classic.PatternLayout&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Pattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;[%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{36} - %msg%n&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;Pattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;layout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;appender&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;appender&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;INFO_LOG&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ch.qos.logback.core.rolling.RollingFileAppender&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;./logs/info.log&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;filter&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ch.qos.logback.classic.filter.LevelFilter&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;level&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;INFO&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;level&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;onMatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;ACCEPT&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;onMatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;onMismatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;DENY&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;onMismatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;encoder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;pattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;[%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - %msg%n&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;pattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;encoder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;rollingPolicy&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ch.qos.logback.core.rolling.TimeBasedRollingPolicy&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;fileNamePattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;./was-logs/info.%d{yyyy-MM-dd}.%i.log.gz&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;fileNamePattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;timeBasedFileNamingAndTriggeringPolicy&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;maxFileSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;100MB&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;maxFileSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;timeBasedFileNamingAndTriggeringPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;maxHistory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;180&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;maxHistory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;rollingPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;appender&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  
 &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;appender&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;WARN_LOG&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ch.qos.logback.core.rolling.RollingFileAppender&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;./logs/warn.log&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;filter&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ch.qos.logback.classic.filter.LevelFilter&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;level&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;WARN&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;level&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;onMatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;ACCEPT&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;onMatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;onMismatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;DENY&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;onMismatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;encoder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;pattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;[%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - %msg%n&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;pattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;encoder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;rollingPolicy&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ch.qos.logback.core.rolling.TimeBasedRollingPolicy&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;fileNamePattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;./was-logs/warn.%d{yyyy-MM-dd}.%i.log.gz&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;fileNamePattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;timeBasedFileNamingAndTriggeringPolicy&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;maxFileSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;100MB&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;maxFileSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;timeBasedFileNamingAndTriggeringPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;maxHistory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;180&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;maxHistory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;rollingPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;appender&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;root&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;INFO&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;appender-ref&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;STDOUT&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;root&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;logger&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;LogController&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;additivity&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;false&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;level&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;DEBUG&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;appender-ref&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;INFO_LOG&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;appender-ref&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;WARN_LOG&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;logger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;logger&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;org.hibernate.SQL&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;additivity&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;false&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;level&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;DEBUG&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;appender-ref&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;INFO_LOG&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;logger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;configuration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 SpringApplication 을 실행 후 접속 등의 다양한 활동을 한 다음 지정해둔 경로(&lt;code class=&quot;language-text&quot;&gt;./logs/&lt;/code&gt;)에 생성된 로그 파일을 확인해 볼 수 있다.&lt;/p&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;간단하게 Spring Boot 에서 logback 을 사용하여 로깅 하는 법을 알아봤다. 더 상세한 설명과 실습을 원한다면 이 &lt;a href=&quot;https://www.youtube.com/watch?v=JqZzy7RyudI&quot;&gt;영상&lt;/a&gt;을 보는 것을 추천한다. 실제 서비스에서는 FILE 이외에도 SockectAppender, LogStash 등도 함께 사용하기 때문에 로깅 입문이 끝났다면 나중에 더 학습해보면 도움될 것이다.&lt;/p&gt;
&lt;p&gt;어떤 로깅 인터페이스를 사용하던 자유지만, 개발을 할 때 Logger 를 설정하고 시작하는 것은 반 필수가 아닌가? 항상 로깅을 생활화하는 습관을 지니면 좋겠다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.sangkon.com/hands-on-springboot-logging/&quot;&gt;Spring Boot - Logging, 20분 정리&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://minkwon4.tistory.com/161&quot;&gt;slf4j, log4j, logback, log4j2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=1MD5xbwznlI&quot;&gt;[10분 테코톡] ☂️ 검프의 Logging(로깅) #1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[웹 애플리케이션의 성능 고려하기]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-08-06-optimization/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-08-06-optimization/</guid><pubDate>Fri, 06 Aug 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;웹 애플리케이션의 성능 고려하기&lt;/h2&gt;
&lt;h3&gt;성능이 좋다는 건&lt;/h3&gt;
&lt;p&gt;웹 애플리케이션(이하 웹 앱) 구현에 어느 정도 익숙해지는 단계가 되면, 언젠가는 맞닥뜨려야 할 산이 있다. 바로 성능이다. 개발자의 관점에서 성능이 당최 무엇을 의미하는지는 몰라도, 누구든 성능이 좋은 앱을 그렇지 않은 앱보다 선호할 것이다. 그렇다면 여기서 말하는 성능이란 과연 무엇일까?&lt;/p&gt;
&lt;p&gt;사전적으로 성능은 &lt;code class=&quot;language-text&quot;&gt;기계 따위가 지닌 성질이나 기능&lt;/code&gt;을 뜻한다. 언뜻 봐도 포괄적이다. 이해를 돕기 위해 웹 앱의 맥락에서 성능이라 칭할 만한 요소 몇 가지를 추려 보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;콘텐츠를 받아오는 이벤트 혹은 그에 따른 서버의 응답이 안정적으로 실행되는가?&lt;/li&gt;
&lt;li&gt;사용자의 이목을 끌 수 있는 콘텐츠가 충분히 렌더링 되는가?&lt;/li&gt;
&lt;li&gt;콘텐츠를 받아오는 중이거나 받아온 후에도 여전히 사용자와 상호 작용이 가능한가?&lt;/li&gt;
&lt;li&gt;앱 조작이 부드럽고 자연스러운가?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위를 보면 단순히 처리 속도가 빠른지에 대한 여부만 판가름하지 않는다. 따라서 웹 앱의 성능을 논할 때는 &lt;strong&gt;속도, 안정성, 반응성, 심미성&lt;/strong&gt; 등 사용자에게 영향을 끼칠 수 있는 모든 요소를 종합해서 파악해야 한다.&lt;/p&gt;
&lt;h3&gt;성능 지표&lt;/h3&gt;
&lt;p&gt;성능은 다양한 이유로 상대적일 수밖에 없다. 사용자의 네트워크 환경이나 기기 종류에 따라 영향을 받거나 이벤트 처리는 빨랐지만, UX 등의 변수로 그 속도를 체감할 수 없는 경우가 종종 있기 때문이다. 많은 곳에서 성능에 관한 정량, 객관적인 기준을 가질 것을 권하며 여기서 제안하는 개념이 바로 &lt;strong&gt;성능 지표&lt;/strong&gt;다.&lt;/p&gt;
&lt;figure&gt;
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/1cee004204762d6deffdb93398bfe013/dcf28/web-performance-metrics.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 26.200000000000003%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAIAAADKYVtkAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAmklEQVR42o2PzRLCMAiE8/6PqY61PwkllgTSkEo7HjKe3BlOyy4fDjHe7o9xmo9/1WxkHQs8Xcp5GF6L97XWfmXXqk1N5dKvS6A5OhYBxBXjRtTbYQMSypnn2ZoDpdTd1oKTFnbMvCyermTrhG/IkjKzD4EonQz6tbRWCd6QnCHZ2z6AtXR568YmkpgDwHqioZFf1qG17HHSXT41liQHlQGffwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;웹 성능 측정 예시&quot; title=&quot;&quot; src=&quot;/static/1cee004204762d6deffdb93398bfe013/f97d7/web-performance-metrics.png&quot; srcset=&quot;/static/1cee004204762d6deffdb93398bfe013/0eb09/web-performance-metrics.png 500w,
/static/1cee004204762d6deffdb93398bfe013/1263b/web-performance-metrics.png 1000w,
/static/1cee004204762d6deffdb93398bfe013/f97d7/web-performance-metrics.png 2000w,
/static/1cee004204762d6deffdb93398bfe013/dcf28/web-performance-metrics.png 2198w&quot; sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
  &lt;figcaption align=&quot;center&quot;&gt;성능 지표를 이용한 측정 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;br /&gt;
&lt;p&gt;성능은 주로 사용자를 위해 측정하기에 대부분의 기준은 사용자의 관점으로 맞추어져 있다. 사용자 기반 성능 지표의 대표적인 측정 사항은 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;초기 페이지 렌더링 속도&lt;/li&gt;
&lt;li&gt;페이지 이동 시 해당 페이지가 JS 코드를 받아와 실행하는 속도&lt;/li&gt;
&lt;li&gt;페이지 렌더링 후 사용자와의 상호작용에 반응하는 속도&lt;/li&gt;
&lt;li&gt;시각적 안정성 (사용자의 예측대로 움직이고 간섭이 없는지)&lt;/li&gt;
&lt;li&gt;부드러운 조작&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;단순한 예시일 뿐이며 이 밖에 HTTP 요청 횟수, 에러 발생 비율, &lt;a href=&quot;https://web.dev/custom-metrics/&quot;&gt;커스텀 지표&lt;/a&gt; 등 앱의 성격을 따져보았을 때 성능 지표의 수는 무궁무진하다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;웹 앱의 전체 성능을 한 번에 알 수 있는 유일 지표는 존재하지 않는다. 또한 여러 가지 지표를 비교하여 무엇이 더 좋다, 우위를 가릴 수도 없다. 현재 만들고 있는 웹 앱의 특성에 가장 적합한 지표가 가장 좋은 지표다.&lt;/p&gt;
&lt;h3&gt;성능 예산&lt;/h3&gt;
&lt;p&gt;앞서 설명한 성능 지표가 측정을 위한 도구였다면, 성능 예산은 이 도구를 활용한 본격적인 측정의 시발점이라 할 수 있다. 예산을 세우는 이유는 현실과 크게 다르지 않다. 가용 자원의 한도를 정해놓고, 초과하여 쓰지 않기 위함이다. 웹 앱에서 예산을 세울 때는 자원의 크기나 네트워크 요청 횟수, 특정 이벤트 처리 시간 등을 예로 들 수 있겠다.&lt;/p&gt;
&lt;p&gt;초기에 예산이 설립되면 예상할 수 있는 이점은 무엇이 있을까?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;웹 디자이너에게 이미지 해상도, 웹 폰트 용량 등의 기준 제시&lt;/li&gt;
&lt;li&gt;개발자에게 프레임워크, 라이브러리를 포함한 스크립트 사이즈 기준 제시&lt;/li&gt;
&lt;li&gt;문제 해결을 위해 방법론을 주장할 때 뒷받침할 근거 제시&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;쉽게 생각하면 성능 예산은 개발을 시작하기 전에 코드 컨벤션을 맞추듯이 최소한의 성능 마지노선에 대한 컨벤션을 맞추는 작업이다. 이 작업에서 성능 지표를 사용하는데, 이때 반드시 지표가 한 개일 필요는 없다. 상황에 맞게 여러 지표를 혼합하여 측정하면 된다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;정량 기반 지표: 이미지의 최대 사이즈, 외부 자원의 총 개수 등&lt;/li&gt;
&lt;li&gt;시간 기반 지표: &lt;a href=&quot;https://dev.to/codesensei/user-centric-performance-metrics-what-are-they-55dm&quot;&gt;유저가 렌더링을 인지하는 데 드는 시간을 세분화&lt;/a&gt;하여 비교&lt;/li&gt;
&lt;li&gt;규칙 기반 지표: &lt;a href=&quot;https://developers.google.com/speed/pagespeed/insights/&quot;&gt;PageSpeed&lt;/a&gt;, &lt;a href=&quot;https://developers.google.com/web/tools/lighthouse&quot;&gt;Lighthouse&lt;/a&gt; 등 신뢰성 높은 도구의 측정 시간&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;자, 이제 도구를 어느 정도 살펴보았으니 각 요소의 한계값을 설정해보자. 한계값은 할 수만 있다면 테스트 후에 자체적인 기준을 세우는 게 좋지만, 여건상 쉽지 않을 때가 더러 있다. 그럴 경우를 대비해 우리는 다음과 같은 비교 대상을 빠르게 설정할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;비슷한 서비스를 운영 중인 타 사이트의 성능&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/your-first-performance-budget/#budget-for-quantity-based-metrics&quot;&gt;실제 조사를 바탕으로 보편화한 기준&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;성능 예산을 세우고 난 뒤에는 꾸준히 해당 성능을 트래킹하는 것이 바람직하다. 이렇게 하면 차후 성능 변경이 비즈니스 흐름에 어떤 식으로 영향을 끼치는지 가늠할 수 있다.&lt;/p&gt;
&lt;h3&gt;성능 측정&lt;/h3&gt;
&lt;p&gt;지금까지의 과정을 통해 줄곧 ‘무엇을’ 측정할지 결정했다면 이제는 ‘어떻게’ 측정을 할 것인지 고민해볼 차례다. 아무리 좋은 지표라 할지라도, 실제 측정이 이루어지지 않는다면 무의미하다. 측정 방식은 크게 &lt;strong&gt;통제된 환경 vs. 실제 환경&lt;/strong&gt; 이 두 가지로 나눌 수 있다.&lt;/p&gt;
&lt;p&gt;한 측정은 일정하고 통제된 환경에서, 또 다른 측정은 사용자와 함께하는 실제 환경에서 진행되었다고 가정해보자. 그렇다면 여기서 어떤 측정 방식이 더욱 가치 있다고 판단할 수 있을까?&lt;/p&gt;
&lt;p&gt;두 방식 모두 나름의 가치가 있다. 전자의 환경에서는 주로 정확하고 신뢰성 높은 정보를 추출할 수 있지만, 통제된 환경에서 얻을 수 있는 정보가 한정적이다. 반면, 후자의 환경에서는 통제된 환경에서 알 수 없었던 다양한 사이드이펙트를 경험할 수 있고 사용자의 빠른 피드백을 얻을 수 있다는 장점이 있다.&lt;/p&gt;
&lt;p&gt;즉, 이전 단계에서 그래왔듯 이 과정에서도 특별히 정해진 답은 없다. 측정을 해야 하는 시점에서 현실적으로 투자할 수 있는 비용 등 여러 요건을 비교하여 진행하는 것이 좋다.&lt;/p&gt;
&lt;h3&gt;요약&lt;/h3&gt;
&lt;p&gt;앱 성능이 좋고 나쁨은 한마디로 표현할 수 없는 영역이다. 게다가 방대한 지식들 속에서 측정이란 더욱 막연하게만 느껴지는 개념이다. 하지만 성능 측정의 본질을 생각해보면 결국 ‘사용자’를 향하고 있다. 이 사실을 인지하고 개발 중인 앱이 사용자에게 제공할 수 있는, 제공해야만 하는 최우선의 핵심 기능을 정리하자. 그 후에 전략을 수립한다면 성능 측정이 한층 더 수월해질 것이다.&lt;/p&gt;
&lt;h3&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cloudsigma.com/how-cost-optimization-with-the-cloud-impacts-services-and-application-architecture/&quot;&gt;대표 이미지 출처&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codeburst.io/web-performance-metrics-everyone-should-care-about-63e395c50778&quot;&gt;성능 지표 이미지 출처&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/metrics/&quot;&gt;Metrics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/performance-budgets-101/&quot;&gt;Performance Budget&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Email 보내기에 비동기, 이벤트를 곁들인.]]></title><description><![CDATA[서비스를 운영하는 데 Email 전송 기능을 도입하는 경우가 많다. 이메일 기능에 비동기와 이벤트 발행 방식을 도입하게 된 이유와 방법에 관해서 설명해 보고자 한다. Email 보내기 Spring…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-31-email-async-event/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-31-email-async-event/</guid><pubDate>Sat, 31 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;서비스를 운영하는 데 Email 전송 기능을 도입하는 경우가 많다. 이메일 기능에 비동기와 이벤트 발행 방식을 도입하게 된 이유와 방법에 관해서 설명해 보고자 한다.&lt;/p&gt;
&lt;!-- end --&gt;
&lt;h2&gt;Email 보내기&lt;/h2&gt;
&lt;p&gt;Spring에서 제공하는 &lt;code class=&quot;language-text&quot;&gt;MailSender&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;JavaMailSender&lt;/code&gt; 인터페이스를 이용하면 쉽게 메일 서비스를 구현할 수 있다. 단순 텍스트로 구성되면
MailSender로 충분하지만, Html Template을 활용하여 메일을 보내는 것을 목표로 두기 때문에 JavaMailSender를 사용한다. 또한, 별도의 메일 서비스를
사용하지 않고 Gmail 개인 계정을 사용하고 진행하였다. 이 부분을 다루고 있는 블로그가 많으므로 코드를 남겨두고 별도의 설명은 생략한다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;implementation&apos;org.springframework.boot:spring-boot-starter-mail&apos;&lt;/code&gt; 의존성 추가&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MailConfiguration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JavaMailSender&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getJavaMailSender&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;JavaMailSenderImpl&lt;/span&gt; mailSender&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JavaMailSenderImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        
        mailSender&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setHost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;smtp.gmail.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        mailSender&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;587&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        mailSender&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setUsername&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;email@gmail.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        mailSender&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPassword&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        
        &lt;span class=&quot;token class-name&quot;&gt;Properties&lt;/span&gt; props&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;mailSender&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getJavaMailProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mail.transport.protocol&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;smtp&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mail.smtp.auth&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mail.smtp.starttls.enable&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mail.debug&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; mailSender&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;email, password를 github 저장소에 올리기 부담될 때에는 &lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-07-31-git-submodule/&quot;&gt;&lt;strong&gt;private-submodule&lt;/strong&gt;&lt;/a&gt;을 사용해 보기를 추천한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;implementation&apos;org.springframework.boot:spring-boot-starter-thymeleaf&apos;&lt;/code&gt; 의존성 추가&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MailService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JavaMailSender&lt;/span&gt; mailSender&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SpringTemplateEngine&lt;/span&gt; templateEngine&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;//생성자 생략&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendJobCancelMail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; email&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; jobName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Context&lt;/span&gt; context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setVariable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;jobName&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jobName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; subject &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[GPU-IS-MINE] Job 예약 취소 메일&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; body &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; templateEngine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;job-cancel.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;sendMail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;email&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; subject&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendMail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; subject&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;MimeMessagePreparator&lt;/span&gt; messagePreparator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; 
            mimeMessage &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MimeMessageHelper&lt;/span&gt; helper &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MimeMessageHelper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mimeMessage&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            helper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;noreply@noreply.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            helper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            helper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSubject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;subject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            helper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        mailSender&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;messagePreparator&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;비교적 친숙했던 thymeleaf template 엔진을 사용했고, SpringTemlateEngine 은 TemplateEngine에 SpringEL의 문법을 추가한 부분이라
원하는 부분을 사용하면 된다. Thymeleaf는 spring-boot에서는 별도의 설정 없이 사용할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Async 비동기의 도입&lt;/h2&gt;
&lt;p&gt;@EnableAsync, @Async 어노테이션을 사용하여 비동기 도입하기.&lt;/p&gt;
&lt;h3&gt;도입 배경&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JobService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; jobId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Job&lt;/span&gt; job&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findJobById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jobId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMember&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        mailService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEmail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getJobName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;현재 비동기가 아니기 때문에, 하나의 Thread에서 일련의 작업을 진행하고 있다. Client가 Job 취소 요청을 보내면, 취소 작업이 끝난 후 취소 메일이 전송되고 나서야 client는 완료되었다는 요청을 받을 수 있다.&lt;/p&gt;
&lt;p&gt;우리의 서비스의 경우, job을 취소가 완료되는 것이 중요한 부분이었고, 메일의 도착 여부와 관계없이 job이 취소된 경우에는 OK 요청을 보내주어야 했다. 메일 전송 완료를 기다리기 위해 클라이언트가 응답을 늦게 받는 상황을 없애고 싶었고, 그러기 위해 비동기 처리를 도입하게 되었다.&lt;/p&gt;
&lt;h3&gt;설정 파일&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableAsync&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AsyncConfiguration&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AsyncConfigurer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Logger&lt;/span&gt; logger &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoggerFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLogger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AsyncConfiguration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;mailExecutor&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Executor&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getAsyncExecutor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ThreadPoolTaskExecutor&lt;/span&gt; executor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ThreadPoolTaskExecutor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        executor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setCorePoolSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        executor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setMaxPoolSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        executor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setQueueCapacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        executor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setThreadNamePrefix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;MailExecutor-&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        executor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; executor&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AsyncUncaughtExceptionHandler&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getAsyncUncaughtExceptionHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ex&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
          logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Exception handler for async method &apos;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toGenericString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&apos; threw unexpected exception itself&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Java Spring에서는 &lt;code class=&quot;language-text&quot;&gt;@EnableAsync&lt;/code&gt; 옵션만 주어도, 비동기 처리를 사용할 수 있다. 이를 Customizing 하기 위해서 위처럼, AsyncConfigurer를 구현한 설정 파일을 만들어 줄 수도 있다. 별도 설정을 하지 않은 경우, CorePoolSize 는 1, MaxPoolSize, QueueCapacity는 &lt;code class=&quot;language-text&quot;&gt;Integer.MAX_VALUE&lt;/code&gt; 로 정의되어 있고, warning 레벨의 log를 출력하도록 설정이 되어 있다.&lt;/p&gt;
&lt;h3&gt;기존 코드에 적용&lt;/h3&gt;
&lt;p&gt;위에서의 설정 파일로 비동기 처리를 위한 쓰레드를 별도로 생성해줄 수 있다. 이 쓰레드를 특정 메소드에 적용하여 비동기 처리를 하기 위해서는 아래의 방식으로 메소드 위에 &lt;code class=&quot;language-text&quot;&gt;@Async&lt;/code&gt; 어노테이션을 붙여주면 된다. 클래스에 어노테이션을 붙여줄 시에는 해당 클래스의 전체 메소드에 사용이 된다. 괄호 안에 별도로 명시해주면, 특정 쓰레드를 지정해줄 수도 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MailService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Async&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mailExecutor&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendJobCancelMail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; email&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; jobName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Context&lt;/span&gt; context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setVariable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;jobName&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jobName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; subject &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[GPU-IS-MINE] Job 예약 취소 메일&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; body &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; templateEngine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;job-cancel.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;sendMail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;email&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; subject&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;테스트 시 주의사항&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;테스트 또한 하나의 쓰레드의 작업이 진행되고, 그 쓰레드의 작업이 완료되면, 테스트는 성공한 것으로 표시된다. 즉, 비동기 처리되어 별도의 쓰레드로 처리되는 부분에 대해서는 기존의 방식으로는 테스트를 진행하는 것은 불가하다. 이 부분에 대해서 저자도 공부를 진행해볼 예정이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;이벤트를 발행하다.&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ApplicationEventPublisher&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;EventListener&lt;/code&gt;을 이용하여 이벤트 발행과 처리 로직을 분리하다.&lt;/p&gt;
&lt;h3&gt;도입 배경&lt;/h3&gt;
&lt;p&gt;우리 서비스는 Job의 &lt;code class=&quot;language-text&quot;&gt;예약&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;취소&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;시작&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;종료&lt;/code&gt;의 모든 경우에 대해서 메일을 보내주는 기능이 있다. 만약, 메일뿐만 아니라 애플리케이션의 알람을 보내주는 로직이 추가된다고 가정해보자. 각각의 메소드에 일일이 추가되는 로직을 추가해줘야 하고, 도메인 로직에 집중하지 못하는 코드가 된다. 이벤트를 발행하는 방법을 통해 JobService와 MailService의 의존성을 끊어줄 수 있고 확장 가능한 구조가 될 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JobService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; jobId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Job&lt;/span&gt; job &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findJobById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jobId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMember&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        mailService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEmail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getJobName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        notificationService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPhoneId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getJobName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        smsService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPhoneNumber&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getJobName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;구현 방법&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ApplicationEventPublisherAware&lt;/code&gt;의 구현체는 publishEvent 메소드를 통해 이벤트를 발행할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JobService&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApplicationEventPublisherAware&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApplicationEventPublisher&lt;/span&gt; eventPublisher&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; jobId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Job&lt;/span&gt; job &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findJobById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jobId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMember&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        eventPublisher&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;publishEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;JobCanceledEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setApplicationEventPublisher&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ApplicationEventPublisher&lt;/span&gt; applicationEventPublisher&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eventPublisher &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; applicationEventPublisher&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;EventListener&lt;/code&gt;는 어노테이션을 통해서 구현이 가능한데, &lt;code class=&quot;language-text&quot;&gt;TransactionalEventListener&lt;/code&gt;를 활용하면, &lt;code class=&quot;language-text&quot;&gt;TransactionaPhase&lt;/code&gt;에 따른 Event를 듣는 시점을 선택할 수 있다. 우리 서비스는 AFTER_COMMIT(DB에 반영된 후)에 Event를 듣도록 설정이 되어 있다. JobEvent의 상속구조를 통해 여러 가지 상황(예약, 취소, 시작, 종료)을 하나의 메소드로 처리할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JobEventListener&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 생략&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Async&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@TransactionalEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;phase &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TransactionPhase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;AFTER_COMMIT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; classes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JobEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;JobEvent&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMember&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; jobName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getJobName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        mailService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendEmail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getEmail&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jobName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEventType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        notificationService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPhoneId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jobName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEventType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        smsService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendSms&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPhoneNumber&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jobName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEventType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;지금까지 Email에 비동기와 이벤트 발행을 적용해 보았다. AOP를 활용하게 되면, publishEvent의 메소드 없이 어노테이션만으로도 동일한 작업을 할 수 있다고 한다.
이 부분에 대해 더 공부해보고 싶은 분은 이 &lt;a href=&quot;https://supawer0728.github.io/2018/03/24/spring-event&quot;&gt;블로그&lt;/a&gt;를 추천한다. AOP를 활용하면 도메인에만 더욱 더 집중할 수 있는 그런 코드가 될 수 있을 것으로 생각한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;참고 자료&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/EnableAsync.html&quot;&gt;Java Doc - EnableAsync&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Async.html&quot;&gt;Java Doc - Async&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationEventPublisher.html&quot;&gt;Java Doc - ApplicationEventPublisher&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://theheydaze.tistory.com/255&quot;&gt;06. 스프링 부트 (Spring Boot) - 자바 메일 센더 (Java Mail Sender)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://supawer0728.github.io/2018/03/24/spring-event&quot;&gt;Spring Event + Async + AOP 적용해보기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[git submodule로 중요한 정보 관리하기]]></title><description><![CDATA[git submodule…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-31-git-submodule/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-31-git-submodule/</guid><pubDate>Sat, 31 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;git submodule은 메인(부모) 레포지토리에 하위(자식) 레포지토리를 두고 관리하기 위한 도구이다.
하나의 프로젝트에서 다른 프로젝트를 함께 사용해야 하는 경우 주로 활용한다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;필자는 이번에 이를 프로젝트 비밀 정보를 숨기기 위한 용도로 이용했다.
어떻게 적용하게 됐는지, 어떻게 적용했는지를 글을 통해 알아보자.&lt;br/&gt;&lt;/p&gt;
&lt;!-- end --&gt;
&lt;br/&gt;
&lt;h2&gt;사용 계기&lt;/h2&gt;
&lt;p&gt;레벨 3 팀 프로젝트를 진행하며 중요한 정보(e.g. secret key)를 외부에 노출되지 않게 관리할 일이 생겼다.
Jasypt으로 암호화하는 방법, submodule로 관리하는 방법 등 여러 방식을 두고 고민하게 됐다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;전자의 경우에는 프로덕션 코드에 암호화를 위한 코드를 추가해야 한다는 부담 때문에 제외했다.
대신에 git submodule을 사용하여 관리하기로 결정했다.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;적용 과정&lt;/h2&gt;
&lt;p&gt;우선 private 레포지토리를 하나 생성한다. 이 레포지토리를 submodule로 이용한다.
예를 들면, 필자는 팀 organization 내부에 private 레포지토리를 만들었다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;2021-pick-git           // 팀 organization
        ⌙ security      // private 레포지토리&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;해당 레포지토리에 외부로 노출되면 안되는 정보를 추가한다.
필자 팀의 경우엔 &lt;code class=&quot;language-text&quot;&gt;application-prod.yml&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;application-secret.yml&lt;/code&gt;에 중요한 정보가 들어있다. 따라서, 두 파일을 이 레포지토리에 넣어뒀다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;2021-pick-git
        ⌙ security
                ⌙ application-prod.yml
                ⌙ application-secret.yml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;이제 git 명령어로 메인 레포지토리에 하위 레포지토리를 추가한다.
이 명령어는 메인 레포지토리에서 작성해야 한다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;아래의 두 명령어는 어떤 차이가 있을까? 사실 &lt;code class=&quot;language-text&quot;&gt;-b {branch_name}&lt;/code&gt;을 제외하면 동일하다.
단지 &lt;code class=&quot;language-text&quot;&gt;-b&lt;/code&gt; 옵션을 주면 하위 레포지토리의 특정 브랜치를 기준으로 submodule을 추가한다.
해당 옵션이 없으면 default branch를 기준으로 submodule을 등록한다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;% git submodule add {submodule_repository_url}

또는

% git submodule add -b {branch_name} {submodule_repository_url}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;메인 레포지토리에 하위 레포지토리를 추가하면, 루트 경로에 &lt;code class=&quot;language-text&quot;&gt;.gitmodules&lt;/code&gt;란 이름의 숨김 파일이 생성된다.
이 파일의 내용을 확인하면 아래와 같다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[submodule &quot;backend/pick-git/security&quot;]
path = backend/pick-git/security
url = https://github.com/2021-pick-git/security.git&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;참고로, &lt;code class=&quot;language-text&quot;&gt;.gitmodules&lt;/code&gt; 파일에 브랜치 정보를 넣어두면 해당 브랜치를 기준으로 submodule을 업데이트한다.
아래와 같이 브랜치 정보를 작성해둘 수 있다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[submodule &quot;backend/pick-git/security&quot;]
path = backend/pick-git/security
url = https://github.com/2021-pick-git/security.git
branch = main&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;메인 레포지토리에 submodule을 추가하고 상태를 확인하면, 새로 커밋해야 할 파일이 나타난다.
이 파일을 add하고 commit해야 비로소 메인 레포지토리에 submodule이 반영된다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;% git status

pick-git/security (새 커밋)

% git add .
% git commit -m &quot;{commit_message}&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;이어서 원격 레포지토리에도 push를 해야 프로젝트 형상관리에 문제가 없다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;% git push {remote_repository_url} {branch_name}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;여기까지 따라왔다면, 본인의 프로젝트에는 submodule이 잘 적용됐다.
만약 팀 프로젝트 중이면 팀원들의 프로젝트에도 submodule을 적용해야 어플리케이션 실행이 잘 될 것이다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;이번에는 팀원들의 프로젝트에 어떻게 submodule을 적용해야 하는지 알아보자.
아직 프로젝트를 clone하지 않은 상황이면, 먼저 프로젝트를 clone한다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;% git clone {project_url}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;다음으로 submodule을 초기화하고 업데이트한다. 이 부분은 가장 처음 딱 1번만 수행하면 된다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// submodule 초기화
% git submodule init

// 메인 레포지토리가 기억하는 submodule의 특정 커밋 시점으로 업데이트
% git submodule update&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;추가로, 메인 프로젝트 clone에서 하위 프로젝트 적용까지 하나의 명령어로 처리할 수도 있다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;% git clone --recurse-submodules {project_url}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;메인 프로젝트에 submodule이 이미 있고, 하위 프로젝트의 새로운 커밋을 가져와야 하는 상황에선 update 명령어를 활용한다.
이 명령어로 메인 프로젝트에서 submodule의 커밋을 가져오면, 이전에 봤던 것처럼 새로 커밋해야 하는 파일이 생긴다.
앞서 한 것과 동일하게 해당 파일을 add, commit, push해서 로컬과 원격 프로젝트에 반영한다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;% git submodule update --remote --merge

// .gitmodules 파일에 정의되어 있는 브랜치(default는 main 또는 master)의 최신 버전으로 업데이트
// % git submodule update --remote

// 로컬에서 작업 중인 부분과 원격에 작업된 부분이 다른 경우 머지까지 진행
// % git submodule update --remote --merge&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;지금까지 메인 프로젝트에 하위 프로젝트를 추가하는 방법을 알아봤다.
내용이 복잡해 보여도 직접 해보면 크게 어렵지 않게 적용할 수 있을 것이다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;프로젝트에 submodule을 적용하는 경우 한 가지 주의할 점이 있다.
submodule에 변경사항이 생겼다면, 메인 프로젝트보다 먼저 push 또는 pull을 해야 한다.
만약 메인 프로젝트를 push/pull하고 submodule을 push/pull하면 예상치 못한 오류가 발생할 수도 있다.
메인 프로젝트는 submodule을 그대로 가지지 않고 path, url, commit 정보만 저장하기 때문이다.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://jujeol-jujeol.github.io/2021/07/12/git-submodule-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0/&quot;&gt;[git] git submodule 적용하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/book/en/v2/Git-Tools-Submodules&quot;&gt;7.11 Git Tools - Submodules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pinedance.github.io/blog/2019/05/28/Git-Submodule&quot;&gt;Git submodule 사용하기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[JPA와 연관관계]]></title><description><![CDATA[JPA란? RDBMS를 사용하던 우리는 java를 이용한 객체지향 프로그래밍을 통해 객체 간 관계가 더욱 복잡해질수록 패러다임 불일치의 문제를 마주하게 된다. 상속과 참조가 쉬워 객체 간 관계를 쉽게 지정할 수 있는 것과 다르게 RDBMS…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-30-jpa-mapping/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-30-jpa-mapping/</guid><pubDate>Fri, 30 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;JPA란?&lt;/h2&gt;
&lt;p&gt;RDBMS를 사용하던 우리는 java를 이용한 객체지향 프로그래밍을 통해 객체 간 관계가 더욱 복잡해질수록 &lt;strong&gt;패러다임 불일치&lt;/strong&gt;의 문제를 마주하게 된다.&lt;/p&gt;
&lt;p&gt;상속과 참조가 쉬워 객체 간 관계를 쉽게 지정할 수 있는 것과 다르게 RDBMS의 테이블에는 객체의 상속관계가 존재하지 않는다.&lt;br&gt;
외래키 지정을 통해 테이블을 상호 참조할 수 있지만 관계가 복잡해질수록 명령어가 길어지고 실수하기 쉽다.&lt;/p&gt;
&lt;p&gt;복잡한 객체 관계를 가지고 있을 때 java에서는 &lt;code class=&quot;language-text&quot;&gt;a-&gt;b-&gt;c&lt;/code&gt;로 가진 연관관계를 &lt;code class=&quot;language-text&quot;&gt;a&lt;/code&gt;만 조회함으로써 &lt;code class=&quot;language-text&quot;&gt;c&lt;/code&gt;도 용이하게 조회할 수 있지만 RDBMS에서는 &lt;code class=&quot;language-text&quot;&gt;a-&gt;b&lt;/code&gt;도 쿼리문, &lt;code class=&quot;language-text&quot;&gt;b-&gt;c&lt;/code&gt;도 쿼리문을 통해 조회해야만 한다.&lt;br&gt;
쿼리문을 많이 사용할수록 성능은 감소하고, 그렇다고 join문을 사용하면 메모리에 사용하지도 않는 데이터를 로딩해야해 부담이 될 수 있다.&lt;/p&gt;
&lt;p&gt;java 컬렉션에 저장하듯이 database에 저장하고 싶다면 어떻게 해야할까?&lt;/p&gt;
&lt;p&gt;이 때, ORM이 등장했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ORM (Object-Relational Mapping)&lt;br&gt;
ORM 프레임워크가 객체와 Database 사이에서 매핑해줌으로써 객체와 RDBMS 간에 존재하는 패러다임 불일치를 해소해주고 CRUD SQL를 직접 작성하지 않아도 되도록 한다.&lt;br&gt;
ORM을 사용함으로써 개발자는 SQL문을 잘못 작성해 허비하던 시간을 객체의 비즈니스 로직을 구현하는데 사용할 수 있게 되었다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JPA (Java Persistence API)&lt;br&gt;
JPA는 java에서 Hibernate를 기반으로 만든 ORM 표준이다.&lt;br&gt;
JPA를 통해 우리는 자유롭게 그래프를 탐색할 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;JPA 객체 매핑&lt;/h3&gt;
&lt;p&gt;특정 database에 종속적이고 각 문법이 다른 문제를 Dialect로 통일해서 JPA가 사용할 수 있도록 &lt;code class=&quot;language-text&quot;&gt;hibernate.dialect&lt;/code&gt;가 돕는다.&lt;br&gt;
JPA는 아래와 같은 어노테이션을 통해 객체를 매핑할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;//..&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Entity&lt;/code&gt;로 선언된 객체에 지정된 컬럼에 따라 객체를 database table로 스키마를 자동 생성하기 때문에 컬럼의 자료형도 아래와 같이 지정해줄 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nullable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; length &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; unique &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; email&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Id&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;@Column&lt;/code&gt; 외에도 날짜를 줄 수 있는 &lt;code class=&quot;language-text&quot;&gt;@Temporal&lt;/code&gt;, enum을 지정하는 &lt;code class=&quot;language-text&quot;&gt;@Enumaratted&lt;/code&gt;, 긴 값을 지정하는 &lt;code class=&quot;language-text&quot;&gt;@Lob&lt;/code&gt;이 있다. database에 저장하지 않는 필드는 &lt;code class=&quot;language-text&quot;&gt;@Transient&lt;/code&gt;로 지정할 수 있다.&lt;/p&gt;
&lt;p&gt;이렇게 지정된 객체 간에 연관관계는 어떻게 지정할 수 있을까?&lt;/p&gt;
&lt;h3&gt;JPA 연관관계 매핑&lt;/h3&gt;
&lt;p&gt;객체는 참조를 통해 관계를 맺어 단방향 매핑이지만, 테이블은 외래키를 사용해 관계를 맺기 때문에 양방향 매핑이다.&lt;br&gt;
객체를 양방향으로 연관관계를 맺으려면 그 관계에 주인이 필요하다.&lt;br&gt;
일대다, 다대일 매핑을 mappedBy로 양방향 관계를 설정할 수 있다.&lt;br&gt;
주인은 그 연관관계를 정의할 수 있는 키를 소유하고 있다.&lt;br&gt;
연관관계 주인은 그 외래키를 등록/수정/삭제할 수 있는 객체이다.&lt;/p&gt;
&lt;p&gt;예를 들어 회사(Company) 내에 팀(Team)이 있고 팀 안에 구성원(Member)이 있다고 생각해보자.&lt;br&gt;
팀과 멤버는 일대다 관계를 갖는다. 멤버와 팀은 다대일 관계를 갖는다.&lt;br&gt;
이 경우 팀과 멤버는 양방향으로 연결된 것 같지만 &lt;strong&gt;단방향으로 상호 연결&lt;/strong&gt;되어 있는 것이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;다대일 관계 매핑&lt;br&gt;
구성원과 팀이 다대일 관계를 갖고 있는 상황에 아래와 같이 매핑할 수 있다.&lt;br&gt;
보통은 다대일에서 &lt;strong&gt;다&lt;/strong&gt;를 가지는 Member가 외래키 소유가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@ManyToOne&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;//매핑 설정&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@JoinColumn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;team_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nullable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;//외래키 이름 지정&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; team&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;일대다 관계 매핑&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;JoincColumn&lt;/code&gt; 설정하면 별도의 중간 과정 없이도 member 테이블에 team_id 컬럼이 생긴다. 따로 지정해주지 않으면 중간 테이블을 jpa에서 자동으로 만든다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;mappedBy&lt;/code&gt; 설정하면 &lt;strong&gt;양방향 관계&lt;/strong&gt;를 가질 수 있게 된다. mappedBy 속성으로 연관관계의 주인을 지정하게 된다.&lt;br&gt;
아래와 같이 지정할 경우 Member가 연관관계의 주인이 된다. 이렇게 됨으로써 Team 객체는 Member에 요청을 보내야 외래키 수정/삭제가 가능하다.&lt;br&gt;
양방향 매핑을 한 경우 연관관계의 주인에 값을 넣지 않고 주인이 아닌 객체에만 넣지 않도록 주의해야 한다. 반드시 양쪽 다 값을 입력해야 함을 기억하도록 하자.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Team&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@OneToMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mappedBy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;team&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@JoinColumn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;team_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; members &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;다대다 관계 매핑&lt;br&gt;
관계형 데이터베이스에서는 지정할 수 없어 잘 사용되지 않는다. 다대다 매핑 시에는 Set을 사용하는 것이 효율적이다.&lt;br&gt;
아래는 각 구성원이 가진 물품들을 저장하는 예시이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@ManyToMany&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@JoinTable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;member_product&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    joinColumns &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@JoinColumn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;member_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    inverseJoinColumns &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@JoinColumn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;product_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Product&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; products&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Product&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@ManyToMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mappedBy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;products&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; members&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;하지만 중간 테이블에 외래키와 더불어서 여러 비즈니스 로직을 담은 데이터를 담기를 원할 수가 있다.&lt;br&gt;
그러나 @ManyToMany를 사용하는 경우 중간 테이블에 외래키를 제외한 다른 데이터를 담을 수 없다.&lt;br&gt;
따라서 다대다 매핑은 보통 일대다와 다대일 연관관계를 구성해 구현하도록 하는 것이 통상적이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;일대일 관계 매핑&lt;br&gt;
주 테이블과 대상 테이블 중 어느 곳에 외래키를 둘지 생각해야 한다.&lt;br&gt;
주 테이블에 두는 경우는 주 테이블만 확인해도 대상 테이블과의 연관관계를 확인할 수 있다.&lt;br&gt;
반면에 대상 테이블에 두는 경우는 일대다로의 확장이 좋다는 장점이 있지만 양방향 매핑을 무조건 해야한다는 단점이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Company&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@OneToOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mappedBy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;company&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Member&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;마치며&lt;/h3&gt;
&lt;p&gt;이렇게 JPA는 다양한 연관관계 매핑을 제공하며 데이터베이스 사용을 편리하게 하고 있다.&lt;br&gt;
자신의 상황에 알맞게 어떠한 연관관계를 사용해야할지 정확한 설계를 통해 구현하도록 하자.&lt;/p&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;자바 ORM 표준 JPA 프로그래밍(김영한 저)&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[리눅스 컨테이너에 대해 알아보자]]></title><description><![CDATA[2014년, Google이 Docker를 채택해 개발자들의 주목을 끌었습니다. 2021년 현재, 이제 Docker의 존재에 대해서 모르는 사람들이 거의 없을 정도로 보편적인 기술이 되었습니다. Docker는 태초에 LXC…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-30-lxc-container/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-30-lxc-container/</guid><pubDate>Fri, 30 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2014년, Google이 Docker를 채택해 개발자들의 주목을 끌었습니다. 2021년 현재, 이제 Docker의 존재에 대해서 모르는 사람들이 거의 없을 정도로 보편적인 기술이 되었습니다.&lt;/p&gt;
&lt;p&gt;Docker는 태초에 LXC라는 컨테이너 기술을 기반으로 만들어진 상위 레벨의 컨테이너 기술입니다.
현재 Docker는 LXC에서 벗어나, 최적화 및 고도화한 runC라는 기술을 기반으로 하고 있습니다.&lt;/p&gt;
&lt;p&gt;이 글에서는 Docker의 기반이었던 LXC에 대해 알아보고자 합니다.
이를 통해 컨테이너 기술이 어떻게 동작하는지 알아보겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;리눅스 컨테이너란&lt;/h2&gt;
&lt;p&gt;기존 시스템(Host OS)에서 독자적인 시스템 환경(Guest OS)을 분리, 구축하는 기술을 가상화라 합니다.&lt;/p&gt;
&lt;p&gt;컨테이너는 기존의 시스템에 존재하는 &lt;strong&gt;프로세스&lt;/strong&gt;를 해당 시스템에서 격리하여, 독자적인 시스템 환경을 구축하는 기술입니다. 이를 리눅스 환경에서 실제로 구현한 것이 리눅스 컨테이너입니다.&lt;/p&gt;
&lt;p&gt;이를 가르켜 &lt;strong&gt;OS수준의 가상화&lt;/strong&gt;라고도 합니다.&lt;/p&gt;
&lt;p&gt;이전에는 ‘application 수준의 가상화’ 기술이 존재했습니다. 이 기술은 기존의 OS(Host OS) 위에 Hypervisor라는 논리적 플랫폼이 필요합니다. 이 플랫폼 위에서 가상의 OS(Guset OS)를 만들어 구동 할 수 있습니다. Hypervisor가 Host OS가 Guest OS의 커널 동작을 해석해서 Host OS가 이해할 수 있는 동작으로 변환해주는 것입니다.&lt;/p&gt;
&lt;p&gt;OS수준의 가상화가 application 수준의 가상화와 주요하게 다른 점은 컨테이너는 Host OS와 커널을 공유한다는 것입니다. 그래서 OS수준의 가상화는 Hypervisor 없이 바로 독자적인 시스템환경을 구축할 수 있습니다.&lt;/p&gt;
&lt;p&gt;다른 말로 컨테이너로 가상화된 프로세스들은 모두 Host OS를 호환해야 합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;리눅스 컨테이너의 장점&lt;/h2&gt;
&lt;p&gt;리눅스 컨테이너를 사용해 얻을 수 있는 장점으로는,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;사용이 편리합니다.
application 수준의 가상화는 가상 OS를 처음부터 세팅 해주어야 하지만 리눅스 컨테이너는 목표하는 어플리케이션 환경만 만들면 되기에 비교적 간편하게 가상화를 이룰 수 있습니다.&lt;/li&gt;
&lt;li&gt;작은 용량을 사용합니다.
가상화를 하기 위해서 OS를 처음부터 새로 설치하지 않아도 됩니다. 이용하고자 하는 어플리케이션 환경만 따로 간편하게 구축을 할 수 있습니다. 따라서, 구축에 필요한 전체적인 저장 공간도 절약할 수 있습니다.&lt;/li&gt;
&lt;li&gt;상대적으로 빠릅니다.
리눅스 컨테이너는 상대적으로 가볍습니다. Hypervisor를 이용해 가상화를 하지 않기 때문에, Hypervisor가 커널동작을 해석하는데 필요한 오버헤드가 발생하지 않습니다. 프로세스가 동작에 필요한 자원만큼만 사용합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;컨테이너를 통해 우리는 실제 개발환경에서 무겁고 느린 가상화를 사용하지 않아도 손쉽고 빠르게 개발을 진행할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;컨테이너가 사용하는 기술&lt;/h1&gt;
&lt;p&gt;리눅스 컨테이너라는 기능이 가능할 수 있었던 이유는 리눅스 커널에서 제공하는 기능 덕분이었습니다.
리눅스 컨테이너 공식문서에서는 리눅스 컨테이너를 가능케한 기술을 설명합니다.&lt;/p&gt;
&lt;p&gt;· namespace
· Chroot
· CGroup&lt;/p&gt;
&lt;p&gt;이외에도 리눅스의 여러 기능이 리눅스 컨테이너를 가능케 했지만, 리눅스 컨테이너를 구성하는 가장 주요한 것은 위 세 개의 기능입니다.
namespace와 chroots, cgroups에 대해 알아보겠습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Chroot&lt;/strong&gt;
프로세스의 루트 디렉토리를 변경하는 것입니다. 프로세스의 루트 디렉토리를 변경하게 되면, 해당 프로세스는 변경된 루트 디렉토리 바깥의 디렉토리에 접근할 수 없게 됩니다. 이를 통해 우리는 프로세스를 물리적으로 격리할 수 있습니다. 이렇게 변경된 환경을 chroot 감옥(chroot jail)이라고 합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;namespace&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;직역하면 이름공간, 의역하면 소속이라 해석할 수 있습니다. 커널의 자원들을 나눠서 이를 특정 프로세스에 제공합니다. 이를 통해 해당 프로세스에서만 사용할 수 있는 자원으로 가상화합니다. 프로세스 테이블(PIDs), ipc, 네트워크(IP주소, 방화벽 등), User ID, 마운트 포인트 등을 가상화할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cgroups&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;프로세스의 자원을 격리하고 사용을 제어하는 기능입니다. 대표적으로 메모리, CPU자원, 네트워크, 디스크 입출력을 제어합니다.
Cgroups의 역할은 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;      a. 자원 제한 : 특정 프로세스의 메모리 사용량을 제한.
      b. 우선 순위 : 특정 프로세스에 더 많은 CPU와 디스크 I/O 처리량을 할당.
      c. 기록 : 프로세스가 자원을 얼마나 사용하고 있는지 측정.
      d. 제어 : 프로세스를 멈추거나, 프로세스의 체크포인트를 설정해주거나 재시작 가능. &lt;/p&gt;
&lt;h1&gt;마치며..&lt;/h1&gt;
&lt;p&gt;이로써 우리는 리눅스 컨테이너에 대해 대략적인 이해를 할 수 있게 되었습니다. LXC가 도커의 기반기술이었던 만큼, 향후에 DOCKER를 이해하는데 있어 많은 도움이 될 것입니다.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://linuxcontainers.org/&quot;&gt;https://linuxcontainers.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.archlinux.org/title/chroot&quot;&gt;https://wiki.archlinux.org/title/chroot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Linux_namespaces&quot;&gt;https://en.wikipedia.org/wiki/Linux_namespaces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Cgroups&quot;&gt;https://en.wikipedia.org/wiki/Cgroups&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[웹 서버와 NginX]]></title><description><![CDATA[…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-30-web-server-and-nginx/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-30-web-server-and-nginx/</guid><pubDate>Fri, 30 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이번에 프로젝트를 진행하면서 처음으로 웹 서버를 직접 구축해보았다.&lt;/p&gt;
&lt;p&gt;웹 서버를 구축하면서 &lt;code class=&quot;language-text&quot;&gt;NginX&lt;/code&gt; 를 사용하게 되었는데, 웹 서버를 구축하는데 필요한 설정들을 편리하게 추가 할 수 있었다.&lt;/p&gt;
&lt;p&gt;하지만 웹 서버를 구축하면서 한 번 사용해본 게 전부이기 때문에, 아직 &lt;code class=&quot;language-text&quot;&gt;NginX&lt;/code&gt; 가 정확하게 무엇이고 어떤 역할을 하는 지 잘모르겠다.&lt;/p&gt;
&lt;p&gt;그래서 이번 글에서는 &lt;code class=&quot;language-text&quot;&gt;NginX&lt;/code&gt; 에 대해서 함께 알아보고자 한다.&lt;/p&gt;
&lt;h2&gt;웹 서버 (Web Server)&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;NginX&lt;/code&gt; 에 대해 알아보기 전에 웹 서버와 웹 서비스의 발전에 대해서 이해할 필요가 있었다.&lt;/p&gt;
&lt;p&gt;초기 웹 서비스는 대부분 정보 전달이 목적인 문서 위주의 정적 페이지였다. 그렇기 때문에 HTML과 같은 마크업 언어로 작성된 문서를 서버에서 보내주기만 하면 됐다.&lt;/p&gt;
&lt;p&gt;이후 조금 발전된 형태로 스크립트 언어를 얹은 동적 페이지가 등장하였지만, 이때까지만 하더라도 서버에서 데이터를 처리하고 클라이언트로 전송해주는데 큰 부담이 되지 않았다.&lt;/p&gt;
&lt;p&gt;하지만 &lt;code class=&quot;language-text&quot;&gt;SPA(Single Page Application)&lt;/code&gt; 라는 개념이 등장하면서 서버가 분리될 필요성이 생겼다.&lt;/p&gt;
&lt;p&gt;SPA는 기존의 웹 페이지와는 달리 페이지 갱신에 필요한 데이터만을 전달받아 페이지를 갱신한다. 이때 필요한 데이터는 사용자마다 달라졌고, 복잡한 연산이 필요하기도 하였다.&lt;/p&gt;
&lt;p&gt;만약 웹 페이지에 필요한 정적 데이터와 페이지 갱신에 필요한 동적 데이터를 하나의 서버에서 처리한다면, 부하가 커지게 되고 처리가 지연됨에 따라 수행 속도가 느려질 것이다.&lt;/p&gt;
&lt;p&gt;그래서 &lt;code class=&quot;language-text&quot;&gt;Web Server&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;WAS(Web Application Server)&lt;/code&gt; 같은 개념이 생겨나게 된다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Web Server&lt;/code&gt; 는 클라이언트의 요청을 처리하는 기능을 담당하고, &lt;code class=&quot;language-text&quot;&gt;WAS(Web Application Server)&lt;/code&gt; 는 DB 조회나 다양한 로직을 처리하는 기능을 담당한다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Web Server&lt;/code&gt;는 클라이언트가 HTML, CSS 와 같은 정적 데이터를 요청하면 앞단에서 빠르게 제공하고, 동적 데이터가 필요하면 &lt;code class=&quot;language-text&quot;&gt;WAS&lt;/code&gt; 에 요청을 보내고, &lt;code class=&quot;language-text&quot;&gt;WAS&lt;/code&gt; 가 처리한 데이터를 클라이언트에 전달한다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;NginX&lt;/code&gt; 는 바로 이 &lt;code class=&quot;language-text&quot;&gt;Web Server&lt;/code&gt; 의 구축을 도와주는 소프트웨어이다.&lt;/p&gt;
&lt;h2&gt;엔진 엑스 (NginX)&lt;/h2&gt;
&lt;p&gt;위에서 설명한 것 처럼 &lt;code class=&quot;language-text&quot;&gt;Web Server&lt;/code&gt; 의 구축을 도와주는 소프트웨어이다. &lt;code class=&quot;language-text&quot;&gt;웹 서버 소프트웨어&lt;/code&gt; 라고도 불린다. 이 &lt;code class=&quot;language-text&quot;&gt;웹 서버 소프트웨어&lt;/code&gt; 은 웹 어플리케이션을 안정적으로 제공할 수 있도록 도와주는 역할을 한다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;웹 서버 소프트웨어&lt;/code&gt; 에는 &lt;code class=&quot;language-text&quot;&gt;NginX&lt;/code&gt; 와도 많이 비교되며, 잘알려져있는 &lt;code class=&quot;language-text&quot;&gt;Apache&lt;/code&gt; 도 있다. 이 둘의 차이는 클라이언트의 요청을 처리하는 동작 방식이라고 한다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Apache&lt;/code&gt; 는 스레드/프로세스 기반으로 하는 방식으로 요청을 처리하는데, 요청 하나당 스레드 하나가 처리하는 구조로 사용자가 많아지면 CPU 와 메모리 사용이 증가해서 성능이 저하될 수 있다고 한다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;NginX&lt;/code&gt; 는 비동기 이벤트 기반으로 하는 방식으로 처리하는데 요청이 들어오면 어떤 동작을 해야하는지만 알려주고 다음 요청을 처리하는 방식으로 진행된다고 하는데, 흐름이 끊기지 않고 응답이 빠르다고 한다.&lt;/p&gt;
&lt;p&gt;이에 맞게 &lt;code class=&quot;language-text&quot;&gt;NginX&lt;/code&gt; 는 공식 홈페이지를 보면 가볍고 빠른 성능을 추구한다고 나와있다.&lt;/p&gt;
&lt;p&gt;그러면 이제 &lt;code class=&quot;language-text&quot;&gt;NginX&lt;/code&gt; 가 웹 어플리케이션을 안정적으로 제공할 수 있도록 도와주는 몇가지 기능에 대해서 알아보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;리버스 프록시 (Reverse Proxy)&lt;/p&gt;
&lt;p&gt;리버스 프록시 서버는 쉽게 설명하면 중계 기능을 하는 하는 서버이다. 클라이언트가 서버를 호출할 때 직접 서버에 접근 하는 것이 아니라 리버스 프록시 서버를 호출하게 되고, 리버스 프록시 서버가 서버에게 요청을 하고 응답을 받아 클라이언트에 전달을 한다.&lt;/p&gt;
&lt;p&gt;클라이언트는 리버스 프록시 서버를 호출하기 때문에 실제 서버의 IP를 감출 수 있고, 이를 통해 보안을 높일 수 있다는 장점이 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;로드밸런싱&lt;/p&gt;
&lt;p&gt;로드밸런서는 직역하면 부하 분산으로, 서버에 가해지는 부하를 분산해주는 역할을 하는 것이다.&lt;/p&gt;
&lt;p&gt;이용자가 많아서 발생하는 요청이 많을 때, 하나의 서버에서 이를 모두 처리하는 것이 아니라 여러대의 서버를 이용하여 요청을 처리하게 한다.&lt;/p&gt;
&lt;p&gt;이때 서버의 로드율과 부하량 등을 고려하여 적절하게 서버들에게 분산처리 하는 것을 로드 밸런싱이라고 한다.&lt;/p&gt;
&lt;p&gt;하나의 서버가 멈추더라도 서비스 중단 없이 다른 서버가 서비스를 계속 유지할 수 있는 무중단 배포가 가능하다는 장점이 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;사실 그 동안 프론트엔드를 공부하면서 서버 구축처럼 인프라를 다루는 부분은 백엔드에게 위임하는 경우가 많았다.&lt;/p&gt;
&lt;p&gt;이번에 백엔드 멍토 크루의 도움을 받아 웹 서버에 대해 공부를 하면서 그동안 무지했던 영역을 조금이나마 공부할 수 있어서 좋았다.&lt;/p&gt;
&lt;p&gt;또한 개발자로써 알아야하는 지식들이 있고, 공부해야 한다는 필요성을 느낄 수 있는 시간이었다.&lt;/p&gt;
&lt;h2&gt;참고 링크&lt;/h2&gt;
&lt;h3&gt;위키백과&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%9B%B9_%EC%84%9C%EB%B2%84&quot;&gt;Web Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%9B%B9_%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98_%EC%84%9C%EB%B2%84&quot;&gt;WAS(Web Application Server)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%95%84%ED%8C%8C%EC%B9%98_%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4_%EC%9E%AC%EB%8B%A8&quot;&gt;Apache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/Nginx&quot;&gt;NginX&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;공식 홈페이지&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://nginx.org/&quot;&gt;NginX&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;유튜브&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Zimhvf2B7Es&amp;#x26;t=670s&quot;&gt;얄팍한 코딩사전 - 아파치, NginX, 톰캣이 뭔가요? (+ 웹서버, WAS, 로드밸런싱, 프록시)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Cypress를 활용한 React 테스팅]]></title><description><![CDATA[cypress에 대한 기본적인 이해가 필요한 글입니다. 0.Intro 바닐라 자바스크립트에서 cypress를 통해 테스트를 진행한 경험은 있지만, react…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-28-react-cypress-testing/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-28-react-cypress-testing/</guid><pubDate>Wed, 28 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;cypress에 대한 기본적인 이해가 필요한 글입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;0.Intro&lt;/h2&gt;
&lt;p&gt;바닐라 자바스크립트에서 cypress를 통해 테스트를 진행한 경험은 있지만, react에서는 아직 적용해보지 못했다. 이번에 프로젝트를 진행하면서 테스팅 도구를 고민하다가 cypress를 선택하게 되었는데, 초기 설정부터 stub response(mock)를 활용한 간단한 network request까지 알아보겠다.&lt;/p&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Cypress 패키지 추가 및 초기 환경 설정&lt;/li&gt;
&lt;li&gt;Stub Response를 활용한 간단한 Network Request 예제&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1. Cypress 패키지 추가 및 초기 환경 설정&lt;/h2&gt;
&lt;p&gt;먼저 관련 패키지들을 추가한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;// shell

&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-D&lt;/span&gt; cypress eslint-plugin-cypress cypress-react-selector&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/cypress-io/eslint-plugin-cypress&quot;&gt;eslint-plugin-cypress&lt;/a&gt;: cypress 테스트 파일 내에서 eslint를 적용하기 위한 패키지&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/abhinaba-ghosh/cypress-react-selector&quot;&gt;cypress-react-selector&lt;/a&gt;: cypress에서 get과 같은 메서드를 이용하지 않고 react component를 바로 불러올 수 있는 패키지&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;먼저 &lt;code class=&quot;language-text&quot;&gt;eslint-plugin-cypress&lt;/code&gt;를 적용하기 위해서 &lt;code class=&quot;language-text&quot;&gt;.eslintrc&lt;/code&gt; 파일로 가서, &lt;code class=&quot;language-text&quot;&gt;&quot;extends&quot;&lt;/code&gt;에 &lt;code class=&quot;language-text&quot;&gt;&quot;plugin:cypress/recommended&quot;&lt;/code&gt;를 추가한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//.eslintrc&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;plugins&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;prettier&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;extends&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    ...

    &lt;span class=&quot;token string&quot;&gt;&quot;plugin:cypress/recommended&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  ...
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 &lt;code class=&quot;language-text&quot;&gt;cypress-react-selector&lt;/code&gt;를 적용하기 위해 cypress의 패키지 추가로 자동 생성된 &lt;code class=&quot;language-text&quot;&gt;cypress/support/index.js&lt;/code&gt; 파일로 가서 &lt;code class=&quot;language-text&quot;&gt;import &apos;cypress-react-selector&apos;&lt;/code&gt;를 추가한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// cypress/support/index.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./commands&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;cypress-react-selector&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;support&lt;/code&gt; 폴더 내 &lt;code class=&quot;language-text&quot;&gt;index.js&lt;/code&gt; 파일에 대해서 잠깐 설명하자면, 이 파일은 모든 &lt;code class=&quot;language-text&quot;&gt;spec file(테스트 파일)&lt;/code&gt;이 실행되기 전에 먼저 실행된다.&lt;/p&gt;
&lt;p&gt;예를 들어, &lt;code class=&quot;language-text&quot;&gt;index.js&lt;/code&gt; 파일에 다음과 같은 코드를 작성하면 cypress 내장 메서드인 &lt;code class=&quot;language-text&quot;&gt;before&lt;/code&gt;의 규칙에 따라 모든 &lt;code class=&quot;language-text&quot;&gt;spec file&lt;/code&gt; 이 실행되기 전에 각각 한 번 씩 실행된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;여긴 cypress/support/index.js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;참고로 &lt;code class=&quot;language-text&quot;&gt;cypress/support/index.js&lt;/code&gt; 파일에서 &lt;code class=&quot;language-text&quot;&gt;before&lt;/code&gt; 혹은 &lt;code class=&quot;language-text&quot;&gt;beforeEach&lt;/code&gt;를 만들고 그 안에 &lt;code class=&quot;language-text&quot;&gt;console&lt;/code&gt;을 찍었을 때는 cypress web ui에 표시되는데, 각각의 테스트 파일(&lt;code class=&quot;language-text&quot;&gt;\*. spec.js&lt;/code&gt;)의 &lt;code class=&quot;language-text&quot;&gt;before&lt;/code&gt; 혹은 &lt;code class=&quot;language-text&quot;&gt;beforeEach&lt;/code&gt;를 만들고 그 안에 &lt;code class=&quot;language-text&quot;&gt;console&lt;/code&gt;을 찍었을 때는 실제 개발자 도구 콘솔에서 표시된다.&lt;/p&gt;
&lt;p&gt;마지막으로 &lt;code class=&quot;language-text&quot;&gt;root&lt;/code&gt; 폴더에 있는 &lt;code class=&quot;language-text&quot;&gt;cypress.json&lt;/code&gt; 파일에서 다음과 같은 속성을 추가한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// cypress.json&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// &quot;baseUrl&quot;: &quot;http://localhost:9000&quot;,&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// &quot;integrationFolder&quot;: &quot;cypress/pages&quot;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;env&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;cypress-react-selector&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;root&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#root&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;root&quot;: &quot;#root&quot;&lt;/code&gt; 속성은 cypress에서 해당 라이브러리를 사용하는 테스트 코드를 작성하면서 이용되는데, 리액트 컴포넌트 트리가 로드되는 것을 확인하기 위해서 &lt;code class=&quot;language-text&quot;&gt;waitForReact&lt;/code&gt;라는 메서드를 사용하는 과정에서 &lt;code class=&quot;language-text&quot;&gt;cy.waitForReact(1000, &apos;#root&apos;)&lt;/code&gt;와 같은 코드를 &lt;code class=&quot;language-text&quot;&gt;cy.waitForReact()&lt;/code&gt;로 간단하게 작성할 수 있도록 도와준다.&lt;/p&gt;
&lt;p&gt;나머지 주석 처리된 부분을 설명하자면, &lt;code class=&quot;language-text&quot;&gt;&quot;baseUrl&quot;&lt;/code&gt;은 각각 테스트 파일마다 &lt;code class=&quot;language-text&quot;&gt;cy.visit(http://localhost:9000&quot;)&lt;/code&gt;을 생략하기 위해서 사용했다(이 속성을 사용해도 각 테스트 파일에서 &lt;code class=&quot;language-text&quot;&gt;cy.visit(&apos;&apos;)&lt;/code&gt;는 써야 한다). 또한, &lt;code class=&quot;language-text&quot;&gt;&quot;integrationFolder&quot;&lt;/code&gt; 속성은 cypress가 통합 테스트 폴더의 path를 지정하는 속성인데 현재 프로젝트에서 page 컴포넌트만 테스트하기 위해서 cypress 폴더 내에 &lt;code class=&quot;language-text&quot;&gt;pages&lt;/code&gt;라는 폴더를 만들어 path를 지정했다(default는 &lt;code class=&quot;language-text&quot;&gt;cypress/integration&lt;/code&gt;). 다른 속성들은 &lt;a href=&quot;https://docs.cypress.io/guides/references/configuration&quot;&gt;공식 문서&lt;/a&gt;에서 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;이제 본격적으로 테스트 코드를 작성해보자.&lt;/p&gt;
&lt;h2&gt;2. Stub Response를 활용한 간단한 Network Request 예제&lt;/h2&gt;
&lt;p&gt;Cypress의 Stubbing 기능을 활용해서 Network Request를 테스트할 수 있다.&lt;/p&gt;
&lt;p&gt;이번 예제에서 실제 url로 요청하고, 미리 작성된 dataset을 통해 응답을 받은 후, 앞서 추가했던 라이브러리인 &lt;code class=&quot;language-text&quot;&gt;cypress-react-selector&lt;/code&gt;로 컴포넌트를 확인하는 방법에 대해서 알아보겠다.&lt;/p&gt;
&lt;p&gt;그럼 코드에서 알아보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Before Login Test&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getReviewList&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;intercept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;GET&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;BASE_URL&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/posts&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;fixture&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;reviewList&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;requestReviewData&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;getReviewList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;visit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;wait&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;@requestReviewData&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;홈페이지에 접속하여 접종 현황을 본다.&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;waitForReact&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;react&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;VaccinationState&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;접종 현황&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;exact&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;접종 현황&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;다음 코드는 프로젝트의 테스트 코드 중 일부이다.&lt;/p&gt;
&lt;p&gt;먼저 HTTP 요청의 행동을 제어하기 위해서 &lt;code class=&quot;language-text&quot;&gt;cy.intercept()&lt;/code&gt; 문법이 사용되었다. &lt;code class=&quot;language-text&quot;&gt;intercept()&lt;/code&gt; 함수는 실제 api를 호출하는 spying, 미리 만들어놓은 데이터로 응답을 반환하는 stubbing, 요청이나 응답을 변경하여 수행할 수 있는 modification을 제공한다. intercept와 관련된 자세한 내용은 &lt;a href=&quot;https://docs.cypress.io/api/commands/intercept&quot;&gt;공식 문서&lt;/a&gt;에서 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;돌아가서 위 코드의 &lt;code class=&quot;language-text&quot;&gt;Line 3&lt;/code&gt;에서는 spying과 stubbing을 진행하고 있는데, 여기서 &lt;code class=&quot;language-text&quot;&gt;fixture&lt;/code&gt;라는 것을 볼 수 있다. &lt;code class=&quot;language-text&quot;&gt;fixture&lt;/code&gt;은 미리 만들어놓은 dataset을 가져오기 위한 cypress 내장 메서드이다. &lt;code class=&quot;language-text&quot;&gt;fixture&lt;/code&gt;은 테스트 환경과 api 요청에 따라 일정한 데이터를 응답할 수 있도록 제어하는 역할을 하는데, 데이터는 &lt;code class=&quot;language-text&quot;&gt;json&lt;/code&gt; 뿐만 아니라 다른 형식들도 가능하다(&lt;a href=&quot;https://docs.cypress.io/api/commands/fixture#JSON&quot;&gt;참조&lt;/a&gt;). 기본적으로 ‘fixture 폴더 내 users 폴더에서 admin.json 파일을 불러온다’라는 의미로 &lt;code class=&quot;language-text&quot;&gt;cy.fixture(&apos;users/admin.json&apos;)&lt;/code&gt; 와 같이 활용되는데, &lt;code class=&quot;language-text&quot;&gt;intercept()&lt;/code&gt; 메서드 내부에서 &lt;code class=&quot;language-text&quot;&gt;{fixture: &apos;dataset의 path&apos;}&lt;/code&gt;와 같은 형태로 축약해 사용될 수 있다. 여기서 &lt;code class=&quot;language-text&quot;&gt;dataset의 path&lt;/code&gt;란 기본적으로 &lt;code class=&quot;language-text&quot;&gt;cypress/fixtures&lt;/code&gt; 안이 기준이다. 예를 들어, dataset이 &lt;code class=&quot;language-text&quot;&gt;fixtures&lt;/code&gt; 폴더 바로 하위에 있다면 &lt;code class=&quot;language-text&quot;&gt;{fixture: &apos;dataset 파일명&apos;}&lt;/code&gt;과 같이 쓰면 된다. 즉, 위의 코드를 풀어서 설명해보면, ‘데이터는 &lt;code class=&quot;language-text&quot;&gt;cypress/fixtures&lt;/code&gt; 폴더 내 &lt;code class=&quot;language-text&quot;&gt;reviewList&lt;/code&gt;라는 파일을 참조’ 라고 말할 수 있다. 여기서 파일명의 확장자는 cypress가 파일을 확인한 후 스스로 해석하므로 생략할 수 있다. 부가적으로 뒤에 &lt;code class=&quot;language-text&quot;&gt;as&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;fixture&lt;/code&gt; 파일의 별칭이다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;fixture&lt;/code&gt; 파일까지 준비되었다면, 바로 코드를 확인해보자. 필자는 테스트 케이스가 실행되기 전에 &lt;code class=&quot;language-text&quot;&gt;before()&lt;/code&gt; 함수 안에 몇 가지 코드를 적어놓았는데, 이는 테스트 케이스를 나타내는 &lt;code class=&quot;language-text&quot;&gt;it()&lt;/code&gt; 안에 넣어도 전혀 문제없다. 코드를 살펴보면 먼저 &lt;code class=&quot;language-text&quot;&gt;intercept()&lt;/code&gt; 메서드를 호출하는 함수를 호출했다. 이후 &lt;code class=&quot;language-text&quot;&gt;wait()&lt;/code&gt;이라는 메서드를 볼 수 있는데, 이는 응답을 stubbing 하는 것과 상관없이, cypress 내에서 요청과 응답을 기다리기 위해서 선언적으로 쓰인다. 현재 코드에서 &lt;code class=&quot;language-text&quot;&gt;cy.wait(&apos;@requestReviewData&apos;)&lt;/code&gt;로 쓰여 있는데, 이 의미는 &lt;code class=&quot;language-text&quot;&gt;requestReviewData&lt;/code&gt;의 요청에 대한 응답이 오기 전까지 기다리는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;이제 &lt;code class=&quot;language-text&quot;&gt;it&lt;/code&gt; 테스트 케이스를 확인해보면, &lt;code class=&quot;language-text&quot;&gt;cy.waitForReact()&lt;/code&gt;라는 메서드를 볼 수 있다. 이 메서드는 이전에 추가했던 &lt;code class=&quot;language-text&quot;&gt;cypress-react-selector&lt;/code&gt; 라이브러리와 관계있는데, 리액트 컴포넌트가 로드되기 전까지 기다리는 메서드이다. 위에서 보았던 &lt;code class=&quot;language-text&quot;&gt;cy.wait(&apos;@requestReviewData&apos;)&lt;/code&gt;와 비슷한 역할을 한다고 보면 된다. 이후 컴포넌트를 불러와 확인하고자 하는 작업을 진행하면 된다. &lt;code class=&quot;language-text&quot;&gt;cypress-react-selector&lt;/code&gt; 메서드와 관련된 자세한 내용은 &lt;a href=&quot;https://github.com/abhinaba-ghosh/cypress-react-selector&quot;&gt;깃허브&lt;/a&gt;에서 확인할 수 있다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Folder-structure&quot;&gt;https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Folder-structure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.cypress.io/guides/references/configuration&quot;&gt;https://docs.cypress.io/guides/references/configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.cypress.io/api/commands/intercept#Syntax&quot;&gt;https://docs.cypress.io/api/commands/intercept#Syntax&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.cypress.io/api/commands/fixture#JSON&quot;&gt;https://docs.cypress.io/api/commands/fixture#JSON&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/abhinaba-ghosh/cypress-react-selector&quot;&gt;https://github.com/abhinaba-ghosh/cypress-react-selector&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[JVM에 관하여 - Part 2, ClassLoader]]></title><description><![CDATA[Java 로 작성한 코드는 어떻게 돌아가는 걸까? 해당 물음에 답을 찾기 위한 JVM 시리즈 2편, JVM 의 구성 요소 중 ClassLoader 에 관한 글입니다.
이번 글에서는 ClassLoader…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-15-jvm-classloader/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-15-jvm-classloader/</guid><pubDate>Mon, 26 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Java&lt;/em&gt; 로 작성한 코드는 어떻게 돌아가는 걸까? 해당 물음에 답을 찾기 위한 &lt;em&gt;JVM&lt;/em&gt; 시리즈 2편, &lt;em&gt;JVM&lt;/em&gt; 의 구성 요소 중 &lt;em&gt;ClassLoader&lt;/em&gt; 에 관한 글입니다.
이번 글에서는 &lt;em&gt;ClassLoader&lt;/em&gt; 가 어떤 역할을 하는지에 대해서 알아봅니다.&lt;/p&gt;
&lt;h2&gt;클래스 파일은 &lt;em&gt;JVM&lt;/em&gt; 에 어떻게 탑재가 될까?&lt;/h2&gt;
&lt;p&gt;지난 글에서는 &lt;em&gt;JVM&lt;/em&gt; 이 원활하게 구동되기 위해서 어떤 환경들이 뒷받침 되는지를 알아보게 되었습니다.
&lt;em&gt;JDK&lt;/em&gt; 에서 개발하고, &lt;em&gt;JRE&lt;/em&gt; 를 통해서 환경을 제공받은 &lt;em&gt;JVM&lt;/em&gt; 은 &lt;em&gt;compile&lt;/em&gt; 된 바이트 코드를 탑재하여 로직을 실행하게 됩니다. 그렇다면 &lt;em&gt;JVM&lt;/em&gt; 에 &lt;em&gt;Class&lt;/em&gt; 는 어떻게 탑재가 되는 걸까요?&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;ClassLoader&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Java Compiler&lt;/em&gt; 를 통해서 &lt;em&gt;.class&lt;/em&gt; 확장자를 가진 클래스 파일은 각 디렉터리에 흩어져 있습니다.
또한, 기본적인 라이브러리의 클래스 파일들은 &lt;em&gt;$JAVA_HOME&lt;/em&gt; 내부 경로에 존재합니다. 각각의 클래스 파일들을 찾아서 &lt;em&gt;JVM&lt;/em&gt; 의 메모리에 탑재해주는 역할을 하는 것이 바로 &lt;em&gt;ClassLoader&lt;/em&gt; 의 역할입니다.&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;ClassLoader&lt;/em&gt; 의 전체적인 역할&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;ClassLoader&lt;/em&gt; 는 클래스 파일을 찾아서 탑재하는 역할뿐만이 아니라 &lt;em&gt;jvm&lt;/em&gt; 에 관련된 다른 일들도 같이하게 됩니다. &lt;em&gt;ClassLoader&lt;/em&gt; 는 크게 &lt;em&gt;Loading&lt;/em&gt;, &lt;em&gt;Linking&lt;/em&gt;, 그리고 &lt;em&gt;Initialization&lt;/em&gt; 3가지 역할을 맡게 됩니다.
&lt;em&gt;Loading&lt;/em&gt; 은 클래스 파일을 탑재하는 과정, &lt;em&gt;Linking&lt;/em&gt; 은 클래스 파일을 사용하기 위해 검증하고, 기본 값으로 초기화하는 과정입니다.
&lt;em&gt;Initialization&lt;/em&gt; 은 &lt;em&gt;static field&lt;/em&gt; 의 값들을 정의한 값으로 초기화를 하는 과정입니다.&lt;br&gt;
각각의 과정을 순서대로 살펴보도록 하겠습니다.&lt;/p&gt;
&lt;h3&gt;&lt;em&gt;Loading&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;ClassLoader&lt;/em&gt; 가 필요한 클래스 파일들을 찾아 탑재하게 됩니다.
각각의 클래스 파일들이 기본으로 제공받는 클래스 파일인지 혹은 개발자가 정의한 클래스 파일인지와 같은 기준에 의해서 &lt;em&gt;ClassLoader&lt;/em&gt; 의 수준도 세 가지로 나뉘게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1225px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8620cd5d0bd1f37f2e603a5a5d763b39/2f3a8/2021-07-26-classloader-process.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 43.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABJ0AAASdAHeZh94AAABg0lEQVR42k1Si3LCMAzj/7+QjULb9N2QtKHp63hostm45U7YOIkjWT2Aa4ozxtuEaV6JBbdpfoP1MEXNZT0eD42RZ4bxpvuyJ/m8rLp3eDyfKNseRVXj+5QguaQ4pxkyU+DMPC9KNL2D8x5fxyOy3KCzHuu+617Je5HNyqZ/N5QfeaHrraJuWmVVMZqy0loge2EnuN/vZBU/zJTpPxWH1wu4+gG9dTBFpUydH9F0vaLtLNxww7ZtsNZSLscTJoXcsVfHByM8G2vDJyXXrUXTdkjOF0oqkGZG4yk5oyhrtJQ8jCNMnvN/BesGZXX6PS8eSI+PZD8ESuxYlFk2ao5hI8ml5segh+VxWWJgf/Uah/DOpemHYadyS5qQaRQWZd3owE1R8MIAz7EcaYqhWY4ErHPIjajJVbYYpQ0XcYhMTslFHRa5XX+l/FRxSXO6WWFdV+x0dl4WVSTzk3tinjB0/q3iEGMk3Qkzhy2HBbKkwbbtWg8hfL7BP+kC+okXXdVc3AXwA1lGqsyXi3lEAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;classloader-process-order&quot;
        title=&quot;&quot;
        src=&quot;/static/8620cd5d0bd1f37f2e603a5a5d763b39/2f3a8/2021-07-26-classloader-process.png&quot;
        srcset=&quot;/static/8620cd5d0bd1f37f2e603a5a5d763b39/0eb09/2021-07-26-classloader-process.png 500w,
/static/8620cd5d0bd1f37f2e603a5a5d763b39/1263b/2021-07-26-classloader-process.png 1000w,
/static/8620cd5d0bd1f37f2e603a5a5d763b39/2f3a8/2021-07-26-classloader-process.png 1225w&quot;
        sizes=&quot;(max-width: 1225px) 100vw, 1225px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;탑재 과정을 추적하기 위해서 다음과 같은 코드를 작성하게 되었습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;util&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LinkedList&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;util&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Main&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token class-name&quot;&gt;UserClass&lt;/span&gt; userClass &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; number &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; numbers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LinkedList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserClass&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; num&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; num&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;num &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; num&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getNum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; num&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;탑재 과정 추적은 다음과 같은 명령어로 알 수가 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;java&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-verbose:class&lt;/span&gt; Main&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;1. &lt;em&gt;Bootstrap ClassLoader&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Bootstrap ClassLoader&lt;/em&gt; 는 다른 모든 &lt;em&gt;ClassLoader&lt;/em&gt; 의 부모가 되는 &lt;em&gt;ClassLoader&lt;/em&gt; 입니다. &lt;em&gt;rt.jar&lt;/em&gt; 를 포함하여, &lt;em&gt;JVM&lt;/em&gt; 을 구동시키기 위한 가장 필수적인 라이브러리의 클래스들을 &lt;em&gt;JVM&lt;/em&gt; 에 탑재하게 됩니다.
가장 상위의 &lt;em&gt;ClassLoader&lt;/em&gt; 이므로 다른 &lt;em&gt;ClassLoader&lt;/em&gt; 와는 다르게 탑재되는 운영체제에 맞게 네이티브 코드로 쓰여있습니다.&lt;br&gt;
다음은 담당 클래스 파일들을 탑재하는 내용입니다. &lt;em&gt;Java&lt;/em&gt; 실행 초기에 작동이 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Opened /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/rt.jar&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Loaded java.lang.Object from /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/rt.jar&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Loaded java.io.Serializable from /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/rt.jar&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.중략&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2. &lt;em&gt;Extensions ClassLoader&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;BootStrap ClassLoader&lt;/em&gt; 다음으로 우선순위를 가지는 &lt;em&gt;ClassLoader&lt;/em&gt; 입니다. &lt;em&gt;localedata&lt;/em&gt;, &lt;em&gt;zipfs&lt;/em&gt; 등 다른 표준 핵심 &lt;em&gt;Java Class&lt;/em&gt; 의 라이브러리들을 &lt;em&gt;JVM&lt;/em&gt; 에 탑재하는 역할을 하고 있습니다.
이들은 &lt;em&gt;$JAVA_HOME/jre/lib/ext&lt;/em&gt; 에 있습니다.&lt;/p&gt;
&lt;h3&gt;3. &lt;em&gt;Application ClassLoader&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;System ClassLoader&lt;/em&gt; 라고도 합니다. &lt;em&gt;Classpath&lt;/em&gt; 에 있는 클래스들을 탑재합니다. 개발자들이 자바 코드로 짠 클래스 파일들을 &lt;em&gt;JVM&lt;/em&gt; 에 탑재하는 역할을 하고 있습니다.
만약 개발자가 &lt;em&gt;ClassLoader&lt;/em&gt; 를 구현하여 사용하게 되면, 기본적으로 위의 &lt;em&gt;Application ClassLoader&lt;/em&gt; 의 자식 형태로 사용자 정의된 &lt;em&gt;ClassLoader&lt;/em&gt; 를 구성하게 됩니다.
다음은 담당 클래스 파일들을 탑재하는 내용입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.중략&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Loaded UserClass from file:/home/hybeom/OTC/java8/src/&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.중략&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위의 각각의 &lt;em&gt;ClassLoader&lt;/em&gt; 들을 모두 거쳤는데도, 클래스 파일을 찾지 못하게 되면, &lt;em&gt;ClassNotFoundException&lt;/em&gt; 이라는 예외를 던지게 됩니다.
해당 상황 묘사를 위하여 &lt;em&gt;UserClass&lt;/em&gt; 클래스 파일을 삭제한 이후에 해당 명령어를 다시 작동시켰습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.중략&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;
java.lang.NoClassDefFoundError: UserClass
    	at Main.main&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Main.java:7&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
Caused by: java.lang.ClassNotFoundException: UserClass
    	at java.net.URLClassLoader.findClass&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;URLClassLoader.java:382&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.중략&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 &lt;em&gt;Loading&lt;/em&gt; 과정에서 하위 &lt;em&gt;ClassLoader&lt;/em&gt; 가 로딩한 클래스 파일은 상위 &lt;em&gt;ClassLoader&lt;/em&gt; 가 로딩한 클래스 파일을 볼 수가 있습니다. 하지만 반대는 불가능합니다.
이를 &lt;em&gt;Visibility&lt;/em&gt; 라고 하며, 각각의 클래스 파일을 계층으로 관리해줄 수 있게 합니다.
또한, 한번 &lt;em&gt;JVM&lt;/em&gt; 에 탑재된 클래스 파일은 종료될 때까지 &lt;em&gt;JVM&lt;/em&gt; 에서 제거되지 않습니다.&lt;/p&gt;
&lt;h4&gt;각각 &lt;em&gt;ClassLoader&lt;/em&gt; 의 구현 구조&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;ClassLoader&lt;/em&gt; 는 앞서 말한 순서대로 클래스 파일들을 찾게 됩니다. 실제로 코드를 통해 부모 역할을 하는 &lt;em&gt;ClassLoader&lt;/em&gt; 를 찾으면 다음과 같이 나오게 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Main&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token class-name&quot;&gt;UserClass&lt;/span&gt; userClass &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Application ClassLoader: &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClassLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Extension ClassLoader: &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClassLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getParent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Bootstrap ClassLoader: &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClassLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getParent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getParent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이때, &lt;em&gt;getParent()&lt;/em&gt; 메소드는 부모 클래스를 찾는 메소드가 아닌 상위 파일을 찾는 메소드입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Application ClassLoader: sun.misc.Launcher&lt;span class=&quot;token variable&quot;&gt;$AppClassLoader&lt;/span&gt;@75b84c92
Extension ClassLoader: sun.misc.Launcher&lt;span class=&quot;token variable&quot;&gt;$ExtClassLoader&lt;/span&gt;@4aa298b7
Bootstrap ClassLoader: null&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;하지만 구현은 다음과 같이 이루어지게 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;
&lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExtClassLoader&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URLClassLoader&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AppClassLoader&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URLClassLoader&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URLClassLoader&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SecureClassLoader&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Closeable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SecureClassLoader&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClassLoader&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClassLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 703px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e08ec3fccf7751fb3fa73c31dbc6e5d7/dac1b/2021-07-26-classloader-implementation.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 106.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAYAAABG1c6oAAAACXBIWXMAAA7DAAAOwwHHb6hkAAADK0lEQVR42q1U6XLaYAzM+z9NO+1kOkmbSRpyQAIhxhcY38Y2931DtpKISYn7s54RtpG8n7Ra6ewNh2s+X2IwHGM4mpzYZDrHZHawz77M3t4yFOCMf/gPywkQp12knf7R2t0B6pYDo27BbNhoJZ0Tf6c3oO9CAc1wBHC73cELY3ycg+OpYRDA0HW4rovtZpPzd/tDAh5+AGYgnI0fJQjjNsJWioCMD2m6AWp6HZrRQNPxDzHkE6PYIEopoe1HydlJU+IoaffltCCKoRLI03MFN7d3+HHxExeXV7h7KOKpXBUKuHxOgjP8+5KS9/s9bDdC/70pI7LeYAg/iAi8hThpE0AK2/GQtrsYTabSwPFkBi+I5TnHoR8mwmFGwYb48n0fOvEXEI+WZUFVVTpsJP79kcMRuiccvjs6lL4XtIibhMBj4Y/5Yg6riiY8uuR3/ZbcA/KxcSK73e605ENGWymBddcfjOD5AZSaiofHIm5+F4jLAh6LJeiGiahFZY7GEjtfLPMccpZNNyRt9YRkzjKKU3lvCX/cgL5Y5uOGMM/83Wg8/czhVsrIyk+SBJqmwTRN2LaNRqMBwzDgOA7K5bL8f6SKxM10nQAKSLsn2uJsYnrmd9aY60fQ67ZMjONFlF2bMh/IVHEs87xeb/6tQxYqBzIIN+GxVMblr2t8+XaOr9/PcXVTQIl0yBrlxjF4ll1Oh3z6lEheEMlj0pntuGIh6TBtd+hD4jNO4Ho+NW2I2XyB1XIlVeV1SG23idzlao01dZtBraYtHeW7QZwxUE3VUK2+otcfiCo2pN9W2jlOywmHPCXMG3eWu8lBEXXYdn0pUTcbpNOIYrrSCI7jjcPf8GDkdLhcrWTsprMFZTCUMVNqGu5pfguFe1zf3KL0XKYlYZJGw+PoTchyHHL6PMusOZ6K50oViqrTEmhiSBorvejU+T5tG49AK3h5raFSVWTOHZqc3D5ktXOHd7stNJpXz6OGNJuo1RSZ6ULx9bAIPAc66TOgGVcUBd1uR6hiCnIcRnEHDdrailaHablQDQvVmi6b3KWNwpkYtLU10xK/opry3qTKuJE5Dv/X9QcL9ilkYAvXZQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;classloader-implementation&quot;
        title=&quot;&quot;
        src=&quot;/static/e08ec3fccf7751fb3fa73c31dbc6e5d7/dac1b/2021-07-26-classloader-implementation.png&quot;
        srcset=&quot;/static/e08ec3fccf7751fb3fa73c31dbc6e5d7/0eb09/2021-07-26-classloader-implementation.png 500w,
/static/e08ec3fccf7751fb3fa73c31dbc6e5d7/dac1b/2021-07-26-classloader-implementation.png 703w&quot;
        sizes=&quot;(max-width: 703px) 100vw, 703px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Bootstrap ClassLoader&lt;/em&gt; 는 네이티브하게 짜져있기 때문에 null 값을 반환하게 됩니다.
나머지 &lt;em&gt;ClassLoader&lt;/em&gt; 는 &lt;em&gt;URLClassLoader&lt;/em&gt;, &lt;em&gt;SecureClassLoader&lt;/em&gt; 라는 객체를 상속받아서 구현이 됩니다.&lt;/p&gt;
&lt;h3&gt;&lt;em&gt;Linking&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Linking&lt;/em&gt; 은 로드된 클래스 파일들을 검증하고, 사용할 수 있게 준비하는 과정을 의미합니다.
&lt;em&gt;Linking&lt;/em&gt; 또한 &lt;em&gt;Verification&lt;/em&gt; , &lt;em&gt;Preparation&lt;/em&gt; , 그리고 &lt;em&gt;Resolution&lt;/em&gt; 이라는 세 가지 단계로 이루어져 있습니다.&lt;/p&gt;
&lt;h3&gt;1. &lt;em&gt;Verification&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;클래스 파일이 유효한지를 확인하는 과정입니다. 클래스 파일이 &lt;em&gt;JVM&lt;/em&gt; 의 구동 조건 대로 구현되지 않았을 경우에는 &lt;em&gt;VerifyError&lt;/em&gt; 를 던지게 됩니다.&lt;/p&gt;
&lt;h3&gt;2. &lt;em&gt;Preparation&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;클래스 및 인터페이스에 필요한 &lt;em&gt;static field&lt;/em&gt; 메모리를 할당하고, 이를 기본값으로 초기화를 합니다. 기본값으로 초기화된 &lt;em&gt;static field&lt;/em&gt; 값들은 뒤의 &lt;em&gt;Initialization&lt;/em&gt; 과정에서 코드에 작성한 초기값으로 변경이 됩니다.
이 때문에 &lt;em&gt;JVM&lt;/em&gt; 에 탑재된 클래스 파일의 코드를 작동시키지는 않습니다.&lt;/p&gt;
&lt;h3&gt;3. &lt;em&gt;Resolution&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Symbolic Reference&lt;/em&gt; 값을 &lt;em&gt;JVM&lt;/em&gt; 의 메모리 구성 요소인 &lt;em&gt;Method Area&lt;/em&gt; 의 런타임 환경 풀을 통하여 &lt;em&gt;Direct Reference&lt;/em&gt; 라는 메모리 주소 값으로 바꾸어줍니다.
해당 단계의 영향을 받는 &lt;em&gt;JVM Instruction&lt;/em&gt; 요소는 &lt;em&gt;new&lt;/em&gt; 및 &lt;em&gt;instanceof&lt;/em&gt; 가 있습니다.&lt;/p&gt;
&lt;h3&gt;&lt;em&gt;Initialization&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Linking&lt;/em&gt; 과정을 거치면 &lt;em&gt;Initialization&lt;/em&gt; 단계에서는 클래스 파일의 코드를 읽게 됩니다. &lt;em&gt;Java&lt;/em&gt; 코드에서의 &lt;em&gt;class&lt;/em&gt; 와 &lt;em&gt;interface&lt;/em&gt; 의 값들을 지정한 값들로 초기화 및 초기화 메서드를 실행시켜줍니다.
이때, &lt;em&gt;JVM&lt;/em&gt; 은 멀티 쓰레딩으로 작동을 하며, 같은 시간에 한 번에 초기화를 하는 경우가 있기 때문에 초기화 단계에서도 동시성을 고려해주어야 합니다.
&lt;em&gt;Class Loader&lt;/em&gt; 를 통한 클래스 탑재 과정이 끝나면 본격적으로 &lt;em&gt;JVM&lt;/em&gt; 에서 클래스 파일을 구동시킬 준비가 끝나게 됩니다.&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;Java 8&lt;/em&gt; 과 &lt;em&gt;Java 11&lt;/em&gt; 의 차이&lt;/h2&gt;
&lt;p&gt;앞서 &lt;em&gt;Java&lt;/em&gt; 를 옵션과 함께 실행하면서, &lt;em&gt;rt.jar&lt;/em&gt; 의 위치를 확인할 수가 있었습니다.
하지만 다른 &lt;em&gt;LTS&lt;/em&gt; 버전인 11을 똑같은 명령어로 실행하면 다음과 같이 나오게 됩니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;.001s&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;info&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;class,load&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; opened: /usr/lib/jvm/java-11-openjdk-amd64/lib/modules
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;.001s&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;info&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;class,load&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; opened: /usr/share/java/java-atk-wrapper.jar
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;.007s&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;info&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;class,load&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; java.lang.Object source: shared objects &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;실제 &lt;em&gt;jvm/lib&lt;/em&gt; 경로에서도 해당 &lt;em&gt;rt.jar&lt;/em&gt; 를 찾을 수가 없습니다. 그 이유는 &lt;em&gt;Java 9&lt;/em&gt; 버전부터 &lt;em&gt;rt.jar&lt;/em&gt; 및 &lt;em&gt;ext&lt;/em&gt; 경로가 삭제되고, 효율성을 위해서 클래스들은 다른 경로로 이동이 되었습니다.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/classloader-in-java/&quot;&gt;GeekforGeek ClassLoader in Java&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://www.baeldung.com/java-classloaders&quot;&gt;baeldung Java Class Loader&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/net/URLClassLoader.html&quot;&gt;Oracle Java docs URLClassLoader&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://jcp.org&quot;&gt;Java Community Process JVM ClassLoader&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/sun/misc/Launcher.java&quot;&gt;sun/misc/Launcher source code&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[JPA Pagination, 그리고 N + 1 문제]]></title><description><![CDATA[1. Pagination 게시판 기능을 제공하는 웹 어플리케이션에 접속하여 게시물 목록을 요청하는 경우를 상상해봅시다. DB…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-26-jpa-pageable/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-26-jpa-pageable/</guid><pubDate>Mon, 26 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. Pagination&lt;/h2&gt;
&lt;p&gt;게시판 기능을 제공하는 웹 어플리케이션에 접속하여 게시물 목록을 요청하는 경우를 상상해봅시다. DB에 저장되어 있는 게시물은 수백 만개에 육박할 수도 있습니다. 모든 게시물 목록을 조회해 화면에 렌더링하는 경우, 클라이언트가 브라우저 혹은 모바일 기기로 이를 한 눈에 보기 어려움을 겪을 공산이 큽니다. 또한 클라이언트가 보지도 않을 데이터까지 DB에서 조회하여 네트워크를 통해 전달하기 때문에, 서버의 리소스가 불필요하게 낭비됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/126959267-f01b6829-19bf-4662-829b-651049cc55c2.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;반면 위 사진처럼 한 페이지에서는 N개의 데이터만 보여주고, 다음 페이지로 이동하라는 클라이언트의 추가 요청이 있을 때 마다 다음 순번의 N개의 데이터를 보여준다면 UX 및 리소스 측면의 단점을 보완할 수 있습니다.&lt;/p&gt;
&lt;p&gt;이 처럼 한 화면에 보여주는 데이터의 범위를 결정하는 일련의 방식을 페이지네이션 혹은 페이징이라고 합니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;2. JPA의 Pagination API&lt;/h2&gt;
&lt;p&gt;DB 벤더에 따라 페이징을 처리하는 쿼리가 천차만별입니다. MySQL의 경우 LIMIT 및 OFFSET 구문 등을 사용함으로써 페이징을 처리할 수 있지만, Oracle은 그보다 더 복잡한 쿼리가 수반됩니다. 그러나 JPA를 사용한다면 별도의 쿼리 작성 없이 Pagination API를 사용하기만 하면 됩니다. JPA가 설정된 DB 벤더 방언에 맞게 페이징 쿼리를 자동으로 생성하기 때문입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PostRepositoryTest.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@DisplayName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;간단한 페이징을 적용해본다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;usePagination&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;EntityManager&lt;/span&gt; entityManager &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; testEntityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntityManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; posts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;select p from Post p&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFirstResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setMaxResults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getResultList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;SQL&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;Hibernate:
    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;
        post0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id1_4_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        post0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; content2_4_
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;
        post post0_ &lt;span class=&quot;token keyword&quot;&gt;limit&lt;/span&gt; ?&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;현재 설정된 방언에 따라 JPQL 쿼리 및 페이지네이션 기능이 SQL 쿼리로 변환되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.1. Spring Data JPA&lt;/h3&gt;
&lt;p&gt;Spring Data JPA의 경우 Pageable 구현체를 쿼리 메서드의 파라미터로 전달함으로써 쿼리에 페이징을 동적으로 추가할 수 있습니다. 개발자가 Pageable 인터페이스를 직접 구현하거나, 미리 준비되어 있는 정적 팩토리 메서드를 통해 간편하게 페이지네이션 범위를 지정할 수 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PostRepository.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;select p from Post p&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findWithPagination&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;PostRepositoryTest.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@DisplayName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Pageable을 사용하여 페이징 처리한다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pagination&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    postRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findWithPagination&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ofSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    postRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findWithPagination&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PageRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;SQL&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;Hibernate:
    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;
        post0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id1_4_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        post0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; content2_4_
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;
        post post0_ &lt;span class=&quot;token keyword&quot;&gt;limit&lt;/span&gt; ?&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Spring Data JPA는 추가적으로 Sort 구현체를 쿼리 메서드의 파라미터로 전달함으로써 정렬 기능을 수행할 수 있습니다.
&lt;ul&gt;
&lt;li&gt;Pageable 관련 정적 팩토리 메서드들은 페이징할 데이터의 개수뿐만 아니라, 페이징 정렬 조건(Sort)까지 함께 지정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이처럼 간편하게 페이징 및 정렬을 처리할 수 있는 이유는, Spring Data JPA가 제공하는 JpaRepository 인터페이스가 PagingAndSortingRepository를 확장하고 있기 때문입니다.
&lt;ul&gt;
&lt;li&gt;아울러 Spring Data JPA는 HandlerMethodArgumentResolver 인터페이스 구현체를 제공하기 때문에, Sort 및 Pageable을 컨트롤러 메소드의 파라미터로 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2&gt;3. N + 1과 Fetch Join&lt;/h2&gt;
&lt;p&gt;N + 1 문제란 쿼리 1번으로 N건의 엔티티를 가져왔는데, 글로벌 지연 로딩 전략으로 인해 관련 컬럼을 얻기 위해 쿼리를 N번 추가로 수행하는 현상을 의미합니다. 페이징으로 조회한 N개의 Post 엔티티를 순회하며 Comment를 조회해봅시다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Post.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GenerationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@OneToMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mappedBy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fetch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FetchType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;LAZY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cascade &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CascadeType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;ALL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; comments &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;PostRepositoryTest.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@DisplayName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Pageable을 사용하여 페이징 처리한다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pagination&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; posts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; postRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findWithPagination&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PageRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; post &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; posts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getComments&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;SQL&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;Hibernate:
    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;
        post0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id1_4_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        post0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; content2_4_
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;
        post post0_ &lt;span class=&quot;token keyword&quot;&gt;limit&lt;/span&gt; ?
Hibernate:
    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; post_id4_2_0_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id1_2_0_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id1_2_1_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; content2_2_1_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;like_id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; like_id3_2_1_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; post_id4_2_1_
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt; comments0_
    &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;?
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;learning&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jpa&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@3a1238cc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
Hibernate:
    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; post_id4_2_0_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id1_2_0_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id1_2_1_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; content2_2_1_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;like_id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; like_id3_2_1_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; post_id4_2_1_
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt; comments0_
    &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;?
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;learning&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jpa&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@ce5df3f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
Hibernate:
    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; post_id4_2_0_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id1_2_0_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id1_2_1_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; content2_2_1_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;like_id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; like_id3_2_1_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; post_id4_2_1_
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt; comments0_
    &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;?
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;learning&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jpa&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@5ab690ec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Post는 1:N 관계를 맺고 있는 Comment에 대해 글로벌 지연 로딩 전략을 채택하고 있습니다. 그 결과, 페이징(LIMIT)을 통해 조회한 Post 리스트를 순회하면서 &lt;code class=&quot;language-text&quot;&gt;getComments()&lt;/code&gt;를 호출할 때마다 추가적으로 Comment 조회 쿼리가 발생합니다.&lt;/p&gt;
&lt;p&gt;즉, 조회한 Post가 1000개라면 1000개의 추가 쿼리가 발생하는 전형적인 &lt;strong&gt;N + 1 문제&lt;/strong&gt;가 나타납니다. 이를 해결하기 위해 대게 Fetch Join을 적용하게 됩니다.&lt;/p&gt;
&lt;h3&gt;3.1. Fetch Join&lt;/h3&gt;
&lt;p&gt;Fetch Join이란 JPQL로 특정 엔티티를 조회할 때 연관된 엔티티 혹은 컬렉션을 즉시 로딩과 같이 한 번에 함께 조회하는 기능입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PostRepository.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;select distinct p from Post p join fetch p.comments&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findWithPagination&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;SQL&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token number&quot;&gt;2021&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;07&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;26&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;22&lt;/span&gt;:&lt;span class=&quot;token number&quot;&gt;34&lt;/span&gt;:&lt;span class=&quot;token number&quot;&gt;32.764&lt;/span&gt;  WARN &lt;span class=&quot;token number&quot;&gt;14832&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;--- [    Test worker] o.h.h.internal.ast.QueryTranslatorImpl   : HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!&lt;/span&gt;
Hibernate:
    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;distinct&lt;/span&gt; post0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id1_4_0_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments1_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id1_2_1_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        post0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; content2_4_0_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments1_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; content2_2_1_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments1_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;like_id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; like_id3_2_1_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments1_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; post_id4_2_1_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments1_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; post_id4_2_0__&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments1_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id1_2_0__
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;
        post post0_
    &lt;span class=&quot;token keyword&quot;&gt;inner&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt; comments1_
            &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; post0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;comments1_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;learning&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jpa&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@72dc246c&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;learning&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jpa&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@6e76be45&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;learning&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jpa&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@37ef8e6b&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Post 전체 조회 쿼리 1개 (1) + 각 Post별 Comment 조회 쿼리 3개 (N)&lt;/code&gt; 총 쿼리가 4개가 날아가던 것이 Fetch Join을 적용함으로써 1개로 줄어들었습니다. 성능이 최적화된 것으로 보이지만 다소 이상한 부분이 존재합니다.&lt;/p&gt;
&lt;p&gt;먼저, 페이징할 때 사용하던 기존의 SQL LIMIT 구문이 등장하지 않았습니다. 또한 쿼리 결과를 전부 메모리에 적재한 뒤 어플리케이션 단에서 Pagination 작업을 수행한다는 경고 로그가 발생합니다. 왜 이러한 현상이 발생하는 것일까요?&lt;/p&gt;
&lt;p&gt;Post 엔티티가 3개 있고, 각각의 Post 엔티티는 연관된 Comment가 7개 존재한다고 가정해봅시다. 1:N 관계를 Join하면 총 21(3 * 7)개의 DB Row가 조회됩니다. 데이터의 수가 변경되기 때문에 단순하게 LIMIT 구문을 사용하는 쿼리로 페이지네이션을 적용하기 어렵습니다. 따라서 조회한 결과를 모두 메모리로 가져와서 JPA가 페이지네이션 계산을 진행합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1:N 관계의 컬렉션을 Fetch Join하면서 동시에 Pagination API를 사용하면 OutOfMemoryError가 발생할 수 있기 때문에, 이 둘을 동시에 사용해서는 안 됩니다.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;CommentTest.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; comments &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entityManager
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;select c from Comment c join fetch c.post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFirstResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setMaxResults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getResultList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;SQL&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;Hibernate:
    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;
        comment0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id1_2_0_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        post1_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id1_4_1_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comment0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; content2_2_0_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comment0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; post_id4_2_0_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        post1_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; content2_4_1_
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt; comment0_
    &lt;span class=&quot;token keyword&quot;&gt;inner&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt;
        post post1_
            &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; comment0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;post1_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;limit&lt;/span&gt; ?&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;반대로 N:1 관계의 엔티티를 Fetch Join할 때는 문제없이 Pagination API를 적용할 수 있습니다.
&lt;ul&gt;
&lt;li&gt;Comment와 Post는 N:1 관계이기 때문에 Join해도 조회되는 DB ROW의 수가 변경되지 않기 때문입니다.&lt;/li&gt;
&lt;li&gt;SQL 로그에도 LIMIT 구문이 잘 찍혀있고, 별도의 경고 로그가 발생하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2&gt;4. 해결 방안&lt;/h2&gt;
&lt;p&gt;Pagination API를 사용할 때 &lt;code class=&quot;language-text&quot;&gt;~ToOne&lt;/code&gt; 관계의 엔티티는 Fetch Join해도 괜찮지만, &lt;code class=&quot;language-text&quot;&gt;~ToMany&lt;/code&gt; 관계의 엔티티에 대해서는 다른 접근이 필요합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;application.properties&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;properties&quot;&gt;&lt;pre class=&quot;language-properties&quot;&gt;&lt;code class=&quot;language-properties&quot;&gt;&lt;span class=&quot;token key attr-name&quot;&gt;spring.jpa.properties.hibernate.default_batch_fetch_size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;1000&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그 중 가장 쉬운 해결 방안으로는 Batch Size를 지정하는 것입니다. @BatchSize 애너테이션을 부착할 수도 있지만, 어플리케이션 전역 설정을 위해 properties에 값을 정의했습니다. 해당 옵션에 대한 설명은 후술하겠습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PostRepositoryTest.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; posts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entityManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;select p from Post p&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFirstResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setMaxResults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getResultList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;SQL&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;Hibernate:
    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;
        post0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id1_4_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        post0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; content2_4_
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;
        post post0_ &lt;span class=&quot;token keyword&quot;&gt;limit&lt;/span&gt; ?
Hibernate:
    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; post_id4_2_1_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id1_2_1_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id1_2_0_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; content2_2_0_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;like_id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; like_id3_2_0_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; post_id4_2_0_
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt; comments0_
    &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;
        comments0_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post_id &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            ?&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ?&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ?
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;learning&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jpa&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@6090a8fc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;learning&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jpa&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@77d45b37&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;learning&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jpa&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@23447af&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Fetch Join 없이 Pagination API를 사용해보았습니다. 페이징(LIMIT)을 통해 POST 리스트를 조회하지만, Comment 관련 조회 쿼리가 기존과 다르게 나갑니다. 기존에는 반복문을 순회하면서 N개의 Post 엔티티에 대해 &lt;code class=&quot;language-text&quot;&gt;where comments0_.post_id=?&lt;/code&gt;를 포함하는 Comment 조회 쿼리가 N번 발생했습니다. 그러나 Batch Size를 적용한 결과 &lt;code class=&quot;language-text&quot;&gt;where comments0_.post_id in (?, ?, ?)&lt;/code&gt;를 포함하는 Comment 조회 쿼리 1개로 줄어들었습니다.&lt;/p&gt;
&lt;p&gt;@BatchSize 혹은 &lt;code class=&quot;language-text&quot;&gt;spring.jpa.properties.hibernate.default_batch_fetch_size&lt;/code&gt; 옵션을 적용하면&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;X 타입 엔티티가 지연 로딩된 &lt;code class=&quot;language-text&quot;&gt;~ToMany&lt;/code&gt; 관계의 Y 타입 컬렉션을 최초 조회할 때&lt;/li&gt;
&lt;li&gt;이미 조회한 X 타입 엔티티(즉, 영속성 컨텍스트에서 관리되고 있는 엔티티)들의 ID들을 모아서&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;WHERE Y.X_ID IN (?, ?, ?...)&lt;/code&gt; 와 같은 SQL IN 구문에 담아 Y 타입 데이터 조회 쿼리를 날립니다.&lt;/li&gt;
&lt;li&gt;X 타입 엔티티들이 필요로 하는 모든 Y 타입 데이터를 한 번에 조회합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;여기서 Batch Size 옵션에 할당되는 숫자는 &lt;strong&gt;IN 구문에 넣을 부모 엔티티 Key(ID)의 최대 개수&lt;/strong&gt;를 의미합니다.&lt;/p&gt;
&lt;p&gt;예를 들어 봅시다. 별다른 조치를 취하지 않은 상황에서, Post 1000개가 담긴 리스트를 순회하면서 Comment를 호출하는 코드는 지연 로딩으로 인해 1000개의 추가 쿼리가 발생합니다.&lt;/p&gt;
&lt;p&gt;반면 Batch Size 옵션을 1000으로 지정해두면, 반복문을 순회하며 Comments를 최초로 조회하는 시점에&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;영속성 컨텍스트에서 관리되고 있는 1000개의 Post 엔티티 ID가&lt;/li&gt;
&lt;li&gt;Comments 조회 쿼리의 IN 구문 &lt;code class=&quot;language-text&quot;&gt;where comment.post_id in (?, ?, ?, ...)&lt;/code&gt;에 포함되어 날아갑니다.&lt;/li&gt;
&lt;li&gt;단 하나의 Comment 조회 쿼리로 1000개의 Post 엔티티가 필요로 하는 모든 Comment 관련 데이터를 조회해옵니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;
&lt;h2&gt;5. 마치며&lt;/h2&gt;
&lt;p&gt;Batch Size 옵션은 상술한 &lt;code class=&quot;language-text&quot;&gt;Pagination + Fetch Join 문제&lt;/code&gt;뿐만 아니라, 다른 JPA의 한계를 극복하는데 도움이 됩니다.&lt;/p&gt;
&lt;p&gt;가령, JPA는 엔티티를 조회할 때 2개 이상의 1:N 관계의 컬렉션을 Fetch Join하지 못하도록 되어있습니다. 조회되는 데이터가 너무 많아지는 카테시안 곱(Cartesian Product) 때문에, 2개 이상의 컬렉션을 Fetch Join하면 MultipleBagFetchException이 발생합니다. 오직 1개의 컬렉션만 Fetch Join이 가능합니다.&lt;/p&gt;
&lt;p&gt;특정 엔티티가 보유하는 1:N 관계의 컬렉션 모두를 즉시 로딩처럼 조회할 수 없다는 것은, 이번 글의 Post 엔티티 예제처럼 N + 1 문제가 발생한다는 것을 시사합니다. 하지만 Batch Size 옵션을 통해 이러한 JPA Fetch Join 한계로 인해 발생하는 N + 1 문제를 어느 정도 해소할 수 있습니다.&lt;/p&gt;
&lt;p&gt;이번 글에서 설명한 문제들의 해결 방법으로는 Batch Size 이외에도 다양한 방식이 존재할 것입니다. 테코블 독자님들이 알고 있는 다른 우아한 해결 방법이 있다면 댓글을 통해 공유해보는 것은 어떨까요? 😊😊&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@rainmaker007/jpa-fetch-join&quot;&gt;jpa fetch join&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jojoldu.tistory.com/457&quot;&gt;MultipleBagFetchException 발생시 해결 방법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.inflearn.com/questions/14663&quot;&gt;fetch join 시 paging 문제&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://javabom.tistory.com/104&quot;&gt;fetch join 과 pagination 을 같이 쓸 때 [HHH000104: firstResult/maxResults specified with collection fetch; applying in memory]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zetawiki.com/wiki/N%2B1_%EC%BF%BC%EB%A6%AC_%EB%AC%B8%EC%A0%9C&quot;&gt;N+1 쿼리 문제&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.infragistics.com/help/aspnet/webdatagrid-paging&quot;&gt;이미지 출처&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/the-marcy-lab-school/what-is-the-n-1-problem-in-graphql-dd4921cb3c1a&quot;&gt;티저 출처&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[RestTemplate과 WebClient]]></title><description><![CDATA[이 글은 자바에서 HTTP 요청을 써봤거나 써보려고 하는 독자를 대상을 작성하였습니다. 스프링 어플리케이션에서 HTTP 요청할 때 사용하는 방법으로 RestTemplate과 WebClient가 있다. 스프링 5.0 이전까지는 클라이언트에서 HTTP…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-25-resttemplate-webclient/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-25-resttemplate-webclient/</guid><pubDate>Sun, 25 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이 글은 자바에서 HTTP 요청을 써봤거나 써보려고 하는 독자를 대상을 작성하였습니다.&lt;/p&gt;
&lt;p&gt;스프링 어플리케이션에서 HTTP 요청할 때 사용하는 방법으로 RestTemplate과 WebClient가 있다. 스프링 5.0 이전까지는 클라이언트에서 HTTP 접근을 위해 사용한 것은 RestTemplate 이었다. 스프링 5.0 에서 WebClient가 나왔고 현재는 WebClient를 사용하기를 권고하고 있다. 이번 팀 프로젝트를 진행하면서 RestTemplate을 도입하였었다. 하지만 RestTemplate이 deprecated 될 가능성이 있다는 얘기를 들었고 새로 시작하는 프로젝트에서는 WebClient를 쓰는 것이 좋겠다는 의견이 있어 WebClient를 적용해보게 되었다. 그럼 RestTemplate과 WebClient는 어떤 특징이 있으며 왜 WebClient를 사용하길 권고하는지 알아보도록 하자.&lt;/p&gt;
&lt;h2&gt;RestTemplate&lt;/h2&gt;
&lt;h3&gt;RestTemplate 이란?&lt;/h3&gt;
&lt;p&gt;스프링 3.0에서부터 지원하는 RestTemplate은 HTTP 통신에 유용하게 쓸 수 있는 템플릿이다. REST 서비스를 호출하도록 설계되어 HTTP 프로토콜의 메서드 (GET, POST, DELETE, PUT)에 맞게 여러 메서드를 제공한다. RestTemplate은 다음과 같은 특징이 있다&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RestTemplate 특징&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;통신을 단순화하고 RESTful 원칙을 지킨다&lt;/li&gt;
&lt;li&gt;멀티쓰레드 방식을 사용&lt;/li&gt;
&lt;li&gt;Blocking 방식을 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;의존성 설정&lt;/h3&gt;
&lt;p&gt;기본 스프링 부트 의존성을 추가하면 RestTemplate 관련 의존성은 자동으로 추가된다. 따라서 스프링 부트를 사용한다면 별다른 신경을 쓰지 않아도 된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;implementation &apos;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;boot&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;spring&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;boot&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;starter&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;web&apos;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;RestTemplate 사용&lt;/h3&gt;
&lt;p&gt;RestTemplate을 생성할 때 어떤 HttpClient를 사용할 것인지 ClientHttpRequestFactory를 전달하여 지정할 수 있다. 기본 생성자의 경우 내부적으로 ClientHttpRequestFactory 의 구현체 SimpleClientHttpRequestFactory를 사용하여 초기화한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RestTemplate의 생성&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RestTemplateClient&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RestTemplate&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;restTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RestTemplateBuilder&lt;/span&gt; builder&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;RestTemplate을 생성할때는 builder를 통하여 생성해 줄 수 있다. builder를 통해 생성하고 스프링 빈으로 사용할 수 있도록 설정해준다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RestTemplateTestClass&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt; 
    &lt;span class=&quot;token class-name&quot;&gt;RestTemplate&lt;/span&gt; restTemplate&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RestTemplate&lt;/span&gt; restTemplate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;restTemplate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; restTemplate&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getSthFromServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; restTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getForObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://example.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;RestTemplate을 사용하기 위해서는 &lt;code class=&quot;language-text&quot;&gt;restTemplate.메소드명()&lt;/code&gt; 을 사용하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63634505/126900492-9e508e47-6872-4de1-a463-25cbbd95dc46.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;h2&gt;WebClient&lt;/h2&gt;
&lt;h3&gt;WebClient 란?&lt;/h3&gt;
&lt;p&gt;WebCleint는 스프링 5.0에서 추가된 인터페이스다. 스프링 5.0 이전에는 비동기 클라이언트로 AsyncRestTemplate을 사용했었다. 하지만 스프링 5.0 이후부터는 WebClient를 사용할 것을 권장한다. WebClient는 다음과 같은 특징이 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;싱글 스레드 방식을 사용&lt;/li&gt;
&lt;li&gt;Non-Blocking 방식을 사용&lt;/li&gt;
&lt;li&gt;JSON, XML을 쉽게 응답받는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;의존성 설정&lt;/h3&gt;
&lt;p&gt;WebClient를 사용하기 위해서는 RestTemplate와 달리 의존성을 추가해야 할 부분이 있다. webflux 의존성을 추가해줘야 한다. Gradle 기준으로 아래와 같이 의존성을 추가해주면 된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// webflux&lt;/span&gt;
implementation &apos;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;boot&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;spring&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;boot&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;starter&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;webflux&apos;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;WebClient 생성&lt;/h3&gt;
&lt;p&gt;WebClient를 생성하는 데는 2가지의 방법이 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WebClient.create();&lt;/li&gt;
&lt;li&gt;Builder를 활용한 클래스 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Webclient&lt;/span&gt; webClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebClient&lt;/span&gt;
	                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;baseUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:8080&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defaultCookie&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;쿠키&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;쿠키값&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defaultHeader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpHeaders&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;CONTENT_TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MediaType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;APPLICATION_JSON_VALUE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Request요청하기&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;WebClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RequestHeaderUriSpec&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; baseSpec &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Webclient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
																								&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;baseUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;주소&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
																								&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
																								&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// baseSpec에 원하는 파라미터를 추가로 붙여서 요청한다. &lt;/span&gt;
baseSpec&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;builder &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                               &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queryParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;이름&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;값&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                               &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;retrieve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Response를 받아옴.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Response 받아오기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;response를 받아오는 방법에는 두 가지가 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;exchange() → ClientResponse를 상태값 그리고 헤더와 함께 가져온다&lt;/li&gt;
&lt;li&gt;retrieve() → body를 바로 가져온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exchange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bodyToMono&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; response2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;retrieve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bodyToMono&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;bodyToMono 는 가져온 body를 Reactor의 Mono 객체로 바꿔준다. Mono 객체는 0-1개의 결과를 처리하는 객체이다. Flux는 0-N개의 결과를 처리하는 객체이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;block()&lt;/code&gt; 을 사용하면 RestTemplate 처럼 동기식으로 사용할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;RestTempalte 과 WebClient의 차이&lt;/h2&gt;
&lt;p&gt;RestTemplate과 WebClient의 가장 큰 차이점은 Non-Blocking과 비동기화 가능 여부일 것이다. 결국 이러한 차이점이 스프링에서 RestTemplate을 사용하는 것 보다 WebClinet의 사용을 권장하는 이유라고 생각한다. 스프링 4.0에서 RestTemplate의 비동기 문제를 해결하기 위해 AsyncRestTemplate을 잠깐 사용하긴 했지만, 현재 deprecated 된 상태이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63634505/126900349-905377fe-27ac-4d7a-8b9a-371fb22aee74.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Non-Blocking?&lt;/p&gt;
&lt;p&gt;시스템을 호출한 직후에 프로그램으로 제어가 다시 돌아와서 시스템 호출의 종료를 기다리지 않고 다음 동작을 진행한다. 호출한 시스템의 동작을 기다리지 않고 동시에 다른 작업을 진행할 수 있어서 작업의 속도가 빨라진다는 장점이 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;성능비교&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/63634505/126900385-ffdecfe7-f5b6-4c7a-a2ed-69145cc85390.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;출처 : &lt;a href=&quot;https://alwayspr.tistory.com/44&quot;&gt;https://alwayspr.tistory.com/44&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Boot1 은 RestTemplate을 사용하고 Boot2 는 WebClient 를 사용한다. 동시 사용자가 1,000명까지는 처리속도가 거의 비슷하지만 그 이후에서는 RestTemplate(Boot1) 이 급격하게 느려지는 것을 볼 수 있다. 동시 사용자의 규모가 별로 없다면 RestTemplate을 사용하는 것은 별문제 없지만 어느정도의 규모가 있다면 WebClient를 선택하는 것이 바람직해 보인다.&lt;/p&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;이전까지 RestTemplate을 사용했다면 굳이 WebClient로 변경할 필요는 없을 것 같다. 하지만 새롭게 개발을 진행한다면 WebClient의 도입을 생각해봐야 한다. WebClient는 RestTemplate이 할 수 있는 동기호출을 할 수 있고 비동기 호출도 가능하다. 하지만 RestTemplate은 WebClient가 가능한 비동기 호출을 할 수 없다. RestTemplate을 사용한다면 응답이 올 때까지 계속 기다려야 할 것이다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://juneyr.dev/2019-02-12/resttemplate-vs-webclient&quot;&gt;https://juneyr.dev/2019-02-12/resttemplate-vs-webclient&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/spring-webclient-resttemplate&quot;&gt;https://www.baeldung.com/spring-webclient-resttemplate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://happycloud-lee.tistory.com/220&quot;&gt;https://happycloud-lee.tistory.com/220&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[사용자 경험 개선 2편 - react concurrent mode]]></title><description><![CDATA[❗ react suspense에 대한 이해가 필요한 글입니다. 사용자 경험 개선 1편을 참고해주세요. 👀 concurrent mode…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-24-concurrent-mode/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-24-concurrent-mode/</guid><pubDate>Sat, 24 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;❗ react suspense에 대한 이해가 필요한 글입니다. 사용자 경험 개선 1편을 참고해주세요.&lt;/p&gt;
&lt;h2&gt;👀 concurrent mode란 무엇인가&lt;/h2&gt;
&lt;p&gt;자바스크립트가 싱글 스레드 언어라는 것을 들어본 적이 있을 것이다.
이는 자바스크립트가 하나의 작업을 수행할 때 다른 작업을 동시에 수행할 수 없음을 의미한다. 리액트도 자바스크립트 기반이기에 물론 싱글 스레드이다.
하지만 리액트에서 &lt;code class=&quot;language-text&quot;&gt;concurrent mode&lt;/code&gt;를 사용하면 여러 작업을 동시에 처리할 수 있다.
오늘은 concurrent mode에 대해서 같이 알아보자.&lt;/p&gt;
&lt;p&gt;우선 여러 작업을 동시에 할 수 있다면 어떤 점이 좋은지는 잠깐 미뤄두자.
필자는 개인적으로 두 눈으로 본 것 아니면 의심하는 경향이 있다.
리액트가 &lt;code class=&quot;language-text&quot;&gt;정말 한 번에 하나의 작업만 처리하는지&lt;/code&gt;부터 확인해보자.&lt;/p&gt;
&lt;p&gt;예를 들어 리액트는 UI 렌더링 도중에 렌더링 이외의 모든 작업을 차단한다. ‘렌더링 도중에 모든 작업을 차단한다고?’ 현재 리액트를 버벅임 없이 사용하고 있는 개발자들은 의아할 것이다.&lt;/p&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/blocking-mode-ogob4?fontsize=14&amp;hidenavigation=1&amp;theme=dark&amp;view=preview&quot;
     style=&quot;width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;&quot;
     title=&quot;blocking-mode&quot;
     allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot;
     sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;
   &gt;&lt;/iframe&gt;
&lt;p&gt;위의 예제에서 아무 값이나 입력해보자. 예제에서는 사용자의 입력이 들어올 때마다 5,000개의 DOM element가 생성된다. 정상적으로 값을 입력하지 못할 정도로 버벅댈 것이다. 실제로는 5000개의 DOM element를 생성할 일은 거의 없지만, 렌더링 도중에 다른 모든 작업을 차단하는 모습을 직접 눈으로 보는 데에는 이만한 예제가 없다.&lt;/p&gt;
&lt;p&gt;그렇다면 이런 의문이 들 것이다. 자바스크립트 자체가 싱글 스레드라면, 리액트에서 동시에 작업을 처리하는 것은 불가능한 것 아닌가?
하지만 리액트 개발자들은 방법을 찾아냈다. 바로 &lt;code class=&quot;language-text&quot;&gt;동시성&lt;/code&gt;이다.&lt;/p&gt;
&lt;p&gt;동시성은 여러 작업을 작은 단위로 나눈 뒤, 그들 간의 우선순위를 정하고 그에 따라 작업을 번갈아 수행하는 방법이다. 서로 다른 작업들이 실제로 동시에 수행되는 것은 아니지만, 작업 간의 전환이 매우 빠르게 이루어지면서 동시에 수행되는 것처럼 보이는 것이다.&lt;/p&gt;
&lt;p&gt;예시로 게임에 빠져 살던 시절 필자는 컴퓨터를 앞에 두고 밥을 같이 먹은 적이 종종 있었다. 게임에 중요한 상황이 있으면 마우스와 키보드를 잡았다가 게임에 신경 쓰지 않아도 되는 타이밍에 빠르게 밥과 김치를 입에 넣곤 했다.
손이 2개밖에 안 되어 실제로 식사와 게임을 &lt;code class=&quot;language-text&quot;&gt;동시에&lt;/code&gt; 하진 못했지만 작업 간의 전환을 빠르게 하여 동시에 수행하는 듯 보였다.&lt;/p&gt;
&lt;p&gt;이렇게 리액트는 동시성 개념을 도입해 싱글 스레드 환경에서 여러 작업을 동시에 할 수 있게 되었다.&lt;/p&gt;
&lt;h2&gt;🤔 여러 작업을 동시에 처리하는 것이 왜 중요한가요?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;concurrent mode가 왜 필요한 건가요? 이렇게까지 동시성을 구현할 필요가 있었나요?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이러한 의문이 드는 것은 자연스러운 일이다.
concurrent mode는 사용자 경험과 굉장히 밀접한 관계가 있다.
사용자 경험을 저해하는 어떤 상황에서 concurrent mode가 빛을 발하는지 대표적인 사례를 알아보자.&lt;/p&gt;
&lt;h3&gt;1. 기존 디바운스와 쓰로틀의 한계&lt;/h3&gt;
&lt;p&gt;사용자가 input을 입력할 때마다 &lt;code class=&quot;language-text&quot;&gt;무거운 작업&lt;/code&gt;을 수행하는 경우 버벅거림을 경험해본 적이 있을 것이다.
예를 들어 응답을 받는 데 시간이 걸리는 네트워크 요청이라든지, 많은 DOM element를 생성한다든지 하는 작업이 있다.
필자는 이 문제를 해결하기 위해서 &lt;code class=&quot;language-text&quot;&gt;디바운스와 쓰로틀&lt;/code&gt;을 사용해 해결한 적이 많았다.
확실히 이를 사용하면 무거운 작업의 수행 빈도수를 줄여주어 버벅거림은 줄어든다.
하지만 분명한 한계점들이 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;디바운스
디바운스는 사용자의 마지막 입력이 끝난 뒤 일정 시간이 지나면 무거운 작업을 수행하는 방법이다.
하지만 이 방법은 기기의 성능과는 관계없이 무조건 &lt;code class=&quot;language-text&quot;&gt;일정 시간&lt;/code&gt;을 기다려야 한다는 단점이 있다.
슈퍼컴퓨터든 10년 된 컴퓨터든 똑같이 마지막 입력 이후에 0.5초를 기다려야 한다면 슈퍼 컴퓨터 입장에서는
안타까울 수밖에 없다. 또한 사용자 입력 중에 무거운 작업의 처리가 이뤄지지 않는다는 점도 아쉬울 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;쓰로틀
디바운스에서 ‘입력 중에 무거운 작업의 처리가 이뤄지지 않는 점’은 쓰로틀로 개선할 수 있다.
쓰로틀은 입력 중에 주기적으로 무거운 작업을 수행하는 방법이다.
하지만 이 방법 또한 모든 사용자 경험을 높이는 방법은 아니다.
쓰로틀 주기를 짧게 가져갈수록 성능이 좋은 기기에서는 사용자 경험을 높일 수 있지만
성능이 나쁜 기기에서는 버벅거림이 심해지기 때문이다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;concurrent mode는 이러한 디바운스와 쓰로틀의 한계점들을 동시성으로 해결한다.
빠른 작업 간 전환으로 사용자 입력과 무거운 작업이 버벅대지 않고 동시에 처리되는 경험을 사용자에게 줄 수 있고
작업 처리 속도는 개발자가 설정한 delay에 의존되는 것이 아니라 사용자의 기기 성능에 좌우된다.&lt;/p&gt;
&lt;h3&gt;2. 충분히 렌더링이 빠름에도 의미 없는 로딩을 보여주는 경우&lt;/h3&gt;
&lt;p&gt;suspense로만 구현된 로딩은 이전 페이지를 유저로부터 &lt;code class=&quot;language-text&quot;&gt;차단(block)&lt;/code&gt;하고 다음 페이지의 전체 로딩 화면으로 대체하므로
사용자는 답답함을 느끼게 되었다.&lt;/p&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/concurrent-mode-1-v5e9v?fontsize=14&amp;hidenavigation=1&amp;theme=dark&amp;view=preview&quot;
  style=&quot;width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;&quot;
  title=&quot;concurrent-mode-1&quot;
  allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot;
  sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;
&gt;&lt;/iframe&gt;
&lt;p&gt;위 예제에서 &lt;code class=&quot;language-text&quot;&gt;페이지 방문하기&lt;/code&gt; 버튼을 누르면 이전 페이지를 모두 차단하고 다음 페이지의 &lt;code class=&quot;language-text&quot;&gt;전체 로딩 화면&lt;/code&gt;을 띄운다.
특히 예제를 보면 전체 로딩 화면이 아주 짧게 나오는 것을 확인할 수 있는데 사용자에게 피로감을 줄 수 있는 불필요한 로딩은 최대한 줄이는 것이 좋다.&lt;/p&gt;
&lt;p&gt;이를 concurrent mode는 일정 시간 동안 현재 페이지와 기능들을 유지하고 다음 페이지에 대한 렌더링을 동시에 진행함으로써 해결한다.
그리고 다음 페이지의 &lt;code class=&quot;language-text&quot;&gt;렌더링 단계가 특정 조건에 부합하면&lt;/code&gt; 해당 페이지를 렌더링한다.
그렇다면 여기서 렌더링 단계와 특정 조건은 무엇일까? 같이 알아보도록 하자.&lt;/p&gt;
&lt;h2&gt;🎯 Concurrent mode의 동작 원리&lt;/h2&gt;
&lt;p&gt;위에서 언급한 concurrent mode의 동작 원리를 자세하게 풀자면 다음과 같다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;특정 state가 변경되었을 때 &lt;code class=&quot;language-text&quot;&gt;현 UI를 유지&lt;/code&gt;하고 해당 변경에 따른 UI 업데이트를 동시에 준비.
준비 중인 UI의 &lt;code class=&quot;language-text&quot;&gt;렌더링 단계&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;특정 조건&lt;/code&gt;에 부합하면 실제 DOM에 반영한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;현 UI 상태를 유지한다는 것은 크게 어렵지 않으리라 생각한다.
그럼 렌더링 단계와 특정 조건은 뭘까? 같이 알아보자.&lt;/p&gt;
&lt;h3&gt;렌더링 단계&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/48755175/126877704-3085e031-bc14-4430-a1e3-b8d5ccc46456.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;Transition, Loading, Done 총 3개의 렌더링 단계가 있다.
일반적으로 UI 업데이트는 state의 변경에 의해 발생하므로
각 단계는 특정 state 변경의 관점에서 보는 렌더링 단계이다.
오른쪽으로 진행할수록 더 최신 렌더링 단계이다. 각 단계에 대해서 조금 더 알아보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Transition 단계
Transition은 state 변경 직후에 일어날 수 있는 UI 렌더링 단계이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Pending 상태
리액트에서 제공하는 &lt;code class=&quot;language-text&quot;&gt;useTransition 훅&lt;/code&gt;을 사용하면 state 변경 직후에도
UI를 업데이트하지 않고 현 UI를 잠시 유지할 수 있는데 이를 Pending 상태라고 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Receded 상태
useTransition 훅을 사용하지 않은 기본 상태. state 변경 직후 UI가 변경된다.
전체 페이지에 대한 로딩 화면이라고 생각하면 이해하기 쉽다.
Pending 상태에서도 Receded 상태로 넘어갈 수 있는데 Pending 상태의 시간이 useTransition 옵션으로 지정된 timeoutMs을
넘으면 강제로 Receded 상태로 넘어간다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Loading 단계&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Skeleton 상태
페이지의 일부만을 로딩하는 상태. 전체 화면을 모두 로딩으로 대체해버리는 Receded와는 다르다.&lt;/p&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/concurrent-mode-3-xmtup?fontsize=14&amp;hidenavigation=1&amp;theme=dark&amp;view=preview&quot;
  style=&quot;width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;&quot;
  title=&quot;concurrent-mode-3&quot;
  allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot;
  sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;
&gt;&lt;/iframe&gt;
&lt;p&gt;위 예제에서 크루들의 닉네임을 눌렀을 때 설명 부분의 로딩 UI가 Skeleton 상태라고 할 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Done 단계&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Complete 상태
로딩 UI 없이 모든 정보가 사용자에게 보이는 상태를 의미한다.
우리가 최종적으로 목표하는 단계라고 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;특정 조건&lt;/h3&gt;
&lt;p&gt;이제 렌더링 조건은 알 것 같다. 그렇다면 UI 업데이트를 실제 DOM에 적용하기 위한 &lt;code class=&quot;language-text&quot;&gt;특정 조건&lt;/code&gt;은 무엇일까?
이 부분은 &lt;a href=&quot;https://ko.reactjs.org/docs/concurrent-mode-patterns.html&quot;&gt;리액트 공식 문서&lt;/a&gt;에서도 모호하게 나와 있어 정확하게 알기는 어려웠다.
하지만 concurrent mode의 원리 중 하나인 &lt;code class=&quot;language-text&quot;&gt;현재 UI를 유지한다&lt;/code&gt;에 암시된 의미와 필자가 실험해본 결과를 조합해보았을 때,
&lt;code class=&quot;language-text&quot;&gt;특정 조건&lt;/code&gt;이란 다음과 같다고 볼 수 있다. (혹시 잘못된 점이 있다면 알려주세요)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;특정 state 변경에 대한 현 화면의 UI 렌더링 단계보다 더 &lt;code class=&quot;language-text&quot;&gt;최신 단계&lt;/code&gt;로 진행하여야 실제 DOM에 반영한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이해를 돕기 위한 다음의 예제를 보자&lt;/p&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/concurrent-mode-4-6wgld?fontsize=14&amp;hidenavigation=1&amp;theme=dark&amp;view=preview&quot;
  style=&quot;width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;&quot;
  title=&quot;concurrent-mode-4&quot;
  allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot;
  sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;
&gt;&lt;/iframe&gt;
&lt;p&gt;예제에서 &lt;code class=&quot;language-text&quot;&gt;페이지 방문하기&lt;/code&gt; 버튼을 누르면 즉시 다음 페이지로 이동하지 않고 잠깐 현 화면을 유지하다가 다음 페이지의 Skeleton 상태로 이동한다. 이 과정을 자세히 들여다보자.&lt;/p&gt;
&lt;p&gt;먼저 버튼을 누르는 즉시 resource라는 state를 변경하고 Pending 상태에 진입한다.
이때 백그라운드에서는 state에 대한 다음 페이지를 준비하는 Receded 상태에 머물러 있을 것이다.&lt;/p&gt;
&lt;p&gt;잠시 뒤 다음 페이지의 Receded 상태가 끝나고 Skeleton 상태에 진입하면 그때서야 실제 DOM에 적용한다.
Skeleton 상태는 현 UI 상태인 Pending 상태보다 더 최신 단계이기 때문이다.&lt;/p&gt;
&lt;p&gt;이제 조금 헷갈릴 수 있는 상황이 등장한다.
테코블 크루의 닉네임과 설명이 모두 렌더링 되어 Complete 상태로 넘어가고 다른 크루의 버튼을 눌러보자.
버튼을 눌렀을 때 UI가 멈춰있는 Pending 상태에 진입하고 잠시 뒤 Skeleton 상태로 넘어갈 것 같지만
Skeleton은 등장하지 않고 바로 다른 크루에 대한 정보가 렌더링 되는 Complete 상태로 넘어간다.&lt;/p&gt;
&lt;p&gt;여기서 간과하는 사실이 있다. 렌더링 상태는 렌더링을 유발하는 state 관점이라는 것이다.
&lt;code class=&quot;language-text&quot;&gt;페이지 방문하기&lt;/code&gt; 버튼을 누르던, &lt;code class=&quot;language-text&quot;&gt;크루의 버튼&lt;/code&gt;을 누르던 resource라는 같은 state의 변경이 일어나고 있다.
다시 한번 화면을 확인해보자. 크루의 버튼을 누르기 전에 resource state의 관점으로 봤을 때, Pending 상태가 아니라 모든 화면에 정보가 완전하게 보이는 complete 상태다.
여기서 크루의 버튼을 누르면 Pending 상태나 Skeleton 상태로 전환되지 않는다. 현 UI는 Complete 상태로, Pending이나 Skeleton 상태보다 더 최신 상태이기 때문이다.
그리고 백그라운드에서 새로운 resource state에 대한 렌더링이 Complete 상태로 넘어갔을 때, 그제야 실제 DOM에 적용한다.
이전 state에 대한 Complete 상태보다 새로운 state에 대한 Complete 상태가 더 &lt;code class=&quot;language-text&quot;&gt;최신 상태&lt;/code&gt;이기 때문이다.&lt;/p&gt;
&lt;h2&gt;🔥 실제로 도입하기&lt;/h2&gt;
&lt;p&gt;지금까지는 concurrent mode가 동작하는 원리를 알아보았다. 이제 concurrent mode를 실제 코드에 적용하기 위해서는 어떻게 해야 할까?
react는 이를 위해 &lt;code class=&quot;language-text&quot;&gt;useTransition&lt;/code&gt;이라는 훅을 제공한다. useTransition을 사용한 다양한 패턴이 있지만, 필자가 구현해본 결과
아직은 useTransition의 가장 간단한 사용법 정도를 훑어보는 정도면 충분할 듯하다. 아직 experimental이라 언제든 실제 구현 코드가 달라질 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;오늘 글에서 가장 먼저 예제로 등장했던 blocking mode input을 concurrent mode로 개선해보자.
먼저 개선한 결과는 다음과 같다.&lt;/p&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/concurrent-mode-5-ei5z8?fontsize=14&amp;hidenavigation=1&amp;theme=dark&amp;view=preview&quot;
  style=&quot;width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;&quot;
  title=&quot;concurrent-mode-5&quot;
  allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot;
  sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;
&gt;&lt;/iframe&gt;
&lt;h3&gt;1. react experimental 버전 설치&lt;/h3&gt;
&lt;p&gt;우선 concurrent mode는 experimental 버전에서만 동작한다. experimental 버전을 설치해주자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;npm install react@experimental react-DOM@experimental&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2. concurrent 모드 활성화&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//index.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;react&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; ReactDOM &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;react-DOM&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; App &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./App&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; rootElement &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;root&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
ReactDOM&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createRoot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rootElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;App &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;기존 blocking mode에서는 ReactDOM.render()를 사용했지만 concurrent mode를 사용하기 위해서는 createRoot()를 사용해야 한다.&lt;/p&gt;
&lt;h3&gt;3. state 반영 우선순위를 정하기&lt;/h3&gt;
&lt;p&gt;우리는 사용자가 input을 입력할 때 버벅거림을 줄이기 위해서 input state의 우선순위를 높게 줄 것이고
5000개의 렌더링을 위한 array state는 우선순위를 낮게 주어 UI 업데이트가 지연될 수 있도록 할 것이다.&lt;/p&gt;
&lt;p&gt;❗ 주의 : 2021년 7월 기준 experimental 버전에서 useTransition의 리턴 형태가 [startTransition, isPending] =&gt; [isPending, startTransition] 으로 바뀌었다.
유의하도록 하자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// App.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;isPending&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; startTransition&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useTransition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;timeoutMs&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 최대 3초 동안 다음 화면의 렌더링을 백그라운드에서 준비하겠다는 의미. 3초가 지나면 강제로 렌더된다.&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleChange&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;setInput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;startTransition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 지연할 UI와 관계된 state를 리액트에게 알려줌&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; newArray &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;TECOBLE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;index &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;TECOBLE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;setArray&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newArray&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;handleChange 내부의 로직을 유심히 보자. setInput은 startTransition 바깥에서 실행되고, setArray는 내부에서 실행되고 있다.&lt;br&gt;
이 차이는 state의 우선순위를 결정한다. setArray를 startTransition으로 감싸주는 의미는 ‘array state 변화는 지연시켜도 돼’라고 리액트에게 알려주는 것이기 때문이다.
따라서 input state는 array state보다 높은 우선순위로 결정된다.&lt;/p&gt;
&lt;p&gt;이렇게 해주는 이유는 사용자의 입력이 5,000개의 DOM element를 렌더링하는 것보다 우선시 되어야 사용자가 입력할 때의 버벅임을 줄일 수 있기 때문이다.
결과를 확인해보자. 이전 blocking mode로 구현했던 예제보다 훨씬 버벅임이 줄어들었을 것이다.&lt;/p&gt;
&lt;h2&gt;✨ 마무리&lt;/h2&gt;
&lt;p&gt;오늘은 사용자 경험 개선을 주제로 concurrent mode까지 알아보았다.
1편에서 다루었던 suspense와는 다르게 concurrent mode는 react의 렌더링 방식을 크게 변화시키는 등
앱 전반적으로 영향을 미칠 수 있는 터라 아직은 production 코드에서 사용하기에는 다소 무리가 있어 보인다.
또한 예제 코드를 작성하면서 개인적으로 느낀 것은 concurrent mode를 지원하는 훅이 아직 직관적으로 사용하기가 불편하다는 생각이 들었다.
(물론 내가 아직 concurrent mode의 개념에 익숙지 않아서 그럴 수는 있다 😂)
하지만 concurrent mode는 곧 나올 React 18에 적용될 기능이기도 하고, 사용자 경험 개선에 있어서 도입하였을 때 많은 이점을 누릴 수 있는 기능이기에
미리 공부하는 것이 나중에 큰 도움이 되리라 생각한다.&lt;/p&gt;
&lt;p&gt;오늘 주제에 관해 공부하면서 사용자 경험의 관점에서 많은 생각을 하게 되었다. 많은 로딩이 사용자 경험에 좋지 않다는 점을 알게 되었고
디바운스와 쓰로틀에 대해서도 다시 생각해보게 되었다. 역시 무엇이든 완벽한 기술은 없다. 이를 염두에 두고 항상 불편함을 찾으며 개선하려는 자세를 가져야겠다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@cadenzah/react-concurrent-mode&quot;&gt;What is React Concurrent Mode?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.reactjs.org/docs/concurrent-mode-patterns.html&quot;&gt;concurrent mode UI 패턴&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[클로져와 가까워지기]]></title><description><![CDATA[0. 인트로 클로져를 MDN…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-16-closure/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-16-closure/</guid><pubDate>Fri, 16 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;0. 인트로&lt;/h2&gt;
&lt;p&gt;클로져를 &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures&quot;&gt;MDN&lt;/a&gt;에서 검색해보면&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;라는 문장이 나옵니다. 무슨 뜻인지 단번에 받아들이기 어렵지 않나요?&lt;/p&gt;
&lt;p&gt;솔직히 프로그래밍을 하면서 클로져를 활용해볼 일이 그렇게 자주 있지는 않은 것 같습니다.&lt;/p&gt;
&lt;p&gt;하지만 클로져를 이해하는 것이 중요하다고 생각하는 이유는 클로져가 자바스크립트 엔진의 동작과 연관이 있기 때문입니다.&lt;/p&gt;
&lt;p&gt;오늘은 클로져에 대해서 알아보도록하겠습니다.&lt;/p&gt;
&lt;h2&gt;1. 클로져란?&lt;/h2&gt;
&lt;p&gt;코드를 보면 어떤 상황을 클로져라 부르는지 쉽게 이해할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;outer&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; outerVariable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;outer!&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1. outer 함수 안에 지역변수 outerVariable 선언&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;inner&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;outerVariable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 2. 바깥의 outerVariable을 참조해 console.log 출력&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; inner&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 3. 바깥의 outerVariable을 참조해 console.log를 출력하는 함수를 반환&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fano &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;outer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 4. outer함수 호출 -&gt; 변수 fano에 inner함수의 주소값이 저장됨&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;fano&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 5. &apos;outer!&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이게 클로져의 전부입니다.&lt;/p&gt;
&lt;p&gt;클로져는 어떤 함수(outer) 내부에 선언된 함수(inner)가 바깥 함수(outer)의 지역변수(outerVariable)를 참조하는 것이 함수(outer)가 종료된 이후에도 계속 유지되는 현상을 말합니다.&lt;/p&gt;
&lt;h2&gt;2. 클로져가 발생하는 이유&lt;/h2&gt;
&lt;p&gt;클로져가 무엇인지 말하기는 간단하지만, 이 현상이 왜 발생하는지 알기 위해서는 자바스크립트에 대해 좀 더 알 필요가 있습니다.&lt;/p&gt;
&lt;h3&gt;1. 스코프&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/125807653-51d7ea02-c340-42e4-8969-f13efe97a501.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;스코프는 우리말로 기회, 범위입니다. 자바스크립트의 스코프는 범위를 뜻합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scopeA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dolharubang &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;슈-욱&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scopeB&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dolharubang &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;슈슉&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dolharubang&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;scopeB&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 슈슉&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dolharubang&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;scopeA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 슈-욱&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dolharubang&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// reference error&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;scopeA&lt;/code&gt;를 호출 했을 때 &lt;strong&gt;슈슉&lt;/strong&gt;이 아닌 &lt;strong&gt;슈-욱&lt;/strong&gt;이 출력됐습니다. 이것은 &lt;code class=&quot;language-text&quot;&gt;scopeB&lt;/code&gt; 함수의 &lt;code class=&quot;language-text&quot;&gt;fano&lt;/code&gt; 변수 선언 및 할당 과정이 &lt;code class=&quot;language-text&quot;&gt;scopeA&lt;/code&gt;의 동작에 영향을 주지 않았다는 것입니다. 즉, &lt;code class=&quot;language-text&quot;&gt;scopeA&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;scopeB&lt;/code&gt;가 고유한 스코프를 가지고 있다고 말할 수 있습니다.&lt;/p&gt;
&lt;p&gt;모든 프로그래밍 언어는 코드를 한 줄씩 읽습니다. 그리고 이를 계산(앞으로는 평가한다고 하겠습니다.)해 메모리에 계산된 값을 저장(&lt;code class=&quot;language-text&quot;&gt;const dolharubang = &apos;슈-욱&apos;&lt;/code&gt;)하거나, 특정한 동작을(&lt;code class=&quot;language-text&quot;&gt;console.log(dolharubang)&lt;/code&gt;) 합니다.&lt;/p&gt;
&lt;p&gt;자바스크립트는 프로그램을 평가하기 전과 함수를 평가하기 전에 &lt;strong&gt;변수 선언&lt;/strong&gt;과 &lt;strong&gt;함수 선언&lt;/strong&gt; 정보를 미리 한 번 쭉 훑어서 수집합니다. (실행컨텍스트의 Environment Record를 수집하는 과정)&lt;/p&gt;
&lt;p&gt;프로그램을 시작하기 전에 &lt;code class=&quot;language-text&quot;&gt;scopeA&lt;/code&gt;함수 선언 정보를 수집하고 한 줄씩 평가를 하다가, &lt;code class=&quot;language-text&quot;&gt;scopeA&lt;/code&gt;가 호출되는 시점에 &lt;code class=&quot;language-text&quot;&gt;scopeA&lt;/code&gt; 내부의 (문자열 슈-욱이 할당되는) &lt;code class=&quot;language-text&quot;&gt;dolharubang&lt;/code&gt; 과 &lt;code class=&quot;language-text&quot;&gt;scopeB&lt;/code&gt;의 정보를 수집하는 것입니다. 이렇게 정보를 수집하고 다시 처음으로 돌아와 한 줄씩 평가를 시작하는데, 평가하는 코드줄에서 &lt;code class=&quot;language-text&quot;&gt;dolharubang&lt;/code&gt;에 값이 할당되거나, &lt;code class=&quot;language-text&quot;&gt;console.log(dolharubang)&lt;/code&gt;과 같이 사용될 때, 미리 수집해둔 정보를 가져와서 값을 새로 저장하고, 사용하는 것입니다.&lt;/p&gt;
&lt;p&gt;그렇기 때문에 함수 호출 전에 담긴 정보로는 다른 함수 내부의 변수를 알 수 있는 방법이 없습니다. 그래서 각 함수는 고유한 스코프를 가지게 됩니다. (es6에서는 &lt;code class=&quot;language-text&quot;&gt;while&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;if&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;for&lt;/code&gt;문 같은 함수가 아닌 블록문에서도 새로 스코프를 만듭니다.) 이렇게 만들어진 스코프는 함수가 종료되면서 사라지게 됩니다. (참고: &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Glossary/Call_stack&quot;&gt;호출 스택&lt;/a&gt;)&lt;/p&gt;
&lt;h3&gt;2. 스코프체인&lt;/h3&gt;
&lt;p&gt;스코프를 설명하면서 다른 함수 내부의 변수를 알 수 있는 방법이 없다고 말했는데 사실 이것은 반만 맞는 말입니다. 사실 스코프 내부에 선언된 함수의 식별자는 알 수 없지만, 스코프 바깥의 식별자는 알 수 있기 때문입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;outer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; outerVariable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;outer!&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;inner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;outerVariable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;inner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// outer!&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;맨 처음 봤던 예제를 살짝 바꿨습니다. &lt;code class=&quot;language-text&quot;&gt;inner&lt;/code&gt;에서는 &lt;code class=&quot;language-text&quot;&gt;outerVariable&lt;/code&gt;이라는 변수가 선언되지 않았는데도 값을 잘 불러왔고, 그 값은 다른 함수인 &lt;code class=&quot;language-text&quot;&gt;outer&lt;/code&gt;의 변수입니다.&lt;/p&gt;
&lt;p&gt;감이 오시나요? 자바스크립트는 스코프 내에 참조할 수 있는 변수나 함수가 존재하지 않으면 바깥의 스코프에서 식별자 정보를 찾습니다.&lt;/p&gt;
&lt;p&gt;이것이 가능한 이유는, 앞서 말했던 함수 평가 이전에 쭉 훑는 과정(변수 선언, 함수선언 수집) 이외에 &lt;strong&gt;바깥 스코프에 대한 정보&lt;/strong&gt;를 수집하는 과정도 있기 때문입니다 (실행컨텍스트의 outerEnvironmentReference에 정보가 담깁니다.)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;furtherOuter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; furtherOuterVariable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;further outer!&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;outer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;inner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;furtherOuterVariable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;inner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// further outer!&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;자바스크립트 엔진은 스코프 안에 참조하는 식별자 정보가 없다면 함수 평가 전에 수집했던 바로 바깥 스코프로 가서 식별자를 찾습니다. 바로 바깥 스코프에도 찾는 식별자가 없다면, 그 다음 스코프로 가서 찾고, 마지막엔 전역 스코프까지 가서 찾는데 이때도 존재하지 않는다면 &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError&quot;&gt;참조에러&lt;/a&gt; 를 발생시킵니다. 이렇게 스코프가 체인처럼 연결 되어있는 것을 스코프체인이라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/125818403-6f53c502-6713-4f21-99ff-d0950beeee60.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;이때 주의해야할 것은 바로 바깥 스코프는 함수를 실행하는 시점의 바깥영역이 아닌 선언되는 시점의 바깥 스코프를 가리킨다는 것입니다. (&lt;a href=&quot;https://stackoverflow.com/questions/1047454/what-is-lexical-scope&quot;&gt;렉시컬 스코프&lt;/a&gt;)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scopeA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fano &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;전환오&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 선언 시점의 상위 스코프&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scopeB&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fano&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; scopeB&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; scopeC &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scopeA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fano &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;hwano jeon&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 실행 시점의 상위 스코프(?)&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;scopeC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// &apos;전환오&apos;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;3. 자바스크립트의 함수는 1급 시민&lt;/h3&gt;
&lt;p&gt;이제 스코프체인으로 어떤 스코프가 갖고있지 않은 변수, 함수는 그 바깥 스코프를 참조한다는 것을 알았습니다. 클로져에 가까워지기 위한 마지막 개념이 하나 남았습니다. 그것은 1급 시민이라는 개념입니다.&lt;/p&gt;
&lt;p&gt;이것은 자바스크립트만의 개념이 아닌 프로그래밍 언어 전반에 사용되는 개념입니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/First-class_citizen&quot;&gt;위키피디아&lt;/a&gt;에 따르면&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;로빈 포플스톤은 일급 시민을 구성하는 요소는 4개의 요구조건이 있다는 정의를 내렸다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;모든 요소는 함수의 실제 매개변수가 될 수 있다.&lt;/li&gt;
&lt;li&gt;모든 요소는 함수의 반환 값이 될 수 있다.&lt;/li&gt;
&lt;li&gt;모든 요소는 할당 명령문의 대상이 될 수 있다.&lt;/li&gt;
&lt;li&gt;모든 요소는 동일 비교의 대상이 될 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;자바스크립트의 객체는 1급시민입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;obj&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 모든 요소는 함수의 반환 값이 될 수 있다.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; obj1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 모든 요소는 함수의 실제 매개변수가 될 수 있다.&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; obj2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 모든 요소는 할당 명령문의 대상이 될 수 있다.&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj1 &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; obj2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 모든 요소는 동일 비교의 대상이 될 수 있다.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;자바스크립트의 함수는 곧 객체이므로, 함수 역시 1급 시민입니다.&lt;/p&gt;
&lt;h3&gt;4. 돌고돌아 클로져&lt;/h3&gt;
&lt;p&gt;스코프, 스코프체인, 1급시민. 위에서 알아본 세 가지 개념을 맨처음 보았던 코드를 다시 보면서 클로져를 설명하는데 적용해보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;outer&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; outerVariable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;outer!&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1. 바깥 함수 outer의 스코프에 변수선언&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;inner&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;outerVariable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 2. 내부 함수 inner의 스코프에서 스코프체인을 타고 바깥 함수 스코프의 변수 참조&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; inner&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 3. 1급 시민인 함수 inner를 바깥으로 반환&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fano &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;outer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 4.  fano에 inner함수의 주소값이 저장됨&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;fano&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 5. outer함수 호출은 종료가 되어서 스코프가 사라져야 하지만 outerVariable은 여전히 잘 참조된다.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;예제 코드의 5번째 주석에서 관찰할 수 있는 현상이 클로져입니다. 맨 처음의 정의를 다시 가져와보겠습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;클로져는 어떤 함수(outer) 내부에 선언된 함수(inner)가 바깥 함수(outer)의 지역변수(outerVariable)를 참조하는 것이 함수(outer)가 종료된 이후에도 계속 유지되는 현상을 말합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;outer 함수 바깥으로 반환된 inner함수가 outer 함수의 outerVariable 변수를 참조하기에 메모리에 outer의 스코프가 여전히 남아있는 것입니다.&lt;/p&gt;
&lt;h2&gt;3. 클로져를 응용할 수 있는 영역&lt;/h2&gt;
&lt;p&gt;이런 클로저를 어떻게 활용할 수 있을까요? 예제를 통해서 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h3&gt;1. 함수를 여러 번 호출하면 상태가 연속적으로 유지되어야할 때&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;counterCreator&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;increase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;decrease&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; counter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;counterCreator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

counter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;increase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1&lt;/span&gt;
counter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;increase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 2&lt;/span&gt;
counter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decrease&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같이 함수를 호출하면 이전 함수 호출 상태가 기억되길 바랄 때 사용할 수 있을 것입니다.&lt;/p&gt;
&lt;p&gt;실제 사례로 프론트엔드 프레임워크인 React 의 hook API가 클로져를 통해서 구현되었습니다.&lt;/p&gt;
&lt;p&gt;hook은 함수를 여러 번 호출하는 상황에서 데이터를 연속적으로 유지하는 기능입니다.&lt;/p&gt;
&lt;p&gt;(다음 예제는 React가 생소하신 분은 넘어가셔도 좋습니다.)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Counter&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setValue&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 이 hook함수가 클로져를 통해 구현되었습니다.&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/125825218-6fc47e17-4820-4c63-8f40-ff405cf16171.gif&quot; alt=&quot;counter-example&quot;&gt;&lt;/p&gt;
&lt;p&gt;상태&lt;code class=&quot;language-text&quot;&gt;value&lt;/code&gt;가 바뀌어 렌더링이 계속 일어남에 따라 &lt;code class=&quot;language-text&quot;&gt;Counter&lt;/code&gt; 함수가 여러 번 호출됩니다. 하지만 &lt;code class=&quot;language-text&quot;&gt;useState&lt;/code&gt;는 0이 아니라 이전 상태 &lt;code class=&quot;language-text&quot;&gt;value&lt;/code&gt;의 값을 유지하고 있습니다. 이는 &lt;code class=&quot;language-text&quot;&gt;useState&lt;/code&gt; 선언 시점의 바깥 변수에 0을 초기화한 다음, &lt;code class=&quot;language-text&quot;&gt;setValue&lt;/code&gt;로 해당 바깥 변수를 변경하는 것입니다. 다음 &lt;code class=&quot;language-text&quot;&gt;Counter&lt;/code&gt;가 호출되고 그 안의 &lt;code class=&quot;language-text&quot;&gt;useState&lt;/code&gt;가 다시 호출되면 변경된 바깥 변수를 &lt;code class=&quot;language-text&quot;&gt;value&lt;/code&gt;로 반환합니다.&lt;/p&gt;
&lt;h3&gt;2. 변수를 숨겨야할 때&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;increase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;decrease&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;increase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// value: -100000&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;decrease&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// -100001&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;1번의 케이스는 전역변수로도 해결할 수 있긴 합니다.&lt;/p&gt;
&lt;p&gt;하지만 전역변수는 로직이 복잡해질 시, 어디서 변경이 되는지 추적이 어려워져 가독성과 유지보수에 어려움을 겪게 합니다.&lt;/p&gt;
&lt;p&gt;그러므로 함수클로저가 더 적절한 선택입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;counterCreator&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;increase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;decrease&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; counter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;counterCreator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

counter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;increase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// error: Uncaught ReferenceError: value is not defined&lt;/span&gt;
counter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decrease&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 정상적으로 진행된다면 0&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같이 클로저로 함수를 만들면 외부에서 변수에 접근시 레퍼런스 에러가 발생하게 됩니다.&lt;/p&gt;
&lt;h3&gt;3. 함수가 독립적으로 동작해야할 때&lt;/h3&gt;
&lt;p&gt;카운터가 두 개가 필요한 상황이 있다고 가정하겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; myValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; yourValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;increaseMyCounter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;myValue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;decreaseMyCounter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;myValue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;increaseYourCounter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;yourValue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;decreaseYourCounter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;yourValue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;카운터 두 개를 만들기 위해서 두 배의 코드를 작성했습니다. 비효율적입니다!&lt;/p&gt;
&lt;p&gt;클로저를 활용하게 되면 외부함수를 호출할때마다 새로운 컨텍스트를 생성하기 때문에 클로저함수 하나로 여러 개의 독립적인 카운터를 만들어 줄 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;counterCreator&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;increase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;decrease&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; myCounter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;counterCreator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; yourCounter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;counterCreator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

myCounter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;increase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1&lt;/span&gt;
myCounter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;increase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 2&lt;/span&gt;
yourCounter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;increase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1&lt;/span&gt;
myCounter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decrease&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같이 &lt;code class=&quot;language-text&quot;&gt;myCounter&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;yourCounter&lt;/code&gt;의 상태는 독립적이 되었습니다. 클로저를 활용해 코드를 작성했기 때문에 가독성이 훨씬 좋아졌습니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[vim을 대하는 자세]]></title><description><![CDATA[리눅스 텍스트 편집기 중 하나인 vim. 많이들 vim을 어렵고 외워야만 하는 것으로 생각한다. 이를 보다 쉽게 사용할 수 있게 해주는 vim을 대하는 자세, ‘대화’의 방법을 소개하고자 한다. Intro 글의 시작에 앞서, 글쓴이는 vim…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-14-vim-attitude/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-14-vim-attitude/</guid><pubDate>Thu, 15 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;리눅스 텍스트 편집기 중 하나인 vim. 많이들 vim을 어렵고 외워야만 하는 것으로 생각한다. 이를 보다 쉽게 사용할 수 있게 해주는 vim을 대하는 자세, ‘대화’의 방법을 소개하고자 한다.&lt;/p&gt;
&lt;!-- end --&gt;
&lt;hr&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;글의 시작에 앞서, 글쓴이는 vim을 자유자재로 다룰 수 있는 실력자는 아니며, IDE로 개발을 진행한다. 단지 vim의 몇 가지 기능들을 큰 불편함 없이 사용하는 정도다. vim을 사용하는데 어려움을 겪는 분들에게, 내가 vim을 효율적으로 학습한 방법을 소개하고자 한다. 그것은 vim 텍스트 에디터와 ‘대화’를 하듯 하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;이 글에서 설명하는 모든 기능은 Normal 모드(ESC를 입력한 상태)에서의 커맨드만을 다룬다.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;vim 에디터는 ESC를 누른 시점부터 입력한 키들을 기억하고, 모든 명령어는 커서가 가르키고 있는 곳을 기준으로 한다. 키를 기억하기 때문에 우리는 다음과 같은 ‘대화’를 몇 가지 키의 조합으로 만들어낼 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;소괄호 안에 있는 값을 지워주세요.&lt;/li&gt;
&lt;li&gt;파일 끝까지 복사하고 붙여넣기 해주세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;이러한 대화를 몇 개의 명령어의 조합으로 쉽게 실행할 수 있다.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;vim 명령어들&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1024px; &quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 70.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAsTAAALEwEAmpwYAAADF0lEQVR42iVSbW/bZBT1T0MCIcQH9mGisGxrKFAFGJUQ39iqUboWbUKask4wqnVBMAkUUPdW6GgXksZO/P76PH5iO3YTkjiOXceOk6xVtjUuZju6Orq6uufec6WL1etNzw2iQTgej4w6UgADEQ9VDkBWBiyEHACsAjkFsqJEH/heNIzCQTgcDbtOF5Ok78PBdjw9SaAhrmHgLZPUQBHwO0loqAyUog5KdXWPp7bH42HSFsdxwlEUYTq8Zlu3xq7+dLTf7te4tqA4stQRRVuSupLVEtq2JDuybIuqA90jxzvqtQ8dOw77YYDdlRZ/KcyLfywJxWyB39jg76zDn35Qcjnm9hr6+TGfo+Q7WSV3k9xY4a6/R8zOywuLhbN3t2Zst4XJHqwFOpC+u01e+bL8zVfMyl7pa5JYvsitVsuXNPwyy63qwirClwF58T6eXmIzn1RSK4V3qWYVS6xP4xNIfbFRPLVMnrlEpfKV96+XZk5JqbUHr/1673VRmUX0GYZM5cizZTL9gM58y2bTUuZHbR3zAy+53rPyMj6v0QvS7+9sbZ5OU/Pru9krhavnqI8uC58tVjM3iLkF/IMlcu4q9fmakM1zvz1iHmJhGCbi8TgslUpVeokrngb5N9jq+TyR+bD88RyZvrA3ew0/f+PhW0823/xn6+0ZIbXj7SaS0A8xpYfAU9078JCqGQbxr7Ub6X/va9ucvr1V+/OxsUOqf7XMJ3s2Djplx6PpPtkbucfPX3gHLlZgb27in/IUKQp8pUKhmvliOpUVVC6RJjKtmgUk1Q+Gh34IRCgIQGFliqSgCpvNJmbQ95v4LbpC0BRt1nXXdZ5PjgSepRmKZmmIoON043g6ikKOpV4VVaT2HMf3fazR6jQ7LiuKFMPIEPR6vePjY4ZhCIKgaQoh1bY7k2fPjqJBTYW6ricLG41Gt9t1HAdLxgCBV3Dc4Ji2Vgv6fvJ9tm1bltV9icFgEJ/Eh0Ff5DmGZSVRDIJgMpkkjOmGoSNkCPy+LJmK7Ln/I7GUcKJ8lXie69i2WU9gmKbZaOy32y3btv8DaluSfS0rS3kAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;vim-chart&quot;
        title=&quot;&quot;
        src=&quot;/static/4d95aa4156f1418f50a6703efdc163da/42a19/2021-07-14-vim-attitude-1.png&quot;
        srcset=&quot;/static/4d95aa4156f1418f50a6703efdc163da/0eb09/2021-07-14-vim-attitude-1.png 500w,
/static/4d95aa4156f1418f50a6703efdc163da/1263b/2021-07-14-vim-attitude-1.png 1000w,
/static/4d95aa4156f1418f50a6703efdc163da/42a19/2021-07-14-vim-attitude-1.png 1024w&quot;
        sizes=&quot;(max-width: 1024px) 100vw, 1024px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
    &lt;/span&gt;
출처 &lt;a href=&quot;http://www.viemu.com&quot;&gt;www.viemu.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;vim을 공부해보겠다고 검색했을 때, 가장 먼저 보이는 이미지다. 시작하기 두렵게 만드는 차트다. 우선, 이 차트 외우기를 미뤄두고 포스팅을 따라 오길 바란다. 이동부터 시작한다.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;vim을 열어두고 명령어를 쳐가면서 진행하는 것을 추천한다.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;이동 관련 명령어&lt;/h2&gt;
&lt;p&gt;vim에서는 방향키와 h,j,k,l이 대부분의 기능에서 동일하게 작동한다. 그럼에도 h,j,k,l을 사용하기를 추천한다. 오른손의 움직임을 최소화와 geek함을 얻어갈 수 있다.&lt;/p&gt;
&lt;p&gt;다음의 이동 관련 명령어들을 숙지하면 좋다.&lt;/p&gt;
&lt;h3&gt;단어 기준&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;w&lt;/strong&gt; : word의 약자로, 다음 단어의 앞글자로 이동&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;e&lt;/strong&gt; : end의 약자로, 다음 단어의 마지막 글자로 이동&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;b&lt;/strong&gt; : back의 약자로, 이전 단어의 앞글자로 이동&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;단어의 앞글자로 이해하면 쉽다.&lt;/p&gt;
&lt;h3&gt;Line(한 줄) 기준&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;0&lt;/strong&gt; : Line의 시작으로 이동&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;^&lt;/strong&gt; : 공백을 제외한 Line의 시작으로 이동&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$&lt;/strong&gt; : Line의 끝으로 이동&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;regex(정규 표현식)의 익숙하면, ^,$의 개념을 그와 비슷하게 생각하면 된다.&lt;/p&gt;
&lt;h3&gt;파일 기준&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;gg&lt;/strong&gt; : 파일의 시작&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;G&lt;/strong&gt; : 파일의 끝&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;워낙 자주 쓰여서 쓰다 보면 익숙해진다. &lt;code class=&quot;language-text&quot;&gt;숫자 + G&lt;/code&gt; 명령어를 사용하면 원하는 줄의 위치로 바로 이동 가능하다.&lt;/p&gt;
&lt;p&gt;숫자와 함께 사용되었을 때, 이동 관련 명령어는 더 강력해진다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;2개의 단어 앞으로 이동&lt;/code&gt;이란 대화를 하기 위해선 어떻게 해야 할까?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;-&gt; 간단하다. 2w를 입력하면 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;4줄 아래로 이동&lt;/code&gt; 의 대화도 간단하게 4j의 입력으로 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 이동 관련 명령어들은 다른 명령어들과 결합하여 더 강력해진다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;f = find, t = till(until)&lt;/h2&gt;
&lt;p&gt;하나의 Line에서 특정 문자를 찾을 때 사용되는 명령어들이 f(find), t(till)이다. 이 명령어들은 찾고자 하는 문자가 뒤에 따라붙는다. 영어의 의미처럼 f는 입력된 문자를 찾고 그 위치로 이동하고, t는 찾은 문자 전의 위치로 이동한다. (전체 문서에서의 검색은 &lt;code class=&quot;language-text&quot;&gt;/검색어&lt;/code&gt;의 방법을 사용한다.)&lt;/p&gt;
&lt;p&gt;대화상황에 따른 명령어 예시는 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&apos;a&apos;를 찾아라&lt;/code&gt; =&gt; &lt;code class=&quot;language-text&quot;&gt;find &apos;a&apos;&lt;/code&gt; =&gt; &lt;code class=&quot;language-text&quot;&gt;fa&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;3번째 등장하는 &apos;a&apos;를 찾아라&lt;/code&gt; =&gt; &lt;code class=&quot;language-text&quot;&gt;find 3rd &apos;a&apos;&lt;/code&gt; = &lt;code class=&quot;language-text&quot;&gt;f3a&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&apos;(&apos;전으로 이동하라&lt;/code&gt; =&gt;  &lt;code class=&quot;language-text&quot;&gt;till &apos;(&apos;&lt;/code&gt; =&gt; &lt;code class=&quot;language-text&quot;&gt;t(&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;d = delete, c = change&lt;/h2&gt;
&lt;p&gt;코드를 수정하는 일이 많기 때문에, 가장 많이 사용되는 명령어 중 하나다. change 명령어는 delete 명령어와 똑같이 작동하나, 삭제 후 insert 모드로 변경된다. 그렇기에 이 글에서는 delete모드 만으로 설명을 진행한다.&lt;/p&gt;
&lt;p&gt;delete 명령어는 뒤에 이동 위치가 오고 그 사이의 값들을 지운다. 이동 위치의 경우 앞서 설명했던 이동 명령어가 사용된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;단어를 지워라&lt;/code&gt; =&gt; &lt;code class=&quot;language-text&quot;&gt;delete word&lt;/code&gt; =&gt; &lt;code class=&quot;language-text&quot;&gt;da&lt;/code&gt; (커서가 기준이기 때문에 커서부터 시작하는 단어가 삭제된다.)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;파일 끝까지 지워라&lt;/code&gt; =&gt; &lt;code class=&quot;language-text&quot;&gt;delete End-Of-File&lt;/code&gt; =&gt; &lt;code class=&quot;language-text&quot;&gt;dG&lt;/code&gt; (커서 이후로 파일 끝까지 삭제)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&apos;(&apos; 이전까지 지워라&lt;/code&gt; =&gt; &lt;code class=&quot;language-text&quot;&gt;delete till &apos;(&apos;&lt;/code&gt; =&gt; &lt;code class=&quot;language-text&quot;&gt;dt(&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;delete, change 명령어 사용 꿀팁&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 481px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7b9ef8ccf5d668d635ba61bc71ba7415/213b2/2021-07-14-vim-attitude-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 14.137214137214137%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAsElEQVR42iWO3W6DMBhDeaBSmr8vIU0CNMBY1arqzd7/Tc7S7eLIki1b7kZzxqgB6wRrDd4pUlDkUf+pF0NYElICvmGTMG0zKSW0sYg0z1rURdH3Z7r5U4xCuW3M88QtC8+t8Fgzrz1RS2K5H9THF/V5sDZeP2+O7ztL3anrzjUXwhjx3tP1F42E+P9QhNNpwJuM0xGnmq8EPRiMccRUGi1rr3wb+Iw48YR4bWTKNPELeLVkxdtyVakAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;vim-delete-cursor&quot;
        title=&quot;&quot;
        src=&quot;/static/7b9ef8ccf5d668d635ba61bc71ba7415/213b2/2021-07-14-vim-attitude-2.png&quot;
        srcset=&quot;/static/7b9ef8ccf5d668d635ba61bc71ba7415/213b2/2021-07-14-vim-attitude-2.png 481w&quot;
        sizes=&quot;(max-width: 481px) 100vw, 481px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
코드를 작성하면서 (),{},&quot;&quot;,” 내부에 코드를 수정하는 경우가 많다.&lt;/p&gt;
&lt;p&gt;위 사진처럼 커서의 위치가 있을 때, 이 내부를 지우고자 한다. 어떻게 해야 할까?&lt;/p&gt;
&lt;p&gt;지금까지 설명한 방식으로 예시를 들자면,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;이전 단어로 이동(커서 기준 이전 단어 시작 위치는 MemberRequest의 M) =&gt; &lt;code class=&quot;language-text&quot;&gt;b&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;’)’ 이전까지 지우고 insert 모드로 전환 (change) =&gt; &lt;code class=&quot;language-text&quot;&gt;ct)&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;의 방식으로 진행할 수 있다. (다양한 방식으로 가능하다)&lt;/p&gt;
&lt;p&gt;이러한 경우를 더 편하게 하기 위해 vim에서는 inside의 i, all의 a 를 제공한다. 다음의 예시를 보면 쉽게 이해할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;() 괄호 안에 값을 지우기&lt;/code&gt; =&gt; &lt;code class=&quot;language-text&quot;&gt;delete inside ()&lt;/code&gt; =&gt; &lt;code class=&quot;language-text&quot;&gt;di(&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;di)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;() 괄호도 포함해서 지우기&lt;/code&gt; =&gt; &lt;code class=&quot;language-text&quot;&gt;delete all ()&lt;/code&gt; =&gt; &lt;code class=&quot;language-text&quot;&gt;da(&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;da)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 방식으로 (),{},&quot;&quot;,” 등에 모두 적용할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;정리&lt;/h3&gt;
&lt;p&gt;거창하게 vim을 대하는 자세로 ‘대화’를 제시했다. 무조건적인 암기보다도 각각의 명령어가 어떤 의미를 지니는지 이해하자는 의미이다. vim의 다른 많은 기능들도 이와 비슷한 방식으로 도전해보기를 바란다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;참고 자료&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://bengi.kr/1349&quot;&gt;Vim 도대체 왜 쓰는가&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[DispatcherServlet - Part 2]]></title><description><![CDATA[지난 1편에서는 DispatcherServlet 정의, 설정 방법, 동작 흐름에 대해 알아봤다.
이번 2편에서는 DispatcherServlet의 동작 원리를 코드와 함께 살펴보자. 아직…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-15-dispatcherservlet-part-2/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-15-dispatcherservlet-part-2/</guid><pubDate>Thu, 15 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;지난 1편에서는 DispatcherServlet 정의, 설정 방법, 동작 흐름에 대해 알아봤다.
이번 2편에서는 DispatcherServlet의 동작 원리를 코드와 함께 살펴보자.&lt;br/&gt;&lt;/p&gt;
&lt;!-- end --&gt;
&lt;p&gt;아직 1편을 읽지 않았다면, 아래 글을 먼저 읽고 오자.&lt;br/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://woowacourse.github.io/tecoble/post/2021-06-25-dispatcherservlet-part-1/&quot;&gt;DispatcherServlet - Part 1&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h2&gt;동작 원리 with 코드&lt;/h2&gt;
&lt;p&gt;WAS에서 &lt;code class=&quot;language-text&quot;&gt;HttpServletRequest&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;HttpServletResponse&lt;/code&gt; 객체를 생성하여 DispatcherServlet에게 전달한다.
DispatcherServlet은 해당 객체들을 파라미터로 전달받고, 웹 요청에 따른 응답을 생성하기 위해 맨 처음으로 &lt;code class=&quot;language-text&quot;&gt;doService()&lt;/code&gt; 메소드를 호출한다.
그리고 해당 메소드 내부에서 이어 &lt;code class=&quot;language-text&quot;&gt;doDispatch()&lt;/code&gt; 메소드를 호출한다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// DispatcherServlet.java&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doService&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;logRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;doDispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;doDispatch() 메소드에는 DispatcherServlet이 호출하는 모든 메소드가 담겨 있다. 이제부터 본격적으로 확인해보자.
먼저 웹 요청을 처리할 수 있는 Handler(Controller)를 찾기 위해 &lt;code class=&quot;language-text&quot;&gt;getHandler()&lt;/code&gt; 메소드를 호출한다.
이때, 해당 메소드의 반환 타입은 &lt;code class=&quot;language-text&quot;&gt;HandlerExecutionChain&lt;/code&gt;이다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// DispatcherServlet.java&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doDispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                mappedHandler &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;processedRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@Nullable&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HandlerExecutionChain&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handlerMappings &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HandlerMapping&lt;/span&gt; mapping &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handlerMappings&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;HandlerExecutionChain&lt;/span&gt; handler &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mapping&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handler &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;찾은 Handler(Controller)를 실행하기 위해 해당 Handler 맞는 HandlerAdapter를 탐색해야 한다.
이를 위해 &lt;code class=&quot;language-text&quot;&gt;getHandlerAdatper()&lt;/code&gt; 메소드를 호출한다. 해당 메소드의 반환 타입은 &lt;code class=&quot;language-text&quot;&gt;HandlerAdapter&lt;/code&gt;이다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// DispatcherServlet.java&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doDispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
            &lt;span class=&quot;token class-name&quot;&gt;HandlerAdapter&lt;/span&gt; ha &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getHandlerAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mappedHandler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HandlerAdapter&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getHandlerAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServletException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handlerAdapters &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HandlerAdapter&lt;/span&gt; adapter &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handlerAdapters&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;adapter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;supports&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; adapter&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;찾은 HandlerAdapter로 Handler의 메소드를 실행하기 앞서 &lt;sup&gt;*&lt;/sup&gt;Interceptor의 &lt;code class=&quot;language-text&quot;&gt;applyPreHandle()&lt;/code&gt; 메소드를 호출한다.
해당 메소드의 반환 타입은 boolean이다.
만약, 반환값이 &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;면 Interceptor를 통과하여 다음 단계로 진행된다.
반면, 반환값이 &lt;code class=&quot;language-text&quot;&gt;false&lt;/code&gt;면 Interceptor를 통과하지 못해 로직을 더이상 수행하지 않고 종료한다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// DispatcherServlet.java&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doDispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;mappedHandler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;applyPreHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;processedRequest&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;applyPreHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;interceptorList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;HandlerInterceptor&lt;/span&gt; interceptor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;interceptorList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;interceptor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;triggerAfterCompletion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;interceptorIndex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; i&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;&lt;sup&gt;*&lt;/sup&gt;Interceptor가 무엇인지 궁금하다면, 테코블에 있는 아래 글을 참고해보자.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://woowacourse.github.io/tecoble/post/2021-05-24-spring-interceptor/&quot;&gt;Spring ArgumentResolver와 Interceptor&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br/&gt;
&lt;p&gt;Interceptor를 통과하면, 앞서 찾은 HandlerAdapter를 이용하여 &lt;code class=&quot;language-text&quot;&gt;handle()&lt;/code&gt; 메소드로 Handler(Controller)의 메소드를 실행한다.
handle() 메소드의 반환 타입은 &lt;code class=&quot;language-text&quot;&gt;ModelAndView&lt;/code&gt;이다.
HandlerAdapter는 인터페이스로 정의되어 있어, 다양한 종류의 HandlerAdapter 구현체에서 해당 메소드를 구현한다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// DispatcherServlet.java&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doDispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            mv &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ha&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;processedRequest&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mappedHandler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// HandlerAdapter.java&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HandlerAdapter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Nullable&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;ModelAndView&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;예를 들어, &lt;code class=&quot;language-text&quot;&gt;HandlerFunctionAdapter, HttpRequestHandlerAdapter, SimpleServletHandlerAdapter&lt;/code&gt;와 같은 구현체들이 존재한다.&lt;br/&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
    &lt;img width=&quot;1212&quot; alt=&quot;handlerAdapterImplementationClasses&quot; src=&quot;https://user-images.githubusercontent.com/50176238/125674059-85a8a308-2588-4b61-b5bf-386575c3652e.png&quot;&gt;
&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;handle() 메소드를 수행하고 나면, Interceptor에서 &lt;code class=&quot;language-text&quot;&gt;applyPostHandle()&lt;/code&gt; 메소드를 호출한다.
이는 비즈니스 로직을 전부 수행하고 실행하는 메소드로, 반환 타입은 void이다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// DispatcherServlet.java&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doDispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
            mappedHandler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;applyPostHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;processedRequest&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mv&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;applyPostHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@Nullable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ModelAndView&lt;/span&gt; mv&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;interceptorList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;HandlerInterceptor&lt;/span&gt; interceptor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;interceptorList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        interceptor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mv&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;handle() 메소드를 실행하면, DispatcherServlet 뒤에 있는 비즈니스 로직을 수행하고 결과로 ModelAndView를 반환한다.
그리고 applyPostHandle() 메소드로 ModelAndView에 대해 후처리를 진행한다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;이때, 1편에도 언급했듯이 @Controller면 ModelAndView를 반환하고 @RestController면 null을 반환한다.
따라서, @RestController의 경우 이후 View에 Model이 렌더링되는 과정은 생략하고 바로 그 다음 단계를 진행한다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;다음으로, &lt;code class=&quot;language-text&quot;&gt;processDispatchResult()&lt;/code&gt; 메소드에서 &lt;code class=&quot;language-text&quot;&gt;render()&lt;/code&gt; 메소드를 호출한다. 해당 메소드의 반환 타입은 void이다.
이 메소드 내부에서 &lt;code class=&quot;language-text&quot;&gt;resolveViewName()&lt;/code&gt; 메소드를 호출하여 View의 논리 이름을 물리 이름으로 변환한다.
마지막으로, View 객체의 &lt;code class=&quot;language-text&quot;&gt;render()&lt;/code&gt; 메소드를 호출하여 View에 Model 데이터를 렌더링한다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// DispatcherServlet.java&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doDispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;processDispatchResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;processedRequest&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mappedHandler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mv&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dispatchException&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;processDispatchResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Nullable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HandlerExecutionChain&lt;/span&gt; mappedHandler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Nullable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ModelAndView&lt;/span&gt; mv&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Nullable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; exception&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mv &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;mv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;wasCleared&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mv&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;errorView&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;WebUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clearErrorRequestAttributes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;ModelAndView&lt;/span&gt; mv&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;View&lt;/span&gt; view&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; viewName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getViewName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;viewName &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        view &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resolveViewName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;viewName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getModelInternal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; locale&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        view&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getModelInternal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@Nullable&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resolveViewName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; viewName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@Nullable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; model&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Locale&lt;/span&gt; locale&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;viewResolvers &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ViewResolver&lt;/span&gt; viewResolver &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;viewResolvers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;View&lt;/span&gt; view &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; viewResolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolveViewName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;viewName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; locale&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;view &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; view&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// View.java&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token annotation punctuation&quot;&gt;@Nullable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; model&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;맨 마지막으로 Interceptor를 한번 더 실행한다. 이때, &lt;code class=&quot;language-text&quot;&gt;triggerAfterCompletion()&lt;/code&gt; 메소드를 호출하고 응답값이 반환되기 전에 마지막 후처리를 진행한다.
해당 메소드는 View에 Model를 렌더링한 이후, 즉 가장 마지막에 실행된다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// DispatcherServlet.java&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doDispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mappedHandler &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        mappedHandler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;triggerAfterCompletion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;triggerAfterCompletion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@Nullable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; ex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;interceptorIndex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;HandlerInterceptor&lt;/span&gt; interceptor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;interceptorList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            interceptor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;afterCompletion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;이렇게 doService()부터 triggerAfterCompletion() 메소드까지 진행되면 DispatcherServlet의 한 흐름이 끝난다.
물론, 매 웹 요청마다 모든 메소드를 수행하는 건 아니다. 상황에 따라 어떤 메소드가 호출돼도 실행되지 않을 수 있다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;전체적인 DispatcherServlet의 동작 원리를 그림으로 나타내면 아래와 같다.&lt;br/&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://user-images.githubusercontent.com/50176238/125677429-6cf0f780-3e75-4f05-a525-ffec0e190569.png&quot;&gt;
&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;DispatcherServlet 내부 코드&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[git 브랜치 전략에 대해서]]></title><description><![CDATA[Git 우리는 개발을 진행하면서 우리가 구현한 소스코드를 git이라는 버전 관리 시스템을 통해 관리한다. git을 사용하지 않았더라면 협업을 진행하면서 메일이나 USB로 소스코드를 주고받아야 했을 것이다.
git…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-15-git-branch/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-15-git-branch/</guid><pubDate>Thu, 15 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Git&lt;/h2&gt;
&lt;p&gt;우리는 개발을 진행하면서 우리가 구현한 소스코드를 git이라는 버전 관리 시스템을 통해 관리한다.&lt;br&gt;
git을 사용하지 않았더라면 협업을 진행하면서 메일이나 USB로 소스코드를 주고받아야 했을 것이다.
git을 사용함으로써 우리는 시시각각 코드를 전송할 수 있다.
게다가 일일이 병합하는 과정을 생략하고도 손쉽게 소스코드를 관리할 수 있다.
뿐만 아니라 꼼꼼한 commit 로그 작성을 통해 과거의 소스코드와도 한 눈에 비교할 수 있다.&lt;br&gt;
협업을 할 때도 이 git을 사용해 분산 버전관리를 할 수 있어 브랜치에 따라 독자적인 개발을 하고 메인 저장소에 merge 하는 방식으로 체계적으로 개발을 진행할 수 있다.&lt;/p&gt;
&lt;p&gt;그렇다면 이러한 브랜치를 어떻게 나누고 개발할 수 있을까?&lt;/p&gt;
&lt;p&gt;이번 글에서는 대표적으로 많이 사용되는 브랜치 전략인 Git flow, Github flow, Gitlab flow에 대해서 알아보도록 하자.&lt;/p&gt;
&lt;h3&gt;Git Flow&lt;/h3&gt;
&lt;p&gt;git flow는 &lt;code class=&quot;language-text&quot;&gt;feature&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;develop&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;release&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;hotfix&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;master&lt;/code&gt; 5가지의 브랜치를 갖는다.&lt;br&gt;
아래 사진은 git flow의 브랜치 전략을 가장 잘 드러내주는 그림이다.
&lt;img src=&quot;https://user-images.githubusercontent.com/43775108/125800526-2ea36d8e-6262-4ba5-9ef0-af7845131d85.png&quot; alt=&quot;git flow&quot;&gt;&lt;/p&gt;
&lt;p&gt;각각의 브랜치에 대해서 간략하게 알아보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;feature&lt;br&gt;
feature 브랜치는 기능의 구현을 담당한다.&lt;br&gt;
브랜치명은 팀마다 컨벤션을 가지고 지을 수 있지만 &lt;code class=&quot;language-text&quot;&gt;feature/{구현기능명}&lt;/code&gt;과 같은 명칭을 준수하는 것이 일반적이다.&lt;br&gt;
예를 들어, &lt;code class=&quot;language-text&quot;&gt;feature/login&lt;/code&gt;은 login 기능을 구현하는 브랜치임을 알 수 있다.&lt;br&gt;
feature 브랜치는 develop 브랜치에서 생성되며, develop 브랜치로 머지된다.&lt;br&gt;
머지된 후에는 해당 브랜치가 삭제된다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;develop&lt;br&gt;
develop 브랜치는 말 그대로 개발을 진행하는 브랜치로 중심적인 브랜치이다.&lt;br&gt;
하나의 feature 브랜치가 머지될 때마다 develop 브랜치에 해당 기능이 더해지며 살을 붙여간다.&lt;br&gt;
develop 브랜치는 배포할 수준의 기능을 갖추면 release 브랜치로 머지된다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;release&lt;br&gt;
release 브랜치는 개발된 내용을 배포하기 위해 준비하는 브랜치이다.&lt;br&gt;
브랜치명은 &lt;code class=&quot;language-text&quot;&gt;release-1&lt;/code&gt;과 같은 방식으로 첫번째 릴리즈, 두번째 릴리즈 등을 지정하는 것이 보편적이다.&lt;br&gt;
release 브랜치에서 충분한 테스트를 통해 버그를 검사하고 수정해 배포할 준비가 완전히 되었다고 판단되면 master로 머지해 배포한다.&lt;br&gt;
release 브랜치는 develop 브랜치에서 생성되며 버그 수정 내용을 develop 브랜치에도 반영하고, 최종적으로 master 브랜치에 머지한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;hotfix&lt;br&gt;
hotfix 브랜치는 배포된 소스에서 버그가 발생하면 생성되는 브랜치이다.&lt;br&gt;
브랜치명은 &lt;code class=&quot;language-text&quot;&gt;hotfix-1&lt;/code&gt;로 지정된다. release 브랜치를 거쳐 한차례 버그 검사를 했지만 예상치 못하게 배포 후에 발견된 버그들에 대해서 수정한다.
hotfix 브랜치는 master 브랜치에서 생성되며, 수정이 완료되면 develop 브랜치, release 브랜치와 master 브랜치에 수정 사항을 반영한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;master&lt;br&gt;
master 브랜치는 최종적으로 배포되는 가장 중심의 브랜치이다.&lt;br&gt;
develop 브랜치에서는 개발이 진행되는 와중에도 이전 release 브랜치 내용이 master에 있어 배포되어 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Git flow 브랜치 전략은 여러 브랜치들이 존재하고 각 브랜치마다 상황이 명확하게 분류되어 있지만, 오히려 이렇게 많은 브랜치가 흐름을 더욱 복잡하게 만들기도 한다.&lt;br&gt;
뿐만 아니라, release와 master의 구분이 모호하기도 하다.&lt;br&gt;
하지만 프로젝트의 규모가 커지면 커질수록 소스코드를 관리하기에 용이하다는 장점이 있다.&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;Github Flow&lt;/h3&gt;
&lt;p&gt;github flow는 git flow의 브랜치 전략이 너무 복잡하고 적용하기 어렵다고 해서 생겨난 브랜치 전략이다.&lt;br&gt;
github flow는 master 브랜치 하나만을 가지고 진행하는 방식이다.&lt;br&gt;
master 브랜치는 어떤 기능이 구현되든, 오류가 수정되든 모두 master에 머지되어 항상 update된 상태를 유지한다.&lt;/p&gt;
&lt;p&gt;아래 흐름은 github flow의 흐름을 보여주는 그림이다.&lt;br&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/43775108/125813582-d1500c51-e1af-44e7-9f90-83901dfec03f.png&quot; alt=&quot;github flow&quot;&gt;&lt;/p&gt;
&lt;p&gt;자세한 github flow의 과정을 알아보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;master 브랜치에서 개발이 시작된다.&lt;/li&gt;
&lt;li&gt;기능 구현이나 버그가 발생하면 issue를 작성한다.&lt;/li&gt;
&lt;li&gt;팀원들이 issue 해결을 위해 master 브랜치에서 생성한 &lt;code class=&quot;language-text&quot;&gt;feature/{구현기능}&lt;/code&gt; 브랜치에서 개발을 하고 commit log를 작성한다.&lt;/li&gt;
&lt;li&gt;push를 하면 pull request를 날릴 수 있다.&lt;/li&gt;
&lt;li&gt;pull request를 통해 팀원들 간의 피드백, 버그 찾는 과정이 진행된다. release 브랜치가 없으므로 이 과정이 탄탄하게 진행되어야 한다.&lt;/li&gt;
&lt;li&gt;모든 리뷰가 이루어지면, merge하기 전에 배포를 통해 최종 테스트를 진행한다.&lt;/li&gt;
&lt;li&gt;테스트까지 진행되면 master 브랜치에 머지한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;github flow는 시시각각 master에 머지될 때마다 배포가 이루어지는 것이 좋다.&lt;br&gt;
따라서 CI/CD를 통한 배포 자동화를 적용하는 것이 좋다.&lt;br&gt;
브랜치 전략이 단순해 master 브랜치에서 pull 하고, 기능 구현하고, 머지하는 일의 반복이다.&lt;br&gt;
하지만 pull request에서 팀원간의 충분한 리뷰와 피드백이 진행되지 않으면 배포된 자체에서 버그가 발생할 수 있으므로 주의해야 한다.&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;GitLab Flow&lt;/h3&gt;
&lt;p&gt;gitlab flow는 복잡하지 않고 효율을 높이고자 생겨난 브랜치 전략으로 master, feature, production 브랜치가 존재한다.&lt;br&gt;
gitlab flow는 이슈트래킹을 연동해 프로세스를 단순화하고자 한다. merge request를 통해 승인이 되는 이슈만 머지하도록 하는 것이 핵심이다.&lt;/p&gt;
&lt;p&gt;아래 그림은 gitlab flow의 흐름을 간단히 보여준다.&lt;br&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/43775108/125891998-ccba14fb-b15d-4259-8220-d11bc1b809f0.png&quot; alt=&quot;gitlab flow&quot;&gt;&lt;/p&gt;
&lt;p&gt;각 브랜치에 대해서 알아보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;feature&lt;br&gt;
모든 기능 구현은 feature 브랜치에서 진행된다.&lt;br&gt;
이 feature 브랜치는 master 브랜치에서 나와 master 브랜치로 머지된다.&lt;br&gt;
기능 구현이 완료되면 merge request를 보낸다.&lt;br&gt;
merge request에서 팀원 간의 협의가 완료되면 master 브랜치로 머지한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;master gitlab flow의 master 브랜치는 git flow의 develop 브랜치와 같다.&lt;br&gt;
master 브랜치는 feature 브랜치에서 병합된 기능에 대해 test 한다.&lt;br&gt;
전체적인 테스트가 진행되어 기능에 대한 보장이 되었다면 production 브랜치로 머지한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;production&lt;br&gt;
production 브랜치는 한 마디로 배포 브랜치이다. git flow의 master 브랜치와 같다.&lt;br&gt;
안정된 소스코드가 되었을 때 production 브랜치에 병합해 배포하도록 한다.&lt;br&gt;
하지만 여기서 견고한 test를 거치고 싶은 경우 pre-production 브랜치를 생성해 production에 병합하기 전에 test server에 배포해 확인할 수도 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;gitlab flow는 git flow처럼 복잡하지 않으면서, github flow처럼 너무 단순하지 않아 비교적 적용이 쉬우면서도 원활한 운영이 가능하다.&lt;br&gt;
gitlab의 CEO는 &lt;a href=&quot;https://about.gitlab.com/blog/2016/07/27/the-11-rules-of-gitlab-flow/&quot;&gt;최대한의 효울을 위해 지켜야 할 11가지 규칙&lt;/a&gt;에 대해 서술해두었다.&lt;br&gt;
이 11가지 규칙에 대해서 이해하고 적용한다면 최상의 gitlab flow 전략을 가져갈 수 있을 것이다.&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;마치며&lt;/h3&gt;
&lt;p&gt;이렇게 오늘은 3가지의 브랜치 전략에 대해서 알아보았다.&lt;br&gt;
각 팀의 상황과 문화에 따라서 적합한 브랜치 전략이 있을 것이다.&lt;br&gt;
협업을 하며 최대의 효율을 내기 위해 적용할 브랜치 전략에 대해서 팀원들과 충분한 대화를 해보는 것은 어떨까?&lt;/p&gt;
&lt;br&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://nvie.com/posts/a-successful-git-branching-model/&quot;&gt;Git Flow&lt;/a&gt;
&lt;a href=&quot;https://guides.github.com/introduction/flow/&quot;&gt;Github Flow&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://scottchacon.com/2011/08/31/github-flow.html&quot;&gt;Github Flow - Scott Chacon&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://about.gitlab.com/topics/version-control/what-is-gitlab-flow/&quot;&gt;GitLab Flow&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[리액트 컴포넌트 npm publish]]></title><description><![CDATA[리액트를 사용할 수 있는 독자를 대상으로 작성한 글입니다. 0. Intro 프로젝트를 진행하면서 이미 친숙한 리액트를 포함하여 다양한 라이브러리들을 npm 또는 yarn…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-13-react-component-npm-publishing/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-13-react-component-npm-publishing/</guid><pubDate>Tue, 13 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;리액트를 사용할 수 있는 독자를 대상으로 작성한 글입니다.&lt;/p&gt;
&lt;h2&gt;0. Intro&lt;/h2&gt;
&lt;p&gt;프로젝트를 진행하면서 이미 친숙한 리액트를 포함하여 다양한 라이브러리들을 npm 또는 yarn으로 설치해서 이용합니다. 그렇다면 이런 라이브러리들은 어떻게 만들어지는 걸까요. ‘간단하게’ 알아보고자 합니다. ‘간단하게’라는 말을 붙인 이유는 다른 테스팅 작업을 제외했기 때문입니다. 필수적인 것은 아니지만, 라이브러리의 안정성을 위해 보통 배포하기 전에 스토리북이나 jest 등의 도구로 기본적인 테스팅 작업이 선행되는데, 이번 글에서는 테스팅과 관련된 부분은 생략하고 진행하겠습니다.&lt;/p&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;깃허브 디렉토리 생성 및 CRA(Create React App) 설치&lt;/li&gt;
&lt;li&gt;컴포넌트 제작&lt;/li&gt;
&lt;li&gt;컴포넌트 패키징&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1. CRA(Create React App) 설치 및 기본 환경 세팅&lt;/h2&gt;
&lt;p&gt;먼저 깃허브 디렉토리를 만들고 로컬로 &lt;code class=&quot;language-text&quot;&gt;clone&lt;/code&gt;을 실행합니다. 이후 간단하게 리액트 환경을 구성하기 위해서 CRA를 실행합니다. CRA는 바로 프로젝트를 시작할 수 있도록 기본적인 환경을 구성해주는 패키지입니다. CRA로 기본적인 환경을 구성하지만 이를 바탕으로 또 다른 패키지(또는 라이브러리)도 만들 수 있습니다. &lt;code class=&quot;language-text&quot;&gt;npx create-react-app &amp;lt;파일명/디렉토리명&gt;&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; clone https://github.com/jum0/jum0-react-library.git
$ &lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; jum0-react-library
$ npx create-react-app &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;2. 컴포넌트 제작&lt;/h2&gt;
&lt;p&gt;CRA 설치 및 기본 환경 세팅을 통해 기본적인 환경 구성을 했습니다. 그러면 다음으로 컴포넌트를 제작하겠습니다. 컴포넌트는 간단하게 &lt;code class=&quot;language-text&quot;&gt;JumoButton&lt;/code&gt;라는 버튼을 하나 만들어보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/components/JumoButton/JumoButton.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;react&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;JumoButton&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; children&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; backgroundColor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fontColor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onClick &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onClick&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;backgroundColor&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;backgroundColor&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;fontColor&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; JumoButton&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;JumoButton 컴포넌트는 props로 텍스트를 자식으로 받고, 배경색, 글자색, 클릭 함수를 받도록 설계했습니다. 그러면 동작하는지 확인해보겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/App.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; JumoButton &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./components/JumoButton/JumoButton&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;JumoButton&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;backgroundColor&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;blue&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;fontColor&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;white&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    테스트 버튼
  &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;JumoButton&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; App&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;로컬 서버에서 제작한 컴포넌트를 확인할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;3. 컴포넌트 패키징&lt;/h2&gt;
&lt;p&gt;그럼 이제부터 제작한 컴포넌트를 라이브러리로 만드는 과정을 진행하겠습니다.
먼저, &lt;code class=&quot;language-text&quot;&gt;components/index.js&lt;/code&gt; 파일을 추가합니다. 이 파일은 우리가 만들 라이브러리의 시작점이 되는 동시에, 아까 만들었던 컴포넌트들을 export 할 위치입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//components/index.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; JumoButton &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./JumoButton/JumoButton&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; JumoButton &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;추가로 &lt;code class=&quot;language-text&quot;&gt;Babel&lt;/code&gt; 관련 몇 가지 패키지를 설치합니다. &lt;code class=&quot;language-text&quot;&gt;Babel&lt;/code&gt;을 통해서 컴포넌트를 컴파일하고, 그 결과물을 이후 &lt;code class=&quot;language-text&quot;&gt;dist&lt;/code&gt; 디렉토리에 복사하기 위함입니다. 설치는 애플리케이션이 빌드할 때만 필요하기 때문에 개발 의존성으로 설치합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; cross-env @babel/cli @babel/preset-env @babel/preset-react --save-dev&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그리고 설치한 패키지를 적용하기 위해서 &lt;code class=&quot;language-text&quot;&gt;babel.config.js&lt;/code&gt; 파일을 작성합니다. (&lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt;과 같은 레벨의 위치에 작성합니다)
&lt;img width=&quot;201&quot; alt=&quot;babel-config-directory&quot; src=&quot;https://user-images.githubusercontent.com/40762111/125733462-80fe9ca9-670b-4abd-8b31-ffaf54d9275f.png&quot;&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// babel.config.js&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  api&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; presets &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;@babel/preset-env&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@babel/preset-react&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; plugins &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;macros&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    presets&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    plugins&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;추가로 이 라이브러리를 사용하는 앱은 리액트 라이브러리가 이미 설치되어 있을 가능성이 높으므로, 기존의 &lt;code class=&quot;language-text&quot;&gt;dependencies&lt;/code&gt;에 있는 패키지들을 &lt;code class=&quot;language-text&quot;&gt;peerDependencies&lt;/code&gt;로 이동시킵니다. 참고로 &lt;a href=&quot;https://classic.yarnpkg.com/en/docs/dependency-types/#toc-peerdependencies&quot;&gt;peerDependencies&lt;/a&gt;를 갖고 있다는 것은 어떤 사람이 자신 프로젝트에 당신의 패키지를 설치할 때, 완전히 똑같은 dependencies를 갖고 있다는 것을 의미합니다. 따라서 &lt;code class=&quot;language-text&quot;&gt;react&lt;/code&gt; 패키지와 함께 사용되는 &lt;code class=&quot;language-text&quot;&gt;react-dom&lt;/code&gt; 패키지에는 &lt;code class=&quot;language-text&quot;&gt;react&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;peerDependencies&lt;/code&gt;로 설치되어 있는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// package.json&lt;/span&gt;

&lt;span class=&quot;token property&quot;&gt;&quot;peerDependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;react&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^17.0.1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;react-dom&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^17.0.1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;react-scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.0.3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    ...
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 배포를 준비하기 위해 &lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt;에 다음과 같은 코드들을 넣어줍니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//package.json&lt;/span&gt;

&lt;span class=&quot;token property&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;dist/index.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&quot;private&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&quot;files&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;dist&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;README.md&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&quot;repository&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;git&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;URL_OF_YOUR_REPOSITORY&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;여기서 &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt;은 이 프로그램의 시작점입니다. &lt;code class=&quot;language-text&quot;&gt;files&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;.gitignore&lt;/code&gt; 파일과 비슷한 형태로 작동하는데, 역할은 반대로 압축될 때 &lt;code class=&quot;language-text&quot;&gt;tarball(.tar)&lt;/code&gt; 파일에 포함되도록 하는 기능을 합니다.&lt;/p&gt;
&lt;p&gt;그리고 마지막으로 &lt;code class=&quot;language-text&quot;&gt;Babel&lt;/code&gt;을 통해서 컴포넌트를 컴파일하고, 그 결과물을 이후 &lt;code class=&quot;language-text&quot;&gt;dist&lt;/code&gt; 디렉토리에 복사하고, 기존의 &lt;code class=&quot;language-text&quot;&gt;dist&lt;/code&gt; 폴더의 내용을 지우는 컴파일 스크립트를 &lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt;에 추가합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//package.json&lt;/span&gt;

&lt;span class=&quot;token property&quot;&gt;&quot;clean&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;rimraf dist&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&quot;compile&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;npm run clean &amp;amp;&amp;amp; cross-env NODE_ENV=production babel src/components --out-dir dist --copy-files&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 차례대로 스크립트를 실행합니다.
먼저 패키지를 빌드합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run compile&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그리고 패키지를 라이브러리에 배포합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; publish&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;npm publish&lt;/code&gt;하면서 발생하는 오류는 &lt;code class=&quot;language-text&quot;&gt;npm adduser&lt;/code&gt;를 통해 npm 사이트에 회원 가입을 한 후, 이메일 인증을 하면 됩니다.&lt;/p&gt;
&lt;p&gt;이제 라이브러리를 만들었으니깐 확인해봐야겠죠.
라이브러리를 해당 프로젝트에 추가합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; jum0-react-library&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;App.js&lt;/code&gt;에서 해당 라이브러리를 불러와 컴포넌트를 사용합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// App.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; JumoButton &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;jum0-react-library&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;react&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;JumoButton&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;backgroundColor&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;green&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;fontColor&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;white&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    테스트 버튼
  &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;JumoButton&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; App&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;마침&lt;/h2&gt;
&lt;p&gt;라이브러리를 직접 만들어보았습니다. 라이브러리를 이제까지 사용만 했지, 직접 만든 것은 처음이었습니다. 이번 실습을 통해 팀이나 조직 간에 재사용 가능한 컴포넌트들은 라이브러리화하여 사용할 수도 있고, 유용하지만 복잡한 코드들을 직접 라이브러리로 만들어보면 도움이 될 것 같습니다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fathomtech.io/blog/create-a-react-component-library-using-create-react-app&quot;&gt;https://fathomtech.io/blog/create-a-react-component-library-using-create-react-app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.npmjs.com/cli/v7/configuring-npm/package-json#main&quot;&gt;https://docs.npmjs.com/cli/v7/configuring-npm/package-json#main&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.npmjs.com/cli/v7/configuring-npm/package-json#files&quot;&gt;https://docs.npmjs.com/cli/v7/configuring-npm/package-json#files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://classic.yarnpkg.com/en/docs/dependency-types&quot;&gt;https://classic.yarnpkg.com/en/docs/dependency-types&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[JVM에 관하여 - Part 1, JVM, JRE, JDK]]></title><description><![CDATA[자바로 작성된 코드는 어떻게 돌아가는 걸까?
해당 물음에 답을 찾기 위한 JVM 시리즈 1편, JVM, JRE, JDK에 관한 글입니다.
이번 글에서는 배포 환경 혹은 개발 환경을 세팅하면서 설치를 하였던 JRE와 JDK…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-12-jvm-jre-jdk/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-12-jvm-jre-jdk/</guid><pubDate>Mon, 12 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;자바로 작성된 코드는 어떻게 돌아가는 걸까?
해당 물음에 답을 찾기 위한 JVM 시리즈 1편, JVM, JRE, JDK에 관한 글입니다.
이번 글에서는 배포 환경 혹은 개발 환경을 세팅하면서 설치를 하였던 JRE와 JDK가 무엇인지에 대해서 다루게 되었습니다.&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;&lt;em&gt;Java&lt;/em&gt; 프로그램 배포 및 개발 환경 세팅&lt;/h2&gt;
&lt;p&gt;개발을 공부하면서 주어진 환경에서 진행하는 것이 아닌 스스로 개발, 배포 그리고 운영 환경을 구축하게 되었습니다.
저희 팀도 AWS의 EC2를 사용하여 &lt;em&gt;Linux&lt;/em&gt; 중 &lt;em&gt;Ubuntu&lt;/em&gt; 배포판 위에서 &lt;em&gt;Java&lt;/em&gt; 프로그램의 개발 및 배포 환경을 설정하였습니다.
이때 습관처럼 다음과 같은 명령어를 입력하게 되었습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; default-jre default-jdk&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;항상 잘 사용하던 명령이지만 다른 프로그래밍 언어인 &lt;em&gt;Python&lt;/em&gt; 이나 &lt;em&gt;Run Time&lt;/em&gt; 환경인 &lt;em&gt;Nodejs&lt;/em&gt; 와 비교했을 때 낯섦이 느껴졌습니다.
난 &lt;em&gt;Java&lt;/em&gt; 를 직접적으로 설치하지 않고, &lt;em&gt;default jdk&lt;/em&gt; 와 &lt;em&gt;default jre&lt;/em&gt; 라는 관계없어 보이는 것을 설치하는 걸까?
이번 포스팅에서는 위의 질문에 대한 답을 찾아갑니다.&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;JVM&lt;/em&gt; 이란&lt;/h2&gt;
&lt;p&gt;설치한 수수께끼의 두 프로그램을 알기 위해서는 이번 시리즈의 주인공인 &lt;em&gt;JVM&lt;/em&gt; 에 대해서 이해를 해야 합니다.
&lt;em&gt;JVM&lt;/em&gt; 은 &lt;em&gt;Java Virtual Machine&lt;/em&gt; 의 약자로 &lt;em&gt;Java&lt;/em&gt; 로 작성된 프로그램이 돌아가도록 만들어주는 프로그램입니다.
&lt;em&gt;Java&lt;/em&gt; 로 작성된 프로그램은 운영체제에 맞는 실행 파일로 컴파일되는 것이 아닌 &lt;em&gt;.class&lt;/em&gt; 라는 확장자를 가진 파일로 변환이 됩니다.
변환된 &lt;em&gt;.class&lt;/em&gt; 파일은 &lt;em&gt;JVM&lt;/em&gt; 위에서 작동이 됩니다.
이때, 미리 설치된 &lt;em&gt;JVM&lt;/em&gt; 은 운영체제 별로 동일하게 작동하도록 &lt;em&gt;.class&lt;/em&gt; 파일에게 환경을 제공합니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 759px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1102ed0280f0a68b7b093eb3fc3b5de8/db779/2021-07-12-jvm.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 93.60000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAAA7DAAAOwwHHb6hkAAADbElEQVR42qWUW09TWRTHm8x8Bz/CfAeND0YF0ReL+KZOfJpHTRTaIihgQltqZuKgpZVr8VIl3kGGAQsxKlUrlxaRAj3MUMRSoPRGaQ/n7PN3730Obc3EmIw7+XXvrrXyz1r77LV0KF6KAhAJP7J0mpL6m4mCJIQddW5lSDKBTFQUChSZQneyrSZA5GJBVQxSBuRDJ5TYdCFbukRJokcF/6lEFr+dIQtPpjeRFYaQXZ1BRqs6sZnFgC+I3jcf4ZmYw+B4CEvLn4CFXkgTdsiBdshTHSAL/fmqdDvKc8sx+P+JYnk9gU9rCW6LpTKYX17HpPAZQmQdU9Qf/hyhjo8gER/Iv0MgSy+grIx9nWF6S0QwHMVT7zSmF6NIZ0UQooAohXtUULhtenuQtZ2fFTU2L8guPZnJYi2xyREl+f9/ZabsHplAo9sDW88ImijWe8Mw3/VwGu8Ow8J3T95WjIXG1t8axIg/pAqyjI41uLDf4MAh0w2UUEoZ1TdwiHK42oGDpja+s/9l1U7u26HsQiv2nruOqra+guBJ6x3o67pQQYWPaegbunG8oQO2pkqcM5thtRrQbDuDi5ZL1HeTxnTxuOOXu3Gkpg21rgFVcJUKnrDextG6Th5UTtHz4E5csNSjzlKLq7azOG9uhMncwMXLtThGxWUXympaUdulCa4nM1xoX2ULDtCy91ex3ckpN/2Ow8ZrKDXYUcIw2lFqVON2KDE6sftsMwztWsnsY/fRh/vno5dweSbQPTyJlt5ROPq8uP7Mh5bnfjgGJ9H+1xRa+8dhp/a2Z15093vRQXFQmp+8wrvZcFGnaG8oNC9gKvCh0N3bOSCyQPt7CSlpBlk5wn1bFNov2FC+0XqSrL67HlcjDGcq+Jk92O3Z95BKd0Go34PKER16Ar9yX3M4h5+H0miaTSMXjyNF3zBrBLVkLbu1WBpCKIjHD3sw5BnltuRiCOPG3zDmOg/3218wOlfD7deCIn5qj+HRXBLx1RjiqVx+mOgUbfREJu2YH7sN34v7GHSbuO3Nag46N3Dq79fAgA6i/zS3/+EVoasK4+DVGbwNCIjGt4pKZnONlbjyAMKoDb7nTtr4D7lNSIkofymixU9HWkAPcdHJ7fd8Wewxb0B/RcD74Ao2WIZQigcsvhoCync7thDFWpcQks/wC4LEGpObvgNwAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;images&quot;
        title=&quot;&quot;
        src=&quot;/static/1102ed0280f0a68b7b093eb3fc3b5de8/db779/2021-07-12-jvm.png&quot;
        srcset=&quot;/static/1102ed0280f0a68b7b093eb3fc3b5de8/0eb09/2021-07-12-jvm.png 500w,
/static/1102ed0280f0a68b7b093eb3fc3b5de8/db779/2021-07-12-jvm.png 759w&quot;
        sizes=&quot;(max-width: 759px) 100vw, 759px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이 과정을 통하여 &lt;em&gt;Java&lt;/em&gt; 는 큰 장점을 얻을 수가 있었습니다.
그것은 바로 높은 이식성입니다.
어느 기기나 운영체제에 상관없이 &lt;em&gt;JVM&lt;/em&gt; 이 설치 및 구동될 수 있는 환경이라면 &lt;em&gt;Java&lt;/em&gt; 로 작성한 프로그램은 실행이 가능합니다.
그 때문에 다른 운영체제에 맞춰서 컴파일을 해줘야 하는 다른 언어보다 높은 이식성을 가질 수 있게 되었습니다.&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;JRE&lt;/em&gt; 이란&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;JVM&lt;/em&gt; 은 우리가 작성한 코드를 기반으로 한 &lt;em&gt;.class&lt;/em&gt; 확장자를 가진 클래스 파일이 작동할 수 있도록 환경을 제공해줍니다.
하지만 원활하게 작동하기 위해서는 코드에 작성된 내용만으로 가능하지는 않습니다.
&lt;em&gt;JVM&lt;/em&gt; 위에서 프로그램이 원활하게 실행을 하기 위해서는 몇 가지 필수적인 요소들이 필요합니다.&lt;br&gt;
이를 제공해주는 것이 &lt;em&gt;JRE&lt;/em&gt;, &lt;em&gt;Java Runtime Environment&lt;/em&gt; 입니다.
&lt;em&gt;JRE&lt;/em&gt; 는 크게 &lt;em&gt;JVM&lt;/em&gt; , &lt;em&gt;Java Class Libraries&lt;/em&gt; 그리고 &lt;em&gt;Class Loader&lt;/em&gt; 라는 요소로 구성이 됩니다.
&lt;em&gt;Java Class Libraries&lt;/em&gt; 는 &lt;em&gt;Java&lt;/em&gt; 를 실행시키는데 필수적인 라이브러리입니다.
&lt;em&gt;java.io&lt;/em&gt; , &lt;em&gt;java.util&lt;/em&gt; , &lt;em&gt;java.thread&lt;/em&gt; 등 작동에 필수적인 요소들을 가지고 있습니다.&lt;/p&gt;
&lt;p&gt;예시 코드와 바이트 코드를 살펴보도록 하겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//Java 코드&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Main&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;
&lt;span class=&quot;token comment&quot;&gt;//바이트코드&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; 중략 &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;L0&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;LINENUMBER&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;L0&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;ICONST_1&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;ISTORE&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
   &lt;span class=&quot;token constant&quot;&gt;L1&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;LINENUMBER&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;L1&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;GETSTATIC&lt;/span&gt; java&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;lang&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ljava&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;io&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PrintStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;ILOAD&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;INVOKEVIRTUAL&lt;/span&gt; java&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;io&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PrintStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;println &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;
   &lt;span class=&quot;token constant&quot;&gt;L2&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;LINENUMBER&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;L2&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;RETURN&lt;/span&gt;
   &lt;span class=&quot;token constant&quot;&gt;L3&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;LOCALVARIABLE&lt;/span&gt; args &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Ljava&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;lang&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;L0&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;L3&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;LOCALVARIABLE&lt;/span&gt; a &lt;span class=&quot;token class-name&quot;&gt;I&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;L1&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;L3&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;MAXSTACK&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;MAXLOCALS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위에서 보는 것처럼 &lt;em&gt;JVM&lt;/em&gt; 에 올라가는 클래스 파일의 바이트 코드에서도 필수 라이브러리를 참고하고 있습니다.
이 때문에 간단한 코드라도 필수 라이브러리가 필요하다는 것을 알 수가 있습니다.
&lt;em&gt;Class Loader&lt;/em&gt; 는 필요한 클래스들을 &lt;em&gt;JVM&lt;/em&gt; 위로 올려주는 역할을 합니다.
자세한 연결 및 작동 과정은 다음 포스팅에서 다루도록 하겠습니다.
&lt;em&gt;JRE&lt;/em&gt; 의 구성요소들을 통해서, &lt;em&gt;JVM&lt;/em&gt; 은 원활히 작동할 수가 있습니다.&lt;br&gt;
하지만 &lt;em&gt;JRE&lt;/em&gt; 는 개발에 사용되는 것이 아닌 실제 프로그램을 구동시키는 데 집중을 하고 있습니다.
실제 &lt;em&gt;Java&lt;/em&gt; 코드가 주어져도 이를 분석하거나 클래스 파일로 변환하는 일은 할 수 없습니다.
&lt;em&gt;Java Compiler&lt;/em&gt; 를 비롯한 개발에 필요한 요소들은 없기 때문에 개발을 하기 위해서는 다른 요소들이 더 필요합니다.&lt;/p&gt;
&lt;h2&gt;JDK이란&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;JDK&lt;/em&gt;, &lt;em&gt;Java Development Kit&lt;/em&gt; 의 약자로 &lt;em&gt;Java&lt;/em&gt; 를 활용하여 프로그램을 개발할 때 필요한 도구 모음입니다.
이때, 실제로 프로그램을 실행시켜보아야 하기 때문에 &lt;em&gt;JRE&lt;/em&gt; 가 포함이 되어 있습니다.
이를 확인하기 위해 &lt;em&gt;JRE&lt;/em&gt; 와 &lt;em&gt;JDK&lt;/em&gt; , 그리고 관련된 의존성을 삭제한 후에 다시 &lt;em&gt;JDK&lt;/em&gt; 만 설치를 해보았습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 677px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/fb5b1e9c9a41b43100eb0313635f1d75/3c503/2021-07-12-jre-dependency.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABJ0AAASdAHeZh94AAAB90lEQVR42m2TV44iQRBEOQDeew+L9yCMQBgJL0Yjwf4godn7HyK2X832CC18pKq6KjMqIjLbEazF5a2FvyMfUrlUVrfb1WAwULvdVr/fV71eVy6XUyqVUjwel9vtls/nk9frfQnHeDLRx+eHrr+v+vrzpcfjoev1qtPppM1mo/l8rul0qsViodVqpeFwqEwmI6fT+R7wdrvpdDiq1+up1Wyp0+moUqkomUwqkUgom80qGo0aduzJ2+12RoHL5Xph6rjf72o2m0ZeqVQyQDCguFgsGqk2OHIB4BwFbwFHo5GReTwetd/vdT6ftVwuxTmF+AcbpM9mMyObe8ABeAG0CyaWl+v1WpfLxYAfDgezbrdb4x9g4/FY5XJZsVjMMGfv9/vl8XhMGEA8q9VqKhQKRjJJ+XzeyMbLarVqmMLItsPOAZQ69pFIxIA78K7VahlpdJBvPGVceIgRohGNRsOcsQIIeDqd/vEb4GAw+A1ozxrFMAqHw+YMYM7JIQDjHgLcAQYwTKlBtvEQ2STaAEhAJhawwoQuIwvpBGxoCENO/DQFOXQUsI7FsG998yJsbTvMjForFhDkIjMUChnQ50476v8S8IWiX1Yj8pYfgNreUGgXYDwRCATe/n6OeqthmNDRTr+ntiW5Ye1L1gN4hGzkPRc9j8n/gH8BGiZ1Y0WASxUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;images&quot;
        title=&quot;&quot;
        src=&quot;/static/fb5b1e9c9a41b43100eb0313635f1d75/3c503/2021-07-12-jre-dependency.png&quot;
        srcset=&quot;/static/fb5b1e9c9a41b43100eb0313635f1d75/0eb09/2021-07-12-jre-dependency.png 500w,
/static/fb5b1e9c9a41b43100eb0313635f1d75/3c503/2021-07-12-jre-dependency.png 677w&quot;
        sizes=&quot;(max-width: 677px) 100vw, 677px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위에서 보이는 것처럼 &lt;em&gt;JDK&lt;/em&gt; 만 설치를 하였을 뿐인데, &lt;em&gt;JRE&lt;/em&gt; 도 자동으로 설치되는 것을 알 수가 있습니다.
그렇다면 &lt;em&gt;JDK&lt;/em&gt; 는 &lt;em&gt;JRE&lt;/em&gt; 이외의 어떤 구성 요소들이 추가된 것일까요?
대표적으로는 &lt;em&gt;Java&lt;/em&gt; 로 이루어진 코드를 클래스 파일로 컴파일하는 &lt;em&gt;javac&lt;/em&gt; 의 기능, 작성된 코드를 디버깅 하는 &lt;em&gt;jdb&lt;/em&gt; 기능 등을 가집니다.&lt;br&gt;
&lt;em&gt;JDK&lt;/em&gt; 에서도 개발과 실행에 필요한 환경 및 기능 제공의 폭에 따라 표준형인 &lt;em&gt;SE(Standard Edition)&lt;/em&gt; 과 여러 기능이 추가된 &lt;em&gt;EE(Enterprise Edition)&lt;/em&gt; 으로 나뉩니다.
하지만 이런 버전 구분 이외에도 출시된 순서에 따라서 숫자로 버전을 구분하는 방법도 있습니다.&lt;/p&gt;
&lt;h2&gt;가장 널리 쓰이는 버전 JDK 버전 8&lt;/h2&gt;
&lt;p&gt;최근까지도 다양한 버전이 출시되고 있는 &lt;em&gt;Java&lt;/em&gt; 이지만 유독 8 버전이 널리 쓰이는 것을 볼 수가 있습니다.
8 버전 이외에도 &lt;em&gt;Ubuntu&lt;/em&gt; 및 &lt;em&gt;Debian&lt;/em&gt; 배포판의 &lt;em&gt;Repository&lt;/em&gt; 에서는 11 버전이 기본으로 제공되고 있습니다.
개발뿐만이 아니라 코딩테스트에서도 이 두 버전만 널리 쓰이게 됩니다.
왜 8 버전과 11 버전이 자주 쓰이게 되는 것일까요?&lt;br&gt;
&lt;em&gt;Java&lt;/em&gt; 는 유명한 &lt;em&gt;Linux&lt;/em&gt; 배포판이나 개발 관련 &lt;em&gt;Runtime Environment&lt;/em&gt;, &lt;em&gt;Library&lt;/em&gt; 와 마찬가지로 여러 버전으로 배포됩니다.
그중 하나가 바로 &lt;em&gt;LTS&lt;/em&gt;, &lt;em&gt;Long Term Support&lt;/em&gt; 개념입니다.
&lt;em&gt;LTS&lt;/em&gt; 의 경우에는 다른 버전보다 안정적이며, 제작한 기업이나 오픈 소스 관리 측에서 긴 기간을 관리해주겠다라는 것을 명시합니다.
일반적으로 각 배포판들은 약 6개월 정도의 지원 기간을 가집니다.
하지만 &lt;em&gt;LTS&lt;/em&gt; 의 경우에는 짧게는 5년 길게는 그 이상을 지원합니다.
때문에 많은 개발자들은 새로운 기능 빨리 적용하기 보단 상대적으로 안심하고 오랜 기간 사용할 수 있는 &lt;em&gt;LTS&lt;/em&gt; 버전을 선호하게 되었습니다.&lt;br&gt;
가장 많이 쓰이는 8 버전은 &lt;em&gt;Java&lt;/em&gt; 의 최초 &lt;em&gt;LTS&lt;/em&gt; 버전이며, &lt;em&gt;Java&lt;/em&gt; 개발사 &lt;em&gt;Sun Microsystems&lt;/em&gt; 가 &lt;em&gt;Oracle&lt;/em&gt; 에 인수되고 나서 출시되었습니다.
그 다음으로 많이 쓰이는 11 버전도 &lt;em&gt;LTS&lt;/em&gt; 버전이며, 8 버전 출시 4년 후에 나오게 되었습니다.
현재 나온 최신 버전인 16 버전은 &lt;em&gt;LTS&lt;/em&gt; 버전이 아니며, 다음 17 버전이 &lt;em&gt;LTS&lt;/em&gt; 로 예정이 되어 있습니다.&lt;/p&gt;
&lt;h2&gt;open JDK와 Oracle JDK, 다양한 JDK 버전들&lt;/h2&gt;
&lt;p&gt;EC2에 기본적인 개발환경을 모두 세팅하고 난 다음에, &lt;em&gt;CI &amp;#x26; CD&lt;/em&gt; 를 위하여 &lt;em&gt;Jenkins&lt;/em&gt; 를 사용하게 되었습니다.
이 때, &lt;em&gt;Jenkins&lt;/em&gt; 는 배포하는 서버와 다른 &lt;em&gt;CI &amp;#x26; CD&lt;/em&gt; 전용 서버에서 운영되기 때문에 또 다시 &lt;em&gt;JDK&lt;/em&gt; 를 설치를 해주어야했습니다.
하지만 &lt;em&gt;Jenkins&lt;/em&gt; 에서는 항상 보던 &lt;em&gt;open JDK&lt;/em&gt; 및 &lt;em&gt;default JDK&lt;/em&gt; 가 아닌 &lt;em&gt;Oracle JDK&lt;/em&gt; 를 권장하고 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1452px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4c717de7c052570dd1cc82db69bf3e33/52c6a/2021-07-12-jenkins-jdk.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 46.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABJ0AAASdAHeZh94AAABJUlEQVR42q2S3UoDMRCF9/1fQLzSJ1EUr7xuQbyoUEq1m+xm87PJJrPJcbJF1FJEwcDHhCScnDlJ44MHzTP+azSkNWgYzm6WUv5EZprRBvSHA7QeEKcJlAgppYX5D86rmBojmjpppYDsOgzsVjPWOVhr4L2vNhdKydXzF74PygVuIjTVqtMG8iDQMYPs4QwLOg9tRoRIiJSRKP9scbmnHAWj2MFt1whvG4z7Z5jdE6TYQsodlHpFr/bwrgWFDikIpkPJiaGlcsOfj1KFAx/q1QuUadlVixQ70CSZjyqRfIvoqxjPl7WBUZyzhd+sYR4fQEJwhjkjTAme+6e5YM7l938kH52Zm1u0F5fwq9Wx5XHkrEI4DeQk/HLuXy1lthby6hr27h7vsSO/MFdQuHcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;images&quot;
        title=&quot;&quot;
        src=&quot;/static/4c717de7c052570dd1cc82db69bf3e33/52c6a/2021-07-12-jenkins-jdk.png&quot;
        srcset=&quot;/static/4c717de7c052570dd1cc82db69bf3e33/0eb09/2021-07-12-jenkins-jdk.png 500w,
/static/4c717de7c052570dd1cc82db69bf3e33/1263b/2021-07-12-jenkins-jdk.png 1000w,
/static/4c717de7c052570dd1cc82db69bf3e33/52c6a/2021-07-12-jenkins-jdk.png 1452w&quot;
        sizes=&quot;(max-width: 1452px) 100vw, 1452px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Java&lt;/em&gt; 를 소유하고 있는 회사는 &lt;em&gt;Oracle&lt;/em&gt; 인건 인지하였지만, 기존의 사용하던 &lt;em&gt;open JDK&lt;/em&gt; 와는 다른 것을 알게 되었습니다.
&lt;em&gt;Oracle JDK&lt;/em&gt; 는 &lt;em&gt;Oracle&lt;/em&gt; 에서 관리하는 &lt;em&gt;JDK&lt;/em&gt; 입니다.
상업적으로 사용하려면 비용을 지불해야하지만, 상대적으로 지원 정도가 높습니다.
또한, &lt;em&gt;open JDK&lt;/em&gt; 에서 지원하지 않는 라이브러리를 가지고 있습니다.
&lt;em&gt;open JDK&lt;/em&gt; 는 오픈소스로 운영이 되며, 상업적으로 사용해도 무료입니다.
많은 &lt;em&gt;Linux&lt;/em&gt; 배포판에서 저장소에 기본으로 등록이 되어 있습니다.
&lt;em&gt;open JDK&lt;/em&gt; 는 상대적으로 지원 정도가 약하지만, 웹 개발에서는 큰 차이가 나지 않는다고 합니다.
이 때문에 많은 사용자들이 &lt;em&gt;open JDK&lt;/em&gt; 를 현재 사용하고 있습니다.&lt;/p&gt;
&lt;h2&gt;마치며&lt;/h2&gt;
&lt;p&gt;이번 포스팅에서는 &lt;em&gt;JAVA&lt;/em&gt; 를 구동시키는데 필수적인 &lt;em&gt;JVM&lt;/em&gt; , &lt;em&gt;JRE&lt;/em&gt; , &lt;em&gt;JDK&lt;/em&gt; 를 알아보았습니다.
그 중 &lt;em&gt;JVM&lt;/em&gt; 은 &lt;em&gt;JAVA&lt;/em&gt; 에서 &lt;em&gt;GC&lt;/em&gt; , 메모리 관리 등이 이뤄지는 핵심적인 요소입니다.
&lt;em&gt;JVM&lt;/em&gt; 의 구성 요소 및 작동 방식은 다음 포스팅에서 다루도록 하겠습니다.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.redhat.com/en/topics/cloud-native-apps/what-is-a-Java-runtime-environment&quot;&gt;Red Hat : What is a Java runtime environment(JRE)?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://openjdk.java.net/groups/core-libs/&quot;&gt;Core Libraries Group&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.12&quot;&gt;Oracle Chapter 2. The Structure of the Java Virtual Machine&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Database Transaction]]></title><description><![CDATA[트랜잭션 이란? SQL을 사용하여 INSERT나 UPDATE 명령을 사용할 때 COMMIT을 해주어야 실제 데이터베이스에 반영이 된다. 트랜잭션에 대하여 아직 깊게 공부하지 않은 사람이라면 ‘나는 COMMIT…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-11-database-transaction/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-11-database-transaction/</guid><pubDate>Sun, 11 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;트랜잭션 이란?&lt;/h2&gt;
&lt;p&gt;SQL을 사용하여 INSERT나 UPDATE 명령을 사용할 때 COMMIT을 해주어야 실제 데이터베이스에 반영이 된다. 트랜잭션에 대하여 아직 깊게 공부하지 않은 사람이라면 ‘나는 COMMIT 한 적이 없는데 데이터베이스에 반영이 되었는데?‘라고 생각할 수도 있다. 이는 자동 커밋이라는 기능이 동작했기 때문이다.&lt;/p&gt;
&lt;p&gt;그렇다면 트랜잭션이란 무엇일까? 트랜잭션은 데이터베이스 상태를 변환시키는 하나의 논리적 기능 수행 단위이다. 즉, 한 번에 수행되어야 할 일련의 연산을 말한다. 여기서 작업의 단위는 질의어 한 문장이 아니다. 질의어 명령문을 사람이 정하는 기준에 따라 정하는 것을 의미한다.&lt;/p&gt;
&lt;h2&gt;트랜잭션이 필요한 이유&lt;/h2&gt;
&lt;p&gt;회원의 등급을 업그레이드하는 작업을 진행한다고 생각해보자. 여러 명의 회원 등급을 업그레이드하는 도중에 시스템상의 문제로 작업이 마무리되지 못하였다. 이때 트랜잭션이 없었다면 어떤 회원의 등급은 업그레이드가 되고 어느 회원은 업그레이드가 되지 않는 문제가 발생하게 된다. 만약 이러한 일이 돈과 관련된 것이라면 더 심각한 문제가 될 수 있다. 트랜잭션으로 원하는 작업의 단위를 정하게 되면 작업을 수행하는 도중 문제가 발생하면 이전에 진행되었던 작업도 진행되지 않는다. 모든 작업을 안정적으로 끝마친 후에야 실제 데이터베이스에 반영이 되는 것이다. 즉, 회원 등급이 업그레이드가 돼야 할 회원 모두가 업그레이드가 되거나 모두 업그레이드가 안 된다는 뜻이다.&lt;/p&gt;
&lt;h2&gt;트랜잭션의 성질&lt;/h2&gt;
&lt;p&gt;위에서 봤던 트랜잭션의 성질을 흔히 ACID 로 표현한다. Atomicity(원자성), Consistency(일관성), Isolation(독립성), Durability(영속성)의 앞글자를 따 ACID로 트랜잭션의 성질을 표현한다. 그럼 ACID에 대하여 살펴보자&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Atomicity(원자성)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;하나의 트랜잭션이 더 작게 나눌 수 없는 최소의 단위라는 뜻이다. 트랜잭션이 모두 반영되거나, 아니면 전혀 반영되지 않아야 하는 특징을 나타낸다. 계좌이체를 하는 경우를 생각해보자. 송금하는 도중에 문제가 발생하여 돈을 받아야 하는 사람에게 제대로 전달되지 않았다. 하지만 돈을 보내는 사람에게서는 이미 돈이 빠져나갔다면 큰 문제가 발생하게 될 것이다. 계좌이체 도중 문제가 발생하게 되면 송금하기 이전상태를 유지하게 되는 것을 원자성이라 볼 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consistency(일관성)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;일관성은 트랜잭션이 완료된 결괏값이 일관적인 데이터베이스 상태를 유지하는 것을 말한다. 사실 이 말만으로는 잘 이해가 되지 않는다. 예를 들면, 고객 정보가 담겨있는 데이터베이스에 새로운 고객이 등록되면 그 데이터베이스를 참조하는 다른 하위 계층의 데이터베이스도 같은 고객의 세부 정보를 가져와야 한다. 또는 모든 계좌의 돈은 0원 이상이어야 한다. 라는 무결성 제약이 있다면 이를 위반하는 트랜잭션은 모두 중단되어야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Isolation(독립성)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;트랜잭션이 수행되고 있을 때, 다른 트랜잭션의 연산작업이 중간에 끼어들어 기존 작업에 영향을 주지 못하도록 하는 것을 말한다. 독립성이 보장된다면 계좌 이체작업을 진행하고 있는 도중에 계좌의 잔액을 조회한다 거나 하는 작업을 동시에 수행할 수 없게 되는 것이다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Durability(지속성)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;트랜잭션을 성공한 후 데이터베이스에 반영된 것은 영원히 반영되어야 한다는 것을 의미한다. 시스템에 문제가 발생하거나 종료되더라도 데이터베이스에 반영된 값은 그대로 유지되어야 한다. 은행에 시스템이 마비되었다가 재개되었을 때 계좌의 금액이 바뀌게 된다면 큰 문제가 될 것이다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;롤백과 커밋&lt;/h2&gt;
&lt;p&gt;트랜잭션을 사용해 데이터를 추가하다 에러가 발생하게 되면 어떻게 처리하게 될까? 이때 트랜잭션을 롤백하여 종료할 수 있다. 롤백을 진행하면 트랜잭션 내에서 행해진 모든 변경사항이 없었던 일이 된다. 트랜잭션 도중에 아무런 에러가 발생하지 않는다면 커밋을 하게 된다. 그러면 트랜잭션의 모든 변경사항이 데이터베이스에 반영되게 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;롤백(Rollback)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;하나의 트랜잭션 처리가 비정상적으로 종료되면 트랜잭션의 원자성을 위해 해당 트랜잭션에서 작업한 모든 연산을 취소하여 실제 데이터베이스에 반영하지 않는다. 롤백하고 난 후에는 해당 트랜잭션을 재시작하거나 폐기하여야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;커밋(Commit)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;하나의 트랜잭션 작업이 정상적으로 끝마쳤을 때 커밋을 하게 된다. 커밋을 해야만 진짜 데이터베이스에 변경사항이 반영되는 것이다. 하나의 트랜잭션이 정상적으로 끝났다는 것을 알려주는 연산이다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;트랜잭션의 상태&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Active(활동 상태)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;트랜잭션이 수행을 시작하여 현재 수행 중인 상태&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Partially Committed(부분 완료 상태)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;마지막 연산이 실행된 직후의 상태. 연산은 모두 처리했지만, 데이터베이스에 반영되지 않았다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Commited(완료 상태)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;트랜잭션이 성공적으로 완료되어 Commit을 한 상태&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Failed(실패 상태)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;장애가 발생하여 트랜잭션이 중단된 상태&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Aborted(철회 상태)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;수행에 실패하여 Rollback 연산을 실행한 상태&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://coding-factory.tistory.com/226&quot;&gt;https://coding-factory.tistory.com/226&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mommoo.tistory.com/62&quot;&gt;https://mommoo.tistory.com/62&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://devuna.tistory.com/30&quot;&gt;https://devuna.tistory.com/30&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;SQL 첫걸음&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[사용자 경험 개선 1편 - react suspense]]></title><description><![CDATA[📜 읽기 전에… 🎈 본문에 등장하는 문제에 대한 예시는 suspense를 사용하지 않고도 해결할 수 있습니다.
하지만 앱이 커지고 복잡해짐에 따라 각 문제를 어떻게 으로 봐주시길 바랍니다. 🎈 2021.07.1…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-11-suspense/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-11-suspense/</guid><pubDate>Sun, 11 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;📜 읽기 전에…&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;🎈 본문에 등장하는 문제에 대한 예시는 suspense를 사용하지 않고도 해결할 수 있습니다.
하지만 앱이 커지고 복잡해짐에 따라 각 문제를 어떻게 &lt;code class=&quot;language-text&quot;&gt;선언적으로 손쉽게 해결할 수 있느냐에 대한 관점&lt;/code&gt;으로 봐주시길 바랍니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🎈 2021.07.11 기준 시험적인 기능입니다. 현재 정식 버전에서 suspense는 code splitting으로만 사용되고 있습니다. 유의해주세요.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🎈 본문은 &lt;a href=&quot;https://ko.reactjs.org/docs/concurrent-mode-suspense.html#what-suspense-lets-you-do&quot;&gt;리액트 공식 문서&lt;/a&gt;를 참고하여 재구성하였음을 알립니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;🙋‍♂️ suspense가 뭐죠?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;혹시 웹페이지에 로딩 화면을 만들어 본 적이 있나요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;대부분 사용자에게 무언가 진행되고 있음을 알리기 위해서 로딩 화면을 띄워본 경험이 있을 것이다.
react suspense는 이러한 로딩 화면과 연관이 있으며 다음과 같이 정의할 수 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;특정 컴포넌트에서 사용되고 있는 데이터의 준비가 아직 끝나지 않았음을
react에 알릴 수 있으며 &lt;code class=&quot;language-text&quot;&gt;data fetching 라이브러리&lt;/code&gt;와 함께 사용할 수 있는 구조.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;위의 정의에서 &lt;code class=&quot;language-text&quot;&gt;데이터의 준비가 끝나지 않았음을 알린다&lt;/code&gt;라는 것은 해당 데이터를 사용하는 컴포넌트를
렌더링하지 않고 다른 로딩 화면을 보여줄 수 있음을 의미한다.&lt;/p&gt;
&lt;p&gt;여기서 누군가는 의아할지도 모른다.
&lt;code class=&quot;language-text&quot;&gt;지금까지 suspense 없이도 로딩 화면을 잘 만들어 왔는데?&lt;/code&gt; 라고 할지도 모른다.
그렇다면 지금까지 만들어왔던 방식보다 suspense가 왜 좋은지 같이 알아보도록 하자.&lt;/p&gt;
&lt;h2&gt;👀 왜 사용하는 건가요?&lt;/h2&gt;
&lt;p&gt;앞서 설명한 것처럼 suspense는 &lt;code class=&quot;language-text&quot;&gt;data fetching 라이브러리&lt;/code&gt;와 함께 사용하면 효과적인 구조다.
그렇다면 data fetching 라이브러리를 사용했을 때, 기존 방식보다 좋은 이유는 무엇인지부터 우선으로 알아보자.&lt;/p&gt;
&lt;h3&gt;data fetching 라이브러리의 역할&lt;/h3&gt;
&lt;p&gt;fetching 라이브러리는 &lt;code class=&quot;language-text&quot;&gt;워터폴(waterfall)&lt;/code&gt; 현상을 막아준다.
워터폴이라고 하면 이전 개발 과정이 끝나야 다음 과정을 진행할 수 있는 프로젝트 방법론을 떠올리는 사람이 많을 것이다.
하지만 fetching 라이브러리에서의 워터폴 현상은 이전 fetch 요청에 대한 응답이 도착해야
다음 fetch 요청을 보낼 수 있는 구조를 의미한다.
예를 들어 다음과 같은 상황이 있다고 생각해보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;컴포넌트 1에서 데이터 1을 요청… 가져오는 동안 로딩 화면만을 렌더링(3초 소요)&lt;/li&gt;
&lt;li&gt;컴포넌트 1에서 데이터 1의 응답을 받고 컴포넌트 2를 렌더링&lt;/li&gt;
&lt;li&gt;컴포넌트 2에서 데이터 2를 요청… 가져오는 동안 로딩 화면만을 렌더링(2초 소요)&lt;/li&gt;
&lt;li&gt;컴포넌트 2에서 데이터 2의 응답을 받고 컴포넌트 3을 렌더링&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 상황에서 무조건 데이터 1에 대한 응답을 받고 나서야 데이터 2에 대한 요청이 실행된다.
데이터 2에 대한 요청 자체는 2초만 소요됨에도 불구하고 데이터 1에 대한 요청 때문에
3초를 무조건 기다려야 하는 문제가 발생하는 것이다. &lt;a href=&quot;https://codesandbox.io/s/fragrant-glade-8huj6&quot;&gt;참고 코드&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이러한 문제는 &lt;code class=&quot;language-text&quot;&gt;컴포넌트 렌더링 -&gt; data fetching 요청 -&gt; data 응답&lt;/code&gt;과 같이 동작하는 구조 때문에 일어난다.
이 문제를 &lt;code class=&quot;language-text&quot;&gt;data fetching 라이브러리&lt;/code&gt;는 컴포넌트 트리 구조에 필요한 모든 data fetching 요청을 렌더링 이전에
실행하도록 중앙화하여 해결한다. 즉 &lt;code class=&quot;language-text&quot;&gt;data fetching 요청 -&gt; data 응답 -&gt; 컴포넌트 렌더링&lt;/code&gt;의 구조로 바뀌는 것이다.&lt;/p&gt;
&lt;p&gt;이렇게 구조가 바뀌면 data fetching 요청이 컴포넌트 렌더링에 의존되지 않고 모두 한 번에 실행되므로
워터폴 현상을 막을 수 있다.&lt;/p&gt;
&lt;p&gt;그렇다면 data fetching 라이브러리만으로 모든 문제가 해결될까? 그건 아니었다.
컴포넌트에 필요한 모든 data 응답을 받을 때까지 해당 컴포넌트는 물론이고 하위 컴포넌트들도 렌더링할 수 없다는 점이 문제였다. 또한 하위 컴포넌트들의 로딩도 의미가 없어져 버렸다. 하위 컴포넌트가 렌더링 된 시점에서는 상위 컴포넌트에 의해 이미 모든 데이터를 응답받은 상태이기 때문에 하위 컴포넌트의 로딩은 절대 렌더링 되지 않을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://codesandbox.io/s/wandering-morning-ev6r0&quot;&gt;참고코드&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;사실 위 참고 코드에서는 &lt;code class=&quot;language-text&quot;&gt;Promise.all()&lt;/code&gt;을 사용하고 있는데 굳이 Promise.all()을 사용하지 않고
따로 fetching 요청을 나누어도 이 문제에 대한 해결이 가능하다. 하지만 이렇게 따로 나누는 방식은
데이터와 컴포넌트 트리가 복잡해짐에 따라 점점 어려워진다. 개발자들은 좀 더 간단한 방식을 원했다.&lt;/p&gt;
&lt;p&gt;여기서 suspense가 등장한다.&lt;/p&gt;
&lt;h3&gt;suspense의 역할&lt;/h3&gt;
&lt;p&gt;suspense의 역할은 크게 2가지로 나눌 수 있다. 하나씩 알아보자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;✨ 1. 모든 요청을 기다리지 않고도 화면을 렌더링할 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;앞서 fetching 라이브러리만을 사용했을 때 구조를 다시 보자.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;data fetching 요청 -&gt; data 응답 -&gt; 컴포넌트 렌더링&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이 과정을 좀 더 자세히 들여다 보면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;data fetching 요청 -&gt; 로딩중 UI 렌더링 -&gt; data 응답 -&gt; 컴포넌트에 응답 반영&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이러한 구조에서 suspense는 요청 직후 &lt;code class=&quot;language-text&quot;&gt;요청 리소스&lt;/code&gt;를 바로 컴포넌트로 주입하는 방식으로 바꿔준다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;data fetching 요청 -&gt; suspense 하위의 컴포넌트에 요청 리소스를 반영 -&gt; suspense에 의해 로딩 UI 렌더 -&gt; 요청 리소스로 data 응답이 들어옴 -&gt; 컴포넌트에 응답 반영&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;여기서 말하는 요청 리소스는 &lt;code class=&quot;language-text&quot;&gt;Promise의 형태가 아니다.&lt;/code&gt;
data fetching 라이브러리 내부적으로 구현되어있는 일반 객체인데
fetch API로 모방한 형태가 궁금하다면 &lt;a href=&quot;https://codesandbox.io/s/trusting-clarke-8twuq?file=/src/fakeApi.js&quot;&gt;여기&lt;/a&gt;를 참고하자.&lt;/p&gt;
&lt;p&gt;이런 구조로 바뀌면 fetching 라이브러리만 사용했을 때 모든 data 응답을 기다려야 컴포넌트 트리를
렌더링할 수 있었던 문제를 해결할 수 있다. fetching 요청 직후 응답 도착 여부와는 상관없이 렌더링을 수행하기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;✨ 2. 경쟁 상태 발생을 방지한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;suspense는 data fetching에 의한 &lt;code class=&quot;language-text&quot;&gt;경쟁 상태(Race Condition)&lt;/code&gt; 발생을 방지한다.&lt;/p&gt;
&lt;p&gt;우선 경쟁 상태라는 용어부터 알아보자.
&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EA%B2%BD%EC%9F%81_%EC%83%81%ED%83%9C&quot;&gt;위키백과&lt;/a&gt;에서는 이렇게 설명하고 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;공유 자원&lt;/code&gt;에 대해 여러 개의 프로세스가 &lt;code class=&quot;language-text&quot;&gt;동시에 접근&lt;/code&gt;을 시도할 때 접근의 타이밍이나 순서 등이 결과값에 영향을 줄 수 있는 상태&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;그렇다면 javascript 환경에서 경쟁 상태란 무엇일까?
여러 개의 비동기 작업(fetching response)의 결과가 하나의 DOM 객체에 반영되는 상황이 있을 것이다.&lt;/p&gt;
&lt;p&gt;예를 들어 다음과 같은 상황을 가정해보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;미키, 주모, 심바, 파노, 카일 5개의 버튼이 있고 각 버튼을 누르면 각 크루에 대한 프로필을 서버로 요청한다.&lt;/li&gt;
&lt;li&gt;프로필 요청 응답을 앱이 받으면 해당 정보를 &lt;Profile /&gt; 컴포넌트에 업데이트한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/suspense-race-condition-2b9jr?fontsize=14&amp;hidenavigation=1&amp;theme=dark&quot;
     style=&quot;width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;&quot;
     title=&quot;suspense-race-condition&quot;
     allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot;
     sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;
   &gt;&lt;/iframe&gt;
&lt;p&gt;여기서 여러 버튼을 빠른 속도로 누른다고 생각해보자. 과연 마지막에 누른 버튼과 &lt;Profile /&gt; 컴포넌트의 정보는
일치할까? 아마 마지막으로 누른 버튼이 미키 버튼이라고 해도 주모의 정보가 표시될 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;이러한 문제는 왜 발생할까? 그 이유는 버튼을 누른 순서대로 프로필 요청에 대한 응답이 도착하고
순서대로 &lt;Profile /&gt;에 반영될 것이라는 잘못된 가정에서 발생한다.
A 요청이 B 요청보다 먼저 수행되었다고 해서 무조건 A 요청에 대한 응답이 먼저 도착하는 것은 아니기 때문에 해당 가정은 잘못되었다.&lt;/p&gt;
&lt;p&gt;그렇다면 이 문제를 suspense는 어떻게 해결할 수 있을까.
suspense는 state 설정 시기를 바꾸어 이를 해결한다.&lt;/p&gt;
&lt;p&gt;이전의 코드는 &lt;code class=&quot;language-text&quot;&gt;A 프로필 요청 -&gt; 로딩 UI 렌더 -&gt; A 프로필 응답 -&gt; &amp;lt;Profile /&gt;에 응답 반영&lt;/code&gt;의 순서였다면
suspense는 이 과정을 &lt;code class=&quot;language-text&quot;&gt;A 프로필 요청 -&gt; &amp;lt;Profile /&gt;에 A 프로필 요청 리소스 반영 -&gt; suspense에 의해 A 요청에 대한 로딩 UI 렌더 -&gt; 요청 리소스로 A 프로필 응답이 들어옴 -&gt; &amp;lt;Profile /&gt;에 응답 반영&lt;/code&gt;으로 바꾼다.&lt;/p&gt;
&lt;p&gt;경쟁 상태를 이런 방식으로 해결 가능한 이유는
suspense가 응답이 언제 오는지, 시간에 대한 것을 고려하지 않아도 되기 때문이다.
프로필을 요청함과 동시에 해당 요청 리소스를 &lt;Profile /&gt;에 반영하기 때문에
이전에 수행하고 있던 요청이 있더라도 해당 요청은 무시하고 새로운 요청으로 대체된다.&lt;/p&gt;
&lt;p&gt;suspense는 이를 통해 경쟁 상태를 해결할 수 있다.&lt;/p&gt;
&lt;h2&gt;🔍 suspense와 함께 사용되는 data fetching 라이브러리&lt;/h2&gt;
&lt;p&gt;거의 무조건 data fetching 라이브러리와 함께 사용하기 때문에 fetching 라이브러리와 suspense를 함께 사용하는 예시를 몇 가지 보도록 하자.&lt;/p&gt;
&lt;h3&gt;Relay&lt;/h3&gt;
&lt;p&gt;graphQL에 의존적인 data fetching 라이브러리다.&lt;/p&gt;
&lt;p&gt;페이스북에서는 suspense를 Relay와 함께 실무에서 사용하고 있다고 한다.
그래서 Relay 외의 다른 라이브러리와 suspense를 사용했을 때 원활히 동작할지 장담할 수 없으므로
만약 suspense를 실무에서 사용하고 싶다면 페이스북은 Relay와 함께 사용할 것을 추천하고 있다.&lt;/p&gt;
&lt;p&gt;만약 graphQL에 익숙하고 suspense 기능을 실무에서 사용해야 한다면 &lt;a href=&quot;https://relay.dev/docs/getting-started/step-by-step-guide/&quot;&gt;Relay&lt;/a&gt;를 쓰는 것이 적합할 것이다.&lt;/p&gt;
&lt;h3&gt;SWR&lt;/h3&gt;
&lt;p&gt;Data Fetching을 위한 react 훅 라이브러리인 SWR도 시험적으로 suspense를 지원하고 있다.
간략하게 코드를 확인해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;import { Suspense } from &apos;react&apos;
import useSWR from &apos;swr&apos;

function Profile () {
  const { data } = useSWR(&apos;/api/user&apos;, fetcher, { suspense: true })
  return &amp;lt;div&gt;hello, {data.name}&amp;lt;/div&gt;
}

function App () {
  return (
    &amp;lt;Suspense fallback={&amp;lt;div&gt;loading...&amp;lt;/div&gt;}&gt;
      &amp;lt;Profile/&gt;
    &amp;lt;/Suspense&gt;
  )
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;useSWR의 세 번째 인자로 &lt;code class=&quot;language-text&quot;&gt;suspense: true&lt;/code&gt;를 전달함으로써 suspense 옵션을 활성화할 수 있다.
이런 간단한 설정으로 suspense 사용이 가능하다.&lt;/p&gt;
&lt;h3&gt;그 외 suspense를 지원하는 라이브러리들&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://react-query.tanstack.com/guides/suspense&quot;&gt;React Query&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://recoiljs.org/ko/docs/guides/asynchronous-data-queries/#asynchronous-example-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%98%88%EC%A0%9C&quot;&gt;Recoil&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;💕 마무리&lt;/h2&gt;
&lt;p&gt;사용자 경험 개선에 대한 주제로 아직 시험적인 기능이지만 React 18에서 추가될 기능인
suspense에 대해서 알아보았다. 공부를 하다 보니 비동기 동작이 얼마나 사용자 경험에 지대한 영향을 미칠 수 있는가에
대해 새로운 인사이트를 얻을 수 있었다. 생각해보면 사용자가 기다리는 대부분의 시간은
data fetching과 관련이 있다. suspense를 통해 이 과정을 좀 더 효율적으로 처리할 수 있다면
좀 더 쾌적한 경험을 사용자에게 줄 수 있을 것이다. 다음 2편에서는 이를 좀 더 개선할 수 있는
concurrent mode에 대해서 알아볼 예정이다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.reactjs.org/docs/concurrent-mode-suspense.html&quot;&gt;리액트 공식 문서 - suspense&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jbee.io/react/error-declarative-handling-1/&quot;&gt;React에서 선언적으로 비동기 다루기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/reactwg/react-18/discussions/4&quot;&gt;리액트 18 소개&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[OAuth 개념 및 동작 방식 이해하기]]></title><description><![CDATA[1. OAuth란? image 웹 서핑을 하다 보면 Google과 Facebook 및 Twitter…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-10-understanding-oauth/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-10-understanding-oauth/</guid><pubDate>Sat, 10 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. OAuth란?&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/125554097-e3b5f9ff-0cda-4328-a673-03b817166aa2.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;웹 서핑을 하다 보면 Google과 Facebook 및 Twitter 등의 외부 소셜 계정을 기반으로 간편히 회원가입 및 로그인할 수 있는 웹 어플리케이션을 쉽게 찾아볼 수 있습니다. 클릭 한 번으로 간편하게 로그인할 수 있을 뿐만 아니라, 연동되는 외부 웹 어플리케이션에서 Facebook 및 Twitter 등이 제공하는 기능을 간편하게 사용할 수 있다는 장점이 있습니다. 예를 들어, 외부 웹 어플리케이션에 Google로 로그인하면 API를 통해 연동된 계정의 Google Calendar 정보를 가져와 사용자에게 보여줄 수 있습니다.&lt;/p&gt;
&lt;p&gt;이 때 사용되는 프로토콜이 OAuth입니다. OAuth에 대한 정의는 다음과 같습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;OAuth는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는, 접근 위임을 위한 개방형 표준이다. (위키백과)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;위 사진을 예시로 들어보겠습니다. 외부 어플리케이션(원티드)은 사용자 인증을 위해 Facebook과 Apple 및 Google의 등의 사용자 인증 방식을 사용합니다. 이 때, OAuth를 바탕으로 제 3자 서비스(원티드)는 외부 서비스(Facebook, Apple, Google)의 특정 자원을 접근 및 사용할 수 있는 권한을 인가받게 됩니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;2. OAuth 참여자&lt;/h2&gt;
&lt;p&gt;OAuth 동작에 관여하는 참여자는 크게 세 가지로 구분할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Resource Server : Client가 제어하고자 하는 자원을 보유하고 있는 서버입니다.
&lt;ul&gt;
&lt;li&gt;Facebook, Google, Twitter 등이 이에 속합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Resource Owner : 자원의 소유자입니다.
&lt;ul&gt;
&lt;li&gt;Client가 제공하는 서비스를 통해 로그인하는 실제 유저가 이에 속합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Client : Resoure Server에 접속해서 정보를 가져오고자 하는 클라이언트(웹 어플리케이션)입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2&gt;3. OAuth Flow&lt;/h2&gt;
&lt;p&gt;간단한 웹 어플리케이션 제작을 통해 OAuth의 동작 프로세스를 이해해봅시다. Github 계정을 바탕으로 웹 어플리케이션에서 로그인하고, Github에서 제공하는 여러 API 기능을 외부 웹 어플리케이션에서 사용해보겠습니다.&lt;/p&gt;
&lt;h3&gt;3.1. Client 등록&lt;/h3&gt;
&lt;p&gt;Client(웹 어플리케이션)가 Resource Server를 이용하기 위해서는 자신의 서비스를 등록함으로써 사전 승인을 받아야 합니다. &lt;a href=&quot;https://github.com/settings/developers&quot;&gt;Github Developer Settings&lt;/a&gt;에서 Github에 웹 어플리케이션을 등록합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/125647361-74fbafb3-ced0-4be0-b79a-0b362cf0aeda.png&quot; alt=&quot;image&quot;&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/125647525-70ca3fcd-55ab-4f81-acb5-c04b071c8d1f.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;등록 절차를 통해 세 가지 정보를 부여받습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Client ID&lt;/strong&gt; : 클라이언트 웹 어플리케이션을 구별할 수 있는 식별자이며, 노출이 무방합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client Secret&lt;/strong&gt; : Client ID에 대한 비밀키로서, 절대 노출해서는 안 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authorized redirect URL&lt;/strong&gt; : Authorization Code를 전달받을 리다이렉트 주소입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Google 등 외부 서비스를 통해 인증을 마치면 클라이언트를 명시된 주소로 리다이렉트 시키는데, 이 때 Query String으로 특별한 Code가 함께 전달됩니다. 클라이언트는 해당 Code와 Client ID 및 Client Secret을 Resource Server에 보내, Resource Server의 자원을 사용할 수 있는 Access Token을 발급 받습니다. 등록되지 않은 리다이렉트 URL을 사용하는 경우, Resource Server가 인증을 거부합니다.&lt;/p&gt;
&lt;h3&gt;3.2. Resource Owner의 승인&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#web-application-flow&quot;&gt;Github Docs&lt;/a&gt;에서는 Github 소셜 로그인을 하기 위해 다음과 같은 주소로 GET 요청과 필요한 파라미터들을 보내도록 명시합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;GET https://github.com/login/oauth/authorize?client_id={client_id}&amp;amp;redirect_uri={redirect_uri}?scope={scope}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;scope는 Client가 Resource Server로부터 인가받을 권한의 범위입니다.&lt;/li&gt;
&lt;li&gt;파라미터의 세부 옵션에 대한 내용은 문서를 참고하시길 바랍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Resource Owner는 Client의 웹 어플리케이션을 이용하다가, 해당 주소로 연결되는 소셜 로그인 버튼을 클릭합니다.&lt;/p&gt;
&lt;img width=&quot;519&quot; alt=&quot;스크린샷 2021-07-15 오전 12 29 44&quot; src=&quot;https://user-images.githubusercontent.com/56240505/125652924-996597a9-10df-498b-a985-535b7e853bff.png&quot;&gt;
&lt;p&gt;Resource Owner는 Resource Server에 접속하여 로그인을 수행합니다. 로그인이 완료되면 Resource Server는 Query String으로 넘어온 파라미터들을 통해 Client를 검사합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;파라미터로 전달된 Client ID와 동일한 ID 값이 존재하는지 확인합니다.&lt;/li&gt;
&lt;li&gt;해당 Client ID에 해당하는 Redirect URL이 파라미터로 전달된 Redirect URL과 같은지 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;img width=&quot;601&quot; alt=&quot;스크린샷 2021-07-15 오전 12 31 48&quot; src=&quot;https://user-images.githubusercontent.com/56240505/125652996-386acd70-938e-448b-8bb0-cb07c3079662.png&quot;&gt;
&lt;p&gt;검증이 마무리 되면 Resource Server는 Resource Owner에게 다음과 같은 질의를 보냅니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;명시한 scope에 해당하는 권한을 Client에게 정말로 부여할 것인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;허용한다면 최종적으로 Resource Owner가 Resource Server에게 Client의 접근을 승인하게 됩니다.&lt;/p&gt;
&lt;h3&gt;3.3. Resource Server의 승인&lt;/h3&gt;
&lt;p&gt;Resource Owner의 승인이 마무리 되면 명시된 Redirect URL로 클라이언트를 리다이렉트 시킵니다. 이 때 Resource Server는 Client가 자신의 자원을 사용할 수 있는 Access Token을 발급하기 전에, 임시 암호인 Authorization Code를 함께 발급합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/125654215-c8d9162e-0603-4395-906b-c802f6752e92.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Query String으로 들어온 code가 바로 Authorization Code입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;OAuthController.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/afterlogin&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;afterlogin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestParam&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalStateException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Github 로그인이 실패했습니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;RestTemplate&lt;/span&gt; restTemplate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RestTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;OauthDto&lt;/span&gt; oauthDto &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OauthDto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    oauthDto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    oauthDto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setClient_ID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;7e930566ecaa306c71b5&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    oauthDto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setClient_secret&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;client secret 입력&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; accessToken &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; restTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postForObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://github.com/login/oauth/access_token&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; oauthDto&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;accessToken&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Client는 ID와 비밀키 및 code를 Resource Owner를 거치지 않고 Resource Server에 직접 전달합니다. Resource Server는 정보를 검사한 다음, 유효한 요청이라면 Access Token을 발급하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/125655418-feead64e-79d8-4421-97ed-ef8b8e3ca288.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;Client는 해당 토큰을 서버에 저장해두고, Resource Server의 자원을 사용하기 위한 API 호출시 해당 토큰을 헤더에 담아 보냅니다.&lt;/p&gt;
&lt;h3&gt;3.4. API 호출&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/125656088-596908d9-03cc-4d45-b40b-8ffe3529ddef.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;이후 Access Token을 헤더에 담아 Github API를 호출하면, 해당 계정과 연동된 Resource Server의 풍부한 자원 및 기능들을 내가 만든 웹 어플리케이션에서 사용할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;3.5. Refresh Token&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Refresh Token의 발급 여부와 방법 및 갱신 주기 등은 OAuth를 제공하는 Resource Server마다 상이합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Access Token은 만료 기간이 있으며, 만료된 Access Token으로 API를 요청하면 401 에러가 발생합니다. Access Token이 만료되어 재발급받을 때마다 서비스 이용자가 재 로그인하는 것은 다소 번거롭습니다.&lt;/p&gt;
&lt;p&gt;보통 Resource Server는 Access Token을 발급할 때 Refresh Token을 함께 발급합니다. Client는 두 Token을 모두 저장해두고, Resource Server의 API를 호출할 때는 Access Token을 사용합니다. Access Token이 만료되어 401 에러가 발생하면, Client는 보관 중이던 Refresh Token을 보내 새로운 Access Token을 발급받게 됩니다.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#web-application-flow&quot;&gt;Github Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://opentutorials.org/course/3405&quot;&gt;생활코딩&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://curity.io/resources/oauth/&quot;&gt;Curity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@undefcat/OAuth-2.0-%EA%B0%84%EB%8B%A8%EC%A0%95%EB%A6%AC&quot;&gt;[OAuth 2.0] OAuth란?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/OAuth&quot;&gt;위키백과&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[웹팩 이해하기 - 2]]></title><description><![CDATA[지난 글에서는 프론트엔드 개발 환경 설정을 위해 가장 많이 사용되는 웹팩의 정의와 필요성에 대해 알아보았다. (*웹팩 이해하기 -…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-10-webpack-exercise/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-10-webpack-exercise/</guid><pubDate>Sat, 10 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;지난 글에서는 프론트엔드 개발 환경 설정을 위해 가장 많이 사용되는 웹팩의 정의와 필요성에 대해 알아보았다. (&lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-06-30-webpack-intro/&quot;&gt;*웹팩 이해하기 - 1&lt;/a&gt; )&lt;/p&gt;
&lt;p&gt;내용을 간략하게 정리하자면 웹팩은 애플리케이션에 필요한 모든 파일(모듈)을 병합하고 압축해서 하나의 결과물(번들)을 생성해주는 도구로써, 애플리케이션의 성능 및 로드 시간을 줄여주는 역할을 한다고 하였다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 웹팩의 핵심 요소인 Entry, Output, Loaders, Plugins 에 대한 개념을 알아보고, 간단하게 웹팩을 설정 해보려고 한다.&lt;/p&gt;
&lt;h2&gt;웹팩 설치&lt;/h2&gt;
&lt;p&gt;먼저 webpack과 webpack-cli를 설치해준다. webpack-cli는 webpack과 관련된 명령어를 사용할 수 있게 해주는 패키지이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$ npm i webpack webpack-cli -D&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;webpack을 설정하기 위해 root 폴더에 webpack.config.js 파일을 만든다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 webpack.config.js 파일을 설정하면서 Entry, Output, Loaders, Plugins에 대해 알아보자.&lt;/p&gt;
&lt;h2&gt;웹팩의 핵심 요소&lt;/h2&gt;
&lt;p&gt;웹팩 공식문서에서 웹팩의 파일 변환 과정을 이해하기 위해서는 Entry, Output, Loaders, Plugins 4가지 개념에 대해서 이해해야 한다고 말하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;엔트리 (Entry)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이제 웹팩이 모듈 번들러라는 건 알았다. 그렇다면 웹팩은 어떻게 애플리케이션에 필요한 모든 모듈을 찾는 것일까? 바로 엔트리를 통해서다.&lt;/p&gt;
&lt;p&gt;엔트리는 번들링을 시작하기 위한 최초 진입점이다. 웹팩은 이 진입점으로부터 의존적인 모듈을 전부 찾아낸다. 이때 모듈 간의 의존 관계로 생기는 구조를 디펜던시 그래프(Dependency Graph)*라고 한다.&lt;/p&gt;
&lt;p&gt;* 웹팩은 특정 지점에서 출발해서, 애플리케이션에 필요한 모든 모듈을 포함하는 그래프를 재귀적으로 완성해 나간다. 한 파일이 다른 파일을 필요로 하면 이를 ‘디펜던시(dependency)‘가 있다고 해석하는데, 이 방식으로 웹팩은 이미지 또는 웹 글꼴과 같이 코드가 아닌 리소스도 디펜던시로 관리할 수 있게 된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./src/index.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// or&lt;/span&gt;

    &lt;span class=&quot;token literal-property property&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;./src/index.js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// or&lt;/span&gt;

    &lt;span class=&quot;token literal-property property&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;bundle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./src/index.js&apos;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;html에서 사용할 자바스크립트의 진입점을 &lt;code class=&quot;language-text&quot;&gt;entry&lt;/code&gt; 에 넣어준다. &lt;code class=&quot;language-text&quot;&gt;entry&lt;/code&gt; 가 여러개일 경우 배열로 적어도 된다. &lt;code class=&quot;language-text&quot;&gt;entry&lt;/code&gt; 의 이름을 따로 지정해주고 싶다면 object 로 작성 할 수 도 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;아웃풋 (Output)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;엔트리를 시작으로 의존되어 있는 모든 모듈을 하나로 묶어 하나의 결과물로 만들었다. 이 결과물이 위치하는 경로를 아웃풋이라고 한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;path&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./dist&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;bundle.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// or&lt;/span&gt;

    &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;dist&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;[name].js&apos;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;path&lt;/code&gt; 는 번들링된 파일을 저장할 경로이고, &lt;code class=&quot;language-text&quot;&gt;filename&lt;/code&gt; 은 번들링된 파일의 이름이다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;path&lt;/code&gt; 에는 경로를 직접 입력할 수 있지만, 아래와 같이 많이 사용하는 것 같다. &lt;code class=&quot;language-text&quot;&gt;__dirname&lt;/code&gt; 은 노드에서 제공하는 현재 디렉토리의 경로에 대한 변수이다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;filename&lt;/code&gt; 도 직접 입력 할 수 있지만, 아래와 같이 입력하면 &lt;code class=&quot;language-text&quot;&gt;entry&lt;/code&gt; 에서 따로 이름을 정해주었다면 그 이름으로 생성된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;로더 (Loader)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;웹팩은 모든 파일을 모듈로 본다. 하지만 웹팩은 자바스크립트 밖에 읽지 못한다. 그래서 HTML, CSS, Images, 폰트 등을 웹팩이 읽을 수 있게 변환해줘야 하는데, 이 역할을 하는 게 바로 로더이다.&lt;/p&gt;
&lt;p&gt;예시로 CSS를 자바스크립트에서 불러와 사용할 수 있도록 로더를 설정해보자.&lt;/p&gt;
&lt;p&gt;CSS를 번들링하기 위해서는 css-loader 와 style-loader 를 함께 사용한다. css-loader 는 CSS를 자바스크립트 코드로만 변경해주기만 한다. 자바스크립트로 변경된 CSS를 돔에 추가하기 위해서 style-loader 가 필요하다.&lt;/p&gt;
&lt;p&gt;먼저 css-loader와 style-loader 를 설치해준다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$ npm install css-loader style-loader -D&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그 다음 css-loader 와 style-loader 에 관련된 설정을 추가해준다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;\.css$&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;style-loader&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;css-loader&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;test&lt;/code&gt;는 로더를 적용할 파일 형식으로 일반적으로 정규 표현식 사용한다. &lt;code class=&quot;language-text&quot;&gt;use&lt;/code&gt;는 해당 파일을 처리할 로더의 이름이다.&lt;/p&gt;
&lt;p&gt;배열로 설정하면 뒤에서부터 앞으로 순서대로 로더가 동작한다. 이제 웹팩은 .css 확장자로 끝나는 모듈을 읽어 들여 css-loader를 적용하고 그 다음 style-loader를 적용한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;플러그인 (Plugin)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;마지막으로 알아야 하는 개념은 플러그인이다. 플러그인은 한마디로 정의하기가 어렵지만, 공식 문서에서는 로더가 할 수 없는 다른 작업을 수행한다고 설명하고 있다.&lt;/p&gt;
&lt;p&gt;로더가 파일(모듈)을 해석하고 변환하는 과정에 필요한다면, 플러그인은 결과물(번들)에 추가적인 처리를 하고싶을 때 필요하다.&lt;/p&gt;
&lt;p&gt;예시로 HtmlWebpackPlugin을 하나 추가해보자. HtmlWebpackPlugin는 template html을 바탕으로 빌드 후에 html파일을 만들어주는 역할을 한다.&lt;/p&gt;
&lt;p&gt;먼저 HtmlWebpackPlugin 을 설치해준다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$ npm install html-webpack-plugin -D&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그 다음 HtmlWebpackPlugin 에 관련된 설정을 추가해준다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; HtmlWebpackPlugin &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;html-webpack-plugin&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HtmlWebpackPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;로더에 비해서 플러그인은 설정이 간단하다. 플러그인의 배열에는 생성자 함수로 생성한 객체 인스턴스만 추가하면 된다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;지금까지 웹팩에 대해 공부한 내용을 정리해보자&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;먼저 엔트리로부터 의존성이 있는 모든 모듈을 찾는다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;로더를 통해 각 모듈을 읽을 수 있게 해석한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;이 모듈들을 병합하고 압축하여 아웃풋에 번들된 결과물을 생성하는 역할을 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;생성된 결과물에 추가적인 작업이 필요하다면 플러그인을 통해 처리한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 2000px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/fcdbf45e49a8715d34850eb9e9d14338/c4525/2021-07-10-webpack-exercise-1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 49.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABlUlEQVR42oWSwU7CQBCGeRdEoYUWCi2FUqHQ0kKLgCTgxXgwnpUYvYEn40XPxtfQ8C4a47P8zmwBSQh4mG62O/vt/89MQq42IVUc8LoKiSJXc1EN+rA6QxheBMMNxd5wo638zUhkrRbUYx+8rhKlSgxsDiZonZ7B6U9gRyNax7CCwf9A3TshaBtq3Rcgaak6Xa7jQLOQVHUk8wYOdRsZs7ETtgYWWyE0p4uyP0CZFPCe/zPc8Ho4nz1jPH2AQvt1WejBnO0JEfl6gEIjiIEKKbPCEUpujyISak0CywTMlCxcPb3h+v0bdx+fmEznSOs1FJuheIzvas2uCJ3uCyAfCGX+kKJPh2F8yEDdwuXjK24XP7hffGF8M8MRPcJulJoHxW4LOO9L5Gpp2SW5HWGTwTpZZDAf5gjKqi/mL8KyShZlsrpqIAO5JOyO78WWKakSDAkYCWWcKG0UmRuTKphIaeZWQwSUJkSju1lSLFeduClskwvLRVbECP0VP8sr5YjY1d2NMUrwh2EM5Si2egIs75m1fWPzC+7oXE/FaxtGAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;webpack&quot;
        title=&quot;&quot;
        src=&quot;/static/fcdbf45e49a8715d34850eb9e9d14338/f97d7/2021-07-10-webpack-exercise-1.png&quot;
        srcset=&quot;/static/fcdbf45e49a8715d34850eb9e9d14338/0eb09/2021-07-10-webpack-exercise-1.png 500w,
/static/fcdbf45e49a8715d34850eb9e9d14338/1263b/2021-07-10-webpack-exercise-1.png 1000w,
/static/fcdbf45e49a8715d34850eb9e9d14338/f97d7/2021-07-10-webpack-exercise-1.png 2000w,
/static/fcdbf45e49a8715d34850eb9e9d14338/c4525/2021-07-10-webpack-exercise-1.png 2290w&quot;
        sizes=&quot;(max-width: 2000px) 100vw, 2000px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위의 사진은 웹팩 공식 홈페이지에 들어가면 가장 먼저 나오는 이미지이다. 이 이미지를 다시 보자 웹팩을 가장 직관적을 설명하고 있다는 생각이 들었다.&lt;/p&gt;
&lt;p&gt;이 글을 읽은 다른 분들에게도 웹팩을 이해하는데 조금이나마 도움이 되었으면 한다.&lt;/p&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://webpack.js.org/&quot;&gt;웹팩 공식문서&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://joshua1988.github.io/webpack-guide/&quot;&gt;웹팩 핸디북&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://jeonghwan-kim.github.io/series/2019/12/10/frontend-dev-env-webpack-basic.html&quot;&gt;김정환블로그 - 프론트엔드 개발환경의 이해: 웹팩(기본)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[.git 내부 구조 파헤치기]]></title><description><![CDATA[개발을 함에 있어 필수 요소가 된 . 개발 과정에서의 수많은 커밋과 브랜치, 관리되는 파일의 정보들이 모두 .git 안에서 관리되는데, 그 내부를 파헤쳐 본다. 이 글은 git을 어느 정도 사용해보고 읽는 것을 추천한다. .git 내부 구조  .git…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-08-dot-git/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-08-dot-git/</guid><pubDate>Thu, 08 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;개발을 함에 있어 필수 요소가 된 &lt;code class=&quot;language-text&quot;&gt;git&lt;/code&gt;. 개발 과정에서의 수많은 커밋과 브랜치, 관리되는 파일의 정보들이 모두 .git 안에서 관리되는데, 그 내부를 파헤쳐 본다. 이 글은 git을 어느 정도 사용해보고 읽는 것을 추천한다.&lt;/p&gt;
&lt;!-- end --&gt;
&lt;hr&gt;
&lt;h2&gt;.git 내부 구조&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 854px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b69b47cea1dc8637bae10ade9d3e8387/b9f8c/2021-07-08-dot-git-1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 4.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAABCAYAAADeko4lAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAU0lEQVR42h3J3QpAMACAUU+A5oJc+C9ly9a4YRtR3v+RPnJ5OtHSSXw/cY+eMFz4NrBXG64JuNrzjCdHaXG5xmYakypCYViFQifyt0lnjPgunngBziMePzNkkBIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;.git_tree&quot;
        title=&quot;&quot;
        src=&quot;/static/b69b47cea1dc8637bae10ade9d3e8387/b9f8c/2021-07-08-dot-git-1.png&quot;
        srcset=&quot;/static/b69b47cea1dc8637bae10ade9d3e8387/0eb09/2021-07-08-dot-git-1.png 500w,
/static/b69b47cea1dc8637bae10ade9d3e8387/b9f8c/2021-07-08-dot-git-1.png 854w&quot;
        sizes=&quot;(max-width: 854px) 100vw, 854px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;.git 폴더의 내부 구조는 위 캡처와 같다. git의 작동원리를 이해하는 데 도움이 되는 부분들을 위주로 설명을 진행해 나간다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/egoing/gistory&quot;&gt;생활코딩 이고잉님의 gistory&lt;/a&gt; 를 활용하면 쉽게 .git의 내부 파일들을 분석할 수 있으며, 이 글에서도 활용하고자 한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;/objects&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 459px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/794233967f38250699d7a7f47b1713b8/e38a7/2021-07-08-dot-git-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 27.886710239651418%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABMElEQVR42k1Q2VLCQBDkSUp90LIMAQFjIGhxZHOQzR1yIpSA+uD//0o72SSWD10z3TPTM7s9Vy1gTnYwpxmYHIBJW+j9GfQrlfDSxH6L/1qHVmc0Y95o6EUSAx864PcrFM8usilHTrFQPFSzACnxQnFJ84RWqj4qQt72ij7i6XjbGPqPOviIw33Y/Jl1qIdzxSfdwW7sYD8PBbrF1SxENnGEaTKy2gtlEx4Z+gMT71ozUBukE048EtrHMhVX1gaH1wTHtx2OFPdzqs/reiZeY90u0EuGFiyZw75bIhmaiEc2kicbMekR8XBgiDwcMKo1WkI9XR4TIuoJ6evEhfXfeZKBQLbwqZeECt9GJbZeWInTusDP9oAzxfOmxGER47KhfJ3TpSm+WIXTKic9gnGt4RcO/LkEBNm3ygAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;objects_tree&quot;
        title=&quot;&quot;
        src=&quot;/static/794233967f38250699d7a7f47b1713b8/e38a7/2021-07-08-dot-git-2.png&quot;
        srcset=&quot;/static/794233967f38250699d7a7f47b1713b8/e38a7/2021-07-08-dot-git-2.png 459w&quot;
        sizes=&quot;(max-width: 459px) 100vw, 459px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;git에서 활용하는 데이터들이 저장되는 곳이다. &lt;code class=&quot;language-text&quot;&gt;tree&lt;/code&gt; 명령어를 실행시켜 보면 위와 같은 구조를 가진다. 2글자의 폴더 밑에 38글자의 파일명을 가지는 특이한 구조를 가진다.&lt;/p&gt;
&lt;p&gt;objects의 구성은 실제 파일에 담긴 값들을 SHA1 해시한 값 40자 중 2자는 폴더명 38자는 파일명으로 두어 식별자로 활용한다. 해싱을 사용했을 때, 소스 코드의 일부만을 바꾸더라도 별개의 해시값이 되기 때문에, 파일 식별이 쉬워지게 된다. (추가로 SHA1 해시 처리 전, zlib으로 한번의 압축이 진행된다.)&lt;/p&gt;
&lt;p&gt;저장되는 파일의 형태는 크게 3가지 blob, tree, commit으로 분리된다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Blob&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;소스 코드, 이미지 등 다양한 파일의 데이터를 저장한다. 파일의 메타 데이터를 저장하지 않고 데이터 자체만을 저장한다. (파일명과 같은 메타데이터는 저장되지 않음) 그렇기 때문에, 동일한 소스 코드를 가진 파일이 여러 개 있더라도 하나의 blob 파일만 생성된다.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Tree&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;폴더 구조를 git에서도 관리해주는 것이 tree 파일이다. Blob에는 실제 파일의 데이터들이 저장되는 것과는 다르게, tree에는 파일 식별자, 파일 데이터의 해시값, 파일의 이름이 저장된다. 폴더가 파일과 폴더로 구성되는 것처럼, tree는 blob 과 또 다른 tree로 구성된다.
파일 식별자는 100644(읽기 파일(blob)), 100755(실행 파일(blob)), 040000(디렉터리(tree)) 세 가지로만 구성된다.&lt;/p&gt;
&lt;p&gt;커밋을 진행했을 때, 자동으로 파일 시스템 모드(권한 관련 설정)가 수정되는 &lt;code class=&quot;language-text&quot;&gt;chmod 644&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;chmod 755&lt;/code&gt; 명령어가 실행된 경험이 있을 것이다. 이는 git에서 지원하는 파일 시스템 모드가 100644, 100755 두 가지 뿐이기 때문이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tree 파일 분석&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 522px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a1037e65dab9227cd467704b1a76bd1a/d15a3/2021-07-08-dot-git-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABSElEQVR42kWR7XqCMAyFvf/Lm87pkI8WyjZoy2cB9cdZEhB/vE/SJD1NmkPTNFBKQWuNLMuQJCmSNEWmNG63m/hxHFM8EV9im2+dQ5gmjGFECEE49H0P773gqMB7B2ctvLNvK3692fVs6TzQ3XmedzERnOgFS0Xc6TAMcD2du5WXX3dBrB9muIHjbGdU7Qjfj1iWBff7XTh0XYcoimQspXN8aouTsjgS7J+JY1bjg7jkTjhtOSYxNcI44vF4rII8Mv8J/6MpS3wbjytTeERmRYQKh/inQVw2UvOq078ePOXeIY/Jy9AsaEoSWi9/kQiLMuwzIkhwngUvlFMkuNA/7oJt28rIaZogL8wuyFy3Ts75+4FXd5yPqFtTObT0//vII81f0qhVVcE1LfK6I3oUjO1REqpaY5osYzj219FSAm05gPfwfD5F9B9dqlXnxRv+HwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;tree_gistory&quot;
        title=&quot;&quot;
        src=&quot;/static/a1037e65dab9227cd467704b1a76bd1a/d15a3/2021-07-08-dot-git-3.png&quot;
        srcset=&quot;/static/a1037e65dab9227cd467704b1a76bd1a/0eb09/2021-07-08-dot-git-3.png 500w,
/static/a1037e65dab9227cd467704b1a76bd1a/d15a3/2021-07-08-dot-git-3.png 522w&quot;
        sizes=&quot;(max-width: 522px) 100vw, 522px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt; &lt;br&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;위 파일을 보면, git으로 관리되고 있는 폴더는 읽기 파일 a,b,c, 실행 파일 d, 그리고 dir 폴더를 가지고 있다. a,b,c,d의 해시값이 같은 것으로 보아 파일의 내용은 모두 동일하다. (실제 네 개 모두 아무 값도 없는 empty 파일이다)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Commit&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 546px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0f5f4ff9fca021bd664cec0e8e685f4d/8c78b/2021-07-08-dot-git-4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 45.4%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABSUlEQVR42n1S2W7DMAzr/3/ermJHW8d2nDjLfbbJBo5ykWFttz0QEhWZcihvmqZBlmXw3iNJEjgidg7GWvIUjnkcxzDGhJpE4YKqqjCOI4Zh+MambdsglCYO75lH5tOQxzzsmfuUA6xhj0Pi4sBliAj/KiiFru9R90fU3YhmmFD1E+MRreSs1eQt0Y3HwE+nGZ8fC+PpBpu6rqGUxlbnuI8KbE2JB13gkXhifqeknoe4tWXoUb7BRNH5L0HLX9JxglftEbkce+vxZlK8RAl2zM/I8Kwcdpp19hmXBsF5ni8FxUOtNREhUopmW3rEJZAf9juowwGWfol/0qOUcM2ogpfTNF0KiodyyxUljZYhZVkGyAvIiyIsQF5EnucoihLr65DzP28ZBKU5iFGg54KkYcWyLBd8hRyWbzceyqrPU4sQ5XbXvvyH60FfnX6qzhX6yGAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;commit_gistory&quot;
        title=&quot;&quot;
        src=&quot;/static/0f5f4ff9fca021bd664cec0e8e685f4d/8c78b/2021-07-08-dot-git-4.png&quot;
        srcset=&quot;/static/0f5f4ff9fca021bd664cec0e8e685f4d/0eb09/2021-07-08-dot-git-4.png 500w,
/static/0f5f4ff9fca021bd664cec0e8e685f4d/8c78b/2021-07-08-dot-git-4.png 546w&quot;
        sizes=&quot;(max-width: 546px) 100vw, 546px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;각각의 커밋별로 하나의 커밋 파일로 저장된다. git으로 관리되는 가장 바깥 tree의 해시값, author, commiter, 커밋 메시지의 정보가 저장이 된다.&lt;/p&gt;
&lt;p&gt;parent에는 직전 커밋의 해시값이 저장되어, Linked List의 형태로 커밋들이 구성된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;/refs&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 457px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/092bdbb8c84cea62f7993abfa44a664c/4c61e/2021-07-08-dot-git-5.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.363238512035004%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABoklEQVR42pWSWVPCQBCEeaG4FATkFjkSUI7sLpA74RBQBMoHS/3/P6WdbCxLSkrwoSubbLZ3vumJWLcexjUfPM8h0vcQ8SZEUiGpEKlOqGB9oK9vF93DfVpH/KqAWRCw6j5ExQW/noBdaeAJFSzepqcCHqcLEuoR49+KWAUNVm4II92FUzXgUsVbbYNF3cCT6mPVsrFuObCyfWl+2vA6MBxgnFIxKzGsmyY+9B12vQXe9C02HT/cD/DOqXB2o8OgCs2ygWV3iUfFJTxF9iPA5sl/IrsVAT07hFWzwWpzsJIN7bIHFmvJHo6S5xl9G9pFjZJVJPK0qsNveHhmG2y6M8ypj8NoMwyHxGLt04ZemcsDjDBn1TGcIofXWcNTl3DrtuyfQYGYJIf6PTo6Rj8MnRKXtwcvZpYOZ3qw8kPsBkvs+xSMscOreMK67WDVtMJwEn8h09hwOW9tTCsjuFThi7bCnlKeUn+D0B4IfZK+k/gnkZ1iOMQ8RXNYINySwLuxl2aDaEP2TaOAgp9P4YYpE/IkxyBqHkSmT6kq34d/6tyUPwGeIVZYnN62yQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;refs_tree&quot;
        title=&quot;&quot;
        src=&quot;/static/092bdbb8c84cea62f7993abfa44a664c/4c61e/2021-07-08-dot-git-5.png&quot;
        srcset=&quot;/static/092bdbb8c84cea62f7993abfa44a664c/4c61e/2021-07-08-dot-git-5.png 457w&quot;
        sizes=&quot;(max-width: 457px) 100vw, 457px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;refs의 폴더 구조는 위와 같이 구성되며, git에서 관리하는 branch들의 정보가 들어 있다. 로컬에서 작업하는 부분은 heads, 원격 저장소는 remotes 안에 관리 된다. 각 브랜치별 마지막 커밋의 해시값을 저장하고 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;/logs&lt;/h2&gt;
&lt;p&gt;HEAD, 각각의 브랜치 별로 작업 목록이 로그로 기록된다. gistory로 확인해보길 바란다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;/hooks&lt;/h2&gt;
&lt;p&gt;git에서 지원하는 기본적인 hook들이 정의되어 있다. &lt;code class=&quot;language-text&quot;&gt;.sample&lt;/code&gt;을 지우면 샘플이 적용되는데, 이 글에서는 각 hook에 대해서는 설명하지 않는다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;HEAD&lt;/h2&gt;
&lt;p&gt;HEAD는 현재 로컬 저장소가 가르키고 있는 브랜치를 참조한다.&lt;/p&gt;
&lt;p&gt;특정 브랜치가 아닌 특정 커밋으로 checkout하면 detach가 됐다는 메시지가 뜬 기억이 있을 것이다. 이때는 그 브랜치를 참조하는 것이 아니라, 커밋의 해시값이 HEAD에 들어가게 된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;index&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 563px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/92380a81ded22a0c09c0c6c1f1f52202/e80ce/2021-07-08-dot-git-6.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 39.6%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABM0lEQVR42m2S2XKDMAxF+f//ywZN0hYwabCNwTaQZeZWEk6aaftwrMWjK8njrO97tG0LrTW0MQhxRBwTMfEaJ39kYlzsC5m1FmVVoVEKdV1RMmKiC2FKPGK+S/EoDeIfMj6ccxhCxBBn2CGiS5g+SNz7nxzDOR+W6aZpwjzPTzJetchzvJVnrEuD9afBrrLYlJZ8jRXF+6bDrubccrclezYOfhhE8HK5PMkMvdvhsMdBtVTUYVsZ5FQsheQzx5NDoayIFkROOW0dQvD/C368H3FUGpvUnQWZLcU8tQiKEAsuTVsS5Pe+Xq+43+9iRVDrZcJ93coEvDILrWhdXnld0pOoTtZlcdmg0jhTXdMoopFfEkIQ0Ywd+TKdx6kLOFmPr2Qf/u9Yu4DgvdQ9vhzr3G43fANthFztMvV2EQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;index_gistory&quot;
        title=&quot;&quot;
        src=&quot;/static/92380a81ded22a0c09c0c6c1f1f52202/e80ce/2021-07-08-dot-git-6.png&quot;
        srcset=&quot;/static/92380a81ded22a0c09c0c6c1f1f52202/0eb09/2021-07-08-dot-git-6.png 500w,
/static/92380a81ded22a0c09c0c6c1f1f52202/e80ce/2021-07-08-dot-git-6.png 563w&quot;
        sizes=&quot;(max-width: 563px) 100vw, 563px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;index파일은 stage에 있는 파일들이다. 즉, &lt;code class=&quot;language-text&quot;&gt;git add&lt;/code&gt;를 진행하면 index의 파일이 수정된다. git은 index와 마지막 커밋을 비교하여 커밋할 파일이 있는지 판단한다. 또한 index와 현재 파일을 비교하여 수정된 파일이 있는지 여부도 확인한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;git의 명령어의 작동 이해하기&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;git fetch&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;git fetch&lt;/code&gt;란 명령어는 원격저장소의 데이터를 가져온다. 이 때 objects와 refs의 변화를 생각해 보면 다음과 같다.&lt;/p&gt;
&lt;p&gt;objects: 파일의 데이터가 같은 경우를 제외한 다른 blob/tree/commit 정보들이 추가된다.&lt;/p&gt;
&lt;p&gt;refs: 새로 생긴 remotes의 branch들을 추가하고, commit이 된 경우에는 각 브랜치 별로 최신 커밋으로 참조값을 수정해줘야 한다.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;git reset&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;강력하면서도 자주 사용하는 명령어가 reset이다. &lt;code class=&quot;language-text&quot;&gt;git reset&lt;/code&gt;이 실행되면, 현재 HEAD가 가르키고 있는 브랜치의 커밋 해시값이 reset 위치의 커밋의 해시값으로 수정된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그렇다면 reset을 한 경우 복원이 가능할까?&lt;/strong&gt; 정답은 “가능하다”이다. git은 reset을 하는 그 시점에 objects의 파일을 삭제하지 않는다.(가비지 컬렉션의 방식으로 데이터가 많아졌을 때, 참조값이 없는 것의 삭제를 진행한다)&lt;/p&gt;
&lt;p&gt;친절하게도 &lt;code class=&quot;language-text&quot;&gt;git reset&lt;/code&gt;을 진행했을 때 &lt;code class=&quot;language-text&quot;&gt;ORIG_HEAD&lt;/code&gt;라는 파일이 생성되어, reset 하기 전 커밋의 해시값을 따로 저장해둔다. 그렇기 때문에 &lt;code class=&quot;language-text&quot;&gt;git reset --hard ORIG_HEAD&lt;/code&gt;를 통해서 reset을 복구할 수 있다. 물론 직접 기존 커밋의 해시값으로 reset을 해도 마찬가지로 동작한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;정리&lt;/h3&gt;
&lt;p&gt;git이 작동하는 원리를 직접 .git 내부를 보며 확인해봤다. 직접 git 프로젝트를 만들어 도전해보기를 바란다. 상당히 재미있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;참고 자료&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://opentutorials.org/module/2676&quot;&gt;지옥에서 온 Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/happyprogrammer-in-jeju/git-%EB%82%B4%EB%B6%80-%EA%B5%AC%EC%A1%B0%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-1-%EA%B8%B0%EB%B3%B8-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-81b34f85fe53&quot;&gt;Git 내부 구조를 알아보자 (1) — 기본 오브젝트&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git.kernel.org/pub/scm/git/git.git/tree/README?id=e83c5163316f89bfbde7d9ab23ca2e25604af290&quot;&gt;Git Manual&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Babel]]></title><description><![CDATA[바벨을 사용하는 게 익숙하지 않은 독자를 대상으로 작성한 글입니다. 0. Intro…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-07-babel/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-07-babel/</guid><pubDate>Wed, 07 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;바벨을 사용하는 게 익숙하지 않은 독자를 대상으로 작성한 글입니다.&lt;/p&gt;
&lt;h2&gt;0. Intro&lt;/h2&gt;
&lt;p&gt;보통 프로젝트를 시작하기 위해 초기 설정을 하며 바벨을 처음 만나게 됩니다. 필요하다고 하니깐 사용하기는 하는데, 초기 설정 이후 우리는 바벨을 잊곤 합니다. 어렴풋이 알고는 있지만 아직은 어색한 사이인 바벨, 조금 더 친해져보도록 하겠습니다.&lt;/p&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;바벨이란&lt;/li&gt;
&lt;li&gt;바벨을 사용하는 이유&lt;/li&gt;
&lt;li&gt;바벨 실습&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1. 바벨이란&lt;/h2&gt;
&lt;img width=&quot;1115&quot; alt=&quot;babel-is-compiler&quot; src=&quot;https://user-images.githubusercontent.com/40762111/124785060-7d3f2e80-df81-11eb-8c4f-eb6ec159328c.png&quot;&gt;
&lt;p&gt;바벨은 ES6+ 버전 이상의 자바스크립트나 JSX, 타입스크립트 코드를 하위 버전의 자바스크립트 코드로 변환 시켜 IE나 다른 브라우저에서 동작할 수 있도록 하는 역할을 합니다. 공식 홈페이지에서는 이러한 바벨을 컴파일러라고 소개하지만, 엄밀히 말하면 컴파일러는 아닙니다. 컴파일러는 사람이 작성한 코드를 컴퓨터가 이해할 수 있도록 머신 코드로 바꿔주는 프로그램을 의미하기 때문입니다. 바벨은 컴파일러의 역할과는 다르게 같은 언어를 다른 실행 환경에서도 돌아갈 수 있도록 형태만 바꾸는 작업을 진행합니다. 이와 같은 작업을 하는 프로그램을 트랜스 파일러라고 합니다. 그래서 바벨은 보통 트랜스 파일러라고 불립니다.&lt;/p&gt;
&lt;h2&gt;2. 바벨을 사용하는 이유&lt;/h2&gt;
&lt;p&gt;바벨을 사용하는 이유를 알기 위해서는 먼저 크로스 브라우징(Cross Browsing)에 대한 이해가 필요합니다. 크로스 브라우징이란 플랫폼이나 각 브라우저의 렌더링 엔진의 차이로 보이는 모습이 다른 경우가 있는데, 최적화 작업을 통해 기존에 의도한 대로 보이도록 하는 작업을 말합니다. 개발을 진행하면서 간혹 최신 브라우저에서 동작하는 기능을 다른 브라우저나 이전 버전의 브라우저에서 구현해야 하는 경우, 기능 자체를 생략하거나 단순화해야 하는 상황이 발생합니다. 이런 이슈는 코드의 일관성을 해칠 수 있으며, 협업 과정에서 또 다른 문제를 야기할 수 있습니다. 바벨은 이런 문제점들을 보완하고 해결하기 위해 등장했습니다.&lt;/p&gt;
&lt;h2&gt;3. 바벨 실습&lt;/h2&gt;
&lt;p&gt;바벨은 무엇이고, 바벨을 사용하는 이유를 알았으니 간단하게 실습해보겠습니다.&lt;/p&gt;
&lt;h3&gt;프로젝트 생성&lt;/h3&gt;
&lt;p&gt;실습을 위해 터미널에서 프로젝트를 생성합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; babel-practice
$ &lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; babel-practice
$ &lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; init &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;프로젝트를 생성하고, 간단하게 ES2015 arrow function 문법을 사용하여 코드를 작성하겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// test.js&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; n &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 바벨을 사용하여 코드를 transpile 하겠습니다.&lt;/p&gt;
&lt;h3&gt;바벨 설치&lt;/h3&gt;
&lt;p&gt;프로젝트에서 &lt;a href=&quot;https://www.npmjs.com/package/@babel/core&quot;&gt;@babel/core&lt;/a&gt;와 &lt;a href=&quot;https://www.npmjs.com/package/@babel/cli&quot;&gt;@babel/cli&lt;/a&gt; 패키지를 개발 의존성(devDependencies)으로 설치합니다. 개발 의존성으로 설치하는 이유는 바벨이 애플리케이션이 실행될 때 필요한 것이 아니라 빌드할 때만 필요하기 때문입니다. 각 패키지에 관해 설명하면 &lt;code class=&quot;language-text&quot;&gt;@babel/core&lt;/code&gt;는 바벨을 사용하는데 필요한 패키지이고, &lt;code class=&quot;language-text&quot;&gt;@babel/cli&lt;/code&gt;는 터미널에서 커맨드를 입력해서 바벨을 사용하기 위해 필요한 패키지입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-D&lt;/span&gt; @babel/core @babel/cli&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이제 터미널에서 이전에 작성했던 코드에 바벨을 적용하겠습니다. &lt;code class=&quot;language-text&quot;&gt;npx babel &amp;lt;파일명/디렉토리명&gt;&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npx babel test.js
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.map&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  n + &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;기존의 코드에서 변화가 없습니다. 바벨에게 코드를 어떻게 변환해야 하는지 규칙을 알려주지 않았기 때문입니다.&lt;/p&gt;
&lt;h3&gt;Plugin/Preset 설정&lt;/h3&gt;
&lt;p&gt;바벨에서 플러그인(plugin)이나 프리셋(preset)을 통해 문법 변환 규칙을 알려줄 수 있습니다. 플러그인은 규칙 하나하나를 세밀하게 적용할 때 사용하고, 프리셋은 이런 규칙들을 모아놓은 세트로, 주로 한 번에 적용할 때 사용합니다.&lt;/p&gt;
&lt;p&gt;실습에서는 넓게 이용되는 프리셋인 &lt;code class=&quot;language-text&quot;&gt;env&lt;/code&gt;를 사용하겠습니다. &lt;code class=&quot;language-text&quot;&gt;env&lt;/code&gt;는 ES6 이상(ES2015+)의 문법으로 작성된 코드를 ES5 문법의 코드로 변환해주는 모든 규칙을 정의하고 있습니다.&lt;/p&gt;
&lt;p&gt;이전에 진행했던 방식과 동일하게 설치하겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-D&lt;/span&gt; @babel/preset-env&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;그리고, 이번에는 바벨에게 코드 변환의 규칙으로 &lt;code class=&quot;language-text&quot;&gt;env&lt;/code&gt;를 알려주겠습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npx babel test.js &lt;span class=&quot;token parameter variable&quot;&gt;--presets&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;@babel/env
&lt;span class=&quot;token string&quot;&gt;&quot;use strict&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.map&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;function &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  n + &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이전의 코드와 비교해서 변경된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;바벨 설정 파일&lt;/h3&gt;
&lt;p&gt;하지만 매번 바벨에게 변환될 규칙을 알려주는 것은 매우 비효율적입니다.
그래서 대부분의 경우 바벨 설정 파일인 &lt;code class=&quot;language-text&quot;&gt;.babelrc&lt;/code&gt;나 &lt;code class=&quot;language-text&quot;&gt;babel.config.js&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;babel.config.json&lt;/code&gt;을 이용합니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;.babelrc&lt;/code&gt;는 주로 하위 디렉토리나 파일에서 특정 플러그인이나 변환(규칙)을 실행할 때 적절하고, &lt;code class=&quot;language-text&quot;&gt;babel.config.js&lt;/code&gt;는 여러 패키지 디렉토리를 가진 프로젝트에서 규칙을 설정할 때 유용합니다. 둘 중에 &lt;code class=&quot;language-text&quot;&gt;babel.config.js&lt;/code&gt;가 보다 보편적으로 사용됩니다.&lt;/p&gt;
&lt;p&gt;또한, &lt;a href=&quot;https://babeljs.io/docs/en/config-files#config-function-api&quot;&gt;공식 문서&lt;/a&gt;에 따르면 &lt;code class=&quot;language-text&quot;&gt;babel.config.js&lt;/code&gt;를 사용하면 구성하는 &lt;code class=&quot;language-text&quot;&gt;api&lt;/code&gt;가 노출됩니다. 이러한 방식은 캐싱과 관련해서 복잡성을 증가시키므로, 대부분은 정적인 구성인 &lt;code class=&quot;language-text&quot;&gt;babel.config.json&lt;/code&gt;을 사용하는 게 더 좋은 선택입니다.&lt;/p&gt;
&lt;p&gt;프로젝트 최상위 디렉토리에 &lt;code class=&quot;language-text&quot;&gt;.babelrc&lt;/code&gt; 파일을 생성 후, 다음 설정을 추가합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// .babelrc&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;presets&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@babel/env&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 하면, 터미널에서 바벨 옵션을 주지 않고 실행해도 규칙이 적용되는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npx babel test.js
&lt;span class=&quot;token string&quot;&gt;&quot;use strict&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.map&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;function &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  n + &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;마침&lt;/h2&gt;
&lt;p&gt;바벨에 대해서 알아보고, 직접 실습해보면서 바벨이 코드를 transpile 하는 과정을 알아봤습니다. 위의 실습과 같이 바벨 설정 파일을 생성하여 단독으로 사용하기도 하지만, 프로젝트를 진행하면 보통 Webpack과 같은 bundler(번들러)와 함께 사용됩니다. 이번 글을 통해 바벨과 조금 더 친해지는 시간이 되었기를 바랍니다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://babeljs.io&quot;&gt;https://babeljs.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://devowen.com/293&quot;&gt;https://devowen.com/293&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.daleseo.com/js-babel&quot;&gt;https://www.daleseo.com/js-babel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/60288375/when-to-use-babel-config-js-and-babelrc&quot;&gt;https://stackoverflow.com/questions/60288375/when-to-use-babel-config-js-and-babelrc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Message Oriented Middleware]]></title><description><![CDATA[프로젝트를 진행하면서, 서버와 서버 사이에 메시지를 통해 비동기적으로 전달하는 방법이 필요했다. 관련된 개념인 Message Oriented Middleware(메시지 기반 미들웨어)에 관해 알아보고, 메시지 전달의 두 가지 방식인 메시지 큐와 Pub…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-07-03-message-oriented-middleware/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-07-03-message-oriented-middleware/</guid><pubDate>Sat, 03 Jul 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;프로젝트를 진행하면서, 서버와 서버 사이에 메시지를 통해 비동기적으로 전달하는 방법이 필요했다. 관련된 개념인 Message Oriented Middleware(메시지 기반 미들웨어)에 관해 알아보고, 메시지 전달의 두 가지 방식인 메시지 큐와 Pub/Sub 모델을 비교한다.&lt;/p&gt;
&lt;!-- end --&gt;
&lt;hr&gt;
&lt;h2&gt;Message Oriented Middleware(MOM)&lt;/h2&gt;
&lt;p&gt;Message Oriented Middleware를 이해하기에 앞서 미들웨어의 개념을 먼저 알아보자. 미들웨어, Middle + (soft)ware, 소프트웨어 중간에 존재하여 상호작용을 하게 해주는 것이다. 대표적으로 Java서버를 DB와 연결해주는 JDBC를 들 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;그렇다면 Message Oriented Middleware는 무엇일까?&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;MOM(Message Oriented Middleware) 시스템은 메시지를 기반으로 한 미들웨어이며, 비동기 메시지를 통해 두 소프트웨어의 통신을 중개한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 455px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ef8e17f7e72cd298ddec764345d8e9b4/c27a4/2021-07-03-message-oriented-middleware-1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 34.72527472527473%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAIAAACHqfpvAAAACXBIWXMAAw1AAAMNQAHgcQlTAAABT0lEQVR42m2Q20+CYBiH+bO77rqrLrqo1SzykIr6FYJCoCADxHlATro1PCUouQoUtQujr7WcW71Xv+33PO/evcjnwex2uyiKlsslQ5VoEk/F0Xw29Vgulgk8DENYQeCQR/7Kvu836qLaUmoVCm6BWRb51Wr1vxz9zk/2/UCRBa7KooncyenNXQbUv+VwD+xhZL1ewzuDIPjYbmfeQu/bw9FYazcAwI+Oz65vM5LAQbn/NIDVKgy3mw2EoQJFBBQwhiYZuqypKic1b3EJkBWxVpFEQRRly9A77QZFPpDVeqzAK82urnUJHLB0CeQwJJlAywSgyKJp6CVWuMKqOYLF0gme5ybjkW3bsizHLs/zJe4izQhKxzJ1kM9S5H0yjiIvnjd3Hdd14J8cd660tMFo4jrTnmUahm6aRq9nPU/Gw/G0qVqL1zfff4ftzHU8z/sC9qxk7LdB9nIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;image&quot;
        title=&quot;&quot;
        src=&quot;/static/ef8e17f7e72cd298ddec764345d8e9b4/c27a4/2021-07-03-message-oriented-middleware-1.png&quot;
        srcset=&quot;/static/ef8e17f7e72cd298ddec764345d8e9b4/c27a4/2021-07-03-message-oriented-middleware-1.png 455w&quot;
        sizes=&quot;(max-width: 455px) 100vw, 455px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
출처: oracle docs&lt;/p&gt;
&lt;p&gt;전달하는 데이터를 &lt;code class=&quot;language-text&quot;&gt;Message&lt;/code&gt;(메시지), 메시지를 보내는 클라이언트를 &lt;code class=&quot;language-text&quot;&gt;Producer&lt;/code&gt;(프로듀서), 메시지를 받는 클라이언트를 &lt;code class=&quot;language-text&quot;&gt;Consumer&lt;/code&gt;(컨슈머), 메시지 전달 공급자의 용어 대신 &lt;code class=&quot;language-text&quot;&gt;Message Broker&lt;/code&gt;(메시지 중개인)로 정의하여 이 글에서는 사용하겠다.&lt;/p&gt;
&lt;p&gt;프로듀서는 API를 호출하여 메시지 중개인에게 메시지를 라우팅하여 전달하고, 메시지를 보낸 후에는 다른 작업을 계속해서 수행한다. 중개인은 컨슈머가 메시지를 검색할 때까지 메시지를 보관한다. 컨슈머는 전달된 데이터를 수신하고 처리한다.&lt;/p&gt;
&lt;p&gt;이런 MOM 시스템은 느슨하게 결합한 시스템을 만들어내어, 각각의 주요 로직에만 집중하여 작업을 진행할 수 있다. 또한 중개자에 인터페이스를 추가하여 성능을 모니터링할 수 있으며, 분산처리와 확장성 측면에서도 이점을 가져 MSA(Micro Service Architecture)의 주요 기술로 사용된다.&lt;/p&gt;
&lt;p&gt;MOM이 메시지를 전달하는 방식은 크게 메시지 큐 모델, Pub-Sub 모델 두 가지로 분리된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;메시지 큐(Queue) 모델&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1117px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1d8ab7ffafe09bf32e92419132cedb50/b25d8/2021-07-03-message-oriented-middleware-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA+UlEQVR42s2Sv0rEQBCH94V8BJ/AJ7C61sLKUhCs7W0trSyEWFiEJJVVgmJOSMQENcUFvRwkZ5zk4u1nzr8gRCGIODCzzAz78WN/q/jlUF0LrfVL9gK+X/4KWLSLiX6rr9lT4ThLkeoemhYimrrWbf+zaiUiBEGA7/uEYUhZPtI8lRwfnbO1OmH35JC1bJuLqx1G8QZpOmI+18RxhOu6eJ5HkiQfz6SKosC2bQzDwHEcsvGEUjIO9k8ZLAmb5h7L+YCzy3Xu4hVubq8RqRgOfSzLwjRNoij6BHYZIlJSNTny0CouaqbTmrw9Z7Omv8vfRdde/dk//DfAZ3aDVVXrbMr+AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;image&quot;
        title=&quot;&quot;
        src=&quot;/static/1d8ab7ffafe09bf32e92419132cedb50/b25d8/2021-07-03-message-oriented-middleware-2.png&quot;
        srcset=&quot;/static/1d8ab7ffafe09bf32e92419132cedb50/0eb09/2021-07-03-message-oriented-middleware-2.png 500w,
/static/1d8ab7ffafe09bf32e92419132cedb50/1263b/2021-07-03-message-oriented-middleware-2.png 1000w,
/static/1d8ab7ffafe09bf32e92419132cedb50/b25d8/2021-07-03-message-oriented-middleware-2.png 1117w&quot;
        sizes=&quot;(max-width: 1117px) 100vw, 1117px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
Queue는 First-In First-Out의 자료구조이다. 즉, 메시지 큐는 들어온 메시지 저장하고 들어온 순서대로 컨슈머에 전달되어 처리되는 구조이다.&lt;/p&gt;
&lt;p&gt;여러 컨슈머를 사용하면 높은 속도로 일을 처리할 수가 있다. 각각의 컨슈머는 메시지를 하나씩 전달받고 병렬적으로 작업을 처리하게 된다. 메시지 큐 모델은 메시지의 단일 처리를 보장하는데, 이를 위해 메시지가 컨슈머에 전달되어 처리되면 큐에서 삭제된다.&lt;/p&gt;
&lt;p&gt;네트워크나 컨슈머의 문제가 생겨 전송되지 못한 메시지의 경우 별도의 &lt;code class=&quot;language-text&quot;&gt;Dead Letter Queue&lt;/code&gt;로 저장된다. 이 큐는 재처리, 무시, 관리자가 직접 처리 등 다양한 방식으로 처리를 할 수 있다. 재처리로 메시지를 새로 메시지 큐에 넣게 되면, 메시지의 순서를 보장하지 못하게 된다.&lt;/p&gt;
&lt;p&gt;대표적인 예시가 정산 시스템이다. 정산은 속도가 빠르면 좋고, 순서는 상관없으며, 한 번씩만 처리되는 것이 매우 중요하기 때문에 메시지 큐를 이용하기 매우 적합하다.&lt;/p&gt;
&lt;p&gt;메시지 큐 모델을 기반으로 만들어진 기술은 Apache ActiveMQ, Amazon SQS, IBM Websphere MQ, RabbitMQ 등이 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Pub/Sub 모델&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1117px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5bfd44cba9b944bb0096f104711c30b3/b25d8/2021-07-03-message-oriented-middleware-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABP0lEQVR42q2TP0/CQByG+4X8CG5+CDdWJz6AcXB3dnV1MjTgaAIMNoTBmDZGoMigEEi0lFba4+ifx14VTaOCUX/Je//v8j73u9P459DWTaZp+uV4kiRYlkWj0cg1HA7f12urxkrFAzOp+q18FXieh2matNttWq0W4/G4eOBnZzB1H1lGLkRZR4CUCXIJQRAwn8+/RxZC0Ol0coRut5ttCInigHP9ioNdh2NDZ8855ObuiIdemZ7dy5DTHNO2bfr9PrPZ7MOh7/vU63VqtRrNZhPnaUogHM5OryltCfYvTtj2Sph2mfvbHUzLRIgFhnFJtVrNNRgMNiMLEbCIPIRy7Eue/UWGGqMMhGFYuHeVpI1Z/i7DKlzXZTKZ5MkYjUaFO9V+89biOM6QDSqVCrqub0b+SShs5UxJSvk3h+t+ygtEn0uBerB2YAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;image&quot;
        title=&quot;&quot;
        src=&quot;/static/5bfd44cba9b944bb0096f104711c30b3/b25d8/2021-07-03-message-oriented-middleware-3.png&quot;
        srcset=&quot;/static/5bfd44cba9b944bb0096f104711c30b3/0eb09/2021-07-03-message-oriented-middleware-3.png 500w,
/static/5bfd44cba9b944bb0096f104711c30b3/1263b/2021-07-03-message-oriented-middleware-3.png 1000w,
/static/5bfd44cba9b944bb0096f104711c30b3/b25d8/2021-07-03-message-oriented-middleware-3.png 1117w&quot;
        sizes=&quot;(max-width: 1117px) 100vw, 1117px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
Pub/Sub 모델은 Publish/Subscribe의 축약형이다. 이 모델에서는 Producer, Consumer를 각각 &lt;code class=&quot;language-text&quot;&gt;Publisher&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;Subscriber&lt;/code&gt;라고 명명한다. 또한, Topic을 단위로 카테고리를 구분한다.&lt;/p&gt;
&lt;p&gt;뉴스나 블로그를 구독하여 한 곳에 받아 볼 수 있는 RSS시스템이 가장 간단한 Pub/Sub 시스템이다. 글 작성자(publisher)가 블로그(topic)에 글을 올리게 되면, 이 블로그(topic)를 구독하고 있는 구독자(subscriber)에게 메시지를 던져준다.&lt;/p&gt;
&lt;p&gt;즉 Pub/Sub 모델은 특정 토픽에 Message가 오면 이 토픽을 구독하고 있는 모든 구독자에게 메시지를 전달해주고, 구독자는 이를 처리한다. 즉, Pub/Sub 모델은 메시지 큐 모델과는 달리 단일 처리를 보장해주지 못한다.&lt;/p&gt;
&lt;p&gt;Pub/Sub 모델을 기반으로 만들어진 기술은 Apache Kafka, Google Cloud Pub/Sub 등이 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;제안&lt;/h3&gt;
&lt;p&gt;위에서 설명한 개념들을 코드로 구현해보고 싶은 독자들은, &lt;a href=&quot;https://www.rabbitmq.com/getstarted.html&quot;&gt;RabbitMq Tutorial&lt;/a&gt;를 도전해 보기 바란다.
다양한 언어로 제공하고 있어, 각자 익숙한 언어를 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;RabbitMq를 다운 받는 방식부터 설명되어 있고, 1.Hello World 파트는 Queue 자체를 사용해 보는 부분이라 쉽게 진행할 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;2.Work queues, 3.Publish/Subscribe 는 각각 메시지 큐 모델, Pub/Sub 모델이며 이를 실제로 구현해 보면 이해를 도울 수 있을 것이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;정리&lt;/h3&gt;
&lt;p&gt;지금까지 Message Oriented Middleware에 대해 알아봤다. 많은 메시지 전달 기술들은 그 기반 기술이 다르더라도, 메시지 큐 모델 방식과 Pub/Sub 방식을 모두 사용할 수 있다. 메시지 큐 모델과 Pub/Sub 모델의 개념이 어떻게 적용되고 사용되는지 공부하는 데 도움이 되었으면 한다.&lt;/p&gt;
&lt;h3&gt;참고 자료&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cloudamqp.com/blog/what-is-message-queuing.html&quot;&gt;What is message queuing?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dzone.com/articles/comparing-publish-subscribe-messaging-and-message&quot;&gt;Comparing Publish-Subscribe Messaging and Message Queuing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/swlh/everything-you-need-to-know-about-message-queues-a-complete-guide-dbf190d001d7&quot;&gt;Everything You Need to Know About Message Queues: A Complete Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/cd/E19148-01/820-0532/6nc919fai/index.html&quot;&gt;메시지 지향 미들웨어(Message-Oriented Middleware(MOM))&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[웹팩 이해하기 - 1]]></title><description><![CDATA[그 동안 React.js로 개발을 진행할 때는 항상 CRA(create-react-app)*를 사용하여 개발 환경을 설정하였다. 그래서 CRA…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-06-30-webpack-intro/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-06-30-webpack-intro/</guid><pubDate>Wed, 30 Jun 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;그 동안 React.js로 개발을 진행할 때는 항상 CRA(create-react-app)*를 사용하여 개발 환경을 설정하였다. 그래서 CRA를 사용하면 바로 개발을 시작할 수 있었기 때문에 개발 환경에 대한 고민을 깊게 해보지 않았다.&lt;/p&gt;
&lt;p&gt;그런데 이번에 팀 프로젝트를 진행하면서, CRA를 사용하지 않고 개발 환경을 설정해야하는 상황을 마주하게 되었다.&lt;/p&gt;
&lt;p&gt;구글에 ‘프론트엔드 개발 환경 설정’과 같은 키워드를 검색하면, 웹팩에 대한 글을 많이 볼 수 있다. 그래서 이번 글에서는 많이 들어본 것 같지만 아직도 잘 모르겠는 웹팩에 대해 알아보려고 한다.&lt;/p&gt;
&lt;p&gt;CRA* - 특정 환경의 개발을 바로 진행할 수 있도록 만든 기초 환경을 통틀어 보일러 플레이트(boiler plate)라고 부르기도 하는데, CRA도 이러한 보일러 플레이트의 일종이라고 할 수 있다.&lt;/p&gt;
&lt;h2&gt;웹팩이란?&lt;/h2&gt;
&lt;p&gt;웹팩 공식문서에서 &lt;code class=&quot;language-text&quot;&gt;웹팩&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;JavaScript 애플리케이션을 위한 module bundler&lt;/code&gt;이라고 설명하고 있다. 그럼 &lt;code class=&quot;language-text&quot;&gt;module&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;bundler&lt;/code&gt;는 또 무엇일까?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;모듈 (module)&lt;/strong&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;모듈은 HTML 파일에서도 자주 봤던 단어이다. 이를 보면 javascript 파일을 의미하는 것처럼 보인다.&lt;/p&gt;
&lt;br&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;module&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;app.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;웹팩 공식문서에서 모듈은 모듈 프로그래밍에서 개별적인 기능을 하는 작은 단위라고 설명하고 있다. javascript 에서 보면 각각의 javascript 파일을 모듈이라고 할 수 있을 것 같다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;그렇지만 웹팩에서의 모듈은 javascript 파일뿐만 아니라 애플리케이션을 구성하는 HTML, CSS, Javascript, Images, Font 등 많은 파일들을 모듈이라고 한다.&lt;/p&gt;
&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;번들러 (bundler)&lt;/strong&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;번들을 우리말로 해석하면 ‘묶음, 꾸러미’ 라는 뜻이다. 그럼 번들러는 묶어주는 도구정도로 유추할 수 있을 것 같다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;웹팩에서는 애플리케이션을 구성하는 모든 모듈을 병합하고 압축해서 만들어진 하나 이상의 파일을 번들 이라고 하며, 이러한 동작을 모듈 번들링이라고 한다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;따라서 웹팩 정의에 나오는 모듈 번들러란 모듈을 번들링하는 도구라고 할 수 있을 것 같다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;요약해보자면 웹팩은 애플리케이션에 필요한 모든 파일(모듈)을 병합하고 압축해서 하나의 결과물(번들)을 생성하는 도구라고 설명할 수 있을 것 같다. 그렇다면 왜 모듈 번들링을 해야하는 걸까?&lt;/p&gt;
&lt;h2&gt;웹팩의 필요성&lt;/h2&gt;
&lt;p&gt;웹팩 공식문서와 핸디북 등에서 웹팩이 만들어진 이유와 웹팩이 사용되기 전의 기술들에 대해 자세하게 설명해주고 있다. 그렇지만 그 당시의 기술들을 경험해보지 못한 입장에서 모든 내용을 완전하게 이해하고 공감하지는 못하였다. 그래도 내가 이해한 부분에 대해 최대한 정리해보려고 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;모듈화의 필요성&lt;/strong&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;웹팩이 등장한 가장 큰 이유는 모듈화와 관련이 있는 듯 하다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;과거의 웹 애플리케이션에서 javascript의 역할은 단순했기 때문에, 파일의 크기도 작았다. 그래서 HTML에 script 태그를 넣는 것만으로도 충분했고, 모듈에 관한 표준 문법이 필요성이 딱히 없었던 것 같다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;하지만 현대의 웹 애플리케이션에서 javascript의 비중이 커지면서, 기능이 복잡해지고 코드의 크기도 커졌다. 커져버린 코드를 하나의 파일로 관리하게 되면 가독성 및 유지 관리에 문제가 발생할 수 있기 때문에 코드를 나누어 관리할 수 있는 모듈에 대한 필요성잉 생기게 된 것이다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;이에 따라 필요한 모듈을 언제든지 불러올 수 있게 해 주거나, 코드를 모듈 단위로 구성해 주는 방법 등 다양한 라이브러리를 만들어졌고, CommonJS*와 AMD*, UMD* 같은 모듈 시스템이 생겨나게 되었다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;이러한 흐름에 맞춰 ES2015+부터는 javascript의 표준 공식 기능으로 모듈화(import, export)를 기본 지원하게 되었다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;CommonJS* - javascript를 브라우저에서뿐만 아니라, 서버사이드 애플리케이션이나 데스크톱 애플리케이션에서도 사용하고자 하는 움직임이 있었고, 이를 위해서는 모듈화가 필요하였다. 이를 위해 모듈 방식을 정의하게 되었는데, 그것이 CommonJS 이다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;AMD(Asynchronous Module Definition)* - 필요한 모듈을 네트워크를 이용해 내려받아야 하는 브라우저 환경(비동기 상황)에서도 javascript 모듈을 사용하고자 하였다. 이를 위해 브라우저 내에서의 실행에 중점을 둔 모듈 방식을 정의하게 되었는데, 그것이 AMD 이다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;UMD(Universal Module Definition)* - UMD는 이름에서 알 수 있듯이, CommonJS와 AMD를 통합한 모듈 방식이다. 모듈 구현방식이 CommonJS 와 AMD로 나뉘기 때문에 서로 간의 호환성 문제가 있었고 이를 해결하기 패턴으로 등장하였다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;빠른 로딩 속도와 높은 성능&lt;/strong&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;웹팩의 공식문서에 보면 웹팩은 성능 및 로딩 시간에 관심이 있다고 나와있다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;웹 어플리케이션을 실행시킨다면 브라우저는 javascript와 images, css 등의 파일을 읽어들일 것이다. 앞서 말했듯이 현대의 웹 어플리케이션의 규모가 커지면서 당연히 파일의 수도 많아지게 되었다. 이러한 파일들을 전부 따로 불러들인다면 로딩 시간이 길어질 수 밖에 없다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;웹팩을 사용해서 많은 파일들(모듈)을 병합하고 압축해서 하나의 결과물(번들)로 만들면, 파일의 수와 크기가 줄어들게 된다. 애플리케이션을 실행시키기 위해서 이 결과물만 읽어들이면 되기 때문에 로딩 시간을 줄일 수 있는 것이다.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;여기에 웹팩의 Code Splitting* 과 같은 방법을 사용하면, 필요할 때에만 불러올 수 있도록 번들를 분할하여 성능을 더 개선할 수 있다. (웹팩을 통해 성능을 개선하는 자세한 방법들에 대해서는 추후에 추가적으로 작성해보려고 한다.)&lt;/p&gt;
&lt;p&gt;*Code Splitting  - Code Splitting은 코드를 분할하고, 요청에 따라 로드할 수 있도록 해주는 방법이다. Code Splitting을 하지 않은 경우, 애플리케이션을 실행시켰을 때 모든 코드가 한번에 요청이 되어서 로딩 시간이 길어질 수 있다. Code Splitting을 하게 되면, 필요한 순간 필요한 코드만 불러오기 때문에 우선 순위를 제어할 수 있고, 로딩 시간도 줄일 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;이번 글에서는 웹팩의 정의와 필요성에 대해 알아보았다. 그 동안 CRA를 사용하면서 웹팩이 해주는 역할을 당연하게 생각하고, 왜 궁금해하지 않았는지 스스로를 되돌아보는 시간이었다.&lt;/p&gt;
&lt;p&gt;다음 글에서는 웹팩의 핵심 요소인 Entry, Output, Loaders, Plugins 에 대한 개념을 알아보고, 웹팩을 설정하는 간단한 실습을 해보려고 한다.&lt;/p&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://webpack.js.org/&quot;&gt;웹팩 공식문서&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://joshua1988.github.io/webpack-guide/&quot;&gt;웹팩 핸디북&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://jeonghwan-kim.github.io/series/2019/12/10/frontend-dev-env-webpack-basic.html&quot;&gt;김정환블로그 - 프론트엔드 개발환경의 이해: 웹팩(기본)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://baeharam.netlify.app/posts/javascript/module&quot;&gt;JavaScript 표준을 위한 움직임: CommonJS와 AMD&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[ConstraintValidator를 이용한 커스텀 어노테이션 예외 처리]]></title><description><![CDATA[를 이용한 커스텀 어노테이션 예외 처리 커스텀 어노테이션이란? 사용자 입력의 유효성을 보다 세부적으로 입력을 검증해야 할 때, 커스텀 어노테이션을 이용하여 예외 검증 로직을 생성할 수 있다. 이 때, hibernate에서 제공하는 Constraint…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-06-21-custom-annotation/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-06-21-custom-annotation/</guid><pubDate>Fri, 25 Jun 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p style=&quot;font-family: sans-serif; text-align: center; color: #aaa; margin-bottom: 3em; font-size: 85%&quot;&gt;image origin: &lt;a href=&quot;https://www.c-sharpcorner.com/article/custom-data-annotation-validation-in-mvc/&quot;&gt;c-sharpcorner.com&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;&lt;code class=&quot;language-text&quot;&gt;ConstraintValidator&lt;/code&gt;를 이용한 커스텀 어노테이션 예외 처리&lt;/h2&gt;
&lt;h3&gt;커스텀 어노테이션이란?&lt;/h3&gt;
&lt;p&gt;사용자 입력의 유효성을 보다 세부적으로 입력을 검증해야 할 때, 커스텀 어노테이션을 이용하여 예외 검증 로직을 생성할 수 있다.&lt;/p&gt;
&lt;p&gt;이 때, hibernate에서 제공하는 &lt;a href=&quot;https://docs.jboss.org/hibernate/beanvalidation/spec/2.0/api/javax/validation/constraints/package-summary.html&quot;&gt;Constraint 어노테이션&lt;/a&gt; 을 사용할 수 있다.&lt;/p&gt;
&lt;h3&gt;커스텀 어노테이션의 장점&lt;/h3&gt;
&lt;p&gt;어노테이션이 가진 가장 큰 장점은 간결함이다.&lt;/p&gt;
&lt;p&gt;로직 흐름에 대한 컨텍스트가 응축돼 있어 적재적소에 사용된다면 불필요한 반복코드가 줄어든다.&lt;/p&gt;
&lt;p&gt;따라서 개발자가 비지니스 로직에 더 집중 할 수 있도록 만들어 준다.&lt;/p&gt;
&lt;h3&gt;커스텀 어노테이션과 &lt;code class=&quot;language-text&quot;&gt;Validator&lt;/code&gt; 구현&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ConstraintValidator&lt;/code&gt;를 구현하는 커스텀 &lt;code class=&quot;language-text&quot;&gt;Validator&lt;/code&gt;, 그리고 커스텀 어노테이션을 만들어 예외 처리를 해볼 것이다.&lt;/p&gt;
&lt;p&gt;지하철 구간이나 노선 추가 시 상행 종점과 하행 종점이 같으면 예외로 간주할 것이다.&lt;/p&gt;
&lt;p&gt;어노테이션의 이름은 &lt;code class=&quot;language-text&quot;&gt;NotEqual&lt;/code&gt;로 하자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Constraint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;validatedBy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NotEqualValidator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RUNTIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NotEqual&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;상행 역과 하행 역은 같을 수 없습니다.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Payload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;upStationId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;downStationId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RUNTIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;NotEqual&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Constraint&lt;/code&gt; 어노테이션의 &lt;code class=&quot;language-text&quot;&gt;validatedBy&lt;/code&gt; 값으로 &lt;code class=&quot;language-text&quot;&gt;NotEqualValidator.class&lt;/code&gt;를 지정하였다.
차후 NotEqualValidator 클래스를 만들어 검증하게 할 것이다.&lt;/p&gt;
&lt;h4&gt;&lt;code class=&quot;language-text&quot;&gt;@Target&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;만들어진 어노테이션이 부착될 수 있는 타입을 지정하는 것이다. &lt;code class=&quot;language-text&quot;&gt;TYPE&lt;/code&gt;은 클래스, 인터페이스, Enum에 부착할 수 있게 한다는 의미이다.&lt;/p&gt;
&lt;h4&gt;&lt;code class=&quot;language-text&quot;&gt;@Retention&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;어노테이션의 라이프 사이클, 즉 어노테이션이 언제까지 살아 남아 있을지를 정하는 것이다.&lt;/p&gt;
&lt;h4&gt;&lt;code class=&quot;language-text&quot;&gt;@interface&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;어노테이션 인터페이스를 의미한다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;이제 검증해 줄&lt;code class=&quot;language-text&quot;&gt;Validator&lt;/code&gt; 클래스를 만든다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;wooteco&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;subway&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;line&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;beans&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;BeanWrapperImpl&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;javax&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;validation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ConstraintValidator&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;javax&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;validation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ConstraintValidatorContext&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NotEqualValidator&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConstraintValidator&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;NotEqual&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; upStationId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; downStationId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;NotEqual&lt;/span&gt; constraintAnnotation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;upStationId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; constraintAnnotation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;upStationId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;downStationId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; constraintAnnotation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;downStationId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isValid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; object&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConstraintValidatorContext&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; upStationValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BeanWrapperImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;object&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPropertyValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;upStationId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; downStationValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BeanWrapperImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;object&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPropertyValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;downStationId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;upStationValue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;downStationValue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ConstraintValidator&lt;/code&gt; 인터페이스를 구현하는데, &lt;code class=&quot;language-text&quot;&gt;initialize&lt;/code&gt;메서드와 &lt;code class=&quot;language-text&quot;&gt;isValid&lt;/code&gt; 메서드를 오버라이딩한다.&lt;/p&gt;
&lt;h4&gt;&lt;code class=&quot;language-text&quot;&gt;initialize&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;어노테이션을 부착한 객체로부터 필드명을 가져와서 초기화한다.&lt;/p&gt;
&lt;h4&gt;&lt;code class=&quot;language-text&quot;&gt;isValid&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;isValid&lt;/code&gt; 메서드는 필수로 오버라이딩하여 예외 상황을 검증하도록 한다.&lt;/p&gt;
&lt;p&gt;어노테이션이 부착된 객체를 인자로 한다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;initialize&lt;/code&gt; 메서드에서 초기화했던 필드명을 이용해 어노테이션이 부착된 객체로부터 필드 값을 가져온다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;BeanWrapperImpl&lt;/code&gt;은 리플렉션을 이용해 필드 값을 가져온다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;이제 요청 객체에 어노테이션을 적용한다. 구간 추가 요청 객체와 노선 추가 요청 객체에 적용시키면 다음과 같다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@NotEqual&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;upStationId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;upStationId&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; downStationId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;downStationId&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SectionRequest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@NotNull&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; upStationId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@NotNull&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; downStationId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@NotEqual&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;upStationId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;upStationId&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; downStationId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;downStationId&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LineRequest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@NotNull&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; upStationId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@NotNull&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; downStationId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;주의할 점&lt;/h3&gt;
&lt;p&gt;어노테이션의 의도는 숨어있기 때문에 내부적으로 어떤 동작을 하게 되는지 명확하지 않다면 로직 플로우를 이해하기 어렵다.&lt;/p&gt;
&lt;p&gt;하물며 ‘커스텀’ 어노테이션은 그 부담을 가중시킬 수 있다.&lt;/p&gt;
&lt;p&gt;어노테이션 추가가 당장의 작업 속도를 끌어올릴 순 있지만, 장기적 관점에서 시의적절한 것인지를 공감할 수 있어야 한다.&lt;/p&gt;
&lt;p&gt;코드가 간결해진다는 장점 하나만 보고 커스텀 어노테이션을 남용하지 않게 주의해야 한다.&lt;/p&gt;
&lt;p&gt;반복적으로 사용하지도 않고, 특정 요청 외에는 사용할 일이 없어 보이는 유효성 검사라면 단순 메서드로 만들어 처리하는 것이 더 좋을 것이다.&lt;/p&gt;
&lt;h3&gt;결론&lt;/h3&gt;
&lt;p&gt;커스텀 어노테이션을 잘 이용하면 불필요한 반복코드가 줄어들고, &lt;strong&gt;비즈니스 로직에 더 집중&lt;/strong&gt;할 수 있다는 장점이 있다.&lt;/p&gt;
&lt;p&gt;다만, 커스텀 어노테이션은 의도와 목적을 명확히 하여 구성원간 공감대를 이룬 후 추가하는 것이 좋다.&lt;/p&gt;
&lt;h3&gt;Reference&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.baeldung.com/spring-mvc-custom-validator&quot;&gt;https://www.baeldung.com/spring-mvc-custom-validator&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://woowabros.github.io/experience/2020/06/26/custom-annotation.html&quot;&gt;https://woowabros.github.io/experience/2020/06/26/custom-annotation.html&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[AOP 입문자를 위한 개념 이해하기]]></title><description><![CDATA[이 글은 AOP 개념이 생소한 입문자들을 위한 포스팅입니다. 1. OOP의 한계 image…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-06-25-aop-transaction/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-06-25-aop-transaction/</guid><pubDate>Fri, 25 Jun 2021 12:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;이 글은 AOP 개념이 생소한 입문자들을 위한 포스팅입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1. OOP의 한계&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/56240505/123369146-27997800-d5b8-11eb-9be7-dfd7a34a4f86.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;객체지향 프로그래밍은 어플리케이션을 설계할 때 책임과 관심사에 따라 클래스를 분리합니다. 클래스가 단일 책임을 가지도록 분리함으로써 각 모듈의 응집도는 높아지고 결합도는 낮아집니다. 클래스를 변경하는 이유는 오직 한 가지이며, 어플리케이션의 한 부분에서 변경이 발생했을 때 그 파급효과가 시스템의 전체로 퍼져나가는 정도가 낮아집니다.&lt;/p&gt;
&lt;p&gt;그러나 전통적인 객체지향 설계 방식을 충실히 따르더라도 한 가지 아쉬운 점이 존재합니다. 위 사진처럼, 여러 클래스에 로깅이나 보안 및 트랜잭션 등 공통된 기능들이 흩어져 존재한다는 점입니다. 이렇게 어플리케이션 전반에 걸쳐 흩어져있는 공통되는 부가 기능들을 &lt;strong&gt;관심사&lt;/strong&gt;라고 합니다. 이러한 관심사를 어플리케이션의 핵심 비즈니스 로직 코드로부터 아름답게 분리하는 방법이 있을까요?&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;2. Transaction 코드&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;UserService.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserDao&lt;/span&gt; userDao&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PlatformTransactionManager&lt;/span&gt; transactionManager&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendMoneyToAnotherUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; senderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; receiverId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;TransactionStatus&lt;/span&gt; transaction &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; transactionManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTransaction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DefaultTransactionDefinition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;//로깅 관련 로직 추가&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;//보안 관련 로직 추가&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt; senderAccount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userDao&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAccountById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;senderId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt; receiverAccount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userDao&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAccountById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;receiverId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            userDao&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateMoney&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;senderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; senderAccount&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withdraw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            userDao&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateMoney&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;receiverId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; receiverAccount&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            transactionManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;transaction&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RuntimeException&lt;/span&gt; runtimeException&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            transactionManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rollback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;transaction&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; runtimeException&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;withdrawMoney&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;TransactionStatus&lt;/span&gt; transaction &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; transactionManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTransaction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DefaultTransactionDefinition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;//로깅 관련 로직 추가&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;//보안 관련 로직 추가&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt; account &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userDao&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAccountById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;senderId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            userDao&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateMoney&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;senderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; account&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withdraw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            transactionManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;transaction&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RuntimeException&lt;/span&gt; runtimeException&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            transactionManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rollback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;transaction&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; runtimeException&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;JPA가 아닌 JDBC 사용 환경을 가정한 코드입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;출금 및 입금을 처리하는 서비스 로직의 원자성 보장을 위해 내부적으로 트랜잭션을 적용한 코드입니다. 문제는 UserService의 클래스에는 &lt;strong&gt;출금 및 입금&lt;/strong&gt;이라는 핵심 비즈니스 로직 이외에도 트랜잭션 경계 설정이라는 &lt;strong&gt;부가 기능 관심사&lt;/strong&gt;가 산재하고 있습니다.&lt;/p&gt;
&lt;p&gt;현재 예제 코드는 부가 기능 관심사가 트랜잭션 하나 뿐이지만, 로깅이나 보안 등의 관심사가 추가되면 어떻게 될까요? &lt;code class=&quot;language-text&quot;&gt;sendMoneyToAnotherUser()&lt;/code&gt; 메서드가 더욱 비대해질 것입니다. 또한 트랜잭션과 로깅 및 보안 등의 부가 기능이 필요한 메서드마다 비슷한 코드를 중복해서 작성해야 하며, UserService 클래스 전체가 비대해지게 됩니다.&lt;/p&gt;
&lt;p&gt;가장 큰 문제는 트랜잭션이나 로깅 및 보안 등의 부가 기능에 관심을 가지는 클래스가 UserService에 국한되지 않다는 점입니다. UserService와 비슷하게 서비스 로직 수행 전 트랜잭션의 경계를 지정해주고 로깅이나 보안 등의 로직을 수행해야 하는 클래스가 100개가 더 있을 수 있습니다. 그 말은 곧 100개의 클래스에 UserService와 같이 중복되는 코드를 반복해서 작성해야 함을 의미합니다.&lt;/p&gt;
&lt;p&gt;만약 트랜잭션이나 로깅 및 보안 등의 부가 기능의 정책이나 API가 변경된다면 어떻게 될까요? 이를 사용하는 100개의 클래스가 모두 함께 수정되어야 합니다. 이는 다시 말해 100개의 클래스를 변경하는 이유는 비즈니스 로직의 변경 및 부가 기능의 변경 등 총 2가지라는 의미이며, 단일 책임 원칙을 위배하게 됩니다. 결국 서비스 클래스의 응집도가 떨어지면 가독성이 나빠지며, 변경할 부분이 명확하게 드러나지 않게 되는등 유지보수 측면에서 아쉬운 점이 많아집니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;3. Proxy를 활용한 리팩토링&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;UserService.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendMoneyToAnotherUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; senderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; receiverId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;UserServiceImpl.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Primary&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserServiceImpl&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserDao&lt;/span&gt; userDao&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendMoneyToAnotherUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; senderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; receiverId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt; senderAccount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userDao&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAccountById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;senderId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt; receiverAccount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userDao&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAccountById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;receiverId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        userDao&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateMoney&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;senderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; senderAccount&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withdraw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        userDao&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateMoney&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;receiverId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; receiverAccount&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;UserServiceProxy.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserServiceProxy&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt; target&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PlatformTransactionManager&lt;/span&gt; transactionManager&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendMoneyToAnotherUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; senderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; receiverId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;TransactionStatus&lt;/span&gt; transaction &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; transactionManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTransaction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DefaultTransactionDefinition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;//로깅 관련 로직 추가&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;//보안 관련 로직 추가&lt;/span&gt;
            target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendMoneyToAnotherUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;senderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; receiverId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            transactionManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;transaction&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RuntimeException&lt;/span&gt; runtimeException&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            transactionManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rollback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;transaction&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; runtimeException&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;프록시는 클라이언트가 사용하려고 하는 실제 타깃처럼 위장해서 요청을 받습니다. UserServiceProxy 클래스와 UserServiceImpl 클래스 모두 동일한 UserService 인터페이스를 구현합니다. UserServiceProxy 클래스의 UserService 타입 target 필드는 실제 핵심 비즈니스 로직을 구현한 UserServiceImpl 인스턴스를 주입합니다.&lt;/p&gt;
&lt;p&gt;프록시 객체에 트랜잭션 등 부가 기능 관련 로직을 위치시키고, 클라이언트 요청이 발생하면 실제 타깃 객체는 프록시로부터 요청을 위임받아 핵심 비즈니스 로직을 실행합니다. 이를 &lt;strong&gt;데코레이터 패턴&lt;/strong&gt;이라고 합니다.&lt;/p&gt;
&lt;p&gt;이와 같은 방법으로 핵심 비즈니스 로직과 부가 기능 관심사를 분리할 수 있었지만 여전히 한계가 분명합니다. 100개의 클래스가 이와 비슷한 기능을 요구한다면, 100개의 프록시 클래스를 생성하고 인터페이스 메서드를 일일이 구현해야 합니다.&lt;/p&gt;
&lt;h3&gt;3.1. Proxy를 편하게 생성하는 방법&lt;/h3&gt;
&lt;p&gt;다행히 이러한 별도의 프록시를 번거롭게 생성하는 작업을 생략하는 방법이 존재합니다. Java의 Reflection API를 이용하거나, Spring의 ProxyFactoryBean 등을 사용하는 것입니다. 해당 내용들을 전부다 다루기에는 분량이 너무 방대하여 관심있는 분들은 토비의 스프링 1권 6장을 참고하시길 바랍니다.&lt;/p&gt;
&lt;p&gt;Spring에서 Bean을 자동으로 프록시로 만들어주는 메커니즘이 존재합니다. 바로 &lt;code class=&quot;language-text&quot;&gt;DefaultAdvisorAutoProxyCreator&lt;/code&gt;라는 특별한 클래스입니다. &lt;code class=&quot;language-text&quot;&gt;BeanPostProcessor&lt;/code&gt;라는 Bean 후처리기 인터페이스를 확장한 클래스이며, 동작 플로우는 다음과 같습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Spring Container는 해당 후처리기가 Bean으로 등록되어 있으면 Bean들을 생성할 때 후처리기에 보내 후처리 작업을 요청합니다.&lt;/li&gt;
&lt;li&gt;Bean이 프록시 적용 대상이라면, 후처리기는 타깃 Bean을 프록시로 감싼 오브젝트로 바꿔치기 하여 Spring Container에게 반환합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이러한 메커니즘을 활용하기 위해서는 추가적인 정보를 제공해야 합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;어떤 작업을 수행할 것인가?&lt;/li&gt;
&lt;li&gt;해당 작업을 수행할 대상(즉, 적용 지점)은 누구인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2&gt;4. AOP(Aspect-Oriented Programming)&lt;/h2&gt;
&lt;p&gt;관점 지향 프로그래밍이란 OOP로 독립적으로 분리하기 어려운 &lt;strong&gt;부가 기능&lt;/strong&gt;을 모듈화하는 방식입니다. 이 글에서 트랜잭션 관리와 같은 부분이 바로 부가 기능 모듈이며, 이를 Aspect라고 합니다. 핵심 비즈니스 로직을 담고 있지는 않지만 어플리케이션에 부가됨으로써 의미를 갖는 특별한 모듈입니다. AOP는 핵심 비즈니스 로직과 부가 기능 Aspect를 분리하는 등 OOP를 보완하는 역할입니다.&lt;/p&gt;
&lt;p&gt;AOP 기능을 제공하는 프레임워크나 라이브러리를 사용하면, 번거로운 프록시 클래스 작성없이 UserService 비즈니스 로직에서 트랜잭션이라는 부가 기능 관심사를 간편하게 분리할 수 있습니다. 더불어 다양한 클래스가 Aspect를 재활용하며 공통 사용할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;4.1. Aspect 구성&lt;/h3&gt;
&lt;p&gt;Aspect는 부가될 기능을 정의한 Advice와, 해당 Advice를 어디에 적용할 지를 결정하는 Pointcut 정보를 가지고 있습니다.&lt;/p&gt;
&lt;h3&gt;4.2. 구현 방법&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Spring AOP를 활용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;이번 포스팅 내용처럼 프록시를 사용함으로써 부가 기능을 실행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;AspectJ를 사용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;AspectJ는 컴파일된 타깃의 클래스 파일을 수정하거나, 클래스가 JVM에 로딩되는 시점에 바이트 코드를 조작함으로써 AOP를 적용합니다.&lt;/li&gt;
&lt;li&gt;프록시 방식보다 더 다양한 지점에서 부가 기능을 부여할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.3. AspectJ를 활용한 리팩토링&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;TxAspect.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Aspect&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TxAspect&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PlatformTransactionManager&lt;/span&gt; transactionManager&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Pointcut&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;execution(* com.demo.user.UserService.send*(..))&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getUsers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Pointcut&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;execution(* com.demo.user.BankService.update*(..))&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getBanks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Around&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;getUsers() || getBakns()&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;applyTx&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProceedingJoinPoint&lt;/span&gt; joinpoint&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;TransactionStatus&lt;/span&gt; transaction &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; transactionManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTransaction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DefaultTransactionDefinition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; object &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; jointPoint&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;proceed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            transactionManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;transaction&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; object&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RuntimeException&lt;/span&gt; runtimeException&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            transactionManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rollback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;transaction&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; runtimeException&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;@Pointcut 애너테이션에 표현식을 달아 특정 패키지의 &lt;code class=&quot;language-text&quot;&gt;send&lt;/code&gt;나 &lt;code class=&quot;language-text&quot;&gt;update&lt;/code&gt;로 시작하는 메서드들을 실행할 때 AOP 부가 기능을 적용하겠다고 지정합니다.&lt;/li&gt;
&lt;li&gt;이러한 AOP 적용을 통해 UserSerivce의 메서드는 별도의 트랜잭션 관리 기능을 제거하고 핵심 비즈니스 로직만 남게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2&gt;5. 마치며&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;UserService.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserDao&lt;/span&gt; userDao&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendMoneyToAnotherUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; senderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; receiverId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt; senderAccount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userDao&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAccountById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;senderId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt; receiverAccount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userDao&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAccountById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;receiverId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        userDao&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateMoney&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;senderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; senderAccount&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withdraw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        userDao&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateMoney&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;receiverId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; receiverAccount&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;money&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;사실 우리가 흔히 보는 @Transactional 애너테이션 또한 AOP가 적용된 대표 사례입니다. Spring은 @Transactional이라는 애너테이션을 메서드에 부착하면 예외 발생 여부에 따라 해당 트랜잭션을 커밋하거나 롤백합니다.&lt;/p&gt;
&lt;p&gt;내부적으로 @Transactional이 붙은 오브젝트에 대해 프록시를 생성하고, @Transactional로 지정한 메서드를 호출하면 트랜잭션을 선언하겠다는 Pointcut과 Advice를 정보를 바탕으로 부가 기능 관심사를 수행합니다.&lt;/p&gt;
&lt;p&gt;AOP는 이해하기 어렵고, 제대로 사용하기 위해서는 꾸준히 학습해야 합니다. 이번 포스팅을 통해 AOP에 어느정도 감이 잡히셨다면, AspectJ 등을 추가 학습해보시면 어떨까요? 😁😁&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://seongmun-hong.github.io/spring/Spring-Aspect-Oriented-Programming(AOP)(1)&quot;&gt;SPRING - AOP(1) - ASPECT ORIENTED PROGRAMMING&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/anouar1611/spring-aop-introduction-and-concepts-of-aop-4oan&quot;&gt;Spring AOP Introduction and Concepts of AOP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jojoldu.tistory.com/72&quot;&gt;AOP 정리 (4)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;토비의 스프링 3.1(이일민 저)&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[bean wiring]]></title><description><![CDATA[스프링을 처음 공부하는 사람을 대상으로 작성한 글입니다. 빈 와이어링(wiring…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-06-25-bean-wiring/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-06-25-bean-wiring/</guid><pubDate>Fri, 25 Jun 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;스프링을 처음 공부하는 사람을 대상으로 작성한 글입니다.&lt;/p&gt;
&lt;h2&gt;빈 와이어링(wiring) 이란?&lt;/h2&gt;
&lt;p&gt;스프링을 사용하는 애플리케이션에서 각 객체가 필요한 다른 객체를 직접 찾거나 생성할 필요가 없다. 컨테이너가 협업할 객체에 대한 정보를 주기 때문이다.
애플리케이션 객체 간의 이러한 연관 관계 형성 작업이 바로 의존성 주입(Dependency Injection)의 핵심이며, 이를 보통 와이어링(wiring)이라 한다.&lt;/p&gt;
&lt;h2&gt;스프링 설정 옵션&lt;/h2&gt;
&lt;p&gt;스프링 컨테이너는 애플리케이션 내에서 빈 생성 및 DI를 통해 객체 사이의 관계를 조정해 준다. 하지만 어떤 빈을 생성할지 어떻게 엮을지는 개발자의 책임이다. 이를 하는 방법에는 세 가지 방법이 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;XML에서 명시적 설정&lt;/li&gt;
&lt;li&gt;자바에서의 명시적 설정&lt;/li&gt;
&lt;li&gt;내재되어 있는 빈을 찾아 자동으로 와이어링&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;어떤 것을 선택할지에는 정답이 없다. 프로젝트에 맞게 적절히 사용하면 된다. 다만 할 수 있다면 자동 설정을 하는 것이 좋다. 명시적인 설정이 적을수록 좋다.&lt;/p&gt;
&lt;h2&gt;빈을 XML로 와이어링하기&lt;/h2&gt;
&lt;p&gt;스프링 초반부터 XML은 설정을 나타내는 주요 방법이었다. 스프링이 XML과 오랜 기간 연관됐지만 유일한 옵션은 아니다. 스프링은 자바 기반 설정과 자동 설정을 강력하게 지원한다. XML이 첫 번째 선택은 아니다. 그렇지만 수많은 XML 기반의 스프링 설정이 사용되므로 XML을 어떻게 사용하는지 아는 것이 중요하다. 새로 작성하는 스프링이라면 자동설정과 JavaConfig를 사용하자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;스프링 XML 설정 파일 포맷&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;빈(Bean) 설정&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;bean&gt;&lt;/code&gt; 태그를 이용해 빈을 정의한다. 필수 속성은 class 속성 하나뿐이다. id는 빈의 id를 통해 참조할 때도 있는 경우에만 설정하면 된다. (getBean() 이나 ref 속성으로 빈을 참조하는 경우)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;bean id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;userDao&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;chap1.dao.UserDao&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;DI(Dependency Injection) 설정&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;생성자 주입&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;constructor-arg&gt;&lt;/code&gt; 요소&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;c:&lt;/code&gt;c-네임스페이스 (스프링 3.0 에서 도입) &lt;code class=&quot;language-text&quot;&gt;&amp;lt;constructor-args&gt;&lt;/code&gt; 를 좀 더 간결하게 표현&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;bean id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;registerService&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ems.member.service.StudentRegisterService&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;constructor&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;arg ref&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;studentDao&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;bean&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;프로퍼티 주입&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;property&gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;p:&lt;/code&gt; p-네임스페이스&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;bean id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;dataBaseConnectionInfoDev&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ems.member.DataBaseConnectionInfo&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
	&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;property name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;jdbcUrl&quot;&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;jdbc:oracle:thin:@localhost:1521:xe&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
	&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;property name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;userId&quot;&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;scott&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
	&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;property name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;userPw&quot;&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;tiger&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;bean&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Java로 빈 와이어링하기&lt;/h2&gt;
&lt;p&gt;대부분 컴포넌트 스캐닝과 오토 와이어링을 사용한 자동 설정을 선호하지만, 자동 설정은 옵션이 아니며 스프링을 명시적으로 설정해야 한다. 타사 라이브러리의 컴포넌트를 애플리케이션으로 와이어하려면 그 라이브러리의 소스 코드를 가지고 있지 않아 &lt;code class=&quot;language-text&quot;&gt;@Component&lt;/code&gt; 와 &lt;code class=&quot;language-text&quot;&gt;@Autowired&lt;/code&gt; 를 사용할 수 없다.
이 경우 두 가지의 선택방법이 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;li&gt;XML&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;JavaConfig는 매우 강력하고, 타입 세이프 하며, 리팩토링이 친화적이므로 명시적인 설정을 위해 선호하는 옵션이다. JavaConfig는 설정용 코드이다. 어떠한 비즈니스 로직도 포함하지 않고 비즈니스 로직에 영향을 주지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;설정 클래스 만들기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;JavaConfig 클래스 핵심은 &lt;code class=&quot;language-text&quot;&gt;@Configuration&lt;/code&gt; 애노테이션을 다는 것이다. 이 애노테이션은 이를 설정 클래스로 식별하고, 스프링 애플리케이션 컨텍스트에서 만들어진 빈의 자세한 내용이 포함될 수 있다는 것을 나타낸다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AppConfig&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;빈 선언하기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;JavaConfig 에서 빈을 선언하기 위해서 원하는 타입의 인스턴스를 만드는 메소드를 만들고, &lt;code class=&quot;language-text&quot;&gt;@Bean&lt;/code&gt; 애노테이션을 단다. &lt;code class=&quot;language-text&quot;&gt;@Bean&lt;/code&gt; 은 이 메소드가 스프링 애플리케이션 컨텍스트에서 빈으로 등록된 객체를 반환해야 함을 나타낸다. 메소드의 몸체는 궁극적으로 빈 인스턴스를 만드는 로직이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserDao&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;userDao&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserDao&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;JavaConfig 주입하기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;스프링의 모든 빈은 싱글톤으로 생성된다. 따라서 서로 다른 빈에서 같은 인스턴스를 참조할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CDPlayer&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cdPlayer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CompactDisc&lt;/span&gt; compactDisc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CDPlayer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;compactDisc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CDPlayer&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cdPlayer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CompactDisc&lt;/span&gt; compactDisc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;CDPlayer&lt;/span&gt; cdPlayer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CDPlayer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    cdPlayer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setCompactDisc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;compactDisc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cdPlayer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;자동으로 빈 와이어링&lt;/h2&gt;
&lt;p&gt;사용하기 편리한 것은 스프링 자동 설정보다 나은 것은 없다. 스프링이 자동으로 설정된다면 명시적인 와이어링 빈을 건드릴 필요가 없다.
스프링은 두 가지 방법으로 오토와이어링을 수행한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;컴포넌트 스캐닝 - 애플리케이션 컨텍스트에서 생성되는 빈을 자동으로 발견한다.&lt;/li&gt;
&lt;li&gt;오토와이어링 - 자동으로 빈 의존성을 충족시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;컴포넌트 스캐닝, 오토와이어링을 모두 사용하면 명시적 설정을 최소한으로 유지 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;발견 가능한 빈 만들기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Component&lt;/code&gt; 애노테이션을 달면 컴포넌트 클래스임을 나타내고, 클래스가 빈으로 만들어야 함을 스프링에 단서로 제공한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SgtPeppers&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CompactDisc&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이 클래스는 &lt;code class=&quot;language-text&quot;&gt;@Component&lt;/code&gt; 가 있으므로 스프링이 빈으로 만든다. 하지만 컴포넌트 스캐닝은 기본적으로 켜져 있지 않다. &lt;code class=&quot;language-text&quot;&gt;@Component&lt;/code&gt;가 있는 클래스를 찾기 위해 명시적인 설정을 작성할 필요가 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;@ComponentScan&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@ComponentScan&lt;/code&gt; 은 더이상의 설정 없이 설정 클래스로서 동일한 클래스를 기본 스캐닝한다. 스프링은 그 패키지와 하위 패키지를 스캔하고, &lt;code class=&quot;language-text&quot;&gt;@Componet&lt;/code&gt; 애너테이트된 클래스를 찾는다. 그리고 자동으로 스프링으로 빈을 만든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ComponentScan&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebMvcConfig&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;XML 설정을 통한 컴포넌트 스캐닝 활성화&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;설정한 패키지 이하에서 componentScan 이 동작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;context:&lt;/span&gt;component-scan&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;base-package&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;com.example.controller&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;컴포넌트 스캔된 빈 명명하기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;스프링 애플리케이션 컨텍스트에서 모든 빈은 ID가 주어진다. 빈에 ID를 명시적으로 설정해주지 않는다면 클래스 명으로부터 유추되어 할당된다.
빈은 클래스 명의 첫 글자를 소문자로 바꾼 ID를 가진다.
원하는 ID를 가지고 싶다면 &lt;code class=&quot;language-text&quot;&gt;@Component&lt;/code&gt; 에 원하는 ID를 값으로 넣어주면 된다. 또 &lt;code class=&quot;language-text&quot;&gt;@Named&lt;/code&gt; 를 사용하는 방법도 있지만 잘 사용하지 않는 방법이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;컴포넌트 스캐닝을 위한 베이스 패키지 세팅&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;애트리뷰트 없이 &lt;code class=&quot;language-text&quot;&gt;@ComponetScan&lt;/code&gt;을 사용하면 베이스 패키지로서 설정 클래스 패키지가 기본이라는 의미이다.
스프링 부트를 사용하면 &lt;code class=&quot;language-text&quot;&gt;@SpringBootApplication&lt;/code&gt;내의 &lt;code class=&quot;language-text&quot;&gt;@ComponetScan&lt;/code&gt;이 있다. 따라서 &lt;code class=&quot;language-text&quot;&gt;@SpringBootApplication&lt;/code&gt;이 있는 클래스가 기본이 된다.&lt;/p&gt;
&lt;p&gt;애플리케이션 코드와 분리하여 설정 코드를 패키지안에 보관할 경우 명시적으로 베이스 패키지를 설정해야한다.
이때 사용할 수 있는 방법은 &lt;code class=&quot;language-text&quot;&gt;@ComponetScan&lt;/code&gt;의 값 속성에 기술하는 것 뿐이다. 이때 사용하는 애트리뷰트에는 basePackages, basePackageClasses가 있다.
이름에 나타나듯이 기본이 되는 패키지를 설정하거나 클래스를 설정할 수 있다. 클래스를 사용하면 클래스가 어떤 패키지 안에 있는 컴포넌트 스캐닝을 위한 베이스 패키지로써 사용한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ComponetScan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.example&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;오토와이어링 되는 빈의 애노테이션&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;오토와이어링은 스프링이 빈의 요구사항과 매칭되는 애플리케이션 컨텍스트상에서 다른 빈을 찾아 빈 간의 의존성을 자동으로 만족하게 하도록 하는 수단이다.
오토와이어링을 수행 하도록 지정하기 위해 &lt;code class=&quot;language-text&quot;&gt;@Autowired&lt;/code&gt; 를 사용한다.&lt;/p&gt;
&lt;p&gt;@Autowired 의 사용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;생성자 주입&lt;/li&gt;
&lt;li&gt;setter 주입&lt;/li&gt;
&lt;li&gt;필드 주입&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;생성자를 사용하면 @Autowired 를 생략해줄 수 있다. 가급적이면 생성자를 통한 주입을 사용하는 것이 좋다. &lt;a href=&quot;https://woowacourse.github.io/javable/post/2020-07-18-di-constuctor-injection/&quot;&gt;왜 Constructor Injection을 사용해야 하는가&lt;/a&gt; 를 읽어보면 좋을 것 같다.&lt;/p&gt;
&lt;p&gt;생성자나 세터 메소드를 포함한 어떤 메소드이든 스프링은 메소드 파라미터에 의존성을 가진다. 한 개의 빈이 일치하면 그 빈은 와이어링 된다. 매칭되는 빈이 없다면 스프링은 애플리케이션 컨텍스트가 생성될 때 예외를 발생시킨다.
예외를 피하기 위해 &lt;code class=&quot;language-text&quot;&gt;@Autowired&lt;/code&gt; 에서 required 애트리뷰트를 false로 설정하면 된다.&lt;/p&gt;
&lt;h2&gt;요약&lt;/h2&gt;
&lt;p&gt;스프링에 빈을 와이어링하는 세 가지 기본 방법에는 자동설정, 명시적인 Java 기반 설정, 명시적인 XML 기반 설정이 있다. 이러한 기술들은 스프링 애플리케이션의 구성요소와 컴포넌트 간의 관계를 설명한다.
명시적인 설정과 관련된 유지보수 비용을 가능한 한 피하기 위해 자동 설정을 쓰는 것이 좋다. 명시적으로 설정하더라도 XML 설정보다는 Java 기반의 설정을 쓰는 것이 좋다.
Java 기반의 설정이 더 강력하고 타입세이프하며 리팩토링 할 수 있기 때문이다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[DispatcherServlet - Part 1]]></title><description><![CDATA[DispatcherServlet에 대해 1, 2편으로 나누어 설명한다. 1편에서는 DispatcherServlet이 무엇인지 알아보고, 이것을 설정하는 방법과 동작 흐름을 살펴본다. DispatcherServlet? DispatcherServlet…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-06-25-dispatcherservlet-part-1/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-06-25-dispatcherservlet-part-1/</guid><pubDate>Fri, 25 Jun 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;DispatcherServlet에 대해 1, 2편으로 나누어 설명한다. 1편에서는 DispatcherServlet이 무엇인지 알아보고, 이것을 설정하는 방법과 동작 흐름을 살펴본다.&lt;br/&gt;&lt;/p&gt;
&lt;!-- end --&gt;
&lt;br/&gt;
&lt;h2&gt;DispatcherServlet?&lt;/h2&gt;
&lt;p&gt;DispatcherServlet은 표현 계층(Presentation layer) 전면에서 HTTP 프로토콜을 통해 들어오는 모든 요청을 중앙집중식으로 처리하는 프론트 컨트롤러(Front Controller)이다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;DispatcherServlet은 Spring MVC의 핵심 요소이다. 클라이언트로부터 어떤 요청이 들어오면 서블릿 컨테이너(ex. 톰캣)이 요청을 받는다. 이때 공통 작업은 DispatcherServlet에서 처리하고, 이외 작업은 적절한 세부 컨트롤러로 위임한다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;DispatcherServlet도 &lt;sup&gt;*&lt;/sup&gt;Servlet이다. DispatcherServlet 클래스 코드를 살펴보면 &lt;code class=&quot;language-text&quot;&gt;HttpServlet&lt;/code&gt;을 상속하고 있다. (정확히는 DispatcherServlet -&gt; FrameworkServlet -&gt; HttpServletBean -&gt; HttpServlet 상속 구조를 갖고 있다.)&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DispatcherServlet&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FrameworkServlet&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FrameworkServlet&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletBean&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletBean&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServlet&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;&lt;sup&gt;*&lt;/sup&gt;Servlet이 무엇인지 궁금하다면, 테코블에 있는 아래 글을 참고해보자.&lt;br/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-05-23-servlet-servletcontainer/&quot;&gt;Servlet 과 ServletContainer&lt;/a&gt;&lt;br/&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h2&gt;설정 방법&lt;/h2&gt;
&lt;p&gt;DispatcherServlet을 직접 구현하는 방법에는 2가지가 있다. 첫번째는 &lt;code class=&quot;language-text&quot;&gt;web.xml&lt;/code&gt;에 DispatcherServlet을 선언하고 URI로 요청 경로를 매핑하는 것이다. 아래는 표준 J2EE 서블릿 설정 예시이다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;web-app&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;servlet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;servlet-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;example&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;servlet-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;servlet-class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;org.springframework.web.servlet.DispatcherServlet&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;servlet-class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;load-on-startup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;1&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;load-on-startup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;servlet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;servlet-mapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;servlet-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;example&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;servlet-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;url-pattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;*.form&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;url-pattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;servlet-mapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;web-app&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;두번째는 &lt;code class=&quot;language-text&quot;&gt;DispatcherServlet&lt;/code&gt;을 상속하는 것이다. 이때는 &lt;code class=&quot;language-text&quot;&gt;@WebServlet&lt;/code&gt; 어노테이션에 urlPatterns를 지정할 수 있다. @WebServlet 어노테이션을 사용하려면 무조건 &lt;code class=&quot;language-text&quot;&gt;HttpServlet&lt;/code&gt;을 상속해야 한다. 앞에서 말했듯이 DispatcherServlet은 HttpServlet을 상속하기 때문에 서블릿 클래스에서 DispatcherServlet을 상속해도 문제가 없다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@WebServlet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;helloServlet&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; urlPatterns &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/hello&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HelloServlet&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DispatcherServlet&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;동작 흐름&lt;/h2&gt;
&lt;p&gt;클라이언트에서 웹 요청을 했을 때, DispatcherServlet에서 이루어지는 간단한 동작 흐름을 아래와 같다. 해당 이미지에서 &lt;code class=&quot;language-text&quot;&gt;Front Controller&lt;/code&gt; 부분이 바로 DispatcherServlet을 나타낸다.&lt;br/&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50176238/123233756-48f55800-d515-11eb-8a94-a8bf3086780b.png&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;이미지 출처: &lt;a href=&quot;https://docs.spring.io/spring-framework/docs/3.0.0.RC2/spring-framework-reference/html/ch15s02.html&quot;&gt;15.2 The DispatcherServlet&lt;/a&gt;&lt;br/&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br/&gt;
&lt;p&gt;좀 더 자세한 동작 흐름은 아래와 같다. 전체적으로 Spring MVC가 어떻게 흘러가는지 한눈에 보여준다.&lt;br/&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/50176238/123378608-f3c64e80-d5c7-11eb-9c27-1a491aa74c56.png&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;이미지 출처: &lt;a href=&quot;https://terasolunaorg.github.io/guideline/5.0.1.RELEASE/en/Overview/SpringMVCOverview.html#overview-of-spring-mvc-processing-sequence&quot;&gt;Overview of Spring MVC Architecture&lt;/a&gt;&lt;br/&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;① DispatcherServlet으로 클라이언트의 &lt;code class=&quot;language-text&quot;&gt;웹 요청(HttpServletRequest)&lt;/code&gt;가 들어온다.&lt;/li&gt;
&lt;li&gt;웹 요청을 LocaleResolver, ThemeResolver, MultipartResolver 인터페이스 구현체에서 분석한다.&lt;/li&gt;
&lt;li&gt;② 웹 요청을 &lt;code class=&quot;language-text&quot;&gt;HandlerMapping&lt;/code&gt;에 위임하여 해당 요청을 처리할 Handler(Controller)를 탐색한다.&lt;/li&gt;
&lt;li&gt;③ 찾은 Handler를 실행할 수 있는 &lt;code class=&quot;language-text&quot;&gt;HandlerAdapter&lt;/code&gt;를 탐색한다.&lt;/li&gt;
&lt;li&gt;④, ⑤ 찾은 HandlerAdapter를 사용해서 Handler의 메소드를 실행한다. 이때, Handler의 반환값은 &lt;code class=&quot;language-text&quot;&gt;Model&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;View&lt;/code&gt;이다.&lt;/li&gt;
&lt;li&gt;⑥ View 이름을 &lt;code class=&quot;language-text&quot;&gt;ViewResolver&lt;/code&gt;에게 전달하고, ViewResolver는 해당하는 View 객체를 반환한다.&lt;/li&gt;
&lt;li&gt;⑦ DispatcherServlet은 View에게 Model을 전달하고 화면 표시를 요청한다. 이때, Model이 null이면 View를 그대로 사용한다. 반면, 값이 있으면 View에 Model 데이터를 렌더링한다.&lt;/li&gt;
&lt;li&gt;⑧ 최종적으로 DispatcherServlet은 &lt;code class=&quot;language-text&quot;&gt;View 결과(HttpServletResponse)&lt;/code&gt;를 클라이언트에게 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;해당 흐름은 @Controller를 기준으로 설명했다. @RestController의 경우 ⑥, ⑦ 과정이 생략된다. 즉, ViewResolver를 타지 않고 반환값에 알맞는 MessageConverter를 찾아 응답 본문을 작성한다.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;지금까지 DispatcherServlet의 정의, 설정 방법, 동작 흐름을 간단하게 짚어봤다. 이어지는 2편에서는 DispatcherServlet의 동작 원리를 코드와 함께 이해해보자.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mangkyu.tistory.com/18&quot;&gt;[Spring]Dispatcher-Servlet이란?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/3.0.0.RC2/spring-framework-reference/html/ch15s02.html&quot;&gt;15.2 The DispatcherServlet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/binghe819/TIL/blob/master/Spring/MVC/Spring%20MVC%20flow.md&quot;&gt;Spring MVC 처리 과정&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/spring-dispatcherservlet&quot;&gt;An Intro to the Spring DispatcherServlet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Optional 과 null 에 대해 ⌥␀]]></title><description><![CDATA[런타임에서 발생하는 NullPointException 방어를 위해 만들어둔 로직체크는 코드의 가독성과 유지 보수성이 떨어진다. 어떻게 null…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-06-20-optional-vs-null/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-06-20-optional-vs-null/</guid><pubDate>Sun, 20 Jun 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;런타임에서 발생하는 NullPointException 방어를 위해 만들어둔 로직체크는 코드의 가독성과 유지 보수성이 떨어진다. 어떻게 null 을 다루면 좋을 지에 대한 해결책을 함수형 언어에서 찾았다. 함수형 언어는 &lt;b&gt;존재하지 않을 수도 있는 값&lt;/b&gt;에 대한 별도의 타입을 가지고 있다. 개발자들은 여러가지 API 를 통해 간접적으로 값에 접근할 수 있다. 자바는 함수형 언어로부터 영감을 받아  &lt;b&gt;자바 8&lt;/b&gt;에 처음 Optional 이 도입 되었다.&lt;/p&gt;
&lt;h2&gt;Optional?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;값이 존재할 수도, 존재하지 않을 수도 있는 값을 포장한 객체&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;b&gt;Null 이 될 가능성을 가진 값&lt;/b&gt;을 객체로 감싸는 래퍼 클래스다. 즉, Optional 에 포장된 객체는 하나의 원소 혹은 Null 원소가 되는 것을 뜻한다. Null 을 직접 다루면 위험한 상황이 발생하거나 굉장히 까다롭다. 이를 Optional 객체에 포장함으로써 유연한 처리가 가능해 진다. Null 을 Optional 에 포장하게 되면 Null 을 값으로 보고 로직을 구현할 수 있다.&lt;/p&gt;
&lt;p&gt;Null 을 직접 다루면 생기는 위험한 상황의 예시다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;phoneNumberFromCustomer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Order&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCustomer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPhoneNumber&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같이 주문(order)한 고객(customer)의 휴대폰 전화번호(phoneNumber)를 반환하는 기능이 있다. 만약 주문(order)이 null 로 넘어온다면 order.getCustomer( )는 null, order.getPhoneNumber( )도  null 이 된다. null 을 리턴하게 되면서 호출 부에 NPE 를 발생시킬 수 있는 상황이 발생한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Order&lt;/span&gt; order &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; phoneNumber &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;phoneNumberFromCustomer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// null 이 반환됨&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;phoneNumber&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// NullPointException 발생&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Optional 사용하기&lt;/h2&gt;
&lt;p&gt;Optional 사용법에 대해서는 &lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html&quot;&gt;공식문서&lt;/a&gt;를 참고해 보는 것을 추천한다.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Optional 객체 생성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;세 가지 정적 팩토리 메서드를 사용해서 만들 수 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Optional.empty( ) : 비어있는(null) Optional 객체를 가져온다.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; optStation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Optional.of(T value) : 객체를 담은 Optional 객체를 생성한다. 이 경우 null 이 들어오면 NPE 가 발생한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Line&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; optLine &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Line&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;1호선&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Optional.ofNullable(T value) : 비어있거나 값이 있을 수 있는 객체를 생성한다. (null 여부를 확신할 수 없을 때)&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Section&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; optNullSection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ofNullable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Section&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; optSection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ofNullable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Section&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;잠실역&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;몽촌토성역&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;850m&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;b&gt;Optional 값에 접근하기&lt;/b&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;get( )&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 값을 가져오고, 비어있는 Optional 객체에 대해서는 NoSuchElementException 예외를 던진다.&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; optStation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;잠실역&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt; station &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; optStation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;orElse(T other)&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 비어있는 Optional 객체에 대해서 orElse 로부터 넘어온 인자를 반환한다.&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; optStation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt; station &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; optStation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orElse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;잠실역&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;orElseGet(Supplier&amp;#x3C;? Extends T&gt; other)&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 비어있는 Otional 객체에 대해서 orElseGet 으로부터 넘어온 함수형 인자를 통해 생성된 객체를 전달한다.&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; optStation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt; station &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; optStation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orElseGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;임시역&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;orElseThrow(Supplier&amp;#x3C;? Extends X&gt; exceptionSupplier)&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 비어있는 Optional 객체에 대해서 orElseThrow 로부터 넘어온 함수형 인자를 통해 예외를 던진다.&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; optStation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt; station &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; optStation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UnsupportedOperationException&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Optional 의 장점&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;명시적으로 변수에 대한 null 가능성을 표현할 수 있다.&lt;/li&gt;
&lt;li&gt;null 체크를 직접하지 않아도 된다.&lt;/li&gt;
&lt;li&gt;Null Point Exception 이 발생할 가능성이 있는 값을 직접 다룰 필요가 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Optional 의 단점&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Wrapper 클래스이기 때문에 두 개의 참조를 가지므로 생성 비용이 비싸다.&lt;/li&gt;
&lt;li&gt;직렬화 불가능하기 때문에 클래스의 인스턴스 필드로 사용하면 안된다.&lt;/li&gt;
&lt;li&gt;필드로 사용하기 위해 고안된 것이 아니기 때문에 값을 반환하는 용도로 사용해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Optional 의 의문&lt;/h2&gt;
&lt;p&gt;Optional 객체의 공백 여부를 확인하기 위해서 Optional 의 isPresent 메서드를 통해 다음과 같은 작업을 한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findStationName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; optStation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;optStation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isPresent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; optStation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;존재하지 않는 역&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;만약 Optional 을 사용하지 않고 똑같은 기능을 구현한다면 어떻게 될까?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findStationName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt; station&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nonNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;station&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; station&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;존재하지 않는 역&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 되면 Optional 을 사용해서 얻는 이점이 없어 보인다. Optional 을 처음 접하거나 Optional 의 개념을 제대로 이해하지 못한 상황이라면 다음과 같은 고민을 하게 된다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;어떻게 Optional 의 null 체크를 해야할까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;정답은 &lt;b&gt;Optional 을 사용한 시점에서 null 체크는 할 필요가 없다.&lt;/b&gt; Optional 을 사용하는 이유는 &lt;b&gt;null 처리를 직접하지 않고 Optional 클래스에 위임&lt;/b&gt;하기 위해서 사용한다.&lt;/p&gt;
&lt;h3&gt;올바른 Optional 사용법은 다음과 같다.&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findStationName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; optStation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; optStation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orElse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Station&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;존재하지 않는 역&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Optional 을 정확하게 이해하고 사용 한다면, 위와 같이 한 줄의 코드로 작성할 수 있어야 한다. 조건문으로 null 체크를 하던 수준에서 &lt;b&gt;함수형 사고&lt;/b&gt;로 전환해야 한다.&lt;/p&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;무분별한 Optional 사용은 되려 독이 될 수 있다. Optional 이 필요하다고 생각되는 부분에서 Optional 을 사용한다면 유연한 코드를 구사할 수 있을 것이다. Null 이라는 것은 개발자가 평생 짊어지고갈 까다로운 녀석이다. 까다로운 녀석을 어떻게 다룰지 한 번 곰곰히 생각해본다면 의외로 간단하게 다룰 수 있을지도 모르겠다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html&quot;&gt;공식문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jdm.kr/blog/234&quot;&gt;자바 옵셔널(Java Optional)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.daleseo.com/java8-optional-before/&quot;&gt;자바8 Optional 1부: 빠져나올 수 없는 null 처리의 늪&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.daleseo.com/java8-optional-after/&quot;&gt;자바8 Optional 2부: null을 대하는 새로운 방법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;모던 자바 인 액션&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Typescript Minimal-Start - 2. 좀 더 나아가기]]></title><description><![CDATA[0.Intro 1편에서 타입스크립트의 기본적인 문법에 대해 알아보았습니다.
이번 포스팅에서는 타입스크립트에서 조금 더 심화된 내용을 다뤄봅니다.
시리즈의 컨셉을 따라 개념적 맥락을 파악할 수 있는 수준에서 최소한의 내용을 다룹니다. Table of…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-06-20-typescript-minimal-start-2/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-06-20-typescript-minimal-start-2/</guid><pubDate>Sun, 20 Jun 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;0.Intro&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://tecoble.techcourse.co.kr/&quot;&gt;1편&lt;/a&gt;에서 타입스크립트의 기본적인 문법에 대해 알아보았습니다.
이번 포스팅에서는 타입스크립트에서 조금 더 심화된 내용을 다뤄봅니다.
시리즈의 컨셉을 따라 개념적 맥락을 파악할 수 있는 수준에서 최소한의 내용을 다룹니다.&lt;/p&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#%ED%83%80%EC%9E%85-%EC%B6%94%EB%A1%A0&quot;&gt;타입 추론&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#%ED%83%80%EC%9E%85-%EA%B0%95%EC%A0%9C&quot;&gt;타입 강제&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#%EC%A0%9C%EB%84%A4%EB%A6%AD&quot;&gt;제네릭&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#%EC%9C%A0%EC%9A%A9%ED%95%9C-%EC%9C%A0%ED%8B%B8%EB%A6%AC%ED%8B%B0-%ED%83%80%EC%9E%85&quot;&gt;유용한 유틸리티 타입&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;타입 추론&lt;/h2&gt;
&lt;p&gt;1편에서 봤던 변수와 함수에 대한 타입선언 기억나시나요?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/121903378-30da4780-cd63-11eb-9eeb-8d680962374e.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;타입스크립트 컴파일러는 위처럼 개발자가 명시적으로 지정하지 않은 부분에 대해서 어떤 타입인지 추론을 합니다.&lt;/p&gt;
&lt;p&gt;이를 테면&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/123501263-993bf980-d67e-11eb-8519-0eea344e3ed1.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;변수선언에 타입을 따로 지정해주지 않았음에도 변수에 1000이라는 숫자 리터럴이 할당되었기에 타입스크립트 컴파일러는 해당 변수의 타입이 &lt;code class=&quot;language-text&quot;&gt;number&lt;/code&gt;라고 추론하는 것이죠.
이는 변수선언 뿐만이 아닌 타입이 명시적으로 선언될 수 있는 함수의 인자, 리턴 타입 등에 적용됩니다.&lt;/p&gt;
&lt;p&gt;이런 타입추론을 통해 개발자는 모든 변수, 리턴타입에 일일이 타입을 지정해주지 않아도 원활하게 타입스크립트 프로그래밍을 할 수 있게 됩니다.
생산성과 타입안정성을 어느정도 교환하는 것이라고 보아도 무방합니다.&lt;/p&gt;
&lt;p&gt;타입스크립트의 타입추론은 생각보다 똑똑하게 동작합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/123501506-64c93d00-d680-11eb-9555-1db3f36c1368.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;forEach메서드에 전달하는 콜백함수에 대한 인자를 추론하기도 하구요.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/123501557-c2f62000-d680-11eb-80cb-4bbb9277d0d7.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;addEventListener메서드에 전달하는 콜백함수의 인자를 event타입이라고 추론하기도 합니다.
이를 코드의 위치에 따라 타입을 추론한다하여, Contextual Typing이라고 합니다.&lt;/p&gt;
&lt;h3&gt;더 나은 타입안정성을 위한 옵션1: noImplicitAny&lt;/h3&gt;
&lt;p&gt;일반적인 함수의 인자 타입과 같이 타입을 직접 명시해주지 않으면, 어떤 타입을 받을 것인지 추론할 수 없는 경우가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/123502037-33527080-d684-11eb-85a7-1f490ad1dc3b.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;이때 타입스크립트 컴파일러는 해당 요소의 타입을 &lt;code class=&quot;language-text&quot;&gt;any&lt;/code&gt;로 추론합니다.
따라서 해당 요소는 모든 타입을 수용할 수 있게 됩니다.
이는 암시적으로 타이핑이 이뤄지는 것이기에 장기적으로 봤을 때 버그가 될 수 있는 지점입니다. 다른 사용자 (혹은 미래의 나)가 함수의 의도를 오인하고 잘못된 타입의 인자를 전달해줄 수 있기 때문입니다. 그러므로 지양해야 합니다.&lt;/p&gt;
&lt;p&gt;이때 &lt;code class=&quot;language-text&quot;&gt;noImplicitAny&lt;/code&gt;라는 &lt;a href=&quot;https://typescript-kr.github.io/pages/tsconfig.json.html&quot;&gt;tsconfig.json&lt;/a&gt; 파일의 옵션을 &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;로 지정해주는 것이 좋습니다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;noImplicitAny&lt;/code&gt;옵션은 명시되지 않은 타입을 컴파일러가 추론할 때 &lt;code class=&quot;language-text&quot;&gt;any&lt;/code&gt;로 추론하게 되면 컴파일 에러로 나타내는 기능입니다.&lt;/p&gt;
&lt;h3&gt;더 나은 타입안정성을 위한 옵션2: noImplicitReturn&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;noImplicitAny&lt;/code&gt;와 같은 맥락의 tsconfig.json 파일의 옵션으로, 함수가 조건에 따라 명시적으로 &lt;code class=&quot;language-text&quot;&gt;return&lt;/code&gt;을 하지 않았을 때 에러를 발생시켜주는 기능입니다.&lt;/p&gt;
&lt;p&gt;함수 내에 &lt;code class=&quot;language-text&quot;&gt;return&lt;/code&gt;문이 존재하지 않는다면 컴파일러는 해당 함수의 리턴타입을 &lt;code class=&quot;language-text&quot;&gt;void&lt;/code&gt;로  추론합니다. 따라서 문제가 발생하지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/123502439-66e2ca00-d687-11eb-8c68-f5992ed0ee15.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;하지만 위와 같이 분기에 따라 반환이 될 수도 있고, 되지 않을 수도 있는 경우에 &lt;code class=&quot;language-text&quot;&gt;noImplicitReturn&lt;/code&gt;옵션이 &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;라면 컴파일 에러가 발생합니다. 반환값이 &lt;code class=&quot;language-text&quot;&gt;undefined&lt;/code&gt;가 되기에 추후에 발생할 수 있는 버그를 미연에 방지할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;더 나은 타입안정성을 위한 옵션3: strictNullCheck&lt;/h3&gt;
&lt;p&gt;객체 인덱싱을 하려고할 때 객체가 런타임에서 &lt;code class=&quot;language-text&quot;&gt;undefined&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;null&lt;/code&gt;이 나올 가능성이 있다면 에러를 출력합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; twoDimensionArray &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; found &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; twoDimensionArray&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; el&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;found&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/123765840-137bb080-d901-11eb-9e12-c0bb3dda35f7.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;find 메서드는 대상을 찾지 못하면 &lt;code class=&quot;language-text&quot;&gt;undefined&lt;/code&gt;를 반환합니다. 따라서 &lt;code class=&quot;language-text&quot;&gt;found[1]&lt;/code&gt;이라는 인덱싱은 가능할 수도, 불가능할 수도 있는 식입니다. &lt;code class=&quot;language-text&quot;&gt;strictNullCheck&lt;/code&gt;라는 옵션에 &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;값을 주면 해당 라인에 에러를 나타냅니다.&lt;/p&gt;
&lt;h2&gt;타입 강제&lt;/h2&gt;
&lt;p&gt;이렇게 똑똑한 타입스크립트 컴파일러지만 모든 경우에 타입추론이 제대로 동작하는 것은 아닙니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/123502923-b2e33e00-d68a-11eb-8ecc-e99cee8fd060.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;임의의 버튼을 가져와 click 이벤트를 발생시키는 코드입니다.
타입이 &lt;code class=&quot;language-text&quot;&gt;Element&lt;/code&gt;여서 개발자 입장에선 타입추론이 온전하게 진행되지 않은 것이죠. 사실 컴파일러 입장에서는 어떤 요소를 가져올지 알 수 없기 때문에 (가져오는 요소는 런타임에서 알 수 있기 때문에) 이는 자연스러운 결과입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/123502984-09e91300-d68b-11eb-9f6f-609f7edc673f.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;as&lt;/code&gt; 라는 키워드를 통해 타입을 강제로 지정했습니다.
그러니 &lt;code class=&quot;language-text&quot;&gt;click&lt;/code&gt; 메서드를 호출하는 부분에서도 에러가 발생하지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/123503031-4d438180-d68b-11eb-8055-c70c84b8e648.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;위와 같이 강제를 할 수도 있습니다. 하지만 최근에는 전자의 문법이 더 흔하게 쓰이는 것으로 보입니다.&lt;/p&gt;
&lt;p&gt;타입 강제는 사용자가 임의로 타입을 정하는 부분이므로 사용에 주의가 필요합니다.
최대한 사용을 자제하되 불가피하게 사용되어야 하는 부분에 정확한 타입을 강제하여 주세요.&lt;/p&gt;
&lt;h2&gt;제네릭&lt;/h2&gt;
&lt;p&gt;제네릭은 타입스크립트만의 특별한 문법이 아닙니다. 그러므로 다른 언어(대표적으로 C++이나 JAVA)에서 제네릭에 대해 배운 적이 있다면 빠르게 넘어가실 수 있을 겁니다.&lt;/p&gt;
&lt;p&gt;처음 접했다면 쫄지 마세요. 대부분의 제네릭은 그렇게 어렵지 않습니다.&lt;/p&gt;
&lt;p&gt;함수를 선언할 때 매개변수(어떤 값이 전달될지 모르는 상태)를 정의해서 임의로 사용합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;sayHi&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;아, 안녕!&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// target이라는 어떤 값이 들어올지 모르는 매개변수를 정해서 임의로 사용했습니다.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;제네릭은 어떤 타입을 전달받을지 미리 알 수 없는 상황에서, 임의의 타입변수를 정해서 사용하는 문법입니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 표현식&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; genericFunc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 선언문&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;genericFunc&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;genericFunc&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;genericFunc&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;2&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;genericFunc&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;숫자&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Argument of type &apos;string&apos; is not assignable to parameter of type &apos;number&apos;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;꺽쇠(&amp;#x3C;&gt;)를 사용해서 임의의 &lt;em&gt;&lt;strong&gt;타입변수&lt;/strong&gt;&lt;/em&gt;(예시 코드에서는 &lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt;)를 지정합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/123505396-d497f180-d699-11eb-8b22-e39b4d8b5cf7.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;타입변수를 전달하지 않았다면 컴파일러가 전달된 함수 인자를 바탕으로 타입추론을 합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; genericFunc2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T2&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;genericFunc2&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;헬로&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// [123, &apos;헬로&apos;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같이 쉼표(,)를 이용해서 여러 개의 타입변수를 전달할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;그런데 지금의 제네릭은 모든 타입을 허용하는 듯합니다. 함수의 인자도 타입을 명시해서 전달할 수 있는 타입을 제한하는데 제네릭은 타입변수를 어떻게 제한할까요?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; genericFunc3 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;genericFunc3&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;hi&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// [&apos;hi&apos;]&lt;/span&gt;

&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;genericFunc3&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Type &apos;boolean&apos; does not satisfy the constraint &apos;string | number&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;extends&lt;/code&gt;라는 키워드를 통해 타입을 제한할 수 있습니다. &lt;code class=&quot;language-text&quot;&gt;boolean&lt;/code&gt; 타입을 타입변수로 전달했더니 컴파일러가 에러를 나타냈습니다.&lt;/p&gt;
&lt;h2&gt;유용한 유틸리티 타입&lt;/h2&gt;
&lt;p&gt;타입스크립트는 사용자가 만든 타입의 활용도를 더 끌어올리기 위해 유틸리티 타입을 제공합니다. 유틸리티 타입은 기존의 타입을 활용해
새로운 타입을 만들어주는 타입이라고 정의할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;Pick&amp;#x3C;Type, Keys&gt;&lt;/h3&gt;
&lt;p&gt;객체타입 &lt;code class=&quot;language-text&quot;&gt;Type&lt;/code&gt;에서 해당 객체타입이 가진 프로퍼티 key가 조합된 &lt;code class=&quot;language-text&quot;&gt;Keys&lt;/code&gt;를 전달해 &lt;code class=&quot;language-text&quot;&gt;Keys&lt;/code&gt; 프로퍼티만으로 구성된 객체타입을 새롭게 구성합니다.&lt;/p&gt;
&lt;p&gt;새롭게 만들려는 타입이 기존 객체타입에 의존성을 갖고 있고, 필요없는 프로퍼티를 제외해 가져오는 것보다 필요한 것만 명시해서 가져오는게 이득이 클 경우 사용합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserInfo&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 유저의 정보를 담는 타입&lt;/span&gt;
  id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  password&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  address&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserLogin&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Pick&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;UserInfo&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;id&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;password&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 로그인에 필요한 ID와 password 프로퍼티만 가져온다.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/123508107-4b3beb80-d6a8-11eb-9ca9-2135bc073c38.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;의도한대로 id와 password 프로퍼티만으로 구성된 객체타입이 만들어졌습니다.&lt;/p&gt;
&lt;h3&gt;Omit&amp;#x3C;Type, keys&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Omit&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;Pick&lt;/code&gt;과 반대 용도로 사용하는 유틸리티 타입입니다. omit, 단어의 뜻 그대로 해당 객체타입 &lt;code class=&quot;language-text&quot;&gt;Type&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;keys&lt;/code&gt; 프로퍼티를 제외한 새로운 객체타입을 만드는데 사용합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SignUpForm&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 회원가입시에 사용하는 객체 타입&lt;/span&gt;
  id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  password&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  passwordForCheck&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  address&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SignUpSubmit&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Omit&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;SignUpForm&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;passwordForCheck&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 서버에 전달할 때는 password 하나만 전달함으로 passwordForCheck를 생략한다.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/123508274-4e83a700-d6a9-11eb-98c3-c25c800d2ccc.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;passwordForCheck&lt;/code&gt; 프로퍼티가 생략된 객체 타입이 만들어졌습니다.&lt;/p&gt;
&lt;h3&gt;Exclude&amp;#x3C;Type, ExcludedUnion&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Exclude&lt;/code&gt; 타입은 &lt;code class=&quot;language-text&quot;&gt;Omit&lt;/code&gt;과 역할이 유사하나, union타입에서 멤버타입을 제외한 타입을 생성하고자 할 때 사용합니다. Omit은 객체타입, Exclude는 union 타입. 기억해두면 편리합니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Color&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;red&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;blue&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;pink&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;orange&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RedishColor&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Exclude&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Color&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;blue&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 색상 중 blue를 제외한 붉은 컬러만 union 타입으로 구성한다.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Partial&lt;Type&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Partial&lt;/code&gt;은 전달한 객체타입의 모든 프로퍼티를 optional로 만듭니다.
&lt;code class=&quot;language-text&quot;&gt;Partial&lt;/code&gt;을 통해 생성된 타입은 원 객체타입 중 프로퍼티를 전달하지 않거나, 일부만 전달해도 적합한 타입으로 인정받지만, 원 객체타입에 없는 프로퍼티를 전달하면 에러를 발산합니다.&lt;/p&gt;
&lt;p&gt;개인적으로 기존 객체를 업데이트하는 함수를 만들때 인자로 Partial 타입을 사용하니 굉장히 코드가 깔끔해졌습니다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserInfo&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  password&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  address&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserInfoUpdate&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Partial&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;UserInfo&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/44419181/123509052-5134cb00-d6ae-11eb-9874-9dd983bef2b1.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;UserInfo&lt;/code&gt;의 모든 프로퍼티가 optional로 바뀐 객체타입이 만들어졌습니다.&lt;/p&gt;
&lt;h2&gt;2부 마침&lt;/h2&gt;
&lt;p&gt;이렇게 1~2부에 걸쳐 타입스크립트의 문법을 다뤄보았습니다.
3부에서는 프론트엔드 프레임워크 React에 실제로 적용해보는 시간과 타입스크립트를 사용하면서 애매한 부분에 대한 내용을 다뤄보겠습니다.&lt;/p&gt;
&lt;p&gt;이해가 되지 않는 부분이 있다면 댓글로 남겨주세요.&lt;/p&gt;
&lt;p&gt;감사합니다!&lt;/p&gt;
&lt;h3&gt;Reference&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/type-inference.html&quot;&gt;https://www.typescriptlang.org/docs/handbook/type-inference.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/utility-types.html&quot;&gt;https://www.typescriptlang.org/docs/handbook/utility-types.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/2/generics.html&quot;&gt;https://www.typescriptlang.org/docs/handbook/2/generics.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions&quot;&gt;https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[@RequestMapping]]></title><description><![CDATA[우리는 client에서 오는 요청을 처리하기 위한 api url을 매핑할 때 Spring의 이라는 어노테이션을 사용한다. Spring은 사용자의 편리를 위해 RequestMapping을 http에서 지원하는 4가지 method…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-06-18-spring-request-mapping/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-06-18-spring-request-mapping/</guid><pubDate>Fri, 18 Jun 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;우리는 client에서 오는 요청을 처리하기 위한 api url을 매핑할 때 Spring의 &lt;code class=&quot;language-text&quot;&gt;@RequestMapping&lt;/code&gt;이라는 어노테이션을 사용한다. Spring은 사용자의 편리를 위해 RequestMapping을 http에서 지원하는 4가지 method인 &lt;code class=&quot;language-text&quot;&gt;@GetMapping&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;@PostMapping&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;@PutMapping&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;@DeleteMapping&lt;/code&gt;과 추가적으로 &lt;code class=&quot;language-text&quot;&gt;@PatchMapping&lt;/code&gt;까지 여러가지 방법의 매핑 방식으로 확대하여 제공하기도 한다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@RequestMapping&lt;/code&gt;을 붙이는 이유에 대해서 간략히 설명하고자 아래 코드를 구현해보았다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExampleController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/example&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Example&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getExample&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Example&lt;/span&gt; example &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Example&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;example&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;example&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;로컬로 프로그램을 실행시킨다는 가정 하에 우리는 &lt;code class=&quot;language-text&quot;&gt;http://localhost:8080/example&lt;/code&gt; 로 &lt;strong&gt;GET&lt;/strong&gt; 요청을 보냈을 때 아래와 같은 결과를 얻을 수 있다.&lt;br&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/43775108/123607948-4ce8e900-d839-11eb-87d2-f748fe6fcb86.png&quot; alt=&quot;get 요청 테스트&quot;&gt;&lt;/p&gt;
&lt;p&gt;여기서 알 수 있다싶이 RequestMapping은 요청이 들어왔을 시에 컨트롤러와 매핑해주고, 그 컨트롤러를 실행시켜 응답을 받는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;그렇다면 이 &lt;code class=&quot;language-text&quot;&gt;@RequestMapping&lt;/code&gt; 어노테이션은 어떻게 위와 같은 일을 수행하는 것일까?&lt;/p&gt;
&lt;h2&gt;@RequestMapping의 동작 방식&lt;/h2&gt;
&lt;p&gt;SpringBoot 어플리케이션이 실행되면 어플리케이션에서 사용할 bean들을 담을 ApplicationContext를 생성하고 초기화한다.&lt;/p&gt;
&lt;p&gt;requestMapping 어노테이션을 붙인 메서드들이 handler에 등록되는 것은 ApplicationContext가 refresh되는 과정에서 일어난다.&lt;/p&gt;
&lt;p&gt;ApplicationContext의 refresh SpringApplication이 실행될 때 일어난다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;SpringApplication&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;DemoApplication&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;메인 메서드인 어플리케이션 실행 메서드에 들어가면 아래와 같이 ApplicationContext.refresh() 메서드가 존재하는 것을 알 수 있다.
&lt;img src=&quot;https://user-images.githubusercontent.com/43775108/123607480-e6fc6180-d838-11eb-8862-d3e17b7e0271.png&quot; alt=&quot;ApplicationContext refresh&quot;&gt;&lt;/p&gt;
&lt;p&gt;이 때 생성되는 Spring Application 구동을 위한 수많은 Bean들이 존재한다. 이들에 대해서 알고 싶다면 &lt;a href=&quot;https://pplenty.tistory.com/6&quot;&gt;이 글&lt;/a&gt;을 보면 자세히 알 수 있다. 이렇게 등록되는 Bean 중 하나가 &lt;code class=&quot;language-text&quot;&gt;RequestMappingHandlerMapping&lt;/code&gt;이고, 이 빈이 우리가 &lt;code class=&quot;language-text&quot;&gt;@RequestMapping&lt;/code&gt;으로 등록한 메서드들을 가지고 있다가 요청이 들어오면 매핑해주는 역할을 수행한다.&lt;/p&gt;
&lt;p&gt;그렇다면 이 &lt;code class=&quot;language-text&quot;&gt;RequestMappingHandlerMapping&lt;/code&gt;은 어떻게 우리가 등록한 Controller들을 찾아 매핑해주는 것일까?&lt;/p&gt;
&lt;p&gt;우선, &lt;code class=&quot;language-text&quot;&gt;WebMvcAutoConfiguration&lt;/code&gt;이라는 기본 설정을 해주는 클래스에 아래와 같이 RequestMappingHandlerMapping을 생성하고 빈으로 등록하는 메서드가 존재한다.&lt;br&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/43775108/123607585-ff6c7c00-d838-11eb-947a-49ff672e8163.png&quot; alt=&quot;create requestMappingHandlerMapping&quot;&gt;&lt;/p&gt;
&lt;p&gt;RequestMapping을 찾아 등록하는 과정은 다음과 같다. 아래 함수들은 차례로 상속 관계를 가진 &lt;code class=&quot;language-text&quot;&gt;AbstractHandlerMethodMapping &amp;lt;- RequestMappingInfoHandlerMapping &amp;lt;- RequestMappingHandlerMapping&lt;/code&gt; 클래스들의 메서드이다.&lt;/p&gt;
&lt;p&gt;afterPropertiesSet() -&gt; initHandlerMethods -&gt; processCandidateBean(beanName) -&gt; isHandler(beanType) -&gt; detectHandlerMethods(beanName) -&gt; registerHandlerMethod(handler, invocableMethod, mapping)&lt;br&gt;
모든 과정을 거쳐 MappingRegistry라는 클래스의 아래 필드 변수들에 저장되는 것이다.&lt;br&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/43775108/123607734-1c08b400-d839-11eb-8b40-da290f0a8ad3.png&quot; alt=&quot;mappingRegistry-fields&quot;&gt;&lt;br&gt;
요청이 들어오면 빈으로 등록된 HandlerMapping이 변수들을 찾아서 Adapter를 거쳐 실행하는 것이다.&lt;/p&gt;
&lt;p&gt;어떠한 함수들을 거치는지 더욱 자세히 알고 싶다면 &lt;a href=&quot;https://pplenty.tistory.com/7&quot;&gt;이 글&lt;/a&gt;을 참고할 수 있다.&lt;/p&gt;
&lt;h2&gt;@RequestMapping이 받는 옵션&lt;/h2&gt;
&lt;p&gt;이제는 @RequestMapping이 받을 수 있는 옵션에 대해서 알아보도록 하자.&lt;br&gt;
RequestMapping은 기본적으로 같은 url과 모든 옵션이 같을 수 없다.&lt;br&gt;
이렇게 지정하는 옵션은 handler에서 매핑하는 기준이 된다.&lt;br&gt;
value, method, params, headers, consumes, produces 중에서 하나라도 달라야지 올바르게 handler가 매핑해줄 수 있다.&lt;/p&gt;
&lt;h3&gt;value&lt;/h3&gt;
&lt;p&gt;value는 연결할 url을 말한다. 보통 호스트 주소와 포트 번호를 제외하고 api 설계 규약에 따라 이름을 짓는다.
다음과 같이 설정할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/example&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;value를 제외하고 다른 옵션이 주어지지 않을 경우에는 &lt;code class=&quot;language-text&quot;&gt;value =&lt;/code&gt;을 생략할 수도 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/example&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/AntPathMatcher.html&quot;&gt;Ant pattern&lt;/a&gt;을 적용한 url mapping도 가능하다.&lt;br&gt;
예를 들어, 아래와 같이 매핑하면 &lt;code class=&quot;language-text&quot;&gt;/example/1&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;/example/2&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;/example/123&lt;/code&gt; 전부 다 연결할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/example/**&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;{}사이에 변수를 넣어서 url을 매핑하는 것도 가능하다. 이렇게 매핑한 경우 메서드 인자로 &lt;code class=&quot;language-text&quot;&gt;@PathVariable&lt;/code&gt;을 id로 받아야만 한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/example/{id}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;method&lt;/h3&gt;
&lt;p&gt;RequestMethod로 명명되어 있는 아래 8가지 중 하나를 사용하는 것이 일반적이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RequestMethod&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token constant&quot;&gt;GET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;HEAD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;POST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PUT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PATCH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DELETE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OPTIONS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;TRACE&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;적용 예시는 다음과 같다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RequestMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;GET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/example&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;GET, POST, PUT, PATCH, DELETE 메서드에 대해서는 각각에 맞는 메서드 옵션이 적용된 어노테이션이 존재해서 다음과 같이 사용할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/example&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;이렇게 하면 코드가 간결해지고 가독성이 좋아져 많이 사용되는 기법이다.&lt;/p&gt;
&lt;h3&gt;params&lt;/h3&gt;
&lt;p&gt;params는 api url을 &lt;code class=&quot;language-text&quot;&gt;/example?id=1&amp;amp;password=2&lt;/code&gt;와 같이 전달하고 싶을 때 사용하는 것으로 params에 전달한 것과 일치하는 param이 붙으면 이 메서드로 매핑해준다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RequestMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;GET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/example&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Example&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getExample&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; paramId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Example&lt;/span&gt; example &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Example&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;example&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; paramId &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;example&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;headers&lt;/h3&gt;
&lt;p&gt;header의 정보를 전달해주는 옵션이다. 예를 들어 아래와 같이 content-type을 지정해준 경우&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token annotation punctuation&quot;&gt;@PostMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/example/{text}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; headers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;content-type=text/plain&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Example&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getExample3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@PathVariable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Example&lt;/span&gt; example &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Example&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;example&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;example&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;들어오는 헤더는 다음과 같다.&lt;br&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/43775108/123607821-2fb41a80-d839-11eb-822d-0176b79e9b2b.png&quot; alt=&quot;header&quot;&gt;&lt;/p&gt;
&lt;p&gt;전달할 수 있는 헤더의 종류는 content-type 외에도 다양한데 &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Headers&quot;&gt;여기&lt;/a&gt;서 알 수 있다.&lt;/p&gt;
&lt;h3&gt;produces&lt;/h3&gt;
&lt;p&gt;response의 accept-request header가 특정 옵션으로 반환될 것을 지정하는 옵션이다.&lt;br&gt;
응답이 무엇인지 미리 예측하는 것이 불가능하기 때문에 같은 url로 요청이 들어왔을 때 produces 옵션으로 구분해서 실행하는 것은 불가능하다.&lt;br&gt;
그리고 &lt;code class=&quot;language-text&quot;&gt;@RestController&lt;/code&gt;에서 ResponseEntity를 반환하는 경우에는 기본으로 &lt;code class=&quot;language-text&quot;&gt;application/json&lt;/code&gt;으로 값이 지정되어 있기 때문에 특정 옵션을 설정할 수 없다.&lt;br&gt;
produces는 필수 옵션이 아니므로 따로 지정해주지 않아도 &lt;code class=&quot;language-text&quot;&gt;application/json&lt;/code&gt;으로 기본 값이 전달되거나 와일드카드로 값이 전달된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token annotation punctuation&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/test&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; produces &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MediaType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TEXT_PLAIN_VALUE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getExample&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;example&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;위와 같이 사용할 수 있다.&lt;/p&gt;
&lt;h3&gt;consumes&lt;/h3&gt;
&lt;p&gt;request의 content-type request header가 일치하는 것을 찾는 옵션이다.&lt;br&gt;
요청에 들어오는 인자 값이 무엇인지에 따라서 같은 url, method임에도 구분이 가능하다.&lt;br&gt;
하지만 &lt;code class=&quot;language-text&quot;&gt;@RequestBody&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;@ModelAttribute&lt;/code&gt;의 경우 HttpMessageConverter에서 객체로 변환하는 과정을 지나며 기본 값으로 &lt;code class=&quot;language-text&quot;&gt;application/json&lt;/code&gt;이 지정되어 있기 때문에 특정 옵션을 줄 수 없다.&lt;br&gt;
consumes는 필수 옵션이 아니므로 지정해주지 않아도 된다.&lt;br&gt;
이 경우 &lt;code class=&quot;language-text&quot;&gt;@RequestBody&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;@ModelAttribute&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;application/json&lt;/code&gt;이 기본 값, &lt;code class=&quot;language-text&quot;&gt;@RequestParam&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;@PathVariable&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;text/plan&lt;/code&gt;이 기본 값으로 전달된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token annotation punctuation&quot;&gt;@PostMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/produce{example}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; consumes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MediaType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TEXT_PLAIN_VALUE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Example&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getExample5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@PathVariable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; example&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Example&lt;/span&gt; example1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Example&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;example&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;example1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/spring-requestmapping&quot;&gt;RequestMapping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://2ham-s.tistory.com/292&quot;&gt;@RequestMapping의 produces,Content-Type,Consumes란?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Prototype, 그리고 Class]]></title><description><![CDATA[Prototype JS…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-06-14-prototype/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-06-14-prototype/</guid><pubDate>Mon, 14 Jun 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Prototype&lt;/h2&gt;
&lt;p&gt;JS에서 프로토타입이란, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes&quot;&gt;객체의 특성을 다른 객체로 상속하는 것을 가능하게 하는 메커니즘&lt;/a&gt;이다. (엄밀히 말하자면 ‘상속’이라는 단어는 적절치 않을 수 있다. 그 이유는 밑에서 다시 다루어 보겠지만, 일단은 일관된 이해를 돕기 위해 이 표현을 계속 사용하려고 한다.)&lt;/p&gt;
&lt;h3&gt;&lt;code class=&quot;language-text&quot;&gt;[[Prototype]]&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;간단한 예시 코드를 살펴보자. 여기서 &lt;code class=&quot;language-text&quot;&gt;Object.create()&lt;/code&gt;는 지정된 프로토타입 객체와 연결된 빈 객체를 반환하는 메서드이지만, 여기서는 서로 다른 두 객체를 이어주는 연결 고리 정도로만 생각해도 좋다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; obj &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; anotherObj &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;anotherObj&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// {}&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;anotherObj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 2&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;콘솔의 결과를 확인해보면, 분명 &lt;code class=&quot;language-text&quot;&gt;anotherObj&lt;/code&gt;는 빈 객체지만 &lt;code class=&quot;language-text&quot;&gt;obj&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;a&lt;/code&gt;를 참조하여 값을 출력했다. &lt;code class=&quot;language-text&quot;&gt;anotherObj&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;obj&lt;/code&gt;의 프로퍼티를 어떻게 찾을 수 있었을까?&lt;/p&gt;
&lt;p&gt;기본적으로 JS의 모든 객체는 &lt;code class=&quot;language-text&quot;&gt;[[Prototype]]&lt;/code&gt;이라고 하는 내부 프로퍼티를 가진다. 이 프로퍼티의 역할을 아주 단순하게 표현하자면 자신에게 연결된 프로토타입 객체에 대한 참조이다. 위의 예시에서 &lt;code class=&quot;language-text&quot;&gt;anotherObj&lt;/code&gt;는 해당 프로퍼티를 통해 연결된 객체인 &lt;code class=&quot;language-text&quot;&gt;obj&lt;/code&gt;로 접근한 뒤, 일치하는 키 값을 찾은 것이다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Object.create()&lt;/code&gt;를 사용하지 않아도 대부분의 객체는 생성 시 각자의 프로토타입 객체와 연결되어 있다. 그리고 이처럼 연결된 두 객체 간의 관계를 &lt;strong&gt;Prototype Link&lt;/strong&gt;라고 한다. (확인해보고 싶다면 브라우저 콘솔에서 아무 타입의 값을 변수에 할당한 뒤 &lt;code class=&quot;language-text&quot;&gt;__proto__&lt;/code&gt; 프로퍼티 접근 혹은 &lt;code class=&quot;language-text&quot;&gt;getPrototypeOf()&lt;/code&gt; 메서드 호출을 시도해보길 바란다.)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;[[Prototype]]&lt;/code&gt;은 내부 프로퍼티이기 때문에 특정 객체의 프로토타입 객체로 직접 접근하는 공식적인 방법은 존재하지 않는다. 하지만 대부분의 모던 브라우저에서 &lt;code class=&quot;language-text&quot;&gt;__proto__&lt;/code&gt;라는 비공식 프로퍼티를 지원하며, ES6부터 &lt;code class=&quot;language-text&quot;&gt;getPrototypeOf()&lt;/code&gt; 메서드를 통해 객체에 연결된 프로토타입 객체를 확인할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;만약 &lt;code class=&quot;language-text&quot;&gt;anotherObj&lt;/code&gt;에서 다른 키 값을 탐색한다면 어떻게 될까?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;anotherObj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// undefined&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;어떠한 객체에서 프로퍼티의 존재 여부를 확인하는 과정을 간단히 요약해 보자면,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;탐색을 시작한 객체에서 프로퍼티가 존재하지 않으면 연결된 프로토타입 객체로 이동하여 탐색한다.&lt;/li&gt;
&lt;li&gt;해당 프로토타입 객체에도 프로퍼티가 존재하지 않으면 그 객체와 연결된 프로토타입 객체로 이동하여 탐색한다. (프로토타입 객체도 자신의 프로토타입 객체를 가질 수 있다.)&lt;/li&gt;
&lt;li&gt;프로토타입 객체의 연쇄적인 흐름 속에서 프로퍼티를 찾았다면 그 값을 반환한다. 하지만 더 이상 연결된 프로토타입 객체가 없는 데도 프로퍼티가 존재하지 않는다면 탐색을 종료하고 &lt;code class=&quot;language-text&quot;&gt;undefined&lt;/code&gt;를 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;3번 과정에서 언급했다시피, 객체와 객체를 이어주는 링크는 한 개가 아닐 수도 있다. 이처럼 다수의 객체가 서로 링크로 연결되어 있다면 이 링크들의 연쇄적인 흐름을 &lt;strong&gt;Prototype Chain&lt;/strong&gt;이라고 한다.&lt;/p&gt;
&lt;p&gt;결국, 객체는 이 체인을 통해 자신이 가지고 있지 않은 특정 프로퍼티에 대한 참조를 다른 객체에 &lt;strong&gt;위임&lt;/strong&gt;하여 해결할 수 있다.&lt;/p&gt;
&lt;h3&gt;&lt;code class=&quot;language-text&quot;&gt;Object.prototype&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;JS에서 모든 Prototype Chain의 종착점에는 &lt;code class=&quot;language-text&quot;&gt;Object.prototype&lt;/code&gt;이라는 객체가 자리하고 있다. 이 객체는 모든 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects&quot;&gt;Built-in 객체&lt;/a&gt;들의 원형(原型) 객체이며, Built-in 객체들의 다양한 공통 특성들을 한 데 가진다. (원형의 의미를 가진 ‘프로토타입’이라는 단어가 사용된 이유를 여기서 짐작해볼 수 있겠다.)&lt;/p&gt;
&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 648px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/66f861a9d26c53bbdd6541ace7883efb/244f0/object_prototype.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 19.2%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAzElEQVR42j2PW5aDIBBE3Ug0Kij4TBDwrcQks/8d1UCfM/NxT/VHdXV1NE4L3p8fjNOGQdWYJ4l9reGOFkJw3OIccZL9c4tT0uSeE3/zPWVEVJQV7DjD2AmDHqA9xmoo1aHtem/i6B+KUIPxHouuVyj9Hi8k6qZHmnGCAmXV4DhfmJfN6wV3fbFsDqVsfYCFNiOWdacvAqe7sB8O63b4wzOVEbIB44KaRiE5XA2BwRyWgzE0ejw1nspQI1m11CD4c1YSWV6QMiZoTlKGX2aceznRveeaAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;object prototype&quot; title=&quot;&quot; src=&quot;/static/66f861a9d26c53bbdd6541ace7883efb/244f0/object_prototype.png&quot; srcset=&quot;/static/66f861a9d26c53bbdd6541ace7883efb/0eb09/object_prototype.png 500w,
/static/66f861a9d26c53bbdd6541ace7883efb/244f0/object_prototype.png 648w&quot; sizes=&quot;(max-width: 648px) 100vw, 648px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Object.prototype&lt;/code&gt;과 연결된 객체마다 필요에 의하여 기능들을 상속받고 확장해 나간다. 그러한 과정에서 공통된 특성들은 하나의 프로토타입 객체에 모아 그것들을 참조하게 하여 메모리 자원을 보다 효율적으로 사용할 수 있다. 또한 프로토타입 객체에서 파생된 객체들의 프로퍼티를 수정하고 싶을 때, 그것들의 프로토타입 객체에만 선언함으로써 리팩토링의 강점을 취할 수도 있다.&lt;/p&gt;
&lt;h3&gt;Prototype Object&lt;/h3&gt;
&lt;p&gt;그렇다면 아까부터 얘기한 프로토타입 객체는 도대체 뭐 하는 녀석일까? 프로토타입 객체란 공통된 특성(공통된 정도는 내장 Built-in 혹은 사용자 정의 등 쓰임새에 따라 다를 수 있다.)을 상속하기 위한 템플릿 객체이며, 생성자 역할을 할 수 있는 모든 함수에 내장된 객체다. 그렇기 때문에 함수의 &lt;code class=&quot;language-text&quot;&gt;prototype&lt;/code&gt; 프로퍼티를 통해 해당 객체에 접근과 객체 프로퍼티의 수정이 가능하다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;construct&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;construct&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// {constructor: construct(), __proto__: Object}&lt;/span&gt;

construct&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;a &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;construct&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// {a: 2, constructor: construct(), __proto__: Object}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;프로토타입 객체의 &lt;code class=&quot;language-text&quot;&gt;constructor&lt;/code&gt;라는 프로퍼티는 해당 객체의 생성자 함수를 가리킨다. 또한 이 객체 역시 &lt;code class=&quot;language-text&quot;&gt;__proto__&lt;/code&gt;를 가지고 있는 걸로 보아, 프로토타입 객체는 상위 프로토타입 객체의 확장이면서도 그 자체로 하위 객체의 프로토타입 객체가 될 수 있는 상대적 개념임을 짐작할 수 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;프로토타입 체인 상에서는 객체를 잇는 링크가 존재할 뿐, 이것들을 계층적으로 구분하는 건 조금 어색해 보인다. 하지만 프로토타입 객체의 상대적 관계에 대한 이해를 돕기 위해 상, 하위로 나누어 표현했다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Prototype Based- vs. Class Based-&lt;/h2&gt;
&lt;h3&gt;Class in ES6&lt;/h3&gt;
&lt;p&gt;JS는 객체 지향 언어지만 기존의 클래스 기반 프로그래밍 방식을 따르지 않으며 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85_%EA%B8%B0%EB%B0%98_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D&quot;&gt;프로토타입 기반 프로그래밍 방식&lt;/a&gt;으로 동작한다. 하지만 ES6 이후로 JS 내에서도 클래스 문법이 출현하였는데, 잠시 두 코드가 메커니즘적으로 어떤 차이를 가질지 고민해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;say&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Hi, My name is &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; crew &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Kyle&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;crew&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;say&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Hi, My name is Kyle.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;say&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Hi, My name is &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; crew &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Kyle&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;crew&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;say&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Hi, My name is Kyle.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;두 코드의 결과는 동일하며, 동작 방식도 거의 차이가 없다. 즉 ES6 클래스 문법은 단지 생성자-프로토타입 패턴을 숨기기 위한 &lt;a href=&quot;https://www.techopedia.com/definition/10212/syntactic-sugar&quot;&gt;Syntactic Sugar&lt;/a&gt;에 불과하다. JS는 여전히 프로토타입 기반 언어이다.&lt;/p&gt;
&lt;p&gt;그렇다면 클래스 기반 언어와 JS에서 다루는 클래스의 동작 방식은 구체적으로 어떻게 다른 걸까? (모든 프로토타입 기반 언어에서 클래스를 가지고 있는 것은 아니기에 JS로 한정하여 비교해보려 한다.)&lt;/p&gt;
&lt;p&gt;클래스 기반 언어에서 클래스란 **‘객체의 상태와 기능을 정의한 틀’**이며 생성될 객체에 대한 일종의 청사진으로 이해할 수 있다. 그리고 이것의 인스턴스 화를 통해 생성된 객체를 인스턴스라고 한다. 여기서 인스턴스는 클래스에서 정의된 모든 특성에 대한 복사본(copy)이기 때문에 두 개념은 개별적이며 서로 간의 참조는 존재하지 않는다.&lt;/p&gt;
&lt;p&gt;반면에 프로토타입 기반 언어에서는 클래스 - 인스턴스의 개념이 아닌 오직 객체 개념만 존재한다. 다시 말하지만, ES6에 도입된 클래스 문법은 실제 클래스 패턴에 대한 모방일 뿐이다. 이것은 JS의 생성자 함수 패턴을 좀 더 클래스답게 보이기 위한 장치이며 생성된 객체는 함수의 프로토타입 객체에 연결되어 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; kyle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;kyle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__proto__&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// {constructor: class Person, __proto__: Object}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;도입부에서 프로토타입 체계에서 ‘상속’이라는 표현은 적절치 않을 수 있다고 이야기했는데, 클래스 기반 언어의 관점으로 보았을 때 상속의 동작은 결국 인스턴스 화와 마찬가지로 copy에 기반한 동작이기 때문이다. JS에서 객체들이 동작하는 방식을 고려해본다면 상속보다는 오히려 링크를 통한 위임으로 바라보는 것이 더 적절하지 않을까.&lt;/p&gt;
&lt;h3&gt;Trade-off&lt;/h3&gt;
&lt;p&gt;클래스 기반 언어와 비교해보았을 때, 프로토타입 기반 언어에 대한 우려의 시선도 적지 않다. 이는 주로 정적 vs. 동적 타입 시스템을 비교하며 우려하는 점과 대체로 비슷하다. 정확성, 안전성, 예측성의 관점에서 클래스는 대체로 타입과 비슷하므로 동적으로 프로퍼티를 변경할 수 있는 프로토타입에 비해 더 신뢰성 높은 코드를 작성할 수 있다고 하는 게 주요 의견이다.&lt;/p&gt;
&lt;p&gt;프로토타입 방식의 장점을 설명할 때, 링크를 통해 여러 객체의 프로퍼티를 동적으로 수정할 수 있어 리팩토링의 이점을 얻을 수 있다고 이야기 한 적이 있다. 하지만 반대로 생각해보면 어느 한 군데에서 프로토타입 객체를 건드리게 되었을 때 다른 쪽에서 사이드이펙트가 나타나지 않으리라는 것을 보장할 수 없는 것도 위와 비슷한 맥락이다.&lt;/p&gt;
&lt;p&gt;아직은 정적, 동적 타입 / 클래스, 프로토타입 기반 방식 등 여러 방법론에 대한 경험이 많지 않아 확실히 무엇이 좋고 나쁜지 얘기하기는 힘들 것 같다. 하지만 우선 JS를 사용하는 입장이라면, 언어에서 채택한 방식을 다른 방식과 비교하며 최대한 다양한 관점에서 이해하고 코드를 작성하는 것이 프로그래밍의 숙련도를 높이는 방법이라고 생각한다.&lt;/p&gt;
&lt;h3&gt;Reference&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@bluesh55/javascript-prototype-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-f8e67c286b67&quot;&gt;프로토타입 이해하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/this%20%26%20object%20prototypes/ch5.md&quot;&gt;You Don’t Know JS: this &amp;#x26; Object Prototypes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.debuggr.io/js-prototype-in-depth/&quot;&gt;JavaScript - The prototype chain in depth&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[단위 테스트 vs 통합 테스트 vs 인수 테스트]]></title><description><![CDATA[소프트웨어 테스트에는 여러 유형들이 있다. 각 테스트는 목적, 방법 등에 따라 차이점을 가진다.
이번 글에서는 그 중 단위 테스트, 통합 테스트, 인수 테스트에 대해 개념을 정리하려 한다. 단위 테스트(Unit Test…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-05-25-unit-test-vs-integration-test-vs-acceptance-test/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-05-25-unit-test-vs-integration-test-vs-acceptance-test/</guid><pubDate>Tue, 25 May 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;소프트웨어 테스트에는 여러 유형들이 있다. 각 테스트는 목적, 방법 등에 따라 차이점을 가진다.
이번 글에서는 그 중 단위 테스트, 통합 테스트, 인수 테스트에 대해 개념을 정리하려 한다.&lt;br/&gt;&lt;/p&gt;
&lt;!-- end --&gt;
&lt;br/&gt;
&lt;h2&gt;단위 테스트(Unit Test)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;단위 테스트는 응용 프로그램에서 테스트 가능한 가장 작은 소프트웨어를 실행하여 예상대로 동작하는지 확인하는 테스트이다.&lt;br/&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;단위 테스트에서 테스트 대상 단위의 크기는 엄격하게 정해져 있지 않다. 하지만, 일반적으로 클래스 또는 메소드 수준으로 정해진다.
단위의 크기가 작을수록 단위의 복잡성이 낮아진다. 따라서, 단위 테스트를 활용하여 동작을 표현하기 더 쉬워진다.
즉, 테스트 대상 단위의 크기를 작게 설정해서 단위 테스트를 최대한 간단하고 디버깅하기 쉽게 작성해야 한다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;소프트웨어를 개발할 때, 소프트웨어 내부 구조나 구현 방법을 고려하여 개발자 관점에서 테스트한다.
그러므로 단위 테스트는 소프트웨어 내부 코드에 관련한 지식을 반드시 알고 있어야 하는 화이트박스 테스트이다.
단위 테스트는 TDD와 함께 할 때 특히 더 강력해진다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;프로그래밍 언어마다 단위 테스트에서 사용하는 프레임워크가 다르다. Java는 주로 &lt;code class=&quot;language-text&quot;&gt;JUnit&lt;/code&gt;으로 테스트한다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;아래 코드는 &lt;code class=&quot;language-text&quot;&gt;JUnit5&lt;/code&gt;로 작성한 단위 테스트 코드이다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@DisplayName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;자동차가 전진한다&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;moveCar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; car &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;dani&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// when&lt;/span&gt;
    car&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;car&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPosition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@DisplayName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;자동차가 멈춘다&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;stopCar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt; car &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Car&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;dani&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// when&lt;/span&gt;
    car&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;car&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPosition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h2&gt;통합 테스트(Integration Test)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;통합 테스트는 단위 테스트보다 더 큰 동작을 달성하기 위해 여러 모듈들을 모아 이들이 의도대로 협력하는지 확인하는 테스트이다.&lt;br/&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;통합 테스트는 단위 테스트와 달리 개발자가 변경할 수 없는 부분(ex. 외부 라이브러리)까지 묶어 검증할 때 사용한다.
이는 DB에 접근하거나 전체 코드와 다양한 환경이 제대로 작동하는지 확인하는데 필요한 모든 작업을 수행할 수 있다.
그러나, 통합 테스트가 응용 프로그램이 완전하게 작동하는 걸 무조건 증명하지는 않는다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;통합 테스트의 장점은 단위 테스트에서 발견하기 어려운 버그를 찾을 수 있다는 점이다.
예를 들어, 통합 테스트에서는 환경 버그(ex. 싱글 코어 CPU에서는 잘 실행되나 쿼드 코어 CPU에서는 잘 실행되지 않음)이 발생할 수 있다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;한편, 통합 테스트의 단점은 단위 테스트보다 더 많은 코드를 테스트하기 때문에 신뢰성이 떨어질 수 있다는 점이다.
또, 어디서 에러가 발생했는지 확인하기 쉽지 않아 유지보수하기 힘들다는 점도 있다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;스프링부트에서는 클래스 상단에 &lt;code class=&quot;language-text&quot;&gt;@SpringBootTest&lt;/code&gt; 어노테이션을 붙여 통합 테스트를 수행할 수 있다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootTest&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SubwayApplicationTests&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;contextLoads&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h2&gt;인수 테스트(Acceptance Test)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;인수 테스트는 사용자 스토리(시나리오)에 맞춰 수행하는 테스트이다.&lt;br/&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;앞선 두 테스트들과 달리 비즈니스 쪽에 초점을 둔다.
프로젝트에 참여하는 사람들(ex. 기획자, 클라이언트 대표, 개발자 등)이 토의해서 시나리오를 만들고, 개발자는 이에 의거해서 코드를 작성한다.
개발자가 직접 시나리오를 제작할 수도 있지만, &lt;code class=&quot;language-text&quot;&gt;다른 의사소통집단으로부터 시나리오를 받아(인수) 개발한다&lt;/code&gt;는 의미를 가지고 있다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;인수 테스트는 애자일 개발 방법론에서 파생했다. 특히, 익스트림 프로그래밍(XP)에서 사용하는 용어이다.
이는 시나리오가 정상적으로 동작하는지를 테스트하기 때문에 통합 테스트와는 분류가 다르다.
시나리오에서 요구하는 것은 &lt;code class=&quot;language-text&quot;&gt;누가, 어떤 목적으로, 무엇을 하는가&lt;/code&gt;이다.
개발을 하다 보면 이런 기능은 API를 통해 드러난다. 인수 테스트는 주로 이 API를 확인하는 방식으로 이뤄진다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;결국, 인수 테스트는 소프트웨어 인수를 목적으로 하는 테스트이다.
소프트웨어를 인수하기 전에 명세한 요구사항(인수 조건)대로 잘 작동하는지 검증이 필요하다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;소프트웨어를 인수할 때, 소프트웨어 내부 구조나 구현 방법을 고려하기보다는 실제 사용자 관점에서 테스트하는 경우가 많다.
따라서, 인수 테스트는 소프트웨어 내부 코드에 관심을 가지지 않는 블랙박스 테스트이다.
실제 사용자 관점에서 테스트할 때 주로 E2E(End-to-End) 형식을 이용해서 확인한다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Java에서는 &lt;code class=&quot;language-text&quot;&gt;RestAssured&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;MockMvc&lt;/code&gt; 같은 도구를 활용하여 인수 테스트를 작성할 수 있다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;아래 코드는 &lt;code class=&quot;language-text&quot;&gt;RestAssured&lt;/code&gt;로 만든 인수 테스트이다.&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExtractableResponse&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; 회원_생성_요청&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MemberRequest&lt;/span&gt; memberRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RestAssured&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;given&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contentType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MediaType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;APPLICATION_JSON_VALUE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;memberRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/api/members&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;extract&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br/&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;지금까지 단위 테스트, 통합 테스트, 인수 테스트에 관해 알아봤다.
각 테스트의 정의와 특징을 살펴보고 간단한 코드로 확인했다.&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;소프트웨어 테스트의 종류는 이외에도 더 다양하게 존재한다.
그렇지만, 그동안 미션을 진행하며 사용했거나 배웠던 테스트들을 중심으로 정리했다.
소프트웨어 개발에 있어 테스트도 중요한 한 부분을 차지한다. 따라서, 상황에 맞는 적절한 테스트를 선택해서 활용할 필요가 있다.&lt;br/&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/4904096/whats-the-difference-between-unit-functional-acceptance-and-integration-test&quot;&gt;What’s the difference between unit, functional, acceptance, and integration tests? [closed]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://martinfowler.com/articles/microservice-testing/&quot;&gt;Testing Strategies in a Microservice Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://joont92.github.io/tdd/%EC%9D%B8%EC%88%98%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%8B%A8%EC%9C%84%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%86%B5%ED%95%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A0%84-%EA%B5%AC%EA%B0%84-%ED%85%8C%EC%8A%A4%ED%8A%B8/&quot;&gt;[tdd] 인수테스트, 단위테스트, 통합테스트, 전 구간 테스트&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=ITVpmjM4mUE&amp;#x26;t=4s&quot;&gt;[3월 우아한테크세미나] 우아한ATDD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Acceptance_testing#Acceptance_testing_in_extreme_programming&quot;&gt;Acceptance testing in extreme programming&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Apache HTTP Server? Apache Tomcat? 서버 바로 알기]]></title><description><![CDATA[Spring Boot 을 공부하면서 어떻게 구성이 되어있는지도 공부를 하게 되었습니다.
전체적인 흐름은 Client 로부터 받은 HTTP 요청을 알맞게 처리하여, Controller 로 넘겨주게 됩니다.
이때, 해당 기능을 실행하는 WAS(Web…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-05-24-apache-tomcat/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-05-24-apache-tomcat/</guid><pubDate>Mon, 24 May 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Spring Boot&lt;/em&gt; 을 공부하면서 어떻게 구성이 되어있는지도 공부를 하게 되었습니다.
전체적인 흐름은 &lt;em&gt;Client&lt;/em&gt; 로부터 받은 &lt;em&gt;HTTP&lt;/em&gt; 요청을 알맞게 처리하여, &lt;em&gt;Controller&lt;/em&gt; 로 넘겨주게 됩니다.
이때, 해당 기능을 실행하는 &lt;em&gt;WAS(Web Application Server)&lt;/em&gt; 의 역할을 &lt;em&gt;Apache Tomcat&lt;/em&gt; 이라는 서버가 하게 된다고 공부를 하였습니다.
하지만 비슷한 이름을 가진 &lt;em&gt;Apache HTTP Server&lt;/em&gt; 라는 서버도 존재합니다.
이 과정에서 &lt;em&gt;WAS&lt;/em&gt; 와 또 다른 개념인 &lt;em&gt;Web Server&lt;/em&gt; 라는 것을 알게 되었습니다.
서버에서 쓰이는 개념들 중 단어들이 상당히 비슷하여 헷갈리는 점이 많았습니다.
&lt;em&gt;Spring Boot&lt;/em&gt; 를 동작시키는 구성 요소 중 &lt;em&gt;Apache HTTP Server&lt;/em&gt; 와 &lt;em&gt;Apache Tomcat&lt;/em&gt; 에 대해서 정리하고자 합니다.&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;Web Server&lt;/em&gt; 와 &lt;em&gt;Apache Server&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Client&lt;/em&gt; 에서는 미리 정해진 &lt;em&gt;HTTP&lt;/em&gt; 규격에 맞게 요청을 보내게 됩니다.
이것을 해석하고 우리는 &lt;em&gt;Client&lt;/em&gt; 가 원하는 데이터를 보내주어야 합니다.
여기서 &lt;em&gt;HTTP&lt;/em&gt; 를 해석하고, 그에 맞는 데이터 형식으로 보내주는 것이 &lt;em&gt;Web Server&lt;/em&gt; 가 할 일입니다.
요청을 데이터와 단순하게 매칭을 하고, 이를 &lt;em&gt;HTTP&lt;/em&gt; 에 맞게 돌려주면 되기 때문에, 이러한 과정을 &lt;em&gt;Static(정적)&lt;/em&gt; 인 웹서버라고도 합니다.
&lt;em&gt;HTML&lt;/em&gt; , &lt;em&gt;CSS&lt;/em&gt; , 그리고 사진과 같은 별도의 데이터 수정 없이 반환되는 자료 형식들이 해당됩니다.
이 때문에 별도의 프로그램 없이 &lt;em&gt;Web Server&lt;/em&gt; 만으로도 &lt;em&gt;URL&lt;/em&gt; 과 대응하여 &lt;em&gt;HTML&lt;/em&gt; 을 반환하여 블로그를 만들 수도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1236px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f427bcd67d55a1ccf901cdec41174952/baa93/2021-05-24-webserver_process.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 31.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABC0lEQVR42m2RiUoDQRBE8/+fFEH8ABWREFFQAmKya/bM3Nc+ZzZRE7GhmJ5huqq7esE/MaVE0oZkbD4tkw/l9YRjOOexZ/iOxfkHpTLJlIj9SDoIktL5lISmI2YRpTXOO6y19KPAWIcxjuGU/xLGHt9cY+or0nCTCRqszIXK4W3I94EoHjHVktQuGZsVpWlr9EweQmTMwj+EUntu1xX3Tx+sXj+x+w7ZCMb6gGgkse142w7crXc8PO942VRzcd/3dF1HStMl4ZStEcpT1R1CO0I75HEVeM9kDL7e4xNsq5bN+46DNPOYPncWYppznf2+8PDvUqJQxKxacFxKEZ5mlCiLkNnjAnvyrxB+AVNn0WJLIeNuAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;WebServer-Diagram&quot;
        title=&quot;&quot;
        src=&quot;/static/f427bcd67d55a1ccf901cdec41174952/baa93/2021-05-24-webserver_process.png&quot;
        srcset=&quot;/static/f427bcd67d55a1ccf901cdec41174952/0eb09/2021-05-24-webserver_process.png 500w,
/static/f427bcd67d55a1ccf901cdec41174952/1263b/2021-05-24-webserver_process.png 1000w,
/static/f427bcd67d55a1ccf901cdec41174952/baa93/2021-05-24-webserver_process.png 1236w&quot;
        sizes=&quot;(max-width: 1236px) 100vw, 1236px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;정해진 데이터만 가져와서 반환하면 된다는 점 때문에, 처리 속도가 빠르며 트래픽의 과부하를 잘 처리할 수 있다는 장점이 있습니다.
또한, &lt;em&gt;HTTP&lt;/em&gt; 를 정해진 약속대로 빠르게 처리를 해주어 반환해야 하는 점 때문에 대부분이 &lt;em&gt;C&lt;/em&gt; 로 구현이 되어있습니다.
이번 글을 쓰기 전에 제가 헷갈렸었던 &lt;em&gt;Apache HTTP Server&lt;/em&gt; 가 바로 이 &lt;em&gt;Web Server&lt;/em&gt; 에 속하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 552px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/af9f1419002f2b0d0c6fe5e7bcf1bc74/d8a93/2021-05-24-apache.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 48%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABqUlEQVR42q2Sy07CQBSGeR2fwMSFCze+ilufwJ1xTWJITFjoShOu4RIBLxBcCAlCgkQx0CK0lUtbbaUUCu3vYRo0BtGNJ/nTM/+cfPNPOh78c3ng2KyZNtOwSkeua8++JhznuxbeSuAcJssQdzdgXPswH3WmFmaGgZEgwLaoH40YZGaaMF9emMegts16s9vFhBifwPbeAe43t4hsQYzFoNfr6GezEEIhqMUixHCYeYNcDr3LS3TTaRitFsYEFwIBiMEg9IcHF2hKfeTXttHa92E6GeHJ60U3k0GbhpjOzsCfnODp8BA9OmReSqkE7viY7Tf8fojxOMaDgQt8PM0hur4DS9GgVcrQymWWRCTQbDiEnM/DbDQwOD9Hj9KrNzfop1J4bzZhShJLKNFN9FrNBVYid6jGymxRJViKrnRbKOCaUmYJHKEUcYLFk0mE6JASeSGCJBIJ5ChxlGAXpMLVlQvUVMP9cbYDTdchiSJkii8rCjqdDjiOA09qUMrndhuKqkKgGZ7n0SSfo2+H1q9vby5wMpn++gyW6o9Zz/K886NseiKr9haaAz8AW+3iD+Zn5rwAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Apache-Logo&quot;
        title=&quot;&quot;
        src=&quot;/static/af9f1419002f2b0d0c6fe5e7bcf1bc74/d8a93/2021-05-24-apache.png&quot;
        srcset=&quot;/static/af9f1419002f2b0d0c6fe5e7bcf1bc74/0eb09/2021-05-24-apache.png 500w,
/static/af9f1419002f2b0d0c6fe5e7bcf1bc74/d8a93/2021-05-24-apache.png 552w&quot;
        sizes=&quot;(max-width: 552px) 100vw, 552px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Apache HTTP Server&lt;/em&gt; 는  1995년에 &lt;em&gt;Apache&lt;/em&gt; 라는 비영리 재단에서 발표된 오픈 소스 &lt;em&gt;Web Server&lt;/em&gt; 입니다.
가장 높은 점유율을 가지고 있는 &lt;em&gt;Web Server&lt;/em&gt; 이기도 합니다.
그 이외에도 최근에 많이 쓰이고 있는 &lt;em&gt;Nginx&lt;/em&gt; 도 &lt;em&gt;Web Server&lt;/em&gt; 의 일종으로 볼 수가 있습니다.&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;Web Application Server&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Web Server&lt;/em&gt; 에서는 임의로 블로그를 만들 수가 있었습니다.
하지만 현실에서의 &lt;em&gt;Web Application&lt;/em&gt; 들은 더욱더 많은 기능을 요구할 때가 많습니다.&lt;br&gt;
예를 들면 커뮤니티 사이트에서 자신이 쓴 글만 모아서 볼 수 있게 하는 기능 등이 있습니다.
하지만 기존의 &lt;em&gt;Web Server&lt;/em&gt; 에서는 &lt;em&gt;DB&lt;/em&gt; 와 함께 유동적으로 로직에 따라 요청을 처리하지 못합니다.
이를 해결하기 위해서 &lt;em&gt;Web Application Server&lt;/em&gt; 를 사용하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1728px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8e3a9b945c7db96d500a25e9e3f6e57c/617a2/2021-05-24-was_process.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 34.400000000000006%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAA7DAAAOwwHHb6hkAAABJ0lEQVR42pWRYU/CMBCG9/9/mJ9UokhQNwYjBijI2Npt3dpujzeQmJhIYptLe+/l2r5PIxi4xO/xl367Hl3lYFt8qQna4IuSvnNnvR8G6qallvq42rbF+R4fBjrn0FVNVTdoU5/zyFdLbPGEP67xjSO0HaGSRpWIfo/VCeqQ0zRjk+GYnyj3U4y6w+gtjXV452m7TuoVUbaOeX17oPxYUeeWQmm6smGfrZi/P5NlMekyYzJ5ZDabofYHVuslcTxlu9tQloY0XVAUhbzUEqm8I90YKnUCsTPIbYPY0AfNS7IjWe3EqsWJHkJPJfvtZ818oVCfWlpaOsHTCBIjtqMrzF78h2+GYww+CEOZfTgzGg81cpEVJJc/GTn+1MZ1zCNhznDrM/85vgD2ih4Cme6YwgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;WAS-Diagram&quot;
        title=&quot;&quot;
        src=&quot;/static/8e3a9b945c7db96d500a25e9e3f6e57c/617a2/2021-05-24-was_process.png&quot;
        srcset=&quot;/static/8e3a9b945c7db96d500a25e9e3f6e57c/0eb09/2021-05-24-was_process.png 500w,
/static/8e3a9b945c7db96d500a25e9e3f6e57c/1263b/2021-05-24-was_process.png 1000w,
/static/8e3a9b945c7db96d500a25e9e3f6e57c/617a2/2021-05-24-was_process.png 1728w&quot;
        sizes=&quot;(max-width: 1728px) 100vw, 1728px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;WAS (Web Application Server)&lt;/em&gt; 는 일부 &lt;em&gt;Web Server&lt;/em&gt; 기능과 &lt;em&gt;Web Container&lt;/em&gt; 로 함께 구성됩니다.
앞단의 &lt;em&gt;Web Server&lt;/em&gt; 는 &lt;em&gt;HTTP&lt;/em&gt; 요청을 받아서 &lt;em&gt;Web Container&lt;/em&gt; 로 넘겨주게 됩니다.
&lt;em&gt;Web Container&lt;/em&gt; 이를 내부 프로그램 로직의 처리에 따라서 데이터를 만들어서 &lt;em&gt;Web Server&lt;/em&gt; 로 다시 전달하게 됩니다.
&lt;em&gt;Java&lt;/em&gt; 에서는 이를 &lt;em&gt;Servlet&lt;/em&gt; 을 통해서 처리하기 때문에 &lt;em&gt;Servlet Container&lt;/em&gt; 라고도 부릅니다.
앞서 언급한 같은 &lt;em&gt;URL&lt;/em&gt; 이지만 로그인한 사용자에 따라 자신이 쓴 글이 보이게 하는 등의 로직이 필요한 기능을 추가할 수가 있습니다.
요청 조건에 따라서 &lt;em&gt;Web Server&lt;/em&gt; 가 유연하게 처리하지 못하는 문제가 해결됩니다.
이렇게 데이터를 가공한 후에 &lt;em&gt;Client&lt;/em&gt; 에게 반환해주는 작업을 &lt;em&gt;Dynamic(동적)&lt;/em&gt; 인 웹처리라고 합니다.&lt;br&gt;
&lt;em&gt;Web Container&lt;/em&gt; 를 통해, 개발자는 로직에 집중을 할 수 있고, DB와 상호 작용을 통해 데이터들을 가공하여 &lt;em&gt;Client&lt;/em&gt; 에게 반환할 수 있습니다.
&lt;em&gt;WAS&lt;/em&gt; 는 &lt;em&gt;Java&lt;/em&gt; ,&lt;em&gt;JavaScript&lt;/em&gt; 를 비롯하여 다양한 언어로 프로그램이 구성되어, 원하는 프레임워크를 선택할 수가 있습니다.
하지만 &lt;em&gt;Web Server&lt;/em&gt; 장점인 빠른 처리 및 트래픽 부하에 대해서는 약한 모습을 보이게 됩니다.&lt;br&gt;
&lt;em&gt;Spring Boot&lt;/em&gt; 가 요청에 따라서 데이터를 처리 및 DB에 영향을 주는 것은 &lt;em&gt;Apache Tomcat&lt;/em&gt; 가 내장이 되어있기 때문입니다.
&lt;em&gt;Apache Tomcat&lt;/em&gt; 은 &lt;em&gt;Apache HTTP Server&lt;/em&gt; 가 개발된 같은 &lt;em&gt;Apache&lt;/em&gt; 재단에서 개발이 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 359px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c16e831d30c18964af380d853e061b67/16540/2021-05-24-apache_tomcat.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 77.15877437325905%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAC40lEQVR42o1UbUhTYRS+EEFG+EEaMwLFXxH1o9/9skgyjCQsEOzDsCAUXS1MKklRExUlamj7yI+JTbtjan6utjkZljanrm3mmh8zFtYkEZSc7t493d3tbk4LOvBy3nu453mf9znnvAT+07xeL+spimKXx+NhPRfnjPhbMk3T7PIl+LzPLBYL1GpNEJwOAI2OjmFiYjKYR+xksfNEivKwXtXTgvjYvXjVLMfMzAyMDIhC2YeEIweh7BAFAKndgD5zuVxYWFiA3W7H1tYmGxvWtGBYkYHmqjSIK86htOAUhtoy8a41HdpBEfyZ9O4rq1Qq6HQ6KJVKxMXFIS/3Dhvvf9uGL+rrWLUKYOzNxsbiQ/wyCfBNfxPqPnGAUACQ08lsNkOv1wf1WFr6Duu0HcuuH6gse4B+6UWsTPDxRpiOETILyoYMWAevQq3qDBaM4DY+02q1cDqdQbb2uUWUPL6Laxd4kJdEQFKZgpVJPpqqz0PEXNumvYWBpkx8MuwoCsdwamoK4+MGdi8VPUdxziFICveAfBqLofp4ZKVGYqz3Nqa1OeiRXsasLhuC/BtMgUzBGoRpuLm5AVKpgkyYC0X5PgaEB3N7EuaUibDIEzAp40FSehIuUxGoxfvobOTj5/JKWEEJ/wezmJL7QnXlObh3iYChiYdafjykDMMxUQRMsijYOngwthzGy+KjKOanYnbeie2SbQP0B8jWGlTzk0DKxThx/Biiow7AMNKN9121aKxOQ3tlEjTP9sMmj4S8LAbN4iqwYnk9wX4lOP2sn8dQkZeIccMH9tvhcCD59BnkFwgwO+dgY7av8xjokjC9mILemmjIigi87xYGmpgOMdxgerfuUTJePEnG6hrNnuiz9fU1kGQHMxnWYJ/57Lebwoh+CGTDFTQUxkBYehYaFRkCdLvdMBo+wsL0IceYm+XwSaLZq3ESuZlzjQY9FI0CkK/rQdH/eBzCZzn0QISA/XMLeHf9/wcAfv9KLTm/WQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Tomcat-logo&quot;
        title=&quot;&quot;
        src=&quot;/static/c16e831d30c18964af380d853e061b67/16540/2021-05-24-apache_tomcat.png&quot;
        srcset=&quot;/static/c16e831d30c18964af380d853e061b67/16540/2021-05-24-apache_tomcat.png 359w&quot;
        sizes=&quot;(max-width: 359px) 100vw, 359px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Servlet Container&lt;/em&gt; 개념으로 개발이 되었으며 데이터의 동적인 처리를 할 수 있습니다.
이 때, 외부 요청을 처리하는 &lt;em&gt;Web Server&lt;/em&gt; 의 기능을 일부 내장하고 있기 때문에 &lt;em&gt;WAS&lt;/em&gt; 로 간주하기도 합니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1666px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/154c8818460791a85eb294b3e869b79f/986a5/2021-05-24-was_with_container_process.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 24%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABG0lEQVR42jWQDW+CQAyG+f9/yxk3l2Vbsulwxo8Ih8oBB9yJcMCzSrJemiZtrn3eN0Bi9B6fG7wpp9pbx394memsIJN+lhmae4vvRzo/yqynrO2Upqq5NXcCZ0K8PjDcvWzupyWdTrF6TWNWmEKxPeyYL2aswhVZkVPpDSZZ4myKa9rpzyivrCzB5fSEjb5w2pFHOTa70VyvqMMrabwkjjcorVDniETHJKlCJ7JYPZPrE6a0/G5C0lQg3I3gZZ0T71PB6kTCyDCKHJH2vS2YvUWE+5jDOWT5ueB7945KE7ZRw/wj5qgMbdtOOfQ9Ve0IYGCQxuTdw8NCqvjR+47GVXK1Jr6cRe4P+9OR2tlJYttYYegmwgfZw8cH4R9fEXqeCLoddQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Servlet-Container-Diagram&quot;
        title=&quot;&quot;
        src=&quot;/static/154c8818460791a85eb294b3e869b79f/986a5/2021-05-24-was_with_container_process.png&quot;
        srcset=&quot;/static/154c8818460791a85eb294b3e869b79f/0eb09/2021-05-24-was_with_container_process.png 500w,
/static/154c8818460791a85eb294b3e869b79f/1263b/2021-05-24-was_with_container_process.png 1000w,
/static/154c8818460791a85eb294b3e869b79f/986a5/2021-05-24-was_with_container_process.png 1666w&quot;
        sizes=&quot;(max-width: 1666px) 100vw, 1666px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;하지만 사실 &lt;em&gt;Apache Tomcat&lt;/em&gt; 도 완벽한 &lt;em&gt;WAS&lt;/em&gt; 의 역할을 하고 있지 않다고 볼 수 있습니다.&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;Apache Tomcat&lt;/em&gt; 은 &lt;em&gt;Servlet Container&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Spring Boot&lt;/em&gt; 에서는 &lt;em&gt;Servlet&lt;/em&gt; 을 단위로 하여 &lt;em&gt;Client&lt;/em&gt; 의 요청을 처리하게 됩니다.
&lt;em&gt;Servlet&lt;/em&gt; 은 &lt;em&gt;Java&lt;/em&gt; 에서 &lt;em&gt;Thread&lt;/em&gt; 기반으로 &lt;em&gt;Client&lt;/em&gt; 요청에 대해서 &lt;em&gt;Dynamic&lt;/em&gt; 하게 작동하는 구성요소입니다.
이때, &lt;em&gt;Interface&lt;/em&gt; 인 &lt;em&gt;Servlet&lt;/em&gt; 을 구현한 추상 객체를 상속받아 구현되어있습니다.
요청에 따라서 각기 다른 &lt;em&gt;Servlet&lt;/em&gt; 이 실행이 됩니다.
&lt;em&gt;Servlet&lt;/em&gt; 을 요청에 연결하고, 수명 관리를 해주는 것이 바로 &lt;em&gt;Servlet Container&lt;/em&gt; 입니다.&lt;br&gt;
&lt;em&gt;Apache Tomcat&lt;/em&gt; 은 기본적으로는 &lt;em&gt;Servlet Container&lt;/em&gt; 이나 자체적으로 &lt;em&gt;Web Server&lt;/em&gt; 가 내장이 되어있습니다.
이 때문에 외부의 &lt;em&gt;HTTP&lt;/em&gt; 요청을 받을 수 있어서 &lt;em&gt;Apache Tomcat&lt;/em&gt; 은 &lt;em&gt;WAS&lt;/em&gt; 기능을 일부 가지고 있는 &lt;em&gt;Servlet Container&lt;/em&gt; 라고 볼 수가 있습니다.&lt;br&gt;
필요한 동적 처리를 모두 해주는데, 완벽히 &lt;em&gt;WAS&lt;/em&gt; 로 부를 수 없는지에 대해서 의아해할 수 있습니다.
&lt;em&gt;Java&lt;/em&gt; 진영의 역사를 보자면, &lt;em&gt;Java EE&lt;/em&gt; 라는 기업 전용 자바 플랫폼의 단점들을 해결하면서 필요한 부분만 적용한 것이 &lt;em&gt;Spring Boot&lt;/em&gt; 라고 볼 수 있습니다.
하지만 이 과정에서 분산 트랜잭션과 같은 기능들이 제외되게 되었습니다.
이런 기능들을 모두 포함해야 &lt;em&gt;WAS&lt;/em&gt; 로 보게 되므로, 해당 기능이 없는 &lt;em&gt;Apache Tomcat&lt;/em&gt; 은 완벽한 &lt;em&gt;WAS&lt;/em&gt; 로 분류하지 않게 되었습니다.&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;Spring Boot&lt;/em&gt; 에서의 &lt;em&gt;Apache Tomcat&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Spring Boot&lt;/em&gt; 에서는 &lt;em&gt;HTTP&lt;/em&gt; 요청을 받고 &lt;em&gt;Servlet&lt;/em&gt; 을 관리할 수 있기 때문에 기본적으로 &lt;em&gt;Apache Tomcat&lt;/em&gt; 을 내장하여 사용하고 있습니다.
먼저, &lt;em&gt;Gradle&lt;/em&gt; 에서 의존성을 살펴보게 되면, &lt;em&gt;Tomcat&lt;/em&gt; 이 내장되어 있음을 확인할 수가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 444px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/204b70c61d5602306ba8db7a40d2bead/39d25/2021-05-24-built_in_tomcat.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 29.72972972972973%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAsklEQVR42n2RURKCMAxEOYkKFKi0adEPFRHvf6uFRDJ0EP3YSTtJXjdpZo1FP77RxSuiC/BnD2oJ0cdVLqKjTs5hqbGVRXEsvpQ1ZY378yXAMIO4mJsYylEhCuc8R2edQMtTKVqBpsFjGD/AxCGfVf/u+kg9G8sP+W+gOmPxuNosOVpzwQe0TQuTm32HbuOQHElTCuS7PkzLmjhWRYWMrd76ATFcZIe8m63DdNR0d3ufMgH/dbQ+SRxLeAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Built-In-Tomcat&quot;
        title=&quot;&quot;
        src=&quot;/static/204b70c61d5602306ba8db7a40d2bead/39d25/2021-05-24-built_in_tomcat.png&quot;
        srcset=&quot;/static/204b70c61d5602306ba8db7a40d2bead/39d25/2021-05-24-built_in_tomcat.png 444w&quot;
        sizes=&quot;(max-width: 444px) 100vw, 444px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;실제로 &lt;em&gt;Spring Boot&lt;/em&gt; 를 실행시키게 되면, 다음과 같이 &lt;em&gt;Tomcat&lt;/em&gt; 이 실행 되었음을 알 수가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 899px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0518c8acfc00bf14a9aa070b36c7359c/f7121/2021-05-24-built_in_tomcat_process.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 13%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAm0lEQVR42i2P/QqDMAzEfZcpyFod2qr90q4WGWPv/zy3JNsfxx2XHwlp3qWgpoQzBFz7jk+teOUMoxV1EWdMuGhe2Wl+rCvWcUQwlmRgtYZqW+iuEzXJWuzLgjDPAufNCfjoe0SeUcdMNMz9M4n5nwyMUri3N1ncFO/BOrZNnJd7Ag1d5iVnjHg6L4cKfZGJ48y9ZOfE7TDATRO+o9piXc5JIk4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Built-In-Tomcat-Process&quot;
        title=&quot;&quot;
        src=&quot;/static/0518c8acfc00bf14a9aa070b36c7359c/f7121/2021-05-24-built_in_tomcat_process.png&quot;
        srcset=&quot;/static/0518c8acfc00bf14a9aa070b36c7359c/0eb09/2021-05-24-built_in_tomcat_process.png 500w,
/static/0518c8acfc00bf14a9aa070b36c7359c/f7121/2021-05-24-built_in_tomcat_process.png 899w&quot;
        sizes=&quot;(max-width: 899px) 100vw, 899px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;기본적으로 내장되어 사용하고 있기 때문에 저희는 &lt;em&gt;Apache Server&lt;/em&gt; 나 &lt;em&gt;Apache Tomcat&lt;/em&gt; 에 대한 지식없이도 &lt;em&gt;Spring Boot&lt;/em&gt; 에서 동적인 웹처리를 할수가 있었습니다.&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;Web Server&lt;/em&gt; 와 &lt;em&gt;WAS&lt;/em&gt; 의 상호 관계&lt;/h2&gt;
&lt;p&gt;유연성이 더 좋은 &lt;em&gt;WAS&lt;/em&gt; 가 &lt;em&gt;Web Server&lt;/em&gt; 보다 더 나은 것이라고 생각할 수 도 있습니다.
하지만 실제로는 &lt;em&gt;Apache HTTP Server&lt;/em&gt; 나 &lt;em&gt;Nginx&lt;/em&gt; 와 같은 &lt;em&gt;Web Server&lt;/em&gt; 를 &lt;em&gt;WAS&lt;/em&gt; 와 함께 쓰고 있습니다.
이유는 다음과 같습니다.&lt;br&gt;
먼저 정적인 응답을 처리할 때 문제입니다.
&lt;em&gt;WAS&lt;/em&gt; 및 &lt;em&gt;Web Server&lt;/em&gt; 둘 다 정적인 파일을 처리할 수가 있습니다.
하지만 &lt;em&gt;WAS&lt;/em&gt; 에서 응답을 처리할 때에는 외부 프로그램, &lt;em&gt;Servlet Container&lt;/em&gt; 등의 존재 때문에 부하가 많이 걸리게 됩니다.
이 때문에 상대적으로 가벼운 &lt;em&gt;Web Server&lt;/em&gt; 가 정적 요청에서는 유리합니다.&lt;br&gt;
두 번째로는 &lt;em&gt;Load Balancing&lt;/em&gt; 의 적용입니다.
하나의 &lt;em&gt;WAS&lt;/em&gt; 에 너무 많은 요청이 몰리게 되면, 데이터 처리할 것이 많아져서 CPU에 부하가 오게 됩니다.
이를 여러 &lt;em&gt;WAS&lt;/em&gt; 로 구성을 하여, 요청을 분산시킬 수가 있습니다.
요청을 분산시키는 기능이 필요하게 되는데, 이를 &lt;em&gt;Load Balancing&lt;/em&gt; 이라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 938px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f553b4302ccba2323d5e389e89ec2d51/7bf1f/2021-05-24-apache_load_balancing.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABn0lEQVR42p1TXU/bMBTt//8ne6SaNCYh3gBNYuxhBToy2kDjtEkcO24+Wtuxz66tDbGpQ6JXurp2lBzd85EJjijvfZzDTqOqJbhoUAsF5xwmOLICaFHWGEcX7/2wg2y27wP8vRgeWYec78HyHMvlEsnDQzyHbSdvfnngEat2OP9aQnUmAlS8Rpo+QUj1vw19pPPSrzTTxuH6h8Sgx3gXBLDKC+QFx4oVsNYSoLOA08A4xJdsnkInN/DWw7UtvNZwwwBvDEbCNdZRewz7ESUXdB7JDI9tN0CqlgDbb0D9EX7zgdDuwaYXYJ9OkX4+xePJCbLLS/ycTiFm39Eb4GrGqWt8uZNInzMsFwvM53NkGQOvGwL05FJsS8OgFz0aIVFsNlHoOBnDVqmoY6AdpiF3i0qgqjienlekn3rD5QOmhAp0r2YiGhIqZC/oV3KJbF3CGHsY8C9TXnUorjTOrkvUNCvSsOt32GsT6Xb9cFwOM4pOKU3ULUkS3N3eYkF5fHew/2WxJrqCfrv1ekPbSqhtdxzgH/otRaUhkDDDduE3/AVYGfCGBVzlxQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;web_server_load_balancing&quot;
        title=&quot;&quot;
        src=&quot;/static/f553b4302ccba2323d5e389e89ec2d51/7bf1f/2021-05-24-apache_load_balancing.png&quot;
        srcset=&quot;/static/f553b4302ccba2323d5e389e89ec2d51/0eb09/2021-05-24-apache_load_balancing.png 500w,
/static/f553b4302ccba2323d5e389e89ec2d51/7bf1f/2021-05-24-apache_load_balancing.png 938w&quot;
        sizes=&quot;(max-width: 938px) 100vw, 938px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Apache HTTP Server&lt;/em&gt; 와 같은 &lt;em&gt;Web Server&lt;/em&gt; 는 이 기능을 가지고 있기 때문에 &lt;em&gt;WAS&lt;/em&gt; 앞단에 배치하게 됩니다.
이 같은 이유 때문에 하나의 종류만 쓰는 것이 아닌 &lt;em&gt;Web Server&lt;/em&gt; 와 &lt;em&gt;WAS&lt;/em&gt; 를 적절히 배치하여 사용하게 됩니다.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://httpd.apache.org/&quot;&gt;Apache Server 공식문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/difference-between-apache-tomcat-server-and-apache-web-server/&quot;&gt;Apache Server vs Apache Tomcat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pjh3749.tistory.com/267&quot;&gt;웹서버 vs 애플리케이션 서버 vs 서블릿 컨테이너&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.java67.com/2016/06/3-difference-between-web-server-vs-application-server-vs-servlet-container.html&quot;&gt;3 Difference between Web Server vs Application vs Servlet Containers in Java JEE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://httpd.apache.org/docs/2.4/mod/mod_proxy_balancer.html&quot;&gt;Apache Server Load Balancing 기능&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Iteration Protocol]]></title><description><![CDATA[Iteration Protocol ES6가 출현하면서 여러 문법뿐만 아니라 Iteration Protocol이라는 규칙이 추가되었다. 이것은 JS…]]></description><link>https://tecoble.techcourse.co.kr/post/2021-05-24-iteration-protocols/</link><guid isPermaLink="false">https://tecoble.techcourse.co.kr/post/2021-05-24-iteration-protocols/</guid><pubDate>Mon, 24 May 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Iteration Protocol&lt;/h2&gt;
&lt;p&gt;ES6가 출현하면서 여러 문법뿐만 아니라 &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Iteration_protocols&quot;&gt;Iteration Protocol&lt;/a&gt;이라는 규칙이 추가되었다. 이것은 JS 자체의 독자적인 특징이라기보다는 여러 언어에서 반복 동작을 수행하기 위해 정의하는 방법에 가깝다. 이번 아티클에서는 JS에 한정하여 해당 규칙을 다루어보고자 한다.&lt;/p&gt;
&lt;h3&gt;Table Of Contents&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Iteration Protocol이 필요한 이유&lt;/li&gt;
&lt;li&gt;JS에서 Iteration Protocol을 정의하는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Why?&lt;/h3&gt;
&lt;p&gt;프로그래밍을 하다 보면, 어떤 구조/객체의 내부에서 반복 동작을 수행하고 싶을 때가 있다. 이때 사용할 수 있는 방법이 &lt;code class=&quot;language-text&quot;&gt;for..of&lt;/code&gt;문과 &lt;code class=&quot;language-text&quot;&gt;spread operator&lt;/code&gt;인데, 이 문법을 사용하기 위한 전제가 바로 Iteration Protocol이다. 현재까지 확인된 바로는, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield*&quot;&gt;yield*&lt;/a&gt;와 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment&quot;&gt;destructuring assignment&lt;/a&gt; 또한 이 규칙에 의존하는 구문, 표현식이라고 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;for..of&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; numbers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; number &lt;span class=&qu