<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Simple is IT, 누구나 보고 누구나 깨닫는 IT</title>
    <link>https://simpleisit.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 26 Jul 2026 14:54:10 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>currenjin</managingEditor>
    <image>
      <title>Simple is IT, 누구나 보고 누구나 깨닫는 IT</title>
      <url>https://tistory1.daumcdn.net/tistory/3877754/attach/034ea6cfde5042078b10b53ca487aa3d</url>
      <link>https://simpleisit.tistory.com</link>
    </image>
    <item>
      <title>나는 무엇을 두려워했는가.</title>
      <link>https://simpleisit.tistory.com/214</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@jung23e/%EB%82%98%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%84-%EB%91%90%EB%A0%A4%EC%9B%8C%ED%96%88%EB%8A%94%EA%B0%80-d8bc49f87da3&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/@jung23e/%EB%82%98%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%84-%EB%91%90%EB%A0%A4%EC%9B%8C%ED%96%88%EB%8A%94%EA%B0%80-d8bc49f87da3&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Life is Good</category>
      <author>currenjin</author>
      <guid isPermaLink="true">https://simpleisit.tistory.com/214</guid>
      <comments>https://simpleisit.tistory.com/214#entry214comment</comments>
      <pubDate>Tue, 12 Aug 2025 21:56:29 +0900</pubDate>
    </item>
    <item>
      <title>도전은 연결된다. &amp;mdash; 5년차 소프트웨어 엔지니어편</title>
      <link>https://simpleisit.tistory.com/213</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@jung23e/%EB%8F%84%EC%A0%84%EC%9D%80-%EC%97%B0%EA%B2%B0%EB%90%9C%EB%8B%A4-5%EB%85%84%EC%B0%A8-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%ED%8E%B8-f26edb188a9f&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/@jung23e/%EB%8F%84%EC%A0%84%EC%9D%80-%EC%97%B0%EA%B2%B0%EB%90%9C%EB%8B%A4-5%EB%85%84%EC%B0%A8-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%ED%8E%B8-f26edb188a9f&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754140056080&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;도전은 연결된다. &amp;mdash; 5년차 소프트웨어 엔지니어편&quot; data-og-description=&quot;Intro: 문제 해결은 도전에서 시작된다&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/@jung23e/%EB%8F%84%EC%A0%84%EC%9D%80-%EC%97%B0%EA%B2%B0%EB%90%9C%EB%8B%A4-5%EB%85%84%EC%B0%A8-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%ED%8E%B8-f26edb188a9f&quot; data-og-url=&quot;https://medium.com/@jung23e/%EB%8F%84%EC%A0%84%EC%9D%80-%EC%97%B0%EA%B2%B0%EB%90%9C%EB%8B%A4-5%EB%85%84%EC%B0%A8-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%ED%8E%B8-f26edb188a9f&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c6k2Qf/hyZqTHDgQC/1KIYNU8rkWuTQZLabjiWf0/img.jpg?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200,https://scrap.kakaocdn.net/dn/bgFlvS/hyZqWK9rjE/B6fw7LG6Z4JGOKMp5mkU4k/img.jpg?width=1358&amp;amp;height=905&amp;amp;face=0_0_1358_905&quot;&gt;&lt;a href=&quot;https://medium.com/@jung23e/%EB%8F%84%EC%A0%84%EC%9D%80-%EC%97%B0%EA%B2%B0%EB%90%9C%EB%8B%A4-5%EB%85%84%EC%B0%A8-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%ED%8E%B8-f26edb188a9f&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/@jung23e/%EB%8F%84%EC%A0%84%EC%9D%80-%EC%97%B0%EA%B2%B0%EB%90%9C%EB%8B%A4-5%EB%85%84%EC%B0%A8-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%ED%8E%B8-f26edb188a9f&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c6k2Qf/hyZqTHDgQC/1KIYNU8rkWuTQZLabjiWf0/img.jpg?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200,https://scrap.kakaocdn.net/dn/bgFlvS/hyZqWK9rjE/B6fw7LG6Z4JGOKMp5mkU4k/img.jpg?width=1358&amp;amp;height=905&amp;amp;face=0_0_1358_905');&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;도전은 연결된다. &amp;mdash; 5년차 소프트웨어 엔지니어편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Intro: 문제 해결은 도전에서 시작된다&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.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>Life is Good</category>
      <category>BackendDevelopment #DomainDrivenDesign #StartupLessons #SagaPattern #EngineeringCulture #GrowthThroughFailure</category>
      <author>currenjin</author>
      <guid isPermaLink="true">https://simpleisit.tistory.com/213</guid>
      <comments>https://simpleisit.tistory.com/213#entry213comment</comments>
      <pubDate>Sat, 2 Aug 2025 22:07:58 +0900</pubDate>
    </item>
    <item>
      <title>Spring Bean Lifecycle</title>
      <link>https://simpleisit.tistory.com/212</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJa5lj/btsKrUgKgpD/deNNHcDQypZeLklK3z4kv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJa5lj/btsKrUgKgpD/deNNHcDQypZeLklK3z4kv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJa5lj/btsKrUgKgpD/deNNHcDQypZeLklK3z4kv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJa5lj%2FbtsKrUgKgpD%2FdeNNHcDQypZeLklK3z4kv1%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;400&quot; height=&quot;279&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;Life Cycle&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;(블로그보단 아래 위키의 가독성이 더 좋다. 추천한다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;a href=&quot;https://currenjin.github.io/wiki/spring-bean/#life-cycle&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://currenjin.github.io/wiki/spring-bean/#life-cycle&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730376762894&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;Spring Bean&quot; data-og-description=&quot; &quot; data-og-host=&quot;currenjin.github.io&quot; data-og-source-url=&quot;https://currenjin.github.io/wiki/spring-bean/#life-cycle&quot; data-og-url=&quot;https://currenjin.github.io/wiki/spring-bean/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bT09p9/hyXpsLYLsf/t7AzA4B2DOI05xMINKNTp0/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500&quot;&gt;&lt;a href=&quot;https://currenjin.github.io/wiki/spring-bean/#life-cycle&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://currenjin.github.io/wiki/spring-bean/#life-cycle&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bT09p9/hyXpsLYLsf/t7AzA4B2DOI05xMINKNTp0/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500');&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 Bean&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;currenjin.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;Bean이라는 녀석은 어떤 타이밍에 생성되고, 또 소멸될까?&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;순서&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;크게 다음과 같은 순서를 갖는다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;생성자 호출 (Bean 인스턴스 생성)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;의존성 주입 (Properties 세팅)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;@PostConstruct 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;afterPropertiesSet() 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;@PreDestroy 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;destroy() 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;순서만 봐서는 와닿지 않을 것이다. 아래와 같은 예제를 생각해보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class UserService {
   private final UserRepository userRepository;

   public UserService(UserRepository userRepository) {
      this.userRepository = userRepository;
   }
}

