<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>지잉지잉 개발일기</title>
    <link>https://developer-jiing.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 12 Jul 2026 00:40:04 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>지잉지잉</managingEditor>
    <image>
      <title>지잉지잉 개발일기</title>
      <url>https://tistory1.daumcdn.net/tistory/4911342/attach/7873ab8efbcd4f78950897fc992a4833</url>
      <link>https://developer-jiing.tistory.com</link>
    </image>
    <item>
      <title>[OpenSearch] Spring - Kotlin 기반의 OpenSearch Client 구현</title>
      <link>https://developer-jiing.tistory.com/58</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ElasticSearch 에 비해 AWS OpenSearch는 JAVA API Client 문서가 불친절한 느낌이다.&lt;/li&gt;
&lt;li&gt;이것 저것 해보면서 겪었던 시행 착오들을 기록하고,&lt;/li&gt;
&lt;li&gt;SpringBoot - Kotlin 기반으로 AWS OpenSearch Instance에 접근하고 색인/검색 요청을 하는 JAVA API Client 설정 및 구현 방법을 정리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Dependency&lt;/h2&gt;
&lt;pre id=&quot;code_1699019041639&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
	...
	implementation(&quot;org.opensearch.client:opensearch-rest-client:2.11.0&quot;)
	implementation(&quot;org.opensearch.client:opensearch-java:2.7.0&quot;)
	implementation(&quot;jakarta.json:jakarta.json-api:2.0.1&quot;)
    	...
 }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;jakarta.json-api 의존성을 추가해주지 않으면,&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;java.lang.NoClassDefFoundError: jakarta/json/JsonException 에러가 난다.&lt;/li&gt;
&lt;li&gt;= ES에서 가져온 JSON 기반의 객체를 파싱하는데 실패하는 것 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. OpenSearchClient&lt;/h2&gt;
&lt;pre id=&quot;code_1699019718443&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.apache.http.HttpHost
import org.apache.http.auth.AuthScope
import org.apache.http.auth.UsernamePasswordCredentials
import org.apache.http.impl.client.BasicCredentialsProvider
import org.opensearch.client.RestClient
import org.opensearch.client.json.jackson.JacksonJsonpMapper
import org.opensearch.client.opensearch.OpenSearchClient
import org.opensearch.client.transport.rest_client.RestClientTransport
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class OpenSearchConfiguration {

    @Bean
    fun openSearchClient(): OpenSearchClient {
        val host = HttpHost(&quot;127.0.0.1&quot;, 9200, &quot;https&quot;) // OpenSearch Domain
        val credential = BasicCredentialsProvider().apply {
            this.setCredentials(
                AuthScope(host),
                UsernamePasswordCredentials(&quot;admin&quot;, &quot;admin&quot;) // OpenSearch Username &amp;amp; Password
            )
        }

        val restClient = RestClient.builder(host).setHttpClientConfigCallback {
            it.setDefaultCredentialsProvider(credential)
        }.build()

        return OpenSearchClient(RestClientTransport(restClient, JacksonJsonpMapper(jacksonObjectMapper())))
    }
}&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;Webflux를 사용한다면 OpenSearchAsyncClient로 생성하여 비동기로 OpenSearch 호출이 가능하다.&lt;/li&gt;
&lt;li&gt;JacksonJsonpMapper도 jacksonObjectMapper로 만들어주자. (안하면 parsing Exception 발생)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Service Example&lt;/h2&gt;
&lt;pre id=&quot;code_1699020697335&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.opensearch.client.opensearch.OpenSearchClient
import org.opensearch.client.opensearch.core.BulkRequest
import org.opensearch.client.opensearch.core.SearchRequest
import org.opensearch.client.opensearch.core.bulk.CreateOperation
import org.opensearch.client.opensearch.indices.CreateIndexRequest
import org.springframework.stereotype.Service

