<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Kay's devlog</title>
    <link>https://kay0426.tistory.com/</link>
    <description>소프트웨어 개발에 대한 이야기를 기록합니다.</description>
    <language>ko</language>
    <pubDate>Wed, 15 Apr 2026 06:15:06 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>iKay</managingEditor>
    <image>
      <title>Kay's devlog</title>
      <url>https://tistory1.daumcdn.net/tistory/2816229/attach/98d401d5d6c54a4b9cb70788b37b9a14</url>
      <link>https://kay0426.tistory.com</link>
    </image>
    <item>
      <title>[NestJS 튜토리얼 초급] 0. NestJS 튜토리얼 초급 소개</title>
      <link>https://kay0426.tistory.com/78</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;nestjs_logo_icon_169927.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOORn2/btsrdkxVmHu/LosYZC0cXClf8oitdPRVO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOORn2/btsrdkxVmHu/LosYZC0cXClf8oitdPRVO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOORn2/btsrdkxVmHu/LosYZC0cXClf8oitdPRVO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOORn2%2FbtsrdkxVmHu%2FLosYZC0cXClf8oitdPRVO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;256&quot; data-filename=&quot;nestjs_logo_icon_169927.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;NestJS를 사용해야 하는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NestJS는&amp;nbsp;NodeJS&amp;nbsp;런타임에서&amp;nbsp;구동되는&amp;nbsp;서버&amp;nbsp;사이드&amp;nbsp;애플리케이션을&amp;nbsp;구축하기&amp;nbsp;위한&amp;nbsp;프레임워크입니다.&amp;nbsp;NestJS&amp;nbsp;프레임워크를&amp;nbsp;학습하기&amp;nbsp;전,&amp;nbsp;NestJS를&amp;nbsp;사용하면&amp;nbsp;어떤&amp;nbsp;점이&amp;nbsp;좋은지를&amp;nbsp;우선&amp;nbsp;소개해&amp;nbsp;드리겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. TypeScript 기반&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NestJS는&amp;nbsp;TypeScript를&amp;nbsp;주&amp;nbsp;언어로&amp;nbsp;사용합니다.&amp;nbsp;TypeScript는&amp;nbsp;정적&amp;nbsp;타입&amp;nbsp;언어로&amp;nbsp;런타임에&amp;nbsp;발생할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;오류를&amp;nbsp;사전에&amp;nbsp;개발&amp;nbsp;과정에서&amp;nbsp;방지할&amp;nbsp;수&amp;nbsp;있고&amp;nbsp;코드의&amp;nbsp;가독성을&amp;nbsp;높여줍니다.&amp;nbsp;이에&amp;nbsp;따라&amp;nbsp;코드&amp;nbsp;유지&amp;nbsp;보수가&amp;nbsp;용이해지며,&amp;nbsp;협업과&amp;nbsp;확장성이&amp;nbsp;향상됩니다.&lt;br /&gt;&lt;br /&gt;TypeScript&amp;nbsp;언어는&amp;nbsp;&quot;lingua&amp;nbsp;franca(공통어)&quot;가&amp;nbsp;되었다고&amp;nbsp;해도&amp;nbsp;과언이&amp;nbsp;아닙니다.&amp;nbsp;누구나&amp;nbsp;TypeScript를&amp;nbsp;쉽게&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있기&amp;nbsp;때문에&amp;nbsp;NestJS를&amp;nbsp;사용하는&amp;nbsp;데&amp;nbsp;부담이&amp;nbsp;없을&amp;nbsp;것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 모듈 구조&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NestJS는 모듈 기반 아키텍처를 채택하고 있습니다. 이는 애플리케이션을 기능 단위로 분리하여 개발 및 유지 보수를 용이하게 해줍니다. 각 모듈은 독립적으로 작동하며 필요한 모듈만 가져와 조합할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 의존성 주입(Dependency Indejction)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성&amp;nbsp;주입은&amp;nbsp;코드의&amp;nbsp;재사용성과&amp;nbsp;유지&amp;nbsp;보수성을&amp;nbsp;향상시키는&amp;nbsp;중요한&amp;nbsp;개념입니다.&amp;nbsp;NestJS는&amp;nbsp;의존성&amp;nbsp;주입을&amp;nbsp;기본적으로&amp;nbsp;지원하며,&amp;nbsp;이를&amp;nbsp;통해&amp;nbsp;컴포넌트&amp;nbsp;간의&amp;nbsp;느슨한&amp;nbsp;결합을&amp;nbsp;유지하고&amp;nbsp;테스트&amp;nbsp;용이성을&amp;nbsp;높일&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;HTTP 와 WebSocket 지원&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NestJS는 Express나 Fastify와 같은 강력한 HTTP 서버 프레임워크 위에서 작동합니다. 이를 통해 RESTful HTTP API, GraphQL 같은 다양한 통신 프로토콜을 구현할 수 있습니다. 또한 WebSocket을 통한 양방향 통신도 쉽게 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. Middleware 와 Interceptor&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NestJS는&amp;nbsp;미들웨어와&amp;nbsp;인터셉터를&amp;nbsp;사용하여&amp;nbsp;요청&amp;nbsp;및&amp;nbsp;응답을&amp;nbsp;가로채고&amp;nbsp;변형시키는&amp;nbsp;기능을&amp;nbsp;제공합니다.&amp;nbsp;이를&amp;nbsp;활용하여&amp;nbsp;로깅,&amp;nbsp;인증,&amp;nbsp;데이터&amp;nbsp;변환&amp;nbsp;등의&amp;nbsp;작업을&amp;nbsp;더욱&amp;nbsp;쉽게&amp;nbsp;수행할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6. 다양한 ORM(객체-관계 매핑) 지원&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NestJS는&amp;nbsp;다양한&amp;nbsp;ORM&amp;nbsp;라이브러리와의&amp;nbsp;통합을&amp;nbsp;지원하여&amp;nbsp;데이터베이스&amp;nbsp;작업을&amp;nbsp;편리하게&amp;nbsp;수행할&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;TypeORM을&amp;nbsp;사용하면&amp;nbsp;TypeScript&amp;nbsp;클래스를&amp;nbsp;데이터베이스&amp;nbsp;엔터티로&amp;nbsp;매핑할&amp;nbsp;수&amp;nbsp;있어&amp;nbsp;개발&amp;nbsp;생산성을&amp;nbsp;높여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7. 테스트 용이성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NestJS는&amp;nbsp;테스트를&amp;nbsp;위한&amp;nbsp;다양한&amp;nbsp;도구와&amp;nbsp;기능을&amp;nbsp;제공합니다.&amp;nbsp;의존성&amp;nbsp;주입과&amp;nbsp;모듈화된&amp;nbsp;구조를&amp;nbsp;활용하면&amp;nbsp;단위&amp;nbsp;테스트와&amp;nbsp;통합&amp;nbsp;테스트를&amp;nbsp;보다&amp;nbsp;쉽게&amp;nbsp;작성할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;8. 풍부한 커뮤니티와 문서&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NestJS는&amp;nbsp;테스트를&amp;nbsp;위한&amp;nbsp;다양한&amp;nbsp;도구와&amp;nbsp;기능을&amp;nbsp;제공합니다.&amp;nbsp;의존성&amp;nbsp;주입과&amp;nbsp;모듈화된&amp;nbsp;구조를&amp;nbsp;활용하면&amp;nbsp;단위&amp;nbsp;테스트와&amp;nbsp;통합&amp;nbsp;테스트를&amp;nbsp;더욱&amp;nbsp;쉽게&amp;nbsp;작성할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;튜토리얼 목표&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NestJS&amp;nbsp;튜터리얼&amp;nbsp;초급&amp;nbsp;강의는&amp;nbsp;간단하지만,&amp;nbsp;실용적인&amp;nbsp;웹&amp;nbsp;커뮤니티&amp;nbsp;게시판&amp;nbsp;API를&amp;nbsp;직접&amp;nbsp;만들어&amp;nbsp;봄으로써&amp;nbsp;NestJS&amp;nbsp;프레임워크&amp;nbsp;개발환경을&amp;nbsp;스스로&amp;nbsp;구축할&amp;nbsp;수&amp;nbsp;있고,&amp;nbsp;프레임워크를&amp;nbsp;어떻게&amp;nbsp;사용하는지를&amp;nbsp;익히는&amp;nbsp;것을&amp;nbsp;목표로&amp;nbsp;합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;튜토리얼 대상&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NestJS 튜토리얼 초급을 웹 커뮤니티 게시판을 만들어 보고 싶은 초급 백엔드 개발자 혹은 백엔드 개발자가 아닐지라도 NestJS 프레임워크를 학습하고 싶은 모든 초보자를 대상으로 만들 예정입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사전지식&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NodeJS 기초&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Express 사용 경험&lt;/li&gt;