@Repository
public class UserRepository {
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;생성자 호출&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;Bean의 생명주기에 따라 콘솔 출력을 해보자. 첫 번째로는, 생성자를 호출하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 생성자 호출 (Bean 인스턴스 생성)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 의존성 주입 (Properties 세팅)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; @PostConstruct 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; afterPropertiesSet() 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; @PreDestroy 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; destroy() 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;UserService의 생성자에 콘솔을 작성해보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public UserService(UserRepository userRepository) {
  System.out.println(&quot;1. 생성자 호출: Bean 인스턴스 생성&quot;);
  this.userRepository = userRepository;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이를 확인하기 위한 테스트는 다음과 같이 작성할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@SpringBootTest
class UserServiceBeanTest {
   @Test
   void lifeCycleTest() {
      AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
      context.register(UserService.class);
      context.register(UserRepository.class);
      context.refresh();

      context.getBean(UserService.class);

      context.close();
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;실행해보면, 아래와 같이 출력된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 생성자 호출: Bean 인스턴스 생성&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;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; &lt;b&gt;생성자 호출 (Bean 인스턴스 생성)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 의존성 주입 (Properties 세팅)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; @PostConstruct 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; afterPropertiesSet() 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; @PreDestroy 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; destroy() 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;의존성 주입&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;의존성 주입도 확인해보자. &lt;code&gt;UserService&lt;/code&gt;가 &lt;code&gt;UserRepository&lt;/code&gt;를 의존하고 있기 때문에 &lt;code&gt;UserRepository&lt;/code&gt;의 생성자에 다음과 같은 콘솔을 추가한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Repository
public class UserRepository {
    public UserRepository() {
        System.out.println(&quot;2. 의존성 주입: Properties 세팅&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;테스트 코드를 실행해보자. 아래와 같은 순서로 출력될 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;2. 의존성 주입: Properties 세팅
1. 생성자 호출: Bean 인스턴스 생성&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;숫자를 2로 마킹해서 그렇지, 당연한 결과다. &lt;code&gt;UserService&lt;/code&gt;를 호출하기 위해 &lt;code&gt;UserRepository&lt;/code&gt; 인스턴스가 먼저 생성되어야 하기 때문이다. (1, 2번의 순서대로 출력될 거라 생각한 사람은 다시금 깨닫는 계기가 될 수 있다)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; &lt;b&gt;생성자 호출 (Bean 인스턴스 생성)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; &lt;b&gt;의존성 주입 (Properties 세팅)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; @PostConstruct 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; afterPropertiesSet() 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; @PreDestroy 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; destroy() 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;@PostConstruct 메서드 실행&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이제, &lt;code&gt;PostConstruct&lt;/code&gt; 메서드로 넘어가보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;PostConstruct 메서드는 이름과 같이, 생성자를 호출한 이후 호출되는 메서드이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;code&gt;UserService&lt;/code&gt;, &lt;code&gt;UserRepository&lt;/code&gt; 모두 적용해보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        System.out.println(&quot;1. 생성자 호출: Bean 인스턴스 생성&quot;);
        this.userRepository = userRepository;
    }

    @PostConstruct
    public void postConstruct() {
        System.out.println(&quot;3. PostConstruct 메서드 실행: UserService&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Repository
public class UserRepository {
    public UserRepository() {
        System.out.println(&quot;2. 의존성 주입: Properties 세팅&quot;);
    }

    @PostConstruct
    public void postConstruct() {
        System.out.println(&quot;3. PostConstruct 메서드 실행: UserRepository&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;결과가 어떻게 나올지 상상해보고 테스트를 실행해보자. 어떻게 나올 것 같은가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;결과는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;2. 의존성 주입: Properties 세팅
3. PostConstruct 메서드 실행: UserRepository
1. 생성자 호출: Bean 인스턴스 생성
3. PostConstruct 메서드 실행: UserService&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;감이 좀 잡히는가?&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; &lt;b&gt;생성자 호출 (Bean 인스턴스 생성)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; &lt;b&gt;의존성 주입 (Properties 세팅)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; &lt;b&gt;@PostConstruct 메서드 실행&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; afterPropertiesSet() 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; @PreDestroy 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; destroy() 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;afterPropertiesSet() 메서드 실행&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이제는 afterPropertiesSet 메서드를 상속받아 실행해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;afterPropertiesSet 메서드는 우리가 application.yml 등에서 정의한 환경변수가 스프링의 빈에 의해 주입된 이후 실행되는 메서드다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;확인하기 위해서는 InitializingBean을 상속하여 메서드를 오버라이딩 해야한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class UserService implements InitializingBean {
   // ...

   @Override
   public void afterPropertiesSet() {
      System.out.println(&quot;4. afterPropertiesSet 메서드 실행: UserService&quot;);
   }
}


@Repository
public class UserRepository implements InitializingBean {
   // ...


   @Override
   public void afterPropertiesSet() {
      System.out.println(&quot;4. afterPropertiesSet 메서드 실행: UserRepository&quot;);
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;실행해보면 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;2. 의존성 주입: Properties 세팅
3. PostConstruct 메서드 실행: UserRepository
4. afterPropertiesSet 메서드 실행: UserRepository
1. 생성자 호출: Bean 인스턴스 생성
3. PostConstruct 메서드 실행: UserService
4. afterPropertiesSet 메서드 실행: UserService&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;어떤 차이인지 모르겠다면 &lt;code&gt;UserService&lt;/code&gt;에서 &lt;code&gt;Value Annotation&lt;/code&gt;을 통해 &lt;code&gt;Properties&lt;/code&gt;를 주입받도록 해준다. (&lt;code&gt;cacheEnabled&lt;/code&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class UserService implements InitializingBean {
   @Value(&quot;${user.cache.enabled:true}&quot;)
   private boolean cacheEnabled;

   private final UserRepository userRepository;

   public UserService(UserRepository userRepository) {
      System.out.println(&quot;1. 생성자 호출: Bean 인스턴스 생성. property : &quot; + cacheEnabled);
      this.userRepository = userRepository;
   }

   @PostConstruct
   public void postConstruct() {
      System.out.println(&quot;3. PostConstruct 메서드 실행: UserService. property : &quot; + cacheEnabled);
   }

   @Override
   public void afterPropertiesSet() {
      System.out.println(&quot;4. afterPropertiesSet 메서드 실행: UserService. property : &quot; + cacheEnabled);
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;결과가 어떨지 상상해보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;2. 의존성 주입: Properties 세팅
3. PostConstruct 메서드 실행: UserRepository
4. afterPropertiesSet 메서드 실행: UserRepository
1. 생성자 호출: Bean 인스턴스 생성. property : false
3. PostConstruct 메서드 실행: UserService. property : true
4. afterPropertiesSet 메서드 실행: UserService. property : true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;무언가 이상하지 않은가? PostConstruct를 호출하는 시점에 이미 properties가 주입되어있는 상태이다. 그렇다면, PostConstructor와 afterPropertiesSet은 도대체 무슨 차이일까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;극명한 차이는 다음과 같다. PostConstruct는 javax 패키지에 있고, afterPropertiesSet 메서드는 springframework 패키지에 있다. 특히 정의된 afterPropertiesSet 메서드를 보면, Exception을 던질 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;void afterPropertiesSet() throws Exception;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이로 인해, afterPropertiesSet 메서드는 property 값에 따라 예외를 핸들링 할 수 있다. 그 외에는 큰 차이가 없으므로 스프링 프레임워크에 의존하지 않는 PostConstruct 어노테이션이 주로 사용된다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; &lt;b&gt;생성자 호출 (Bean 인스턴스 생성)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; &lt;b&gt;의존성 주입 (Properties 세팅)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; &lt;b&gt;@PostConstruct 메서드 실행&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; &lt;b&gt;afterPropertiesSet() 메서드 실행&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; @PreDestroy 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; destroy() 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;@PreDestroy 메서드 실행&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;PreDestroy 또한 어노테이션이 존재한다. &lt;code&gt;UserService&lt;/code&gt;와 &lt;code&gt;UserRepository&lt;/code&gt;에 다음과 같이 적용해보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class UserService implements InitializingBean {
   // ...

   @PreDestroy
   public void preDestroy() {
      System.out.println(&quot;5. PreDestroy 메서드 실행: UserService. property : &quot; + cacheEnabled);
   }
}

@Repository
public class UserRepository implements InitializingBean {
   // ...

   @PreDestroy
   public void preDestroy() {
      System.out.println(&quot;5. PreDestroy 메서드 실행: UserRepository&quot;);
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;그리고 테스트 코드에서 destroy의 구분을 위해 콘솔을 추가한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@SpringBootTest
class UserServiceBeanTest {
    @Test
    void lifeCycleTest() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(UserService.class);
        context.register(UserRepository.class);
        context.refresh();

        context.getBean(UserService.class);

        System.out.println(&quot;============================&quot;);

        context.close();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;자, 이번엔 결과가 어떨지 상상해보자. 이제는 예상이 가지 않는가? 자연스럽게 상상해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;결과는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;2. 의존성 주입: Properties 세팅
3. PostConstruct 메서드 실행: UserRepository
4. afterPropertiesSet 메서드 실행: UserRepository
1. 생성자 호출: Bean 인스턴스 생성. property : false
3. PostConstruct 메서드 실행: UserService. property : true
4. afterPropertiesSet 메서드 실행: UserService. property : true
============================
5. PreDestroy 메서드 실행: UserService. property : true
5. PreDestroy 메서드 실행: UserRepository&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;code&gt;context.close&lt;/code&gt; 메서드가 호출되면, &lt;code&gt;UserRepository&lt;/code&gt;를 의존하는 &lt;code&gt;UserService&lt;/code&gt;에서 먼저 &lt;code&gt;Destroy&lt;/code&gt;가 진행된다. 당연한 결과다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; &lt;b&gt;생성자 호출 (Bean 인스턴스 생성)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; &lt;b&gt;의존성 주입 (Properties 세팅)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; &lt;b&gt;@PostConstruct 메서드 실행&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; &lt;b&gt;afterPropertiesSet() 메서드 실행&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; &lt;b&gt;@PreDestroy 메서드 실행&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; destroy() 메서드 실행&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;destroy() 메서드 실행&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이제 마지막 단계인 destroy() 메서드는 어떨까?&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Service
public class UserService implements InitializingBean, DisposableBean {
   // ...

   @Override
   public void destroy() {
      System.out.println(&quot;6. destroy 메서드 실행: UserService. property : &quot; + cacheEnabled);
   }
}

@Repository
public class UserRepository implements InitializingBean, DisposableBean {
   // ...

   @Override
   public void destroy() {
      System.out.println(&quot;6. destroy 메서드 실행: UserRepository&quot;);
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;결과는 대부분 예상할 수 있을 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;2. 의존성 주입: Properties 세팅
3. PostConstruct 메서드 실행: UserRepository
4. afterPropertiesSet 메서드 실행: UserRepository
1. 생성자 호출: Bean 인스턴스 생성. property : false
3. PostConstruct 메서드 실행: UserService. property : true
4. afterPropertiesSet 메서드 실행: UserService. property : true
============================
5. PreDestroy 메서드 실행: UserService. property : true
6. destroy 메서드 실행: UserService. property : true
5. PreDestroy 메서드 실행: UserRepository
6. destroy 메서드 실행: UserRepository&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;눈치챈 사람도 있겠지만, PreDestroy는 javax 패키지에 존재하고, destroy 메서드는 springframework 패키지에 존재하며 에러 핸들링이 가능하다는 차이가 있다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Simple is IT/Programming</category>
      <category>Bean</category>
      <category>lifecycle</category>
      <category>spring</category>
      <category>SpringBoot</category>
      <category>빈</category>
      <category>생명주기</category>
      <category>스프링</category>
      <category>스프링부트</category>
      <author>currenjin</author>
      <guid isPermaLink="true">https://simpleisit.tistory.com/212</guid>
      <comments>https://simpleisit.tistory.com/212#entry212comment</comments>
      <pubDate>Thu, 31 Oct 2024 21:14:08 +0900</pubDate>
    </item>
    <item>
      <title>앤 드루얀의 코스모스: 가능한 세계들</title>
      <link>https://simpleisit.tistory.com/211</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;9791190403283.jpg&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;413&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csjLSw/btsJ2UWJea2/EiMkNnFdVcG1Ewe2wBt8LK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csjLSw/btsJ2UWJea2/EiMkNnFdVcG1Ewe2wBt8LK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csjLSw/btsJ2UWJea2/EiMkNnFdVcG1Ewe2wBt8LK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsjLSw%2FbtsJ2UWJea2%2FEiMkNnFdVcG1Ewe2wBt8LK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;280&quot; height=&quot;413&quot; data-filename=&quot;9791190403283.jpg&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;413&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;시간이 지나고, 우리의 끝은 이 거대한 서사의 시작에 한 번이라도 닿았을까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Life is Good/Book</category>
      <category>ann druyan</category>
      <category>Cosmos</category>
      <category>possible worlds</category>
      <category>가능한 세계들</category>
      <category>앤 드루얀</category>
      <category>코스모스</category>
      <author>currenjin</author>
      <guid isPermaLink="true">https://simpleisit.tistory.com/211</guid>
      <comments>https://simpleisit.tistory.com/211#entry211comment</comments>
      <pubDate>Sat, 12 Oct 2024 22:22:55 +0900</pubDate>
    </item>
    <item>
      <title>칼 세이건의 코스모스 - 두 가지 질문</title>
      <link>https://simpleisit.tistory.com/210</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;9788983711540.jpg&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;495&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/duKJov/btsJU9j5i3J/NPqjOqAYk3wmS3USCeOjrk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/duKJov/btsJU9j5i3J/NPqjOqAYk3wmS3USCeOjrk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/duKJov/btsJU9j5i3J/NPqjOqAYk3wmS3USCeOjrk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FduKJov%2FbtsJU9j5i3J%2FNPqjOqAYk3wmS3USCeOjrk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;495&quot; data-filename=&quot;9788983711540.jpg&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;495&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;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이 책의 682(마지막)페이지를 넘긴 후에는 두 가지의 질문만이 머릿속에 맴돈다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;나는 지금까지 어떻게 살아왔는가? 그리고 또 어떻게 살아갈 것인가?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;벌써 몇 번째 정독하는 건지 모를. 이번에 가장 기억에 남는 키워드는 바로 &amp;lsquo;핵전쟁&amp;rsquo;이다. 누군가가 나에게 &amp;lsquo;핵전쟁은 비극인가?&amp;rsquo;라는 질문을 한다면 나는 확신을 담아 대답할 수 있다. &amp;ldquo;절대 비극이다.&amp;rdquo; 핵전쟁에 사용되는 핵무기는 도대체 왜 탄생하게 됐을까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;개인의 생존 위협과 분노에 &amp;lsquo;살인&amp;rsquo;과 같은 상황이 펼쳐지듯, 국가의 생존 위협과 분노에 &amp;lsquo;전쟁&amp;rsquo;이라는 상황이 수없이 발생해왔다. 나는 &amp;lsquo;심리적 관점에서 전쟁은 살인과 같다&amp;rsquo;는 칼 세이건의 말에 전적으로 동의한다. 2차 세계대전이 한창일 때, 추축국 독일은 핵무기 제작에 돌입했었으며, 연합국 미국이 이를 지켜볼 수만은 없었다. 이후 수많은 학자를 미국으로 데려와 말도 안 되는 짧은 시간 안에 이론을 검증했고, 결과적으로 끔찍하기 그지없는 &amp;lsquo;핵무기&amp;rsquo;가 탄생했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;왜 우리는 서로 같은 문명끼리 화해할 줄 모르고, 증오심으로 가득 차 자기 파괴의 길로 다가서는 것인가? 우주에서 내려다본 지구는 국경선도 없는 창백한 푸른 점에 불과할 뿐인데 말이다. 엄청난 양의 에너지가 뿜어져 나온 대폭발부터, 핵융합 반응이 시작되고, 연료를 소모한 별들은 또 폭발하며 여러 별이 탄생했다. 그리고 그 옆에 핵융합 반응을 일으키기 어려운 질량을 가진 행성들. 그중 하나가 바로 우리의 원시 지구이다. 우주의 변두리에 존재할 뿐인 인간은 자신과 다른 생각을 하는 사람이나 사회를 기괴한 존재로 간주하며 혐오하고는 한다. 스스로에 대한 의심은 갖지 않으면서 말이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;하지만 수많은 행성 중 생물이 탄생하는 과정은 아주 극한의 어려움이 따른다. 우주에서 벌어진 진화의 단계를 이해한다면, 거대한 &amp;lsquo;수소 산업&amp;rsquo;의 최종 산물로서 태어난 생물 하나하나가 정말 소중한 존재임을 알게 된다. 그리고 그들이 문명을 이뤄 현재는 스스로 기원을 인식할 수 있는 정도까지 성장해 왔다. 이것이 코스모스의 대서사시가 아니면 무엇이겠는가.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;우주에 박힌 미물이면서 기적에 가까운 우리 인간이 자신에게 가장 위험한 존재로 변하다니. 얼마나 끔찍한가? 우리는 아주 오래전부터 사랑의 범주를 넓혀왔다. 자기가 속한 마을에서, 부족으로, 그리고 도시에서, 국가의 순으로 말이다. 그리고 이제는 우리가 사랑해야 할 범주를 지구로 확대해야 하지 않을까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;그렇다면 질문을 해보자. 나는 어떻게 살아왔고, 또 어떻게 살아갈까?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Life is Good/Book</category>
      <category>Carl Sagan</category>
      <category>Cosmos</category>
      <category>칼세이건</category>
      <category>코스모스</category>
      <category>핵전쟁</category>
      <author>currenjin</author>
      <guid isPermaLink="true">https://simpleisit.tistory.com/210</guid>
      <comments>https://simpleisit.tistory.com/210#entry210comment</comments>
      <pubDate>Thu, 3 Oct 2024 15:12:50 +0900</pubDate>
    </item>
    <item>
      <title>Fixture Monkey를 적용해보자 w/JPA Test</title>
      <link>https://simpleisit.tistory.com/209</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://currenjin.github.io/wiki/fixture-monkey-with-jpa/&quot;&gt;Link&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Fixture Monkey를 적용해보자&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지루함을 편함으로 바꿔주는 Fixture Monkey에 대한 문서이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/91f37f68-ca1b-495b-872a-f53d2986ab59&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Problem&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 작성하며 각자 느끼는 고충이 있을 것이다. 그 중 많은 사람들이 테스트를 위한 셋업 코드를 작성하며 많은 시간을 소요하고, 지루함을 느끼곤 한다. TDD의 저자 켄트벡은 두려움이 지루함으로 변할 때까지 테스트를 작성하라고 하지만, 나는 더 나아가 반복되는 지루함은 반드시 자동화해야 한다고 생각한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드를 작성하는 우리의 두려움은 지루함으로 바뀌었으니, 이 지루함의 반복됨을 편함으로 바꿀 수는 없을까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Fixture Monkey&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fixture Monkey는 이런 고민을 해결해준다. 이 라이브러리를 사용하면,&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;테스트 데이터 생성을 자동화하여 시간을 아낄 수 있다.&lt;/li&gt;
&lt;li&gt;다양한 시나리오를 쉽게 테스트할 수 있다.&lt;/li&gt;
&lt;li&gt;테스트 코드의 가독성과 유지보수성이 높아진다.&lt;/li&gt;
&lt;li&gt;엣지 케이스를 쉽게 생성하여 더 견고한 테스트가 가능하다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fixture Monkey를 사용하면, 테스트 작성에 대한 부담은 줄어들고 실제 비즈니스 로직 테스트에 더 집중할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring Boot JPA&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fixture Monkey를 테스트하기 위해 Spring Data JPA 프로젝트에 구현되어있는 테스트 코드를 참고할 생각이다. JPA가 무엇인지, 사용법은 어떻게 되는지는 이 글을 보는 사람이라면 알고 있다고 가정하겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Links&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-data-jpa/blob/main/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java&quot;&gt;GitHub - Spring Data JPA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/currenjin/TDD/blob/main/fixture-monkey/src/test/java/com/tdd/domain/user/UserRepositoryTest.java&quot;&gt;GitHub - Example Code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Environment&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Spring Boot Framework 2.6.4&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Java 17&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Fixture Monkey 1.0.25&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Spring Data JPA&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;H2 Database&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Domain Model&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;User Entity&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Entity
@Table(name = &quot;user&quot;)
public class User {
    public User() {
        this(null, null, null);
    }

    public User(String firstName, String lastName, String emailAddress) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.emailAddress = emailAddress;
        this.active = true;
        this.createdAt = new Date();
    }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String firstName;

    private String lastName;

    private boolean active;

    @Column(nullable = false, unique = true)
    private String emailAddress;

    @Temporal(TemporalType.TIMESTAMP)
    private Date createdAt;

    // ...Getter
    // ...Setter
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;User Repository&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Repository
public interface UserRepository extends JpaRepository&amp;lt;User, Long&amp;gt; {
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Test&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;pre class=&quot;groovy&quot;&gt;&lt;code&gt;@DataJpaTest
class UserRepositoryTest {
    @PersistenceContext
    private EntityManager em;

    @Autowired
    private UserRepository repository;

    // ...Test Function
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명해보자면,&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;@DataJpaTest&lt;/code&gt; : JPA 테스트를 위한 어플리케이션 컨텍스트만 로드하여 인메모리 데이터베이스를 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@PersistenceContext EntityManager&lt;/code&gt; : JPA EntityManager를 주입받는다. 테스트에서 직접적인 데이터베이스 조작이 필요한 경우 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Autowired UserRepository&lt;/code&gt; : 테스트 대상인 UserRepository를 주입받는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Creation AS-IS&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째로, 생성을 위한 테스트를 진행하겠다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@BeforeEach
void setUp() {
    firstUser = new User(&quot;hyunjin&quot;, &quot;jeong&quot;, &quot;hyun0524e@naver.com&quot;);
}

@Test
void creation() {
    Query query = em.createQuery(&quot;select count(u) from User u&quot;);
    Long before = (Long) query.getSingleResult();

    repository.save(firstUser);

    assertEquals(before + 1L, query.getSingleResult());
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;firstUser 객체를 직접 생성한다.&lt;/li&gt;
&lt;li&gt;entityManager를 통해 count 쿼리를 생성한다.&lt;/li&gt;
&lt;li&gt;생성 이전 count를 조회한다.&lt;/li&gt;
&lt;li&gt;firstUser를 db에 저장한다.&lt;/li&gt;
&lt;li&gt;생성 이후 count와 생성 이전 count + 1과 일치한지 확인한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드만 보면 우리가 일반적으로 작성하는 테스트 코드로 생각된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서, firstUser 객체에 더 많은 파라미터를 담게 된다면 어떨까?&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@BeforeEach
void setUp() {
    firstUser = new User(&quot;hyunjin&quot;, &quot;jeong&quot;, &quot;hyun0524e@naver.com&quot;, &quot;서울특별시 강남구 태헤란로 xx번길&quot;, &quot;xx아파트 xxx동 xxxx호&quot;, &quot;010-0000-0000&quot;, LocalDate.of(2000, 5, 24));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;게다가&lt;/b&gt; 객체를 여러 개 생성하여 테스트하려면 어떻게 될까?&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;private User firstUser;
private User secondUser;
private User thirdUser;
private User fourthUser;
private User fifthUser;

@BeforeEach
void setUp() {
    firstUser = new User(&quot;hyunjin&quot;, &quot;jeong&quot;, &quot;hyun0524e@naver.com&quot;, &quot;서울특별시 강남구 태헤란로 xx번길&quot;, &quot;xx아파트 xxx동 xxxx호&quot;, &quot;010-0000-0000&quot;, LocalDate.of(2000, 5, 24));
    secondUser = new User(&quot;hyunjin&quot;, &quot;jeong&quot;, &quot;hyun0524e@naver.com&quot;, &quot;서울특별시 강남구 태헤란로 xx번길&quot;, &quot;xx아파트 xxx동 xxxx호&quot;, &quot;010-0000-0000&quot;, LocalDate.of(2000, 5, 24));
    thirdUser = new User(&quot;hyunjin&quot;, &quot;jeong&quot;, &quot;hyun0524e@naver.com&quot;, &quot;서울특별시 강남구 태헤란로 xx번길&quot;, &quot;xx아파트 xxx동 xxxx호&quot;, &quot;010-0000-0000&quot;, LocalDate.of(2000, 5, 24));
    fourthUser = new User(&quot;hyunjin&quot;, &quot;jeong&quot;, &quot;hyun0524e@naver.com&quot;, &quot;서울특별시 강남구 태헤란로 xx번길&quot;, &quot;xx아파트 xxx동 xxxx호&quot;, &quot;010-0000-0000&quot;, LocalDate.of(2000, 5, 24));
    fifthUser = new User(&quot;hyunjin&quot;, &quot;jeong&quot;, &quot;hyun0524e@naver.com&quot;, &quot;서울특별시 강남구 태헤란로 xx번길&quot;, &quot;xx아파트 xxx동 xxxx호&quot;, &quot;010-0000-0000&quot;, LocalDate.of(2000, 5, 24));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;게다가&lt;/b&gt; 우리가 만든 User Entity에서 이메일은 중복할 수 없다는 점은 잊지 말자&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@BeforeEach
void setUp() {
    firstUser = new User(&quot;hyunjin&quot;, &quot;jeong&quot;, &quot;hyun0524e@naver.com&quot;, &quot;서울특별시 강남구 태헤란로 xx번길&quot;, &quot;xx아파트 xxx동 xxxx호&quot;, &quot;010-0000-0000&quot;, LocalDate.of(2000, 5, 24));
    secondUser = new User(&quot;hyunjin&quot;, &quot;jeong&quot;, &quot;qwe@test.com&quot;, &quot;서울특별시 강남구 태헤란로 xx번길&quot;, &quot;xx아파트 xxx동 xxxx호&quot;, &quot;010-0000-0000&quot;, LocalDate.of(2000, 5, 24));
    thirdUser = new User(&quot;hyunjin&quot;, &quot;jeong&quot;, &quot;zxc@test.com&quot;, &quot;서울특별시 강남구 태헤란로 xx번길&quot;, &quot;xx아파트 xxx동 xxxx호&quot;, &quot;010-0000-0000&quot;, LocalDate.of(2000, 5, 24));
    fourthUser = new User(&quot;hyunjin&quot;, &quot;jeong&quot;, &quot;dfg@test.com&quot;, &quot;서울특별시 강남구 태헤란로 xx번길&quot;, &quot;xx아파트 xxx동 xxxx호&quot;, &quot;010-0000-0000&quot;, LocalDate.of(2000, 5, 24));
    fifthUser = new User(&quot;hyunjin&quot;, &quot;jeong&quot;, &quot;asd@test.com&quot;, &quot;서울특별시 강남구 태헤란로 xx번길&quot;, &quot;xx아파트 xxx동 xxxx호&quot;, &quot;010-0000-0000&quot;, LocalDate.of(2000, 5, 24));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자, 이제 어떻게 할 것인가? 이메일만 따로 받아 객체를 생성하는 메소드를 작성할 것인가? 그러면 완전 다른 값이 필요한 객체는?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국엔 객체를 생성하며 모든 값을 '생각하고' 넣어주어야 한다는 것이다. Fixture Monkey에서는 우리가 테스트 로직에만 집중할 수 있도록 도와준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Dependency &amp;amp; Build object&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 종속성을 추가하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dependency&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;dependencies {
    testImplementation 'com.navercorp.fixturemonkey:fixture-monkey-starter:1.0.25'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용을 위해 테스트 클래스에서 FixtureMonkey 객체를 빌드한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;First SetUp&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;@DataJpaTest
class UserRepositoryTest {
    FixtureMonkey fixtureMonkey = FixtureMonkey.builder().objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE).build();

    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Creation TO-BE&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 정의했던 객체를 FixtureMonkey를 통해 생성해 보자.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@BeforeEach
void setUp() {
    firstUser = fixtureMonkey.giveMeOne(User.class);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;놀랍게도 이게 끝이다!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fixtureMonkey 객체에서 giveMeOne(class)를 호출하면 필요한 파라미터에 난수값을 적용해 해당 클래스의 객체를 생성해주는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Debug를 통해 생성된 객체를 확인해 볼까?&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;User {
    id = null;
    firstName = &quot;춝䪼ꧏ&quot;;
    lastName = &quot;苖⻵ꪘ쫏▷膄톣㷂䱧&quot;;
    active = true;
    emailAddress = &quot;ᛩോ튅⛝櫕ᣵ솙칖谌ｰ퀭埘樅㺏㧑⍭規喲ꫮ൐Ὧ륑삲砎鳃抝Фꞻ᣾⤇崆&quot;;
    createdAt = &quot;Mon Sep 16 12:18:00 KST 2024&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Fixture Monkey 동작&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 동작이 궁금하지 않다면 넘어가도 좋다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;내부 동작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리의 소스코드를 파악해 본 결과 다음과 같은 동작 과정을 거친다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/b79fec6c-0c74-4f11-8b40-2212abfad4c0&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;giveMeOne 메서드를 호출한다.&lt;/li&gt;
&lt;li&gt;타입 정보를 담은 default builder 인스턴스를 생성한 후 sample 메서드를 호출한다.&lt;/li&gt;
&lt;li&gt;sample 메서드에서는 각 필드의 타입에 일치하는 값을 생성하고, 이 값으로 해당하는 타입 인스턴스를 생성한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;resolve 함수가 호출되면 타입에 일치하는 값을 생성한다.&lt;/li&gt;
&lt;li&gt;conbimed 함수가 호출되면 필드에 생성된 값으로 인스턴스를 생성해 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;giveMeOne&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;fsharp&quot;&gt;&lt;code&gt;public &amp;lt;T&amp;gt; T giveMeOne(Class&amp;lt;T&amp;gt; type) {
    return this.giveMe(type, 1).get(0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 객체를 넘기며 호출하는 내부 함수 giveMe가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;giveMe&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public &amp;lt;T&amp;gt; List&amp;lt;T&amp;gt; giveMe(Class&amp;lt;T&amp;gt; type, int size) {
    return this.giveMe(type).limit(size).collect(toList());
}

public &amp;lt;T&amp;gt; Stream&amp;lt;T&amp;gt; giveMe(Class&amp;lt;T&amp;gt; type) {
    return Stream.generate(() -&amp;gt; this.giveMeBuilder(type).sample());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;giveMeBuilder를 호출한 후, sample을 호출하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 giveMeBuilder를 보자.&lt;/p&gt;
&lt;pre class=&quot;elm&quot;&gt;&lt;code&gt;public &amp;lt;T&amp;gt; ArbitraryBuilder&amp;lt;T&amp;gt; giveMeBuilder(Class&amp;lt;T&amp;gt; type) {
    TypeReference&amp;lt;T&amp;gt; typeReference = new TypeReference&amp;lt;T&amp;gt;(type) {
    };
    return giveMeBuilder(typeReference);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;giveMeBuilder에서는 type을 받고, 해당하는 타입에 대한 ArbitraryBuilder를 리턴받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArbitraryBuilder를 만들어주는 함수를 확인해볼까? 이해하기 쉽게 주석을 달아놨다.&lt;/p&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;public &amp;lt;T&amp;gt; ArbitraryBuilder&amp;lt;T&amp;gt; giveMeBuilder(TypeReference&amp;lt;T&amp;gt; type) {
    // 1. Type에 대한 정보를 담는다.
    RootProperty rootProperty = new RootProperty(type.getAnnotatedType());

    // 2. Type에 해당하는 ArbitraryBuilder를 매칭한다.
    ArbitraryBuilderContext builderContext = registeredArbitraryBuilders.stream()
        .filter(it -&amp;gt; it.match(rootProperty))
        .map(MatcherOperator::getOperator)
        .findAny()
        .map(DefaultArbitraryBuilder.class::cast)
        .map(DefaultArbitraryBuilder::getContext)
        .orElse(new ArbitraryBuilderContext());

    // 3. Type 정보를 담은 DefaultArbitraryBuilder 인스턴스를 생성한다.
    return new DefaultArbitraryBuilder&amp;lt;&amp;gt;(
        fixtureMonkeyOptions,
        rootProperty,
        new ArbitraryResolver(
            traverser,
            manipulatorOptimizer,
            monkeyManipulatorFactory,
            fixtureMonkeyOptions,
            monkeyContext,
            registeredArbitraryBuilders
        ),
        traverser,
        monkeyManipulatorFactory,
        builderContext.copy(),
        registeredArbitraryBuilders,
        monkeyContext,
        fixtureMonkeyOptions.getInstantiatorProcessor()
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 User Type을 통해 호출하니 User Type에 대한 정보를 담은 ArbitraryBuilder가 반환될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위에서 언급한 sample 메서드가 호출되면 어떤일이 발생할까?&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public interface ArbitraryBuilder&amp;lt;T&amp;gt; {
    T sample();
}

// 구현체
public final class DefaultArbitraryBuilder&amp;lt;T&amp;gt; implements ArbitraryBuilder&amp;lt;T&amp;gt;, ExperimentalArbitraryBuilder&amp;lt;T&amp;gt; {
    public T sample() {
        return this.resolveArbitrary(this.context).combined();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;resolverArbitrary를 호출하면, 해당 컨텍스트 정보에 맞게 값을 생성한다. User class 내 각 필드 타입에 따라 값을 생성한다는 의미다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 호출하는 resolve 메서드에는 ObjectTree 인스턴스를 구성한다. 일부 코드를 가져와서 확인해보자.&lt;/p&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;public CombinableArbitrary&amp;lt;?&amp;gt; resolve(
        RootProperty rootProperty,
        ArbitraryBuilderContext builderContext
) {
   // ...

   objectTree -&amp;gt; {
       List&amp;lt;ArbitraryManipulator&amp;gt; registeredManipulators =
           monkeyManipulatorFactory.newRegisteredArbitraryManipulators(
               registeredArbitraryBuilders,
               objectTree.getMetadata().getNodesByProperty()
           );

       List&amp;lt;ArbitraryManipulator&amp;gt; joinedManipulators =
           Stream.concat(registeredManipulators.stream(), manipulators.stream())
               .collect(Collectors.toList());

       List&amp;lt;ArbitraryManipulator&amp;gt; optimizedManipulator = manipulatorOptimizer
           .optimize(joinedManipulators)
           .getManipulators();

       for (ArbitraryManipulator manipulator : optimizedManipulator) {
           manipulator.manipulate(objectTree);
       }
       return objectTree.generate();
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;resolve 메서드의 반환 코드 중 일부인데, ArbitraryManipulator 인스턴스를 이용해 ObjectTree 인스턴스를 컨트롤하고, generate 메서드를 호출해 최종 객체를 완성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;resolve 메서드에서 반환되는 객체는 CombinableArbitrary 인스턴스이고, combined 메서드를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;combined 메서드가 호출되면 생성되었던 ObjectTree 인스턴스를 이용해 User 인스턴스를 생성하고 반환한다. 결과적으로 아래 값이 반환된다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;User {
    id = null;
    firstName = &quot;춝䪼ꧏ&quot;;
    lastName = &quot;苖⻵ꪘ쫏▷膄톣㷂䱧&quot;;
    active = true;
    emailAddress = &quot;ᛩോ튅⛝櫕ᣵ솙칖谌ｰ퀭埘樅㺏㧑⍭規喲ꫮ൐Ὧ륑삲砎鳃抝Фꞻ᣾⤇崆&quot;;
    createdAt = &quot;Mon Sep 16 12:18:00 KST 2024&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Fixture Monkey 문자열 난수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열의 난수는 어떤 방식으로 생성될까?&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;로직 분석&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, User 클래스의 firstName 타입은 문자열(String)이다. 문자열은 MonkeyStringArbitrary 클래스를 통해 처리된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public final class MonkeyStringArbitrary implements StringArbitrary {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 클래스는 StringArbitrary 인터페이스를 구현했는데, 관련된 함수가 jqwik 라이브러리에 의존하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드를 보면 잘 확인할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public final class MonkeyStringArbitrary implements StringArbitrary {
    // 문자 생성 담당
    private CharacterArbitrary characterArbitrary = new DefaultCharacterArbitrary();

    // 문자열 생성 메서드 
    @Override
    public RandomGenerator&amp;lt;String&amp;gt; generator(int genSize) {
        long maxUniqueChars = characterArbitrary
              .exhaustive(maxLength())
              .map(ExhaustiveGenerator::maxCount)
              .orElse((long)maxLength());
        return RandomGenerators.strings(
              randomCharacterGenerator(),
              minLength, maxLength(), maxUniqueChars,
              genSize, lengthDistribution,
              newThreadSafeArbitrary(characterArbitrary)
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;generator 메서드 호출을 통해 jqwik 라이브러리로 위임을 했다. DefaultCharacterArbitrary 클래스를 확인해 보면 아래와 같은 코드가 있다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;private Arbitrary&amp;lt;Character&amp;gt; defaultArbitrary() {
   return this.rangeArbitrary('\u0000', '\uffff').filter((c) -&amp;gt; {
      return !isNoncharacter(c) &amp;amp;&amp;amp; !isPrivateUseCharacter(c);
   });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 유니코드를 생성하고 있는데 이 때문에 여러 언어가 포함된 값이 생성된다. 대신, 비문자와 개인용 문자를 제외한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 사용하는 과정을 MonkeyStringArbitrary 클래스부터 확인해보자.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// MonkeyStringArbitrary
private RandomGenerator&amp;lt;Character&amp;gt; randomCharacterGenerator() {
   RandomGenerator&amp;lt;Character&amp;gt; characterGenerator = effectiveCharacterArbitrary().generator(1, false);
   if (repeatChars &amp;gt; 0) {
      return characterGenerator.injectDuplicates(repeatChars);
   } else {
      return characterGenerator;
   }
}

// DefaultCharacterArbitrary
public RandomGenerator&amp;lt;Character&amp;gt; generator(int genSize) {
   return this.arbitrary().generator(genSize);
}

private Arbitrary&amp;lt;Character&amp;gt; arbitrary() {
   if (this.partsWithSize.isEmpty()) {
      return this.defaultArbitrary();
   } else {
      return this.partsWithSize.size() == 1 ? (Arbitrary)((Tuple.Tuple2)this.partsWithSize.get(0)).get2() : Arbitraries.frequencyOf(this.partsWithSize);
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 defaultArbitrary를 호출하여 값을 생성하는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 테스트 환경 특성 상 중국어 간체라던가 키릴문자가 나오는 상황이 발생하면 안 될 수도 있다. 알파벳 값만 얻고 싶은가?&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Override
public StringArbitrary alpha() {
  this.characterArbitrary = this.characterArbitrary.alpha();
  return this;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친절하게도 MonkeyStringArbitrary 구현체에 구현이 되어있다. 알파벳 뿐만 아니라 ascii, numberic, whitespace 등을 사용할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;로직 구현&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 이용해 모든 문자열 필드는 알파벳만으로 구성해 보겠다.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;@BeforeEach
void setUp() {
    firstUser = fixtureMonkey.giveMeBuilder(User.class)
            .set(&quot;firstName&quot;, new MonkeyStringArbitrary().alpha())
            .sample();
}

// 결과
User {
   id = null;
   firstName = &quot;NiGPfLvWoDwxIrhOdnXkyVYCmtRpASM&quot;;
   lastName = &quot;彜ॵ䕥殿騍눭≖鿤ᄭ똊暿즹쓨ᷧ⻐ᣡ媻哙떬បଝ䗳&quot;;
   active = true;
   emailAddress = null;
   createdAt = &quot;Mon Sep 16 17:07:37 KST 2024&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 객체를 생성할 때, 한 번에 적용하고 싶다면? 처음 fixtureMonkey 인스턴스를 빌드할 때 register 해주면 된다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
      .objectIntrospector(FieldReflectionArbitraryIntrospector.INSTANCE)
      .register(User.class, arbitraryBuilder -&amp;gt;
              arbitraryBuilder.giveMeBuilder(String.class)
                      .set(&quot;firstName&quot;, new MonkeyStringArbitrary().alpha())
                      .set(&quot;lastName&quot;, new MonkeyStringArbitrary().alpha())
      )
      .build();

User {
   id = null;
   firstName = &quot;sZfq&quot;;
   lastName = &quot;PbAuticMWIXoUJVLhJZdrVRCPzh&quot;;
   active = false;
   emailAddress = &quot;&quot;;
   createdAt = &quot;Thu Feb 08 00:00:00 KST 2024&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한글 문자열 적용(feat. contribute)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/naver/fixture-monkey/pull/1056&quot;&gt;PR Link&lt;/a&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;&lt;b&gt;문자열 난수&lt;/b&gt; 챕터에서 확인했듯이, FixtureMonkey 인스턴스를 빌드하는 과정에서 MonkeyStringArbitrary를 적용할 수 있을 것이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;로직 분석&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한글만 적용해주기 위해서는 일단 alpha 메서드를 확인할 필요가 있다. (한글만 적용해주는 메서드가 없기 때문이다)&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Override
public StringArbitrary alpha() {
   this.characterArbitrary = this.characterArbitrary.alpha();
   return this;
}

public CharacterArbitrary alpha() {
    return this.range('A', 'Z').range('a', 'z');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jqwik 라이브러리를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부에서 range를 알파벳으로 정의해 주는데, 직접 range를 설정할 수 있는 메서드를 지원하지 않을리가 없다고 생각한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Override
public StringArbitrary all() {
    return this.withCharRange(Character.MIN_VALUE, Character.MAX_VALUE);
}

@Override
public StringArbitrary withCharRange(char from, char to) {
   this.characterArbitrary = this.characterArbitrary.range(from, to);
   return this;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MonkeyStringArbitrary 클래스 내에 all 메서드를 발견했는데, 해당 메서드에서는 모든 범위를 직접 지정해준다. 운이 좋게도 withCharRange는 public 메서드여서 직접 정의할 수가 있겠다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;어떤 값을 정의하는가?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jqwik library 내의 defaultArbitrary에서 확인했듯이 Unicode로 범위를 지정해줘야 한다. 한글은 AC00부터 D7AF까지이며, 실질적 사용 범위는 D7A3까지이다. (나머지 문자는 정의되지 않음)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/b970cae4-b0a0-46ec-8bd8-4ad81fec42b9&quot; alt=&quot;image&quot; width=&quot;125&quot; /&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/b9c2ba8d-3b6b-4e84-a674-2e20d6b0a8e8&quot; alt=&quot;스크린샷 2024-09-21 오후 9 50 55&quot; width=&quot;125&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이를 문자로 봤을 때 &lt;code&gt;가&lt;/code&gt;부터 &lt;code&gt;힣&lt;/code&gt; 까지이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 정의해 보면,&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
   .objectIntrospector(FieldReflectionArbitraryIntrospector.INSTANCE)
   .register(User.class, arbitraryBuilder -&amp;gt;
      arbitraryBuilder.giveMeBuilder(String.class)
         .set(&quot;firstName&quot;, new MonkeyStringArbitrary().withCharRange('가', '힣'))
         .set(&quot;lastName&quot;, new MonkeyStringArbitrary().withCharRange('가', '힣'))
      )
   .build();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 객체를 확인해 봤을 때 값은 아래와 같다.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;User {
   id = null;
   firstName = &quot;짇쇅&quot;;
   lastName = &quot;뚼셃룫욻믉묩돚펇뒄냱쨃뮀튣퇿졷쑂졗징욡뎩몁&quot;;
   active = true;
   emailAddress = null;
   createdAt = &quot;Wed Feb 07 00:00:00 KST 2024&quot;;
} &lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Contribute&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 메서드화될 수 있다고 생각하여 FixtureMonkey Repository를 Fork하여 &lt;a href=&quot;https://github.com/naver/fixture-monkey/pull/1056&quot;&gt;Pull Request&lt;/a&gt;를 진행해 둔 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MonkeyStringArbitrary&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;public StringArbitrary korean() {
   this.characterArbitrary = this.characterArbitrary.range('가', '힣');
   return this;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 위 메서드를 검증하기 위한 테스트도 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MonkeyStringArbitraryTest&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;StringArbitrary koreanStringArbitrary = new MonkeyStringArbitrary().korean();

@Test
void koreanShouldGenerateOnlyKoreanCharacters() {
   StringArbitrary arbitrary = koreanStringArbitrary.ofMinLength(1).ofMaxLength(10);

   String sample = arbitrary.sample();

   assertTrue(sample.chars().allMatch(ch -&amp;gt; ch &amp;gt;= '가' &amp;amp;&amp;amp; ch &amp;lt;= '힣'));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플 데이터를 추출하여 Unicode 상 한글 범위에 해당하는지 테스트했다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Property(tries = 100)
void koreanShouldAlwaysGenerateStringsWithinKoreanCharacterRange(
    @ForAll @Size(min = 1, max = 50) String ignored
) {
   String sample = koreanStringArbitrary.sample();

   assertTrue(sample.chars().allMatch(ch -&amp;gt; ch &amp;gt;= '가' &amp;amp;&amp;amp; ch &amp;lt;= '힣'));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 여러번 시도하여 항상 한글 범위에 해당하는지 테스트했다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Test
void koreanShouldRespectMinAndMaxLength() {
   int minLength = 5;
   int maxLength = 10;
   StringArbitrary arbitrary = koreanStringArbitrary.ofMinLength(minLength).ofMaxLength(maxLength);

   String sample = arbitrary.sample();

   assertTrue(sample.length() &amp;gt;= minLength &amp;amp;&amp;amp; sample.length() &amp;lt;= maxLength);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 샘플 데이터 생성을 요청한 길이가 올바른지 또한 검증했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 PR이 통과할지는 불분명하겠지만, 불편함을 편함으로 바꿔주는 FixtureMonkey를 다루며 편리함을 추구하는 시도는 지속할 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;객체 리스트 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체를 하나하나 정의하기에도 지루함이 느껴질 때가 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;private User firstUser;
private User secondUser;
private User thirdUser;
private User fourthUser;
private User fifthUser;

@BeforeEach
void setUp() {
  firstUser = fixtureMonkey.giveMeOne(User.class);
  secondUser = fixtureMonkey.giveMeOne(User.class);
  thirdUser = fixtureMonkey.giveMeOne(User.class);
  fourthUser = fixtureMonkey.giveMeOne(User.class);
  fifthUser = fixtureMonkey.giveMeOne(User.class);
}

@Test
void creation() {
   Query query = em.createQuery(&quot;select count(u) from User u&quot;);
   Long before = (Long) query.getSingleResult();
   List&amp;lt;User&amp;gt; users = List.of(firstUser, secondUser, thirdUser, fourthUser, fifthUser);

   repository.saveAll(users);

   assertEquals(before + 5L, query.getSingleResult());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제 코드와 같이 사용할 수도 있겠지만, 여러 개의 객체를 한 번에 생성하고자 하면 어떻게 해야할까?&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;로직 분석&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작 과정을 살펴보던 중 giveMeOne 메서드에서 호출하던 giveMe 메서드를 보자.&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;public &amp;lt;T&amp;gt; T giveMeOne(Class&amp;lt;T&amp;gt; type) {
    return this.giveMe((Class)type, 1).get(0);
}

public &amp;lt;T&amp;gt; List&amp;lt;T&amp;gt; giveMe(Class&amp;lt;T&amp;gt; type, int size) {
   return (List)this.giveMe(type).limit((long)size).collect(Collectors.toList());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;size 값만큼 객체를 생성해주는 것으로 보인다. 마침, public 메서드이니 우리도 사용할 수 있겠다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;로직 구현&lt;/h4&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;private List&amp;lt;User&amp;gt; userList;

@BeforeEach
void setUp() {
    userList = fixtureMonkey.giveMe(User.class, 5);
}

@Test
void creation() {
   Query query = em.createQuery(&quot;select count(u) from User u&quot;);
   Long before = (Long) query.getSingleResult();

   repository.saveAll(userList);

   assertEquals(before + 5L, query.getSingleResult());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;giveMe 메서드를 호출하여 5개의 객체 생성을 요청했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트는 성공했고, Debug mode로 userList를 확인해 보자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/466a4709-9ab4-4330-b3aa-91511adcd71c&quot; alt=&quot;image&quot; width=&quot;743&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다섯 개의 User 인스턴스가 생성된 것을 확인할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;The End&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FixtureMonkey를 활용하면 테스트 데이터 생성의 복잡성을 줄이고 테스트 자체에 더 집중할 수 있다. 이는 코드의 품질을 높이고 개발 생산성을 향상시키는 데 큰 도움이 될 것이다. FixtureMonkey와 함께 더 나은 테스트 문화를 만들어나가길 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로, 사실과 다른 내용이 있다면 댓글로 남겨주길 바란다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TO-DO(memo)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Nullable 테스트를 고려하기(ex. null인 경우 예외, null이 아닌 경우 예외 등)&lt;/li&gt;
&lt;li&gt;특정 포맷을 적용하기(ex. &lt;a href=&quot;mailto:email@test.com&quot;&gt;email@test.com&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;특정 범위만 허용/제외하기(ex. 1-99)&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Simple is IT/Programming</category>
      <category>fixturemonkey</category>
      <category>object</category>
      <category>TDD</category>
      <category>test</category>
      <category>테스트</category>
      <category>테스트객체</category>
      <category>테스트객체생성</category>
      <category>픽스쳐몽키</category>
      <author>currenjin</author>
      <guid isPermaLink="true">https://simpleisit.tistory.com/209</guid>
      <comments>https://simpleisit.tistory.com/209#entry209comment</comments>
      <pubDate>Sat, 21 Sep 2024 22:40:14 +0900</pubDate>
    </item>
    <item>
      <title>[Consider - The Clean Coder] 테스트 주도 개발</title>
      <link>https://simpleisit.tistory.com/208</link>
      <description>&lt;h2&gt;밥 아저씨가 생각하는 테스트 주도 개발&lt;/h2&gt;
&lt;h3&gt;TDD의 세 가지 법칙&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;실패한 단위 테스트를 만들기 전에는 제품 코드를 만들지 않는다.&lt;/li&gt;
&lt;li&gt;컴파일이 안 되거나 실패한 단위 테스트가 있으면 더 이상의 단위 테스트를 만들지 않는다.&lt;/li&gt;
&lt;li&gt;실패한 단위 테스트를 통과하는 이상의 제품 코드는 만들지 않는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;확신&lt;/h3&gt;
&lt;p&gt;FitNesse의 코드는 6만 4천 줄인데, 그중 2만 8천 줄은 테스트 코드다.&lt;/p&gt;
&lt;p&gt;어떤 부분이라도 바꾸게 되면 별 생각 없이 단위 테스트 코드를 돌리는데, 통과하면 내가 만든 변경이 다른 부분을 망가뜨리지 않았다고 거의 확신할 수 있다.&lt;/p&gt;
&lt;h3&gt;용기&lt;/h3&gt;
&lt;p&gt;믿음직한 테스트 묶음이 있으면 변경에 대한 두려움이 모두 사라진다. 나쁜 코드가 보이면 그저 그 자리를 깨끗이 치우면 된다.&lt;/p&gt;
&lt;h3&gt;문서화&lt;/h3&gt;
&lt;p&gt;세 가지 법칙에 따라 만든 각 단위 테스트는 코드로 만든 에제이며 시스템을 어떻게 사용해야 하는지 알려준다.&lt;/p&gt;
&lt;p&gt;단위 테스트는 문서다. 시스템의 가장 낮은 단계의 설계를 알려준다. 낮은 단계에 대한 문서 중 가장 훌륭한 형태다.&lt;/p&gt;
&lt;h3&gt;설계&lt;/h3&gt;
&lt;p&gt;테스트를 먼저 만드는 일은 의존성이 낮은 좋은 설계를 만드는 힘이 된다.&lt;/p&gt;
&lt;p&gt;&amp;quot;나는 테스트를 나중에 만들 수 있어요.&amp;quot;라는 말은 틀렸다. 사실이 아니며 만들 수 없다. 일이 벌어진 후에 만드는 테스트는 수비다. 먼저 만드는 테스트는 공격이다.&lt;/p&gt;
&lt;h2&gt;내가 생각하는 테스트 주도 개발&lt;/h2&gt;
&lt;p&gt;나는 테스트 주도 개발을 좋아한다. 아니, 좋아하다 못해 사랑한다. TDD가 추구하는 방향은 내가 추구하는 방향과 많은 의미에서 맞닿아 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;첫 번째로, 고객(유저) 중심적으로 생각할 수 있다는 점이다.&lt;/strong&gt; TDD는 결과물을 먼저 테스트 코드로 작성하고 이를 충족하기 위해 로직을 채운다. 로직은 딱 테스트를 충족시킬 만큼만 채운다. 다시 테스트를 추가하고 이를 충족시키는 로직을 채우며 사이클을 반복한다.&lt;/p&gt;
&lt;p&gt;나는 사용자에게 빠르면서 핵심적인 가치를 전달하고 싶다. 이를 위해서는 정말 필요한 일들만 하는 훈련이 필요하다. TDD는 나에게 이런 훈련을 위한 도구가 된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;두 번째로, 안정성을 느낄 수 있다는 점이다.&lt;/strong&gt; 내가 만드는 결과물이 성장해 나가는 과정에서 스텝마다의 테스트를 통해 확신을 갖고 나아간다. 이 확신은 내가 가치를 전달함에 있어서 안정성을 느끼게 해준다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;세 번째로, 커뮤니케이션 리소스가 줄어든다.&lt;/strong&gt; 효율적으로 커뮤니케이션할 수 있다는 게 더 명확한 것 같다. 테스트 코드를 남겨놓으면 이 테스트 코드는 다른 사람들이 보고 이해할 수 있는 핵심적인 문서가 될 것이다.&lt;/p&gt;
&lt;p&gt;중학생이 보고도 이해가 가는 코드를 짜는 것이 목표인 나에게는 테스트 코드라는 문서를 어떻게 하면 가독성을 좋게 만들 수 있을지 고민하게 하며 나를 성장시킨다.&lt;/p&gt;</description>
      <category>Simple is IT/Programming</category>
      <category>consider</category>
      <category>TDD</category>
      <category>the clean coder</category>
      <category>Uncle Bob</category>
      <category>고찰</category>
      <category>로버트 마틴</category>
      <category>생각</category>
      <category>엉클밥</category>
      <category>클린코더</category>
      <category>테스트 주도 개발</category>
      <author>currenjin</author>
      <guid isPermaLink="true">https://simpleisit.tistory.com/208</guid>
      <comments>https://simpleisit.tistory.com/208#entry208comment</comments>
      <pubDate>Tue, 25 Jun 2024 12:54:36 +0900</pubDate>
    </item>
    <item>
      <title>[Consider - The Clean Coder] 미리 읽어두기</title>
      <link>https://simpleisit.tistory.com/207</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;태도, 원칙, 그리고 행동&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;내가 바라는 개발자의 태도&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;우리는 목적을 함께하는 사람이다.&lt;/b&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;작은 조직에서는 서로의 일이 가까이 있기 때문에 목적을 함께하는 느낌이 강하고, 어떤 일을 진행함에 있어서도 서로 노력할 것이다. 하지만 조직의 규모가 커지면 서로의 일을 멀리서 바라보게 되고, 다양한 이해관계가 생기며 그 목적을 인지하기도, 함께한다고 생각하기도 어려워진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 나는 조직이 크든 작든 바라보는 목적이 같아야 한다고 생각한다. 개발자는 서로의 다른 목적을 충족시키기 위해 개발하는 것이 아니라, 함께 목적을 이루기 위해 개발하는 것이다. 그 목적에 다가가기 위해 각자가 가장 잘하는 것으로 빠르게 접근해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 바라는 개발자의 태도는 단 하나다. 누군가의 요청이나 요구사항이 있을 때 이를 서로 다른 목적으로 인지하는 것이 아닌, &lt;b&gt;함께 목적을 달성하기 위한 일련의 과정으로 바라보는 것.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;내가 바라는 개발자의 원칙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;우리가 무엇을 하는지에 대한 정확한 인지와 이해가 필요하다.&lt;/b&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;이런 상황을 겪다 보면 원칙을 좋아하지 않지만 최소한의 원칙은 필요하다는 생각이 든다. 원칙을 정할 때에는 모두의 이해와 공감, 그리고 끊임없이 개선할 수 있는 여지가 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 과정에서 우리는 모두 한 목표를 바라보고, 같은 목적에 다가가고 있다는 느낌을 받아야 한다. &lt;b&gt;결국 모두가 한 가지 목적을 인지해야 한다는 원칙을 말하고 싶다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;내가 바라는 개발자의 행동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;건설적이고 비판적인 토론&lt;/b&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;이 과정에서 발생하는 커뮤니케이션은 불가피하다. 토론, 브레인스토밍 등 다양한 형태의 커뮤니케이션이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비판적으로 접근하며, 목적에 호의적인 자세를 유지한다면 우리는 고객에게 닿는 제품을 더욱 완전하게 만들 수 있을 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;나의 현재 모습(태도, 원칙, 행동)은 어떨까?&lt;/h3&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;우리가 목적에 더욱 다가가기 위해서는 모두가 그 목적을 인지해야 한다고 생각하지만, 일이 바쁘다는 핑계로 목적에 대한 설명을 늦추기도 한다. 개인적으로 정말 중요하게 생각하는 가치를 뒤로 미루고 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 해야 모두가 목적을 완전하게 인지하며, 빠르게 나아갈 수 있을까? 요즘의 내 고민이다.&lt;/p&gt;</description>
      <category>Simple is IT/Programming</category>
      <category>consider</category>
      <category>the clean coder</category>
      <category>Uncle Bob</category>
      <category>고찰</category>
      <category>로버트 마틴</category>
      <category>미리 읽어두기</category>
      <category>생각</category>
      <category>엉클밥</category>
      <category>클린코더</category>
      <author>currenjin</author>
      <guid isPermaLink="true">https://simpleisit.tistory.com/207</guid>
      <comments>https://simpleisit.tistory.com/207#entry207comment</comments>
      <pubDate>Sun, 23 Jun 2024 10:39:28 +0900</pubDate>
    </item>
    <item>
      <title>[개발자 필독]   개발자를 위한 북마크 모음집</title>
      <link>https://simpleisit.tistory.com/206</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/currenjin/site-for-developers&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/currenjin/site-for-developers&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706153447592&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - currenjin/site-for-developers:   개발자를 위한 북마크 모음집&quot; data-og-description=&quot;  개발자를 위한 북마크 모음집. Contribute to currenjin/site-for-developers development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/currenjin/site-for-developers&quot; data-og-url=&quot;https://github.com/currenjin/site-for-developers&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dnRYzu/hyU8VXcJ4q/cEIlpkVFhzm3XyKBG2kbBk/img.png?width=1200&amp;amp;height=600&amp;amp;face=976_129_1019_175&quot;&gt;&lt;a href=&quot;https://github.com/currenjin/site-for-developers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/currenjin/site-for-developers&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dnRYzu/hyU8VXcJ4q/cEIlpkVFhzm3XyKBG2kbBk/img.png?width=1200&amp;amp;height=600&amp;amp;face=976_129_1019_175');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - currenjin/site-for-developers:   개발자를 위한 북마크 모음집&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  개발자를 위한 북마크 모음집. Contribute to currenjin/site-for-developers development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Simple is IT/Programming</category>
      <author>currenjin</author>
      <guid isPermaLink="true">https://simpleisit.tistory.com/206</guid>
      <comments>https://simpleisit.tistory.com/206#entry206comment</comments>
      <pubDate>Thu, 25 Jan 2024 12:30:54 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin + Spring Multi module Template</title>
      <link>https://simpleisit.tistory.com/205</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/currenjin/kopring-multi-module-template&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/currenjin/kopring-multi-module-template&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704944230146&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - currenjin/kopring-multi-module-template: Kotlin + Spring Multi module Template(Layered Architecture  ️)&quot; data-og-description=&quot;Kotlin + Spring Multi module Template(Layered Architecture  ️) - GitHub - currenjin/kopring-multi-module-template: Kotlin + Spring Multi module Template(Layered Architecture  ️)&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/currenjin/kopring-multi-module-template&quot; data-og-url=&quot;https://github.com/currenjin/kopring-multi-module-template&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vaoR6/hyU2ebhkrq/e1PkN57GVfbKAp4klkN0rk/img.png?width=1200&amp;amp;height=600&amp;amp;face=976_129_1019_175&quot;&gt;&lt;a href=&quot;https://github.com/currenjin/kopring-multi-module-template&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/currenjin/kopring-multi-module-template&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vaoR6/hyU2ebhkrq/e1PkN57GVfbKAp4klkN0rk/img.png?width=1200&amp;amp;height=600&amp;amp;face=976_129_1019_175');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - currenjin/kopring-multi-module-template: Kotlin + Spring Multi module Template(Layered Architecture  ️)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin + Spring Multi module Template(Layered Architecture  ️) - GitHub - currenjin/kopring-multi-module-template: Kotlin + Spring Multi module Template(Layered Architecture  ️)&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;Description&lt;/h1&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write the project name and introduction.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;This is a Kotlin + Spring Multi-module project template based on layered architecture.&lt;/p&gt;
&lt;h1 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;a id=&quot;user-content-repository-overview&quot; style=&quot;color: #000000;&quot; href=&quot;https://github.com/currenjin/kopring-multi-module-template#repository-overview&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;Repository Overview&lt;/h1&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write the directory structure.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;The Kopring multi-moudle template basically has an&lt;span&gt;&amp;nbsp;&lt;/span&gt;api,&lt;span&gt;&amp;nbsp;&lt;/span&gt;core&lt;span&gt;&amp;nbsp;&lt;/span&gt;module based on layered architecture.&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;a id=&quot;user-content-api&quot; style=&quot;color: #000000;&quot; href=&quot;https://github.com/currenjin/kopring-multi-module-template#api&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;API&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Controller, Application Layer is included as a package.&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;a id=&quot;user-content-core&quot; style=&quot;color: #000000;&quot; href=&quot;https://github.com/currenjin/kopring-multi-module-template#core&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;Core&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Domain, Infrastructure Layer is included as a package.&lt;/p&gt;
&lt;h1 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;a id=&quot;user-content-getting-started&quot; style=&quot;color: #000000;&quot; href=&quot;https://github.com/currenjin/kopring-multi-module-template#getting-started&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;Getting Started&lt;/h1&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write the 'How to run?' and environment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;a id=&quot;user-content-version&quot; style=&quot;color: #000000;&quot; href=&quot;https://github.com/currenjin/kopring-multi-module-template#version&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;Version&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java : 21&lt;/li&gt;
&lt;li&gt;Gradle : 8.5&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;a id=&quot;user-content-build&quot; style=&quot;color: #000000;&quot; href=&quot;https://github.com/currenjin/kopring-multi-module-template#build&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;Build&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$&amp;nbsp;./gradlew&amp;nbsp;build&lt;/p&gt;
&lt;h1 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;a id=&quot;user-content-example&quot; style=&quot;color: #000000;&quot; href=&quot;https://github.com/currenjin/kopring-multi-module-template#example&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;Example&lt;/h1&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write simple instructions and examples.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>Simple is IT/Programming</category>
      <category>kopring</category>
      <category>Kotlin</category>
      <category>multi module</category>
      <category>spring</category>
      <category>template</category>
      <category>멀티모듈</category>
      <category>스프링</category>
      <category>코틀린</category>
      <category>코프링</category>
      <category>템플릿</category>
      <author>currenjin</author>
      <guid isPermaLink="true">https://simpleisit.tistory.com/205</guid>
      <comments>https://simpleisit.tistory.com/205#entry205comment</comments>
      <pubDate>Thu, 11 Jan 2024 12:37:39 +0900</pubDate>
    </item>
  </channel>
</rss>