@Service
class OpenSearchService(
    private val openSearchClient: OpenSearchClient
) {

    /**
     * 인덱스 생성
      */
    fun createIndex() {
        try {
            val indexName = &quot;test-index&quot;

            val request = CreateIndexRequest.of {
                it.index(indexName)
            }

            openSearchClient.indices().create(request)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * bulk index(색인)
     */
    fun bulk() {
        val indexName = &quot;test-index&quot;
        val dummyData = listOf(
            TestIndex(
                id = &quot;test1&quot;,
                name = &quot;test1&quot;,
                age = 20
            ),
            TestIndex(
                id = &quot;test2&quot;,
                name = &quot;test2&quot;,
                age = 30
            )
        )
        try {
            val bulkRequest = BulkRequest.Builder()

            dummyData.forEach {  testIndex -&amp;gt;
                bulkRequest.operations { operation -&amp;gt;
                    operation.create { co: CreateOperation.Builder&amp;lt;TestIndex&amp;gt; -&amp;gt;
                        co.index(indexName).id(testIndex.id).document(testIndex)
                    }
                }
            }

            openSearchClient.bulk(bulkRequest.build())
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * 검색
     */
    fun search(keyword: String): List&amp;lt;TestIndex&amp;gt; {
        val indexName = &quot;test-index&quot;
        return try {
            val request = SearchRequest.of { searchRequest -&amp;gt;
                searchRequest.index(indexName)
                searchRequest.query { query -&amp;gt;
                    query.match { matchQuery -&amp;gt;
                        matchQuery.field(&quot;name&quot;)
                        matchQuery.query {
                            it.stringValue(keyword)
                        }
                    }
                }
            }

            openSearchClient.search(request, TestIndex::class.java).hits().hits().mapNotNull {
                it.source()
            }
        } catch (e: Exception) {
            e.printStackTrace()
            emptyList()
        }
    }

    data class TestIndex(
        val id: String,
        val name: String,
        val age: Int,
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인덱스 생성, 색인, 검색 예제&lt;/li&gt;
&lt;li&gt;간단하게 request 객체를 만들어 요청할 수 있다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발/그 외</category>
      <category>aws opensearch</category>
      <category>Elasticsearch</category>
      <category>ES</category>
      <category>kotlin</category>
      <category>OpenSearch</category>
      <category>opensearch client</category>
      <category>springboot</category>
      <author>지잉지잉</author>
      <guid isPermaLink="true">https://developer-jiing.tistory.com/58</guid>
      <comments>https://developer-jiing.tistory.com/58#entry58comment</comments>
      <pubDate>Fri, 3 Nov 2023 23:13:14 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] RestDocs로 API 문서화를 간단히 해보자</title>
      <link>https://developer-jiing.tistory.com/57</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. build.gradle.kts 설정&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추가해야 할 부분만 표시해놓았습니다~&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
   ...
   id(&quot;com.epages.restdocs-api-spec&quot;) version &quot;0.16.0&quot;
   ...
}

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

java {
   sourceCompatibility = JavaVersion.VERSION_17
}

repositories {
   mavenCentral()
}

dependencies {
   ...
   testImplementation(&quot;com.epages:restdocs-api-spec-mockmvc:0.16.2&quot;)
}

openapi3 {
   this.setServer(&quot;https://localhost:8080&quot;) 
   title = &quot;openapi3 test&quot;
   description = &quot;테스트 입니당&quot;
   version = &quot;0.1.0&quot;
   format = &quot;yaml&quot; // or json
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. API 작성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간단한 테스트용 API를 작성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;@RestController
class TestController {

    @GetMapping(&quot;/test&quot;)
    fun getTest(@RequestParam name: String, @RequestParam age: Int) = ResponseEntity&amp;lt;TestResponse&amp;gt;(
        TestResponse(
            200, &quot;Hello $name ($age).&quot;
        ),
        HttpStatus.OK
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Test Code 작성&lt;/h2&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper
import com.epages.restdocs.apispec.ResourceDocumentation.parameterWithName
import com.epages.restdocs.apispec.ResourceSnippetParametersBuilder
import com.epages.restdocs.apispec.SimpleType
import org.junit.jupiter.api.BeforeEach

import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.restdocs.RestDocumentationContextProvider
import org.springframework.restdocs.RestDocumentationExtension
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get
import org.springframework.restdocs.payload.JsonFieldType
import org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import org.springframework.web.context.WebApplicationContext

@ExtendWith(RestDocumentationExtension::class, SpringExtension::class)
@SpringBootTest
internal class TestControllerTest {

    private lateinit var mockMvc: MockMvc

    @BeforeEach
    fun setUp(context: WebApplicationContext, restDocumentation: RestDocumentationContextProvider) {
        mockMvc = MockMvcBuilders.webAppContextSetup(context)
            .apply&amp;lt;DefaultMockMvcBuilder&amp;gt;(MockMvcRestDocumentation.documentationConfiguration(restDocumentation))
            .build()
    }

    @Test
    fun `스웨거 테스트`() {
        val name = &quot;mark&quot;
        val age = 20
        mockMvc.perform(
            get(&quot;/test?name={name}&amp;amp;age={age}&quot;, name, age)
        )
            .andExpect(status().isOk)
            .andDo(
                MockMvcRestDocumentationWrapper.document(
                    identifier = &quot;test&quot;,
                    resourceDetails = ResourceSnippetParametersBuilder()
                        .tag(&quot;테스트&quot;)
                        .description(&quot;테스트 입니당&quot;)
                        .requestParameters(
                            parameterWithName(&quot;name&quot;).description(&quot;이름&quot;).type(SimpleType.STRING),
                            parameterWithName(&quot;age&quot;).description(&quot;나이&quot;).type(SimpleType.NUMBER),
                        )
                        .responseFields(
                            fieldWithPath(&quot;status&quot;).type(JsonFieldType.NUMBER).description(&quot;The status.&quot;),
                            fieldWithPath(&quot;value&quot;).type(JsonFieldType.STRING).description(&quot;The value.&quot;),
                        ),
                ),
            )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. run openapi3&amp;nbsp;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트코드를 작성한 후, openapi3 를 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;187&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p1SAg/btskhCGlqOx/hlYIUC5iyYXB3KVGdHoRxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p1SAg/btskhCGlqOx/hlYIUC5iyYXB3KVGdHoRxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p1SAg/btskhCGlqOx/hlYIUC5iyYXB3KVGdHoRxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp1SAg%2FbtskhCGlqOx%2FhlYIUC5iyYXB3KVGdHoRxk%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;187&quot; height=&quot;309&quot; data-origin-width=&quot;187&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. openapi3.yaml 확인&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNphG3/btskigXmq7j/R3BbHRUKxWTPCpoTIrBkiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNphG3/btskigXmq7j/R3BbHRUKxWTPCpoTIrBkiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNphG3/btskigXmq7j/R3BbHRUKxWTPCpoTIrBkiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNphG3%2FbtskigXmq7j%2FR3BbHRUKxWTPCpoTIrBkiK%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;1184&quot; height=&quot;668&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 yaml을 swagger editor(&lt;a href=&quot;https://editor.swagger.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://editor.swagger.io/&lt;/a&gt;)에 붙여넣으면 정상적으로 문서화가 된 것을 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1881&quot; data-origin-height=&quot;909&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YhDNU/btskllX50k8/W05ltxwLeMANMWSkWbBZb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YhDNU/btskllX50k8/W05ltxwLeMANMWSkWbBZb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YhDNU/btskllX50k8/W05ltxwLeMANMWSkWbBZb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYhDNU%2FbtskllX50k8%2FW05ltxwLeMANMWSkWbBZb0%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;1881&quot; height=&quot;909&quot; data-origin-width=&quot;1881&quot; data-origin-height=&quot;909&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아직 테스트 중이라 수정해야할 게 많습니다. 감안해서 확인 부탁드립니다~&lt;/h2&gt;</description>
      <category>개발/SPRING</category>
      <category>API</category>
      <category>api 문서화</category>
      <category>kotlin</category>
      <category>openapi3</category>
      <category>Restdocs</category>
      <category>Spring</category>
      <category>Swagger</category>
      <category>문서화</category>
      <author>지잉지잉</author>
      <guid isPermaLink="true">https://developer-jiing.tistory.com/57</guid>
      <comments>https://developer-jiing.tistory.com/57#entry57comment</comments>
      <pubDate>Sun, 18 Jun 2023 21:58:35 +0900</pubDate>
    </item>
    <item>
      <title>동대문 임장 내용 정리 (feat. 전농동, 답십리, 장안동)</title>
      <link>https://developer-jiing.tistory.com/56</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;2023-05-29 12시 ~ 15시 (조금 흐렸다가, 쨍쨍해진 날씨)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;돌아다닌 단지는 아래 지도에 표시된 전농동, 답십리 일대와 장안동은 현대홈타운 1차 아파트만 방문했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;613&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MBJTP/btshAZLfkzo/dyRyT0HMaYYUDSEU9Am9z1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MBJTP/btshAZLfkzo/dyRyT0HMaYYUDSEU9Am9z1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MBJTP/btshAZLfkzo/dyRyT0HMaYYUDSEU9Am9z1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMBJTP%2FbtshAZLfkzo%2FdyRyT0HMaYYUDSEU9Am9z1%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;818&quot; height=&quot;613&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;613&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목록
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;전농SK1차&lt;/li&gt;
&lt;li&gt;전농우성&lt;/li&gt;
&lt;li&gt;전농래미안엘파인&lt;/li&gt;
&lt;li&gt;답십리대림&lt;/li&gt;
&lt;li&gt;동답한신&lt;/li&gt;
&lt;li&gt;답십리동아&lt;/li&gt;
&lt;li&gt;답십리두산1단지&lt;/li&gt;
&lt;li&gt;답십리파크자이&lt;/li&gt;
&lt;li&gt;답십리청솔우성&lt;/li&gt;
&lt;li&gt;장안현대홈타운1차&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;단지 목록&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 전농SK1차&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 40px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 20px;&quot;&gt;단지&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 20px;&quot;&gt;평형&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 20px;&quot;&gt;가격대&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 20px;&quot;&gt;교통&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 20px;&quot;&gt;인프라&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 20px;&quot;&gt;전농SK1차&lt;br /&gt;&lt;br /&gt;1,830세대&lt;br /&gt;2000-07-06&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 20px;&quot;&gt;24A&lt;br /&gt;24B&lt;br /&gt;33A&lt;br /&gt;33B&lt;br /&gt;42&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 20px;&quot;&gt;6.1 ~ 6.3억&lt;br /&gt;2023-04, 24A평형 기준&lt;br /&gt;전세가율 52~54%&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 20px;&quot;&gt;청량리역 도보 30분&lt;br /&gt;답십리역 도보 28분&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 20px;&quot;&gt;단지 앞 학원, 마트 등 많은 상권 존재&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주차하기 편리해보임&lt;/li&gt;
&lt;li&gt;단지는 깨끗하지만 내부 엘리베이터나 현관문 등 컨디션은 좋지 않은듯..&lt;/li&gt;
&lt;li&gt;상가에 이것저것 많아서 단지내에서 해결할 수 있는게 많은 것 같음.&lt;/li&gt;
&lt;li&gt;유동인구가 많은 편은 아니고, 특히 아이들이 많이 보이지 않았다..!&lt;/li&gt;
&lt;li&gt;근처 지하철역이 도보로 꽤 오래 걸리기 때문에 (28~30분), 근처 버스나 자차를 이용해야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure data-ke-type=&quot;image&quot; data-ke-style=&quot;alignCenter&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cauS3M/btshWtDxwsu/eiM2iO279Ajo8beWkVnRc0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cauS3M/btshWtDxwsu/eiM2iO279Ajo8beWkVnRc0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cauS3M/btshWtDxwsu/eiM2iO279Ajo8beWkVnRc0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcauS3M%2FbtshWtDxwsu%2FeiM2iO279Ajo8beWkVnRc0%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;1080&quot; height=&quot;1440&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c72Z2q/btshKQGolXE/T17CFtkNQ66hq3QtKMUix1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c72Z2q/btshKQGolXE/T17CFtkNQ66hq3QtKMUix1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c72Z2q/btshKQGolXE/T17CFtkNQ66hq3QtKMUix1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc72Z2q%2FbtshKQGolXE%2FT17CFtkNQ66hq3QtKMUix1%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;1080&quot; height=&quot;1440&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5oV09/btshAwo3cGr/rCUESjR3NWYXMR2m0G5GWK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5oV09/btshAwo3cGr/rCUESjR3NWYXMR2m0G5GWK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5oV09/btshAwo3cGr/rCUESjR3NWYXMR2m0G5GWK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5oV09%2FbtshAwo3cGr%2FrCUESjR3NWYXMR2m0G5GWK%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;1080&quot; height=&quot;1440&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2. 전농우성&lt;/h3&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;단지&lt;/td&gt;
&lt;td&gt;평형&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&gt;
&lt;td&gt;전농 우성&lt;br /&gt;1,234세대&lt;br /&gt;1992-03-11&lt;/td&gt;
&lt;td&gt;16&lt;br /&gt;22&lt;br /&gt;28&lt;br /&gt;38&lt;br /&gt;41&lt;/td&gt;
&lt;td&gt;5.8억 ~ 6.8억&lt;br /&gt;22평형 기준&lt;/td&gt;
&lt;td&gt;청량리역 도보 33분&lt;br /&gt;답십리역 도보 35분&lt;/td&gt;
&lt;td&gt;딱히..?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 아파트의 장점을 잘 찾지 못했다..ㅠㅠ&lt;/li&gt;
&lt;li&gt;아파트 외관도 좋지 않은 편이라고 생각했고, 전농SK에 비하면 인프라나 교통도 별로. 심지어 유동인구도 거의 보지 못했다..&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LDbct/btshPopfe0v/eiZWD2sh6igBKfOtLYODHK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LDbct/btshPopfe0v/eiZWD2sh6igBKfOtLYODHK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LDbct/btshPopfe0v/eiZWD2sh6igBKfOtLYODHK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLDbct%2FbtshPopfe0v%2FeiZWD2sh6igBKfOtLYODHK%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;1080&quot; height=&quot;1440&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. 전농래미안엘파인&lt;/h3&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;단지&lt;/td&gt;
&lt;td&gt;평형&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&gt;
&lt;td&gt;전농래미안엘파인&lt;br /&gt;472세대&lt;br /&gt;2011-06-23&lt;/td&gt;
&lt;td&gt;24&lt;br /&gt;32A&lt;br /&gt;32B&lt;br /&gt;33C&lt;br /&gt;42&lt;/td&gt;
&lt;td&gt;7.3 ~ 8.2억&lt;br /&gt;2023-05, 24평형 기준&lt;br /&gt;전세가율 52%&lt;/td&gt;
&lt;td&gt;청량리역 도보 40분&lt;br /&gt;답십리역 도보 36분&lt;/td&gt;
&lt;td&gt;여기도 딱히..?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;근처 전농SK, 전농우성에 비하면 확실히 신축 아파트 느낌이 났다.&lt;/li&gt;
&lt;li&gt;하지만 여기도 상권과 교통이 별로였다. 유동인구도 거의 보지 못함.&lt;/li&gt;
&lt;li&gt;주변에 빌라촌이 있는 듯 했다.&lt;/li&gt;
&lt;li&gt;단지 내나 아파트 외관은 굉장히 깨끗하고 정리가 잘 된 느낌이였지만,&amp;nbsp;&lt;/li&gt;
&lt;li&gt;개인적으로 느낀 점은, 굳이 7억대에 여기로? 라는 의문이 들었다. (지극히 주관적인 생각입니다..!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/swSRK/btshE97pWR8/Q0cCxYvw1FIlOATAdN48bk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/swSRK/btshE97pWR8/Q0cCxYvw1FIlOATAdN48bk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/swSRK/btshE97pWR8/Q0cCxYvw1FIlOATAdN48bk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FswSRK%2FbtshE97pWR8%2FQ0cCxYvw1FIlOATAdN48bk%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;1080&quot; height=&quot;1440&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;4. 답십리대림&lt;/h3&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;단지&lt;/td&gt;
&lt;td&gt;평형&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&gt;
&lt;td&gt;답십리대림&lt;br /&gt;228세대&lt;br /&gt;2003-01-08&lt;/td&gt;
&lt;td&gt;24&lt;br /&gt;32A&lt;br /&gt;32B&lt;br /&gt;42&lt;/td&gt;
&lt;td&gt;6.1 ~ 6.7억&lt;br /&gt;2023-05, 24평형 기준&lt;br /&gt;전세가율 57~59%&lt;/td&gt;
&lt;td&gt;답십리역 도보 24분&lt;/td&gt;
&lt;td&gt;단지 바로 앞 조그만 상가가 있다.&lt;br /&gt;그 외 아파트 앞에도 작은 상권들이 모여있지만,&lt;br /&gt;대부분 미용실이나 배달음식점 등등.. 그저그랬다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동답초등학교가 바로 붙어 있다.&lt;/li&gt;
&lt;li&gt;그냥 초붙아 외에는 큰 장점이 없다고 느꼈다. (지극히 개인적인 느낌)&lt;/li&gt;
&lt;li&gt;계단 + 복도식 아파트&lt;/li&gt;
&lt;li&gt;나름 깔끔하고 정리가 잘되어 있다.&lt;/li&gt;
&lt;li&gt;단지 나왔을 때 골목이 낡고 빌라촌들이 많았다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dz9P4r/btshBml3mMc/kx8vvGqYehRi9wObVf9VAK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dz9P4r/btshBml3mMc/kx8vvGqYehRi9wObVf9VAK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dz9P4r/btshBml3mMc/kx8vvGqYehRi9wObVf9VAK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdz9P4r%2FbtshBml3mMc%2Fkx8vvGqYehRi9wObVf9VAK%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;1080&quot; height=&quot;1440&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;5. 동답한신&lt;/h3&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;단지&lt;/td&gt;
&lt;td&gt;평형&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&gt;
&lt;td&gt;동답한신&lt;br /&gt;600세대&lt;br /&gt;1992-01-03&lt;/td&gt;
&lt;td&gt;14&lt;br /&gt;17&lt;br /&gt;23&lt;br /&gt;33&lt;br /&gt;34&lt;/td&gt;
&lt;td&gt;5.2 ~ 6억&lt;br /&gt;2023-05, 23평형 기준&lt;br /&gt;전세가율 58%&lt;/td&gt;
&lt;td&gt;답십리역 도보 22분&lt;/td&gt;
&lt;td&gt;단지 바로 앞 조그만 상가가 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;답십리 대림과 바로 붙어있다.&lt;/li&gt;
&lt;li&gt;주변 상권도 공유하는 느낌. 가격은 굉장히 싸다고 느꼈다.&lt;/li&gt;
&lt;li&gt;건물 외관이나 단지 내부 환경은 조금 낡아보이는 정도? 큰 특징은 없었다.&lt;/li&gt;
&lt;li&gt;살짝 언덕&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwgEDx/btshE1uTLOV/GkqycXhvL0SyuqmkDh1tP0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwgEDx/btshE1uTLOV/GkqycXhvL0SyuqmkDh1tP0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwgEDx/btshE1uTLOV/GkqycXhvL0SyuqmkDh1tP0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwgEDx%2FbtshE1uTLOV%2FGkqycXhvL0SyuqmkDh1tP0%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;1080&quot; height=&quot;1440&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;6. 답십리동아&lt;/h3&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;단지&lt;/td&gt;
&lt;td&gt;평형&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&gt;
&lt;td&gt;답십리동아&lt;br /&gt;741세대&lt;br /&gt;2001-11-23&lt;/td&gt;
&lt;td&gt;27&lt;br /&gt;32&lt;br /&gt;38&lt;br /&gt;42&lt;/td&gt;
&lt;td&gt;5.8 ~ 6.5억&lt;br /&gt;2023-05, 27평형 기준&lt;br /&gt;전세가율 58~59%&lt;/td&gt;
&lt;td&gt;답십리역 도보 19분&lt;/td&gt;
&lt;td&gt;단지 내 편의점 등 상가가 있고,&lt;br /&gt;입구에서 조금 내려오면 작은 상권들이 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기는 동별로 좋고 나쁨이 차이가 난 것 같다.&lt;/li&gt;
&lt;li&gt;쪽문?후문? 쪽 단지는 별로 안좋아 보였지만, 정문 쪽 단지에는 인프라 접근성 등이 괜찮아 보였다.&lt;/li&gt;
&lt;li&gt;살짝 오르막이 있다.&lt;/li&gt;
&lt;li&gt;답십리 주민센터와 딱 붙어있어서 좋아보였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;7. 답십리두산1단지&lt;/h3&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;단지&lt;/td&gt;
&lt;td&gt;평형&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&gt;
&lt;td&gt;답십리두산1단지&lt;br /&gt;739세대&lt;br /&gt;2000-08-02&lt;/td&gt;
&lt;td&gt;23&lt;br /&gt;25&lt;br /&gt;32&lt;br /&gt;42&lt;/td&gt;
&lt;td&gt;6.2 ~ 7.2억&lt;br /&gt;2023-05, 23평형 기준&lt;br /&gt;전세가율 52~53%&lt;/td&gt;
&lt;td&gt;답십리역 도보 22분&lt;/td&gt;
&lt;td&gt;단지 내 조그마한 상가 존재&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;언덕이 가파랐다..(후문 쪽인 것 같음.)&lt;/li&gt;
&lt;li&gt;아파트 내부 엘리베이터가 깨끗하고 현관쪽도 깨끗해보였다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;아파트 외관이나 단지 내부 환경 등 컨디션이 괜찮아보였다.&lt;/li&gt;
&lt;li&gt;정문에 씨유 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ccz3R/btshBlOfkYf/5VENkPQfnvwjHb4QcaZtg1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ccz3R/btshBlOfkYf/5VENkPQfnvwjHb4QcaZtg1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ccz3R/btshBlOfkYf/5VENkPQfnvwjHb4QcaZtg1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCcz3R%2FbtshBlOfkYf%2F5VENkPQfnvwjHb4QcaZtg1%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;3024&quot; height=&quot;4032&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m7aUu/btshCci3tFC/U8qxaF1Qpgy5vJ42vGz4T1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m7aUu/btshCci3tFC/U8qxaF1Qpgy5vJ42vGz4T1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m7aUu/btshCci3tFC/U8qxaF1Qpgy5vJ42vGz4T1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm7aUu%2FbtshCci3tFC%2FU8qxaF1Qpgy5vJ42vGz4T1%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;3024&quot; height=&quot;4032&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;8. 답십리파크자이&lt;/h3&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;단지&lt;/td&gt;
&lt;td&gt;평형&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&gt;
&lt;td&gt;답십리파크자이&lt;br /&gt;802세대&lt;br /&gt;2019-01-22&lt;/td&gt;
&lt;td&gt;20A&lt;br /&gt;20B&lt;br /&gt;25A&lt;br /&gt;25E&lt;br /&gt;25B&lt;br /&gt;25C&lt;br /&gt;33A&lt;/td&gt;
&lt;td&gt;7.6 ~ 8.4억&lt;br /&gt;2023-05, 20평형 기준&lt;br /&gt;전세가율 50~51%&lt;/td&gt;
&lt;td&gt;답십리역 도보 15분&lt;/td&gt;
&lt;td&gt;잘 못봤다. 단지 내에 있었나?&lt;br /&gt;지도상에는 단지 나와서 &lt;br /&gt;아래쪽에 조그만 상권이 있어보임&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;답십리동아, 답십리두산과 밀접해있음&lt;/li&gt;
&lt;li&gt;주민센터가 초 근접해있음.&lt;/li&gt;
&lt;li&gt;단지내 환경이나 외관 등 컨디션이 최상수준&lt;/li&gt;
&lt;li&gt;단지로 통하는 문이 잠겨있음 (보안)&lt;/li&gt;
&lt;li&gt;오늘 본 단지 중 입지나 아파트 컨디션은 최고인듯했다. 다만 가격이 비싼게 문제..ㅠ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf5jK0/btshMVnczvD/GrkBmVncKjc1SHGwWmWKSK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf5jK0/btshMVnczvD/GrkBmVncKjc1SHGwWmWKSK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf5jK0/btshMVnczvD/GrkBmVncKjc1SHGwWmWKSK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf5jK0%2FbtshMVnczvD%2FGrkBmVncKjc1SHGwWmWKSK%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;3024&quot; height=&quot;4032&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WuunC/btshAvKs26C/3ZDC12uxUPSpxSy7Y5Wfkk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WuunC/btshAvKs26C/3ZDC12uxUPSpxSy7Y5Wfkk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WuunC/btshAvKs26C/3ZDC12uxUPSpxSy7Y5Wfkk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWuunC%2FbtshAvKs26C%2F3ZDC12uxUPSpxSy7Y5Wfkk%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;3024&quot; height=&quot;4032&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;9. 답십리청솔우성&lt;/h3&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;단지&lt;/td&gt;
&lt;td&gt;평형&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&gt;
&lt;td&gt;답십리청솔우성1차&lt;br /&gt;1,542세대&lt;br /&gt;2000-03-18&lt;/td&gt;
&lt;td&gt;23A&lt;br /&gt;23B&lt;br /&gt;32&lt;br /&gt;42&lt;/td&gt;
&lt;td&gt;6.7 ~ 7.7억&lt;br /&gt;2023-05, 23A평형 기준&lt;br /&gt;전세가율 49~50%&lt;/td&gt;
&lt;td&gt;청량리역 도보 30분&lt;br /&gt;답십리역 도보 26분&lt;/td&gt;
&lt;td&gt;시장과 연결되어 있음&lt;br /&gt;시장 주변으로 약간의 상권&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무난무난한 아파트.&lt;/li&gt;
&lt;li&gt;전농SK 아파트와 근접해있다.&lt;/li&gt;
&lt;li&gt;전농SK와 가격차이가 있는 만큼(5천+), 내 기준 전농 SK보다 깨끗하고 컨디션이 좋아 보였음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;10. 장안현대홈타운1차&lt;/h3&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;단지&lt;/td&gt;
&lt;td&gt;평형&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&gt;
&lt;td&gt;동답한신&lt;br /&gt;2,182세대&lt;br /&gt;2003-10-28&lt;/td&gt;
&lt;td&gt;22C2&lt;br /&gt;23C1&lt;br /&gt;23C&lt;br /&gt;32B&lt;br /&gt;32B1&lt;br /&gt;42&lt;br /&gt;50&lt;/td&gt;
&lt;td&gt;6.5 ~ 7.3억&lt;br /&gt;2023-05, 22C2평형 기준&lt;br /&gt;전세가율 61%&lt;/td&gt;
&lt;td&gt;장한평역 도보 40분&lt;/td&gt;
&lt;td&gt;단지 바로 앞 시장이 있고 상권이 엄청 발달함.&lt;br /&gt;없는게 없는 정도?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인프라가 굉장히 좋고 유동인구가 굉장히 많은 동네&lt;/li&gt;
&lt;li&gt;그렇다고 해서 유해시설 등은 딱히 안보임.&lt;/li&gt;
&lt;li&gt;장안/답십리에 비해 학생들이 많이 보였다. (장안/답십리는 거의 안보였던 정도?)&lt;/li&gt;
&lt;li&gt;단지 내부나 건물 외관도 굉장히 깨끗함.&lt;/li&gt;
&lt;li&gt;단점은 교통이 별로 좋지 않은 듯 함. (주변에 버스정류장이 있는지는 모르겠지만..)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rsiQe/btshBlOfqja/LlCy0InK0aJWv8bxkXDzs1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rsiQe/btshBlOfqja/LlCy0InK0aJWv8bxkXDzs1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rsiQe/btshBlOfqja/LlCy0InK0aJWv8bxkXDzs1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrsiQe%2FbtshBlOfqja%2FLlCy0InK0aJWv8bxkXDzs1%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;3024&quot; height=&quot;4032&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;방문 전, 전농SK나 답십리우성, 두산 등 대단지 아파트가 6억초반대라니! 라는 기대를 안고 갔었다.&lt;/li&gt;
&lt;li&gt;굉장히 싼 가격이고 대단지라는 점에서는 좋아보였지만, 교통이나 유동인구, 인프라 등이 조금 아쉬운 수준이였다.&lt;/li&gt;
&lt;li&gt;그에 반해 장안은 교통은 불편하지만 유동인구와 인프라가 굉장히 좋아보였다.&lt;/li&gt;
&lt;li&gt;오늘 임장 중 사고 싶은 아파트를 고르라면
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내 픽: 전농SK, 답십리두산&lt;/li&gt;
&lt;li&gt;루책임 픽: 장안현대홈타운1차&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>부동산공부/임장</category>
      <category>답십리</category>
      <category>동대문</category>
      <category>동대문 부동산</category>
      <category>동대문 임장</category>
      <category>부동산</category>
      <category>임장</category>
      <category>장안동</category>
      <category>전농동</category>
      <category>청량리</category>
      <author>지잉지잉</author>
      <guid isPermaLink="true">https://developer-jiing.tistory.com/56</guid>
      <comments>https://developer-jiing.tistory.com/56#entry56comment</comments>
      <pubDate>Mon, 29 May 2023 22:52:55 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] DI를 쉽게 하는 방법 (Feat. @RequiredArgsConstructor)</title>
      <link>https://developer-jiing.tistory.com/55</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring DI 방법은 대표적으로 아래의 세 가지를 이용한다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성자 주입&lt;/li&gt;
&lt;li&gt;Field 주입&lt;/li&gt;
&lt;li&gt;Setter 주입&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;보통은 @Autowired를 이용한 Field 주입을 많이 사용하는데, 생성자 주입을 권장한다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순환참조 이슈&lt;/li&gt;
&lt;li&gt;Bean을 Immutable 하게 주입 가능 등 장점이 많다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 기본적인 생성자 주입 방법&lt;/h2&gt;
&lt;pre id=&quot;code_1677389933199&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;&quot;)
public class TestController {

    private final ProjectService projectService;
    private final CatalogService catalogService;
    private final MailService mailService;
    
    public TestController(ProjectService projectService, CatalogService catalogService, MailService mailService) {
        this.projectService = projectService;
        this.catalogService = catalogService;
        this.mailService = mailService;
    }
}&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;생성자에 Dependencies를 추가한다.&lt;/li&gt;
&lt;li&gt;Spring은 해당 Dependency를 찾아와 자동으로 주입해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 더 간단하게 생성자 주입&lt;/h2&gt;
&lt;pre id=&quot;code_1677390219486&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;&quot;)
@RequiredArgsConstructor
public class TestController {

    private final ProjectService projectService;
    private final CatalogService catalogService;
    private final MailService mailService;
}&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;@RequiredArgsConstructor
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;final 혹은 @NonNull 이 붙은 필드만 받는 생성자 자동생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;@RequiredArgsConstructor로 생성자를 생략할 수 있고, 자동으로 의존성 주입(생성자 주입)까지 된다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발/SPRING</category>
      <category>Dependency Injection</category>
      <category>DI</category>
      <category>Lombok</category>
      <category>RequiredArgsConstructor</category>
      <category>Spring</category>
      <author>지잉지잉</author>
      <guid isPermaLink="true">https://developer-jiing.tistory.com/55</guid>
      <comments>https://developer-jiing.tistory.com/55#entry55comment</comments>
      <pubDate>Sun, 26 Feb 2023 14:44:41 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Intellij Community로 Multi Module Project 생성하기</title>
      <link>https://developer-jiing.tistory.com/54</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저는 Intellij Community를 사용하고 있는데, 제가 못하는건지 원래 안되는건지, Springboot-Java 프로젝트를 생성하는게 잘 안됐었습니다.&lt;/li&gt;
&lt;li&gt;그래서 Springboot Initializer로 Project 들을 생성하고, 그 이후 root-project에 모듈들을 등록함으로써 Multi Module Project를 구성했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Project 구성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;User라는 root-project와, 그 아래 admin/client/core/internal 4개의 Submodule을 구성했습니다.&lt;/li&gt;
&lt;li&gt;user (root-project)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;admin&lt;/li&gt;
&lt;li&gt;client&lt;/li&gt;
&lt;li&gt;core&lt;/li&gt;
&lt;li&gt;internal&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Root Project 생성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring initializer로 root project(user) 을 생성합니다. (&lt;a href=&quot;https://start.spring.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://start.spring.io/&lt;/a&gt;)위 처럼 프로젝트 설정을 마친 후, &quot;GENERATE&quot; 버튼을 클릭하여 프로젝트 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1772&quot; data-origin-height=&quot;883&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mtrhz/btr0IHzHRBr/J6k6evnpS6NynXanqzUxgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mtrhz/btr0IHzHRBr/J6k6evnpS6NynXanqzUxgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mtrhz/btr0IHzHRBr/J6k6evnpS6NynXanqzUxgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmtrhz%2Fbtr0IHzHRBr%2FJ6k6evnpS6NynXanqzUxgk%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;1772&quot; height=&quot;883&quot; data-origin-width=&quot;1772&quot; data-origin-height=&quot;883&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성이 완료 되었으면 압축을 풀고 Intellij에서 Open 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;383&quot; data-origin-height=&quot;573&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kxsKS/btr0NH6L7LG/BuKiPopzF7rw6Spczruhw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kxsKS/btr0NH6L7LG/BuKiPopzF7rw6Spczruhw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kxsKS/btr0NH6L7LG/BuKiPopzF7rw6Spczruhw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkxsKS%2Fbtr0NH6L7LG%2FBuKiPopzF7rw6Spczruhw1%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;383&quot; height=&quot;573&quot; data-origin-width=&quot;383&quot; data-origin-height=&quot;573&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Sub Module 생성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위에서 Springboot Initializer를 통해 4개의 모듈도 동일한 방법으로 생성해줍니다.&lt;/li&gt;
&lt;li&gt;생성한 4개의 Sub Module을 Root Project Directory의 하위로 옮깁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;591&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dl1RiY/btr0GaCEk15/mWdB6dGgGE6mqPlB9bSg10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dl1RiY/btr0GaCEk15/mWdB6dGgGE6mqPlB9bSg10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dl1RiY/btr0GaCEk15/mWdB6dGgGE6mqPlB9bSg10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdl1RiY%2Fbtr0GaCEk15%2FmWdB6dGgGE6mqPlB9bSg10%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;850&quot; height=&quot;591&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;591&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Sub Module 설정&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위처럼 프로젝트를 모두 Root Project 아래에 넣고 Intellij에서 확인해보면, 해당 프로젝트는 Multi module Project로 인식이 되지 않습니다.&lt;/li&gt;
&lt;li&gt;settings.gradle 파일에 4개의 Sub Module을 Include 하면, Root project에서는 include 된 4개의 프로젝트를 sub module로 인식합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;287&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z970d/btr0HtosA8h/D3OKlZWwZkB8dNm4aSm40k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z970d/btr0HtosA8h/D3OKlZWwZkB8dNm4aSm40k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z970d/btr0HtosA8h/D3OKlZWwZkB8dNm4aSm40k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ970d%2Fbtr0HtosA8h%2FD3OKlZWwZkB8dNm4aSm40k%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;1002&quot; height=&quot;287&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;287&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;</description>
      <category>개발/SPRING</category>
      <category>gradle</category>
      <category>IntelliJ</category>
      <category>multi-module-project</category>
      <category>springboot</category>
      <author>지잉지잉</author>
      <guid isPermaLink="true">https://developer-jiing.tistory.com/54</guid>
      <comments>https://developer-jiing.tistory.com/54#entry54comment</comments>
      <pubDate>Sun, 26 Feb 2023 13:55:59 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Webflux] 웹플럭스에 대한 간단 정리</title>
      <link>https://developer-jiing.tistory.com/53</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring MVC와 Spring Webflux&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring MVC는 Blocking / 동기방식으로 동작한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청이 발생할 경우, 스레드풀 내 스레드 하나를 점유해 요청을 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Spring Webflux는 Non-Blocking / 비동기방식으로 동작한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청이 발생할 경우, 이벤트 루프가 돌아서 해당 이벤트를 처리하는 핸들러에게 처리를 위임하고, 처리가 완료되면 callback 메소드 등을 통해 응답을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Webflux를 사용하는 이유&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring MVC에 비해 적은 스레드를 이용하여 동시성을 핸들링한다.&lt;/li&gt;
&lt;li&gt;즉 서버 자원을 낭비하지 않고 효율적으로 동작하는 것을 목적으로 사용한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통 Spring MVC 의 Tomcat 서버에서는 스레드 수가 Default 200개이다. 서버에 요청이 많지 않을 경우, 노는 스레드가 생기고, 그만큼 자원이 낭비된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reactive&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;613&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y0M0D/btrX9Go2NCj/kG3nl1gbinpKp2Y1AoS3p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y0M0D/btrX9Go2NCj/kG3nl1gbinpKp2Y1AoS3p0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y0M0D/btrX9Go2NCj/kG3nl1gbinpKp2Y1AoS3p0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY0M0D%2FbtrX9Go2NCj%2FkG3nl1gbinpKp2Y1AoS3p0%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;1202&quot; height=&quot;613&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;613&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reactive Stream&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Webflux 에서 사용하는 Reactor는 Reactive Stream의 구현체이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;논블로킹과 백프레셔를 이용한 비동기 데이터 처리의 표준&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CBTwD/btrX2XssmOG/uBu9DMmk0lP2iyikUnybuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CBTwD/btrX2XssmOG/uBu9DMmk0lP2iyikUnybuK/img.png&quot; data-alt=&quot;https://engineering.linecorop.com/ko/blog/reactive-streams-with-armeria-1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CBTwD/btrX2XssmOG/uBu9DMmk0lP2iyikUnybuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCBTwD%2FbtrX2XssmOG%2FuBu9DMmk0lP2iyikUnybuK%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;718&quot; height=&quot;358&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://engineering.linecorop.com/ko/blog/reactive-streams-with-armeria-1&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Subscriber -&amp;gt; Publisher: 구독 요청&lt;/li&gt;
&lt;li&gt;Publisher -&amp;gt; Subscriber: Subscription 정보 전달&lt;/li&gt;
&lt;li&gt;Subscriber -&amp;gt; Subscription -&amp;gt; Publisher: Subscription의 request 함수를 통해 Publisher에게 요청&lt;/li&gt;
&lt;li&gt;Publisher -&amp;gt; Subscription -&amp;gt; Subscriber: Subscription의 시그널(onNext, onComplete, onError 등)을 응답&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Mono와 Flux&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flux와 Mono는 Reactive Stream의 구현체인 Reactor 객체이며, 차이점은 발행하는 데이터 갯수이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flux: 0 ~ N 개의 데이터 전달&lt;/li&gt;
&lt;li&gt;Mono: 0 ~ 1 개의 데이터 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발/SPRING</category>
      <category>Flux</category>
      <category>Mono</category>
      <category>reactive programming</category>
      <category>Reactive Stream</category>
      <category>spring webflux</category>
      <category>webflux</category>
      <author>지잉지잉</author>
      <guid isPermaLink="true">https://developer-jiing.tistory.com/53</guid>
      <comments>https://developer-jiing.tistory.com/53#entry53comment</comments>
      <pubDate>Sun, 5 Feb 2023 22:31:46 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Batch] Spring Batch에 대하여</title>
      <link>https://developer-jiing.tistory.com/51</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Batch란?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대용량 일괄처리의 편의를 위해 설계된 가볍고 포괄적인 배치 프레임워크&lt;/li&gt;
&lt;li&gt;Spring Scheduler와는 별개의 개념이다. Batch는 보통 Jenkins나 Spring Scheduler에 의해 Trigger 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 쓸까? (내 생각..)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A라는 한 서버가 있고 이 서버에는 주기적으로 데이터를 처리해야하는 일련의 작업이 있다.&lt;/li&gt;
&lt;li&gt;A서버의 해당 작업은 Spring Scheduler에 의해 해당 로직이 수행된다.&lt;/li&gt;
&lt;li&gt;그러면 A서버는 데이터를 주기적으로 처리하는 일 뿐만아니라 본래 수행해야하는 요청/응답까지 수행했을 때 리소스가 부족하거나 하진 않을까?&lt;/li&gt;
&lt;li&gt;이 작업을 Spring Batch로 구현한다면 =&amp;gt; 별도의 배치서버로 구현하고 해당 서버는 Scheduler에 의해 실행이 된다.&lt;/li&gt;
&lt;li&gt;즉 Spring Scheduler에 의해 Trigger가 된다면, 새로운 Job Instance가 생기고 배치작업이 끝나면 Job Instance는 사라진다. 배치가 실행되는 시간에만 서버의 리소스를 사용한다.&lt;/li&gt;
&lt;li&gt;그렇기 때문에 서버에서는 리소스 관리를 수월하게 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Tasklet 처리 방식과 Chunk 지향 처리 방식에 대하여&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Batch에서 &quot;Job&quot;은 순차적인 Step을 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Tasklet 처리 방식&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 메서드로 구성되어 있는 간단한 인터페이스 이다. 1Step:1Tasklet&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Chunk 지향 처리 방식&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Chunk란, Batch 처리에서는 chunk 단위로 트랜잭션을 수행한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;chunk는 트랜잭션 내에서 커밋하는 row 수 이며 chunk 단위만큼 커밋되거나 롤백된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvJWWo/btrXoCaiaEk/P6orFNLzPmqng21dighuYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvJWWo/btrXoCaiaEk/P6orFNLzPmqng21dighuYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvJWWo/btrXoCaiaEk/P6orFNLzPmqng21dighuYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvJWWo%2FbtrXoCaiaEk%2FP6orFNLzPmqng21dighuYK%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;594&quot; height=&quot;327&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;484&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기서 ItemReader와 ItemProcessor에서는 데이터 1건씩 다뤄지고, Writer에서는 Chunk 단위로 처리된다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발/SPRING</category>
      <category>chunk</category>
      <category>Spring Batch</category>
      <category>tasklet</category>
      <category>배치</category>
      <category>스프링배치</category>
      <author>지잉지잉</author>
      <guid isPermaLink="true">https://developer-jiing.tistory.com/51</guid>
      <comments>https://developer-jiing.tistory.com/51#entry51comment</comments>
      <pubDate>Sun, 29 Jan 2023 02:32:58 +0900</pubDate>
    </item>
    <item>
      <title>어떤 캐싱을 도입할까? feat. 로컬캐시, Redis, 웹캐시</title>
      <link>https://developer-jiing.tistory.com/50</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;캐시란?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 연산이나 시간이 오래 걸리는 연산을 미리 수행한 후, 그 결과 값을 저장하여 필요시 빠르게 가져와 사용할 수 있게 하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;캐시의 종류&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로컬캐시&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;DB내부 캐시&lt;/li&gt;
&lt;li&gt;웹캐시&lt;/li&gt;
&lt;li&gt;CDN 등&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;종류별 특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;로컬캐시: 서버의 리소스(Memory, Disk) 내 결과값을 저장한다. (Caffeine cache, EHcache)&amp;nbsp;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타 서버간 통신비용이 발생하지 않는다.&lt;/li&gt;
&lt;li&gt;서버 인스턴스 메모리에 저장하기 때문에 가져오는데 속도가 빠르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버가 여러대로 클러스터링 되어있는 경우, 동기화가 되지 않는다.&lt;/li&gt;
&lt;li&gt;캐시 데이터가 커질수록 서버 메모리가 부족해 지며 이에 따라 애플리케이션 성능이 저하 될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Redis: 데이터 영속성을 지원하는 In-memory 저장소 (스냅샷 기능을 통해 Disk에 캐시데이터를 저장할 수 있기 때문)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 복제 지원. Master-slave 구조로 여러대의 복제본을 만들 수 있다.&lt;/li&gt;
&lt;li&gt;트랜잭션 지원&lt;/li&gt;
&lt;li&gt;여러 데이터 자료형 지원(문자열, 숫자, boolean 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점(?)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;싱글스레드로 동작하기 때문에 병목현상이 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;로컬캐시에 비해 서버 &amp;lt;-&amp;gt; 레디스 간 통신 비용이 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;웹캐시: 웹 사용자에 의해 빈번히 요청되는 데이터를 웹캐시 서버(Nginx 등)에 보관함으로써 응답한다.&amp;nbsp;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Client - Nginx proxy server - server 구조 일 때, 요청이 nginx 서버에 캐시되어 있다면 실제 요청은 server에 전달되지 않고 Client에게 반환한다.&lt;/li&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청이 실제 서버로 넘어가지 않기 때문에 부하 자체를 줄여줄 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;별도의 웹캐시용의 서버 구성 필요&lt;/li&gt;
&lt;li&gt;Client에서 Server로 요청하는 과정에 Proxy 서버가 중간에 끼게 되는 형태인데, 통신 과정이 늘어나니까 당연히 소요시간이 길어질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발/Infra</category>
      <category>Cache</category>
      <category>localcache</category>
      <category>memcached</category>
      <category>nginx</category>
      <category>redis</category>
      <category>web cache</category>
      <category>webcache</category>
      <category>웹캐시</category>
      <category>캐시</category>
      <author>지잉지잉</author>
      <guid isPermaLink="true">https://developer-jiing.tistory.com/50</guid>
      <comments>https://developer-jiing.tistory.com/50#entry50comment</comments>
      <pubDate>Sat, 7 Jan 2023 18:23:41 +0900</pubDate>
    </item>
    <item>
      <title>트랜잭션과 격리 수준 (Isolation Level)</title>
      <link>https://developer-jiing.tistory.com/49</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;트랜잭션(Transaction)이란?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스의 상태를 변화시키기 위해 수행하는 작업의 단위&lt;/li&gt;
&lt;li&gt;SELECT, INSERT, DELETE, UPDATE (SQL) 하나만을 의미하는게 아니다. 하나의 작업에는 여러개의 질의어가 포함될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;트랜잭션의 특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ACID
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원자성 (Atomicity)&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스에 모두 반영되던가 (commit), 모두 반영되지 않던가 (rollback,aborted)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;일관성 (Consistency)&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션의 작업 처리 결과가 항상 일관성 있어야 한다.&lt;/li&gt;
&lt;li&gt;트랜잭션이 진행되는 동안 데이터베이스가 변경되더라도, 업데이트 이전의 데이터베이스에 트랜잭션이 진행됨으로써 일관성 있게 데이터를 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;독립성 (Isolation)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;둘 이상의 트랜잭션이 동시 실행 되고있는 경우, 하나의 트랜잭션이 완료될 때 까지, 다른 트랜잭션이 해당 트랜잭션의 결과를 참조할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;지속성 (Durability)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션이 성공적으로 완료됐을 경우, 결과는 영구적으로 반영되어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;트랜잭션 격리 수준 (Isolation Level) 이란?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동시에 여러 트랜잭션이 처리될 때, 트랜잭션끼리 서로 얼마나 고립되어 있는지를 나타내는 것.&lt;/li&gt;
&lt;li&gt;즉, 특정 트랜잭션이 다른 트랜잭션에 변경한 데이터를 볼 수 있도록 허용할지 말지를 결정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;격리 수준간 차이&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;격리 수준은 아래 4개로 나뉜다. 아래로 갈수록 격리 수준이 높아지며, 성능이 떨어진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. READ UNCOMMITTED&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션 진행간, Commit 되지 않은 데이터를 참조하여 트랜잭션을 진행한다.&lt;/li&gt;
&lt;li&gt;Dirty Read 문제가 발생할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1. A 트랜잭션에서 A 테이블의 데이터를 수정중인 상태 (Commit 전)&lt;/li&gt;
&lt;li&gt;2. B 트랜잭션에서 A 테이블의 데이터를 조회 (Commit 전이지만 A 트랜잭션에 의해 수정된 데이터를 조회해옴.)&amp;nbsp; -&amp;gt; Dirty Read&lt;/li&gt;
&lt;li&gt;3. A 트랜잭션에서 문제가 발생해 Rollback&lt;/li&gt;
&lt;li&gt;4. B 트랜잭션은 A 트랜잭션에 의해 수정된 데이터를 이용하여 트랜잭션을 진행함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. READ COMMITTED (Oracle Default)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1과 달리 Commit 된 데이터만 조회해온다.&lt;/li&gt;
&lt;li&gt;UNDO 영역에 저장된 데이터를 참조하기 때문에 Dirty Read가 발생하지 않는다.&lt;/li&gt;
&lt;li&gt;하지만 NON-REPEATABLE READ 부정합 문제가 발생할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;B 트랜잭션에서 ID 1번 상품의 가격을 조회 =&amp;gt; 10,000원&lt;/li&gt;
&lt;li&gt;A 트랜잭션에서 ID 1번 상품의 가격을 10,000 -&amp;gt; 20,000원으로 수정 후 Commit&lt;/li&gt;
&lt;li&gt;B 트랜잭션에서 ID 1번 상품의 가격을 다시 조회 =&amp;gt; 20,000원 (NON-REPEATABLE READ)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. REPETABLE READ (MySQL Default)&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;li&gt;자신의 트랜잭션 번호보다 낮은 트랜잭션 번호에서 Commit된 데이터만 조회함.&lt;/li&gt;
&lt;li&gt;자신의 트랜잭션 번호보다 높은 트랜잭션에 의해 변경된 데이터는 Commit에 상관 없이 UNDO 영역만 조회.&lt;/li&gt;
&lt;li&gt;2와 달리 NON-REPEATABLE READ 부정합 문제가 발생하지 않는다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A 트랜잭션(no: 10)에서 ID 1번 상품의 가격을 조회 =&amp;gt; 10,000원&lt;/li&gt;
&lt;li&gt;B 트랜잭션(no: 12)에서 ID 1번 상품의 가격을 10,000 -&amp;gt; 20,000원으로 수정 후 Commit&lt;/li&gt;
&lt;li&gt;A 트랜잭션에서 ID 1번 상품의 가격을 다시 조회 =&amp;gt; 10,000원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. SERIALIZABLE&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션에서 데이터 SELECT 시 공유잠금을 설정함으로써, 해당 데이터를 수정하지 못하도록 설정함.&lt;/li&gt;
&lt;li&gt;격리수준을 REPETABLE READ 로 적용하면 뭐든 해결인가? =&amp;gt; 아니다. Phantom READ (유령읽기) 문제가 발생한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A 트랜잭션에서 SELECT * FROM PRODUCT WHERE PRODUCT_ID &amp;lt; 5 =&amp;gt; 1개 상품 조회&lt;/li&gt;
&lt;li&gt;B 트랜잭션에서 id 3인 상품 신규 생성 후 커밋&lt;/li&gt;
&lt;li&gt;A 트랜잭션에서 동일한 SQL 실행 =&amp;gt; 2개 상품 조회&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/DB</category>
      <category>MySQL</category>
      <category>oracle</category>
      <category>Transaction</category>
      <category>격리수준</category>
      <category>고립수준</category>
      <category>트랜잭션</category>
      <category>트랜잭션 격리수준</category>
      <author>지잉지잉</author>
      <guid isPermaLink="true">https://developer-jiing.tistory.com/49</guid>
      <comments>https://developer-jiing.tistory.com/49#entry49comment</comments>
      <pubDate>Fri, 30 Dec 2022 15:38:01 +0900</pubDate>
    </item>
    <item>
      <title>[Kubernetes] Canary 배포 환경 구성</title>
      <link>https://developer-jiing.tistory.com/48</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;0. 개요&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Canary 배포란?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529;&quot;&gt;구 버전의 서비스와 새 버전의 서비스를 동시 구성, 일부 트래픽을 새 버전으로 분산하여 에러 여부 판단 및 모니터링 하는 배포방식&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #212529;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;보통 &lt;b&gt;구 버전은 PRODUCTION / 새 버전은 CANARY&lt;/b&gt; 로 서비스를 칭한다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3hreU/btrUTJpC69M/IwO1aEkkVhqWkgeTR8GlIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3hreU/btrUTJpC69M/IwO1aEkkVhqWkgeTR8GlIK/img.png&quot; data-alt=&quot;그림 출처:&amp;amp;amp;nbsp;https://medium.com/@domi.stoehr/canary-deployments-on-kubernetes-without-service-mesh-425b7e4cc862&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3hreU/btrUTJpC69M/IwO1aEkkVhqWkgeTR8GlIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3hreU%2FbtrUTJpC69M%2FIwO1aEkkVhqWkgeTR8GlIK%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;852&quot; height=&quot;512&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림 출처:&amp;amp;nbsp;https://medium.com/@domi.stoehr/canary-deployments-on-kubernetes-without-service-mesh-425b7e4cc862&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;k8s의 nginx-ingress-controller를 통해 트래픽을 분산할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;weight 기반: 100 - X % / X % 로 트래픽 분산 (random)&lt;/li&gt;
&lt;li&gt;cookie 기반: 요청의 cookie value를 확인하고 production / canary 서비스로 분산&lt;/li&gt;
&lt;li&gt;header 기반: 요청의 header value를 확인하고 production / canary 서비스로 분산&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 테스트용 프로젝트 생성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간단하게 2개의 프로젝트를 구성한다.&lt;/li&gt;
&lt;li&gt;동일한 API path를 가졌지만, 구 버전과 새 버전의 응답값이 다르게 (production / canary 구분 가능하도록) 구성했다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1672361727821&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
public class ProductionController {
	
    @GetMapping(&quot;/test&quot;)
    public ResponseEntity&amp;lt;String&amp;gt; test() {
    	return new ResponseEntity&amp;lt;String&amp;gt;(&quot;[PRODUCTION] requested.&quot;, HttpStatus.OK);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1672361788516&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
public class CanaryController {
	
    @GetMapping(&quot;/test&quot;)
    public ResponseEntity&amp;lt;String&amp;gt; test() {
    	return new ResponseEntity&amp;lt;String&amp;gt;(&quot;[CANARY] requested.&quot;, HttpStatus.OK);
    }
}&lt;/code&gt;&lt;/pre&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. Docker build&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 서비스를 k8s에 올리기 위해 Docker image를 만든다.&lt;/li&gt;
&lt;li&gt;Dockerfile도 간단하게 만든다..!&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1672362100992&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM adoptopenjdk/openjdk11
ARG JAR_FILE_PATH=./build/libs/TestProject-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE_PATH} TestProject-0.0.1-SNAPSHOT.jar
ENTRYPOINT [&quot;java&quot;,&quot;-jar&quot;,&quot;TestProject-0.0.1-SNAPSHOT.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 도커파일로 이미지를 빌드한다.&lt;/li&gt;
&lt;li&gt;나는 구 버전을 test-project:0.0.1, 새 버전을 test-project-0.0.2 로 빌드했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1672362461266&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker build -t test-project:0.0.1 . 
...
$ docker build -t test-project:0.0.2 .
...
$ docker images
REPOSITORY		TAG		IMAGE ID	CREATED		SIZE
test-project		0.0.1		...		1 minute ago	459MB
test-project		0.0.2		...		1 minute ago	459MB&lt;/code&gt;&lt;/pre&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. Kubernetes 배포&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구 버전 / 새 버전의 서비스 총 2개의 서비스를 Kubernetes에 올리기 위해 yaml 파일을 만든다. (커맨드로도 가능하다.)&lt;/li&gt;
&lt;li&gt;아래 예시는 k8s deployment 생성과 service 노출(등록) 설정이다. production / canary 서비스 각각 이미지가 다르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;production-service.yaml&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1672363000629&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: svc-production
spec:
  replicas: 1
  selector:
    matchLabels:
      app: svc-production
  template:
    metadata:
      labels:
        app: svc-production
    spec:
      containers:
        - name: svc-production
          image: test-project:0.0.1
          ports:
           - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: svc-production
spec:
  type: NodePort
  selector:
    app: svc-production
  ports:
    - port: 8080&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;canary-service.yaml&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1672363114146&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: svc-canary
spec:
  replicas: 1
  selector:
    matchLabels:
      app: svc-canary
  template:
    metadata:
      labels:
        app: svc-canary
    spec:
      containers:
        - name: svc-canary
          image: test-project:0.0.2
          ports:
           - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: svc-canary
spec:
  type: NodePort
  selector:
    app: svc-canary
  ports:
    - port: 8080&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이제 위 2개의 파일을 가지고 서비스를 만들어보자&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1672363334694&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl apply -f production-service.yaml
deployment.apps/svc-production created
service/svc-production created

$ kubectl apply -f canary-service.yaml
deployment.apps/svc-canary created
service/svc-canary created&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;서비스 확인 (deployment, service, pod, replicaset 모두 정상적으로 생성되었다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1672363427087&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl get all
NAME			READY	STATUS		RESTARTS	AGE
pod/svc-canary-...	1/1	Running		0		1m
pod/svc-production-...	1/1	Running		0		1m	

NAME				TYPE		CLUSTER_IP		EXTERNAL-IP	PORT(S)		AGE
service/svc-canary-...		NodePort	10.xxx.xxx.xxx		&amp;lt;none&amp;gt;		8080:32309/TCP	1m
service/svc-production-...	NodePort	10.xxx.xxx.xxx		&amp;lt;none&amp;gt;		8080:31373/TCP	1m

NAME					READY	UP-TO-DATE	AVAILABLE	AGE
deployment.apps/svc-canary-...		1/1	1		1		1m
deployment.apps/svc-production-...	1/1	1		1		1m

NAME					DESIRED	CURRENT		READY		AGE
replicaset.apps/svc-canary-...		1	1		1		1m
replicaset.apps/svc-production-...	1	1		1		1m&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이제 해당 두 서비스를 클러스터 외부에서 접근하기 위해 ingress를 생성한다.&lt;/li&gt;
&lt;li&gt;Ingress도 서비스와 마찬가지로 각각 yaml 파일로 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;production-ingress.yaml&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1672364009673&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: production-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: canary-test.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: svc-production
                port:
                  number: 8080&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;canary-ingress.yaml 파일을 생성하기 전에! production ingress 부터 생성해보자&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1672364458080&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl apply -f production-ingress.yaml
ingress.networking.k8s.io/production-ingress created
...
$ kubectl get ingress
NAME			CLASS	HOSTS			ADDRESS			PORTS	AGE
production-ingress	nginx	canary-test.com		192.xxx.xxx.xxx		80	1m&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;ingress를 생성한 후 kubectl get ingress 로 ingress 목록을 확인 할 때, ADDRESS가 아직 매핑이 안되있을 수 있다. (최대 n분 이상 소요 될 수 있다고 하니 기다려보자.)&lt;/li&gt;
&lt;li&gt;ADDRESS가 매핑이 완료되었다면 production 서비스의 API를 호출해본다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1672364688466&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ curl http://canary-test.com/test
[PRODUCTION] requested.&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;production 서버의 API 요청/응답이 정상적으로 이루어졌다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;만약 정상 응답이 되지 않는다면 리눅스의 hosts 파일에 해당 host를 등록해야한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1672365706528&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ vi /etc/hosts

// 맨 아랫줄에 ip 호스트 등록
...
127.0.0.1	localhost
...
192.xxx.xxx.xxx canary-test.com&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Canary 배포 설정&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앞에서 production 서비스를 배포하고 ingress 도 설정을 완료했으니 이제 canary 서비스의 ingress 생성 및 설정을 해야한다.&lt;/li&gt;
&lt;li&gt;그 전에 nginx-ingress의 canary 관련 annotations를 간단히 살펴보자면..&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 43.4496%;&quot;&gt;annotations&lt;/td&gt;
&lt;td style=&quot;width: 11.124%;&quot;&gt;type&lt;/td&gt;
&lt;td style=&quot;width: 45.4263%;&quot;&gt;description&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 43.4496%;&quot;&gt;nginx.ingress.kubernetes.io/canary&lt;/td&gt;
&lt;td style=&quot;width: 11.124%;&quot;&gt;String&lt;/td&gt;
&lt;td style=&quot;width: 45.4263%;&quot;&gt;카나리 배포 적용 여부(&quot;true&quot;, &quot;false&quot;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 43.4496%;&quot;&gt;&lt;span style=&quot;background-color: #efefef;&quot;&gt;nginx.ingress.kubernetes.io/canary-by-header&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.124%;&quot;&gt;String&lt;/td&gt;
&lt;td style=&quot;width: 45.4263%;&quot;&gt;header key 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 43.4496%;&quot;&gt;&lt;span style=&quot;background-color: #efefef;&quot;&gt;nginx.ingress.kubernetes.io/canary-by-header-value&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.124%;&quot;&gt;String&lt;/td&gt;
&lt;td style=&quot;width: 45.4263%;&quot;&gt;header value 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 43.4496%;&quot;&gt;&lt;span style=&quot;background-color: #efefef;&quot;&gt;nginx.ingress.kubernetes.io/canary-by-header-pattern&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.124%;&quot;&gt;String&lt;/td&gt;
&lt;td style=&quot;width: 45.4263%;&quot;&gt;정규표현식을 사용한 header value 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 43.4496%;&quot;&gt;&lt;span style=&quot;background-color: #efefef;&quot;&gt;nginx.ingress.kubernetes.io/canary-by-cookie&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.124%;&quot;&gt;String&lt;/td&gt;
&lt;td style=&quot;width: 45.4263%;&quot;&gt;cookie 기반 라우팅 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 43.4496%;&quot;&gt;&lt;span style=&quot;background-color: #efefef;&quot;&gt;nginx.ingress.kubernetes.io/canary-weight&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.124%;&quot;&gt;String&lt;/td&gt;
&lt;td style=&quot;width: 45.4263%;&quot;&gt;weight 기반 라우팅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 43.4496%;&quot;&gt;&lt;span style=&quot;background-color: #efefef;&quot;&gt;nginx.ingress.kubernetes.io/canary-weight-total&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.124%;&quot;&gt;String&lt;/td&gt;
&lt;td style=&quot;width: 45.4263%;&quot;&gt;총 weight 수. (default: 100)&lt;br /&gt;canary-weight-total: 1000&lt;br /&gt;canary-weight: 10 일 때,&lt;br /&gt;1000번 요청 중 10번 요청이 canary 서비스로 요청됨&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;출처: https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/nginx-configuration/annotations.md#canary&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;canary-ingress.yaml&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1672365097405&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: canary-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/canary: &quot;true&quot; # enable canary
    # weight 기반 라우팅 (100번 요청 중 50번)
    nginx.ingress.kubernetes.io/canary-weight: &quot;50&quot;
    # cookie 기반 라우팅 (cookie에 canary=always 로 설정시 canary 서버로 요청, canary=never 일 때 production)
    nginx.ingress.kubernetes.io/canary-by-cookie: &quot;canary&quot; 
    # header 기반 라우팅 (header에 target=canary 로 설정시 canary 서버로 요청, 아니면 production)
    nginx.ingress.kubernetes.io/canary-by-header: &quot;target&quot;
    nginx.ingress.kubernetes.io/canary-by-header-value: &quot;canary&quot;
spec:
  rules:
    - host: canary-test.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: svc-canary
                port:
                  number: 8080&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성 및 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1672365517088&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl apply -f production-ingress.yaml
ingress.networking.k8s.io/production-ingress created
...
$ kubectl get ingress
NAME			CLASS	HOSTS			ADDRESS			PORTS	AGE
production-ingress	nginx	canary-test.com		192.xxx.xxx.xxx		80	10m
canary-ingress		nginx	canary-test.com		192.xxx.xxx.xxx		80	1m&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;http://canary-test.com의 동일한 호스트로 production / canary 서비스가 canary 배포 구성이 완료되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. Test&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5-1. weight 기반 라우팅&lt;/h4&gt;
&lt;pre id=&quot;code_1672365813104&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ curl http://canary-test.com/test
[PRODUCTION] requested.
$ curl http://canary-test.com/test
[CANARY] requested.
$ curl http://canary-test.com/test
[PRODUCTION] requested.
$ curl http://canary-test.com/test
[PRODUCTION] requested.
$ curl http://canary-test.com/test
[CANARY] requested.&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5-2. cookie 기반 라우팅&lt;/h4&gt;
&lt;pre id=&quot;code_1672366040402&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ curl --cookie &quot;canary=always&quot; canary-test.com/test
[CANARY] requested.
$ curl --cookie &quot;canary=always&quot; canary-test.com/test
[CANARY] requested.
$ curl --cookie &quot;canary=always&quot; canary-test.com/test
[CANARY] requested.
$ curl --cookie &quot;canary=always&quot; canary-test.com/test
[CANARY] requested.
$ curl --cookie &quot;canary=never&quot; canary-test.com/test
[PRODUCTION] requested.
$ curl --cookie &quot;canary=never&quot; canary-test.com/test
[PRODUCTION] requested.
$ curl --cookie &quot;canary=never&quot; canary-test.com/test
[PRODUCTION] requested.
$ curl --cookie &quot;canary=never&quot; canary-test.com/test
[PRODUCTION] requested.&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5-3. header 기반 라우팅&lt;/h4&gt;
&lt;pre id=&quot;code_1672366116221&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ curl -H &quot;target:canary&quot; canary-test.com/test
[CANARY] requested.
$ curl -H &quot;target:canary&quot; canary-test.com/test
[CANARY] requested.
$ curl -H &quot;target:canary&quot; canary-test.com/test
[CANARY] requested.
$ curl -H &quot;target:canary&quot; canary-test.com/test
[CANARY] requested.
$ curl -H &quot;target:canary&quot; canary-test.com/test
[CANARY] requested.&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발/Infra</category>
      <category>Canary</category>
      <category>canary release</category>
      <category>Ingress</category>
      <category>K8S</category>
      <category>k8s canary</category>
      <category>Kubernetes</category>
      <category>kubernetes canary</category>
      <category>nginx</category>
      <category>Spring</category>
      <author>지잉지잉</author>
      <guid isPermaLink="true">https://developer-jiing.tistory.com/48</guid>
      <comments>https://developer-jiing.tistory.com/48#entry48comment</comments>
      <pubDate>Fri, 30 Dec 2022 11:09:30 +0900</pubDate>
    </item>
  </channel>
</rss>