<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>justgo_developer</title>
    <link>https://justgo-developer.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 5 Jul 2026 12:47:08 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>다날92</managingEditor>
    <item>
      <title>Kotlin Coroutine 성능 및 최적화 개선</title>
      <link>https://justgo-developer.tistory.com/200</link>
      <description>&lt;h3 data-end=&quot;41&quot; data-start=&quot;0&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  Kotlin Coroutine 최적화 개선 내용 4가지&lt;/b&gt;&lt;/h3&gt;
&lt;hr data-end=&quot;46&quot; data-start=&quot;43&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;107&quot; data-start=&quot;48&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1️⃣ runBlocking &amp;rarr; suspend fun으로 변경 (동기 실행 문제 해결)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-end=&quot;120&quot; data-start=&quot;109&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;257&quot; data-start=&quot;121&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;175&quot; data-start=&quot;121&quot;&gt;기존 코드에서 runBlocking을 사용하여 &lt;b&gt;코루틴이 동기적으로 실행&lt;/b&gt;되고 있었음.&lt;/li&gt;
&lt;li data-end=&quot;229&quot; data-start=&quot;176&quot;&gt;runBlocking을 사용하면 &lt;b&gt;현재 스레드를 블로킹&lt;/b&gt;하기 때문에 &lt;b&gt;비효율적&lt;/b&gt;.&lt;/li&gt;
&lt;li data-end=&quot;257&quot; data-start=&quot;230&quot;&gt;&lt;b&gt;비동기 실행의 장점을 활용할 수 없음&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;272&quot; data-start=&quot;259&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개선 사항&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;385&quot; data-start=&quot;273&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;339&quot; data-start=&quot;273&quot;&gt;runBlocking을 제거하고, &lt;b&gt;함수를 suspend fun으로 변환&lt;/b&gt;하여 완전한 비동기 실행 가능.&lt;/li&gt;
&lt;li data-end=&quot;385&quot; data-start=&quot;340&quot;&gt;suspend 방식은 &lt;b&gt;스레드를 블로킹하지 않고 비동기적으로 실행됨&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;429&quot; data-start=&quot;387&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;비교: runBlocking vs suspend fun&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방식실행 방식블로킹 여부동시성 지원성능&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;682&quot; data-start=&quot;430&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;682&quot; data-start=&quot;520&quot;&gt;
&lt;tr data-end=&quot;603&quot; data-start=&quot;520&quot;&gt;
&lt;td&gt;runBlocking&lt;/td&gt;
&lt;td&gt;동기 실행 (Blocking)&lt;/td&gt;
&lt;td&gt;✅ 블로킹됨&lt;/td&gt;
&lt;td&gt;❌ 제한적 (한 번에 하나의 작업 실행)&lt;/td&gt;
&lt;td&gt;❌ 성능 저하 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;682&quot; data-start=&quot;604&quot;&gt;
&lt;td&gt;suspend&lt;/td&gt;
&lt;td&gt;비동기 실행 (Non-blocking)&lt;/td&gt;
&lt;td&gt;❌ 블로킹되지 않음&lt;/td&gt;
&lt;td&gt;✅ 여러 개의 코루틴 실행 가능&lt;/td&gt;
&lt;td&gt;✅ 고성능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-end=&quot;694&quot; data-start=&quot;684&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;828&quot; data-start=&quot;695&quot; data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;스레드를 블로킹하지 않기 위해 suspend fun을 사용해야 함.&lt;/b&gt;&lt;br /&gt;✅ &lt;b&gt;비동기 API와 자연스럽게 연동되므로 성능 최적화 가능.&lt;/b&gt;&lt;br /&gt;✅ &lt;b&gt;일반적인 비즈니스 로직에서는 suspend fun 방식이 더 적합.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-end=&quot;833&quot; data-start=&quot;830&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;923&quot; data-start=&quot;835&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2️⃣ CoroutineScope(Dispatchers.IO).launch() 대신 withContext(Dispatchers.IO) 사용&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-end=&quot;936&quot; data-start=&quot;925&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1114&quot; data-start=&quot;937&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1014&quot; data-start=&quot;937&quot;&gt;기존 코드에서는 CoroutineScope(Dispatchers.IO).launch {}를 사용하여 I/O 작업을 실행하고 있었음.&lt;/li&gt;
&lt;li data-end=&quot;1076&quot; data-start=&quot;1015&quot;&gt;하지만 launch는 &lt;b&gt;새로운 코루틴을 생성&lt;/b&gt;하기 때문에, &lt;b&gt;불필요한 리소스 낭비 발생 가능&lt;/b&gt;.&lt;/li&gt;
&lt;li data-end=&quot;1114&quot; data-start=&quot;1077&quot;&gt;실행 순서가 보장되지 않으며, &lt;b&gt;결과 값을 받을 수 없음&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;1129&quot; data-start=&quot;1116&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개선 사항&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1275&quot; data-start=&quot;1130&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1215&quot; data-start=&quot;1130&quot;&gt;launch 대신 withContext(Dispatchers.IO) {} 사용하여 &lt;b&gt;새로운 코루틴을 만들지 않고, 현재 컨텍스트만 변경&lt;/b&gt;.&lt;/li&gt;
&lt;li data-end=&quot;1275&quot; data-start=&quot;1216&quot;&gt;withContext를 사용하면 &lt;b&gt;I/O 작업을 실행 순서를 보장하면서 동기적으로 실행 가능&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;1314&quot; data-start=&quot;1277&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;비교: launch vs withContext&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차이점launch (Job)withContext (suspend 블록)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1707&quot; data-start=&quot;1315&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;1707&quot; data-start=&quot;1420&quot;&gt;
&lt;tr data-end=&quot;1459&quot; data-start=&quot;1420&quot;&gt;
&lt;td&gt;&lt;b&gt;반환값&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;없음 (Job)&lt;/td&gt;
&lt;td&gt;T 값을 반환 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1522&quot; data-start=&quot;1460&quot;&gt;
&lt;td&gt;&lt;b&gt;코루틴 생성 여부&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;새로운 코루틴 생성&lt;/td&gt;
&lt;td&gt;&lt;b&gt;새로운 코루틴을 생성하지 않고 컨텍스트만 변경&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1572&quot; data-start=&quot;1523&quot;&gt;
&lt;td&gt;&lt;b&gt;실행 순서 보장&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;❌ 실행 순서를 보장하지 않음&lt;/td&gt;
&lt;td&gt;✅ 실행 순서를 보장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1624&quot; data-start=&quot;1573&quot;&gt;
&lt;td&gt;&lt;b&gt;예외 처리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;부모 코루틴으로 전파됨&lt;/td&gt;
&lt;td&gt;예외를 호출한 곳으로 직접 전달 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1707&quot; data-start=&quot;1625&quot;&gt;
&lt;td&gt;&lt;b&gt;사용 목적&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;결과값이 필요 없는 비동기 실행 (Fire &amp;amp; Forget)&lt;/td&gt;
&lt;td&gt;비동기 작업을 동기적으로 실행하고 결과를 받아야 할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-end=&quot;1719&quot; data-start=&quot;1709&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1866&quot; data-start=&quot;1720&quot; data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;I/O 작업을 수행할 때는 withContext(Dispatchers.IO)를 사용해야 함.&lt;/b&gt;&lt;br /&gt;✅ &lt;b&gt;불필요한 코루틴 생성을 방지하고 실행 순서를 보장할 수 있음.&lt;/b&gt;&lt;br /&gt;✅ &lt;b&gt;예외를 안전하게 처리하면서 비동기 작업을 동기적으로 실행 가능.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-end=&quot;1871&quot; data-start=&quot;1868&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1928&quot; data-start=&quot;1873&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3️⃣ launch 대신 async + await() 사용하여 병렬 실행 최적화&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-end=&quot;1941&quot; data-start=&quot;1930&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2121&quot; data-start=&quot;1942&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2001&quot; data-start=&quot;1942&quot;&gt;기존 코드에서는 launch를 사용하여 비동기 작업을 실행한 후 join()을 사용하여 기다림.&lt;/li&gt;
&lt;li data-end=&quot;2059&quot; data-start=&quot;2002&quot;&gt;하지만 launch는 Deferred를 반환하지 않기 때문에, &lt;b&gt;결과를 받을 수 없음&lt;/b&gt;.&lt;/li&gt;
&lt;li data-end=&quot;2121&quot; data-start=&quot;2060&quot;&gt;launch는 &lt;b&gt;병렬 실행에는 적합하지 않으며&lt;/b&gt;, 결과를 필요로 하는 비동기 작업에서는 비효율적임.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;2136&quot; data-start=&quot;2123&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개선 사항&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2238&quot; data-start=&quot;2137&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2194&quot; data-start=&quot;2137&quot;&gt;launch 대신 async를 사용하여 &lt;b&gt;비동기 작업 후 결과를 받을 수 있도록 변경&lt;/b&gt;.&lt;/li&gt;
&lt;li data-end=&quot;2238&quot; data-start=&quot;2195&quot;&gt;&lt;b&gt;병렬 실행이 필요한 경우 awaitAll()을 활용하여 최적화&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;2271&quot; data-start=&quot;2240&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;비교: launch vs async&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차이점launch (Job)async (Deferred&amp;lt;T&amp;gt;)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;2578&quot; data-start=&quot;2272&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;2578&quot; data-start=&quot;2372&quot;&gt;
&lt;tr data-end=&quot;2420&quot; data-start=&quot;2372&quot;&gt;
&lt;td&gt;&lt;b&gt;반환값&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;없음 (Job)&lt;/td&gt;
&lt;td&gt;Deferred&amp;lt;T&amp;gt; (결과 있음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2477&quot; data-start=&quot;2421&quot;&gt;
&lt;td&gt;&lt;b&gt;결과 접근&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;.join() (완료 대기)&lt;/td&gt;
&lt;td&gt;.await() (결과 가져오기)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2520&quot; data-start=&quot;2478&quot;&gt;
&lt;td&gt;&lt;b&gt;예외 처리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;즉시 전파됨&lt;/td&gt;
&lt;td&gt;await() 호출 시 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2578&quot; data-start=&quot;2521&quot;&gt;
&lt;td&gt;&lt;b&gt;사용 목적&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;단순한 비동기 실행 (Fire &amp;amp; Forget)&lt;/td&gt;
&lt;td&gt;결과를 반환해야 할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-end=&quot;2590&quot; data-start=&quot;2580&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;2735&quot; data-start=&quot;2591&quot; data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;결과가 필요 없는 경우 &amp;rarr; launch 사용&lt;/b&gt;&lt;br /&gt;✅ &lt;b&gt;비동기 결과를 받아야 하는 경우 &amp;rarr; async + await() 사용&lt;/b&gt;&lt;br /&gt;✅ &lt;b&gt;즉, launch는 &quot;실행만&quot;, async는 &quot;결과를 반환하는 비동기 처리&quot;에 적합!&lt;/b&gt;  &lt;/p&gt;
&lt;hr data-end=&quot;2740&quot; data-start=&quot;2737&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2792&quot; data-start=&quot;2742&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4️⃣ await() / awaitAll() 사용하여 병렬 실행 최적화&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-end=&quot;2805&quot; data-start=&quot;2794&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2975&quot; data-start=&quot;2806&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2899&quot; data-start=&quot;2806&quot;&gt;기존 코드에서 awaitAll()을 사용하면 성능 최적화가 가능하지만, &lt;b&gt;각 API의 반환 타입이 다르기 때문에 Type Mismatch 오류 발생 가능&lt;/b&gt;.&lt;/li&gt;
&lt;li data-end=&quot;2975&quot; data-start=&quot;2900&quot;&gt;awaitAll()을 사용하면 &lt;b&gt;모든 async 결과가 같은 타입이어야 함&lt;/b&gt;, 하지만 현재 구조에서는 반환 타입이 다름.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;2990&quot; data-start=&quot;2977&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개선 사항&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3120&quot; data-start=&quot;2991&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3054&quot; data-start=&quot;2991&quot;&gt;&lt;b&gt;각 API의 응답 DTO 타입이 다르므로, 개별 await()을 사용하는 것이 더 안전하고 명확함&lt;/b&gt;.&lt;/li&gt;
&lt;li data-end=&quot;3120&quot; data-start=&quot;3055&quot;&gt;특정 API의 결과를 먼저 받아야 하는 경우 await()을 사용하면 &lt;b&gt;순서대로 로직을 제어할 수 있음&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;3162&quot; data-start=&quot;3122&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;비교: awaitAll() vs 개별 await()&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차이점awaitAll()개별 await()&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;3425&quot; data-start=&quot;3163&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;3425&quot; data-start=&quot;3239&quot;&gt;
&lt;tr data-end=&quot;3312&quot; data-start=&quot;3239&quot;&gt;
&lt;td&gt;&lt;b&gt;실행 방식&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;모든 async 작업을 병렬 실행 후 한 번에 결과 수집&lt;/td&gt;
&lt;td&gt;개별 async 결과를 각각 가져옴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3371&quot; data-start=&quot;3313&quot;&gt;
&lt;td&gt;&lt;b&gt;타입 문제&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;모든 응답 타입이 같아야 함&lt;/td&gt;
&lt;td&gt;각 응답 타입이 다를 때 안전하게 처리 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3425&quot; data-start=&quot;3372&quot;&gt;
&lt;td&gt;&lt;b&gt;순서 제어&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;순서를 제어하기 어려움&lt;/td&gt;
&lt;td&gt;특정 API 결과를 먼저 사용할 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-end=&quot;3437&quot; data-start=&quot;3427&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;3589&quot; data-start=&quot;3438&quot; data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;모든 응답이 같은 타입일 때 &amp;rarr; awaitAll() 사용&lt;/b&gt;&lt;br /&gt;✅ &lt;b&gt;각 API의 응답 타입이 다를 때 &amp;rarr; 개별 await() 사용&lt;/b&gt;&lt;br /&gt;✅ &lt;b&gt;즉, 현재 코드에서는 awaitAll()보다 개별 await()을 사용하는 것이 더 적절함!&lt;/b&gt;  &lt;/p&gt;
&lt;hr data-end=&quot;3594&quot; data-start=&quot;3591&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;3611&quot; data-start=&quot;3596&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  최종 정리&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-end=&quot;3658&quot; data-start=&quot;3612&quot; data-ke-size=&quot;size23&quot;&gt;✅ &lt;b&gt;1️⃣ runBlocking &amp;rarr; suspend fun 변경&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3730&quot; data-start=&quot;3659&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3696&quot; data-start=&quot;3659&quot;&gt;runBlocking을 제거하여 &lt;b&gt;비동기 실행 최적화&lt;/b&gt;.&lt;/li&gt;
&lt;li data-end=&quot;3730&quot; data-start=&quot;3697&quot;&gt;&lt;b&gt;멀티스레드 환경에서도 효율적인 비동기 처리 가능&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;3823&quot; data-start=&quot;3732&quot; data-ke-size=&quot;size23&quot;&gt;✅ &lt;b&gt;2️⃣ CoroutineScope(Dispatchers.IO).launch() 대신 withContext(Dispatchers.IO) 사용&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3881&quot; data-start=&quot;3824&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3845&quot; data-start=&quot;3824&quot;&gt;&lt;b&gt;불필요한 코루틴 생성 방지&lt;/b&gt;.&lt;/li&gt;
&lt;li data-end=&quot;3881&quot; data-start=&quot;3846&quot;&gt;&lt;b&gt;I/O 작업에서 실행 순서 보장 및 예외 처리 강화&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;3941&quot; data-start=&quot;3883&quot; data-ke-size=&quot;size23&quot;&gt;✅ &lt;b&gt;3️⃣ launch 대신 async + await() 사용하여 병렬 실행 최적화&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4007&quot; data-start=&quot;3942&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4007&quot; data-start=&quot;3942&quot;&gt;launch는 &lt;b&gt;결과를 반환하지 않으므로&lt;/b&gt;, 병렬 실행이 필요할 때 async + await() 활용.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;4062&quot; data-start=&quot;4009&quot; data-ke-size=&quot;size23&quot;&gt;✅ &lt;b&gt;4️⃣ await() / awaitAll() 사용하여 병렬 실행 최적화&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4142&quot; data-start=&quot;4063&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4113&quot; data-start=&quot;4063&quot;&gt;응답 DTO 타입이 다르므로 awaitAll() 대신 개별 await() 사용.&lt;/li&gt;
&lt;li data-end=&quot;4142&quot; data-start=&quot;4114&quot;&gt;&lt;b&gt;병렬 실행 최적화 및 코드 안전성 향상&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-is-last-node=&quot;&quot; data-end=&quot;4213&quot; data-start=&quot;4144&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/기타</category>
      <category>Coroutine</category>
      <category>kotlin</category>
      <category>성능개선</category>
      <category>최적화</category>
      <category>코루틴</category>
      <category>코틀린</category>
      <author>다날92</author>
      <guid isPermaLink="true">https://justgo-developer.tistory.com/200</guid>
      <comments>https://justgo-developer.tistory.com/200#entry200comment</comments>
      <pubDate>Wed, 19 Feb 2025 21:54:36 +0900</pubDate>
    </item>
    <item>
      <title>golang ent + sqlite</title>
      <link>https://justgo-developer.tistory.com/199</link>
      <description>&lt;h1&gt;golang ent + sqlite&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ent 프레임워크&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ent는 Facebook에서 개발한 Go 언어용 엔티티 프레임워크입니다. 이 프레임워크는 강력한 ORM(Object-Relational Mapping) 기능을 제공하며, 다음과 같은 특징을 가집니다:&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;/li&gt;
&lt;li&gt;&lt;b&gt;그래프 기반 쿼리:&lt;/b&gt; 복잡한 데이터 관계를 쉽게 탐색할 수 있는 그래프 기반 쿼리를 지원합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스키마 마이그레이션:&lt;/b&gt; 데이터베이스 스키마 변경을 쉽게 관리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성:&lt;/b&gt; 사용자 정의 필드와 엣지를 통해 복잡한 비즈니스 로직을 구현할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터베이스 지원:&lt;/b&gt; SQLite를 포함한 다양한 데이터베이스를 지원합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ent를 사용하면 다음과 같은 이점을 얻을 수 있습니다:&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;/li&gt;
&lt;li&gt;&lt;b&gt;생산성 향상:&lt;/b&gt; 반복적인 CRUD 작업을 자동화하여 개발 시간을 단축할 수 있습니다.&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;ent와 SQLite를 함께 사용하면, Go 애플리케이션에서 경량화된 데이터베이스 작업을 타입 안전하고 효율적으로 수행할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SQLite 데이터베이스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLite는 경량화된 관계형 데이터베이스 관리 시스템(RDBMS)입니다. 다음과 같은 주요 특징을 가지고 있습니다:&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;/li&gt;
&lt;li&gt;&lt;b&gt;자체 완결성:&lt;/b&gt; 전체 데이터베이스가 단일 파일로 저장되어 이식성이 매우 뛰어납니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;제로 구성:&lt;/b&gt; 복잡한 설정이나 관리가 필요 없어 사용이 간편합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;트랜잭션 지원:&lt;/b&gt; ACID(원자성, 일관성, 고립성, 지속성) 속성을 준수하는 트랜잭션을 지원합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;표준 SQL 지원:&lt;/b&gt; SQL 표준의 대부분을 지원하여 사용이 익숙합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLite는 다음과 같은 상황에서 특히 유용합니다:&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; 모바일 앱, IoT 장치 등 리소스가 제한된 환경에 적합합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로컬 저장소:&lt;/b&gt; 데스크톱 애플리케이션이나 브라우저의 로컬 데이터 저장에 활용됩니다.&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;Go 언어에서 SQLite를 사용할 때는 주로 database/sql 패키지와 SQLite 드라이버를 조합하여 사용합니다. ent 프레임워크와 함께 사용하면 타입 안전성과 ORM의 장점을 누리면서도 SQLite의 경량화된 특성을 효과적으로 활용할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SQLite 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.sqlite.org/download.html&quot;&gt;SQLite 다운로드 페이지&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치:&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;sudo wget &amp;lt;https://www.sqlite.org/snapshot/sqlite-snapshot-202410221619.tar.gz&amp;gt;
sudo tar -xvf sqlite-snapshot-202410221619.tar.gz
cd sqlite-snapshot-202106031851
sudo ./configure
sudo make
sudo make install

sqlite3 --version
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Ent 사용법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://entgo.io/docs/getting-started/&quot;&gt;Ent 시작하기 문서&lt;/a&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;스키마 생성: 자동으로 &quot;프로젝트명/ent/schema/&quot; 경로에 스키마명.go 파일이 생성됨.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;go run -mod=mod entgo.io/ent/cmd/ent new 스키마명
&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;기본으로 생성된 Fields 함수에 스키마명.go 파일에 사용하는 항목 추가(lnkg_dts, key, data)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Edges는 Relation을 테이블간 연관관계 의미함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;package schema

import (
    &quot;entgo.io/ent&quot;
    &quot;entgo.io/ent/schema/field&quot;
    &quot;time&quot;
)

// LimiterLog holds the schema definition for the LimiterLog entity.
type LimiterLog struct {
    ent.Schema
}

// Fields of the LimiterLog.
func (LimiterLog) Fields() []ent.Field {
    return []ent.Field{
        field.Time(&quot;lnkg_dts&quot;).Default(time.Now()),
        field.String(&quot;key&quot;),
        field.String(&quot;data&quot;),
    }
}

// Edges of the LimiterLog.
func (LimiterLog) Edges() []ent.Edge {
    return nil
}
&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;go generate 명령어 실행
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CRUD와 같은 기능들을 담당하는 go 파일이 자동으로 생성됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;verilog&quot;&gt;&lt;code&gt;go generate ./ent
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;sqlite3 db로 조회 및 저장 하기&lt;/h2&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;package main

import (
    &quot;context&quot;
    &quot;log&quot;

    &quot;entdemo/ent&quot;

    _ &quot;github.com/mattn/go-sqlite3&quot;
)

func main() {
    client, err := ent.Open(&quot;sqlite3&quot;, &quot;file:ent?mode=memory&amp;amp;cache=shared&amp;amp;_fk=1&quot;)
    if err != nil {
        log.Fatalf(&quot;failed opening connection to sqlite: %v&quot;, err)
    }
    defer client.Close()
    // Run the auto migration tool.
    if err := client.Schema.Create(context.Background()); err != nil {
        log.Fatalf(&quot;failed creating schema resources: %v&quot;, err)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 모드 방식과 파일모드 방식 2가지로 Sqlite를 사용할수 있음.&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;// memory mode
client, err := ent.Open(&quot;sqlite3&quot;, &quot;file:ent?mode=memory&amp;amp;cache=shared&amp;amp;_fk=1&quot;)
// file mode
client, err := ent.Open(&quot;sqlite3&quot;, &quot;file:data.db?_fk=1&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ent를 이용해 CRUD 간편히 할수 있음.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;func CreateLimiterLog(ctx context.Context, client *ent.Client, limiterLog *ent.LimiterLog) (*ent.LimiterLog, error) {
    ll, err := client.LimiterLog.Create().SetLnkgDts(time.Now()).SetKey(limiterLog.Key).SetData(limiterLog.Data).Save(ctx)

    if err != nil {
        return nil, fmt.Errorf(&quot;failed creating limiterLog : %w&quot;, err)
    }
    log.Println(&quot;limiterLog was created: &quot;, ll)
    return ll, nil
}

func SelectLimiterLog(ctx context.Context, client *ent.Client, key string) ([]*ent.LimiterLog, error) {
    ll := client.LimiterLog.Query().Where(limiterlog.Key(key)).AllX(ctx)

    return ll, nil
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>IT/Golang</category>
      <category>Ent</category>
      <category>golang</category>
      <category>sqlite3</category>
      <author>다날92</author>
      <guid isPermaLink="true">https://justgo-developer.tistory.com/199</guid>
      <comments>https://justgo-developer.tistory.com/199#entry199comment</comments>
      <pubDate>Thu, 7 Nov 2024 20:18:09 +0900</pubDate>
    </item>
    <item>
      <title>카프카 리밸런싱으로 인한 Consume 불가 이슈</title>
      <link>https://justgo-developer.tistory.com/198</link>
      <description>&lt;h2&gt;이슈 : Consumer가 전체적으로 대부분의 토픽을 처리하지 못하고 있는 현상.&lt;/h2&gt;
&lt;h3&gt;발생한 로그&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Attempt to heartbeat failed since group is rebalancing    
: 카프카 heartbeat가 실패해서 rebalancing 발생&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;Join group failed with org.apache.kafka.common.errors.MemberIdRequiredException: The group member needs to have a valid member id.   

Join group failed with org.apache.kafka.common.errors.UnknownMemberIdException: The coordinator is not aware of this member.   

: 카프카가 유효한 member id를 찾지 못해서 발생하는 로그 → 결국 rebalancing 이루어짐. &lt;/code&gt;&lt;/pre&gt;&lt;p&gt;카프카(Kafka)에서 리밸런싱(rebalancing)이 발생하는 주요 원인은 파티션과 컨슈머 그룹(consumer group)의 상태 변화에 따른 것입니다.&lt;br&gt;리밸런싱은 파티션을 컨슈머에게 다시 할당하여 데이터 처리의 부하를 균형 있게 유지하는 작업으로, 시스템 안정성을 위해 필수적이지만 자주 발생하면 성능 저하를 유발   &lt;/p&gt;
&lt;p&gt;카프카 rebalancing이 일어나는 케이스는 크게 2가지가 있는데   &lt;/p&gt;
&lt;p&gt;1.제한시간내에 poll 요청 실패 : max.poll.interval.ms&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXMB4p/btsJ6rTMhGa/idZpU17KXQt1l6Kzeg2hUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXMB4p/btsJ6rTMhGa/idZpU17KXQt1l6Kzeg2hUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXMB4p/btsJ6rTMhGa/idZpU17KXQt1l6Kzeg2hUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXMB4p%2FbtsJ6rTMhGa%2FidZpU17KXQt1l6Kzeg2hUk%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;841&quot; height=&quot;522&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;522&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;2.컨슈머 생성 및 삭제 : session.timeout.ms+ heartbeat.timeous.ms&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;381&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pz4jJ/btsJ7c9EaQj/3C8fU4cXpDGS96oKZF5dlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pz4jJ/btsJ7c9EaQj/3C8fU4cXpDGS96oKZF5dlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pz4jJ/btsJ7c9EaQj/3C8fU4cXpDGS96oKZF5dlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpz4jJ%2FbtsJ7c9EaQj%2F3C8fU4cXpDGS96oKZF5dlk%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;767&quot; height=&quot;381&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;381&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;발생한 케이스는 1번 케이스로 poll 호출하는 시간 간격이 5분을 초과하여 발생.&lt;/p&gt;
&lt;p&gt;이슈 발생 시점에 대량 메시지 추정 → db latency 증가됨.&lt;/p&gt;
&lt;p&gt;100 X 1회 호출시간(대략 5초라고 가정) 대략 8분 &amp;gt; 5 min&lt;/p&gt;
&lt;p&gt;&amp;quot;max.poll.records&amp;quot; 100개, max.poll.interval.ms 5분으로 지정&lt;/p&gt;
&lt;h4&gt;해결&lt;/h4&gt;
&lt;p&gt;카프카 관련 2개 설정 수정&lt;/p&gt;
&lt;p&gt;1.max.poll.records: poll 처리 갯수 감소&lt;/p&gt;
&lt;p&gt;2.max.poll.interval.ms : interval 시간 증가&lt;/p&gt;</description>
      <category>IT/kafka</category>
      <category>Kafka</category>
      <category>리밸런싱</category>
      <category>카프카</category>
      <category>컨슈머</category>
      <author>다날92</author>
      <guid isPermaLink="true">https://justgo-developer.tistory.com/198</guid>
      <comments>https://justgo-developer.tistory.com/198#entry198comment</comments>
      <pubDate>Tue, 15 Oct 2024 20:33:31 +0900</pubDate>
    </item>
    <item>
      <title>리눅스 환경에서 Go 설치</title>
      <link>https://justgo-developer.tistory.com/197</link>
      <description>&lt;p&gt;#Go 설치&lt;br&gt;wget &lt;a href=&quot;https://dl.google.com/go/go1.22.5.linux-amd64.tar.gz&quot;&gt;https://dl.google.com/go/go1.22.5.linux-amd64.tar.gz&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;tar -C /usr/local -xzf go1.22.5.linux-amd64.tar.gz&lt;/p&gt;
&lt;p&gt;#설치한 Go 버전 확인&lt;br&gt;cd /usr/local/bin&lt;/p&gt;
&lt;p&gt;./go version&lt;br&gt;-&amp;gt; go version go1.22.5 linux/amd64&lt;/p&gt;
&lt;p&gt;#Go 프로젝트 경로 생성&lt;br&gt;/data01/sw/project/goPATH&lt;/p&gt;
&lt;p&gt;#Go 환경변수 설정&lt;br&gt;vi ~/.bash_profile&lt;/p&gt;
&lt;p&gt;export GOPATH=$HOME/go&lt;br&gt;export PATH=$PATH:$GOPATH/bin&lt;br&gt;export PATH=$PATH:/usr/local/go/bin&lt;/p&gt;
&lt;p&gt;#변경 사항 즉시 반영&lt;br&gt;source ~/.bash_profile&lt;/p&gt;
&lt;p&gt;#Go 확인&lt;br&gt;go version&lt;/p&gt;
&lt;h2&gt;Go 실행 확인&lt;/h2&gt;
&lt;p&gt;#Go 파일 생성&lt;br&gt;vi example.go&lt;/p&gt;
&lt;p&gt;#Go 빌드&lt;br&gt;go build example.go&lt;/p&gt;
&lt;p&gt;#Go 실행&lt;br&gt;go run example.go&lt;/p&gt;
&lt;h2&gt;go: go.mod file not found in current directory or any parent directory; see &amp;#39;go help modules&amp;#39;&lt;/h2&gt;
&lt;p&gt;go env&lt;/p&gt;
&lt;p&gt;&lt;code&gt;go env -w GO111MODULE=auto&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;to this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;go env -w GO111MODULE=off&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://stackoverflow.com/questions/66894200/error-message-go-go-mod-file-not-found-in-current-directory-or-any-parent-dire&quot;&gt;https://stackoverflow.com/questions/66894200/error-message-go-go-mod-file-not-found-in-current-directory-or-any-parent-dire&lt;/a&gt;&lt;/p&gt;</description>
      <category>IT/Golang</category>
      <category>GO</category>
      <category>golang</category>
      <category>go언어</category>
      <category>고설치</category>
      <author>다날92</author>
      <guid isPermaLink="true">https://justgo-developer.tistory.com/197</guid>
      <comments>https://justgo-developer.tistory.com/197#entry197comment</comments>
      <pubDate>Fri, 16 Aug 2024 14:15:56 +0900</pubDate>
    </item>
    <item>
      <title>Spring Cloud Sleuth 적용</title>
      <link>https://justgo-developer.tistory.com/196</link>
      <description>&lt;h1&gt;Spring Cloud Sleuth 적용&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;상세&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sleuth는 RestTemplate, Feign, WebClient와 같은 스프링 진형의 HTTP Client 모듈을 사용하는 경우 Sleuth 의존성을 추가하는 것으로도 설정이 자동적으로 동작하게 됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring boot 2.X 버전에서만 Spring Cloud Sleuth 사용 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring boot 3.X 버전에서는 Micrometer Tracking으로 변경&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;Spring Cloud Sleuth will not work with Spring Boot 3.x onward. The last major version of Spring Boot that Sleuth will support is 2.x.

The core of this project got moved to [Micrometer Tracing](https://micrometer.io/docs/tracing) project and the instrumentations will be moved to [Micrometer](https://micrometer.io/) and all respective projects (no longer all instrumentations will be done in a single repository.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sleuth는 RestTemplate, Feign, WebClient와 같은 스프링 진형의 HTTP Client 모듈을 사용하는 경우 Sleuth 의존성을 추가하는 것으로도 설정이 자동적으로 동작&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
   &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
   &amp;lt;artifactId&amp;gt;spring-cloud-starter-sleuth&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;dependencies {
    implementation(&quot;org.springframework.cloud:spring-cloud-starter-sleuth&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Sleuth는 B3-Propagation 타입을 기본으로 사용하여 서버간 정보를 전파&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기본적으로 Sleuth는 MDC Context에 traceId와 spanId 저장&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;traceId : 하나의 Trace안의 모든 Span이 공유하는 값으로 최초 보낸 http 요청에 대한 ID&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;spanId : 각 서비스별 Id&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kJWwG/btsGee4JbEd/h9ChR7AAUl06HHfBu4ads0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kJWwG/btsGee4JbEd/h9ChR7AAUl06HHfBu4ads0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kJWwG/btsGee4JbEd/h9ChR7AAUl06HHfBu4ads0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkJWwG%2FbtsGee4JbEd%2Fh9ChR7AAUl06HHfBu4ads0%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;701&quot; height=&quot;376&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;376&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2HOve/btsGd1EBH66/j0HpIuA37JNuFZX1VrNQzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2HOve/btsGd1EBH66/j0HpIuA37JNuFZX1VrNQzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2HOve/btsGd1EBH66/j0HpIuA37JNuFZX1VrNQzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2HOve%2FbtsGd1EBH66%2Fj0HpIuA37JNuFZX1VrNQzK%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;691&quot; height=&quot;242&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;X-B3-TraceId vs TraceId의 차이점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/56767956/which-to-log-x-b3-spanid-or-spanid-x-b3-traceid-or-traceid-spring-sleuth&quot;&gt;https://stackoverflow.com/questions/56767956/which-to-log-x-b3-spanid-or-spanid-x-b3-traceid-or-traceid-spring-sleuth&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린 코루틴에서 적용되지 않는 현상&lt;br /&gt;This issue is somewhat mitigated by coroutine's slf4j support, that carries the MDC context into the coroutine:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;GlobalScope.launch(MDCContext()) {
    // your code goes here
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;However, it does not work with the automatic instrumentation of RestTemplates when calling another service. The tracing headers seem not to be set on RestTemplates used within coroutines. Only solution I know of so far uses a special Callable class and is described here: &lt;a href=&quot;https://stackoverflow.com/questions/47026664/spring-sleuth-zipkin-spans-not-nesting-in-kotlin-coroutine&quot;&gt;https://stackoverflow.com/questions/47026664/spring-sleuth-zipkin-spans-not-nesting-in-kotlin-coroutine&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 코틀린 + 코루틴은 작동 불가 : 실제 현재 프로젝트에 로그로는 출력 가능하나 다른 서비스에 traceId, spanId 전달 불가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 기본적으로 Sleuth 는MDC Context 에 traceId 와 spanId 를 저장한다.Sleuth 는 기본적으로 B3-Propagation 을 통해서 서버간 정보를 전파할 수 있다.&lt;/p&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;b&gt;composite 프로젝트에 코틀린 + 코루틴으로 멀티쓰레드로 api 호출하는 로직&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코루틴 내부는 MDC context가 전달되지 않음&lt;/b&gt;&lt;/p&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;b&gt;하지만 이 방법은 실제 현재 프로젝트에 로그로는 출력 가능하나 다른 서비스에 traceId, spanId 전달 불가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr; Feign X-B3-TraceId header에 강제로 넣어서 호출되도록 처리, 최초 시작되는 시점이므로 TraceId만 넣어줘도 무방&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;카프카 consumer 프로젝트 적용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sleuth는 HTTP Client기반으로 Trace를 생성하는 구조다 보니 kafka consumer가 메시지를 읽을때는 생성하지 못함.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr; 공통 aop로 개발하여 메시지 읽을때 traceId, spanId 생성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;284&quot; data-origin-height=&quot;62&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5WIgz/btsGfMe0HO3/caSc0ZhBKDVlK4SRF7gZo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5WIgz/btsGfMe0HO3/caSc0ZhBKDVlK4SRF7gZo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5WIgz/btsGfMe0HO3/caSc0ZhBKDVlK4SRF7gZo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5WIgz%2FbtsGfMe0HO3%2FcaSc0ZhBKDVlK4SRF7gZo0%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;284&quot; height=&quot;62&quot; data-origin-width=&quot;284&quot; data-origin-height=&quot;62&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/paXp9/btsGcswjmkL/LDQ435zKeFVbKb2bwGDxJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/paXp9/btsGcswjmkL/LDQ435zKeFVbKb2bwGDxJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/paXp9/btsGcswjmkL/LDQ435zKeFVbKb2bwGDxJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpaXp9%2FbtsGcswjmkL%2FLDQ435zKeFVbKb2bwGDxJK%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;1211&quot; height=&quot;157&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;157&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;consumer 트랜잭션&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;private final Tracer tracer;
   @Before(&quot;@annotation(com.ssg.api.collect.kafka.common.annotaion.KafkaLogger)&quot;)
   public void printLogging(JoinPoint joinPoint) {
       Span span = tracer.nextSpan().name(&quot;span&quot;).start();

           MDC.put(&quot;traceId&quot;, span.context().traceIdString());
           MDC.put(&quot;spanId&quot;, span.context().spanIdString());
   }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;traceId, spanId를 강제 생성 한후 feign header에 생성&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;public class SsgFeignConfig implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {

        requestTemplate.header(&quot;Accept&quot;, &quot;application/json&quot;);
        requestTemplate.header(&quot;Content-Type&quot;, &quot;application/json;charset=utf-8&quot;);
        requestTemplate.header(&quot;X-B3-TraceId&quot;, MDC.get(&quot;traceId&quot;));

        requestTemplate.header(HttpHeaders.CONNECTION, &quot;keep-alive&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blN9Cf/btsGcMORpHt/pxcmNoGHkpdoQDRcog4bF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blN9Cf/btsGcMORpHt/pxcmNoGHkpdoQDRcog4bF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blN9Cf/btsGcMORpHt/pxcmNoGHkpdoQDRcog4bF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblN9Cf%2FbtsGcMORpHt%2FpxcmNoGHkpdoQDRcog4bF0%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;432&quot; height=&quot;236&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;236&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;참고&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-cloud-sleuth/docs/current-SNAPSHOT/reference/html/&quot;&gt;https://docs.spring.io/spring-cloud-sleuth/docs/current-SNAPSHOT/reference/html/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cloud.spring.io/spring-cloud-sleuth/2.1.x/single/spring-cloud-sleuth.html#_propagation&quot;&gt;https://cloud.spring.io/spring-cloud-sleuth/2.1.x/single/spring-cloud-sleuth.html#_propagation&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Spring Cloud</category>
      <category>Sleuth</category>
      <category>spanId</category>
      <category>Spring Cloud</category>
      <category>spring cloud sletuh</category>
      <category>traceid</category>
      <author>다날92</author>
      <guid isPermaLink="true">https://justgo-developer.tistory.com/196</guid>
      <comments>https://justgo-developer.tistory.com/196#entry196comment</comments>
      <pubDate>Sat, 30 Mar 2024 17:07:08 +0900</pubDate>
    </item>
    <item>
      <title>Bearer Authorization</title>
      <link>https://justgo-developer.tistory.com/195</link>
      <description>&lt;h1&gt;Bearer Authorization&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Token 기반 인증&lt;/li&gt;
&lt;li&gt;Bearer 인증&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업체 쪽 API를 호출하는데 특정 API 401 Authorized 에러가 갑자기 발생하였다.&lt;br /&gt;업체 쪽 환경은 MS Azure를 사용하고 토큰 기반으로 인증을 한다.&lt;br /&gt;토큰 생성 API로 토큰을 생성하고 헤더 Authorization에 토큰 값을 담아 API를 호출하는 구조를 가지고 있다.&lt;br /&gt;원인은 MS Azure 보안 인증이 강화되어 Bearer 인증 기능이 활성화되었다고 한다.&lt;br /&gt;Bearer 인증이 토큰 앞 부분에 &quot;Bearer &quot;만 추가하면 되는데 무엇인지 알아보려고 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&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;1. Token 기반 인증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Token 기반 인증은 Session/Cookie 기반 인증 방식을 보완하기 위해 나온 인증 방식인데&lt;br /&gt;Session/Cookie 기반 인증 방식은 별도의 저장소를 두기 때문에 DB의 부하가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Token 인증 기반 서버에서 특정 API를 사용하려면 Token 발급을 받고&lt;br /&gt;해당 토큰을 HTTP Header Authorization에 보내어&lt;br /&gt;토큰 검증을 통해 이용가능하다.&lt;br /&gt;토큰 검증 실패시 401 Authorized 에러 발생&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/28687900/160305980-790269e8-982c-4ea8-9640-384ebef0a3fd.png&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adfit&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;728x90&lt;/div&gt;
    &lt;ins class=&quot;kakao_ad_area&quot; style=&quot;display: none;&quot; data-ad-unit=&quot;DAN-TimwJ9fwkCtm16gs&quot; data-ad-width=&quot;728px&quot; data-ad-height=&quot;90px&quot;&gt;&lt;/ins&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;//t1.daumcdn.net/kas/static/ba.min.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Bearer 인증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bearer는 토큰 기반 인증에서 토큰 유형을 나타내는 용어.&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;# Authorization 구조 
Authorization : [type] [token]  &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증 타입 종류에는 여러가지가 있는데 그중 Bearer 타입은&lt;br /&gt;Bearer : JWT 혹은 OAuth에 대한 토큰을 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, Beare 인증 기반을 사용할때는 Header Authorization에 Bearer + &quot; &quot; + 토큰를 담아 넘겨주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※&lt;br /&gt;Authorization 타입으로 'Bearer'를 반드시 명시해야 하며, 'Bearer'와 'Token' 사이에 공백(space)을 빠트리지 않도록 주의한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;</description>
      <category>IT/기타</category>
      <category>bearer</category>
      <category>인증</category>
      <author>다날92</author>
      <guid isPermaLink="true">https://justgo-developer.tistory.com/195</guid>
      <comments>https://justgo-developer.tistory.com/195#entry195comment</comments>
      <pubDate>Tue, 10 Oct 2023 18:28:22 +0900</pubDate>
    </item>
    <item>
      <title>Gson 이용 시 Unicode 변환 해결 방법</title>
      <link>https://justgo-developer.tistory.com/194</link>
      <description>&lt;h1&gt;Gson 이용 시 Unicode 변환 해결 방법&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Unicode란?&lt;/li&gt;
&lt;li&gt;Gson 사용시 unicode 문제&lt;/li&gt;
&lt;li&gt;Gson 사용시 unicode 해결방법&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 처리 중 암호화된 데이터 값이 달라지는 걸 발견하였는데,&lt;br /&gt;알고보니 객체를 json으로 변환 중 문자가 unicode로 변환되어 나오는 것을 확인하였고,&lt;br /&gt;해결방법에 대해서 알아보려고 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&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;1. unicode란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Unicode 정의&lt;/code&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니코드(영어: Unicode)는 전 세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 산업 표준이다. 유니코드는 유니코드 협회(Unicode Consortium)가 제정한다. 또한 이 표준에는 ISO 10646 문자 집합, 문자 인코딩, 문자 정보 데이터베이스, 문자들을 다루기 위한 알고리즘 등을 포함하고 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Gson 사용시 unicode 문제&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;    @Test
    public void gsonUnicodeTest() {
        Map&amp;lt;String, String&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
        map.put(&quot;unicode&quot;, &quot;7456156==&quot;);
        System.out.println(JsonUtils.toJson(map));
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{&quot;unicode&quot;:&quot;7456156\u003d\u003d&quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; Gson을 이용하여 Json 변환 시, 문자 '='가 '\u003d' 유니코드로 변환된다.&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adfit&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;728x90&lt;/div&gt;
    &lt;ins class=&quot;kakao_ad_area&quot; style=&quot;display: none;&quot; data-ad-unit=&quot;DAN-TimwJ9fwkCtm16gs&quot; data-ad-width=&quot;728px&quot; data-ad-height=&quot;90px&quot;&gt;&lt;/ins&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;//t1.daumcdn.net/kas/static/ba.min.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Gson 사용시 unicode 해결방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;기존 : 일반적인 Gson 객체 생성&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;    public static &amp;lt;T&amp;gt; String toJson(T obj) {
        Gson gson = new Gson();
        return gson.toJson(obj);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;변경 : GsonBuilder().disableHtmlEscaping().create() 사용하여 Gson 객체 생성&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;    public static &amp;lt;T&amp;gt; String toJsonWithoutUnicode(T obj) {
        Gson gson = new GsonBuilder().disableHtmlEscaping().create();
        return gson.toJson(obj);
    }&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;GsonBuilder().disableHtmlEscaping().create() 사용하여 HTML Escape 비활성화하면 unicode로 변환하지 않고 정상처리 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{&quot;unicode&quot;:&quot;7456156==&quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.javadoc.io/doc/com.google.code.gson/gson/2.8.5/com/google/gson/GsonBuilder.html#disableHtmlEscaping--&quot;&gt;https://www.javadoc.io/doc/com.google.code.gson/gson/2.8.5/com/google/gson/GsonBuilder.html#disableHtmlEscaping--&lt;/a&gt;&lt;/p&gt;</description>
      <category>IT/기타</category>
      <category>Gson</category>
      <category>unicode</category>
      <author>다날92</author>
      <guid isPermaLink="true">https://justgo-developer.tistory.com/194</guid>
      <comments>https://justgo-developer.tistory.com/194#entry194comment</comments>
      <pubDate>Tue, 10 Oct 2023 18:27:10 +0900</pubDate>
    </item>
    <item>
      <title>Json File Download(HttpURLConnection, FeignClient)</title>
      <link>https://justgo-developer.tistory.com/193</link>
      <description>&lt;h1&gt;Json File Download(HttpURLConnection, FeignClient)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;HttpURLConnection 이용한 파일 다운로드&lt;/li&gt;
&lt;li&gt;FeignClient 이용한 파일 다운로드&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&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;1. HttpURLConnection 이용한 파일 다운로드&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InputStream : 파일 데이터를 읽거나 네트워크 소켓을 통해 데이터를 읽거나 키보드에서 입력한 데이터를 읽을 때 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;read(byte[] b): 배열 b의 크기만큼 데이터를 읽어와서 b에 저장한다.&lt;/li&gt;
&lt;li&gt;read(byte[] b, int off, int len) : len의 크기만큼 데이터를 읽어와서 배열 b의 off 위치부터 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FileOutputStream : 데이터를 파일에 바이트 스트림으로 저장하기 위해 사용한다.
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;               주어진 이름의 파일을 쓰기 위한 객체를 생성하여 파일 생성됨  &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;write(byte[] b) : 배열 b에 저장된 모든 내용을 출력소스에 쓴다&lt;/li&gt;
&lt;li&gt;write(byte[] b, int off, int len) : 배열 b에 저장된 내용을 off 위치부터 len개 만큼 출력소스에 쓴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ HttpURLConnection의 getInputStream() 메소드를 통해 응답 데이터를 읽을 수 있는 InputStream 객체를 얻을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ InputStream read시 더 이상 읽은 바이트가 없으면 -1 리턴&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;  public void downloadFile(String requestUrl) {
        String outputDir = &quot;C:/Code/file&quot;;
        InputStream is = null;
        FileOutputStream os = null;
        try{
            URL url = new URL(requestUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            int responseCode = conn.getResponseCode();

            if (responseCode == HttpURLConnection.HTTP_OK) {
                String fileName = &quot;downloadTest&quot; + System.currentTimeMillis() + &quot;.json&quot;;
                System.out.println(&quot;fileName = &quot; + fileName);
                File file = new File(outputDir);

                if(!file.exists()) file.mkdirs();

                is = conn.getInputStream();
                os = new FileOutputStream(file + &quot;/&quot; + fileName);

                int bytesRead;
                byte[] buffer = new byte[1024];
                while ((bytesRead = is.read(buffer)) != -1) {
                    // 입력받은 내용을 파일 내용으로 기록한다.
                    os.write(buffer, 0, bytesRead);
                }

            } else {
                System.out.println(&quot;fail =&amp;gt; responseCode : &quot; + responseCode);
            }
            conn.disconnect();
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            try {
                if (is != null){
                    is.close();
                }
                if (os != null){
                    os.close();
                }
            } catch (IOException e){
                e.printStackTrace();
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adfit&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;728x90&lt;/div&gt;
    &lt;ins class=&quot;kakao_ad_area&quot; style=&quot;display: none;&quot; data-ad-unit=&quot;DAN-TimwJ9fwkCtm16gs&quot; data-ad-width=&quot;728px&quot; data-ad-height=&quot;90px&quot;&gt;&lt;/ins&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;//t1.daumcdn.net/kas/static/ba.min.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. FeignClient 이용한 파일 다운로드&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;FeignClient 호출시 리턴 타입을 feign에서 제공해주는 Reponse 객체로 받는다.&lt;/li&gt;
&lt;li&gt;Feign.Reponse.Body 객체 안에 있는 asInputStream 메소드를 통해 InputStream 객체를 얻을수 있음.&lt;/li&gt;
&lt;li&gt;그 이후 파일 다운로드 방법은 위의 HttpURLConnection 사용 시 예제와 유사&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@FeignClient(name = &quot;download-api&quot;,
        url = &quot;https://raw.githubusercontent.com/people92/self-study/main/java-study/src/main/resources/json&quot;
)
public interface DownloadFeignClient {

    @GetMapping(value = &quot;/sample.json&quot;)
    Response downloadJsonFile();
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;    @Test
    public void downloadJsonFileTest() throws IOException {

        String outputDir = &quot;C:/Code/file&quot;;
        String fileName = &quot;downloadTest&quot; + System.currentTimeMillis() + &quot;.json&quot;;

        Response response = downloadFeignClient.downloadJsonFile();
        Response.Body body = response.body();
        InputStream inputStream = body.asInputStream();

        if(response.status() == 200) {
            FileOutputStream os = new FileOutputStream(outputDir + &quot;/&quot; + fileName);
            int bytesRead;
            byte[] buffer = new byte[1024];
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                // 입력받은 내용을 파일 내용으로 기록한다.
                os.write(buffer, 0, bytesRead);
            }
            inputStream.close();
            os.close();
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;</description>
      <category>IT/자바</category>
      <category>feign</category>
      <category>json file</category>
      <category>파일 다운</category>
      <author>다날92</author>
      <guid isPermaLink="true">https://justgo-developer.tistory.com/193</guid>
      <comments>https://justgo-developer.tistory.com/193#entry193comment</comments>
      <pubDate>Tue, 10 Oct 2023 18:26:22 +0900</pubDate>
    </item>
    <item>
      <title>추상 팩토리 패턴(Abstract Factory Pattern)를 이용한 인터페이스 중복 메소드 제거</title>
      <link>https://justgo-developer.tistory.com/192</link>
      <description>&lt;h1&gt;추상 팩토리 패턴(Abstract Factory Pattern)를 이용한 인터페이스 중복 메소드 제거&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;추상 팩토리 패턴(Abstract Factory Pattern) 정의&lt;/li&gt;
&lt;li&gt;추상 팩토리 패턴을 이용한 설계&lt;/li&gt;
&lt;li&gt;추상 팩토리 패턴을 이용한 예제&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 개발했던 연동 서비스에 새로운 업체가 추가되어야 한다.&lt;br /&gt;interface는 똑같지만 일부 메소드는 거의 동일하고 일부 메소드는 다르게 구현하여야 할 것 같다.&lt;br /&gt;그래서 추상 팩토리 패턴을 이용해 공통코드를 추상클래스로 뽑아내려고 한다.&lt;br /&gt;그 개념에 대해서 알아 보려고 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&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;1. 추상 팩토리 패턴(Abstract Factory Pattern) 정의&lt;/h3&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;p data-ke-size=&quot;size16&quot;&gt;내가 이해한대로 쉽게 말하자면,&lt;br /&gt;공통 메소드를 뽑아내 abstract 클래스에 뽑아낸다.&lt;br /&gt;abstract 클래스는 인터페이스를 구현하고,&lt;br /&gt;나머지 로직이 다른 메소드는 생성한 absract 클래스를 상속(extends)해 구현하여야 함.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 추상 팩토리 패턴을 이용한 설계&lt;/h3&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;동일한 메소드는 중복을 제거해야함 : 중복된 메소드들을 abstract 클래스로 뽑아냄&lt;/li&gt;
&lt;li&gt;abstract 클래스는 interface를 implements함&lt;/li&gt;
&lt;li&gt;각 클래스들은 동일한 메소드 뽑아낸 abstract class를 extends(상속)하여 나머지 메소드들을 구현&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;factory -&amp;gt; interface -&amp;gt; abstract class -&amp;gt; concrete class&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/28687900/133371532-296eb267-390c-4d81-be8c-81d1d77f7e53.png&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adfit&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;728x90&lt;/div&gt;
    &lt;ins class=&quot;kakao_ad_area&quot; style=&quot;display: none;&quot; data-ad-unit=&quot;DAN-TimwJ9fwkCtm16gs&quot; data-ad-width=&quot;728px&quot; data-ad-height=&quot;90px&quot;&gt;&lt;/ins&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;//t1.daumcdn.net/kas/static/ba.min.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 추상 팩토리 패턴을 이용한 예제&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. interface 메소드 정의&lt;/h4&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public interface StockService {

    StockGroup getStockGroup();

    String findKoreanName();

    String findCommonType(); //공통으로 사용 될 메소드
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 인터페이스 구현하는 추상클래스 생성 &amp;gt; 공통으로 사용되는 메소드 abstract class에 구현&lt;/h4&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
abstract class StockAbstractFactory implements StockService{

    @Override
    public String findCommonType() {
        return &quot;STOCK&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 추상클래스 상속받는 클래스 생성 &amp;gt; 나머지 메소드 concrete class에 구현&lt;/h4&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;@Service
public class AirbnbServiceImpl extends StockAbstractFactory {
    @Override
    public StockGroup getStockGroup() {
        return StockGroup.AIRBNB;
    }

    @Override
    public String findKoreanName() {
        return StockGroup.AIRBNB.getKoreanName();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. Test 코드&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;    @Test
    public void findCommonTypeTest() {
        String appleService = stockServiceFactory.getSerivce(APPLE).findCommonType();
        String airbnbService = stockServiceFactory.getSerivce(AIRBNB).findCommonType();
        String samsungService = stockServiceFactory.getSerivce(SAMSUNG).findCommonType();
        String naverService = stockServiceFactory.getSerivce(NAVER).findCommonType();

        Assertions.assertEquals(appleService, &quot;STOCK&quot;);
        Assertions.assertEquals(airbnbService, &quot;STOCK&quot;);
        Assertions.assertEquals(samsungService, &quot;STOCK&quot;);
        Assertions.assertEquals(naverService, &quot;STOCK&quot;);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;</description>
      <category>IT/자바</category>
      <category>디자인 패턴</category>
      <category>추상 팩토리 패턴</category>
      <author>다날92</author>
      <guid isPermaLink="true">https://justgo-developer.tistory.com/192</guid>
      <comments>https://justgo-developer.tistory.com/192#entry192comment</comments>
      <pubDate>Tue, 10 Oct 2023 18:25:19 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Spring Bean 주입 + 팩토리 메소드 디자인 패턴</title>
      <link>https://justgo-developer.tistory.com/191</link>
      <description>&lt;h1&gt;Spring Bean Injection + Factory Method Design Pattern&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&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;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&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;1. 팩토리 메소드 디자인 패턴 정의&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;팩토리 메소드 디자인 패턴(Factory Method Design Pattern)&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체를 생성하기 위한 인터페이스를 정의하고, 어떤 클래스의 인스턴스를 생성할지에 대한 처리는 서브클래스가 결정하는 디자인 패턴 - GoF&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;팩토리 메소드 패턴 사용 이유&lt;/h4&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;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adfit&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;728x90&lt;/div&gt;
    &lt;ins class=&quot;kakao_ad_area&quot; style=&quot;display: none;&quot; data-ad-unit=&quot;DAN-TimwJ9fwkCtm16gs&quot; data-ad-width=&quot;728px&quot; data-ad-height=&quot;90px&quot;&gt;&lt;/ins&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;//t1.daumcdn.net/kas/static/ba.min.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 팩토리 메소드 디자인 패턴을 이용해 동적으로 빈 주입 예제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 인터페이스에 여러개의 구현체가 있을 경우,&lt;br /&gt;매번 구현클래스를 다르게 선언하지 않고, 팩토리 메소드 디자인 패턴을 이용하여 빈 객체를 동적으로 가져와 사용할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring에서는 Bean을 Collection으로 주입할(Injection) 수 있다.&lt;br /&gt;다시 말해, 하나의 인터페이스를 상속받고 있는 구현체들인 bean들을 List로 주입할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 코드를 작성해보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 주식 회사의 한국 이름을 가져오는 StockService Interface가 있다.&lt;br /&gt;getStockGroup 메소드는 구현된 클래스를 구별하기 위한 return이 enum 클래시인 메소드이다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface StockService {

    StockGroup getStockGroup();

    String findKoreanName();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주식 회사의 종류는 애플, 네이버, 에어비앤비, 삼성 4가지로 Enum 타입을 이용해 작성하였다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public enum StockGroup {

    APPLE(&quot;애플&quot;,&quot;1001&quot;),
    NAVER(&quot;네이버&quot;,&quot;1002&quot;),
    AIRBNB(&quot;에어비앤비&quot;,&quot;1003&quot;),
    SAMSUNG(&quot;삼성&quot;,&quot;1004&quot;),
    EMPTY(&quot;없음&quot;, &quot;0000&quot;);

    private String koreanName;
    private String stockId;

    StockGroup(String koreanName, String stockId){
        this.koreanName = koreanName;
        this.stockId = stockId;
    }

    public static StockGroup findByStockId(String stockId) {
        return Arrays.stream(StockGroup.values())
                .filter(stockGroup -&amp;gt; stockId.equals(stockGroup.stockId))
                .findAny().orElse(EMPTY);
    }

    public String getKoreanName() {
        return koreanName;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StockService를 구현하는 클래스는 회사별로 총 4가지 클래스로 구현되어있다.&lt;br /&gt;각 클래스마다 StockGroup Enum 클래스를 리턴해주는 메소드, 한글 이름을 리턴해주는 메소드로 작성하였다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class AirbnbServiceImpl implements StockService{
    @Override
    public StockGroup getStockGroup() {
        return StockGroup.AIRBNB;
    }

    @Override
    public String findKoreanName() {
        return StockGroup.AIRBNB.getKoreanName();
    }
}
@Service
public class AppleServiceImpl implements StockService{
    @Override
    public StockGroup getStockGroup() {
        return StockGroup.APPLE;
    }

    @Override
    public String findKoreanName() {
        return StockGroup.APPLE.getKoreanName();
    }
}
@Service
public class NaverServiceImpl implements StockService{
    @Override
    public StockGroup getStockGroup() {
        return StockGroup.NAVER;
    }

    @Override
    public String findKoreanName() {
        return StockGroup.NAVER.getKoreanName();
    }
}
@Service
public class SamsungServiceImpl implements StockService{
    @Override
    public StockGroup getStockGroup() {
        return StockGroup.SAMSUNG;
    }

    @Override
    public String findKoreanName() {
        return StockGroup.SAMSUNG.getKoreanName();
    }
}
&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;실제 구현하는 클래스들의 빈들을 Map에 담아 주입한다.&lt;br /&gt;이때 Map의 Key는 위에서 선언한 StockGroup의 Enum 클래스이고, Value는 StockService 인터페이스이다.&lt;br /&gt;Map에 인터페이스를 구현하는 클래스들을 모두 담고, getService라는 메소드를 이용해 키값 별로 원하는 클래스들에 접근할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Component
public class StockServiceFactory {

    private final Map&amp;lt;StockGroup, StockService&amp;gt; serviceMap = new HashMap&amp;lt;&amp;gt;();

    public StockServiceFactory(List&amp;lt;StockService&amp;gt; stockServices) {

        for(StockService stockService : stockServices) {
            this.serviceMap.put(stockService.getStockGroup(), stockService);
        }
    }

    public StockService getSerivce(StockGroup stockGroup) {
        return this.serviceMap.get(stockGroup);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팩토리 메서드 패턴을 이용하기 위해 생성한 -Factory.class를 주입받아서 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Test 코드&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@SpringBootTest
public class FactoryPatternTest {

    @Autowired
    StockServiceFactory stockServiceFactory;

    @Test
    public void beanInjectionTest() {
        StockService appleService = stockServiceFactory.getSerivce(APPLE);
        StockService airbnbService = stockServiceFactory.getSerivce(AIRBNB);
        StockService samsungService = stockServiceFactory.getSerivce(SAMSUNG);
        StockService naverService = stockServiceFactory.getSerivce(NAVER);

        Assertions.assertEquals(appleService.getStockGroup(), APPLE);
        Assertions.assertEquals(airbnbService.getStockGroup(), AIRBNB);
        Assertions.assertEquals(samsungService.getStockGroup(), SAMSUNG);
        Assertions.assertEquals(naverService.getStockGroup(), NAVER);
    }
    @Test
    public void getKoreanNameTest() {
        String appleKoreanName = stockServiceFactory.getSerivce(APPLE).findKoreanName();
        String airbnbKoreanName = stockServiceFactory.getSerivce(AIRBNB).findKoreanName();
        String samsungKoreanName = stockServiceFactory.getSerivce(SAMSUNG).findKoreanName();
        String naverKoreanName = stockServiceFactory.getSerivce(NAVER).findKoreanName();

        Assertions.assertEquals(appleKoreanName, APPLE.getKoreanName());
        Assertions.assertEquals(airbnbKoreanName, AIRBNB.getKoreanName());
        Assertions.assertEquals(samsungKoreanName, SAMSUNG.getKoreanName());
        Assertions.assertEquals(naverKoreanName, NAVER.getKoreanName());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 소스 : &lt;a href=&quot;https://github.com/people92/design-pattern&quot;&gt;https://github.com/people92/design-pattern&lt;/a&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;</description>
      <category>IT/Spring-boot</category>
      <category>Spring</category>
      <category>디자인 패턴</category>
      <category>스프링</category>
      <category>팩토리 메소드</category>
      <author>다날92</author>
      <guid isPermaLink="true">https://justgo-developer.tistory.com/191</guid>
      <comments>https://justgo-developer.tistory.com/191#entry191comment</comments>
      <pubDate>Tue, 10 Oct 2023 18:23:33 +0900</pubDate>
    </item>
  </channel>
</rss>