<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>GyuTech</title>
    <link>https://tech.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 28 May 2026 09:23:46 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Kyle H</managingEditor>
    <image>
      <title>GyuTech</title>
      <url>https://tistory1.daumcdn.net/tistory/4375648/attach/f169ec71defb410ca857685f83ac8431</url>
      <link>https://tech.tistory.com</link>
    </image>
    <item>
      <title>eclipse 플러그인 오프라인 설치</title>
      <link>https://tech.tistory.com/74</link>
      <description>&lt;p&gt;망분리가 된 환경에서 eclipse만 쓰다보면 답답한 상황에 많이 마주한다.오늘은 gradle로 된 java 프로젝트를 빌드하려고 하는데, 인텔리제이로 딸깍하면 끝났던 것들이 이클립스 환경에서는 순탄하지 않았다.&lt;/p&gt;
&lt;p&gt;이클립스 구버전을 쓰다보니 gradle을 다루는 플러그인인 Buildship이 오래되어 Run Configuraion이 되지 않았다.결국 외부망에서 직접 플러그인을 다운 받아 zip 형태로 옮긴 후 설치할 수 밖에 없었다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;C:\\devtool\\eclipse\\jee-2025-09\\eclipse\\eclipse.exe 
-application org.eclipse.equinox.p2.artifact.repository.mirrorApplication 
-source [https://download.eclipse.org/buildship/updates/latest/](https://download.eclipse.org/buildship/updates/latest/) 
-destination C:\\devtool\\buildship&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;C:\\devtool\\eclipse\\jee-2025-09\\eclipse\\eclipse.exe 
-application org.eclipse.equinox.p2.metadata.repository.mirrorApplication 
-source [https://download.eclipse.org/buildship/updates/latest/](https://download.eclipse.org/buildship/updates/latest/) 
-destination C:\\devtool\\buildship&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Java/환경 설정</category>
      <author>Kyle H</author>
      <guid isPermaLink="true">https://tech.tistory.com/74</guid>
      <comments>https://tech.tistory.com/74#entry74comment</comments>
      <pubDate>Wed, 5 Nov 2025 17:58:30 +0900</pubDate>
    </item>
    <item>
      <title>[Mac] mac에 node.js 설치하기 (homebrew, nvm 사용)</title>
      <link>https://tech.tistory.com/73</link>
      <description>&lt;p&gt;회사에서 신규로 react로 구성된 프로젝트를 맡게 되었다.&lt;br&gt;대충은 못하는 성격이라 퇴근 후에 공부하면서 mac에 환경을 구성해보았다.&lt;br&gt;기록으로 남겨야 나중에 다시 볼 때 편할 것 같다.&lt;/p&gt;
&lt;h2&gt;  1. nvm 설치 (Homebrew 사용)&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install nvm&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;옵션 설명&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;brew install nvm&lt;/code&gt;: Homebrew 패키지 관리자를 통해 nvm(Node Version Manager)을 설치합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;설치가 완료되면 안내 메시지에 &lt;strong&gt;설정 경로&lt;/strong&gt;가 표시됩니다. 보통 다음 경로를 만들어야 합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir ~/.nvm&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;⚙️ 2. 셸 환경 설정&lt;/h2&gt;
&lt;p&gt;nvm은 &lt;strong&gt;bash / zsh 등 셸 초기화 파일&lt;/strong&gt;에 환경 변수를 추가해야 합니다.&lt;br&gt;사용 중인 셸에 따라 아래 중 하나를 수정하세요:&lt;/p&gt;
&lt;h3&gt;zsh (macOS 기본 셸)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nano ~/.zshrc&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;bash 사용 시&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nano ~/.bash_profile&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;파일 맨 아래에 다음 내용을 추가합니다  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export NVM_DIR=&amp;quot;$HOME/.nvm&amp;quot;
[ -s &amp;quot;/opt/homebrew/opt/nvm/nvm.sh&amp;quot; ] &amp;amp;&amp;amp; \. &amp;quot;/opt/homebrew/opt/nvm/nvm.sh&amp;quot;
[ -s &amp;quot;/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm&amp;quot; ] &amp;amp;&amp;amp; \. &amp;quot;/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;옵션 설명&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;export NVM_DIR=&amp;quot;$HOME/.nvm&amp;quot;&lt;/code&gt;: nvm 환경 변수가 저장될 디렉터리 지정.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;[ -s &amp;quot;파일경로&amp;quot; ] &amp;amp;&amp;amp; \. &amp;quot;파일경로&amp;quot;&lt;/code&gt;: 파일이 존재할 경우(&lt;code&gt;-s&lt;/code&gt;) 해당 스크립트를 현재 셸에 로드(&lt;code&gt;.&lt;/code&gt;)합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;변경 후 셸을 다시 로드합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;source ~/.zshrc&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;  3. nvm 설치 확인&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nvm --version&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;출력 예시:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0.39.7&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;→ 이런 식으로 버전이 나오면 정상적으로 설치된 것입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;  4. Node LTS 버전 설치&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nvm install --lts&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;옵션 설명&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--lts&lt;/code&gt;: Long Term Support(장기 지원) 버전 설치. 안정적인 Node 버전을 설치합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;설치 완료 후 자동으로 해당 버전을 활성화하려면:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nvm use --lts&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;현재 사용 중인 Node 버전 확인:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;node -v&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;  5. 프로젝트 기본 버전 설정 (선택사항)&lt;/h2&gt;
&lt;p&gt;프로젝트 루트에서:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo &amp;quot;lts/*&amp;quot; &amp;gt; .nvmrc&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 프로젝트 디렉터리에 들어올 때마다 자동으로 LTS 버전 사용이 가능합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nvm use&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;✅ 최종 점검&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nvm list&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;출력 예시:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-&amp;gt;     v20.18.0
        system
default -&amp;gt; lts/* (-&amp;gt; v20.18.0)
node -&amp;gt; stable (-&amp;gt; v20.18.0) (default)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;모두 정상이라면, 이제 Node LTS 버전 환경이 완벽히 구성된 상태입니다  &lt;/p&gt;
&lt;hr&gt;</description>
      <category>mac</category>
      <author>Kyle H</author>
      <guid isPermaLink="true">https://tech.tistory.com/73</guid>
      <comments>https://tech.tistory.com/73#entry73comment</comments>
      <pubDate>Wed, 5 Nov 2025 00:19:12 +0900</pubDate>
    </item>
    <item>
      <title>Spring Event로 강결합 서비스 분리하기</title>
      <link>https://tech.tistory.com/69</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&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;이 값들은 업무 담당자라면 누구나 수정할 수 있는 값입니다. 그렇다고 변경이 제대로 공유되지는 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;강결합된 서비스&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class Fund(  
    val code: String,  
    val name: String,  
    var team: Team,  
) {  
    fun changeTeam(newTeam: Team) {  
        this.team = newTeam  
    }  
}

class Team(  
    val code: String,  
    val name: String  
)&lt;/code&gt;&lt;/pre&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;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Service  
@Transactional  
class FundUpdateService(  
    private val messengerService: SendMessengerService  
) {  
    private val log = LoggerFactory.getLogger(this::class.java)  

    fun updateFund() {  
        log.info(&quot;펀드 변경 시작~&quot;)  

        val fund = getFundById()  
        fund.changeTeam(Team(&quot;A02&quot;, &quot;IB2팀&quot;))  
        messengerService.sendAlarm(fund)  

        log.info(&quot;펀드 변경 완료!&quot;)  
    }  

    private fun getFundById(): Fund {  
        return Fund(&quot;F01&quot;, &quot;IB1팀-운용&quot;, Team(&quot;A01&quot;, &quot;IB1팀&quot;))  
    }  
}&lt;/code&gt;&lt;/pre&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;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Component  
class SendMessengerService {  

    private val log = LoggerFactory.getLogger(this::class.java)  

    fun sendAlarm(target: Any) {  
        log.info(&quot;메신저 서버 호출 = {}&quot;, target)  

        for (second in 1..3) {  
            Thread.sleep(1000)  
            log.info(&quot;{}초 기다림...&quot;, second)  
        }  

        log.info(&quot;메신저 전송 완료 = {}&quot;, target)  
    }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class FundUpdateServiceTest {  

    @Test  
    @DisplayName(&quot;강결합 양상 테스트&quot;)  
    fun `tightly coupled`() {  
        val fundUpdateService = FundUpdateService(SendMessengerService())  

        fundUpdateService.updateFund()  
    }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20230925222045.png&quot; data-origin-width=&quot;2498&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Vylbe/btsvM11GNZK/UdN1LkfNOL1H4Qf78SYot1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Vylbe/btsvM11GNZK/UdN1LkfNOL1H4Qf78SYot1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Vylbe/btsvM11GNZK/UdN1LkfNOL1H4Qf78SYot1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVylbe%2FbtsvM11GNZK%2FUdN1LkfNOL1H4Qf78SYot1%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;2498&quot; height=&quot;440&quot; data-filename=&quot;Pasted image 20230925222045.png&quot; data-origin-width=&quot;2498&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예외가 발생한다면?&lt;/h3&gt;
&lt;hr data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Component  
class SendMessengerService {  

    private val log = LoggerFactory.getLogger(this::class.java)  

    fun sendAlarm(target: Any) {  
        log.info(&quot;메신저 서버 호출 = {}&quot;, target)  

        for (second in 1..3) {  
            Thread.sleep(1000)  
            log.info(&quot;{}초 기다림...&quot;, second)  
            throw RuntimeException(&quot;메신저 서버 오류 발생!!!!!!!!!!!!!&quot;) 
        }  

        log.info(&quot;메신저 전송 완료 = {}&quot;, target)  
    }  
}&lt;/code&gt;&lt;/pre&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;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20230925222215.png&quot; data-origin-width=&quot;2460&quot; data-origin-height=&quot;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DDyxI/btsvJLE1pe8/GyKbvFuv4imjf9ViCgPva1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DDyxI/btsvJLE1pe8/GyKbvFuv4imjf9ViCgPva1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DDyxI/btsvJLE1pe8/GyKbvFuv4imjf9ViCgPva1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDDyxI%2FbtsvJLE1pe8%2FGyKbvFuv4imjf9ViCgPva1%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;2460&quot; height=&quot;672&quot; data-filename=&quot;Pasted image 20230925222215.png&quot; data-origin-width=&quot;2460&quot; data-origin-height=&quot;672&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;즉, 세부적인 사항 때문에 핵심적인 비즈니스가 이뤄지지 못하게 됩니다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Evenet를 사용하기 위해, ApplicationEventPublisher를 주입받아 줍니다.&lt;/li&gt;
&lt;li&gt;그리고 이벤트를 발행하여 줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Service  
@Transactional  
class FundUpdateServiceWithEvent(  
    private val eventPublisher: ApplicationEventPublisher  
) {  
    private val log = LoggerFactory.getLogger(this::class.java)  

    fun updateFund() {  
        log.info(&quot;펀드 변경 시작~&quot;)  

        val fund = getFundById()  
        fund.changeTeam(Team(&quot;A02&quot;, &quot;IB2팀&quot;))  
        eventPublisher.publishEvent(UpdateEvent(fund))  

        log.info(&quot;펀드 변경 완료!&quot;)  
    }  

    private fun getFundById(): Fund {  
        return Fund(&quot;F01&quot;, &quot;IB1팀-운용&quot;, Team(&quot;A01&quot;, &quot;IB1팀&quot;))  
    }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Component  
class UpdateEventListener(  
    private val sendMessengerService: SendMessengerService  
) {  

    @TransactionalEventListener  
    fun listenEvent(updateEvent: UpdateEvent) {  
        sendMessengerService.sendAlarm(updateEvent.target)  
    }  
}&lt;/code&gt;&lt;/pre&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;트랜잭션이 관련 있을 경우에는 &lt;code&gt;@EventListener&lt;/code&gt; 가 아니라 &lt;code&gt;@TransactionalEventListener&lt;/code&gt;를 사용해아 합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@EventListener&lt;/code&gt; 는 트랜잭션 범위 내에서 동기적으로 실행되고, &lt;code&gt;@TransactionalEventListener&lt;/code&gt; 는 커밋 전/후 등을 자유롭게 지정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@SpringBootTest  
class FundUpdateServiceWithEventTest {  

    @Autowired  
    private lateinit var fundUpdateServiceWithEvent: FundUpdateServiceWithEvent  

    @Test  
    @DisplayName(&quot;이벤트 분리 테스트&quot;)  
    fun `event isolation test`() {  
        fundUpdateServiceWithEvent.updateFund()  
    }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20230925224630.png&quot; data-origin-width=&quot;2762&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2huNU/btsvvCWlBRV/4c7YEnO3kLhMkHSlaiuBa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2huNU/btsvvCWlBRV/4c7YEnO3kLhMkHSlaiuBa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2huNU/btsvvCWlBRV/4c7YEnO3kLhMkHSlaiuBa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2huNU%2FbtsvvCWlBRV%2F4c7YEnO3kLhMkHSlaiuBa1%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;2762&quot; height=&quot;960&quot; data-filename=&quot;Pasted image 20230925224630.png&quot; data-origin-width=&quot;2762&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&amp;nbsp;&lt;/p&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;/li&gt;
&lt;li&gt;&lt;code&gt;@Async&lt;/code&gt; 어노테이션은 메서드를 별도의 쓰레드에서 실행하도록 합니다.&lt;/li&gt;
&lt;li&gt;따라서 &lt;code&gt;@Async&lt;/code&gt; 메서드에서 발생하는 예외는 해당 스레드에서 처리되며, 원래의 트랜잭션에 영향을 주지 않습니다.&lt;/li&gt;
&lt;li&gt;이를 통해 &lt;code&gt;RuntimeException&lt;/code&gt;이 발생하더라도 펀드의 변경은 그대로 유지될 수 있습니다.주의사항&lt;/li&gt;
&lt;li&gt;다만, &lt;code&gt;@Async&lt;/code&gt; 어노테이션을 사용할 때는 다음과 같은 주의사항이 있습니다:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;@Async&lt;/code&gt; 메서드는 항상 &lt;code&gt;public&lt;/code&gt;이어야 하며, 같은 클래스 내에서 직접 호출하면 비동기로 동작하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@EnableAsync&lt;/code&gt; 어노테이션을 Spring Boot 애플리케이션의 설정 클래스에 추가해야 &lt;code&gt;@Async&lt;/code&gt;가 동작합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@EnableAsync  
@Configuration  
class AsyncConfig {  
}&lt;/code&gt;&lt;/pre&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;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@SpringBootApplication  
class SpringEventApplication {  

    @Autowired  
    private lateinit var fundUpdateServiceWithEvent: FundUpdateServiceWithEvent  

    @Bean  
    fun init(): CommandLineRunner {  
        return CommandLineRunner {  
            for (i in 1..100) {  
                fundUpdateServiceWithEvent.asyncUpdateFund()  
            }  
        }  
    }  
}  

fun main(args: Array&amp;lt;String&amp;gt;) {  

    runApplication&amp;lt;SpringEventApplication&amp;gt;()  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20230925234304.png&quot; data-origin-width=&quot;2946&quot; data-origin-height=&quot;1770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VvI85/btsvMNbshIy/NECZafLmfy3ZxrHkDHeBc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VvI85/btsvMNbshIy/NECZafLmfy3ZxrHkDHeBc0/img.png&quot; data-alt=&quot;예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VvI85/btsvMNbshIy/NECZafLmfy3ZxrHkDHeBc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVvI85%2FbtsvMNbshIy%2FNECZafLmfy3ZxrHkDHeBc0%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;2946&quot; height=&quot;1770&quot; data-filename=&quot;Pasted image 20230925234304.png&quot; data-origin-width=&quot;2946&quot; data-origin-height=&quot;1770&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예시&lt;/figcaption&gt;
&lt;/figure&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;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@EnableAsync  
@Configuration  
class AsyncConfig {  

    @Bean(name = [&quot;customAsyncExecutor&quot;])  
    fun taskExecutor(): Executor {  
        val executor = ThreadPoolTaskExecutor()  
        executor.corePoolSize = 10  
        executor.maxPoolSize = 20  
        executor.queueCapacity = 500  
        executor.setThreadNamePrefix(&quot;Async-&quot;)  
        executor.initialize()  
        return executor  
    }  

}&lt;/code&gt;&lt;/pre&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;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Service  
@Transactional  
class FundUpdateServiceWithEvent(  
    private val eventPublisher: ApplicationEventPublisher  
) {  
    private val log = LoggerFactory.getLogger(this::class.java)  

    fun asyncUpdateFund() {  
        log.info(&quot;펀드 변경 시작~&quot;)  

        val fund = getFundById()  
        fund.changeTeam(Team(&quot;A02&quot;, &quot;IB2팀&quot;))  
        eventPublisher.publishEvent(AsyncUpdateEvent(fund))  

        log.info(&quot;펀드 변경 완료!&quot;)  
    }  


    private fun getFundById(): Fund {  
        return Fund(&quot;F01&quot;, &quot;IB1팀-운용&quot;, Team(&quot;A01&quot;, &quot;IB1팀&quot;))  
    }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Component  
class UpdateEventListener(  
    private val sendMessengerService: SendMessengerService  
) {  

    @TransactionalEventListener()  
    @Async(&quot;customAsyncExecutor&quot;)
    fun listenEventWithAsync(updateEvent: AsyncUpdateEvent) {  
        sendMessengerService.sendAlarm(updateEvent.target)  
    }  
}&lt;/code&gt;&lt;/pre&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;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Test  
@DisplayName(&quot;이벤트 분리와 비동기&quot;)  
fun `async event isolation test`() {  
  fundUpdateServiceWithEvent.asyncUpdateFund()  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20230925235113.png&quot; data-origin-width=&quot;2746&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KPF59/btsvvCWlCWX/7zMxgUjrkOFXtK6YPQt6w0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KPF59/btsvvCWlCWX/7zMxgUjrkOFXtK6YPQt6w0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KPF59/btsvvCWlCWX/7zMxgUjrkOFXtK6YPQt6w0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKPF59%2FbtsvvCWlCWX%2F7zMxgUjrkOFXtK6YPQt6w0%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;2746&quot; height=&quot;284&quot; data-filename=&quot;Pasted image 20230925235113.png&quot; data-origin-width=&quot;2746&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&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;쓰레드의 이름은 위에서 설정한 `Async-`로 시작합니다. 호출하는 쓰레드와 명백히 다른 쓰레드입니다.&lt;/li&gt;
&lt;li&gt;이후에 메신저 서버 호출에서 오류가 발생하여도 펀드 변경 작업에는 영향을 주지 못합니다.&lt;/li&gt;
&lt;/ul&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>Spring/Core</category>
      <category>Spring Event</category>
      <category>비동기</category>
      <category>스프링 이벤트</category>
      <author>Kyle H</author>
      <guid isPermaLink="true">https://tech.tistory.com/69</guid>
      <comments>https://tech.tistory.com/69#entry69comment</comments>
      <pubDate>Tue, 26 Sep 2023 00:06:53 +0900</pubDate>
    </item>
    <item>
      <title>'setReadTimeout(int)' is deprecated and marked for removal 해결</title>
      <link>https://tech.tistory.com/67</link>
      <description>&lt;div style=&quot;background-color: #282a36; color: #f8f8f2; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RestTemplate를 사용할 때, 연결 타임아웃 시간과 읽기 타임아웃 시간은 기본적으로 셋팅하게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만&amp;nbsp;spring 6.0 부터는 RestTemplate를 설정할 때, HttpComponentsClientHttpRequestsFactory()에서 setReadTimeout() 메서드를 사용할 수 없습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2158&quot; data-origin-height=&quot;928&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MIJdm/btstxQJmoZA/tPd0vTaoJRB2Mvax39PD9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MIJdm/btstxQJmoZA/tPd0vTaoJRB2Mvax39PD9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MIJdm/btstxQJmoZA/tPd0vTaoJRB2Mvax39PD9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMIJdm%2FbtstxQJmoZA%2FtPd0vTaoJRB2Mvax39PD9K%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;2158&quot; height=&quot;928&quot; data-origin-width=&quot;2158&quot; data-origin-height=&quot;928&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;org.springframework.http.client.HttpComponentsClientHttpRequestFactory&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1660&quot; data-origin-height=&quot;928&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mqBFs/btstxN6VrmI/5F98SlCp5LDZUsa5nodQkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mqBFs/btstxN6VrmI/5F98SlCp5LDZUsa5nodQkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mqBFs/btstxN6VrmI/5F98SlCp5LDZUsa5nodQkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmqBFs%2FbtstxN6VrmI%2F5F98SlCp5LDZUsa5nodQkK%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;1660&quot; height=&quot;928&quot; data-origin-width=&quot;1660&quot; data-origin-height=&quot;928&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 해결&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RestTemplateBuilder를 사용하면 해당 문제를 해결 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1832&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csiL0C/btstMh6nh6C/4ETDwLl0P3D1QZXYuFrG0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csiL0C/btstMh6nh6C/4ETDwLl0P3D1QZXYuFrG0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csiL0C/btstMh6nh6C/4ETDwLl0P3D1QZXYuFrG0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsiL0C%2FbtstMh6nh6C%2F4ETDwLl0P3D1QZXYuFrG0K%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;1832&quot; height=&quot;510&quot; data-origin-width=&quot;1832&quot; data-origin-height=&quot;510&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1694443324504&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean(&quot;restTemplateCustom&quot;)
fun restTemplate(builder: RestTemplateBuilder): RestTemplate {
    return builder.requestFactory { settings -&amp;gt;
                       BufferingClientHttpRequestFactory(
                           ClientHttpRequestFactories.get(HttpComponentsClientHttpRequestFactory::class.java, settings)
                       )
                   }
                  .setConnectTimeout(Duration.ofSeconds(300))
                  .setReadTimeout(Duration.ofSeconds(300))
                  .build()
}&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4276&quot; data-origin-height=&quot;936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbJuuY/btstG8vuQN5/vwpG4zMmJHkLbkfIgKzC61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbJuuY/btstG8vuQN5/vwpG4zMmJHkLbkfIgKzC61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbJuuY/btstG8vuQN5/vwpG4zMmJHkLbkfIgKzC61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbJuuY%2FbtstG8vuQN5%2FvwpG4zMmJHkLbkfIgKzC61%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;4276&quot; height=&quot;936&quot; data-origin-width=&quot;4276&quot; data-origin-height=&quot;936&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1694444875620&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'restTemplateCustom' defined in class path resource [com/mybookgal/mybookgallery/configuration/RestTemplateConfig.class]: Failed to instantiate [org.springframework.web.client.RestTemplate]: Factory method 'restTemplate' threw exception with message: org/apache/hc/client5/http/socket/LayeredConnectionSocketFactory
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:659) ~[spring-beans-6.0.11.jar:6.0.11]
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:647) ~[spring-beans-6.0.11.jar:6.0.11]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1332) ~[spring-beans-6.0.11.jar:6.0.11]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1162) ~[spring-beans-6.0.11.jar:6.0.11]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560) ~[spring-beans-6.0.11.jar:6.0.11]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[spring-beans-6.0.11.jar:6.0.11]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-beans-6.0.11.jar:6.0.11]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.11.jar:6.0.11]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[spring-beans-6.0.11.jar:6.0.11]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.0.11.jar:6.0.11]
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[spring-beans-6.0.11.jar:6.0.11]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1417) ~[spring-beans-6.0.11.jar:6.0.11]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337) ~[spring-beans-6.0.11.jar:6.0.11]
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:888) ~[spring-beans-6.0.11.jar:6.0.11]
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ~[spring-beans-6.0.11.jar:6.0.11]
	... 24 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.client.RestTemplate]: Factory method 'restTemplate' threw exception with message: org/apache/hc/client5/http/socket/LayeredConnectionSocketFactory
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:171) ~[spring-beans-6.0.11.jar:6.0.11]
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:655) ~[spring-beans-6.0.11.jar:6.0.11]
	... 38 common frames omitted
Caused by: java.lang.NoClassDefFoundError: org/apache/hc/client5/http/socket/LayeredConnectionSocketFactory
	at org.springframework.boot.web.client.ClientHttpRequestFactories.get(ClientHttpRequestFactories.java:124) ~[spring-boot-3.1.3.jar:3.1.3]
	at com.mybookgal.mybookgallery.configuration.RestTemplateConfig.restTemplate$lambda$0(RestTemplateConfig.kt:19) ~[main/:na]
	at org.springframework.boot.web.client.RestTemplateBuilder.buildRequestFactory(RestTemplateBuilder.java:662) ~[spring-boot-3.1.3.jar:3.1.3]
	at org.springframework.boot.web.client.RestTemplateBuilder.configure(RestTemplateBuilder.java:628) ~[spring-boot-3.1.3.jar:3.1.3]
	at org.springframework.boot.web.client.RestTemplateBuilder.build(RestTemplateBuilder.java:603) ~[spring-boot-3.1.3.jar:3.1.3]
	at com.mybookgal.mybookgallery.configuration.RestTemplateConfig.restTemplate(RestTemplateConfig.kt:24) ~[main/:na]
	at com.mybookgal.mybookgallery.configuration.RestTemplateConfig$$SpringCGLIB$$0.CGLIB$restTemplate$0(&amp;lt;generated&amp;gt;) ~[main/:na]
	at com.mybookgal.mybookgallery.configuration.RestTemplateConfig$$SpringCGLIB$$2.invoke(&amp;lt;generated&amp;gt;) ~[main/:na]
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258) ~[spring-core-6.0.11.jar:6.0.11]
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[spring-context-6.0.11.jar:6.0.11]
	at com.mybookgal.mybookgallery.configuration.RestTemplateConfig$$SpringCGLIB$$0.restTemplate(&amp;lt;generated&amp;gt;) ~[main/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:139) ~[spring-beans-6.0.11.jar:6.0.11]
	... 39 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) ~[na:na]
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) ~[na:na]
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) ~[na:na]
	... 55 common frames omitted&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 이유는 위에서 javadoc의 클래스 이름들이 빨간색으로 나오는 이유와 동일합니다. 말 그대로 클래스를 찾을 수 없는 상태입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`HttpComponentsClientHttpRequestFactory`를 들어가보면..&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1718&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/59kXY/btstHcYZeyd/F1sdO7WrMyhb5u3O8daBI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/59kXY/btstHcYZeyd/F1sdO7WrMyhb5u3O8daBI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/59kXY/btstHcYZeyd/F1sdO7WrMyhb5u3O8daBI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F59kXY%2FbtstHcYZeyd%2FF1sdO7WrMyhb5u3O8daBI0%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;1718&quot; height=&quot;420&quot; data-origin-width=&quot;1718&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apche HttpComponents 5.1 이상의 버전이 필요하다고 나와있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build.gradle.kts에 의존성을 추가해주겠습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #282a36; color: #f8f8f2; text-align: start;&quot;&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;implementation(&quot;org.apache.httpcomponents.client5:httpclient5:5.2.1&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5&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;gradle 새로고침을 하고나면, 다음과 같이 라이브러리를 불러온 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmfgNv/btstOkatlxp/qmGUOmmP04sKYYTpF4jyDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmfgNv/btstOkatlxp/qmGUOmmP04sKYYTpF4jyDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmfgNv/btstOkatlxp/qmGUOmmP04sKYYTpF4jyDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmfgNv%2FbtstOkatlxp%2FqmGUOmmP04sKYYTpF4jyDK%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;1088&quot; height=&quot;176&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;176&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 링크&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문제에 대한 spring-boot issue 링크입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/75242683/i-am-migrating-spring-boot-version-2-7-3-to-spring-boot-3-0-0-so-existing-code-i&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/75242683/i-am-migrating-spring-boot-version-2-7-3-to-spring-boot-3-0-0-so-existing-code-i&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1694443166496&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;I am migrating spring boot version 2.7.3 to spring-boot 3.0.0 so existing code is breaking related to HttpClients&quot; data-og-description=&quot;import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.springframework.context.anno...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/75242683/i-am-migrating-spring-boot-version-2-7-3-to-spring-boot-3-0-0-so-existing-code-i&quot; data-og-url=&quot;https://stackoverflow.com/questions/75242683/i-am-migrating-spring-boot-version-2-7-3-to-spring-boot-3-0-0-so-existing-code-i&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qXwd1/hyTSAmmMpO/l9oRZKcOJdmMfCeK3Jgq9k/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/75242683/i-am-migrating-spring-boot-version-2-7-3-to-spring-boot-3-0-0-so-existing-code-i&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/75242683/i-am-migrating-spring-boot-version-2-7-3-to-spring-boot-3-0-0-so-existing-code-i&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qXwd1/hyTSAmmMpO/l9oRZKcOJdmMfCeK3Jgq9k/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&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;I am migrating spring boot version 2.7.3 to spring-boot 3.0.0 so existing code is breaking related to HttpClients&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.springframework.context.anno...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-boot/issues/35658&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/spring-projects/spring-boot/issues/35658&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1694443226400&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;RestTemplate builder: HttpComponentsClientHttpRequestFactory has the setReadTimeout method marked as deprecated &amp;middot; Issue #35658 &quot; data-og-description=&quot;Spring boot version: 3.x Cannot instantiate RestTemplate with HttpComponentsClientHttpRequestFactory, it raises Caused by: java.lang.IllegalStateException: Request factory org.springframework.http....&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/spring-projects/spring-boot/issues/35658&quot; data-og-url=&quot;https://github.com/spring-projects/spring-boot/issues/35658&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bG0b5c/hyTSqD29cw/iyNBjSwcPKN729JCgkDd9k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-boot/issues/35658&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/spring-projects/spring-boot/issues/35658&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bG0b5c/hyTSqD29cw/iyNBjSwcPKN729JCgkDd9k/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;RestTemplate builder: HttpComponentsClientHttpRequestFactory has the setReadTimeout method marked as deprecated &amp;middot; Issue #35658&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring boot version: 3.x Cannot instantiate RestTemplate with HttpComponentsClientHttpRequestFactory, it raises Caused by: java.lang.IllegalStateException: Request factory org.springframework.http....&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;</description>
      <category>오류 해결/환경</category>
      <author>Kyle H</author>
      <guid isPermaLink="true">https://tech.tistory.com/67</guid>
      <comments>https://tech.tistory.com/67#entry67comment</comments>
      <pubDate>Mon, 11 Sep 2023 23:43:46 +0900</pubDate>
    </item>
    <item>
      <title>[Querydsl] Querydsl with Kotlin (Kotlin으로 querydsl 사용하기)</title>
      <link>https://tech.tistory.com/66</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. 의존성 설정 &lt;br /&gt;2. JPAQueryFactory 빈으로 등록하기&lt;br /&gt;3. Querydsl을 사용할 인터페이스와 구현 클래스 만들기 &lt;br /&gt;4. 확인해 보기&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kotlin으로 querydsl을 사용하기 위해 작업했던 내용을 기록으로 남겨보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 코드는 &lt;a href=&quot;https://github.com/gyuhwanhwang/my-book-gallery-kotlin&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/gyuhwanhwang/my-book-gallery-kotlin&lt;/a&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;&lt;b&gt;1. 의존성 설정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, querydsl을 사용하기 위해선 `jakarta.persistence.@Entity` 애너테이션을 스캔하여 Q-types를 만들어내야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러기 위해선 kapt(Kotlin Annotation Processing Tool) 플러그인을 사용하여 Kotlin에서도 Java 애너테이션 프로세서를 사용할 수 있게 해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kotlinlang.org/docs/kapt.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kotlinlang.org/docs/kapt.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1694088517917&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;kapt compiler plugin | Kotlin&quot; data-og-description=&quot; &quot; data-og-host=&quot;kotlinlang.org&quot; data-og-source-url=&quot;https://kotlinlang.org/docs/kapt.html&quot; data-og-url=&quot;https://kotlinlang.org/docs/kapt.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/clgCmQ/hyTPAG4XtJ/vea6fkKbW0qDwQ3v2oNIUk/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400&quot;&gt;&lt;a href=&quot;https://kotlinlang.org/docs/kapt.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kotlinlang.org/docs/kapt.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/clgCmQ/hyTPAG4XtJ/vea6fkKbW0qDwQ3v2oNIUk/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400');&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;kapt compiler plugin | Kotlin&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kotlinlang.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;kapt가 공식적으로 추가 개발이 없는 유지 모드에 들어갔다고 이야기하며, ksp로 대신할 것을 권장하고 있습니다. 그런데 querydsl github에 ksp 마이그레이션 문제가 아직 오픈 이슈로 남아있고, 공식 문서랑 해외 쪽 자료 찾아봐도 ksp가 querydsl을 지원한다는 것을 찾을 수가 없네요 ㅜㅜ 아직은 kapt를 사용해야 될 것 같습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 `build.gradle.kts`에 kapt 플러그인을 추가해 줍니다. 저는 이전에 이미 추가해 놓아서 properties라고 주석이 적혀있는데, properties도 Java 애너테이션 프로세서를 사용해야 해서 그렇습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m2WBA/btstmtMFPeo/dLW9bHT7O8vBsT229aE0fK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m2WBA/btstmtMFPeo/dLW9bHT7O8vBsT229aE0fK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m2WBA/btstmtMFPeo/dLW9bHT7O8vBsT229aE0fK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm2WBA%2FbtstmtMFPeo%2FdLW9bHT7O8vBsT229aE0fK%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;1158&quot; height=&quot;578&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서와 같이 querydsl 버전을 변수로 선언해 주고, 디펜던시에 추가해 주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;querydsl-apt에서 kapt 플러그인 사용하여 디펜던시를 추가해 줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRM2vt/btstfR2h9BJ/DtXPyNfEbCnPytZldaE0dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRM2vt/btstfR2h9BJ/DtXPyNfEbCnPytZldaE0dK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRM2vt/btstfR2h9BJ/DtXPyNfEbCnPytZldaE0dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRM2vt%2FbtstfR2h9BJ%2FDtXPyNfEbCnPytZldaE0dK%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;1344&quot; height=&quot;616&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;616&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gradle을 새로고침 해주고, 스프링 부트를 다시 실행시키면 다음과 같이 Q-type들이 만들어지게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKAlDW/btstg3bpKnK/jpYs7CIahJu3zNNI1Pnuz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKAlDW/btstg3bpKnK/jpYs7CIahJu3zNNI1Pnuz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKAlDW/btstg3bpKnK/jpYs7CIahJu3zNNI1Pnuz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKAlDW%2Fbtstg3bpKnK%2FjpYs7CIahJu3zNNI1Pnuz0%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;826&quot; height=&quot;500&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;b&gt;2. JPAQueryFactory 빈으로 등록하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;querydsl을 사용하는 클래스에서 엔티티 매니저를 주입받고, 생성자에서 JPAQueryFactory를 초기화해서 사용해도 되지만, 좀 더 편하게 사용하기 위해 설정 클래스에 빈을 선언해 주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 빈 선언처럼 간단한 문법을 작성할 때 코틀린이 참 매력적으로 느껴지는 것 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsJHCZ/btstnvR2APB/I4z1F9D3CDJO53ERo73MmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsJHCZ/btstnvR2APB/I4z1F9D3CDJO53ERo73MmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsJHCZ/btstnvR2APB/I4z1F9D3CDJO53ERo73MmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsJHCZ%2FbtstnvR2APB%2FI4z1F9D3CDJO53ERo73MmK%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;864&quot; height=&quot;444&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;444&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;b&gt;3. Querydsl을 사용할 인터페이스와 구현 클래스 만들기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Querydsl을 위한 인터페이스와 이를 구현한 클래스, 즉 querydsl을 직접 사용하는 클래스를 만들 차례입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M5n0d/btstqDbacPH/fGIydUd51qysOKCHSEGHn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M5n0d/btstqDbacPH/fGIydUd51qysOKCHSEGHn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M5n0d/btstqDbacPH/fGIydUd51qysOKCHSEGHn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM5n0d%2FbtstqDbacPH%2FfGIydUd51qysOKCHSEGHn0%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;1238&quot; height=&quot;204&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;204&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스 선언은 특별할 게 없습니다. 페이징 기능을 사용할 거라 Spring Data의 Pageable을 인자로, Page를 반환값으로 설정했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1526&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxBNaj/btstqZZktGT/1BQlyq7c8cVFkkgzuqTsiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxBNaj/btstqZZktGT/1BQlyq7c8cVFkkgzuqTsiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxBNaj/btstqZZktGT/1BQlyq7c8cVFkkgzuqTsiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxBNaj%2FbtstqZZktGT%2F1BQlyq7c8cVFkkgzuqTsiK%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;1526&quot; height=&quot;924&quot; data-origin-width=&quot;1526&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 만든 인터페이스를 구현하는, 실제 querydsl을 사용하는 클래스입니다. 위에서 빈으로 등록해 주었던 JPAQueryFactory를 주입받아 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 문제가 없는 것처럼 보이지만, 처음에 기존에 java에서 사용하던 대로 코드를 작성하려니 아래와 같은 두 가지 문제가 있었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) Type missmatch&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SDogj/btsteMG3gyG/KJSb9WXIj5kAJTf2iWJ1c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SDogj/btsteMG3gyG/KJSb9WXIj5kAJTf2iWJ1c1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SDogj/btsteMG3gyG/KJSb9WXIj5kAJTf2iWJ1c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSDogj%2FbtsteMG3gyG%2FKJSb9WXIj5kAJTf2iWJ1c1%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;708&quot; height=&quot;332&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;332&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;limit은 인자로 Long 타입을 받는데, pageable.getPageSize() 메서드는 int 값을 반환하고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저기서 바로 .toLong() 메서드를 호출해도 되지만, 다른 querydsl 쿼리를 작성할 때도 동일한 문제가 반복될 거기에 Kotlin의 확장 함수 기능을 사용해 보았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCM69R/btstg3VHjll/MY4dwfzR6ntyC9knOA0Vck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCM69R/btstg3VHjll/MY4dwfzR6ntyC9knOA0Vck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCM69R/btstg3VHjll/MY4dwfzR6ntyC9knOA0Vck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCM69R%2Fbtstg3VHjll%2FMY4dwfzR6ntyC9knOA0Vck%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;482&quot; height=&quot;176&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 JPAQuery 수신 객체가 limit 함수에 대해 내부적으로 toLong()을 호출하도록 하여, toLong() 메서드를 반복적으로 사용하지 않도록 작성해 보았습니다.&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;&lt;b&gt;2) LongSupplier&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJtvC4/btstayWXcj9/1wHEmSIP4k75aOAfFvkw61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJtvC4/btstayWXcj9/1wHEmSIP4k75aOAfFvkw61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJtvC4/btstayWXcj9/1wHEmSIP4k75aOAfFvkw61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJtvC4%2FbtstayWXcj9%2F1wHEmSIP4k75aOAfFvkw61%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;969&quot; height=&quot;722&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 Type missmatch 문제가 또 있었는데, PageableExecutionUtils.getPage() 메서드의 마지막 인자가 다음과 같이 LongSupplier로 되어 있습니다. 문제는 fetchOne() 메서드의 결과가 null을 허용하기 때문에 타입 불일치가 발생하고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LongSupplier는 아래와 같이 함수형 인터페이스로 되어있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;744&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZ12dP/btstqsHtovL/XuVMp53jTt1oVSfaiXY4Z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZ12dP/btstqsHtovL/XuVMp53jTt1oVSfaiXY4Z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZ12dP/btstqsHtovL/XuVMp53jTt1oVSfaiXY4Z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZ12dP%2FbtstqsHtovL%2FXuVMp53jTt1oVSfaiXY4Z0%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;1572&quot; height=&quot;744&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;744&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;962&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1BoLH/btstlKvl4N1/TMBQaCCkE5BuY7G6jdgLN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1BoLH/btstlKvl4N1/TMBQaCCkE5BuY7G6jdgLN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1BoLH/btstlKvl4N1/TMBQaCCkE5BuY7G6jdgLN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1BoLH%2FbtstlKvl4N1%2FTMBQaCCkE5BuY7G6jdgLN0%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;1792&quot; height=&quot;962&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;962&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 getPage() 메서드를 확인해 보면, total count를 계산해야 하는 경우에만 LongSupplier를 호출하여 값을 계산하고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnhDln/btstr4F6jF0/dDfPCH8oxYvk25IqZwF1v0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnhDln/btstr4F6jF0/dDfPCH8oxYvk25IqZwF1v0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnhDln/btstr4F6jF0/dDfPCH8oxYvk25IqZwF1v0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnhDln%2Fbtstr4F6jF0%2FdDfPCH8oxYvk25IqZwF1v0%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;1314&quot; height=&quot;674&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 AbstarctJPAQuery 추상 클래스의 fetchOne() 메서드를 확인해 보면 null을 반환할 수 있게 되어있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qzuyc/btsteRBx6cu/ivEmKBZL55L8g0uuQanzT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qzuyc/btsteRBx6cu/ivEmKBZL55L8g0uuQanzT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qzuyc/btsteRBx6cu/ivEmKBZL55L8g0uuQanzT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQzuyc%2FbtsteRBx6cu%2FivEmKBZL55L8g0uuQanzT0%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;750&quot; height=&quot;451&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;451&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 엘비스 연산자 `?:` 를 사용하여 fetchOne()이 null을 반환할 경우에는 무조건 0L을 반환하게 하여 null 가능성을 제거하고 LongSupplier의 조건을 충족시켜 주었습니다. 그리고 Kotlin의 문법 중 하나인, 메서드의 마지막 인자가 함수를 받게 되면 람다식을 분리할 수 있는 문법을 적용해 보았습니다.&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;b&gt;4. 확인해 보기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;in-going 어댑터인 웹 컨트롤러에서 요청이 들어오면서부터, out-going 어댑터인 영속성 계층까지 요청이 전달되는 과정은 해당 포스트에서 굳이 다루진 않겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 부트를 재실행하고 설정한 엔드포인트를 호출하면 아래와 같이 결과가 반환됨을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;1852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r1JgE/btstg3OVqe2/cewzT4SbdRWVjlbWiTbIck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r1JgE/btstg3OVqe2/cewzT4SbdRWVjlbWiTbIck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r1JgE/btstg3OVqe2/cewzT4SbdRWVjlbWiTbIck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr1JgE%2Fbtstg3OVqe2%2FcewzT4SbdRWVjlbWiTbIck%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;1438&quot; height=&quot;1852&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;1852&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1041&quot; data-origin-height=&quot;1320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RKK2R/btstkqQBD91/aST9ddtChkbTvnGEVvkxR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RKK2R/btstkqQBD91/aST9ddtChkbTvnGEVvkxR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RKK2R/btstkqQBD91/aST9ddtChkbTvnGEVvkxR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRKK2R%2FbtstkqQBD91%2FaST9ddtChkbTvnGEVvkxR0%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;1041&quot; height=&quot;1320&quot; data-origin-width=&quot;1041&quot; data-origin-height=&quot;1320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 쿼리도 잘 수행되고 있습니다. count 쿼리를 따로 작성하였기 때문에 쿼리가 따로 호출됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCNraa/btstlMMpRQe/h6yviD7bere1Ec7JQZSe6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCNraa/btstlMMpRQe/h6yviD7bere1Ec7JQZSe6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCNraa/btstlMMpRQe/h6yviD7bere1Ec7JQZSe6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCNraa%2FbtstlMMpRQe%2Fh6yviD7bere1Ec7JQZSe6K%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;1262&quot; height=&quot;484&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;484&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 테스트 데이터를 30개를 넣었는데 size를 40개 요청하면 위에서 살펴봤던 LongSupplier의 getAsLong()이 호출되지 않을 것입니다. 따라서 count 쿼리도 나가지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxXgkF/btstkqC4aCO/QdZoltCbZuskYEY5wF8nG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxXgkF/btstkqC4aCO/QdZoltCbZuskYEY5wF8nG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxXgkF/btstkqC4aCO/QdZoltCbZuskYEY5wF8nG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxXgkF%2FbtstkqC4aCO%2FQdZoltCbZuskYEY5wF8nG1%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;3024&quot; height=&quot;1290&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1290&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;일단 querydsl이 정상 작동하는지 확인하기 위해서 다른 최적화들을 진행하지 않았는데, 다음에는 적용할 수 있는 최적화들에 대해서 다뤄보겠습니다.&lt;/p&gt;</description>
      <category>JPA/Querydsl</category>
      <category>JPAQueryFactory</category>
      <category>Kotlin</category>
      <category>Page</category>
      <category>Pageable</category>
      <category>querydsl</category>
      <author>Kyle H</author>
      <guid isPermaLink="true">https://tech.tistory.com/66</guid>
      <comments>https://tech.tistory.com/66#entry66comment</comments>
      <pubDate>Thu, 7 Sep 2023 23:33:02 +0900</pubDate>
    </item>
    <item>
      <title>[Unix] mac 특정 포트를 사용중인 프로세스 종료시키기: (lsof, kill 명령어) Web server failed to start. Port 8080 was already in use. 해결</title>
      <link>https://tech.tistory.com/65</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. PID (프로세스 ID) 찾기&lt;br /&gt;2. 프로세스 종료시키기&lt;br /&gt;&amp;nbsp; &amp;nbsp; 2-1) `9` KILL 시그널 주의사항&lt;br /&gt;&amp;nbsp; &amp;nbsp; 2-2) default 시그널 `15` TERM&lt;/blockquote&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2402&quot; data-origin-height=&quot;934&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfBaQT/btstk4zKTyz/ZKvjQH07gyXg9svUDl7Gc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfBaQT/btstk4zKTyz/ZKvjQH07gyXg9svUDl7Gc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfBaQT/btstk4zKTyz/ZKvjQH07gyXg9svUDl7Gc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfBaQT%2Fbtstk4zKTyz%2FZKvjQH07gyXg9svUDl7Gc0%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;2402&quot; height=&quot;934&quot; data-origin-width=&quot;2402&quot; data-origin-height=&quot;934&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 경우에는 IntelliJ IDEA가 오류로 혼자 종료되었는데, tomcat 프로세스는 죽지 않고 살아있어서 발생하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지 unix 명령어를 기록할 겸 간단한 포스팅 남겨봅니다.&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. PID (프로세스 ID) 찾기&lt;/h2&gt;
&lt;pre id=&quot;code_1694012042692&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;lsof -i :포트번호&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[lsof] : &quot;List Open Files&quot;의 약자이며, 파일이나 네트워크 연결 등 시스템에서 열려 있는 모든 '파일'에 대한 정보를 제공합니다.&lt;/li&gt;
&lt;li&gt;[-i] : 네트워크 인터페이스 옵션입니다. 이 옵션을 사용하면 네트워크 연결에 관련된 파일만 필터링하여 볼 수 있습니다.&lt;/li&gt;
&lt;li&gt;[:포트번호] : 이 부분에 특정 포트 번호를 지정하여 그 포트를 사용하는 프로세스 정보만을 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n3uhV/btstktfucG5/JROYukymeWwLjyRW5GG0k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n3uhV/btstktfucG5/JROYukymeWwLjyRW5GG0k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n3uhV/btstktfucG5/JROYukymeWwLjyRW5GG0k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn3uhV%2FbtstktfucG5%2FJROYukymeWwLjyRW5GG0k1%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;1764&quot; height=&quot;184&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 명령어를 통해 8080을 사용중인 프로세스 ID를 확인합니다.PID를 확인했으면 프로세스를 종료해야합니다.&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;2. 프로세스 종료시키기&lt;/h2&gt;
&lt;pre id=&quot;code_1694012911701&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kill 프로세스ID&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kill 명령어는 UNIX 또는 Linux 시스템에서 프로세스를 종료하기 위해 사용됩니다. 이 명령어는 다양한 종류의 시그널을 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/egbTcf/btstfRVuJvr/qdW0R6wFct2BBr7IeQdkkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/egbTcf/btstfRVuJvr/qdW0R6wFct2BBr7IeQdkkK/img.png&quot; data-alt=&quot;man kill&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/egbTcf/btstfRVuJvr/qdW0R6wFct2BBr7IeQdkkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FegbTcf%2FbtstfRVuJvr%2FqdW0R6wFct2BBr7IeQdkkK%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;1720&quot; height=&quot;654&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;man kill&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kill 명령어에 대한 메뉴얼을 보면 사용할 수 있는 종류의 시그널이 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 `-9` 옵션을 사용하는 것을 자주 보셨을 겁니다.&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-1) `9` KILL 시그널 주의사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시그널은 프로세스에게 선택의 여지 없이 즉시 종료하도록 지시합니다. 프로세스는 SIGKILL을 무시하거나 잡아낼 수 없으며, 이로 인해 어떠한 정리 작업도 수행되지 않고 프로세스는 즉시 종료됩니다.&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-2) default 시그널 `15` TERM&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메뉴얼을 보면 시그널의 default 옵션이 `15` TERM 인것을 확인할 수 있습니다. 해당 시그널은 프로세스에게 종료하라는 요청을 보내게 됩니다. 프로세스는 이 시그널을 받아들여 정상적으로 종료 처리를 할 수 있습니다. 예를 들어, 파일을 저장하거나 네트워크 연결을 닫는 등의 정리 작업을 수행할 수 있습니다.&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;가능하면 먼저 TERM을 사용하여 프로세스에게 종료를 요청하고, 그 후에도 종료되지 않을 경우 KILL을 사용하는 것이 좋습니다.&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;kill 명령어로 위에서 확인한 PID를 전달하고, 다시 8080 포트를 사용중인지 확인해 봅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8EdDy/btstePDMo3G/AIIiYXckKdAVmvlnPKMR31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8EdDy/btstePDMo3G/AIIiYXckKdAVmvlnPKMR31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8EdDy/btstePDMo3G/AIIiYXckKdAVmvlnPKMR31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8EdDy%2FbtstePDMo3G%2FAIIiYXckKdAVmvlnPKMR31%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;378&quot; height=&quot;120&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 다시 스프링 부트를 실행하여 tomcat이 기동되는지 확인합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4504&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u49G5/btstayo4mEc/C20zCbSoNrzUHAckax5q6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u49G5/btstayo4mEc/C20zCbSoNrzUHAckax5q6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u49G5/btstayo4mEc/C20zCbSoNrzUHAckax5q6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu49G5%2Fbtstayo4mEc%2FC20zCbSoNrzUHAckax5q6K%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;4504&quot; height=&quot;252&quot; data-origin-width=&quot;4504&quot; data-origin-height=&quot;252&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;개발을 하다보면 default 옵션인 것들은 그럴만한 이유가 있다고 많이 느낍니다.kill 명령어 역시 이 중에 하나가 아닌가 싶습니다.&lt;/p&gt;</description>
      <category>오류 해결/환경</category>
      <author>Kyle H</author>
      <guid isPermaLink="true">https://tech.tistory.com/65</guid>
      <comments>https://tech.tistory.com/65#entry65comment</comments>
      <pubDate>Thu, 7 Sep 2023 00:22:21 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] 코프링 프로젝트 예제 build.gradle.kts 기록</title>
      <link>https://tech.tistory.com/64</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kotlinlang.org/docs/jvm-create-project-with-spring-boot.html#explore-the-generated-spring-boot-application&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kotlinlang.org/docs/jvm-create-project-with-spring-boot.html#explore-the-generated-spring-boot-application&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1693226914227&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;Create a Spring Boot project with Kotlin | Kotlin&quot; data-og-description=&quot; &quot; data-og-host=&quot;kotlinlang.org&quot; data-og-source-url=&quot;https://kotlinlang.org/docs/jvm-create-project-with-spring-boot.html#explore-the-generated-spring-boot-application&quot; data-og-url=&quot;https://kotlinlang.org/docs/jvm-create-project-with-spring-boot.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bJ3hFp/hyTIOex7GB/QBmQWfWOGbpbBdhVxvgU2k/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/k0cGD/hyTMeWXSGZ/x6avunOKl6vhePXOMrQrGk/img.png?width=2068&amp;amp;height=1758&amp;amp;face=0_0_2068_1758,https://scrap.kakaocdn.net/dn/btBzqy/hyTIMA3iXc/gBKMGzArGUpAnhhNYKFIpK/img.png?width=1510&amp;amp;height=1270&amp;amp;face=0_0_1510_1270&quot;&gt;&lt;a href=&quot;https://kotlinlang.org/docs/jvm-create-project-with-spring-boot.html#explore-the-generated-spring-boot-application&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kotlinlang.org/docs/jvm-create-project-with-spring-boot.html#explore-the-generated-spring-boot-application&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bJ3hFp/hyTIOex7GB/QBmQWfWOGbpbBdhVxvgU2k/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/k0cGD/hyTMeWXSGZ/x6avunOKl6vhePXOMrQrGk/img.png?width=2068&amp;amp;height=1758&amp;amp;face=0_0_2068_1758,https://scrap.kakaocdn.net/dn/btBzqy/hyTIMA3iXc/gBKMGzArGUpAnhhNYKFIpK/img.png?width=1510&amp;amp;height=1270&amp;amp;face=0_0_1510_1270');&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;Create a Spring Boot project with Kotlin | Kotlin&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kotlinlang.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린 공식 문서에 나와있는 코프링 프로젝트의 예제를 작성하다가, gradle 빌드 스크립트를 groovy 에서 kotlin으로 처음 사용해 보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 Java와 gradle-groovy를 사용할 때와 몇가지 추가된 설정들이 있어 주석과 함께 기록을 남겨봅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1693226823342&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.jetbrains.kotlin.gradle.tasks.KotlinCompile // For `KotlinCompile` task below

plugins {
    id(&quot;org.springframework.boot&quot;) version &quot;3.1.3&quot;
    id(&quot;io.spring.dependency-management&quot;) version &quot;1.1.3&quot;
    kotlin(&quot;jvm&quot;) version &quot;1.9.10&quot; // The version of Kotlin to use
    
    // Spring Framework 기능과 호환되도록 Kotlin 클래스에 open 수정자를 추가하는 Kotlin Spring 컴파일러 플러그인
    kotlin(&quot;plugin.spring&quot;) version &quot;1.9.10&quot; 
}

group = &quot;com.example&quot;
version = &quot;0.0.1-SNAPSHOT&quot;

java {
    sourceCompatibility = JavaVersion.VERSION_17
}

repositories {
    mavenCentral()
}

dependencies {
    implementation(&quot;org.springframework.boot:spring-boot-starter-data-jdbc&quot;)
    implementation(&quot;org.springframework.boot:spring-boot-starter-web&quot;)

    // 이 모듈은 Kotlin 클래스 및 데이터 클래스의 직렬화 및 역직렬화에 대한 지원을 추가합니다.
    implementation(&quot;com.fasterxml.jackson.module:jackson-module-kotlin&quot;) // Jackson extensions for Kotlin for working with JSON
    // 코틀린 리플렉션 라이브러리
    implementation(&quot;org.jetbrains.kotlin:kotlin-reflect&quot;) // Kotlin reflection library, required for working with Spring

    runtimeOnly(&quot;com.h2database:h2&quot;)
    testImplementation(&quot;org.springframework.boot:spring-boot-starter-test&quot;)
}

// 여기에서 컴파일러에 추가 인수를 추가하여 다양한 언어 기능을 활성화하거나 비활성화할 수 있습니다.
tasks.withType&amp;lt;KotlinCompile&amp;gt; { // Settings for `KotlinCompile` tasks
    kotlinOptions { // Kotlin compiler options
        freeCompilerArgs += &quot;-Xjsr305=strict&quot; // `-Xjsr305=strict` enables the strict mode for JSR-305 annotations
        jvmTarget = &quot;17&quot; // This option specifies the target version of the generated JVM bytecode
    }
}

tasks.withType&amp;lt;Test&amp;gt; {
    useJUnitPlatform()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2496&quot; data-origin-height=&quot;1938&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkvaWN/btssgsC8ucS/oKnkvJ5hTC21ndBKjdNPqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkvaWN/btssgsC8ucS/oKnkvJ5hTC21ndBKjdNPqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkvaWN/btssgsC8ucS/oKnkvJ5hTC21ndBKjdNPqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkvaWN%2FbtssgsC8ucS%2FoKnkvJ5hTC21ndBKjdNPqk%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;2496&quot; height=&quot;1938&quot; data-origin-width=&quot;2496&quot; data-origin-height=&quot;1938&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;</description>
      <category>Kotlin/환경 설정</category>
      <category>build.gradle.kts</category>
      <category>gradle</category>
      <category>Kotlin</category>
      <author>Kyle H</author>
      <guid isPermaLink="true">https://tech.tistory.com/64</guid>
      <comments>https://tech.tistory.com/64#entry64comment</comments>
      <pubDate>Mon, 28 Aug 2023 21:54:02 +0900</pubDate>
    </item>
    <item>
      <title>[컨퍼런스] 점핏 개취콘 백엔드편 오프라인 후기</title>
      <link>https://tech.tistory.com/63</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;[컨퍼런스] 점핏 개취콘 백엔드편 오프라인 후기&lt;br /&gt;1. 입장&lt;br /&gt;2. 세션&lt;br /&gt;3. 후기&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;0. 오프라인 참여자 선정&amp;nbsp;&lt;/b&gt;&lt;/h2&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;928&quot; data-origin-height=&quot;1732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sIJTW/btssk1wVI8k/obo1bWfRjmDCZEKsbV5LZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sIJTW/btssk1wVI8k/obo1bWfRjmDCZEKsbV5LZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sIJTW/btssk1wVI8k/obo1bWfRjmDCZEKsbV5LZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsIJTW%2Fbtssk1wVI8k%2Fobo1bWfRjmDCZEKsbV5LZK%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;312&quot; height=&quot;1732&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;1732&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경쟁률이 10대 1 정도 되었다고 하는데, 운 좋게 당첨되었네요. 인프콘에 못 가서 많이 아쉬운 상태였는데, 개취콘 당첨 문자를 받고 기분이 많이 좋아졌습니다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 입장&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 계획은 입장 시작 시간인 12시 30분에 맞춰서 가는 것이었습니다. 이전 프론트엔드편 후기들을 보니, 처음에 가야 도서들도 다양하고 부스 체험도 할 수 있다고 해서 좀 일찍 가려고 했는데 준비하다 보니 좀 늦어졌습니다 ㅜㅜ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강남역에서 1시 좀 넘어서 내리고 조금 걷다보니 모나코스페이스가 나왔습니다. 이쪽이 강남역치곤 좀 조용한 구역이었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4FZDJ/btssgmvj4On/ZnTkcVBCuKh459bNAsRViK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4FZDJ/btssgmvj4On/ZnTkcVBCuKh459bNAsRViK/img.gif&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-filename=&quot;tempImageMGrjxY.gif&quot; width=&quot;500&quot; style=&quot;width: 49.418605%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4FZDJ/btssgmvj4On/ZnTkcVBCuKh459bNAsRViK/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4FZDJ%2Fbtssgmvj4On%2FZnTkcVBCuKh459bNAsRViK%2Fimg.gif&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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqhLvk/btsshpkNvY2/WXsKdUSHLF8FPtgXfvotbk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqhLvk/btsshpkNvY2/WXsKdUSHLF8FPtgXfvotbk/img.gif&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-filename=&quot;tempImagebyFvkL.gif&quot; style=&quot;width: 49.418605%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqhLvk/btsshpkNvY2/WXsKdUSHLF8FPtgXfvotbk/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqhLvk%2FbtsshpkNvY2%2FWXsKdUSHLF8FPtgXfvotbk%2Fimg.gif&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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안내판이 잘 되어있고 모나코 스페이스 이름이 잘 보여서 헷갈리지 않게 찾을 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAqby2/btssgDDB3jO/ZMWfeTSZX2RPEEjnALkJk1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAqby2/btssgDDB3jO/ZMWfeTSZX2RPEEjnALkJk1/img.gif&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-filename=&quot;tempImagekXo3Z2.gif&quot; style=&quot;width: 49.418605%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAqby2/btssgDDB3jO/ZMWfeTSZX2RPEEjnALkJk1/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAqby2%2FbtssgDDB3jO%2FZMWfeTSZX2RPEEjnALkJk1%2Fimg.gif&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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5kJfO/btssbhV97GU/PcWTdHo1FNak3bLEpjRgGk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5kJfO/btssbhV97GU/PcWTdHo1FNak3bLEpjRgGk/img.gif&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-filename=&quot;tempImageW9GzOp.gif&quot; style=&quot;width: 49.418605%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5kJfO/btssbhV97GU/PcWTdHo1FNak3bLEpjRgGk/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5kJfO%2FbtssbhV97GU%2FPcWTdHo1FNak3bLEpjRgGk%2Fimg.gif&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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&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;&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;채용관련된 정보나 궁금한점을 작성하는 폼을 제출하고 나니, 담당자분께서 cto님께 지금 궁금한 것 물어보시면 답변해주신다고 알려주셨습니다. 처음에는 용기가 안 나서 뒤에 있는 부스에 갔다가 다시 용기 내서 찾아가 궁금한 점을 물어보았습니다.&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;처음에 용기가 안났던 것이 무색할 정도로 친절하게 질문에 대답해 주셔서 정말 감사했습니다. 이런 기회가 아니면 언제 토스뱅크 CTO님과 이런 대화를 나눌까라는 생각이 들어, 오프라인 행사에 오길 정말 잘했다는 생각이 들었네요 ㅎㅎ&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;오기 전부터 기대했던 책 나눔 코너는 이미 많이 가져가셔서 그런 건지 마땅히 마음에 드는 책이 없었습니다 ㅎㅎㅎ 사이드 프로젝트할 때 React를 사용해보고 싶었는데 마침 클론코딩 책 한 권이 남아서 얼른 집어왔습니다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 세션&amp;nbsp;&lt;/b&gt;&lt;/h2&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;&lt;b&gt;Session 1 중요한 건 인터페이스야&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소통에서 빈틈이 생길 수밖에 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;오너십을 갖고 빈틈에 대해 확인하고 일하는 사람&lt;/li&gt;
&lt;li&gt;내 일처럼 오너십을 갖고 일하는 사람&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;취업/이직
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이번뿐이야라고 생각되는 기회가 항상 이번뿐이 아니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Session 2 토스뱅크 기업문화와 성장하는 주니어 BE특징&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 내게 맞는 성장 환경 찾기&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이런 문화를 만들기 위해 노력해야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다 터놓고 이야기해야 한다. (QA조직에게, 업무 연관자에게)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;color: #ee2323;&quot;&gt;&lt;b&gt;전체가 아니더라도 무언가 바꾸어야한다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;color: #ee2323;&quot;&gt;&lt;b&gt;끝까지 파봐야 한다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&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;&amp;rArr; 좋은 경험&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;문제를 발견하고 어떤 노력을 해봤는지가 정말 중요하다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;높은 트래픽을 받을 수 있게 대비는 되어 있어야 한다.&lt;/li&gt;
&lt;li&gt;논리적으로 설득하고 주장하라.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;오늘 강의 중에 가장 느낀점이 많았습니다. 다른 것보다도 문화가 갖추어진 조직에서 일하고 싶다는 열망이 가장 강하게 들었습니다. 현재 조직에서의 변화는 .. 솔직히 불가능하지 않을까라는 생각이 크게 들었습니다.&amp;nbsp;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Session 3 알아두면 쓸 데 있는 코틀린&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;언어에 대한 성숙도&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;GPT를 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;최근 코틀린에 대해 공부하고 있는터라 가장 기대가 됐던 세션이었습니다. 세션을 듣기 위해 너무 준비했던 탓인지 ㅎㅎ 이미 당근마켓 밋업 영상과 코틀린 인 액션에서 초반부에서 많이 다루는 얘기만 짧게 진행되어 많이 아쉬웠습니다. Kotlin 공부 이후에 스프링에 녹이는 부분에 대해 질문을 하고 싶었는데, 현장 질문이 2개로 한정되어 말씀드리지 못했습니다 ㅜㅜ&amp;nbsp;&lt;br /&gt;그래도 하나 느낀 점이 현재 내가 너무 양반식으로 코틀린을 공부하고 있다는 것을 깨닫게 되었습니다. 아래 후기에 자세히 다뤄보겠습니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Session 4 쿠버네티스는 왜! BE 주니어에게 특별히 좋을까요&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;쿠버네티스란
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도커와의 포함관계 X&lt;/li&gt;
&lt;li&gt;컨테이너를 조율하는 오케스트레이션&lt;/li&gt;
&lt;li&gt;클라우드, 가상화, 리눅스에 대한 추상화된 접근 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;필요한 부분에 대해 강의나 책을 보고 파고들기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;기술서적
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기술서적 2가지
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;10년이 지나도 적용되는 철학성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두고두고 천천히&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;백엔드 개발자 CKAD &amp;rarr; 배포 관련된 내용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;언젠가는 꼭 공부해야지! 하고 못하고 있는 쿠버네티스 세션이었습니다. 쿠버네티스를 왜 공부해야 하는지 뼈저리게 알게 되었지만... 당장은 언어와 프레임워크, CS에 대한 부분을 더 공부하는 게 우선이지 않을까라는 고민이 늘게 되었습니다. 도커&amp;amp;쿠버네티스 관련해서 사놓은 책을 올해 안에는 꼭 읽을 수 있었으면 좋겠다는 생각이 들었습니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 후기&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;tempImage6JmpP9.gif&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DdYv4/btssfG2bUF0/RWDlNKCg15gw0yhfDdG5g1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DdYv4/btssfG2bUF0/RWDlNKCg15gw0yhfDdG5g1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DdYv4/btssfG2bUF0/RWDlNKCg15gw0yhfDdG5g1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/DdYv4/btssfG2bUF0/RWDlNKCg15gw0yhfDdG5g1/img.gif&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;514&quot; height=&quot;685&quot; data-filename=&quot;tempImage6JmpP9.gif&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 오프라인 컨퍼런스에 참가하는 것이 처음이었는데 너무 좋은 경험이었습니다. CTO님과의 대화, 여러 굿즈, 그리고 오프라인이라 더욱 세션에 집중하면서 들을 수 있었습니다.&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;&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;tempImageEoLzH7.gif&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boL8RA/btsscCFJ4Bw/3C2msJF6nRcYB8gfKOzr21/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boL8RA/btsscCFJ4Bw/3C2msJF6nRcYB8gfKOzr21/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boL8RA/btsscCFJ4Bw/3C2msJF6nRcYB8gfKOzr21/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/boL8RA/btsscCFJ4Bw/3C2msJF6nRcYB8gfKOzr21/img.gif&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;467&quot; height=&quot;623&quot; data-filename=&quot;tempImageEoLzH7.gif&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 코틀린을 공부하자!라고 생각한 이후에 코틀린 인 액션 책을 처음부터 차근히 읽어 가고 있었는데, 공부 방식을 바꿔보아야겠다는 생각이 들었습니다. 김영한님 강의를 들으면서도 생각했지만 저의 공부 스타일은 양반식 공부에 가까웠습니다. (컴공 2학년 과탑을 차지한 의문의 토목공학도) 이 공부 방식이 사실 저에겐 좀 더 편하게 다가왔는데, 이제 의식적으로라도 상놈식으로 공부를 해야 될 것 같습니다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://spring.io/guides/tutorials/spring-boot-kotlin/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://spring.io/guides/tutorials/spring-boot-kotlin/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1693140620954&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;Getting Started | Building web applications with Spring Boot and Kotlin&quot; data-og-description=&quot;Instead of using util classes with abstract methods like in Java, it is usual in Kotlin to provide such functionalities via Kotlin extensions. Here we are going to add a format() function to the existing LocalDateTime type in order to generate text with th&quot; data-og-host=&quot;spring.io&quot; data-og-source-url=&quot;https://spring.io/guides/tutorials/spring-boot-kotlin/&quot; data-og-url=&quot;https://spring.io/guides/tutorials/spring-boot-kotlin/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dUYrG9/hyTIIZtQsx/YmI3pqlBBBwckGrPxAIYck/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/IhNj1/hyTMbeDPbY/pcAUYKLsk7wDejCfAoUbL1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://spring.io/guides/tutorials/spring-boot-kotlin/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://spring.io/guides/tutorials/spring-boot-kotlin/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dUYrG9/hyTIIZtQsx/YmI3pqlBBBwckGrPxAIYck/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/IhNj1/hyTMbeDPbY/pcAUYKLsk7wDejCfAoUbL1/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;Getting Started | Building web applications with Spring Boot and Kotlin&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Instead of using util classes with abstract methods like in Java, it is usual in Kotlin to provide such functionalities via Kotlin extensions. Here we are going to add a format() function to the existing LocalDateTime type in order to generate text with th&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;spring.io&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;그리고 아래 사이트 예제를 확인하고 간단한 CRUD 기능부터 만들어 볼 계획입니다. 코틀린 인 액션 책은 회사 점심시간에 찬찬히 읽어 봐야겠네요 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kotlin-example.hatemogi.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kotlin-example.hatemogi.com&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1693140859996&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;예제로 격파하는 코틀린&quot; data-og-description=&quot;JetBrains의 Kotlin By Example 비공식 한국어 편역&quot; data-og-host=&quot;kotlin-example.hatemogi.com&quot; data-og-source-url=&quot;https://kotlin-example.hatemogi.com&quot; data-og-url=&quot;https://kotlin-example.hatemogi.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eFb4o5/hyTMbsatwo/wg5LPNqkfAbIBYwPBjkl7K/img.png?width=2160&amp;amp;height=2160&amp;amp;face=0_0_2160_2160&quot;&gt;&lt;a href=&quot;https://kotlin-example.hatemogi.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kotlin-example.hatemogi.com&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eFb4o5/hyTMbsatwo/wg5LPNqkfAbIBYwPBjkl7K/img.png?width=2160&amp;amp;height=2160&amp;amp;face=0_0_2160_2160');&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;예제로 격파하는 코틀린&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;JetBrains의 Kotlin By Example 비공식 한국어 편역&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kotlin-example.hatemogi.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;/p&gt;</description>
      <category>후기/컨퍼런스</category>
      <category>개취콘</category>
      <category>백엔드</category>
      <category>컨퍼런스</category>
      <category>코틀린</category>
      <author>Kyle H</author>
      <guid isPermaLink="true">https://tech.tistory.com/63</guid>
      <comments>https://tech.tistory.com/63#entry63comment</comments>
      <pubDate>Sun, 27 Aug 2023 21:58:28 +0900</pubDate>
    </item>
    <item>
      <title>[클린 아키텍처] 아키텍처 청소부 스터디 후기</title>
      <link>https://tech.tistory.com/62</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;0. 클린 아키텍처 스터디를 시작하게 된 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 기회로 내놓으라 하는 국내 서비스 대표기업에서 주최하는 스터디에 참여할 기회를 얻게 되었습니다.&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;&lt;b&gt;여러 가지 주제&lt;/b&gt;가 있었고, 그중에 선택해서 지원하는 방식이었는데 클린 아키텍처 스터디와, 대규모 시스템 설계 스터디 둘 중에 고민을 하게 되었습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;지원 당시에 한참 객체지향에 대해 공부하고 있었습니다. JPA를 공부하고 프로젝트를 시작해보려고 하는데, &lt;b&gt;어느 메소드를 어느 클래스&lt;/b&gt;에 만들어야 하는지가 스스로 명확히 할 수 없었기 때문이었습니다.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;어느 정도 객체지향을 공부하고 보니 &quot;그래, 이 객체에 이 역할을 부여해야지&quot;라는 생각이 들기 시작했는데, 이제 문제는 &lt;b&gt;&quot;얘를 어디에 배치하지? &quot;였습니다.&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;우리가 흔히 접하는 계층형 아키텍처(레이어드 아키텍처)에서도 &lt;b&gt;객체를 어떻게 분류하고 조직화(패키지) 할지는 너무나 각양각색&lt;/b&gt;이었고, &lt;b&gt;DTO를 만들어야 할 때마다 &quot;얘를 어느 계층까지 끌고 가야 되는 거지?&quot;라는&lt;/b&gt; 생각이 들게 되었습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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;&lt;b&gt;1. 기존 개발의 문제점 인지&lt;/b&gt;&lt;/h3&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;b&gt;객체의 분류 및 조직화&lt;/b&gt;&lt;span style=&quot;color: #374151; text-align: left;&quot;&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;저의 경우에는 위에서 얘기한 것처럼, 계층을 나누긴 나눴는데 세부 클래스들을 다시 어떤 패키지로 묶을지가 애매하다고 느꼈습니다. Java에서 &quot;default&quot; 접근 제어자를 같은 패키지 (하위 패키지도 안됨)까지 한정한 이유에 대해 생각하면 더 머리가 아파집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터베이스 주도 설계 유도&lt;/b&gt;: 계층형 아키텍처를 사용하다 보면 자연스레 데이터베이스 주도 설계를 하게 됩니다. 웹 -&amp;gt; 도메인 -&amp;gt; 영속성 레이어로 이어지는 흐름 속에서, 일반적으로 애플리케이션의 행동보다는 상태를 주체로 설계하고 개발하게 됩니다. 테이블 레이아웃을 설계하고, 영속성 레이어를 구현한 뒤 도메인, 웹 레이어 구현으로 이어지게 됩니다. 이렇게 하면 정작 비즈니스를 이끌어가는 행동이 뒷전이 되어버립니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;계층 건너뛰기&lt;/b&gt;: 웹 계층에서 도메인 계층을 건너뛰고, 바로 영속성 계층을 접근할 수 있게 됩니다. 이러한 쉬운 길을 택하다 보면 어느새 계층 간 경계가 모호해지고, 도메인 로직이 여기저기 퍼지게 됩니다. 실제로 회사에서 레거시 코드를 보거나 SI 외주업체에서 만들고 도망간 코드를 보면 온갖 비즈니스 로직이 컨트롤러에 작성한 상황을 마주하게 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 난이도 상승&lt;/b&gt;: 웹 계층에 도메인 로직이 섞여 들어가게 되면 단위 테스트가 복잡해지고, 테스트 시 영속성 계층도 모킹해야 하는 일이 생겨납니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모호한 유스케이스&lt;/b&gt;: 하나의 도메인 계층 서비스에서 다양한 유스케이스를 처리하는 경우, 코드를 수정하거나 추가할 때 위치 찾기가 어려워집니다. 이러한 이유로 여러 사람이 동시에 작업하는 것도 어려워집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 클린 아키텍처에서의 해결 방안&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이어드 아키텍처에서도, 계층에 대해 이해하고 빌드 도구나 테스트 도구 등을 통해 제약을 강화하면 위의 문제들을 해결할 수 있습니다. 여기서는 스터디를 진행하면서 학습하게 된 클린 아키텍처에서의 해결 방안들에 대해 작성해 보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #374151; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;객체의 위치 결정&lt;/b&gt;: 레이어드 아키텍처에서는 주로 Presentation Layer, Business Logic Layer, Data Access Layer로 나뉩니다. 그러나 이렇게 3 계층으로 나뉘어진 아키텍처에서 객체를 어디에 위치시켜야 하는지를 결정하는 것은 쉽지 않습니다. 클린 아키텍처에서는 이 문제를 엔티티, 유스케이스, 인터페이스 어댑터, 프레임워크와 드라이버라는 4가지 계층으로 나누어 해결하였습니다. 각 계층은 명확한 역할과 책임을 가지고 있으며, 이를 이해하면 객체의 위치 결정에 대한 문제를 해결할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;객체의 분류 및 조직화&lt;/b&gt;: 클린 아키텍처에서는 어느 정도 명확한 객체의 위치를 제공합니다. 외부에 의해 호출되는(인고잉) 어댑터와 포트, 그리고 외부로 나가는 호출(아웃고잉) 어댑터와 포트, 그리고 애플리케이션 코어에 해당하는 유스케이스, 엔티티 계층으로 그 역할이 분명하게 나뉘어 있습니다. 이러한 계층 구조로 패키지를 구성하면 개발 직무 이외의 사람과의 소통에서도 어느 지점에 새로운 기능을 추가하고 변경할지 명확히 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;567&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAmsHn/btsrBOsYPX0/3b4UXZA6vENsYxNZd3f9Fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAmsHn/btsrBOsYPX0/3b4UXZA6vENsYxNZd3f9Fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAmsHn/btsrBOsYPX0/3b4UXZA6vENsYxNZd3f9Fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAmsHn%2FbtsrBOsYPX0%2F3b4UXZA6vENsYxNZd3f9Fk%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;772&quot; height=&quot;567&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;567&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #374151; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;DTO의 범위&lt;/b&gt;: 클린 아키텍처에서는 의존성에 대한 규칙이 명확합니다. &quot;의존성은 내부로만 향한다.&quot; 이 원칙을 준수하면서 개발을 하려고 보면, DTO 객체뿐만 아니라 다른 객체에 대해서도 의존성을 명확히 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;도메인 주도 설계&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 클린 아키텍처에서는 애플리케이션 코어라고 부르는, 엔티티와 유스케이스가 말 그대로 애플리케인션의 핵심으로 다루어집니다. 오직 해결하려는 도메인의 문제에만 집중하여 애플리케이션 코어단을 유지하기 때문에, DB와 웹 이런 것들은 세부사항으로 분류합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;계층 간의 명확한 경계 나누기&lt;/b&gt; : 클린 아키텍처에서는 각 계층 간의 경계를 명확하게 나누고 그에 따라 참조할 수 있는 의존성을 관리합니다. Java의 접근 제어자와 패키지 구조, 추가로 멀티 모듈 기능을 통해 코드베이스를 나눈다면 컨트롤러에서 영속성 계층을 직접 참조하는 일을 막을 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 용이성&lt;/b&gt; : 클린 아키텍처에서는 &lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;각 계층은 독립적이며, 따라서 단위 테스트를 쉽게 작성할 수 있습니다. 모킹이 필요한 경우에도, 계층의 경계에서만 이루어져 복잡성을 줄일 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;명확한 유스케이스 :&lt;/b&gt; &lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;클린 아키텍처는 유스케이스를 명확히 정의하고, 각 유스케이스를 독립적으로 관리합니다. 이로 인해 코드 위치를 찾기 어렵거나 여러 사람이 동시에 작업하기 어려운 문제를 해결합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #374151; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 스터디를 통해 배운 것&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 이 스터디를 하면서 무엇을 배웠냐?라고 물어보면 의존성 관리라고 말하고 싶습니다. 책을 읽다 보면 자주 반복되는 키워드와 주제들이 있었습니다. 이러한 키워드와 주제를 관통하는 핵심은 &lt;b&gt;&quot;의존성을 관리하라.&quot;&lt;/b&gt;가 아닌가 싶습니다. 결국 클린 아키텍처에서 자랑하는 장점들은 모두 &lt;b&gt;뚜렷한 경계를 나누고&lt;/b&gt;, &lt;b&gt;의존성을 관리&lt;/b&gt;하였을 때 누릴 수 있는 것들이기 때문이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 하지만 그게 꼭 클린 아키텍처여야 하는가? 에 대해선 좀 의문이 들었습니다. 이미 많은 빅테크 기업들에서도 클린 아키텍처 없이 시스템을 운영하고 있다 보니 &lt;b&gt;&quot;꼭 필요한가?&quot;&lt;/b&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>
      <author>Kyle H</author>
      <guid isPermaLink="true">https://tech.tistory.com/62</guid>
      <comments>https://tech.tistory.com/62#entry62comment</comments>
      <pubDate>Mon, 21 Aug 2023 02:00:41 +0900</pubDate>
    </item>
    <item>
      <title>[멀티 모듈] 왜 멀티 모듈인가? 멀티 모듈 학습하기</title>
      <link>https://tech.tistory.com/61</link>
      <description>&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;저의 경우엔 &quot;만들면서 배우는 클린 아키텍처&quot; 책을 보다가 10장. 아키텍처 경계 강제하기에서 나온 내용을 보고 왜 멀티 모듈이 중요한지 알게 되었습니다.&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;b&gt;1. 왜 중요할까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 예시로 드는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;공통적으로 참조&lt;/b&gt;&lt;/span&gt;하는 도메인 영역에 대한 예시는 많이 나와있으니, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;아키텍처 경계&lt;/b&gt;&lt;/span&gt;에 대한 이야기를 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린 아키텍처가 아니더라도, 아키텍처의 핵심은 의존성에 대한 관리입니다. 의존성을 제대로 관리하지 못하면 코드에 점점 순환참조가 생겨나고, 새로운 기능 추가나 유지보수를 어렵게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 클린 아키텍처에서는 한가지 명확한 룰은 정했습니다. &quot;의존성은 안쪽으로만 흐른다.&quot;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;567&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BG9jm/btsro7fsyW9/ceReMEj61inbBUel3HEQpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BG9jm/btsro7fsyW9/ceReMEj61inbBUel3HEQpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BG9jm/btsro7fsyW9/ceReMEj61inbBUel3HEQpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBG9jm%2Fbtsro7fsyW9%2FceReMEj61inbBUel3HEQpK%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;772&quot; height=&quot;567&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;567&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아키텍처의 안쪽으로 갈수록, 우리가 다루는 핵심 관심사가 되고, 웹, DB, UI 등은 세부사항으로 분류됩니다.&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;&lt;b&gt;2. 문제는?&lt;/b&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;자바에서는 기본적으로 패키지와 접근 제어자로 경계를 생성하는데, 문제가 되는 것은 유스케이스 계층의 포트들입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인고잉, 아웃고잉 포트 모두 자바에선 인터페이스로 정의되는데, 인고잉 웹 어댑터에서는 호출을 위해 접근을 해야하니 public으로 열려 있어야 하고, 아웃고잉 영속성 어댑터에서는 유스케이스 인터페이스를 구현해야 하니 또 public으로 열려있어야 합니다.&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: #ee2323;&quot;&gt;&lt;b&gt;인터페이스가 public으로 열려있으면 내부 계층인 도메인 계층에서도 유스케이스를 참조&lt;/b&gt;&lt;/span&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;&lt;b&gt;3. 해결방안은?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러는 아키텍처 룰은 깼다고 해서 경고를 주지 않습니다.&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;먼저 ArchUnit을 사용하는 방법이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArchUnit과 JUnit을 통해 의존성의 방향이 기대한 대로 잘 설정돼있나 체크할 수 있는 하나의 방법입니다.&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;&lt;b&gt;4. 왜 멀티모듈인가?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 방법이 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;멀티 모듈을 통해 빌드 아티팩트를 분리&lt;/b&gt;&lt;/span&gt;하는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예에서 도메인 계층을 따로 jar 파일로 만들고, 유스케이스 계층 따로 jar 파일로 만들어지게 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일과 런타임 시에 어떤 의존성을 가져갈지 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;명시적으로 선언&lt;/b&gt;&lt;/span&gt;해줄 수 있기 때문에 더욱 더 명확하게 경계를 만들어낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 계층에서 유스케이스 계층을 참조한다는 선언이 없으면, 아무리 포트 인터페이슥 public으로 열려있다고 하더라도 엔티티 계층에서는 이를 참조 할 수 없습니다.&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;b&gt;5. 클린아키텍처와 멀티 모듈&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 위와 같은 고민들은 사이드 프로젝트를 클린 아키텍처로 구현해보자! 라는 목표를 시작으로 하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린 아키텍처에 gradle 멀티 모듈까지 얹어서 다루다 보니 딱 마음에 드는 참고자료가 많이 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 돌고돌아 위에서 언급한 책의 저자인 Tom Hombergs가 남긴 블로그글에서 클린 아키텍처와 이 저자가 얘기하는 조립하는 컴포넌트의 개념에 감이 좀 잡힌 것 같습니다. 추가로 배민 기술 블로그와 인프콘에 관련 내용이 있어 같이 링크 남깁니다.&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://reflectoring.io/spring-boot-gradle-multi-module/&quot;&gt;https://reflectoring.io/spring-boot-gradle-multi-module/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1692275257154&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;Building a Multi-Module Spring Boot Application with Gradle&quot; data-og-description=&quot;A tutorial on how to split up a Spring Boot application into multiple Gradle modules.&quot; data-og-host=&quot;reflectoring.io&quot; data-og-source-url=&quot;https://reflectoring.io/spring-boot-gradle-multi-module/&quot; data-og-url=&quot;https://reflectoring.io/spring-boot-gradle-multi-module/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pm3nH/hyTFnmjORZ/dxnnLukRJXn05r5bwXM4rK/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/u8VFk/hyTFcLRm1S/4kJZIrzpaWlOUxLaNi2mE1/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/ktz1s/hyTFjRKuom/Ju8EFIIkG9wk2kzVIUeRyk/img.jpg?width=650&amp;amp;height=340&amp;amp;face=0_0_650_340&quot;&gt;&lt;a href=&quot;https://reflectoring.io/spring-boot-gradle-multi-module/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://reflectoring.io/spring-boot-gradle-multi-module/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pm3nH/hyTFnmjORZ/dxnnLukRJXn05r5bwXM4rK/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/u8VFk/hyTFcLRm1S/4kJZIrzpaWlOUxLaNi2mE1/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/ktz1s/hyTFjRKuom/Ju8EFIIkG9wk2kzVIUeRyk/img.jpg?width=650&amp;amp;height=340&amp;amp;face=0_0_650_340');&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;Building a Multi-Module Spring Boot Application with Gradle&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A tutorial on how to split up a Spring Boot application into multiple Gradle modules.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;reflectoring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2637/&quot;&gt;https://techblog.woowahan.com/2637/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1692196045942&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;멀티모듈 설계 이야기 with Spring, Gradle | 우아한형제들 기술블로그&quot; data-og-description=&quot;{{item.name}} 멀티 모듈 설계 이야기 안녕하세요. 배달의민족 프론트 서버를 개발하고 있는 권용근입니다. 멀티 모듈의 개념을 처음알게 되었을 때부터 현재까지 겪었던 문제점들과 그것을 어떻게&quot; data-og-host=&quot;techblog.woowahan.com&quot; data-og-source-url=&quot;https://techblog.woowahan.com/2637/&quot; data-og-url=&quot;https://techblog.woowahan.com/2637/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/XnqEA/hyTCGAPexw/QgYJ78K2KQLPeLlps3vzRk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/b0Mj5q/hyTCLhNoWQ/jcjGFGIsdZ7GsGdzo4szmK/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/BXpir/hyTFpqDEez/sYHHuT9hzA3RtF12e8nEKk/img.png?width=1210&amp;amp;height=766&amp;amp;face=0_0_1210_766&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2637/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.woowahan.com/2637/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/XnqEA/hyTCGAPexw/QgYJ78K2KQLPeLlps3vzRk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/b0Mj5q/hyTCLhNoWQ/jcjGFGIsdZ7GsGdzo4szmK/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/BXpir/hyTFpqDEez/sYHHuT9hzA3RtF12e8nEKk/img.png?width=1210&amp;amp;height=766&amp;amp;face=0_0_1210_766');&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;멀티모듈 설계 이야기 with Spring, Gradle | 우아한형제들 기술블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;{{item.name}} 멀티 모듈 설계 이야기 안녕하세요. 배달의민족 프론트 서버를 개발하고 있는 권용근입니다. 멀티 모듈의 개념을 처음알게 되었을 때부터 현재까지 겪었던 문제점들과 그것을 어떻게&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.woowahan.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/12720/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://techblog.woowahan.com/12720/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1692199215128&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;Spring Boot Kotlin Multi Module로 구성해보는 헥사고날 아키텍처 | 우아한형제들 기술블로그&quot; data-og-description=&quot;{{item.name}} 들어가는 말 백엔드 개발자들은 프로젝트를 처음 구성할 때 사용할 기술에 관해 많은 고민을 합니다. 우리 데이터베이스(DB)로 CockroachDB가 요즘 흥한다던데 이걸 사용해 보는 건 어떨&quot; data-og-host=&quot;techblog.woowahan.com&quot; data-og-source-url=&quot;https://techblog.woowahan.com/12720/&quot; data-og-url=&quot;https://techblog.woowahan.com/12720/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ikSDr/hyTCEQxjG6/ketzIsqG5tiXszZV8hjPi0/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/czAUIs/hyTCKXuDd4/sU94V61zPzQ48pCd3dgisk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/bwhNDz/hyTCCSJN0j/ft2mpx6iOiH8WYZVSdWw91/img.png?width=1024&amp;amp;height=447&amp;amp;face=0_0_1024_447&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/12720/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.woowahan.com/12720/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ikSDr/hyTCEQxjG6/ketzIsqG5tiXszZV8hjPi0/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/czAUIs/hyTCKXuDd4/sU94V61zPzQ48pCd3dgisk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/bwhNDz/hyTCCSJN0j/ft2mpx6iOiH8WYZVSdWw91/img.png?width=1024&amp;amp;height=447&amp;amp;face=0_0_1024_447');&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;Spring Boot Kotlin Multi Module로 구성해보는 헥사고날 아키텍처 | 우아한형제들 기술블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;{{item.name}} 들어가는 말 백엔드 개발자들은 프로젝트를 처음 구성할 때 사용할 기술에 관해 많은 고민을 합니다. 우리 데이터베이스(DB)로 CockroachDB가 요즘 흥한다던데 이걸 사용해 보는 건 어떨&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.woowahan.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=ipDzLJK-7Kc&amp;amp;t=107s&quot;&gt;https://www.youtube.com/watch?v=ipDzLJK-7Kc&amp;amp;t=107s&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=ipDzLJK-7Kc&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cAGuI2/hyTCzhpay7/PhOFikDuadYERFUwfgwzT1/img.jpg?width=640&amp;amp;height=480&amp;amp;face=0_0_640_480&quot; data-video-width=&quot;640&quot; data-video-height=&quot;480&quot; data-video-origin-width=&quot;640&quot; data-video-origin-height=&quot;480&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/ipDzLJK-7Kc&quot; width=&quot;640&quot; height=&quot;480&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>아키텍처</category>
      <author>Kyle H</author>
      <guid isPermaLink="true">https://tech.tistory.com/61</guid>
      <comments>https://tech.tistory.com/61#entry61comment</comments>
      <pubDate>Thu, 17 Aug 2023 21:29:03 +0900</pubDate>
    </item>
  </channel>
</rss>