&lt;li&gt;JavaScript 또는 TypeScript 사용 경험 (TypeScript 사용 경험이 없어도 무방합니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;튜토리얼 목차&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜토리얼 목차는 다음과 같이 12 챕터로 구성될 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;NestJS 튜토리얼 초급 소개&lt;/li&gt;
&lt;li&gt;NestJS 개발 환경 갖추기&lt;/li&gt;
&lt;li&gt;NestJS Controller 알아보기&lt;/li&gt;
&lt;li&gt;NestJS Provider 알아보기&lt;/li&gt;
&lt;li&gt;NestJS Module 알아보기&lt;/li&gt;
&lt;li&gt;NestJS Middleware 알아보기&lt;/li&gt;
&lt;li&gt;NestJS Interceptor 알아보기&lt;/li&gt;
&lt;li&gt;NestJS Exception Filter 알아보기&lt;/li&gt;
&lt;li&gt;NestJS Pipe 알아보기&lt;/li&gt;
&lt;li&gt;NestJS Guard 알아보기&lt;/li&gt;
&lt;li&gt;NestJS Custom Route Decorator 알아보기&lt;/li&gt;
&lt;li&gt;NestJS 튜토리얼 초급 마무리 하기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NodeJS 백엔드 프레임워크로써 NestJS가 큰 인기를 얻고 있지만 초급 튜토리얼 강의가 현재 많지 않은 것 같고&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;NestJS를 좋아하시는 분들이 많아졌으면 하는 마음에 튜토리얼 초급 강의를&lt;/span&gt;&amp;nbsp;기획하기로 결심했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://discord.com/invite/nestjs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NestJS Korea 커뮤니티 Discord&lt;/a&gt; 도 많이 활성화되어 있으니 많은 관심과 참여 부탁드려요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>NestJS/NestJS 튜토리얼 초급</category>
      <category>Nest</category>
      <category>nestJS</category>
      <category>TypeScript</category>
      <category>강의</category>
      <category>웹 프레임워크</category>
      <category>초급</category>
      <category>튜토리얼</category>
      <author>iKay</author>
      <guid isPermaLink="true">https://kay0426.tistory.com/78</guid>
      <comments>https://kay0426.tistory.com/78#entry78comment</comments>
      <pubDate>Mon, 14 Aug 2023 16:11:00 +0900</pubDate>
    </item>
    <item>
      <title>hey, 사용이 간단한 웹서버 부하테스트 커맨드라인 프로그램</title>
      <link>https://kay0426.tistory.com/77</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;웹서버 부하테스트 툴은 많습니다. 오늘은 여러 툴 중 하나인 커맨드라인으로 간단히 테스트를 쉽게할 수 있는 오픈소스인 hey를 소개합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스코드는 &lt;a href=&quot;https://github.com/rakyll/hey&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/rakyll/hey&lt;/a&gt;에서 보실 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1690818250605&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - rakyll/hey: HTTP load generator, ApacheBench (ab) replacement&quot; data-og-description=&quot;HTTP load generator, ApacheBench (ab) replacement. Contribute to rakyll/hey development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/rakyll/hey&quot; data-og-url=&quot;https://github.com/rakyll/hey&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dtocEi/hyTvg8R6kZ/sILlWKYbKHmogKNuBtjwpk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/rakyll/hey&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/rakyll/hey&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dtocEi/hyTvg8R6kZ/sILlWKYbKHmogKNuBtjwpk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - rakyll/hey: HTTP load generator, ApacheBench (ab) replacement&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;HTTP load generator, ApacheBench (ab) replacement. Contribute to rakyll/hey development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;소개&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clCJVN/btsppJG5Z3Y/6Z8T6A2GLTiSqYrF7oHSI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clCJVN/btsppJG5Z3Y/6Z8T6A2GLTiSqYrF7oHSI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clCJVN/btsppJG5Z3Y/6Z8T6A2GLTiSqYrF7oHSI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclCJVN%2FbtsppJG5Z3Y%2F6Z8T6A2GLTiSqYrF7oHSI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;369&quot; height=&quot;108&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hey는 웹 애플리케이션을 부하테스트 하는 경량화된 프로그램으로 Go 언어로 작성되었고&amp;nbsp;&lt;a href=&quot;https://github.com/tarekziade/boom&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/tarekziade/boom&lt;/a&gt;에서 영향을 받아서 만들어 졌다고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설치하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하는 OS에 따라 설치파일이 다릅니다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;linux 64bit: &lt;a href=&quot;https://hey-release.s3.us-east-2.amazonaws.com/hey_linux_amd64&quot;&gt;https://hey-release.s3.us-east-2.amazonaws.com/hey_linux_amd64&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mac 64bit: &lt;a href=&quot;https://hey-release.s3.us-east-2.amazonaws.com/hey_darwin_amd64&quot;&gt;https://hey-release.s3.us-east-2.amazonaws.com/hey_darwin_amd64&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Windows 64bit: &lt;a href=&quot;https://hey-release.s3.us-east-2.amazonaws.com/hey_windows_amd64&quot;&gt;https://hey-release.s3.us-east-2.amazonaws.com/hey_windows_amd64&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mac 사용자라라면 homebrew로도 설치 가능합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1690818508680&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install hey&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;장점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부하테스트를 하기 위한 프로그램은 많은데요. 이 프로그램을 사용하는 이유는 다음과 같은 장점이 있기 때문입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무료입니다. 생각보다 쓸만한 무료 부하테스트 툴이 별로 없는 듯 합니다.&lt;/li&gt;
&lt;li&gt;용량이 작고 사용이 쉽고 가볍습니다.&lt;/li&gt;
&lt;li&gt;요청 클라이언트 query per second(QPS) 옵션이 있습니다. 생각보다 이 기능이 다른 툴에 없습니다.&lt;/li&gt;
&lt;li&gt;http2 요청도 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 예&lt;/h2&gt;
&lt;pre id=&quot;code_1690819842791&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 예 1
hey -c 5 -z 60s -H &quot;Content-Type: application/json&quot; -m POST -d '{&quot;key1&quot;:&quot;val1&quot;}' 'https://www.google.com'

# 옵션 설명
-c 5: 테스트 수행 클라이언트 5개
-z 60s: 60초 동안 테스트 수행 
-H &quot;Content-Type: application/json&quot;: header에 &quot;Content-Type: application/json&quot; 내용 넣어서 요청
-m POST: 요청 메소드 POST
-d '{&quot;key1&quot;:&quot;val1&quot;}': POST body에 요청할 payload


# 예 2
hey -c 3 -z 60s -q 10 'https://www.google.com'

# 옵션 설명
-c 3: 테스트 수행 클라이언트 5개
-z 60s: 60초 동안 테스트 수행
-q 10: 1초당 10개의 요청으로 제한함&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;응답 예&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.google.com에&quot;&gt;https://www.google.com에&lt;/a&gt; 60초 동안 10 QPS, 5개의 클라이언트로 제한하여 부하테스트를 한 결과 예시 입니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690819883715&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;hey -c 5 -z 60s -q 10 'https://www.google.com'

Summary:
  Total:        60.1204 secs
  Slowest:      1.0855 secs
  Fastest:      0.1090 secs
  Average:      0.1331 secs
  Requests/sec: 37.4748
  

Response time histogram:
  0.109 [1]     |
  0.207 [2226]  |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.304 [12]    |
  0.402 [3]     |
  0.500 [5]     |
  0.597 [1]     |
  0.695 [0]     |
  0.793 [0]     |
  0.890 [0]     |
  0.988 [1]     |
  1.086 [4]     |


Latency distribution:
  10% in 0.1196 secs
  25% in 0.1227 secs
  50% in 0.1265 secs
  75% in 0.1323 secs
  90% in 0.1417 secs
  95% in 0.1567 secs
  99% in 0.2127 secs

Details (average, fastest, slowest):
  DNS+dialup:   0.0006 secs, 0.1090 secs, 1.0855 secs
  DNS-lookup:   0.0001 secs, 0.0000 secs, 0.0388 secs
  req write:    0.0001 secs, 0.0000 secs, 0.0035 secs
  resp wait:    0.1243 secs, 0.1017 secs, 1.0834 secs
  resp read:    0.0080 secs, 0.0003 secs, 0.2225 secs

Status code distribution:
  [200] 2253 responses&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;단점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 커맨드라인에서 실행시켜야 하기 때문에 dynamic 하게 쿼리스트링, path param 을 변경하는 것은 어렵습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 많이 사용되고 star도 많이 받은 오픈소스이지만 관리는 잘 되지 않고 있는 느낌입니다. 마지막 v0.1.4가 2020년 8월 이후로 업데이트가 없는 듯 하구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 커맨드라인 툴로써 부하테스트를 할 수 있는 프로그램을 찾는다면 hey를 사용하는 것은 괜찮은 것 같습니다.&amp;nbsp; 한 번 사용해보시는 것을 추천 드립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>툴</category>
      <category>CLI</category>
      <category>go</category>
      <category>hey</category>
      <category>QPS</category>
      <category>tarekziade</category>
      <category>부하테스트</category>
      <category>오픈소스</category>
      <category>커맨드라인</category>
      <category>툴</category>
      <category>프로그램</category>
      <author>iKay</author>
      <guid isPermaLink="true">https://kay0426.tistory.com/77</guid>
      <comments>https://kay0426.tistory.com/77#entry77comment</comments>
      <pubDate>Tue, 1 Aug 2023 01:14:54 +0900</pubDate>
    </item>
    <item>
      <title>NestJS Provider Injection scopes에 대해(singleton, request, transient)</title>
      <link>https://kay0426.tistory.com/76</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;현대 웹 개발에서 확장 가능하고 유지보수가 용이한 애플리케이션을 구축하는 것은 성공에 아주 중요합니다. NestJS는 강력하고 진보적인 Node.js 프레임워크로서, 체계적이고 효율적이며 확장 가능한 애플리케이션을 만들 수 있는 능력으로 큰 인기를 얻고 있습니다. NestJS 애플리케이션의 유지보수성과 유연성에 기여하는 핵심 기능 중 하나가 강력한 의존성 주입 시스템입니다.&lt;br /&gt;&lt;br /&gt;이&amp;nbsp;의존성&amp;nbsp;주입&amp;nbsp;시스템의&amp;nbsp;핵심에는&amp;nbsp;&quot;프로바이더(Provider)&quot;라는&amp;nbsp;개념이&amp;nbsp;있습니다.&amp;nbsp;프로바이더는&amp;nbsp;다른&amp;nbsp;부분에서&amp;nbsp;주입될&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;클래스&amp;nbsp;또는&amp;nbsp;값으로,&amp;nbsp;기능을&amp;nbsp;구성하고&amp;nbsp;공유하는&amp;nbsp;데&amp;nbsp;도움을&amp;nbsp;줍니다.&amp;nbsp;그러나&amp;nbsp;프로바이더를&amp;nbsp;정의하는&amp;nbsp;것만으로는&amp;nbsp;충분하지&amp;nbsp;않습니다.&amp;nbsp;또한&amp;nbsp;이러한&amp;nbsp;프로바이더들이&amp;nbsp;어떻게&amp;nbsp;애플리케이션&amp;nbsp;내에서&amp;nbsp;관리되고&amp;nbsp;공유되어야&amp;nbsp;하는지&amp;nbsp;고려해야&amp;nbsp;합니다.&amp;nbsp;이것이&amp;nbsp;&quot;프로바이더&amp;nbsp;스코프(Provider&amp;nbsp;Scope)&quot;&amp;nbsp;개념이&amp;nbsp;등장하는&amp;nbsp;곳입니다.&lt;br /&gt;&lt;br /&gt;이번 스트에서는 NestJS Provider scopes 중 singleton, request 그리고 transient에 대해 정리해 보고자 합니다. 이러한 스코프를 이해하는 것은 애플리케이션 구조를 결정하고 provider의 라이프사이클을 효과적으로 관리하는 데에 꼭 필요하다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;429&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OYHAk/btsplP1TPJU/A8TrIXaPPPvmSVpXnpzIvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OYHAk/btsplP1TPJU/A8TrIXaPPPvmSVpXnpzIvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OYHAk/btsplP1TPJU/A8TrIXaPPPvmSVpXnpzIvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOYHAk%2FbtsplP1TPJU%2FA8TrIXaPPPvmSVpXnpzIvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;820&quot; height=&quot;429&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;429&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Singleton&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;default 옵션으로 동작하기 때문에 가장 일반적으로 사용되는 scope입니다. singleton scope에서 Provider는 애플리케이션 전체에서 단 하나의 인스턴스만 생성되고 공유됩니다. 즉, 처음 bootstrap 됐을 때 인스턴스가 생성되며, 그 후로는 같은 인스턴스가 계속해서 사용됩니다.&lt;br /&gt;&lt;br /&gt;방금 설명드린대로 싱글톤은 기본적으로 NestJS 프로바이더의 default 스코프입니다. 따라서 `@Injectable()` 데코레이터를 사용하여 Provider를 정의하면 scope가 명시되지 않은 경우 자동으로 singleton scope로 취급됩니다.&lt;br /&gt;&lt;br /&gt;singleton은 애플리케이션에서 여러 곳에서 동일한 인스턴스를 사용해야 할 때 유용합니다. 예를 들어, 데이터베이스 연결, 로깅 서비스, 설정 정보, 캐싱 메커니즘 등과 같이 여러 부분에서 동일한 상태를 공유해야 하는 경우에 singleton scope를 사용합니다.&lt;br /&gt;&lt;br /&gt;singleton scope를 사용하는 경우, 주의해야 할 점이 있습니다. Provider가 애플리케이션 전체에서 하나의 인스턴스를 공유하기 때문에, Provider에 상태를 저장하면 다른 부분들이 이 상태를 공유하게 됩니다. 따라서 싱글톤 프로바이더의 상태는 멱등성(idempotence)을 유지하고, 사이드 이펙트가 없도록 신경 써야 합니다.&lt;br /&gt;&lt;br /&gt;Singleton은 애플리케이션의 공통적인 상태와 로직을 공유하는 데 유용하지만, 상태 변경에 대한 주의가 필요하다는 점을 잊지 말아야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Request&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 HTTP 요청과 관련된 단위 작업에서 프로바이더의 라이프사이클을 유지해야 하는 경우 사용됩니다. Request scope에서 프로바이더는 각각의 HTTP 요청마다 새로운 인스턴스가 생성되고, 해당 요청을 처리하는 동안에만 유지됩니다.&lt;br /&gt;&lt;br /&gt;Request scope는 웹 서버 애플리케이션에서 각각의 클라이언트 요청을 독립적으로 처리해야 할 때 유용합니다. HTTP 요청은 보통 서로 다른 클라이언트들이 동시에 접속하고 요청하는 상황이 발생하게 되는데, Request scope는 각각의 요청 처리를 위해 별도의 인스턴스를 제공함으로써 각 Request 마다 고유한 데이터를 가지게 됩니다.&lt;br /&gt;&lt;br /&gt;이러한 특성으로 인해, Request scope는 주로 요청마다 state를 유지해야 하는 경우에 사용됩니다. 예를 들어, 사용자의 로그인 정보, 요청마다 다른 인증 토큰, 요청마다 생성되는 request id 등이 이에 해당합니다.&lt;br /&gt;&lt;br /&gt;Request scope 사용하기 위해서는 `@Injectable()` 데코레이터와 함께 `Scope.REQUEST` 옵션을 설정해야 합니다:&lt;br /&gt;&lt;br /&gt;Request scope는 HTTP 요청마다 독립적인 상태를 유지해야 하는 경우에 매우 유용하지만, 동시에 인스턴스를 많이 생성하므로 성능에 영향을 미칠 수 있습니다. 따라서 신중하게 사용해야 하며, 필요한 경우에만 적절하게 활용하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2 data-ke-size=&quot;size26&quot;&gt;Transient&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Transient scope는 injection 될 때마다 새로운 인스턴스가 생성되어야 하는 Provider에 사용됩니다. 이 Scope는 애플리케이션의 다른 부분들과 상태를 공유하지 않으려는 경우에 유용하며, 각 Injection 지점이 새롭고 독립적인 인스턴스를 받도록 보장할 수 있습니다.&lt;br /&gt;&lt;br /&gt;Transient scope는 생소하기도 하고 특별한 경우인 것 같아 NestJS에서 Transient scope를 사용하는 일반적인 상황에 대해 위 둘의 내용보다는 조금 더 자세히 정리하고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 상태를 유지하는 서비스(Stateful Services)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 작업 또는 요청에 대해 상태를 유지하는 서비스가 있는 경우, Transient scope를 사용하여 각 요청이나 작업에 대해 독립적인 서비스 인스턴스를 생성합니다. 이렇게 함으로써 서로 다른 요청들 간에 상태가 공유되지 않고, 데이터 오염이나 예상치 못한 동작을 방지할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 요청별 데이터 저장(Request-Specific Data)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 HTTP 요청과 관련된 데이터를 저장해야 할 때(예: 요청별 데이터 캐싱 등), Transient scope를 사용할 수 있습니다. 이렇게 하면 각 요청마다 데이터를 격리하여 서로 간섭하지 않도록 보장할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;3. 성능 최적화(Performance Optimization)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 경우에는 Transient scope를 사용하여 성능 최적화를 할 수 있습니다. 예를 들어, Provider를 생성하는 데 자원이 많이 소모되는 경우, 해당 프로바이더의 수명을 최소한으로 제한하여 작업이 완료된 후 인스턴스가 폐기되도록 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 병렬 작업(Parallel Operations)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티스레드 또는 병렬 처리 환경에서는 Transient scope를 사용하여 공유 리소스에 대한 경합을 피할 수 있습니다. 각 스레드 또는 프로세스는 자체적인 프로바이더 인스턴스를 가져오므로 동기화와 잠재적인 병목 현상을 줄일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 외부 API 또는 연결(External APIs or Connections)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 API나 연결(예: 데이터베이스 연결, 웹소켓 등)과 같이 독립된 인스턴스가 필요한 경우, Transient scope를 사용하여 각 애플리케이션 부분이 독립적인 연결 또는 세션을 받도록 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;하지만 Transient scope를 사용할 때는 신중해야 합니다. 각 주입 지점마다 새로운 인스턴스를 생성하는 것은 메모리와 리소스를 더 소비할 수 있으므로, singleton이나 request scope와 비교해 메모리 사용에 주의해야 합니다. 애플리케이션의 기능과 요구사항에 따라 적절한 스코프를 선택하는 것이 중요합니다.&lt;br /&gt;&lt;br /&gt;모든 서비스가 tansient로 정의되어야 하는 것은 아니며, 많은 서비스는 기능과 요구사항에 따라 singleton이나 request scope로 정의하는 것이 적절합니다. 각 provider에 적합한 scope를 선택하여 NestJS 애플리케이션의 올바른 동작과 성능을 보장하는 것이 중요합니다.&lt;/p&gt;</description>
      <category>NestJS</category>
      <category>default</category>
      <category>Di</category>
      <category>Injection scopes</category>
      <category>nestJS</category>
      <category>provider</category>
      <category>request</category>
      <category>singleton</category>
      <category>transient</category>
      <author>iKay</author>
      <guid isPermaLink="true">https://kay0426.tistory.com/76</guid>
      <comments>https://kay0426.tistory.com/76#entry76comment</comments>
      <pubDate>Mon, 31 Jul 2023 00:28:11 +0900</pubDate>
    </item>
    <item>
      <title>NestJS + TypeORM 트랜잭션(Transaction) 사용에 대한 고민</title>
      <link>https://kay0426.tistory.com/75</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;과거 그리고 현재에도 웹 애플리케이션을 개발할 때 Spring Boot(&lt;a href=&quot;https://spring.io/projects/spring-boot&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://spring.io/projects/spring-boot&lt;/a&gt;)가 많이 사용되고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 요즘 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;TypeScript 생태계가 점점 강력해지면서 &lt;/span&gt;NestJS의 점유율도 조금씩 증가하는 것 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;703&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bR712h/btsoIMQCdO7/r3E1hI1ao7HJQmfQbO44N1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bR712h/btsoIMQCdO7/r3E1hI1ao7HJQmfQbO44N1/img.png&quot; data-alt=&quot;그림1. 구글트렌드 Spring Boot vs NestJS&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bR712h/btsoIMQCdO7/r3E1hI1ao7HJQmfQbO44N1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbR712h%2FbtsoIMQCdO7%2Fr3E1hI1ao7HJQmfQbO44N1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1160&quot; height=&quot;703&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;703&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1. 구글트렌드 Spring Boot vs NestJS&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NestJS는 Spring을 모방하면서 발전한다는 생각이 드는데 이유는 사용법과 철학이 매우 유사하게 느껴지기 때문입니다. Spring에서 주요하다고 여겨지는 개념인 DI, IoC, AOP 등의 개념이 NestJS 에서도 그대로 보여지구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 아직은 Spring Boot에 비해 다른 기능들이 빈약한 편이긴 합니다. 한 가지 예로, 트랜잭션(Transaction) 사용법입니다. Spring에서 표준이다 시피한 JPA를 사용한다면 @Transactional 어노테이션을 반드시 사용해 봤을 것입니다. 이에 Spring 사용자라면 Service Layer에서 트랜잭션 사용에 대한 불편함을 크게 경험해 본적이 없을 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 말하는 불편함이란, 트랜잭션 커넥션을 열고 예외가 발생하면 롤백하거나 성공하면 커밋 하는 등의 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;애플리케이션 로직을&lt;/span&gt; 직접&amp;nbsp; Service Layer에서 해야 한다는 점을 말하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NestJS에는 Spring에서의 JPA와 같이 표준 ORM이 없는 것 같습니다. 현재는 TypeORM이 가장 많이 사용되지만 가장 많이 사용된다고 표준이라 할 수는 없고 앞으로 현재 인기를 얻고 있는 Prisma(&lt;a href=&quot;https://www.prisma.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.prisma.io/&lt;/a&gt;) 같은 것이 더 많이 사용될 수도 있을 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 현재 NestJS + TypeORM 의 조합이 가장 많이 사용되는 것 같으니 TypeORM 에 대한 이야기를 당분간 해보려 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsTVol/btsoxwaZbFV/YQDwHvUW74achYZKPLCLOk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsTVol/btsoxwaZbFV/YQDwHvUW74achYZKPLCLOk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsTVol/btsoxwaZbFV/YQDwHvUW74achYZKPLCLOk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsTVol%2FbtsoxwaZbFV%2FYQDwHvUW74achYZKPLCLOk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제까지 NestJS + TypeORM 조합으로 애플리케이션을 개발할 때 트랜잭션 처리에 대한 불편함을 많이 느껴왔는데 이것을 어떻게 해결하면 좋을지 고민해본 내용을 정리하고자 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쇼핑몰에서 다수 계정의 상태를 일괄적으로 비활성화 시키는 기능을 추가하고자 하는 예제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;User Entity는 다음과 같습니다. beInactive() 메소드를 추가해서 user를 비활성화 시키는 메소드를 추가하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1690092098501&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity()
export class User {
  @PrimaryGeneratedColumn({ type: 'int', unsigned: true })
  id: number;

  @Column({ type: 'varchar', length: 20 })
  name: string;

  @Column({ default: true })
  isActive: boolean;

  constructor(args?: { name: string; isActive?: boolean }) {
    if (args) {
      this.name = args.name;
      this.isActive = args.isActive ?? true;
    }
  }

  /**
   * 비활성화 시킨다
   */
  beInactive() {
    this.isActive = false
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserRepository는 다음과 같이 custom repository를 구성하였습니다. findByIds()와 save()를 하는 메소드를 UserRepository에 정의한 후 구현하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1690092255818&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Injectable()
export class UsersRepository {
    constructor(
        @InjectRepository(User)
        private usersRepository: Repository&amp;lt;User&amp;gt;,
    ) {}

    async findByIds(ids: number[]): Promise&amp;lt;User[]&amp;gt; {
        const users = await this.usersRepository.findBy({ id: In(ids) })
        return users
    }

    async save(users: User[]): Promise&amp;lt;User[]&amp;gt; {
        const savedUsers = await this.usersRepository.save(users, { transaction: false })
        return savedUsers
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실제로 로직을 구성해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 계정의 상태를 일괄적으로 비활성화 시키는 기능을 추가하고자 합니다. 목적을 달성하기 위해 다음과 같이 Service layer에 코드를 작성할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1690091888213&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Injectable()
export class UsersService {
 constructor(
        private usersRepository: UsersRepository,
    ) {}

    async makeUsersBeInactive(userNos: number[]): Promise&amp;lt;void&amp;gt; {
        const users = await this.usersRepository.findByIds(userNos)
        users.forEach((user) =&amp;gt; user.beInactive())
        await this.usersRepository.save(users)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 동작하는 것 처럼 보이겠지만 중간 실패시 롤백처리를 하기 위해 트랜잭션 기능을 넣고 싶은 경우 어떻게 하면 좋을까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. QueryRunner를 통해 트랜잭션을 제어&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 쉽게는 DataSource로 부터 QueryRunner를 얻어 트랜잭션을 직접 제어하는 방식입니다. 이 방법은 NestJS 공식문서(&lt;a href=&quot;https://docs.nestjs.com/techniques/database#typeorm-transactions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.nestjs.com/techniques/database#typeorm-transactions&lt;/a&gt;)에서도 소개하고 있는 방법이고 합니다. 그리고 Node.js를 사용해온 사람이라면 가장 흔하게 사용할 방법일 것 같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690093591755&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export class UsersService {
    constructor(
        private dataSource: DataSource,
    ) {}

    async makeUsersBeInactive(userNos: number[]): Promise&amp;lt;void&amp;gt; {
        const queryRunner = this.dataSource.createQueryRunner()

        await queryRunner.connect()
        await queryRunner.startTransaction()
        try {
            const users = await queryRunner.manager.findBy(User, { id: In(userNos) })
            users.forEach((user) =&amp;gt; user.beInactive())
            await queryRunner.manager.save(users)

            await queryRunner.commitTransaction()
        } catch (err) {
            await queryRunner.rollbackTransaction()
        } finally {
            await queryRunner.release()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 잘 동작합니다. 그러나 두 가지 문제가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 째, userRepository custom repository를 사용할 수 없습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 째, user를 비활성화 시키는 비즈니스 로직과 트랜잭션을 제어하는 애플리케이션 로직이 혼재돼 있어 관심사 분리 측면에서 서로 분리가 필요해 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 가지 문제를 해결해 보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. custom repository와 dataSource를 공유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UsersService.makeUsersBeInActive는 dataSource를 통해 queryRunner를 얻었고 이것을 custom repository인 UsersRepository와 queryRunner를 공유하면 custom repository를 이용하면서 트랜잭션을 사용할 수 있을 것 같습니다. 그러면 다음과 같이 Repository와 Service 코드를 변경할 수 있을 것 같습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690099748972&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Injectable()
export class UsersRepository {
    constructor(
        @InjectRepository(User)
        private usersRepository: Repository&amp;lt;User&amp;gt;,
    ) {}
    
     async findByIds(ids: number[], queryRunner?: QueryRunner): Promise&amp;lt;User[]&amp;gt; {
        if (!queryRunner) {
            const users = await this.usersRepository.findBy({ id: In(ids) })
            return users
        } else {
            const users = await queryRunner.manager.findBy(User, { id: In(ids) })
            return users
        }
    }

    async save(users: User[], queryRunner?: QueryRunner): Promise&amp;lt;User[]&amp;gt; {
        if (!queryRunner) {
            const savedUsers = await this.usersRepository.save(users, { transaction: false })
            return savedUsers
        } else {
            const savedUsers = await queryRunner.manager.save(users, { transaction: false })
            return savedUsers
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690099694332&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export class UsersService {
    constructor(
      private usersRepository: UsersRepository,
      private dataSource: DataSource,
    ) {}
  
    async makeUsersBeInactive(userNos: number[]): Promise&amp;lt;void&amp;gt; {
      const queryRunner = this.dataSource.createQueryRunner();
      await queryRunner.startTransaction();
      try {
        const users = await this.usersRepository.findByIds(userNos, queryRunner);
        users.forEach((user) =&amp;gt; user.beInactive());
        await this.usersRepository.save(users, queryRunner);

        await queryRunner.commitTransaction();
      } catch (err) {
        await queryRunner.rollbackTransaction();
      } finally {
        await queryRunner.release();
      }
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 문제였던 custom repository를 사용하는 목적은 달성하였습니다. 하지만 repository가 괴상해졌습니다. 각 메소드마다 queryRunner 파라미터를 검사해서 있으면 넘겨받은 queryRunner를 사용하고 없으면 inject된 repository를 사용하는게 괴상하지 않나요? 그리고 일일이 service에서 queryRunner를 만들어서 넘기는 것도 괴상하지 않나요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 repository에서 생성자로 queryRunner를 넘겨받아서 queryRunner를 통해 manager로 usersRepositry를 생성해서 사용하면 될 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690100596122&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export class UsersRepository {
  private usersRepository: Repository&amp;lt;User&amp;gt;;

  constructor(private queryRunner?: QueryRunner) {
    if (queryRunner) {
      this.usersRepository = queryRunner.manager.getRepository(User);
    }
  }

  async findByIds(ids: number[]): Promise&amp;lt;User[]&amp;gt; {
    const users = await this.usersRepository.findBy({ id: In(ids) });
    return users;
  }

  async save(users: User[]): Promise&amp;lt;User[]&amp;gt; {
    const savedUsers = await this.usersRepository.save(users, {
      transaction: false,
    });
    return savedUsers;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690100634181&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Injectable()
export class UsersService {
  constructor(
    private dataSource: DataSource,
  ) {}
  
  async makeUsersBeInactive(userNos: number[]): Promise&amp;lt;void&amp;gt; {
    const queryRunner = this.dataSource.createQueryRunner();
    const usersRepository = new UsersRepository(queryRunner)
    await queryRunner.startTransaction();
    try {
      const users = await usersRepository.findByIds(userNos);
      users.forEach((user) =&amp;gt; user.beInactive());
      await usersRepository.save(users);

      await queryRunner.commitTransaction();
    } catch (err) {
      await queryRunner.rollbackTransaction();
    } finally {
      await queryRunner.release();
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 또 다른 문제를 낳았어요. UsersService에서 UsersRepository를 inject 할 수 없고 필요한 곳에서 객체를 직접 생성해야만 하는 점이 말이에요. 이렇게 되면 모킹이 어려워 테스트 하기가 어려운 코드가 되어 좋지 않습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 현재로서는 dataSource를 UsersService가 생성해서 UsersRepository에 넘겨줘야하기 때문에 트랜잭션에 관한 애플리케이션 로직도 제거하기 힘들어 보입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이쯤에서 고민해 봤을 때 문제는 dataSource를 생성하고 접근하는 부분인 것 같습니다. 근본적인 해결을 위해서는 dataSource를 전역에서 접근할 수 있는 곳에 저장해야 맞을 것 같습니다. 그리고 dataSource는 각 요청(request)마다 격리되어 생성되어야 하며, 응답(response)이 완료됐을 때 dataSource에 관한 resouce가 반환 되어야 할 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Request 마다 전역적으로 접근 가능한 QueryRunner 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;request 마다 전역적으로 접근 가능한 무언가를 만들어 주기 위해 우선 Injection scopes를 알아야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.nestjs.com/fundamentals/injection-scopes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.nestjs.com/fundamentals/injection-scopes&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1690110396026&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Documentation | NestJS - A progressive Node.js framework&quot; data-og-description=&quot;Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea&quot; data-og-host=&quot;docs.nestjs.com&quot; data-og-source-url=&quot;https://docs.nestjs.com/fundamentals/injection-scopes&quot; data-og-url=&quot;https://docs.nestjs.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bmLqEU/hyTo1K75vq/uVVNbJ9oNvwOBSMr1pXB9k/img.png?width=820&amp;amp;height=429&amp;amp;face=0_0_820_429&quot;&gt;&lt;a href=&quot;https://docs.nestjs.com/fundamentals/injection-scopes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.nestjs.com/fundamentals/injection-scopes&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bmLqEU/hyTo1K75vq/uVVNbJ9oNvwOBSMr1pXB9k/img.png?width=820&amp;amp;height=429&amp;amp;face=0_0_820_429');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Documentation | NestJS - A progressive Node.js framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.nestjs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 request 마다 생성되는 request 객체에 db connection을 넣을 것인데 싱글 쓰레드로 동작하는 Node.js 환경에서 각 request 마다 독립된 한경을 갖기 위해서는 매번 각 요청마다 객체를 생성하는 방법이 있습니다. 이를 이용할 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 UsersRepository의 scope를 REQUEST로 설정합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690110550441&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Injectable({ scope: Scope.REQUEST })
export class UsersRepository {&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 각 request 마다 독립된 queryRunner를 만들기 위해 QueryRunnerMiddleware라는 것을 만듭니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690110634104&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Injectable()
export class QueryRunnerMiddleware implements NestMiddleware {
  constructor(private dataSource: DataSource) {}
  async use(req: Request, res: Response, next: NextFunction) {
    const queryRunner = this.dataSource.createQueryRunner()
    await queryRunner.connect()
    // @ts-expect-error
    req.queryRunner = queryRunner
    next();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QueryRunnerMiddleware에서는 QueryRunner를 만들어 request 객체에 넣습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 전역적으로 QueryRunner에 접근가능합니다. 그래서 UsersRepository와 UsersService를 다음과 같이 수정할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690110751404&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Injectable({ scope: Scope.REQUEST })
export class UsersRepository {
  usersRepository: Repository&amp;lt;User&amp;gt;;

  constructor(
    @Inject(REQUEST) private request: Request,
  ) {
    // @ts-expect-error
    const queryRunner: QueryRunner = this.request.queryRunner
    queryRunner.manager.getRepository(User)
    this.usersRepository = queryRunner.manager.getRepository(User)
  }

  async findByIds(ids: number[]): Promise&amp;lt;User[]&amp;gt; {
    const users = await this.usersRepository.findBy({ id: In(ids) });
    return users;
  }

  async save(users: User[]): Promise&amp;lt;User[]&amp;gt; {
    const savedUsers = await this.usersRepository.save(users, {
      transaction: false,
    });
    return savedUsers;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690110865668&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Injectable()
export class UsersService {
  constructor(
    private usersRepository: UsersRepository,
    @Inject(REQUEST) private request: Request,
  ) {}

  async makeUsersBeInactive(userNos: number[]): Promise&amp;lt;void&amp;gt; {
    // @ts-expect-error
    const queryRunner: QueryRunner = this.request.queryRunner
    await queryRunner.startTransaction()
    try {
      const users = await this.usersRepository.findByIds(userNos);
      users.forEach((user) =&amp;gt; user.beInactive());
      await this.usersRepository.save(users);
      throw new Error() // 예외를 던져 commit 되지 않는 것을 확인합니다.

      await queryRunner.commitTransaction();
    } catch (err) {
        console.log(err)
      await queryRunner.rollbackTransaction();
    } finally {
      await queryRunner.release();
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UsersService에서 볼 수 있듯이 중간에 throw Error 를 해서 데이터의 변경이 롤백되는 것을 확인할 수 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 대략 첫 번째 문제였던 Custorm Repository를 사용할 수 없던 것을 해결한 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 두 번째 문제였던 UsersService에서 애플리케이션 로직과 비즈니스 로직을 분리하는 방법에 대해 고민해봅시다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 트랜잭션(Transaction)을 데코레이터(Decorator)로 분리하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UsersService 메소드를 다시 살펴보면 트랜잭션을 제어하는 애플리케이션 로직과 유저를 비활성화 시키는 비즈니스 로직은 서로 연관이 거의 없음을 알 수 있습니다. 심지어 서로 파라미터를 주고 받지도 않습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690111263602&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  async makeUsersBeInactive(userNos: number[]): Promise&amp;lt;void&amp;gt; {
    // @ts-expect-error
    const queryRunner: QueryRunner = this.request.queryRunner
    await queryRunner.startTransaction()
    try {
      const users = await this.usersRepository.findByIds(userNos);
      users.forEach((user) =&amp;gt; user.beInactive());
      await this.usersRepository.save(users);

      await queryRunner.commitTransaction();
    } catch (err) {
        console.log(err)
      await queryRunner.rollbackTransaction();
    } finally {
      await queryRunner.release();
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 메소드를 실행시킬 때 로직 전 후 서로 영향을 미치지 않는 어떤 로직을 실행시키고 싶은 경우 여러 가지 방법이 있겠으나 프록시를 이용해 데코레이터를 사용할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690112530586&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export function Transactional() {
    return function(target: any, key: string, description: PropertyDescriptor) {
        const originalMethod = description.value

        description.value = new Proxy(originalMethod, {
            async apply(target, thisArgs, args) {
                // originalMethod 실행 전 애플리케이션 로직 실행 

                const result = await originalMethod.apply(thisArgs, args)
                
                // originalMethod 실행 전 애플리케이션 로직 실행
                return result
            }
        })
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해하기 어려울 것 같아 코드 완성 전 모습을 보여드리자면, 위 주석으로 설명한대로 originalMethod가 실행되기 전후에 트랜잭션을 생성하고 커밋, 롤백하는 등의 로직을 넣을 예정입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완성하면 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690112882497&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import type { QueryRunner } from 'typeorm';

export function Transactional() {
  return function (target: any, key: string, description: PropertyDescriptor) {
    const originalMethod = description.value;

    description.value = new Proxy(originalMethod, {
      async apply(target, thisArgs, args) {
        const queryRunner: QueryRunner = thisArgs.request.queryRunner;
        await queryRunner.startTransaction();
        try {
          const result = await originalMethod.apply(thisArgs, args);
          await queryRunner.commitTransaction();
          return result;
        } catch (err) {
          await queryRunner.rollbackTransaction();
        } finally {
          await queryRunner.release();
        }
      },
    });
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690112923113&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Injectable()
export class UsersService {
  constructor(
    private usersRepository: UsersRepository,
  ) {}

  @Transactional()
  async makeUsersBeInactive1(userNos: number[]): Promise&amp;lt;void&amp;gt; {
      const users = await this.usersRepository.findByIds(userNos);
      users.forEach((user) =&amp;gt; user.beInactive());
      await this.usersRepository.save(users);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 두 번째 목적도 드디어 달성했습니다. 제가 원하던 코드가 이런 것이었습니다! UsersService에 비즈니스 로직과 애플리케이션 로직이 분리되어 훨씬 깔끔해졌네요..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 scope를 Request를 선언했다는 점에서 매 요청마다 객체를 계속 만들기 때문에 자원을 비효율적으로 사용할 것 같다는 느낌이 남습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. Typeorm Transactional 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 쉬운 방법이 있습니다. Typeorm Transactional을 사용하는 것입니다. &lt;a href=&quot;https://www.npmjs.com/package/typeorm-transactional&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.npmjs.com/package/typeorm-transactional&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1690117657517&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;typeorm-transactional&quot; data-og-description=&quot;A Transactional Method Decorator for typeorm that uses cls-hooked to handle and propagate transactions between different repositories and service methods. Inpired by Spring Trasnactional Annotation and Sequelize CLS. Latest version: 0.4.1, last published: &quot; data-og-host=&quot;www.npmjs.com&quot; data-og-source-url=&quot;https://www.npmjs.com/package/typeorm-transactional&quot; data-og-url=&quot;https://www.npmjs.com/package/typeorm-transactional&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Y9lv4/hyTpcMMcXk/jfmZcz3pVze6KcHuwKM601/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/typeorm-transactional&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.npmjs.com/package/typeorm-transactional&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Y9lv4/hyTpcMMcXk/jfmZcz3pVze6KcHuwKM601/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;typeorm-transactional&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A Transactional Method Decorator for typeorm that uses cls-hooked to handle and propagate transactions between different repositories and service methods. Inpired by Spring Trasnactional Annotation and Sequelize CLS. Latest version: 0.4.1, last published:&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.npmjs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본래 &lt;a href=&quot;https://github.com/odavid/typeorm-transactional-cls-hooked&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/odavid/typeorm-transactional-cls-hooked&lt;/a&gt; 였는데 TypeORM이 0.3 버전이 나오면서 호환되로록 folk 된 버전입니다.&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1690117801262&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - odavid/typeorm-transactional-cls-hooked: A Transactional Method Decorator for typeorm that uses cls-hooked to handle an&quot; data-og-description=&quot;A Transactional Method Decorator for typeorm that uses cls-hooked to handle and propagate transactions between different repositories and service methods. Inpired by Spring Trasnactional Annotation...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/odavid/typeorm-transactional-cls-hooked&quot; data-og-url=&quot;https://github.com/odavid/typeorm-transactional-cls-hooked&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/9uywH/hyTo6sexTR/aavqfQ7N1iiQFYwAhTKaQ0/img.png?width=1200&amp;amp;height=600&amp;amp;face=1010_174_1085_256&quot;&gt;&lt;a href=&quot;https://github.com/odavid/typeorm-transactional-cls-hooked&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/odavid/typeorm-transactional-cls-hooked&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/9uywH/hyTo6sexTR/aavqfQ7N1iiQFYwAhTKaQ0/img.png?width=1200&amp;amp;height=600&amp;amp;face=1010_174_1085_256');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - odavid/typeorm-transactional-cls-hooked: A Transactional Method Decorator for typeorm that uses cls-hooked to handle an&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A Transactional Method Decorator for typeorm that uses cls-hooked to handle and propagate transactions between different repositories and service methods. Inpired by Spring Trasnactional Annotation...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원리는 cls-hooked(&lt;a href=&quot;https://www.npmjs.com/package/cls-hooked&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.npmjs.com/package/cls-hooked&lt;/a&gt;)를 사용해서 request 마다 context를 만들어서 거기에 connection을 저장해서 사용하는 것으로 보입니다.&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1690117786024&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;cls-hooked&quot; data-og-description=&quot;CLS using AsynWrap instead of async-listener - Node &amp;gt;= 4.7.0. Latest version: 4.2.2, last published: 6 years ago. Start using cls-hooked in your project by running &amp;#96;npm i cls-hooked&amp;#96;. There are 719 other projects in the npm registry using cls-hooked.&quot; data-og-host=&quot;www.npmjs.com&quot; data-og-source-url=&quot;https://www.npmjs.com/package/cls-hooked&quot; data-og-url=&quot;https://www.npmjs.com/package/cls-hooked&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lBlqE/hyTpe4SDH5/866UYWgKfrLLGFkxIQdkw1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/cls-hooked&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.npmjs.com/package/cls-hooked&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lBlqE/hyTpe4SDH5/866UYWgKfrLLGFkxIQdkw1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;cls-hooked&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;CLS using AsynWrap instead of async-listener - Node &amp;gt;= 4.7.0. Latest version: 4.2.2, last published: 6 years ago. Start using cls-hooked in your project by running `npm i cls-hooked`. There are 719 other projects in the npm registry using cls-hooked.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.npmjs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3년 전에 이와 관련된 내용을 다룬 적이 있는데 아마 동작 원리가 비슷할 것 같습니다. &lt;a href=&quot;https://kay0426.tistory.com/60&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kay0426.tistory.com/60&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1690117919651&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;AsyncHooks로 context에 DB connection 저장하기&quot; data-og-description=&quot;서론 Node 13.X 버전부터 AsyncHooks라는 것이 도입되었다. AsyncHook이란, 쉽게 설명하자면, 실행 context마다 고유한 id를 줘서 callback후에도 callback 전의 context를 알 수 있게 해주는 것이다. 더 자세한 내&quot; data-og-host=&quot;kay0426.tistory.com&quot; data-og-source-url=&quot;https://kay0426.tistory.com/60&quot; data-og-url=&quot;https://kay0426.tistory.com/60&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bwGfwM/hyTpgVWrFl/0Ii8VuMYZZ1TYYRC4hLW2k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bSHzA4/hyTo1Et2SG/5YjTSJKJN2QEhnDeTuaQJ1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/Hd0T4/hyTo3WzXl5/Xo8AAObx1tKEem87hKuvgK/img.png?width=1080&amp;amp;height=1080&amp;amp;face=0_0_1080_1080&quot;&gt;&lt;a href=&quot;https://kay0426.tistory.com/60&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kay0426.tistory.com/60&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bwGfwM/hyTpgVWrFl/0Ii8VuMYZZ1TYYRC4hLW2k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bSHzA4/hyTo1Et2SG/5YjTSJKJN2QEhnDeTuaQJ1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/Hd0T4/hyTo3WzXl5/Xo8AAObx1tKEem87hKuvgK/img.png?width=1080&amp;amp;height=1080&amp;amp;face=0_0_1080_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;AsyncHooks로 context에 DB connection 저장하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;서론 Node 13.X 버전부터 AsyncHooks라는 것이 도입되었다. AsyncHook이란, 쉽게 설명하자면, 실행 context마다 고유한 id를 줘서 callback후에도 callback 전의 context를 알 수 있게 해주는 것이다. 더 자세한 내&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kay0426.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리화 했기 때문인지라 사용법은 정말 쉽습니다. 설명대로,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 라이브러리를 설치하고&lt;/p&gt;
&lt;pre id=&quot;code_1690117989806&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add typeorm-transactional
yarn add typeorm reflect-metadata&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main.ts에 initializeTransactionalContext()를 추가해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1690118030006&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { NestFactory } from '@nestjs/core';
import { initializeTransactionalContext } from 'typeorm-transactional';

import { AppModule } from './app';

const bootstrap = async () =&amp;gt; {
  initializeTransactionalContext();

  const app = await NestFactory.create(AppModule);

  await app.listen(3000);
};

bootstrap();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 app.module.ts 에 다음과 같이 dataSourceFactory에 addTransactionalDataSource()를 추가해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690118090344&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { DataSource } from 'typeorm';
import { addTransactionalDataSource } from 'typeorm-transactional';

@Module({
  imports: [
    UsersModule,
    TypeOrmModule.forRootAsync({
      useFactory() {
        return {
          type: 'mysql',
          host: 'localhost',
          port: 3306,
          username: 'root',
          password: 'root',
          database: 'store',
          logging: true,
          entities: [User],
          synchronize: true,
        }
      },
      async dataSourceFactory(options) {
        if (!options) {
          throw new Error('Invalid options passed')
        }
        
        return addTransactionalDataSource(new DataSource(options));
      }
    }),
  ],
  controllers: [...],
  providers: [...],
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 별다른 수정없이 트랜잭션이 잘 동작합니다; 너무 쉽습니다;&lt;/p&gt;
&lt;pre id=&quot;code_1690118253226&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Transactional } from 'typeorm-transactional'

@Injectable()
export class UsersService {
  constructor(
    private usersRepository: UsersRepository,
  ) {}
  
  @Transactional()
  async makeUsersBeInactive(userNos: number[]): Promise&amp;lt;void&amp;gt; {
      const users = await this.usersRepository.findByIds(userNos);
      users.forEach((user) =&amp;gt; user.beInactive());
      await this.usersRepository.save(users);
      throw new Error() // 예외를 던지면 transaction이 rollback
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별 다른 이유가 없다면 현재로서는 typeorm-transactional을 사용하는 것이 베스트 프랙티스가 아닌가 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 예전 부터 NestJS에서 typeorm transaction을 잘 사용하는 방법에 대해 고민해본 점을 정리해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Service layer에서 트랜잭션과 관련된 애플리케이션 로직과 User를 다루는 비즈니스 로직이 혼재되어 있는 부분이 좋지 못하다는 것을 밝혔습니다. 이를 해결하기 위해 query runner를 어떻게 다룰지를 긴 여정을 통해 살펴봤고 결국에는 UsersRepository scope를 Request로 두면서 request 객체에 query runner를 저장하여 이를 해결하였습니다. 또한, decorator를 이용해 트랜잭과 관련된 애플리케이션 로직을 감추는데 성공했습니다. 하지만 이 경우 Request 마다 객체를 생성하는 방식이라 리소르를 낭비할 수 있다는 한계점이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 좋은 방법은 이미 있는 오픈소스를 사용하는 것입니다. 이미 이 문제를 해결하기 위해 많은 사람들이 노력해온것 같고 그 방법으로는 typeorm-transactional(&lt;a href=&quot;https://www.npmjs.com/package/typeorm-transactional&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.npmjs.com/package/typeorm-transactional&lt;/a&gt;)을 사용하는 것입니다. typeorm-transctional은 cls-hooked 기반으로 동작하여 db connection을 async local storage에 저장하여 동작할 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 실제 프로덕트에서 NestJS가 더 흥했으면 하는 바람입니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 글 읽어주셔서 감사합니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>NestJS</category>
      <category>asynchook</category>
      <category>cls hooked</category>
      <category>Injection scopes</category>
      <category>nestJS</category>
      <category>Spring</category>
      <category>spring boot</category>
      <category>Transactional</category>
      <category>TypeORM</category>
      <category>typeorm-transactional</category>
      <category>트랜잭션</category>
      <author>iKay</author>
      <guid isPermaLink="true">https://kay0426.tistory.com/75</guid>
      <comments>https://kay0426.tistory.com/75#entry75comment</comments>
      <pubDate>Sun, 23 Jul 2023 13:48:02 +0900</pubDate>
    </item>
    <item>
      <title>MySQL innodb_flush_log_at_trx_commit 옵션</title>
      <link>https://kay0426.tistory.com/73</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에 있어서 MySQL 설정 중 하나인 innodb_flush_log_at_trx_commit 옵션이 무엇인지 알아두면 좋다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서 내용을 참고&lt;/a&gt;하는 것이 가장 정확하겠지만 영어문서를 읽는 것이 익숙하지 않다면 아래 정리 내용을 봐도 좋을 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;innodb_flush_log_at_trx_commit 옵션은 InnoDB 스토리지 엔진에서 트랜잭션의 커밋(commit) 작업 시 로그 파일을 언제 디스크에 쓸 것인지를 결정하는 옵션이다. DB 운영 중 고민 해야 할 부분이 성능(performance)와 데이터 트랜잭션 커밋 신뢰성(ACID) 사이의 균형일텐데 이 둘 사이의 균형을 조절하는 옵션이라 보면 될 것 같다. 성능과 트랜잭션 커밋된 데이터의 신뢰성은 양립할 수 없고 경우에 따라 이 둘의 중요도에 따라 옵션을 잘 선택하는 것이 중요하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;innodb_flush_log_at_trx_commit 은 0, 1, 2 값을 가질 수 있는데 &lt;u&gt;기본값은 1 이다&lt;/u&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 경우에 대해 설명하면 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1.&amp;nbsp; innodb_flush_log_at_trx_commit = 0 인 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션의 커밋이 발생할 때마다 로그를 즉시 기록하지 않고, 일정 시간(기본적으로 대략 1초이지만 항상 1초를 보장하지는 않음)마다 로그가 기록되고 디스크에 flush 된다. 따라서 flush 되지 않은 트랜잭션은 DBMS가 다운되면 해당 데이터를 잃어버리게 된다. 또한 데이터 영속도 시간차가 발생해 트랜잭션에서 데이터는 삽입, 변경 후 직후 트랜잭션에서 쿼리하면 데이터가 제대로 쿼리되지 않을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 DBMS의 안정성은 낮겠지만 가장 높은 성능을 낼 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.&amp;nbsp; innodb_flush_log_at_trx_commit = 1 인 경우 (default, 기본값)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션의 커밋이 발생할 때마다 로그가 기록되고 디스크에 flush 된다. 즉, 모든 트랜잭션에 대해 &lt;span style=&quot;background-color: #ffffff; color: #555555;&quot;&gt;full ACID compliance를 준수한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 데이터베이스 시스템의 안정성이 가장 높겠지만 매우 빈번한 디스크 I/O가 발생하므로 성능 저하가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.&amp;nbsp; innodb_flush_log_at_trx_commit = 2 인 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션의 커밋이 발생할 때마다 로그를 기록하고, 일정 시간(기본적으로 대략 1초이지만 항상 1초를 보장하지는 않음)마다 디스크에 flush 한다. &amp;nbsp;따라서 0으로 설정했을 때와 마찬가지로 flush 되지 않은 트랜잭션은 DBMS가 다운되면 해당 데이터를 잃어버리게 된다. 또한 데이터 영속도 시간차가 발생해 트랜잭션에서 데이터는 삽입, 변경 후 직후 트랜잭션에서 쿼리하면 데이터가 제대로 쿼리되지 않을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 데이터베이스 시스템의 안정성과 성능 모두를 어느 정도 만족시키는 중간정도의 선택이라 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Log Flushing 에 대해&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;innodb_flush_log_at_timeout 설정에 의해 Log Flushing 주기가 설정되는데 기본값은 1초이다. 이 설정에 대해서는 우선 &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_flush_log_at_timeout&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서 링크로&lt;/a&gt; 대체하고 다음에 다시 포스팅하면 좋을 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 설정에 대해 성능과 DMBS의 안정성에 대한 관계를 표로 정리하면 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 70px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;b&gt;innodb_flush_log_at_trx_commit 값&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;b&gt;성능(Performance)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;b&gt;안정성(ACID compliance)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;상&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;하&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;1(기본값)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;하&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;중&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;중&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능과 안정성은 양립할 수 없는 관계이기 때문에 innodb_flush_log_at_trx_commit 옵션을 어떻게 설정하는지는 정답 없으며 운영하는 서비스 성격에 맞게 &lt;span&gt;DBMS의 안정성과 성능 사이에서 균형있게 &lt;/span&gt;설정하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>MySQL</category>
      <category>innodb_flush_log_at_trx_commit</category>
      <category>MYSQL</category>
      <author>iKay</author>
      <guid isPermaLink="true">https://kay0426.tistory.com/73</guid>
      <comments>https://kay0426.tistory.com/73#entry73comment</comments>
      <pubDate>Fri, 3 Mar 2023 00:17:50 +0900</pubDate>
    </item>
    <item>
      <title>헤드퍼스트 디자인패턴</title>
      <link>https://kay0426.tistory.com/72</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;얼마 전 지인에게 &lt;b&gt;「헤드퍼스트 디자인페턴」&lt;/b&gt;이라는 책을 추천받아 읽게 되어 책을 읽고 난 후 느낀 점을 남기려 한다. 평점은 4/5점을 주고 싶다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.yes24.com/Product/Goods/108192370&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://www.yes24.com/Product/Goods/108192370&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1661092735673&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;book&quot; data-og-title=&quot;헤드 퍼스트 디자인 패턴 - YES24&quot; data-og-description=&quot;유지관리가 편리한 객체지향 소프트웨어 만들기!&amp;ldquo;『헤드 퍼스트 디자인 패턴(개정판)』 한 권이면 충분하다.이유 1. 흥미로운 이야기와 재치 넘치는 구성이 담긴 〈헤드 퍼스트〉 시리즈! 하나&quot; data-og-host=&quot;www.yes24.com&quot; data-og-source-url=&quot;http://www.yes24.com/Product/Goods/108192370&quot; data-og-url=&quot;http://www.yes24.com/Product/Goods/108192370&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cm7UoM/hyPxgK9J7g/4cEM5pdoOGS9wuGeBn8UrK/img.jpg?width=994&amp;amp;height=1200&amp;amp;face=699_172_845_331,https://scrap.kakaocdn.net/dn/b1sUIy/hyPv8gWPJS/HrLoJPOtOTh8EBiBCMuuZk/img.jpg?width=994&amp;amp;height=1200&amp;amp;face=699_172_845_331,https://scrap.kakaocdn.net/dn/upXI2/hyPxry9uck/KAC8aCXRJhiJy0cfUUANp1/img.jpg?width=994&amp;amp;height=1200&amp;amp;face=699_172_845_331&quot;&gt;&lt;a href=&quot;http://www.yes24.com/Product/Goods/108192370&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://www.yes24.com/Product/Goods/108192370&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cm7UoM/hyPxgK9J7g/4cEM5pdoOGS9wuGeBn8UrK/img.jpg?width=994&amp;amp;height=1200&amp;amp;face=699_172_845_331,https://scrap.kakaocdn.net/dn/b1sUIy/hyPv8gWPJS/HrLoJPOtOTh8EBiBCMuuZk/img.jpg?width=994&amp;amp;height=1200&amp;amp;face=699_172_845_331,https://scrap.kakaocdn.net/dn/upXI2/hyPxry9uck/KAC8aCXRJhiJy0cfUUANp1/img.jpg?width=994&amp;amp;height=1200&amp;amp;face=699_172_845_331');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;헤드 퍼스트 디자인 패턴 - YES24&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;유지관리가 편리한 객체지향 소프트웨어 만들기!&amp;ldquo;『헤드 퍼스트 디자인 패턴(개정판)』 한 권이면 충분하다.이유 1. 흥미로운 이야기와 재치 넘치는 구성이 담긴 〈헤드 퍼스트〉 시리즈! 하나&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.yes24.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 자주 읽고 쓰는 사람이라면 디자인 패턴을 학습하는 것이 필요하다고 생각한다. 왜냐하면 다른 사람의 코드를 더 쉽게 읽을 수 있어 더 빠르게 파악할 수 있다고 생각하기 때문이다. 좋은 코드를 작성하기 위해서는 다른 사람의 코드를 오해 없이 이해하는 것이 우선이다.&amp;nbsp;이런 이유로 스스로도 평소에 틈틈이 이런 부류의 책을 시간을 내서 읽으려고 노력한다. 그리고 좋은 예제 소스코드(주로 유명한 오픈소스)로 부터 내가 아는 패턴을 발견했을 때 즐거움을 느끼곤 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인 패턴의 고전은 그 유명한&amp;nbsp;&lt;b&gt;「GoF의 디자인 패턴」&amp;nbsp;&lt;/b&gt;일 것이다. 이 책도 한 번 읽은 적은 있는데 워낙 오래전이고 이해를 못 했던 부분이 많아 책을 구해서 다시 읽어 보는 것도 좋을 것 같다. 그런데 한글 번역판 내용이 좋지 못하다는 것을 많이 들어서 한글책을 살지 말지 계속 고민이 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.yes24.com/Product/Goods/17525598&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://www.yes24.com/Product/Goods/17525598&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1661093692502&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;book&quot; data-og-title=&quot;GoF의 디자인 패턴 - YES24&quot; data-og-description=&quot;이 책은 디자인 패턴을 다룬 이론서로 디자인 패턴의 기초적이고 전반적인 내용을 학습할 수 있다.&quot; data-og-host=&quot;www.yes24.com&quot; data-og-source-url=&quot;http://www.yes24.com/Product/Goods/17525598&quot; data-og-url=&quot;http://www.yes24.com/Product/Goods/17525598&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cJgSOy/hyPxfFu2Hg/OKEcXqHywKLKlxIZTT8ymK/img.jpg?width=465&amp;amp;height=600&amp;amp;face=0_0_465_600,https://scrap.kakaocdn.net/dn/dplRnn/hyPxnQ3ZMb/xHD82CaC4Woto4y9Sn2Swk/img.jpg?width=465&amp;amp;height=600&amp;amp;face=0_0_465_600,https://scrap.kakaocdn.net/dn/8osSD/hyPxdnnI9D/bIahlKEVJ55Qjzo0J6kcY0/img.jpg?width=465&amp;amp;height=600&amp;amp;face=0_0_465_600&quot;&gt;&lt;a href=&quot;http://www.yes24.com/Product/Goods/17525598&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://www.yes24.com/Product/Goods/17525598&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cJgSOy/hyPxfFu2Hg/OKEcXqHywKLKlxIZTT8ymK/img.jpg?width=465&amp;amp;height=600&amp;amp;face=0_0_465_600,https://scrap.kakaocdn.net/dn/dplRnn/hyPxnQ3ZMb/xHD82CaC4Woto4y9Sn2Swk/img.jpg?width=465&amp;amp;height=600&amp;amp;face=0_0_465_600,https://scrap.kakaocdn.net/dn/8osSD/hyPxdnnI9D/bIahlKEVJ55Qjzo0J6kcY0/img.jpg?width=465&amp;amp;height=600&amp;amp;face=0_0_465_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GoF의 디자인 패턴 - YES24&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 책은 디자인 패턴을 다룬 이론서로 디자인 패턴의 기초적이고 전반적인 내용을 학습할 수 있다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.yes24.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서론이 길었던 것 같다. 본론으로 들어가 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책은 각 챕터마다 하나의 디자인패턴을 다루고 있다. 각 챕터에서는 실제 세계에 있을법한 상황을 던져주고 이 문제를 해결하기 위해 어떤 디자인 패턴을 사용하면 될지를 보여주면서 문제를 해결해 나간다. 문제를 해결하기 위해 실제 어떻게 설계하면 될지 다이어그램을 그려보고 그것을 Java 코드로 옮기고 있다. 그렇게 문제를 해결한 후 각 디자인 패턴에 대한 정의, 설명도 자세히 다루는 편이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 나도 어떤 소프트웨어 문제를 해결하기 위해 바로 코딩을 시작하기보다는 다이어그램을 이렇게 저렇게 많이 그려 보면서 설계를 많이 고민하는 편이다. 그래서 그런지 이 책에서 문제를 해결하는 방식이 공감이 잘 되는 편이었다. 만약 소프트웨어 입문 자라면 이 책을 통해 디자인 패턴에 대한 지식뿐만 아니라 실제 있을 법한 문제를 해결해 나가는 습관까지 기를 수 있지 않을까 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 챕터의 순서는 저자가 생각하기에 주로, 자주 사용되는 순서로 배치를 한 것 같다. 그렇다고 뒤에 등장하는 챕터들이 중요하지 않은 것은 아닌 것 같다. 하지만 맨 마지막 챕터에서 여러 디자인 패턴을 두 페이지 정도에 간략히 설명하고 있는 부분이 있는데 이 부분은 조금 아쉽다. 각 패턴을 간략히 설명하려다 보니 설명이 너무 부족한 것 같다. 차라리 맨 마지막 챕터는 없애는 게 좋지 않을까 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책의 내용은 굳이 요약하지 않겠다. 그냥 사서 읽어라. 읽는 과정에서 이 패턴이 어떤 문제를 해결할 수 있는지를 따라가는 재미를 내가 뺏고 싶지 않기 때문이다.&lt;span&gt;&amp;nbsp;책의 구성이 좋은 편이고 몰입력도 좋은 편인 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 디자인패턴 입문, 초보 수준이라면 이 책을 한 번 읽어보는 것이 큰 도움이 될 것 같다. 아마 코드를 보는 시야가 더 넓어질 것이고 코드를 작성하는 습관이 달라질 것이다. 그리고 이 책에서 다루지 않고 있는 다른 디자인 패턴을 학습하는 데 있어서 큰 밑거름이 될 것 같다. 좋은 책인 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>독후감/디자인패턴</category>
      <category>헤드퍼스트 디자인패턴</category>
      <author>iKay</author>
      <guid isPermaLink="true">https://kay0426.tistory.com/72</guid>
      <comments>https://kay0426.tistory.com/72#entry72comment</comments>
      <pubDate>Mon, 22 Aug 2022 00:21:25 +0900</pubDate>
    </item>
    <item>
      <title>multi-stage로 TypeScript 기반 Nodejs docker image 크기 줄이기</title>
      <link>https://kay0426.tistory.com/71</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 몇 년 전만 해도 소스코드의 runtime을 container 환경으로 빌드해서 배포&amp;amp;운영 한다는 것이 생소하고 어려웠던 것으로 인식되던 것으로 기억한다. 하지만 요즘은 container 환경으로 빌드만 할 수 있으면 쉽게 배포&amp;amp;운영할 수 있게 해주는 제품들이 시장에 많이 나와서 이러 진입장벽이 많이 낮아진 것 같다. 심지어 소규모 스타트업 회사가 kubernates를 운영할 줄 모른다고 해도&amp;nbsp;AWS의 ECS, EKS 등을 사용한다면 container 환경으로 쉽게 배포&amp;amp;운영하는데 큰 지장이 없을 정도이다. 현재 내가 몸담고 있는 서비스회사에서도 ECS를 사용하는 곳이 있는데 크게 무리없이 서비스를 잘 하고 있는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;container는 주로 Docker가 사용되는 것 같다. 사실 나도 Docker말고는 다른 container를 사용해본적이 거의 없고 사실상 현재 업계에서는 Docker가 container로써 표준인 것 처럼 인식되는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 Nodejs 런타임을 Docker container로 배포하고 운영하는게 난이도가 쉽고 작은 서비스에서는 매우 효율적이라고 생각한다. 왜 좋은지는 다음에 한 번 상세히 다뤄보면 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 사실상 Nodejs로 백엔드를 운영한다면 JavaScript를 직접 쓰는 곳은 거의 없고 TypeScript를 사용할 것이다. 나도 현재는 매일매일 TypeScript를 사용하고 있다. 하지만 나는 Nodejs 백엔드를 TypeScript를 이용해서 제대로 운영하고 있을까 매일매일 의심하기도 한다. TypeScript, Nodejs 자체가 진입장벽이 낮고 자유도가 높으니 말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker도 마찬가지이다. 아마 구글에 &quot;typescript dockerfile example&quot; 라고 치고 맨 위에 있는 Dockerfile 스크립트를 복사 붙여넣기 해도 실제 운영환경에 배포가능한 docker image가 빌드될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1659369348250&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM node:18-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD [&quot;npm&quot;, &quot;start&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 생각없이 붙여넣은 스크립트가 당장은 동작하기 때문에 지금 이 순간에는 정답이라 볼 수 있겠지만 프로젝트 덩치가 커지면 배포되는 container image 크기도 커질 것이기 때문에 배포시 효율적이지 못하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서론이 길었는데 오늘은 간단히 TypeScript 기반 &amp;nbsp;Nodejs Docker container image의 크기를 줄이는 방법에 대해 살펴보려고 한다. 여러 시도가 있겠지만 최종적으로는 multi-stage 방법으로 그 크기를 최소화 해 볼 예정이다. &amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스코드는&amp;nbsp;&lt;a href=&quot;https://github.com/i-kay/multi-stage-example-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/i-kay/multi-stage-example-1&lt;/a&gt;&amp;nbsp;를 참고하면 되겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;준비&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제를 보이기 위해 NestJs 웹 애플리케이션 하나를 준비했다. 아래 cli 한 줄이면 NestJS 프로젝트가 생성된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1659369680755&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;nest new .&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.dockerignore 파일을 하나 만들어서 내용을 채워준다. Dockerfile에서 Copy시 무시되는 파일, 디렉토리들이다.&lt;/p&gt;
&lt;pre id=&quot;code_1659369775255&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vi .dockerignore

# 아래 내용 추가
dist
node_modules

# :wq&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게만 하면 웹 애플리케이션 하나를 개발하고 컨네티어로 만들 준비가 벌써 끝나게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1 단계 - 시작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서론에서 보였듯이 Dockerfile을 아래처럼 작성하고,&lt;/p&gt;
&lt;pre id=&quot;code_1659370010739&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM node:18-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD [&quot;npm&quot;, &quot;start&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;image를 build 해보자&lt;/p&gt;
&lt;pre id=&quot;code_1659370272040&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker build . -t multi-stage-example-1:1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;image size는 다음과 같다&lt;/p&gt;
&lt;pre id=&quot;code_1659370335457&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker images           
---
REPOSITORY             TAG  ...  SIZE
multi-stage-example-1  1    ...  532MB&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2 단계 - devDependencies를 삭제?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nodejs를 경험해봤다면 devDependencies가 운영할때 필수가 아님을 누구나 알 것이다. 그렇다면 Dockerfile에서 &amp;nbsp;`npm install` 된 `node_modules`를 모두 삭제하고 다시 `npm install --production`을 해주면 container image size가 줄어들지 않을까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dockerfile을 아래와 같이 수정해보자. 방금 설명대로 `npm_modules`를 production 환경에 맞게 재설치해서 크기를 줄이려 하는 의도이다.&lt;/p&gt;
&lt;pre id=&quot;code_1659370605534&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM node:18-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
+ RUN rm -rf ./npm_modules
+ RUN npm install --production
EXPOSE 3000
CMD [&quot;npm&quot;, &quot;start&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;image를 build 해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1659370725291&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker build . -t multi-stage-example-1:2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 용량이 줄어 들지 않는다. 오히려 2MB 늘었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1659370826935&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker images           
---
REPOSITORY             TAG  ...  SIZE
multi-stage-example-1  2    ...  534MB
multi-stage-example-1  1    ...  532MB&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1659370875806&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;+ RUN rm -rf ./npm_modules
+ RUN npm install --production&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 추가했을지라도 그 전 명령어들이 image layer에 남아있기 때문이다. 쉽게 말해서 `&amp;gt;` 표시된 부분이 image가 build될 때 그대로 남게 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1659370997516&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM node:18-alpine
WORKDIR /usr/src/app
COPY package*.json ./
&amp;gt; RUN npm install
&amp;gt; COPY . .
&amp;gt; RUN npm run build
+ RUN rm -rf ./npm_modules
+ RUN npm install --production
EXPOSE 3000
CMD [&quot;npm&quot;, &quot;start&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 내용은 `docker image history`를 통해 자세히 볼 수 있다. 결과를 보면 알 수 있겠듯이 상당한 용량을 차지하는 `npm install` 부분이 제거되지 않을 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1659371073715&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker history multi-stage-example-1:2
IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
c1674234860f   2 hours ago   CMD [&quot;npm&quot; &quot;start&quot;]                             0B        buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      2 hours ago   EXPOSE map[3000/tcp:{}]                         0B        buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      2 hours ago   RUN /bin/sh -c npm install --production # bu&amp;hellip;   1.98MB    buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      2 hours ago   RUN /bin/sh -c rm -rf ./npm_modules # buildk&amp;hellip;   0B        buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      2 hours ago   RUN /bin/sh -c npm run build # buildkit         62.5kB    buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      2 hours ago   COPY . . # buildkit                             470kB     buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      2 hours ago   RUN /bin/sh -c npm install # buildkit           365MB     buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      2 hours ago   COPY package*.json ./ # buildkit                325kB     buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      2 hours ago   WORKDIR /usr/src/app                            0B        buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      4 days ago    /bin/sh -c #(nop)  CMD [&quot;node&quot;]                 0B        
&amp;lt;missing&amp;gt;      4 days ago    /bin/sh -c #(nop)  ENTRYPOINT [&quot;docker-entry&amp;hellip;   0B        
&amp;lt;missing&amp;gt;      4 days ago    /bin/sh -c #(nop) COPY file:4d192565a7220e13&amp;hellip;   388B      
&amp;lt;missing&amp;gt;      4 days ago    /bin/sh -c apk add --no-cache --virtual .bui&amp;hellip;   7.77MB    
&amp;lt;missing&amp;gt;      4 days ago    /bin/sh -c #(nop)  ENV YARN_VERSION=1.22.19     0B        
&amp;lt;missing&amp;gt;      4 days ago    /bin/sh -c addgroup -g 1000 node     &amp;amp;&amp;amp; addu&amp;hellip;   153MB     
&amp;lt;missing&amp;gt;      4 days ago    /bin/sh -c #(nop)  ENV NODE_VERSION=18.7.0      0B        
&amp;lt;missing&amp;gt;      13 days ago   /bin/sh -c #(nop)  CMD [&quot;/bin/sh&quot;]              0B        
&amp;lt;missing&amp;gt;      13 days ago   /bin/sh -c #(nop) ADD file:a2648378045615c37&amp;hellip;   5.53MB&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3 단계 - devDependencies를 정말 삭제할 수 없을까?&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하자면 docker가 build될 때 devDependencies를 삭제할 수 있다. `RUN` 명령어 마다 layer를 만들기 때문에 하나의 명령어로 실행되게끔 만들면 되긴 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 change 이고&lt;/p&gt;
&lt;pre id=&quot;code_1659371327295&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM node:18-alpine
WORKDIR /usr/src/app
- COPY package*.json ./
- RUN npm install
COPY . .
- RUN npm run build
- RUN rm -rf ./npm_modules
- RUN npm install --production
+ RUN npm install \
	&amp;amp;&amp;amp; npm run build \
	&amp;amp;&amp;amp; rm -rf ./npm_modules \
	&amp;amp;&amp;amp; npm install --production
EXPOSE 3000
CMD [&quot;npm&quot;, &quot;start&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이건 결과 script 이다. 이것을 보면 되겠다.&lt;/p&gt;
&lt;pre id=&quot;code_1659371401186&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM node:18-alpine
WORKDIR /usr/src/app
COPY . .
RUN npm install \
	&amp;amp;&amp;amp; npm run build \
	&amp;amp;&amp;amp; rm -rf ./npm_modules \
	&amp;amp;&amp;amp; npm install --production
EXPOSE 3000
CMD [&quot;npm&quot;, &quot;start&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보다시피 심플하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 image를 build 해서&lt;/p&gt;
&lt;pre id=&quot;code_1659371444983&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker build . -t multi-stage-example-1:3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;image 사이즈를 보면 줄긴했다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1659371508459&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker images           
---
REPOSITORY             TAG  ...  SIZE
multi-stage-example-1  3    ...  301MB
multi-stage-example-1  2    ...  534MB
multi-stage-example-1  1    ...  532MB&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과로만 보면 이 방법도 나쁘진 않은 것 같다. 하지만 이 방법의 문제점은 한 줄로 명령어를 작성해서 보기가 좋지 않고 layer가 캐싱될 수 없다는 것이다. 대부분의 경우 소스코드의 수정이 일어나지만 node module을 새로 설치하는 경우는 드물 것이다. 그래서&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1659371683996&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;COPY package*.json ./
RUN npm install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;부분을 따로 1 단계, 2 단계 에서 분리한 것인데 3 단계에서는 그것을 이용할 수가 없다. 이 캐싱을 잘 이용하면 빌드를 매우 빠르게 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker의 layer caching에 대해서는 다음에 한 번 다뤄보면 좋을 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4 단계 - multi-stage 도입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 3 단계까지 고민을 했었고 더 나은 방법이 없을까 찾아봤을 때 쉽게 답을 찾을 수 있어서 조금 허무했다. 그 방벙이 지금 소개할 multi-stage 방식이다. multi-stage 방식에서는 흔히 builder라는 stage와 실제 배포될 stage를 구분해서 build 될 때 불필요한 것들을 가지지 않게 하는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Dockerfile을 보자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1659371956385&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM node:18-alpine AS builder
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM node:18-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --production
COPY --from=builder /usr/src/app/dist ./dist
EXPOSE 3000
CMD [&quot;npm&quot;, &quot;run&quot;, &quot;start:prod&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;script를 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FROM으로 stage는 구분되고 첫 번째 stage는 `AS builder`라고 붙여서 `builder` 라고 명시를 하였다. 이름을 붙이지 않으면 `STAGE-0` 이런식으로 숫자가 붙게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`package.json`과 `package-lock.json`만 복사해오고 `npm install --production` 함으로써 `devDependencies`는 제외하고 `dependencies`만 설치돼 매우 경량화 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 stage에서 `/usr/src/app`이란 곳에 install &amp;amp; build를 하게 돼서 1 단계와 같은 결과를 가지고 있을 것이다 하지만 두 번째 stage로 넘어오면서 `COPY --from=builder {src} {dest}` 해준 것만 실제로 전달되며 최종 stage로만 image는 build 된다. script 내용대로 `COPY --from=builder` 할 때 실행가능한 .js 파일들, 즉 dist 디렉토리만 복사해오게 돼 매우 경량화된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 `devDependencies`애 있던 `nestcli`를 이젠 더 이상 사용할 수 없기 때문에 `npm run start:prod` 로 실행되도록 해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;image를 build하고&lt;/p&gt;
&lt;pre id=&quot;code_1659372127175&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker build . -t multi-stage-example-1:4&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;image size를 보자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1659372640158&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker images           
---
REPOSITORY             TAG  ...  SIZE
multi-stage-example-1  4    ...  258MB
multi-stage-example-1  3    ...  301MB
multi-stage-example-1  2    ...  534MB
multi-stage-example-1  1    ...  532MB&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1 단계로부터 약 280MB나 줄일 수 있게 됐고 캐싱 레이어도 유지할 수 있게 되었다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker history를 보자. 첫 번째 stage에서 install, build 등은 포함되지 않는 것을 한 번 더 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1659372778025&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker history multi-stage-example-1:4
IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
4d6a2809a48f   2 hours ago   CMD [&quot;npm&quot; &quot;run&quot; &quot;start:prod&quot;]                  0B        buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      2 hours ago   EXPOSE map[3000/tcp:{}]                         0B        buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      2 hours ago   COPY /usr/src/app/dist ./dist # buildkit        60.8kB    buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      2 hours ago   RUN /bin/sh -c npm install --production # bu&amp;hellip;   91.3MB    buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      2 hours ago   COPY package*.json ./ # buildkit                325kB     buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      2 hours ago   WORKDIR /usr/src/app                            0B        buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      4 days ago    /bin/sh -c #(nop)  CMD [&quot;node&quot;]                 0B        
&amp;lt;missing&amp;gt;      4 days ago    /bin/sh -c #(nop)  ENTRYPOINT [&quot;docker-entry&amp;hellip;   0B        
&amp;lt;missing&amp;gt;      4 days ago    /bin/sh -c #(nop) COPY file:4d192565a7220e13&amp;hellip;   388B      
&amp;lt;missing&amp;gt;      4 days ago    /bin/sh -c apk add --no-cache --virtual .bui&amp;hellip;   7.77MB    
&amp;lt;missing&amp;gt;      4 days ago    /bin/sh -c #(nop)  ENV YARN_VERSION=1.22.19     0B        
&amp;lt;missing&amp;gt;      4 days ago    /bin/sh -c addgroup -g 1000 node     &amp;amp;&amp;amp; addu&amp;hellip;   153MB     
&amp;lt;missing&amp;gt;      4 days ago    /bin/sh -c #(nop)  ENV NODE_VERSION=18.7.0      0B        
&amp;lt;missing&amp;gt;      13 days ago   /bin/sh -c #(nop)  CMD [&quot;/bin/sh&quot;]              0B        
&amp;lt;missing&amp;gt;      13 days ago   /bin/sh -c #(nop) ADD file:a2648378045615c37&amp;hellip;   5.53MB&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 TypeScript기반 Nodejs 웹 애플리케이션을 Docker container로 build 하는 과정에서 multi-stage 방식을 사용함으써 container image를 줄여봤다. container image size가 계속 커진다면 이렇게 multi-stage 방식으로 builder를 분리시켜 운영하는 것도 고려해볼 만한 선택지 중 하나가 될 것 같다. &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://docs.docker.com/develop/dev-best-practices/#how-to-keep-your-images-small&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.docker.com/develop/dev-best-practices/#how-to-keep-your-images-small&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Docker</category>
      <category>docker</category>
      <category>docker multi-stage</category>
      <category>multi-stage</category>
      <category>Nodejs</category>
      <category>TypeScript</category>
      <author>iKay</author>
      <guid isPermaLink="true">https://kay0426.tistory.com/71</guid>
      <comments>https://kay0426.tistory.com/71#entry71comment</comments>
      <pubDate>Tue, 2 Aug 2022 01:54:26 +0900</pubDate>
    </item>
    <item>
      <title>Clustered Index와 Secondary Index</title>
      <link>https://kay0426.tistory.com/70</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Index란?&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Index란, 테이블에 대한 동작속도를 높여주는 자료구조이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;조금 더 자세히 설명하자면,&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Disk에서 발생하는 I/O 시간은 Main Memery에서 발생하는 I/O에 비해 매우 크다. 따라서, DB를 통해 데이터를 검색시 Disk I/O가 빈번히 일어나지 않도록 하는 것이 유리할 것 같다. 이를 위해 Index를 사용한다. Index를 통해 Disk I/O의 횟수를 최대한 줄여 DB의 성능을 높일 수 있는 것이다.&amp;nbsp;&lt;/span&gt;따라서, Index는 DB에서 뿐만 아니라, random access를 할 때 사용하면 적합하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아래 그림은 id=106번 학생을 검색하는 과정이다. Disk I/O에 비해, block 내의 탐색 시간은 매우 적게 걸리므로 일단 무시한다. 순서대로 정렬되어 있는 key인 경우, 이진탐색을 한다면 꽤 빠를 것이다.&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Index를 이용한다면,&lt;br /&gt;[index block1 -&amp;gt; (index block2) -&amp;gt; data block3-&amp;gt; 2 번째 레코드] 이렇게 탐색하게 되면 총 I/O는 2~3번 일어난다. 다른 레코드를 검색한다고 해도 총 2~3번이 보장될 것이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;index를 이용하지 않으면, &lt;br /&gt;최선의 경우 바로 data block3에 random access 하게 되어 1번의 I/O만으로 id=106을 검색성공 하겠지만, 최악의 경우는 data block1, data block2, ... data block N 이렇게 모든 N개의 disk block을 scan 해야 할 수도 있다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eJiPaD/btqURutDaZd/wnNGnGKBW1psivCy5yTZk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eJiPaD/btqURutDaZd/wnNGnGKBW1psivCy5yTZk0/img.png&quot; data-alt=&quot;그림1. Index를 통해 data를 검색하는 과정 예&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eJiPaD/btqURutDaZd/wnNGnGKBW1psivCy5yTZk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeJiPaD%2FbtqURutDaZd%2FwnNGnGKBW1psivCy5yTZk0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1. Index를 통해 data를 검색하는 과정 예&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Index는 data와 별개로 저장된다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;data 파일 레코드를 저장하는 것에 비해 공간을 덜 차지해 하나의 디스크 블록에 더 많은 데이터가 담길 수 있어서 한 번의 I/O 발생시, Index는 데이터 파일 레코드에 비해 더 많은 데이터를 읽을 수 있다. 따라서, &lt;span style=&quot;color: #333333;&quot;&gt;index block에 데이터가 더 많이 저장되기 때문에 index를 fully scan 하는 것이 data를 fully scan 하는 것 보다 부담이 덜하다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Index를 통해 data를 검색을 하는 것은 Index가 없는 것 보다 시간이 적게 걸리겠지만 새로운 data를 insert 하는 경우는 index 와 data를 순차적으로 정렬해야 하는 경우가 발생할 수도 있기 때문에 좋은 성능을 내지 못 할 수가 있어서 항상 Index를 사용하는 것은 올바르지 않다. 또한, Index는 Clustered Index와 Secondary Index로 보통 종류가 나뉘는데 둘의 특징을 잘 구분해서 사용해야 더 좋은 성능을 낼 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Spatial Index(Spatial Index는 R-Tree)를 제외하고는 보통 &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/glossary.html#glos_b_tree&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;B-Tree&lt;/a&gt;로 구현된다. B-Tree로 구현되기 때문에 Binary Tree에 비해 child가 많아 Block 단위로 I/O가 일어날 수 있도록 구현한다면 I/O를 최소화 할 수 있어 Binary Tree에 비해 유리한 것 같다. Memory DB의 경우 Index로써 Hash Table로 구현되는 경우도 있다. 이 경우 access time은 B-Tree보다 빠르겠지만 크기를 비교하는데는 사용할 수 없다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Index에 대해 다룰 주제는 많겠지만 오늘은 Clustered Index와 Non Clustered Index의 탐색 방법에 초점을 맞춰서 차이점을 비교해 보고자 한다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Clustered Index&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;InnoDB 엔진에서 table의 Primary Key를 정의하면 Clustered Index가 된다.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;테이블당 하나만 가질 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Insert시 data가 정렬되고 index는 data block의 첫 번째 레코드의 주소값을 갖게 된다. index가 곧 바로 data block에 접근해서 Secondary Index보다 동작이 빠른 편이다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;data가 정렬되어 저장되므로, Secondary Index에 비해 범위로 질의를 하는 것에 유리하다. 빈번한 I/O가 덜 발생할 것이기 때문이다.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;사실 위에서 나타낸 그림이 Clustered Index 이다. 그림을 보면 알겠지만 id=106인 Brian을 찾기 위해 우선 index block을 scan(1~2번의 I/O 발생) 하고, data block3번 에서(1번의 I/O 발생) id=106 Brian을 총 2~3번의 I/O로 찾을 수 있다. &amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;범위로 검색하는 경우를 보자. 만약 id=101~104번의 학생을 fetch 한다고 생각해보자. 그러면, index block을 scan 한 후(1~2번의 I/O 발생) data block1를 가져오고(I/O 1번 발생), data block2(I/O 1번 발생)를 가져오면 범위 검색을 할 수 있다. 총 3~4번의 I/O가 발생하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xdoPE/btqUZtT6YMm/Kkm0RYLiLPBC7bxsYvYcSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xdoPE/btqUZtT6YMm/Kkm0RYLiLPBC7bxsYvYcSK/img.png&quot; data-alt=&quot;그림 2. Clustered Index&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xdoPE/btqUZtT6YMm/Kkm0RYLiLPBC7bxsYvYcSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxdoPE%2FbtqUZtT6YMm%2FKkm0RYLiLPBC7bxsYvYcSK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림 2. Clustered Index&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Secondary Index&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Primary Key 이외에 필요한 정렬 기준이 있을 경우 사용한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;테이블당 여러 개 가질 수 있다.&lt;/li&gt;
&lt;li&gt;data record가 정렬되어 있지 않다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;index가 data record의 모든 주소값을 가지고 있어야 한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;unique 하지 않아도 된다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;데이터 레코드가 index 순서대로 정렬 되어 있지 않기 때문에 범위 조건으로 검색하게 되면 많은 I/O가 발생할 수 있다. 그러면 좋은 성능을 내지 못 할 것이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;index는 모든 레코드에 대한 색인 데이터를 들고 있어야 하고 정렬된다. 따라서, update, delete, insert시 오래 걸릴 수 있고, clustered index에 비해 더 많은 공간을 차지하게 된다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;학생의 경우 id는 Primary Key로써 clustered index가 되지만 name으로 검색 조건을 더 빠르게 하고 싶을 수 있다. 이런 경우 Secondary Index를 사용한다. 아래 그림이 그 예이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;name이 Jane인 학생을 찾는 과정은 id=105로 Jane을 찾는 것과 비슷하다. 하지만 ASCII Code, 알파벳 순서대로 Bill~James의 범위의 레코드를 가져오려면 index block1로부터 data block1, data block2, data block3, data block4를 모두 읽어야 하므로 그림상 I/O가 5~6번 정도 발생할 것임이 보인다. 이처럼 범위로 데이터를 검색하는 경우는 clustered index에 비해 불리하고, 모든 data의 entry에 대해 색인을 갖고 있어야 해서 더 많은 공간을 차지하게 된다. &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HyDsj/btqUYgocN5V/QhQwkIrD0MkhpGVjUhs0j0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HyDsj/btqUYgocN5V/QhQwkIrD0MkhpGVjUhs0j0/img.png&quot; data-alt=&quot;그림 3. Secondary Index&amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HyDsj/btqUYgocN5V/QhQwkIrD0MkhpGVjUhs0j0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHyDsj%2FbtqUYgocN5V%2FQhQwkIrD0MkhpGVjUhs0j0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림 3. Secondary Index&amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p&gt;index에 대해 간략히 알아봤고, 그 중 clustered index, secondary index의 차이와 동작방식에 대해 조금 정리를 해 보았다. 아직 실제로 인덱스로 DB를 튜닝해본 경험이 거의 없지만 나중에 실제로 튜닝해보면서 성능이 얼마나 차이가 나는지 실험하여 내용을 추가하면 좋을 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>MySQL</category>
      <author>iKay</author>
      <guid isPermaLink="true">https://kay0426.tistory.com/70</guid>
      <comments>https://kay0426.tistory.com/70#entry70comment</comments>
      <pubDate>Thu, 28 Jan 2021 01:03:58 +0900</pubDate>
    </item>
    <item>
      <title>TypeScript, tsconfig.json 주요 설정</title>
      <link>https://kay0426.tistory.com/69</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p&gt;TypeScript를 더 잘 사용하기 위해 컴파일 하기 위한 설정, 동작방식을 정의하는 tsconfig를 어느 정도 이해할 필요가 있다고 생각한다. Node.js 프로젝트에 TypeScript를 설정하기 위해서 공식문서를 참고하는 것이 가장 정확하겠지만 정보의 양이 방대해서 주요한 몇몇 설정만 정리해 보고자 한다. 앞으로 지식과 경험이 더 넓어지면 내용을 계속 덧붙여 나갈 계획이다. 자세한 설명은 당연히 &lt;a href=&quot;https://www.typescriptlang.org/tsconfig&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;를 참조하는 것이 좋겠다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;tsconfig.json을 작성하는 것은 옳고 그름의 문제가 아니다. 프로젝트 마다, 팀마다 각각 상황에 맞게 설정하면 되는 것이기 때문에 무조건 따라야 하는 설정은 없다. 잘 모르겠으면 권장하는대로, 기본값대로 사용해도 될 것이다. 다만, 각 프로젝트 마다 성격에 맞게 사용하기 위해, &quot;&lt;i&gt;어떤 설정이 있었고, 이 설정이 무엇을 뜻했던 것 같다.&quot;&lt;/i&gt; 정도는 알고 있는 것이 좋을 것 같아서 정리해 보고자 한다. 만약 TypeScript 설정에 당장은 관심이 없고 잘은 모르겠지만 TypeScript를 사용해야 하는 상황이라면 공식 문서에서 &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/tsconfig-json.html#tsconfig-bases&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;추천&lt;/a&gt;해주는 대로 extends 해서 일단 사용하는 것도 큰 노력을 사용하지 않는 좋은 방법일 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;TypeScript가 컴파일 될 때, 보통 TypeScript 프로젝트의 root에 tsconfig.json 파일이 위치하는데, compile하기 위한 소스코드의 위치, compile 옵션을 이곳에 json 파일로 나타내면 된다. 만약, tsc 명령어로 compile 하는데 input file이 주어지지 않으면 현재 디렉토리에서 부터 부모 디렉토리 체인으로 거슬러 올라가면서 tsconfig.json 파일을 찾는다. 반대로, tsc 명령어에 input file이 주어지면 tsconfig.json 파일을 검색하지 않는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;설정들에 대한 설명들을 레퍼런스 문서에서 나누고 있는 큰 범주대로 나누었다. 공식문서에는 알파벳 순으로 설정들을 나열하고 있지만 여기서는 개인적으로 중요하다고 생각되는 것을들 먼저 나열할 수도 있다. &amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnng3G/btqTNBeBeKo/o6tgeiol74lWW0Sir3Lyw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnng3G/btqTNBeBeKo/o6tgeiol74lWW0Sir3Lyw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnng3G/btqTNBeBeKo/o6tgeiol74lWW0Sir3Lyw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbnng3G%2FbtqTNBeBeKo%2Fo6tgeiol74lWW0Sir3Lyw0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. File Inclusion&lt;/h2&gt;
&lt;p&gt;TypeScript가 compile에 포함하거나 제거할 파일들을 명시하고, compile 하기 위해 참조해야하는 설정이 있는 파일들을 설정한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;include, exclude&lt;/h3&gt;
&lt;p&gt;컴파일 할때 포함하거나 제외할 filenames 배열이나 패턴을 명시한다. 패턴을 명시할 때 &lt;a href=&quot;https://en.wikipedia.org/wiki/Glob_(programming)&quot;&gt;glob pattern&lt;/a&gt;을 사용하고, filenames의 path는 tsconfig.json 이 있는 곳으로 부터 상대경로에 있는 것들이다.&lt;/p&gt;
&lt;p&gt;주의할 점은 exclude는 include와 함께 동작하면서 include 될 것을 exclude 시키는 것이지, code에 있는 것을 제외시키는 것은 아니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;include는 defalut로 다음과 같은 값을 갖는다.&lt;/p&gt;
&lt;pre id=&quot;code_1610028002621&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[&quot;**/*&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;exclude는 default로 다음과 같은 값을 갖는다.&lt;/p&gt;
&lt;pre id=&quot;code_1610028859612&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 만약 outDir이 명시되어 있으면 그것도 default에 포함된다.
[&quot;node_modules&quot;, &quot;bower_components&quot;, &quot;jspm_packages&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예&lt;/p&gt;
&lt;pre id=&quot;code_1610028127845&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;include&quot;: [&quot;src/**/*&quot;, &quot;tests/**/*&quot;],
  &quot;exclude&quot;: [&quot;tests/modules/*&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;extends&lt;/h3&gt;
&lt;p&gt;상속받을 다른 base file을 명시한다. 설정들은 base file이 우선 load 되고, 상속하는 file이 override 하게 동작한다. 팀 내에서 &lt;a href=&quot;https://www.npmjs.com/package/@tsconfig/recommended&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이런식으로&lt;/a&gt; tsconfig.json 파일을 npm으로 관리해도 좋을 것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;files&lt;/h3&gt;
&lt;p&gt;include 시킬 파일들을 개별적으로 포함시킨다. 보통의 경우 include만으로 해결될 것이기 때문에 아마 잘 사용되지 않을 것 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예&lt;/p&gt;
&lt;pre id=&quot;code_1610030119004&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {},
  &quot;files&quot;: [
    &quot;core.ts&quot;,
    &quot;sys.ts&quot;,
    &quot;types.ts&quot;,
    &quot;scanner.ts&quot;,
    &quot;parser.ts&quot;,
    &quot;utilities.ts&quot;,
    &quot;binder.ts&quot;,
    &quot;checker.ts&quot;,
    &quot;tsc.ts&quot;
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Compiler Options&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Project Options&lt;/h3&gt;
&lt;p&gt;TypeScript가 compile 되는 방식, 동작하는 방식을 설정한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;사용할 수 있는 많은 옵션들이 있다. 중요하다고 생각되는 것들, 자주 사용될 것 같은 것 위주로 정리해 보고자 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;lib&lt;/h4&gt;
&lt;p&gt;lib는 target과 함께 사용되기도 해서 target과 함께 구분해가면서 봐두면 좋다고 생각한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;lib는 타입스크립트는 built-in JS API(예, Math)와 브라우저 환경(예, document) 등에서 사용할 수 있는 타입을 기본적으로 제공한다. JS 버전마다 사용할 수 있는 문법이나 API 등이 조금씩 다른다. 보통 계속 추가되면서 하위호환을 유지하는 편이다. 이런 것들 중 target에 설정한 버전에 부합하는 API 등의 타입을 사용할 수 있게 해준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예를 들어,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;target에 ES6라고 설정한 경우, ES6+부터 Map이라는 것을 JS에서 네이티브하게 사용할 수 있기 때문에 Map을 사용하기 위한 타입을 제공한다.&lt;/li&gt;
&lt;li&gt;백엔드 개발을 하는 경우 브라우저 환경의 개발환경을 설정할 필요가 없다. 그래서 이런 경우, &quot;dom&quot; 타입을 설정할 필요가 없다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;polyfill을 사용하거나, nodeJS가 native 적으로 ECMAScript 버전을 일부 지원하는 경우 target 버전과 완전히 동일하지 않아도 liib로 선택적으로 버전을 올려서 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;타입이 어떻게 설정될까 궁금해서 조금 찾아보다가,&amp;nbsp;&lt;a href=&quot;https://github.com/microsoft/TypeScript/tree/master/lib&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;TypeScript의 lib에 대한 소스코드&lt;/a&gt;에서 동작하는 방식을 추론할 수 있었는데, 소스코드에서 볼 수 있듯이 상위버전은 하위 버전을 포함해서 사용할 수 있게 되고, lib.esXXXX.d.ts는 ECMAScript의 XXXX 버전에 해당하는 모든 것을 포함한다는 것도 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1610791792660&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 예, lib.es2019.d.ts 파일의 소스코드

/// &amp;lt;reference no-default-lib=&quot;true&quot;/&amp;gt;


/// &amp;lt;reference lib=&quot;es2018&quot; /&amp;gt;
/// &amp;lt;reference lib=&quot;es2019.array&quot; /&amp;gt;
/// &amp;lt;reference lib=&quot;es2019.object&quot; /&amp;gt;
/// &amp;lt;reference lib=&quot;es2019.string&quot; /&amp;gt;
/// &amp;lt;reference lib=&quot;es2019.symbol&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Node.JS 가 지원하는 ES 버전에 맞는 맞는 lib 설정을 할 필요가 있을 것이다. 그렇다면 Node.JS가 어떤 버전의 ES를 지원하는지 잘 알아야 한다. 그런 곳을 정리해 놓은 곳이 있는데, &lt;a href=&quot;https://node.green&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이것&lt;/a&gt;을 보고 lib에 ES 버전을 적절히 올리거나 필요한 것만 추가해서 사용하면 될 것 같다. 예를 들어, Node12 버전을 사용하는 경우, ES2019는 100% 지원하고 ES2020은 BigInt, Promose.allSettled 등을 일부 지원하므로 이런 것을 추가해서 사용하면 될 것 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;default는 ES3이고, 현대 JavaScript 브라우저와 애플리케이션은 보통은 ES6+에서 동작하므로 ES6(ES2015)로 설정하기를 개인적으로 권장한다. 만약 &lt;span style=&quot;color: #333333;&quot;&gt;target을 설정했는데&lt;/span&gt;&amp;nbsp;lib를 생략했다면, lib는 target과 같은 버전으로 변경되어 동작하게 된다고 공식문서에서 설명하고 있다. 다음은 공식문서의 &lt;a href=&quot;https://www.typescriptlang.org/tsconfig#target&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;target 부분에서 인용한 것&lt;/a&gt;이다. 이 부분 때문이라도 lib를 target과 함께 봐두면 좋은 것 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Changing&lt;span&gt;&amp;nbsp;&lt;/span&gt;target&lt;span&gt;&amp;nbsp;&lt;/span&gt;also changes the default value of&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://www.typescriptlang.org/#lib&quot;&gt;lib&lt;/a&gt;. You may &amp;ldquo;mix and match&amp;rdquo;&lt;span&gt;&amp;nbsp;&lt;/span&gt;target&lt;span&gt;&amp;nbsp;&lt;/span&gt;and&lt;span&gt;&amp;nbsp;&lt;/span&gt;lib&lt;span&gt;&amp;nbsp;&lt;/span&gt;settings as desired, but you could just set&lt;span&gt;&amp;nbsp;&lt;/span&gt;target&lt;span&gt;&amp;nbsp;&lt;/span&gt;for convenience.&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;target&lt;/h4&gt;
&lt;p&gt;target은 TypeScript가 컴파일 되는 결과물인 JavaScript 코드로 변환되는 버전을 결정한다. 즉, 위의 lib 설정에서 밝힌 것 처럼, 만약 백엔드 Node.JS를 운영할 것이라면, 운영하는 &lt;a href=&quot;https://node.green&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Node.JS가 지원하는 버전&lt;/a&gt;의 ES 버전에 맞게 target을 지정하면 될 것이다. 프론트엔드라면 개발하려는 브라우져 버전에 맞게 target을 설정하면 될 것 같다. &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;allowJs&lt;/h4&gt;
&lt;p&gt;TypeScript는 기본적으로 JavaScript 위에서 동작하는 언어이고, JavaScript를 TypeScript로 점차적으로 변환시켜서 사용할 수도 있다. 이 점으로 볼 때, .ts 파일에서 .js 파일도 import 있게 할 수 있어야 하는 경우도 있을 것 같다. 그래서 allowJS는 .ts, .tsx 파일에서 .js 파일을 import 할 수 있게 해준다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기본적으로 false 이지만, JavaScript 프로젝트를 점차 TypeScript로 변환시키는 경우 true로 설정하는 것이 필요할 것이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;removeComments&lt;/h4&gt;
&lt;p&gt;TypeScript 코드를 JavaScript로 컴파일 시킬 때, 주석(comments)을 모두 제거 시키는 옵션이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기본적으로 false 이지만, 번들 사이드 등을 줄이기 위해 소스코드에서 주석을 제거하고 싶다면 true로 하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;sourceMap&lt;/h4&gt;
&lt;p&gt;sourcemap fiile(xxx.js.map)을 생성할 것인지 유무를 설정하는 것이다. 이 file은 JavaScript와 함께 생성되면, debugger나 다른 툴이 본래의 TypeScript 소드 코드를 볼 수 있게 해주도록 한다. hello.ts를 컴파일 하고 sourceMap을 true로 하면 hello.js와 함께 hello.js.map 파일이 생성되는 것을 볼 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기본값은 false인데, 디버깅이 필요한 개발환경에서는 true로 하고, 배포 사이즈를 줄여야 하는 배포환경에서는 false 해, 서로 다른 환경에서 적절하게 컴파일 되도록 설정하면 될 것 같다. &amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Strict Checks&lt;/h3&gt;
&lt;p&gt;Type을 얼마나 엄격하게 검사할지를 검사하는 옵션이라고 보면 될 것 같다.&amp;nbsp;strict 에는 8가지 옵션이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1) strict&lt;/p&gt;
&lt;p&gt;2) alwaysStrict&lt;/p&gt;
&lt;p&gt;3) noImplicityAny&lt;/p&gt;
&lt;p&gt;4) noImplycityThis&lt;/p&gt;
&lt;p&gt;5)&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;strictBindCallApply&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;6)&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;strictFunctionTypes&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;7)&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;strictNullChecks&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;8) strictPropertyInitialization&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위에서도 밝혔지만, 여기서는 각 옵션이 어떤 것인지를 간단히만 밝히고 있다. 더 자세한 설명은 공식문서를 보는 것이 더 잘 이해가 될 것이고 정확하다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) strict&lt;/h4&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;만약 이 모든 옵션을 모두 사용하려면 단지, { &quot;strict&quot;: true } 로 설정하면 될 것 같다. 하지만, JavaScript에서 TypeScript로 옮기는 경우에는 바로 strict 를 적용할 수 없으니 여러 옵션들 중 선택해서 사용해야 하는 경우도 있을 것이다. 그래서 나머지 strict 특징들을 알아두었다가 필요할 때 적절히 사용하는 것도 좋을 것 같다. 또한 나머지 각각의 특징을 이해하는 것이 strict를 이해하는 것이기도 할 것 같다. 그리고 strict: true로 설정한 후 TypsScript 버전을 올렸을 때 추가되는 속성이 있다면 그 시점에 타입에러가 발생할 수도 있을 것이다. &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;default는 false 이지만 true가 권장된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) alwaysStrict&lt;/h4&gt;
&lt;p&gt;strict과 비슷해서 헷갈릴 수 있을 것 같다. strict는 위에서 설명한대로 모든 옵션을 사용하겠다라는 것이지만 alwaysStrict는 &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Strict_mode&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ECMAScript strict 모드&lt;/a&gt;를 사용하겠다는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;default는 false 이지만 true가 권장된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3) &lt;span style=&quot;color: #333333;&quot;&gt;noImplicityAny&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;noImplicityAny설정을 끄고, argument 등의 type을 명시하지 않으면 arguemnt는 any로 type이 추론된다. 하지만 설정을 켜면 TypeScript는 이렇게 any로 추론되는 것을 에러로 간주하게 된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;default는 false 이지만 true가 권장된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;4) noImplycityThis&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;this의 contex가 any를 추론하게 된다면 현재 객체의 context가 아니라는 것을 뜻하게 되어서 이 경우 에러를 발생시킨다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;default는 false 이지만 true가 권장된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;5)&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;strictBindCallApply&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;TypeScript가 call, bind 그리고 apply 를 사용할 때, 함수, 메소드의 argument의 타입을 추론하여 사용할 수 있게 한다. 옵션이 켜져잇을 경우 잘못된 타입의 argument를 넘기면 에러가 발생한다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;default는 false 이지만 true가 권장된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;6)&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;strictFunctionTypes&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;옵션을 켜면 함수, 메소드의 argument의 타입을 더 정확히 추론하여 사용할 수 있게 해준다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;default는 false 이지만 true가 권장된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;7)&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;strictNullChecks&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;strictNullChecks 옵션이 꺼져 있다면, null 또는 undefined 체킹이 무시되는데 이것은 런타임 에러를 발생시킬 가능성이 크다. 컴파일 시간에 이런 에러를 잡지 못하면 보통 오타, 값의 미대입 등으로 존재하지 않는 개체의 속성, 값이 없는 속성에 접근하는 경우가 많이 발생하게 되어 런타임 에러오 이어지는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt; &lt;span style=&quot;color: #333333;&quot;&gt;default는 false 이지만 true가 권장된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;8) strictPropertyInitialization&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;strictPropertyInitialization 옵션을 켜두면 초기화 되지 않는 클래스의 멈버 변수에 대해 에러를 발생시킨다. 초기화 시키는 방법으로는 속성에 기본값을 직접 대입하거나 생성자에서 대입하는 두 가지 방법이 있는데, 이런식으로 속성을 초기화 하지 않으면 undefined가 되어서 undefined도 될 수 있다는 타입을 명시할 필요가 있다. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Module Resolution&lt;/h3&gt;
&lt;p&gt;모듈의 path를 resolve 하는 설정이다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;baseurl&lt;/h4&gt;
&lt;p&gt;TypeScript가 compile할 때 소스코드의 가장 상위 base directory, root directory를 설정하는 것이다. 다른 파일에서 import 등을 하여 경로를 명시해야 할 때, 상대경로를 사용하지 않아도 baseurl이 명시된 곳을 기준으로 절대경로처럼 시작점을 명시해주는 것이다. 실제로 path를 resolve 할 때에도 상대경로가 아니라면 baseurl 부터 찾는다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;paths&lt;/h4&gt;
&lt;p&gt;모듈 import시 반복되는 path를 사용하거나 깊이가 길어지면 path 가 길어진다. path에 별명을 줘서 map 시켜서 간단하게 사용할 수 있게 해준다. angular를 해보았다면 &quot;@app&quot; 이라던가, &quot;@config&quot; 등의 path를 사용해 본적이 있을 텐데 이것이 그런 설정이다. &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Expetrimental&lt;/h3&gt;
&lt;p&gt;JavaScript에 확실히 추가된 기능은 아닐지라도 실험적으로 사용할 수 있는 것들을 TypeScript도 실험적으로 지원하는 기능들이다. 이런 것들은 버전이 바뀌면서 없어질 수도 있는 안정적인 기능은 아니니 남용해서는 안될 것 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;experimentalDecorator&lt;/h4&gt;
&lt;p&gt;현재 &lt;a href=&quot;https://github.com/tc39/proposal-decorators&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;decorator&lt;/a&gt; 라는 것은 TC39 standard 의 stage 2에 있는데, TypeScript에서 이 decorator를 실험적으로&amp;nbsp;사용할 수 있게 해준다. &lt;span style=&quot;color: #333333;&quot;&gt;참고로, decorator는 class, method, member variable, argument 등에 사용될 수 있고,&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/decorators.html#decorators&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt; decorator를 정의&lt;/a&gt;할 때, tartget, propertyKey, descriptor를 가진다.&lt;/span&gt;&amp;nbsp;흡사 Java 기반 프레임워크인 Spring의 annonation과 모습과 기능이 비슷해 보인다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;emitDecoratorMetadata&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://www.npmjs.com/package/reflect-metadata&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;reflect-metadata&lt;/a&gt; 모듈과 함께 사용되는 decorator를 위한 metadata의 타입을 추론하게 해준다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기본값으로 false 이다. &amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Advanced&lt;/h3&gt;
&lt;p&gt;굳이 번역하자면 &quot;기타&quot;정도의 범주에 속하는 것 같다. 컴파일 하거나 기능 동작에 있어서 크게 중요한 부분을 차지 하지 않는 것 같지만 그래도 몇몇 옵션들은 유용한 것 같으니 알아두면 좋을 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;noEmitOnError&lt;/h4&gt;
&lt;p&gt;noEmitOnError을 true로 설정하면, 에러발생시 JavaScript 소드코드, source-maps, declaration이 dist 디렉토리에 생기지 않는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기본값으로 false 이지만 사소하더도 error가 있다는 코드가 빌드되는 것을 원치 않는다면 true로 설정하는 것이 좋을 것 같다. &amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;skipLibCheck&lt;/h4&gt;
&lt;p&gt;사용하는 라이브러리의 파일의 타입 검사를 skip 하게 해, type 검사 정확도를 조금 희생할지라도 컴파일 시간을 줄여준다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기본값은 false 인데, true로 하는 것이 권장된다. 아마 보통의 경우 라이브러리의 타입을 검사할 필요가 없기 때문인 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;resolveJsonModule&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;span&gt;.json으로 된 파일의 모듈을 import 할 수 있께 해준다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;기본값은 false 인데, 프로젝트에서 설정 파일 등을 .json 파일을 모듈로써 import 해서 사용할 일이 있다면 true로 해주면 될 것 같다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;noErrorTruncation&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;에러 메시지가 잘리지 않게 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기본값으로 false이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p&gt;현재 실제 프로덕트로 Node.JS로 백엔드를 개발하고 있다. 최근에 TypeScript의 버전을 4.0으로 올리고, NodeJs 버전을 12로 올리면서 이에 적절한 tsconfig.json으로 설정할 필요가 있었다. 이번 tsconfig.json 설정 작업을 위해 조사하는 과정에서 tsconfig 설정만이 아니라, TypeScript에 대해 조금 이해도가 높아진 계기가 된 것 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아직 다루지 않았지만 중요한 설정이 있을 수 있으니 그 때 마다 돌아와서 이 글을 수정할 예정이다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming language/Typescript</category>
      <category>tsconfig</category>
      <category>TypeScript</category>
      <author>iKay</author>
      <guid isPermaLink="true">https://kay0426.tistory.com/69</guid>
      <comments>https://kay0426.tistory.com/69#entry69comment</comments>
      <pubDate>Thu, 7 Jan 2021 03:49:38 +0900</pubDate>
    </item>
    <item>
      <title>TypeScript, any와 unknown 타입 비교</title>
      <link>https://kay0426.tistory.com/68</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVyCYu%2FbtqNr8pWJPC%2FkL1s9ybBLHUkmAKBdPKNj0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;400&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blcT6B/btqOve4n9P0/iKGw5oZuNzNOhGhh9U2hZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blcT6B/btqOve4n9P0/iKGw5oZuNzNOhGhh9U2hZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blcT6B/btqOve4n9P0/iKGw5oZuNzNOhGhh9U2hZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVyCYu%2FbtqNr8pWJPC%2FkL1s9ybBLHUkmAKBdPKNj0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;400&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;any vs unknown&lt;/h2&gt;
&lt;p&gt;TypeScript에서 any와 unknown 타입은 용도가 다르기 때문에 구분해야 해야한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음과 같이 User class가 role에 따라 Member, Store로 나뉘는 경우를 보자. Member는 일반 계정으로 주문을 넣을 수 있고, Store는 상점 계정으로 주문을 완료시킬 수 있는 메소드를 갖는다.&lt;/p&gt;
&lt;pre id=&quot;code_1606655878390&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export abstract class User {
    id!: number;

    role!: 'member' | 'store';
}

export class Member extends User {
    constructor({ id, role}: { id: number; role: 'member' }) {
        super();
        this.id = id;
        this.role = role
    }

    placeOrder() {
        console.log('상품 주문');
    }
}

export class Store extends User {
    constructor({ id, role}: { id: number; role: 'store' }) {
        super();
        this.id = id;
        this.role = role
    }

    completeOrder() {
        console.log('주문 완료 처리');
    }
}

function main() {
    const user1: unknown = new Member({ id: 1, role: 'member'});
    const user2: unknown = new Store({ id: 2, role: 'store'});
    const user3: any = new Member({ id: 1, role: 'member' });

    if (user1 instanceof Member ) {
        user1.placeOrder();
        user1.completeOrder(); // Compile error: Property 'completeOrder' does not exist on type 'Member'.
    }

    if (user2 instanceof Store) {
        user2.placeOrder(); // Compile error: Property 'placeOrder' does not exist on type 'Store'.
        user2.completeOrder();
    }

    user3.placeOrder(); 
    user3.completeOrder(); // Runtime error; user3.completeOrder is not a function
}

main();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;우선 any를 보자. user3은 any type으로 선언되었다. 보다시피, any는 모든 타입의 값이 대입가능 한데, 대입된 값을 사용할 때, 객체의 없는 속성에 접근해도 컴파일 에러가 발생하지 않는다. 마치 JavaScript를 사용하는 것과 같이 말이다. &lt;u&gt;그래서 Runtime error가 발생하게 된다. &lt;/u&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;user1, user2는 unknown 타입으로 선언했다. any와 달리 타입가드(type-gurad)를 해주지 않으면 &lt;u&gt;Compile error가 발생&lt;/u&gt;해서 객체의 속성에 접근할 수 없다. 그래서 Runtime에 일어날 버그를 미리 방지할 수 있겠다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p&gt;보통 type 추론이 어려운 경우 편리성 때문에 any를 사용하는 경우가 많다. 만약 JavaScript 코드를 TypeScript 코드로 옮기고 있거나, 다른 모듈의 type을 추론할 수 없어서 수 없어서 any를 사용하는 경우 정말 사용해도 될지 한 번 더 고민해 보는 것이 좋을 것 같다. TypeScript를 사용하는 이점 없이 JavaScript를 사용하는 것과 같기 때문이다.&lt;/p&gt;</description>
      <category>Programming language/Typescript</category>
      <author>iKay</author>
      <guid isPermaLink="true">https://kay0426.tistory.com/68</guid>
      <comments>https://kay0426.tistory.com/68#entry68comment</comments>
      <pubDate>Sun, 29 Nov 2020 22:42:21 +0900</pubDate>
    </item>
  </channel>
</rss>