<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Victolee</title>
    <link>https://victorydntmd.tistory.com/</link>
    <description>웹 개발</description>
    <language>ko</language>
    <pubDate>Fri, 15 May 2026 10:04:13 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>빅토리_</managingEditor>
    <item>
      <title>[Springboot] 테스트 코드 작성 (2) - 통합 테스트 (@SpringBootTest)</title>
      <link>https://victorydntmd.tistory.com/354</link>
      <description>&lt;p style=&quot;text-align: right;&quot; data-ke-size=&quot;size16&quot;&gt;Updated - 2024. 12. 08&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #333333;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://victorydntmd.tistory.com/353&quot;&gt;테스트 코드 작성 (1) - 단위 테스트&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;color: #333333;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://victorydntmd.tistory.com/354&quot;&gt;테스트 코드 작성 (2) - 통합 테스트&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글에서는 테스트 코드에 대한 전반적인 내용과 단위 테스트 코드를 작성하는 방법에 대해 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 전체 애플리케이션을 테스트 하는 방법에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 코드는&amp;nbsp;&lt;a href=&quot;https://victorydntmd.tistory.com/353&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;의 Guestbook REST-API를 기준으로 작성하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;01. 통합 테스트 코드 작성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통합 테스트 코드는 전체 애플리케이션 컨텍스트(ApplicationContext )를 로드하여 실제 API 호출이 예상대로 동작하는지 확인하기 위해 @SpringBootTest를 사용합니다.&lt;br /&gt;실제 환경과 유사하지만 애플리케이션이 클수록 테스트가 오래 걸린다는 단점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;기본 구조&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 테스트 파일의 기본 구조는 다음과 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1733636560446&quot; class=&quot;actionscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.example.guestbook.domain.entity.GuestbookEntity;
import com.example.guestbook.dto.GuestbookDto;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class GuestbookIntegrationTest {

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    private String getBaseUrl() {
        return &quot;http://localhost:&quot; + port + &quot;/api/guestbook&quot;;
    }

    @Test
    void testCreateGuestbook() {
    	...
        assertThat(createdEntry.getId()).isNotNull();
        assertThat(createdEntry.getName()).isEqualTo(&quot;홍길동&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;@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
&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;@LocalServerPort
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내장 웹 서버(Tomcat 등)를 실행하는 경우, 자동으로 할당된 포트를 얻고 싶을 때 사용하는 애너테이션 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TestRestTemplate
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REST&amp;nbsp;API를&amp;nbsp;호출하여&amp;nbsp;실제&amp;nbsp;HTTP&amp;nbsp;요청/응답을&amp;nbsp;테스트할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;유틸리티&amp;nbsp;클래스입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;getBaseUrl()
&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;/li&gt;
&lt;li&gt;assertThat()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AssertJ에서 제공하는 검증 메서드이며, JUnit과 함께 사용하여 더 직관적으로 검증을 할 수 있게 도와줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1733636560450&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 값 비교
assertThat(actualValue).isEqualTo(expectedValue);  // 값이 같은지 확인
assertThat(actualValue).isNotEqualTo(expectedValue);  // 값이 다른지 확인

// 컬렉션 검증
assertThat(list).hasSize(3);  // 리스트 크기 검증
assertThat(list).contains(&quot;Apple&quot;, &quot;Banana&quot;);  // 리스트에 특정 항목이 포함되어 있는지 확인

// 빈 값 검증
assertThat(actual).isNull();  // 값이 null인지 확인
assertThat(actual).isNotNull();  // 값이 null이 아닌지 확인

// 숫자 비교
assertThat(actual).isGreaterThan(10);  // 값이 10보다 큰지
assertThat(actual).isLessThan(5);  // 값이 5보다 작은지

// 문자열 
assertThat(actual).startsWith(&quot;Hello&quot;);  // 문자열이 특정 접두어로 시작하는지 확인
assertThat(actual).endsWith(&quot;World&quot;);  // 문자열이 특정 접미어로 끝나는지 확인
assertThat(actual).contains(&quot;o&quot;);  // 문자열에 특정 문자가 포함되어 있는지 확인&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 기존 구조를 잡고, 아래에서 테스트 할 함수를 작성하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나씩 작성을 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) POST&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1733636560452&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void testCreateGuestbook() {
    // 1. 방명록 생성
    var newEntry = GuestbookDto.builder()
            .name(&quot;홍길동&quot;)
            .message(&quot;안녕하세요!&quot;)
            .build();

    ResponseEntity&amp;lt;GuestbookEntity&amp;gt; createResponse = restTemplate.postForEntity(getBaseUrl(), newEntry, GuestbookEntity.class);

    // POST 요청 확인
    assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
    GuestbookEntity createdEntry = createResponse.getBody();
    assertThat(createdEntry).isNotNull();
    assertThat(createdEntry.getId()).isNotNull();
    assertThat(createdEntry.getName()).isEqualTo(&quot;홍길동&quot;);
    assertThat(createdEntry.getMessage()).isEqualTo(&quot;안녕하세요!&quot;);

    // 2. 생성된 항목 조회
    ResponseEntity&amp;lt;GuestbookEntity&amp;gt; getResponse = restTemplate.getForEntity(getBaseUrl() + &quot;/&quot; + createdEntry.getId(), GuestbookEntity.class);

    // GET 요청 확인
    assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
    GuestbookEntity retrievedEntry = getResponse.getBody();
    assertThat(retrievedEntry).isNotNull();
    assertThat(retrievedEntry.getId()).isEqualTo(createdEntry.getId());
    assertThat(retrievedEntry.getName()).isEqualTo(&quot;홍길동&quot;);
    assertThat(retrievedEntry.getMessage()).isEqualTo(&quot;안녕하세요!&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;POST 요청을 하여 응답 값을 확인하고, 다시 GET 요청을 하여 정말로 저장이 잘 되었는지 확인하는 테스트입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;postForEntity()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;POST 요청을 보내고, 응답 데이터(상태 코드, 헤더, 본문 데이터)를 ResponseEntity 객체로 반환하는 메서드입니다.&lt;/li&gt;
&lt;li&gt;참고로 post() 메서드를 호출하면 단순히 처리만 할 수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;getForEntity()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마찬가지로 GET 요청을 보내서 응답 데이터를 ResponseEntity 객체로 반환합니다.&lt;/li&gt;
&lt;li&gt;POST 요청한 name, message 값을 비교하여 테스트를 검증합니다.&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 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) UPDATE&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1733636560454&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void testUpdateGuestbook() {
    // 1. 방명록 생성
    var newEntry = GuestbookDto.builder()
            .name(&quot;홍길동&quot;)
            .message(&quot;안녕하세요!&quot;)
            .build();

    ResponseEntity&amp;lt;GuestbookEntity&amp;gt; createResponse = restTemplate.postForEntity(getBaseUrl(), newEntry, GuestbookEntity.class);

    // 생성된 항목 가져오기
    GuestbookEntity createdEntry = createResponse.getBody();
    assertThat(createdEntry).isNotNull();

    // 2. 방명록 수정
    var updatedEntry = GuestbookDto.builder()
            .name(&quot;홍길동&quot;)
            .message(&quot;반갑습니다!&quot;)
            .build();

    restTemplate.put(getBaseUrl() + &quot;/&quot; + createdEntry.getId(), updatedEntry);

    // 3. 수정된 항목 조회
    ResponseEntity&amp;lt;GuestbookEntity&amp;gt; getResponse = restTemplate.getForEntity(getBaseUrl() + &quot;/&quot; + createdEntry.getId(), GuestbookEntity.class);
    GuestbookEntity updatedEntity = getResponse.getBody();

    // 확인
    assertThat(updatedEntity).isNotNull();
    assertThat(updatedEntity.getId()).isEqualTo(createdEntry.getId());
    assertThat(updatedEntity.getMessage()).isEqualTo(&quot;반갑습니다!&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;POST 요청하여 생성된 항목을 가져온 후, message 값을 수정하고 이를 조회하여 값이 변경되었는지 확인하는 테스트입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;put()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PUT 요청을 하는 메서드입니다.&lt;/li&gt;
&lt;li&gt;응답 결과를 GET 요청하여 확인할 것이므로 굳이 ResponseEntity로 반환할 필요는 없어서 PUT 요청 처리만 합니다.&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 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3) DELETE&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1733636560456&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void testDeleteGuestbook() {
    // 1. 방명록 생성
    var newEntry = GuestbookDto.builder()
            .name(&quot;홍길동&quot;)
            .message(&quot;안녕하세요!&quot;)
            .build();

    ResponseEntity&amp;lt;GuestbookEntity&amp;gt; createResponse = restTemplate.postForEntity(getBaseUrl(), newEntry, GuestbookEntity.class);

    // 생성된 항목 가져오기
    GuestbookEntity createdEntry = createResponse.getBody();
    assertThat(createdEntry).isNotNull();

    // 2. 방명록 삭제
    restTemplate.delete(getBaseUrl() + &quot;/&quot; + createdEntry.getId());

    // 3. 삭제된 항목 조회 시 404 응답 확인
    ResponseEntity&amp;lt;GuestbookEntity&amp;gt; getResponse = restTemplate.getForEntity(getBaseUrl() + &quot;/&quot; + createdEntry.getId(), GuestbookEntity.class);
    assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;POST 요청하여 생성된 항목을 다시 삭제하고, 이를 조회하여 요청을 처리할 수 없는지 확인하는 테스트입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;delete()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DELETE 요청하는 메서드입니다.&lt;/li&gt;
&lt;li&gt;PUT 테스트와 마찬가지로 삭제 처리만 하고 GET 요청하여 삭제 여부를 확인합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정상적으로 삭제 되었으면 해당 엔티티는 존재하지 않으므로 404 코드가 응답될 것입니다.&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 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4) Get List&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1733636560458&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void testGetAllGuestbook() {
    // 1. 방명록 여러 항목 생성
    restTemplate.postForEntity(getBaseUrl(), GuestbookDto.builder().name(&quot;홍길동&quot;).message(&quot;안녕하세요!&quot;).build(), GuestbookEntity.class);
    restTemplate.postForEntity(getBaseUrl(), GuestbookDto.builder().name(&quot;이몽룡&quot;).message(&quot;반갑습니다!&quot;).build(), GuestbookEntity.class);

    // 2. 모든 항목 조회
    ResponseEntity&amp;lt;List&amp;gt; getResponse = restTemplate.getForEntity(getBaseUrl(), List.class);

    // 확인
    assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
    List&amp;lt;?&amp;gt; entries = getResponse.getBody();
    assertThat(entries).isNotNull();
    assertThat(entries.size()).isGreaterThanOrEqualTo(2);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;POST 요청으로 2개의 방명록을 생성하고, 리스트를 조회하는 테스트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트는 개수를 확인하는 방식으로 진행하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상으로 통합 테스트 코드를 작성하는 방법에 대해 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위 테스트는 작은 단위로 빠르게 실행할 수 있으므로, CI/CD 파이프라인에서 빠른 피드백을 받을 수 있고,&lt;br /&gt;통합 테스트는 일반적으로 더 많은 자원을 소모하므로, 일정한 주기로 실행하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 Spring Security, Webflux 등의 테스트도 지원하니&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://docs.spring.io/spring-boot/appendix/test-auto-configuration/slices.html&quot;&gt;문서&lt;/a&gt;를 확인해보면 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고&lt;/b&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;a href=&quot;https://docs.spring.io/spring-boot/reference/testing/spring-boot-applications.html#testing.spring-boot-applications.autoconfigured-spring-restdocs.with-mock-mvc&quot;&gt;https://docs.spring.io/spring-boot/reference/testing/spring-boot-applications.html#testing.spring-boot-applications.autoconfigured-spring-restdocs.with-mock-mvc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ksh-coding.tistory.com/53&quot;&gt;https://ksh-coding.tistory.com/53&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>웹 프로그래밍/SpringBoot</category>
      <author>빅토리_</author>
      <guid isPermaLink="true">https://victorydntmd.tistory.com/354</guid>
      <comments>https://victorydntmd.tistory.com/354#entry354comment</comments>
      <pubDate>Sun, 8 Dec 2024 20:00:20 +0900</pubDate>
    </item>
    <item>
      <title>[Springboot] 테스트 코드 작성 (1) - 단위 테스트</title>
      <link>https://victorydntmd.tistory.com/353</link>
      <description>&lt;p style=&quot;text-align: right;&quot; data-ke-size=&quot;size16&quot;&gt;Updated - 2024. 12. 08&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #333333;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://victorydntmd.tistory.com/353&quot;&gt;테스트 코드 작성 (1) - 단위 테스트&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;color: #333333;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://victorydntmd.tistory.com/354&quot;&gt;테스트 코드 작성 (2) - 통합 테스트&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 REST-API를 단위 테스트 코드를 작성하는 방법에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;01. REST-API 방명록 작성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 방명록 예제를 최대한 간단하게 작성하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Guestbook 단위 테스트.PNG&quot; data-origin-width=&quot;319&quot; data-origin-height=&quot;521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgeXh4/btsK9QeGolx/8bQ3SRrDnHvZWj3lkgymXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgeXh4/btsK9QeGolx/8bQ3SRrDnHvZWj3lkgymXk/img.png&quot; data-alt=&quot;패키지 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgeXh4/btsK9QeGolx/8bQ3SRrDnHvZWj3lkgymXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgeXh4%2FbtsK9QeGolx%2F8bQ3SRrDnHvZWj3lkgymXk%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;319&quot; height=&quot;521&quot; data-filename=&quot;Guestbook 단위 테스트.PNG&quot; data-origin-width=&quot;319&quot; data-origin-height=&quot;521&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;패키지 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Gradle&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733623270920&quot; class=&quot;clean&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

    /* h2 */
    runtimeOnly 'com.h2database:h2'

    /* Lombok */
    implementation 'org.projectlombok:lombok:1.18.36'
    annotationProcessor 'org.projectlombok:lombok:1.18.36'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}&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;Lombok을 사용하였습니다.&lt;/li&gt;
&lt;li&gt;DB는 H2를 사용하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Application.yml&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733623270921&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;spring:
  application:
    name: guestbook
  datasource:
    url: jdbc:h2:mem:guestbook
    driverClassName: org.h2.Driver
    username: admin
    password: password
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
  h2:
    console:
      enabled: true&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;H2 접속 정보
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;http://localhost:8080/h2-console 경로에서 테이블 확인할 수 있습니다.&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Controller&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733623270921&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import com.example.guestbook.domain.entity.GuestbookEntity;
import com.example.guestbook.dto.GuestbookDto;
import com.example.guestbook.service.GuestbookService;
import lombok.AllArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@AllArgsConstructor
@RestController
@RequestMapping(&quot;/api/guestbook&quot;)
public class GuestbookController {
    private final GuestbookService service;

    @GetMapping
    public List&amp;lt;GuestbookEntity&amp;gt; getAllGuestbooks() {
        return service.findAll();
    }

    @GetMapping(&quot;/{id}&quot;)
    public GuestbookEntity getGuestbookById(@PathVariable Long id) {
        return service.findById(id);
    }

    @PostMapping
    public GuestbookEntity createGuestbook(@RequestBody GuestbookDto guestbook) {
        GuestbookEntity createGuestbook = GuestbookEntity.builder()
                .name(guestbook.getName())
                .message(guestbook.getMessage())
                .build();
        return service.save(createGuestbook);
    }

    @PutMapping(&quot;/{id}&quot;)
    public GuestbookEntity updateGuestbook(@PathVariable Long id, @RequestBody GuestbookDto updatedGuestbook) {
        GuestbookEntity updateGuestbook = GuestbookEntity.builder()
                .id(id)
                .name(updatedGuestbook.getName())
                .message(updatedGuestbook.getMessage())
                .build();
        return service.save(updateGuestbook);
    }

    @DeleteMapping(&quot;/{id}&quot;)
    public ResponseEntity&amp;lt;Void&amp;gt; deleteGuestbook(@PathVariable Long id) {
        service.deleteById(id);
        return ResponseEntity.noContent().build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Service&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733623270924&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import com.example.guestbook.domain.entity.GuestbookEntity;
import com.example.guestbook.domain.repository.GuestbookRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;

@AllArgsConstructor
@Service
public class GuestbookService {
    private final GuestbookRepository repository;

    public List&amp;lt;GuestbookEntity&amp;gt; findAll() {
        return repository.findAll();
    }

    public GuestbookEntity findById(Long id) {
        return repository.findById(id).orElseThrow(() -&amp;gt; new RuntimeException(&quot;방명록 항목을 찾을 수 없습니다.&quot;));
    }

    public GuestbookEntity save(GuestbookEntity guestbook) {
        return repository.save(guestbook);
    }

    public void deleteById(Long id) {
        repository.deleteById(id);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Repository&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733623270926&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import com.example.guestbook.domain.entity.GuestbookEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface GuestbookRepository extends JpaRepository&amp;lt;GuestbookEntity, Long&amp;gt; {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Entity&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733623270927&quot; class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Builder
public class GuestbookEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String message;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dto&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733623270928&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import lombok.*;

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
@Setter
public class GuestbookDto {
    private String name = &quot;&quot;;
    private String message = &quot;&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;02. 단위 테스트에 대하여&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위 테스트 코드를 작성하기 전에 알아두면 좋을 정보들을 먼저 정리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) 단위 테스트란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위 테스트는 각 계층별로 분리하여 개별 클래스나 메서드를 테스트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위 테스트는 작고 독립적으로 실행될 수 있도록 구성되어야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Controller
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 요청과 응답을 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Service
&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;Repository
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB와의 실제 상호작용을 테스트&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) Mock과 Mocking이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위 테스트에서는 실제 환경(ex. DB, 네트워크, 외부 API 등)을 직접 사용하지 않고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 의존성을 대체할 수 있는 가짜 객체(&lt;b&gt;Mock&lt;/b&gt;)를 만들어 테스트 대상 코드의 동작을 검증합니다.&lt;br /&gt;이 과정을 &lt;b&gt;Mocking&lt;/b&gt;이라고 하며, 이는 테스트의 독립성을 높이고, 테스트가 더 빠르고 신뢰성 있게 동작하도록 만듭니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mock 객체
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Mock 등의 애너테이션으로 선언된 객체&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;Mocking&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트에서 사용되는 가짜(Mock) 객체를 생성하고 (Given)&lt;br /&gt;이를 통해 외부 의존성과의 상호작용을 흉내 내며 (When)&lt;br /&gt;테스트를 검증하는 행위 (Then)&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3) 행위 기반 테스트 (BDD, Behavior-Driven&amp;nbsp;Development)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;테스트 코드는 행위 기반 테스트(&lt;b&gt;BDD&lt;/b&gt;) 방식으로 given, when, then 구조로 작성하는 것이 직관적입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Given
&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;객체를 생성하거나 특정 값을 설정하거나 Mock 객체를 설정하는 등의 작업을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;When
&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;Then
&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4) 테스트 파일 위치&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Maven과 Gradle은 표준 디렉토리 구조(src/main/java 및 src/test/java)를 따르며,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;통합 테스트 코드는 기본적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;src/test/java&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;아래에 위치해야 빌드 도구에서 자동으로 인식됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 테스트 파일은 src/test/java 패키지 하위에 작성합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5) JUnit과 AssertJ &lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;JUnit 5&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;간단하고&lt;span&gt; &lt;/span&gt;&lt;/span&gt;기본적인 단위 테스트하는 데 유용한 라이브러리리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;AssertJ&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;더 복잡하고 체이닝 문법을 통한 읽기 쉬운 코드를 제공하며, 특히 다양한 타입의 객체나 조건을 비교해야 할 때 유리합니다.&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;JUnit 5와 AssertJ는 함께 사용할 수 있습니다&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6) 단위 테스트와 통합 테스트&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;단위 테스트에서는 동작의 관점에서 통합 테스트는 기능의 관점에서 접근하는 것이 좋습니다.&lt;br /&gt;대부분의 비즈니스 로직은 Service와 Repository 계층에서 다루므로, 해당 계층을 중심으로 단위 테스트를 진행하는 것이 좋으며, Controller는 클라이언트-서버 간의 인터페이스나 예외 처리 등을 점검하고 싶을 때 작성하는 것이 좋습니다&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 글에서는 Controller 테스트는 통합 테스트에서 다루고 단위 테스트에서는 비즈니스 로직의 동작을 점검하는데 중점을 두겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;03. 단위 테스트 코드 작성&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Service, Repository를 각각 테스트하는 단위 테스트를 작성해보겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) Service&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GuestbookService 서비스 클래스를 메서드 단위로 테스트하는 기본 구조입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1733623270931&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import com.example.guestbook.domain.entity.GuestbookEntity;
import com.example.guestbook.domain.repository.GuestbookRepository;
import com.example.guestbook.service.GuestbookService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

public class GuestbookServiceTest {

    @Mock
    private GuestbookRepository guestbookRepository;

    @InjectMocks
    private GuestbookService guestbookService;

    private GuestbookEntity guestbookEntity;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        guestbookEntity = new GuestbookEntity(1L, &quot;John Doe&quot;, &quot;Hello, World!&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;@Mock
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대상 클래스의 의존성을 실제 구현체 대신 가짜 객체인 모의 객체(Mock Object)로 생성합니다.&lt;/li&gt;
&lt;li&gt;즉, 테스트 대상 객체의 동작만 검증하기 위해 사용합니다. GustbookRepository는 DB에 접근하는 Bean인데 실제 DB에 접근하지 않더라도 테스트를 할 수 있게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;@InjectMocks
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 대상 클래스에 의존성 객체를 자동으로 주입합니다. 주입될 객체는 @Mock으로 생성된 모의 객체이거나, 수동으로 생성한 다른 객체일 수 있습니다.&lt;/li&gt;
&lt;li&gt;즉, GuestbookService 객체를 생성하며면서 클래스 내부에서 의존하는 GuestbookRepository가 자동으로 주입됩니다. 이때, 주입되는 GuestbookRepository는 @Mock으로 생성된 객체입니다. @Mock으로 생성한 Mock 객체가 @InjectMocks로 주입되는 구조입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;GuestbookEntity
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Entity는 Bean이 아닌 JPA의 EntityManager가 생명 주기를 관리하므로 단순히 객체처럼 사용하기만 하면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;nbsp;@BeforeEach
&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;테스트&amp;nbsp;메서드의&amp;nbsp;실행&amp;nbsp;직전에&amp;nbsp;호출됩니다.&lt;/li&gt;
&lt;li&gt;여기서는 Mockto를 초기화하고 Entity에 객체를 생성하여 할당하는 동작이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MockitoAnnotations.openMocks()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mockito 초기화 : 테스트 클래스에서 @Mock과 @InjectMocks를 사용하려면 Mockito를 초기화해야 합니다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실제 비즈니스 로직 동작을 체크하는 테스트 코드를 작성하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BDD 방식으로 Given, When, Then 구조로 테스트 코드를 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1-1) Guestbook 추가 서비스 로직&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1733623590811&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void testSaveGuestbook() {
    // given
    when(guestbookRepository.save(any(GuestbookEntity.class))).thenReturn(guestbookEntity);

    // when
    GuestbookEntity savedGuestbook = guestbookService.save(guestbookEntity);

    // then
    assertNotNull(savedGuestbook);
    assertEquals(&quot;John Doe&quot;, savedGuestbook.getName());
    assertEquals(&quot;Hello, World!&quot;, savedGuestbook.getMessage());

    verify(guestbookRepository, times(1)).save(any(GuestbookEntity.class));
}&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;given
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;when()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mockito의 메서드로, Mock 객체의 특정 메서드가 호출될 때 동작을 정의합니다.&lt;/li&gt;
&lt;li&gt;괄호 안에 조건으로 지정된 메서드가 호출되었을 때 반환값을 설정하거나 예외를 던지도록 설정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;guestbookRepository.save(any(GuestbookEntity.class))
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mock 객체인 guestbookRepository의 save 메서드가 호출되었을 때의 조건을 지정합니다.&lt;/li&gt;
&lt;li&gt;any(GuestbookEntity.class)는 어떤 GuestbookEntity 객체가 전달되든 조건을 만족하도록 설정합니다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;any&amp;nbsp;:&amp;nbsp;특정&amp;nbsp;클래스의&amp;nbsp;객체라면&amp;nbsp;어떤&amp;nbsp;값이든&amp;nbsp;매칭,&amp;nbsp;매개변수&amp;nbsp;값이&amp;nbsp;중요하지&amp;nbsp;않을&amp;nbsp;때&lt;/li&gt;
&lt;li&gt;eq : 객체 값이 예상한 값과 완전히 동일해야 할 때&lt;/li&gt;
&lt;li&gt;argThat : 객체의 특정 필드나 조건을 검증할 때&lt;/li&gt;
&lt;li&gt;커스텀 Matcher : 조건 검증이 복잡하거나 코드 재사용성을 높이고 싶을 때&lt;/li&gt;
&lt;li&gt;객체 직접 전달 : 생성된 객체와 동일한 객체만 매칭해야 할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;thenReturn(guestbookEntity)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;when()의 조건이 충족될 경우 반환할 값을 지정합니다. 여기서는 setUp() 메서드에서 미리 할당해둔 guestbookEntity 객체를 반환하도록 설정했습니다.&lt;/li&gt;
&lt;li&gt;즉, guestbookRepository.save() 메서드가 호출되면, DB에 실제로 저장되는 것이 아니라 미리 준비된 guestbookEntity 객체를 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;when
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;guestbookService의 save() 메서드를 호출합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;then
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JUnit 5에서 제공하는 assertion 메서드를 사용하여 결과를 검증합니다.&lt;/li&gt;
&lt;li&gt;verify()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Mockito의 메서드로 호출 검증 기능을 합니다. Mock 객체에서 특정 메서드가 호출되었는지, 호출 횟수가 맞는지, 그리고 올바른 인수가 사용되었는지 등을 확인하기 위해 사용됩니다. 즉, 테스트 코드에서 예상대로 메서드가 동작했는지를 검증할 수 있습니다.&lt;/li&gt;
&lt;li&gt;이 코드에서는 guestbookRepository의 save() 메서드가 1번, GuestbookEntity 인수로 호출되었는지 확인 합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&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;검증을 실행하는 verify() 메서드는 다음과 같이 다양한 방식으로 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1733644376552&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1) 호출 횟수 검증
//someMethod()가 1번 호출되었는지 확인
verify(mockObject, times(1)).someMethod()
//someMethod()가 호출되지 않았는지 확인
verify(mockObject, never()).someMethod()
//someMethod()가 최소 2번 호출되었는지 확인
verify(mockObject, atLeast(2)).someMethod()

// 2)메서드 인수 검증
// &quot;test&quot;라는 인수로 메서드 호출 여부 확인
verify(mockObject).someMethod(&quot;test&quot;)
// 어떤 String 인수로 호출되었는지 확인
verify(mockObject).someMethod(any(String.class))


3) 메서드 호출 순서 검증
// firstMethod() 메서드 호출 후 secondMethod()가 호출되었는지 확인
InOrder inOrder = inOrder(mock1, mock2);
inOrder.verify(mock1).firstMethod();
inOrder.verify(mock2).secondMethod();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1-2) Guestbook 조회 Service 로직&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1733624898100&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void testGetGuestbookById() {
    // given
    when(guestbookRepository.findById(1L)).thenReturn(java.util.Optional.of(guestbookEntity));

    // when
    GuestbookEntity foundGuestbook = guestbookService.findById(1L);

    // then
    assertNotNull(foundGuestbook);
    assertEquals(&quot;John Doe&quot;, foundGuestbook.getName());
    assertEquals(&quot;Hello, World!&quot;, foundGuestbook.getMessage());
}&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;given
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Optional
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPA의 findById() 메서드는 반환값으로 항상 Optional&amp;lt;T&amp;gt; 타입을 제공하므로, null 처리에 대한 일관성을 유지하기 위해 Optional을 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;when
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;guestbookService의 findById() 메서드를 호출합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;then
&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) Repository&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GuestbookRepository 레포지토리 클래스를 메서드 단위로 테스트하는 기본 구조입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1733635836606&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.example.guestbook.domain.entity.GuestbookEntity;
import com.example.guestbook.domain.repository.GuestbookRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

import static org.junit.jupiter.api.Assertions.*;

@DataJpaTest
public class GuestbookRepositoryTest {

    @Autowired
    private GuestbookRepository guestbookRepository;

    private GuestbookEntity guestbookEntity;

    @BeforeEach
    void setUp() {
        guestbookEntity = new GuestbookEntity(null, &quot;John Doe&quot;, &quot;Hello, World!&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;@DataJpaTest
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@DataJpaTest는 JPA와 관련된 Spring Data JPA 구성 요소만 초기화하는 테스트 전용 애너테이션입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPA 관련 설정만 로드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 계층(Controller, Service 등)이나 불필요한 빈은 로드하지 않으므로 테스트가 가볍고 빠릅니다.&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;H2 같은 in-memory DB가 의존성으로 포함되어 있다면, 기본적으로 이를 사용하고,&lt;br /&gt;만약 외부 데이터베이스(MySQL, PostgreSQL 등)를 테스트에 사용하고 싶다면 별도의 설정이 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;트랜잭션&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Hibernate DDL 생성 검증
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Entity에 정의된 매핑 정보가 정확한지 확인하며, 잘못된 매핑이 있으면 오류를 발생시킵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;자동 Repository 주입
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Repository로 정의된 컴포넌트가 자동으로 주입되며, 이를 통해 데이터베이스 연산을 검증할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-1) Guestbook Repository 테스트 로직&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1733636403928&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void testSaveGuestbook() {
    // when
    GuestbookEntity savedEntity = guestbookRepository.save(guestbookEntity);

    // then
    assertNotNull(savedEntity.getId());
    assertEquals(&quot;John Doe&quot;, savedEntity.getName());
    assertEquals(&quot;Hello, World!&quot;, savedEntity.getMessage());
}

@Test
void testFindById() {
    // given
    GuestbookEntity savedEntity = guestbookRepository.save(guestbookEntity);

    // when
    GuestbookEntity foundEntity = guestbookRepository.findById(savedEntity.getId()).orElse(null);

    // then
    assertNotNull(foundEntity);
    assertEquals(savedEntity.getId(), foundEntity.getId());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Service 계층 테스트와 크게 다른 부분은 없어서 설명은 생략하겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상으로 단위 테스트를 통해 Service, Repository 계층의 동작을 검증하는 간단한 테스트 코드를 살펴보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 운영 환경에 적합하기에는 무리가 있지만 개념 정도만 확인하면 좋을것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 애플리케이션 전체 기능을 점검하는 &lt;a href=&quot;https://victorydntmd.tistory.com/354&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;통합 테스트 코드를 작성하는 방법&lt;/a&gt;에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>웹 프로그래밍/SpringBoot</category>
      <author>빅토리_</author>
      <guid isPermaLink="true">https://victorydntmd.tistory.com/353</guid>
      <comments>https://victorydntmd.tistory.com/353#entry353comment</comments>
      <pubDate>Sun, 8 Dec 2024 17:00:57 +0900</pubDate>
    </item>
    <item>
      <title>2024 복귀</title>
      <link>https://victorydntmd.tistory.com/352</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;조금 긴 글을 적어보려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발과 관련되지 않은 다소 TMI가 많이 있어요  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코로나 이전의 삶&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 이 단어가 어색할 수 있는데, 코로나는 2020년 2월에 본격적으로 시작이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 이 블로그는 2020년에 멈췄다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코로나 이전 당시 나는 개발이 제일 재미있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모르는 것이 너무 많았고 또 그것을 공부하는게 때로는 압박감이 있었지만 성취감이 더 컸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주말마다 모각코 스터디를 했었는데 아침 10시쯤 카페에 모여서 17~18시까지 각자 자유롭게 공부해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부한 내용들을 공유하는 모임이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연애를 했을 때라 주말에 공부하는게 쉽지 않았는데 짬을 내서라도 참여했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 개발 공부하는게 더 재미있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇이 나를 미치게 만들었을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주니어 시절이었기에 열정도 있었겠지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 시절에 공부를 열심히 해둬야 나중에 좋은 개발자가 될 수 있다는 글을 언제인지 읽었었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인사이드 아웃2를 보면 &quot;불안&quot;이라는 감정이 때로는 &quot;성장&quot;과 연결된다는 것을 보여주었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 딱 그랬던것 같다. 지금 공부하지 않으면 나중에 힘들 것이라고.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불안이라는 감정이 동기가 되어 나를 노트북 앞에 앉혔고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발이 재미는 있었지만 알고 싶은 게 너무 많아서 때로는 벅차기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코로나 시기&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코로나의 공포로 사회적 거리 두기가 시행되었고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나 또한 내가 코로나에 걸려서 가족들과 주변 사람들에게 피해를 끼칠까 봐 누구를 만나기가 꺼려졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코로나가 퍼지고 스터디를 나갈 수 없게 되었고,&amp;nbsp;회사도 재택근무로 바뀌었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하루종일 집에만 있었는데 이때부터 나의 관심사가 조금씩 바뀌기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것은 바로 주식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주식이 폭락하기 직전에 주식을 시작해서 하락빔을 다 맞았고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피 같은 내 돈이 떨어지는 것을 보고 퇴근 후, 주말에 하루종일 유튜브를 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거창한 주식 공부를 한 것은 아니고 생태계를 이해하고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 시기 경제 유튜브가 많아졌고, 정보를 많이 수집할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코로나 시기 동안 나를 포함해 사람들은 주식, 코인에 관심을 많이 가졌고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인플레이션으로 인해 주식 시장은 호황이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;때 돈 벌어서 은퇴를 했다느니,&lt;span&gt; &lt;/span&gt;&lt;/span&gt;돈이 복사된다느니, 침팬지가 주식 골라도 수익이 난다... 등 그랬었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 분위기 속에서 노동의 가치는 희석되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열심히 공부하고 전문성을 기르면 뭐 하나? 내가 잘 때 월급 이상의 돈을 벌 수 있는데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 묵묵히 공부하는 게 더 바보 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나 또한 대단한 수익이 난 것은 아니지만 유의미한 성과는 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 점점 자기 계발과 멀어져 갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 열심히 했으니 게임도 하고, 취미도 만들고, 재밌는 것도 보고...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금은 쉬자.... 조금만 더 쉬자......&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스스로를 합리화했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코로나 이후&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금의 아내에게 모든 시간을 쏟고 열심히 연애한 결과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2023년 12월 어쩌면 인생의 가장 큰 이슈일지도 모를 이벤트인 결혼을 하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거의 연애와 달리 연애하기 바빠서 자기 계발을 할 여유는 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니 어쩌면 자기 계발을 안하는 모습으로 조금씩 길들여지고 있었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 인간으로서 나 자신을 더 가꾸고, 아내와 많은 시간을 보냈기에 결혼을 할 수 있었던 것은 아닐까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자기 계발은 나에게 주요 관심사가 아니었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 문득 정신을 차려보니 내가 개발 공부를 너무 안 하고 있다는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니 정확하게는 회사 일이든 개인적으로든 공부는 하고 있었지만 블로그로 기록하지는 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 시장에서 잘 팔리는 개발자일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객관적으로 나의 기술력은 어떤가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 나보고 갑자기 나가라고 하면 난 어느 회사로 갈 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 시대에 개발자의 수명은? 5년 이후에도 개발 일을 할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문득 자기 객관화가 필요하다는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;블로그에 대하여&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한창 블로그를 열심히 할 때는 방문자가 하루에 적게는 2천 명, 많게는 5천 명까지 유입되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신입 개발자로서 공부했던 것을 정리하는 공간이었고,&amp;nbsp;딱히 블로그가 잘되길 바라고 글을 썼던 것은 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 순수한 마음으로 나와 비슷한 문제를 가진 사람들이 문제 해결을 바라는 마음과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 내가 다시 보려고 나만의 언어로 기록하기 위함이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영향력이 커질수록 뿌듯함, 성취감이 커지고 기뻤지만, 부담도 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 잘못된 내용을 기록하는 건 아닌지,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현업에서 써보지도 않은 것을 혼자 공부해봤다고 마치 아는 것 마냥 기록하는 건 아닌지...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 생각해 보면 내가 공부한 것을 잘 정리하는 것에 왜 부담을 느꼈어야 했었는지 잘 모르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엄청 대단한 사람이라도 된 것처럼 거만했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코로나 이후 자기 계발과 점점 멀어졌고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 부담 때문에 블로그로 손이 안 가게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 다행인지 배운 건 기록을 해야만 직성이 풀리는 성격 탓에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 경험하고 배운 것들은 개인 공간에 잘 기록하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 년 사이에 시대가 많이 바뀌었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 모르는 내용은 구글링 하지 않고 Chat GPT, Claude, Perplexity, Copilot, Cursor AI 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 기반의 툴을 이용하여 문제를 해결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 구글링 보다 히트율이 몇 배는 좋아서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;생산성이 말도 안 되게 좋아졌다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;즉, 블로그 검색을 많이 안 한다는 뜻이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 나는 왜 다시 블로그를 시작하려는 걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 정체성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신을 소개할 때, 재직 중인 회사 말고 나를 소개할 무언가가 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 내가 좋든 싫든 회사를 그만두게 되었을 때, 나를 어떻게 표현할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘은 유튜브, 인스타, 블로그 등 여러 매체를 통해 자신을 브랜딩 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 나중에 무엇을 하게 될지 모르겠지만, 개발자로서 전문성을 나타내는 방법은 블로그가 최적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 블로그를 수단으로 이용할 생각은 없다. 온전히 나의 공부를 기록하는 공간이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자로서 나를 표현할 수 있는 수단으로 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 현실에 안주하지 말자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 기술 공부한다고 업무에서 써먹니?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 능력으로도 충분히 좋은 성과내고 있는데 왜 힘들게 시간 내서 공부해?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거의 내가 이런 생각을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 툴을 많이 사용할수록 편하고 생산성이 좋아졌지만, 점점 내가 바보가 되는 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 알려주는 대로 하면 잘 되지만, 깊이 있는 공부는 꾸준히 해야 남들과 다를 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 AI를 통해 얻은 새로운 지식들을 잘 정리하는 것도 내 몫이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 열정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전부터 장인들을 좋아했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;돈을 많이 벌든 사회적으로 인정 받든 말든 상관없이,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 좋아서 열정적으로 그 일을 해내는 장인들의 정신이 부러웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거창하게 말하면 꿈을 위해 나아가는 사람들이겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자라는 직업으로 일하는 동안은 내 자신이 우스워보이거나 이 일을 가볍게 여기고 싶지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것을 글로써 발자취를 남겨보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 내가 블로그에 손을 댈 수 없었던 (손을 대지 않았던..) 이유와&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 시작하려는 이유를 적어봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자로서 짬을 내서라도 공부했던 그 열정이 식었다는 것은 부끄럽기도 하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 한 인간으로 봤을 때는 한 발씩 나아가고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학창 시절에 아버지가 했던 얘기가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&quot;공부는 때가 있는 거라고. 공부가 잘 안 되고 그럴 땐 쉬는 것도 방법이다.&quot;&lt;/i&gt;라고.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 세월 동안 충분히 잘 쉬었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 더 늦지 않게 다시 시작해보려고 한다. 적절한 때가 온 듯 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 GD도 7년 만에 가수로 컴백했는데, 나는 4년밖에 안 됐다 ^^&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 충분히 젊고, 그동안의 경험들을 잘 기록해보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거처럼 매몰되지는 않더라도 꾸준히 이어나가자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;b&gt;When you feel like quitting remember why you started.&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그만두고 싶을 때 왜 시작했는지 기억하라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초심을 잃지 말라는 뜻.&lt;/p&gt;
&lt;div id=&quot;gtx-trans&quot; style=&quot;position: absolute; left: 300px; top: 180px;&quot;&gt;
&lt;div class=&quot;gtx-trans-icon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>끄적끄적/일기</category>
      <author>빅토리_</author>
      <guid isPermaLink="true">https://victorydntmd.tistory.com/352</guid>
      <comments>https://victorydntmd.tistory.com/352#entry352comment</comments>
      <pubDate>Sun, 10 Nov 2024 17:41:46 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot] Couchbase 연동하기</title>
      <link>https://victorydntmd.tistory.com/349</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 글에서는 Springboot에 Couchbase를 연동하는 방법에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;Couchbase는 docker로 올리고, Springboot에서 JSON 로그를 &lt;span style=&quot;color: #333333;&quot;&gt;couchbase에 &lt;/span&gt;insert, select하는 간단한 예제를 소개합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;전체 소스는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/victolee93/Springboot-couchbase-exam&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;u&gt;깃헙&lt;/u&gt;&lt;/a&gt;을 참고하시기 바랍니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;01. 개발 환경 셋팅&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) 프로젝트 구조&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;385&quot; data-origin-height=&quot;421&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOcoc6/btqNq0MA2dH/Nk7g8ks2wPf5XS8Af7p7lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOcoc6/btqNq0MA2dH/Nk7g8ks2wPf5XS8Af7p7lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOcoc6/btqNq0MA2dH/Nk7g8ks2wPf5XS8Af7p7lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOcoc6%2FbtqNq0MA2dH%2FNk7g8ks2wPf5XS8Af7p7lk%2Fimg.png&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;385&quot; data-origin-height=&quot;421&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) build.gradle&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1605426157668&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    
    /* couchbase */
    compile group: 'org.springframework.data', name: 'spring-data-couchbase', version: '4.0.3.RELEASE'
    
    /* lombok */
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3) Couchbase 컨테이너 실행&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;Couchbase 컨테이너는 &lt;u&gt;&lt;a href=&quot;https://hub.docker.com/_/couchbase&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 이미지&lt;/a&gt;&lt;/u&gt;를 사용하며, 문서에 실행 방법에 대한 설명이 잘나와 있으므로 참고하시길 바랍니다.&lt;/p&gt;
&lt;p&gt;Docker가 설치되어 있지 않다면 &lt;u&gt;&lt;a href=&quot;https://victorydntmd.tistory.com/346&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이글&lt;/a&gt;&lt;/u&gt;을 참고해주세요!&lt;/p&gt;
&lt;pre id=&quot;code_1605418516923&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# docker run -d --name db -p 8091-8094:8091-8094 -p 11210:11210 couchbase:community-6.5.0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;146&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mYm6b/btqNqZ7XvDB/Z4qKpKG8X2TYhQWwZ3apr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mYm6b/btqNqZ7XvDB/Z4qKpKG8X2TYhQWwZ3apr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mYm6b/btqNqZ7XvDB/Z4qKpKG8X2TYhQWwZ3apr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmYm6b%2FbtqNqZ7XvDB%2FZ4qKpKG8X2TYhQWwZ3apr1%2Fimg.png&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;146&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;여기서는 cummunity-6.5.0 이미지를 다운받아 실행했지만, 다른 버전으로 진행해도 상관없을겁니다.&lt;/p&gt;
&lt;p&gt;컨테이너를 실행했으면 &lt;span&gt;&lt;u&gt;&lt;a href=&quot;http://localhost:8091/ui/index.html&quot;&gt;http://localhost:8091/ui/index.html&lt;/a&gt;&lt;/u&gt;에 접근하여 클러스터를 설정해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이 글에서 설정한 클러스터 정보는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cluster Name : exam&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; letter-spacing: 0px;&quot;&gt;Create Admin Username : victolee&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; letter-spacing: 0px;&quot;&gt;Create Password : 123456&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000; letter-spacing: 0px;&quot;&gt;설정을 마치고 아래의 대시보드로 진입했으면 끝입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1813&quot; data-origin-height=&quot;515&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m2zjC/btqNpH1eDqt/Bwj64v0CmGMvlMHpdbsVzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m2zjC/btqNpH1eDqt/Bwj64v0CmGMvlMHpdbsVzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m2zjC/btqNpH1eDqt/Bwj64v0CmGMvlMHpdbsVzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm2zjC%2FbtqNpH1eDqt%2FBwj64v0CmGMvlMHpdbsVzk%2Fimg.png&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1813&quot; data-origin-height=&quot;515&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;02. 구현하기&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) CouchbaseConfig.java&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1605426334202&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;

@Configuration
@EnableCouchbaseRepositories(basePackages={&quot;com.victolee.couchbaseexam&quot;})
public class CouchbaseConfig extends AbstractCouchbaseConfiguration {
    @Override
    public String getConnectionString() {
        return &quot;couchbase://127.0.0.1&quot;;
    }

    @Override
    public String getBucketName() {
        return &quot;sample_bucket&quot;;
    }

    @Override
    public String getUserName() {
        return &quot;victolee&quot;;
    }

    @Override
    public String getPassword() {
        return &quot;123456&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Couchbase 서버에 접속하기 위한 connection 설정을 합니다.&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;getConnectionString()&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;Couchbase 서버 url을 명시하면 됩니다.&lt;/li&gt;
&lt;li&gt;여기서는 local에 컨테이너를 띄운 것이므로 127.0.0.1을 작성했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;getBuckectName()&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;접근할 버킷명을 작성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;getUserName()&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;Couchbase 클러스터 생성시 기입했던 Admin Username을 작성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;getPassword()&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;Couchbase 클러스터 생성시 기입했던 Admin Password를 작성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div id=&quot;references&quot;&gt;
&lt;p&gt;❗❗&lt;span style=&quot;color: #333333;&quot;&gt;❗&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;주의&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;해당 파일은 DB에 커넥션하는 설정 파일이므로 공개된 저장소에 저장되지 않도록 &lt;b&gt;.gitignore&lt;/b&gt;를 해주시는것이 좋습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) MessageLogController.java&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1605426419317&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.victolee.couchbaseexam.dto.MessageLogDto;
import com.victolee.couchbaseexam.service.MessageLogService;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@AllArgsConstructor
@RequestMapping(value = &quot;/log&quot;)
public class MessageLogController {
    private final MessageLogService messageLogService;

    @GetMapping
    public List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt; getLogList(String startDate, String endDate) {
        List&amp;lt;MessageLogDto&amp;gt; messageLogDtos = messageLogService.getLogList(LocalDateTime.parse(startDate), LocalDateTime.parse(endDate));

        List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt; messageLogList = new ArrayList&amp;lt;&amp;gt;();
        for (MessageLogDto mailLogDto : messageLogDtos) {
            Map&amp;lt;String, Object&amp;gt; result = new HashMap&amp;lt;&amp;gt;();
            result.put(&quot;sender&quot;, mailLogDto.getSender());
            result.put(&quot;receiver&quot;, mailLogDto.getReceiver());
            result.put(&quot;content&quot;, mailLogDto.getContent());
            result.put(&quot;date&quot;, mailLogDto.getDate());
            messageLogList.add(result);
        }

        return messageLogList;
    }

    @PostMapping
    public String setLog(MessageLogDto messageLogDto) {
        messageLogService.setLog(messageLogDto);

        return &quot;success&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;컨트롤러를 작성합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;getLostList()&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;파라미터인 두 일시 사이에 존재하는 로그 리스트를 조회하는 API&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;setLog()&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;로그를 기록하는 API&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3) MessageLogDocument.java&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1605426432150&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.Field;
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
import org.springframework.data.couchbase.core.mapping.id.GenerationStrategy;

@Builder
@Data
@AllArgsConstructor
@Document
public class MessageLogDocument {
    @Id
    @GeneratedValue(strategy = GenerationStrategy.UNIQUE)
    final String id;

    @Field
    String sender;

    @Field
    String receiver;

    @Field
    String content;

    @Field
    @CreatedDate
    String date;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;실제 DB 컬럼과 일치하는 domain을 작성합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4) MessageLogDto.java&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1605426441943&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import lombok.*;

import java.time.LocalDateTime;

@Builder
@Getter
@Setter
@ToString
@AllArgsConstructor
public class MessageLogDto {
    private String id;
    private String sender;
    private String receiver;
    private String content;
    private LocalDateTime date;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;데이터 전달용 dto를 작성합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;5) MessageLogService.java&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1605426453588&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.victolee.couchbaseexam.domain.document.MessageLogDocument;
import com.victolee.couchbaseexam.domain.repository.MessageLogRepository;
import com.victolee.couchbaseexam.dto.MessageLogDto;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Service
@AllArgsConstructor
public class MessageLogService {
    private final MessageLogRepository messageLogRepository;

    /**
     * get log
     */
    public List&amp;lt;MessageLogDto&amp;gt; getLogList(LocalDateTime startDateTime, LocalDateTime endDateTime) {
        List&amp;lt;MessageLogDto&amp;gt; messageLogDtos = new ArrayList&amp;lt;&amp;gt;();
        List&amp;lt;MessageLogDocument&amp;gt; messageLogDocuments = messageLogRepository.findAllByDateBetween(startDateTime, endDateTime);

        for (MessageLogDocument messageLogDocument : messageLogDocuments) {
            messageLogDtos.add(this.convertDocumentToDto(messageLogDocument));
        }

        return messageLogDtos;
    }

    /**
     * add log
     */
    public void setLog(MessageLogDto messageLogDto) {
        messageLogRepository.save(this.createLogDocument(messageLogDto));
    }

    /**
     * convert Dto to Document
     */
    private MessageLogDocument createLogDocument(MessageLogDto messageLogDto) {
        return MessageLogDocument.builder()
                .content(messageLogDto.getContent())
                .sender(messageLogDto.getSender())
                .receiver(messageLogDto.getReceiver())
                .date(LocalDateTime.now())
                .build();
    }

    /**
     * convert Document to Dto
     */
    private MessageLogDto convertDocumentToDto(MessageLogDocument messageLogDocument) {
        return MessageLogDto.builder()
                .sender(messageLogDocument.getSender())
                .receiver(messageLogDocument.getReceiver())
                .content(messageLogDocument.getContent())
                .date(messageLogDocument.getDate())
                .build();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;로그 리스트를 조회하고, 추가하는 비즈로직을 작성합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6) MessageLogRepository.java&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1605426463338&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.victolee.couchbaseexam.domain.document.MessageLogDocument;
import org.springframework.data.repository.CrudRepository;

import java.time.LocalDateTime;
import java.util.List;

public interface MessageLogRepository extends CrudRepository&amp;lt;MessageLogDocument, String&amp;gt; {
    List&amp;lt;MessageLogDocument&amp;gt; findAllByDateBetween(LocalDateTime startDateTime, LocalDateTime endDateTime);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다른 Spring Data에서 사용하는 것처럼 couchbase도 CrudRepository 인터페이스를 상속받아서 사용합니다.&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;findAllByDateBetween&lt;/span&gt;는 API 파라미터 두 날짜 사이에 기록된 로그를 조회하기 위해 추가한 메서드입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;03. 테스트&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) Couchbase 어드민 셋팅&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;우선 테스트하기에 앞서, Couchbase dashboard의 Buckets탭에서 버킷을 생성합니다.&lt;/p&gt;
&lt;p&gt;버킷명은 &lt;span style=&quot;color: #009a87;&quot;&gt;CouchbaseConfig&lt;/span&gt;에서 작성한 것과 동일하게 &lt;b&gt;sample_bucket&lt;/b&gt;으로 생성해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1807&quot; data-origin-height=&quot;501&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coYePN/btqNq00ajpy/afb9umKxDwpf5vklkWipvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coYePN/btqNq00ajpy/afb9umKxDwpf5vklkWipvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coYePN/btqNq00ajpy/afb9umKxDwpf5vklkWipvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoYePN%2FbtqNq00ajpy%2Fafb9umKxDwpf5vklkWipvK%2Fimg.png&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1807&quot; data-origin-height=&quot;501&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음으로 Query 탭에서 인덱스를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1605427634885&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE PRIMARY INDEX ON sample_bucket;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1404&quot; data-origin-height=&quot;481&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b11r4r/btqOxru6Nib/3gPsz7RpPWE7eKXc4fsDS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b11r4r/btqOxru6Nib/3gPsz7RpPWE7eKXc4fsDS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b11r4r/btqOxru6Nib/3gPsz7RpPWE7eKXc4fsDS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb11r4r%2FbtqOxru6Nib%2F3gPsz7RpPWE7eKXc4fsDS0%2Fimg.png&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1404&quot; data-origin-height=&quot;481&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) 메시지 등록&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;이제 postman으로 API를 호출하여, 메시지를 작성해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1219&quot; data-origin-height=&quot;369&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C5hH1/btqOuIqO4HF/E9GngHbrdrmyo7RGArVVkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C5hH1/btqOuIqO4HF/E9GngHbrdrmyo7RGArVVkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C5hH1/btqOuIqO4HF/E9GngHbrdrmyo7RGArVVkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC5hH1%2FbtqOuIqO4HF%2FE9GngHbrdrmyo7RGArVVkk%2Fimg.png&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1219&quot; data-origin-height=&quot;369&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;메시지는 Couchbase 어드민의 Documents 탭에서 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;495&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cY8RRd/btqOtg9IoZM/ORHSZSWvSJkkhEPGnykc71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cY8RRd/btqOtg9IoZM/ORHSZSWvSJkkhEPGnykc71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cY8RRd/btqOtg9IoZM/ORHSZSWvSJkkhEPGnykc71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcY8RRd%2FbtqOtg9IoZM%2FORHSZSWvSJkkhEPGnykc71%2Fimg.png&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;495&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;날짜 조회가 정상적으로 수행되는지 확인하기 위해 시간 간격을 조금 두고 메시지를 여러개 등록해두면 좋습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3) 메시지 검색&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;다음으로 로그 리스트를 조회해보도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2020-11-29 기준으로 조회해보면 다음과 같이 총 2개의 리스트가 출력이 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1221&quot; data-origin-height=&quot;645&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgtJLi/btqOtgPoaSW/PKGvwUUqKI1pGby4VEZUM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgtJLi/btqOtgPoaSW/PKGvwUUqKI1pGby4VEZUM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgtJLi/btqOtgPoaSW/PKGvwUUqKI1pGby4VEZUM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgtJLi%2FbtqOtgPoaSW%2FPKGvwUUqKI1pGby4VEZUM0%2Fimg.png&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1221&quot; data-origin-height=&quot;645&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;시간을 수정해서 API를 조회해도 정상적으로 조회되는것을 확인할수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;548&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLQhZa/btqOzB5kJg8/TNC0KLtBqIALSrIHPXVZI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLQhZa/btqOzB5kJg8/TNC0KLtBqIALSrIHPXVZI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLQhZa/btqOzB5kJg8/TNC0KLtBqIALSrIHPXVZI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLQhZa%2FbtqOzB5kJg8%2FTNC0KLtBqIALSrIHPXVZI0%2Fimg.png&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;548&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이상으로 Springboot에서 Couchbase를 연동하고 간단한 테스트를 해보았습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;참고 자료&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;docs.spring.io/spring-data/couchbase/docs/current/reference/html/#reference&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>웹 프로그래밍/SpringBoot</category>
      <author>빅토리_</author>
      <guid isPermaLink="true">https://victorydntmd.tistory.com/349</guid>
      <comments>https://victorydntmd.tistory.com/349#entry349comment</comments>
      <pubDate>Sun, 29 Nov 2020 14:16:24 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot] Kafka 연동하기</title>
      <link>https://victorydntmd.tistory.com/348</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 글에서는 Springboot에 Kafka를 연동하는 방법에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;Kafka가 궁금하시다면 &lt;u&gt;&lt;a href=&quot;https://victorydntmd.tistory.com/344&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이글&lt;/a&gt;&lt;/u&gt;을 참고해주세요!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 글에서 다루는 내용은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;Springboot에서 Kafka의 특정 Topic에 메시지를 생산(Produce)하고 해당 Topic을 Listen합니다.&lt;/p&gt;
&lt;p&gt;Kafka 서버에 해당 메시지가 전달되고, Springboot에서 이를 소비(Consume)할 준비가 되면 메시지를 pull 하는 아주아주 간단한 예제입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;전체 소스는 &lt;u&gt;&lt;a href=&quot;https://github.com/victolee93/Springboot-Kafka-exam&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;깃헙&lt;/a&gt;&lt;/u&gt;을 참고하시기 바랍니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;01. 개발 환경 셋팅&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) 프로젝트 구조&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;248&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dTmCAF/btqNpd0nR2l/r2CppUdQVcKXaXm9DjDFuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dTmCAF/btqNpd0nR2l/r2CppUdQVcKXaXm9DjDFuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dTmCAF/btqNpd0nR2l/r2CppUdQVcKXaXm9DjDFuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdTmCAF%2FbtqNpd0nR2l%2Fr2CppUdQVcKXaXm9DjDFuK%2Fimg.png&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;248&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) Kafka 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;Kafka 설치는 &lt;span style=&quot;color: #333333;&quot;&gt;&lt;u&gt;&lt;a href=&quot;https://victorydntmd.tistory.com/347&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Docker로 설치하는 글&lt;/a&gt;&lt;/u&gt;과 &lt;/span&gt;&lt;u&gt;&lt;a href=&quot;https://victorydntmd.tistory.com/345&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Window에서 설치하는 글&lt;/a&gt;&lt;/u&gt;을 참고해주시길 바랍니다.&lt;/p&gt;
&lt;p&gt;이 글에서는 Docker로 설치한 것을 기준으로 작성하였는데, 시스템에 Kafka만 설치되어 있다면 실행에 크게 문제가 발생하진 않을듯 합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3) build.gradle&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1598608160456&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'

    /* kafka */
    implementation 'org.springframework.kafka:spring-kafka'

    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;kafka 연동을 위해 &lt;u&gt;&lt;a href=&quot;https://mvnrepository.com/artifact/org.springframework.kafka/spring-kafka&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;spring-kafka&lt;/a&gt;&lt;/u&gt; 의존성을 추가합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;02. 구현하기&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) application.yml&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1598608206222&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  kafka:
    consumer:
      bootstrap-servers: localhost:9092
      group-id: foo
      auto-offset-reset: earliest
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    producer:
      bootstrap-servers: localhost:9092
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;consumer와 producer에 대한 설정을 해줍니다.&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;spring.kafka.consumer&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;&amp;nbsp;bootstrap-servers
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;Kafka 클러스터에 대한 초기 연결에 사용할 &lt;span style=&quot;color: #009a87;&quot;&gt;호스트:포트&lt;/span&gt;쌍의 쉼표로 구분된 목록입니다.&lt;/li&gt;
&lt;li&gt;글로벌 설정이 있어도, consumer.bootstrap-servers가 존재하면 consuemer 전용으로 오버라이딩 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;group-id
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;Consumer는 Consumer Group이 존재하기 때문에, 유일하게 식별 가능한 Consumer Group을 작성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;auto-offset-reset
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;Kafka 서버에 초기 offset이 없거나, 서버에 현재 offset이 더 이상 없는 경우 수행할 작업을 작성합니다.&lt;/li&gt;
&lt;li&gt;Consumer Group의 Consumer는 메시지를 소비할 때 Topic내에 Partition에서 다음에 소비할 offset이 어디인지 공유를 하고 있습니다. 그런데 오류 등으로 인해. 이러한 offset 정보가 없어졌을 때 어떻게 offeset을 reset 할 것인지를 명시한다고 보시면 됩니다.
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;latest : 가장 최근에 생산된 메시지로 offeset reset&lt;/li&gt;
&lt;li&gt;earliest : 가장 오래된 메시지로 offeset reset&lt;/li&gt;
&lt;li&gt;none : offset 정보가 없으면 Exception 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;직접 Kafka Server에 접근하여 offset을 reset할 수 있지만, Spring에서 제공해주는 방식은 위와 같습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;key-deserializer / value-deserializer
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;Kafka에서 데이터를 받아올 때, key / value를 역직렬화 합니다.&lt;/li&gt;
&lt;li&gt;여기서 key와 value는 뒤에서 살펴볼 KafkaTemplate의 key, value를 의미합니다.&lt;/li&gt;
&lt;li&gt;이 글에서는 메시지가 문자열 데이터이므로 StringDeserializer를 사용했습니다. JSON 데이터를 넘겨줄 것이라면 JsonDeserializer도 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;spring.kafka.producer&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp;bootstrap-servers&lt;/span&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;consumer.bootstrap-servers와 동일한 내용이며, producer 전용으로 오버라이딩 하려면 작성합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;key-serializer / &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;value-serializer&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;Kafka에 데이터를 보낼 때, key / value를 직렬화 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;consumer에서 살펴본 key-deserializer, value-deserializer와 동일한 내용입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;더 많은 설정 내용은 여기서 다룰수 없기 때문에 &lt;u&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;&lt;/u&gt;에서 kafka를 검색하셔서 참고해주시기 바랍니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;references&quot;&gt;
&lt;p&gt;  참고&lt;/p&gt;
&lt;p&gt;여기서는 Producer/Consumer 설정을 application.yml에 작성했지만, bean을 통해 설정하는 방법도 있습니다.&lt;/p&gt;
&lt;p&gt;Producer, Consumer의 설정을 여러 개로 관리하고 싶다면 bean으로 구현하는 것도 좋은 방법일듯 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) KafkaController.java&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1598608568992&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.victolee.kafkaexam.Service.KafkaProducer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = &quot;/kafka&quot;)
public class KafkaController {
    private final KafkaProducer producer;

    @Autowired
    KafkaController(KafkaProducer producer) {
        this.producer = producer;
    }

    @PostMapping
    public String sendMessage(@RequestParam(&quot;message&quot;) String message) {
        this.producer.sendMessage(message);

        return &quot;success&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;post 방식으로 message 데이터를 받아서, Producer 서비스로 전달합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3) KafkaProducer.java&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1598608585802&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class KafkaProducer {
    private static final String TOPIC = &quot;exam&quot;;
    private final KafkaTemplate&amp;lt;String, String&amp;gt; kafkaTemplate;

    @Autowired
    public KafkaProducer(KafkaTemplate kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void sendMessage(String message) {
        System.out.println(String.format(&quot;Produce message : %s&quot;, message));
        this.kafkaTemplate.send(TOPIC, message);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;u&gt;&lt;a href=&quot;https://docs.spring.io/spring-kafka/api/org/springframework/kafka/core/KafkaTemplate.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;KafkaTemplate&lt;/a&gt;&lt;/u&gt;에 Topic명과 Message를 전달합니다.&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;KafkaTemplate.send()&lt;/span&gt; 메서드가 실행되면, Kafka 서버로 메시지가 전송됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4) KafkaConsumer.java&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1598608603036&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;

import java.io.IOException;

@Service
public class KafkaConsumer {

    @KafkaListener(topics = &quot;exam&quot;, groupId = &quot;foo&quot;)
    public void consume(String message) throws IOException {
        System.out.println(String.format(&quot;Consumed message : %s&quot;, message));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kafka로부터 메시지를 받으려면 &lt;u&gt;&lt;a href=&quot;https://docs.spring.io/spring-kafka/api/org/springframework/kafka/annotation/KafkaListener.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;@KafkaListener&lt;/a&gt;&lt;/u&gt; 어노테이션을 달아주면 됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;03. 테스트&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) Springboot &amp;amp; Kafka연동 확인&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;테스트를 해보기전에, Kafka가 잘 실행되고 있는지 확인을 해보시기 바랍니다.&lt;/p&gt;
&lt;p&gt;Springboot 애플리케이션 실행시 아래의 이미지처럼 커넥션 실패 로그가 계속 출력이 되면, Kafka 서버가 실행이 안됐다던지 등의 이유로 연동이 안되고 있는 상태이므로 우선적으로 해결이 필요합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;878&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mxoqt/btqNpHUsCBV/CULQajKXGGCFZOFjSITM0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mxoqt/btqNpHUsCBV/CULQajKXGGCFZOFjSITM0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mxoqt/btqNpHUsCBV/CULQajKXGGCFZOFjSITM0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMxoqt%2FbtqNpHUsCBV%2FCULQajKXGGCFZOFjSITM0K%2Fimg.png&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;878&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;애플리케이션이 실행되면 Kafka 서버와 커넥션이 이루어지는데, Cosunmer의 &lt;span style=&quot;color: #009a87;&quot;&gt;@KafkaListener&lt;/span&gt;에서 설정한 exam 토픽을 자동으로 생성하는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;71&quot; width=&quot;483&quot; height=&quot;57&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GpNa8/btqNpGBbWPo/EGWycuKu0uXGsJaEgyDgP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GpNa8/btqNpGBbWPo/EGWycuKu0uXGsJaEgyDgP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GpNa8/btqNpGBbWPo/EGWycuKu0uXGsJaEgyDgP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGpNa8%2FbtqNpGBbWPo%2FEGWycuKu0uXGsJaEgyDgP1%2Fimg.png&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;71&quot; width=&quot;483&quot; height=&quot;57&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;참고로 이는 broker의 설정과 관련이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;auto.create.topcis.enable&lt;/span&gt;이 설정되어 있으면 topci이 없을경우 topic을 자동으로 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;54&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k4Mc9/btqNpfDvp9V/1dkF0ruApLcKjO50cFTXUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k4Mc9/btqNpfDvp9V/1dkF0ruApLcKjO50cFTXUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k4Mc9/btqNpfDvp9V/1dkF0ruApLcKjO50cFTXUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk4Mc9%2FbtqNpfDvp9V%2F1dkF0ruApLcKjO50cFTXUk%2Fimg.png&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;54&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) 메시지 pub/sub&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;Springboot와 Kafka 연동이 확인되었으므로 이제 메시지 pub/sub 테스트를 해보겠습니다.&lt;/p&gt;
&lt;p&gt;먼저 Kafka 컨테이너에서 exam 토픽에 메시지가 전송되었는지 확인해봅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1598609194118&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# docker exec -it {컨테이너명} bash 
# kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic exam&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1682&quot; data-origin-height=&quot;125&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzPLKN/btqNpHz9RZT/w3S2KIwZF1hm6RW6Aohluk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzPLKN/btqNpHz9RZT/w3S2KIwZF1hm6RW6Aohluk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzPLKN/btqNpHz9RZT/w3S2KIwZF1hm6RW6Aohluk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzPLKN%2FbtqNpHz9RZT%2Fw3S2KIwZF1hm6RW6Aohluk%2Fimg.png&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1682&quot; data-origin-height=&quot;125&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;아직은 메시지 발송된 것이 없으니 아무것도 출력되고 있지 않습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 포스트맨으로 API를 call 합니다.&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;응답은 success가 출력되었으니, 정상 실행된 것을 확인할수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;418&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EErfN/btqNqB0vvvF/7naTdkeTFIFOiPKAEjxZc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EErfN/btqNqB0vvvF/7naTdkeTFIFOiPKAEjxZc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EErfN/btqNqB0vvvF/7naTdkeTFIFOiPKAEjxZc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEErfN%2FbtqNqB0vvvF%2F7naTdkeTFIFOiPKAEjxZc0%2Fimg.png&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;418&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음으로 &lt;span style=&quot;color: #333333;&quot;&gt;Kafka 컨테이너의 topic 메시지를 확인해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1606633659651&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; # kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic exam&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;41&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buspdo/btqNwv5NhG9/PHD39m7YqDviMrDNPlOUs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buspdo/btqNwv5NhG9/PHD39m7YqDviMrDNPlOUs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buspdo/btqNwv5NhG9/PHD39m7YqDviMrDNPlOUs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbuspdo%2FbtqNwv5NhG9%2FPHD39m7YqDviMrDNPlOUs0%2Fimg.png&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;41&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;메시지를 publish하면 로그를 남기도록 Producer에서 작성했으므로 Springboot에서도 로그를 확인해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;791&quot; data-origin-height=&quot;152&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFJR59/btqNti6XIQt/PKtqNrikM4OUlfZy3A4wwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFJR59/btqNti6XIQt/PKtqNrikM4OUlfZy3A4wwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFJR59/btqNti6XIQt/PKtqNrikM4OUlfZy3A4wwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFJR59%2FbtqNti6XIQt%2FPKtqNrikM4OUlfZy3A4wwk%2Fimg.png&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;791&quot; data-origin-height=&quot;152&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;produce와 consume 메시지가 잘 출력되고 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이상으로 Springboot에서 Kafka를 연동해보는 간단한 예제를 구현해보았습니다.&lt;/p&gt;
&lt;p&gt;더 많은 사용방법은 역시 &lt;a href=&quot;https://docs.spring.io/spring-kafka/reference/html/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;를&amp;nbsp;참고하시면 좋을듯 합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;참고 자료&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;https://docs.spring.io/spring-kafka/reference/html/&lt;/li&gt;
&lt;li&gt;https://www.confluent.io/blog/apache-kafka-spring-boot-application/&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;https://team-platform.tistory.com/37&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>웹 프로그래밍/SpringBoot</category>
      <author>빅토리_</author>
      <guid isPermaLink="true">https://victorydntmd.tistory.com/348</guid>
      <comments>https://victorydntmd.tistory.com/348#entry348comment</comments>
      <pubDate>Sun, 15 Nov 2020 13:25:55 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] Kafka 환경 구축하기</title>
      <link>https://victorydntmd.tistory.com/347</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 글에서는 local 환경에서 Docker를 이용하여 Kafka 서버를 구축하는 방법에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;01. 도커 이미지 선택&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span&gt;현재&lt;span&gt;(2020-08-26 &lt;/span&gt;기준&lt;span&gt;) Kafka &lt;/span&gt;이미지는 공식 버전이 없으므로 &lt;span&gt;Star&lt;/span&gt;가 제일 많은 이미지를 선택하려고 합니다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ENmla/btqHjW35JHJ/hk5j4qOZXyMPiKogSBj7Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ENmla/btqHjW35JHJ/hk5j4qOZXyMPiKogSBj7Kk/img.png&quot; data-alt=&quot;kafka 이미지 리스트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ENmla/btqHjW35JHJ/hk5j4qOZXyMPiKogSBj7Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FENmla%2FbtqHjW35JHJ%2Fhk5j4qOZXyMPiKogSBj7Kk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;kafka 이미지 리스트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;u&gt;&lt;a href=&quot;https://hub.docker.com/r/wurstmeister/kafka&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;wurstmeister/kafka&lt;/a&gt;&lt;/u&gt; 이미지가 제일 인기가 많네요.&lt;/p&gt;
&lt;p&gt;문서도 잘되어있고, 참조할 레퍼런스도 많은듯하여 해당 이미지를 선택하도록 하겠습니다 ㅎㅎ&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;b&gt;02. 사전 준비&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;599&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eq4nml/btqHhQQiC27/PLqWOKsYBH9rrhGfcpv0YK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eq4nml/btqHhQQiC27/PLqWOKsYBH9rrhGfcpv0YK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eq4nml/btqHhQQiC27/PLqWOKsYBH9rrhGfcpv0YK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feq4nml%2FbtqHhQQiC27%2FPLqWOKsYBH9rrhGfcpv0YK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;599&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span&gt;wurstmeister/kafka 이미지에서 제공하는 문서를 보시면, 사전 준비가 몇가지 필요합니다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;우선 본인의 시스템에서&amp;nbsp;&lt;u&gt;&lt;a href=&quot;https://docs.docker.com/compose/install/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;docker-compose&lt;/a&gt;&lt;/u&gt;를 사용할 수 있는지 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;kafka는 항상 zookeeper가 사전에 실행되어야 하므로, compose로 실행하는 것이 편리합니다.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;Docker for Mac/Windows 또는 Docker Toolbox를 설치하셨다면 이미 docker-compose는 설치가 되어있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1598428639237&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# docker-compose version&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;357&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MQDDO/btqHseozABJ/2q1MyE450VrrncuJy3cBRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MQDDO/btqHseozABJ/2q1MyE450VrrncuJy3cBRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MQDDO/btqHseozABJ/2q1MyE450VrrncuJy3cBRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMQDDO%2FbtqHseozABJ%2F2q1MyE450VrrncuJy3cBRk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;357&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;문서의 나머지 항목들은 docker-compose.yml 파일을 작성할때 필요에 따라 작성해주면 됩니다.&lt;/p&gt;
&lt;p&gt;여기서는 간단하게 환경을 구축할 목적이므로 &lt;span style=&quot;color: #9d9d9d;&quot;&gt;KAFKA_ADVERTISED_HOST_NAME&lt;/span&gt; 부분만 수정하도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;b&gt;03. docker-compose.yml 파일 작성&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;가장 기본이 되는 docker-compose.yml 파일은 다음과 같습니다. ( &lt;u&gt;&lt;a href=&quot;https://raw.githubusercontent.com/wurstmeister/kafka-docker/master/docker-compose.yml&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고링크&lt;/a&gt;&lt;/u&gt; )&lt;/p&gt;
&lt;pre id=&quot;code_1598429252301&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '2'
services:
  zookeeper:
    image: wurstmeister/zookeeper
    container_name: zookeeper
    ports:
      - &quot;2181:2181&quot;
  kafka:
    image: wurstmeister/kafka:2.12-2.5.0
    container_name: kafka
    ports:
      - &quot;9092:9092&quot;
    environment:
      KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;kafka 뿐만 아니라, zookeeper도 작성되어 있는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;문서에 따르면, &lt;span style=&quot;color: #9d9d9d;&quot;&gt;KAFKA_ADVERTISED_HOST_NAME&lt;/span&gt;는 본인의 docker host ip로 수정해주면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;저는 여기서 multi broker를 사용하지 않을 것이므로, localhost(127.0.0.1)을 작성하도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;해당 파일의 작성위치는 편하신 곳에 작성하시면 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;04. 실행 및 테스트&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span&gt;docker-compose.yml 파일을 작성했으니 compose를 실행해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1598430041095&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# docker-compose up -d&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5CvnK/btqHmzOlwCC/kOBokDw1jKimTRRUWagUY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5CvnK/btqHmzOlwCC/kOBokDw1jKimTRRUWagUY1/img.png&quot; data-alt=&quot;compose 실행 &amp;amp;amp;amp; 이미지 다운로드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5CvnK/btqHmzOlwCC/kOBokDw1jKimTRRUWagUY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5CvnK%2FbtqHmzOlwCC%2FkOBokDw1jKimTRRUWagUY1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;compose 실행 &amp;amp; 이미지 다운로드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;이미지를 잘 받았고... done이 되었으니, 실제로 컨테이너가 실행되고 있는지 상태를 확인해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1598430121364&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# docker container ls&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JnqNa/btqHq2hEHBm/krMaOMrdNX87FzZ0OUYs91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JnqNa/btqHq2hEHBm/krMaOMrdNX87FzZ0OUYs91/img.png&quot; data-alt=&quot;container 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JnqNa/btqHq2hEHBm/krMaOMrdNX87FzZ0OUYs91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJnqNa%2FbtqHq2hEHBm%2FkrMaOMrdNX87FzZ0OUYs91%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;container 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;컨테이너도 잘 실행되었네요~&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음으로 실제로 메시지를 생산/소비하면서 잘 동작하는지 확인해봐야겠죠?&lt;/p&gt;
&lt;p&gt;그러기 위해서는 컨테이너에 접근하여 명령어를 직접 실행해봐야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1598430684797&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# docker container exec -it kafka bash
# kafka-topics.sh --list --bootstrap-server localhost:9092&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bv4FCg/btqHgqdlyzu/s5zJEt0llGQdemgcgEgkck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bv4FCg/btqHgqdlyzu/s5zJEt0llGQdemgcgEgkck/img.png&quot; data-alt=&quot;kafka container 접근&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bv4FCg/btqHgqdlyzu/s5zJEt0llGQdemgcgEgkck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbv4FCg%2FbtqHgqdlyzu%2Fs5zJEt0llGQdemgcgEgkck%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;kafka container 접근&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이렇게 kafka-topcis.sh 파일을 실행하여 옵션들을 작성해주면 명령어들을 실행해볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;위의 예시는 토픽 목록을 확인한 것이며, 저의 kafka 서버에는 email이라는 토픽이 존재하고 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;테스트를 하기 위한 Kafka 명령어는 &lt;u&gt;&lt;a href=&quot;https://victorydntmd.tistory.com/345#Quick%20Start&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이글&lt;/a&gt;&lt;/u&gt; 또는 &lt;u&gt;&lt;a href=&quot;https://kafka.apache.org/quickstart&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서 Quick Start&lt;/a&gt;&lt;/u&gt;를 참고하시길 바랍니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이상으로 Docker로 Kafka local 환경을 구축해보았습니다.&lt;/p&gt;
&lt;p&gt;구체적인 사용 방법들은 &lt;u&gt;&lt;a href=&quot;https://hub.docker.com/r/wurstmeister/kafka&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;문서&lt;/a&gt;&lt;/u&gt;를 확인하시길 바랍니다!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;참고 문서&lt;/b&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;a href=&quot;https://hub.docker.com/r/wurstmeister/kafka&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://hub.docker.com/r/wurstmeister/kafka&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/big-data-engineering/hello-kafka-world-the-complete-guide-to-kafka-with-docker-and-python-f788e2588cfc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://medium.com/big-data-engineering/hello-kafka-world-the-complete-guide-to-kafka-with-docker-and-python-f788e2588cfc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div id=&quot;gtx-trans&quot; style=&quot;position: absolute; left: 350px; top: 3562.62px;&quot;&gt;
&lt;div class=&quot;gtx-trans-icon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Cloud/Docker</category>
      <author>빅토리_</author>
      <guid isPermaLink="true">https://victorydntmd.tistory.com/347</guid>
      <comments>https://victorydntmd.tistory.com/347#entry347comment</comments>
      <pubDate>Wed, 26 Aug 2020 17:45:06 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] Docker for Windows 설치하기</title>
      <link>https://victorydntmd.tistory.com/346</link>
      <description>&lt;p&gt;이 글에서는 Window에 Docker를 설치하는 방법에 대해 알아보겠습니다.&lt;br /&gt;Window 외 환경은 아래의 링크들을 참고하시면 되겠습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Docker for Mac ( &lt;a href=&quot;https://docs.docker.com/docker-for-mac/install/&quot;&gt;&lt;u&gt;링크&lt;/u&gt;&lt;/a&gt; )&lt;/li&gt;
&lt;li&gt;AWS EC2 ( &lt;a href=&quot;https://gist.github.com/npearce/6f3c7826c7499587f00957fee62f8ee9&quot;&gt;&lt;u&gt;링크&lt;/u&gt;&lt;/a&gt; )&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;1. 가상화 사용 확인&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;Docker for Windows를 사용하려면 윈도우 10 프로, 엔터프라이즈, 에듀케이션 버전이어야 하며,&lt;/p&gt;
&lt;p&gt;Hyper-V 가상화 기능이 필요합니다.&lt;/p&gt;
&lt;p&gt;따라서 이를 확인하기 위해 &quot;&lt;span style=&quot;color: #000000;&quot;&gt;작업관리자 &amp;gt; 성능&lt;/span&gt;&quot; 탭에서 가상화가 사용중인지 확인합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;491&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PQHok/btqHopxvGax/eZlVpE2lEhHPF24xGTP66K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PQHok/btqHopxvGax/eZlVpE2lEhHPF24xGTP66K/img.png&quot; data-alt=&quot;가상화 사용 가능 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PQHok/btqHopxvGax/eZlVpE2lEhHPF24xGTP66K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPQHok%2FbtqHopxvGax%2FeZlVpE2lEhHPF24xGTP66K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;491&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;가상화 사용 가능 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;가상화 사용으로 설정되어 있지 않으면 설정이 해주어야 합니다.&lt;/p&gt;
&lt;p&gt;가상화 설정 방벙은 바이오스에서 할 수 있는데, 그 방법은 &lt;a href=&quot;https://wjdqh6544.tistory.com/224&quot;&gt;&lt;u&gt;링크&lt;/u&gt;&lt;/a&gt;로 대체합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;2. Hyper-V 사용&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;가상화를 설정했으면, 다음으로 Hyper-V도 실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;554&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brm9OR/btqHhQo9BaB/KybsY543tBvuEwdTUJbgtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brm9OR/btqHhQo9BaB/KybsY543tBvuEwdTUJbgtK/img.png&quot; data-alt=&quot;Hyper-V 기능 켜기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brm9OR/btqHhQo9BaB/KybsY543tBvuEwdTUJbgtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbrm9OR%2FbtqHhQo9BaB%2FKybsY543tBvuEwdTUJbgtK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;554&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Hyper-V 기능 켜기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&quot;제어판 &amp;gt; 프로그램 &amp;gt; 프로그램 및 기능 &amp;gt; Windows 기능 켜기/끄기&quot;에 접근&lt;/li&gt;
&lt;li&gt;Hyper-V 체크&lt;/li&gt;
&lt;li&gt;컴퓨터 재실행&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;재부팅을 한 후에, &quot;제어판 &amp;gt; 시스템 및 보안 &amp;gt; 관리 도구&quot;에 접근하면 &quot;Hyper-V&quot; 관리자가 추가된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;564&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbLdhh/btqHgvzeUU1/FoRUW1oIKZ5bpiaLpWQNx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbLdhh/btqHgvzeUU1/FoRUW1oIKZ5bpiaLpWQNx1/img.png&quot; data-alt=&quot;Hyper-V 관리자 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbLdhh/btqHgvzeUU1/FoRUW1oIKZ5bpiaLpWQNx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbLdhh%2FbtqHgvzeUU1%2FFoRUW1oIKZ5bpiaLpWQNx1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;564&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Hyper-V 관리자 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;3. Docker for Windows &lt;/span&gt;다운로드&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;다음으로 &lt;a href=&quot;https://hub.docker.com/editions/community/docker-ce-desktop-windows&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;u&gt;Docker for Windows를 다운로드&lt;/u&gt;&lt;/a&gt; 하기위해 도커 허브에 접근해야 합니다.&lt;/p&gt;
&lt;p&gt;도커 허브에 접근하려면 회원가입이 필요하므로 회원가입을 진행해주시기 바랍니다.&lt;/p&gt;
&lt;p&gt;위의 링크를 클릭하면 문서에 설치 방법들을 설명해주고 있으니, 참고하시면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxxOij/btqG9BT5PIJ/03onh3xlnUKj5RtUjqI2Lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxxOij/btqG9BT5PIJ/03onh3xlnUKj5RtUjqI2Lk/img.png&quot; data-alt=&quot;Docker for Windows 설치방법&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxxOij/btqG9BT5PIJ/03onh3xlnUKj5RtUjqI2Lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxxOij%2FbtqG9BT5PIJ%2F03onh3xlnUKj5RtUjqI2Lk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Docker for Windows 설치방법&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;안정화된 stable 버전을 다운로드하고, Installer를 실행하면 작업표시줄에 Docker가 실행되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;220&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dugEFO/btqHnkQB7T6/Blu6xcKfJuujT6jQdKIfb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dugEFO/btqHnkQB7T6/Blu6xcKfJuujT6jQdKIfb1/img.png&quot; data-alt=&quot;작업표시줄 Docker 아이콘&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dugEFO/btqHnkQB7T6/Blu6xcKfJuujT6jQdKIfb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdugEFO%2FbtqHnkQB7T6%2FBlu6xcKfJuujT6jQdKIfb1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;220&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;작업표시줄 Docker 아이콘&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;4. &lt;/span&gt;도커 설치 확인&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span&gt;설치가 완료되었으니, 커맨더 창에서 docker 명령어를 실행해보겠습니다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;# docker version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;482&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brRcCL/btqHoqXvQKz/z56V7m9LuAjB3w7XLhKlW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brRcCL/btqHoqXvQKz/z56V7m9LuAjB3w7XLhKlW0/img.png&quot; data-alt=&quot;docker version&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brRcCL/btqHoqXvQKz/z56V7m9LuAjB3w7XLhKlW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrRcCL%2FbtqHoqXvQKz%2Fz56V7m9LuAjB3w7XLhKlW0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;482&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;docker version&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이상으로 Docker for Windows를 설치해보았습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;참고 문서&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;가상화 설정방법 - &lt;a href=&quot;https://wjdqh6544.tistory.com/224&quot;&gt;https://wjdqh6544.tistory.com/224&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Hyper-V 설치 - &lt;a href=&quot;https://blog.djjproject.com/161&quot;&gt;https://blog.djjproject.com/161&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Cloud/Docker</category>
      <author>빅토리_</author>
      <guid isPermaLink="true">https://victorydntmd.tistory.com/346</guid>
      <comments>https://victorydntmd.tistory.com/346#entry346comment</comments>
      <pubDate>Wed, 26 Aug 2020 15:55:58 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] 다운로드 및 Quick Start</title>
      <link>https://victorydntmd.tistory.com/345</link>
      <description>&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이 글에서는 Kafka를 다운로드하고 실행하는 방법에 대해 알아보겠습니다.&lt;/p&gt;&lt;p&gt;윈도우를 기준으로 명령어를 작성하였지만, 리눅스 환경과 실행 파일만 다를 뿐 명령어는 동일합니다.&lt;/p&gt;&lt;p&gt;( &lt;u&gt;&lt;a href=&quot;https://victorydntmd.tistory.com/347&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;도커로 설치하기&lt;/a&gt;&lt;/u&gt; )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;1. Kafka 다운로드&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1) Windows&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;u&gt;&lt;a href=&quot;https://kafka.apache.org/downloads&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;Kafka 공홈&lt;/a&gt;&lt;/u&gt;에서 Kafka를 다운로드 받습니다.&lt;/p&gt;&lt;p&gt;( 현재 최신 버전은 2019년 12월에 Release된&amp;nbsp;2.4 입니다. )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 719px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/996018355E69B0A705&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F996018355E69B0A705&quot; width=&quot;719&quot; height=&quot;349&quot; filename=&quot;1.PNG&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Binary 압축 파일을 다운받습니다.&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;스칼라 버전별로 binary 파일을 제공해주는데, 어떤 버전이라도 돌아가게 하려면 Scala 2.12를 추천한다고 하네요.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Kafka는 스칼라로 개발 되었으므로, 시스템에 JVM이 설치되어 있어야 합니다. ( jdk8 이상 )&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2) linux&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99DD15395E6DC2321A&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99DD15395E6DC2321A&quot; width=&quot;900&quot; height=&quot;386&quot; filename=&quot;1.PNG&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;p&gt;&lt;font face=&quot;맑은 고딕, sans-serif&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;1. wget 패키지 설치&lt;/span&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;# yum -y install wget&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;2. Kafka 다운로드&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;# wget http://mirror.apache-kr.org/kafka/2.4.1/kafka-2.4.1-src.tgz&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;3. 압축 해제&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;# tar -xzf kafka-2.4.1-src.tgz&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;4-1. Java 설치 확인&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;# java -version&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;4-2. Java 설치가 안되어 있으면 설치&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;# yum -y install java-11-openjdk java-11-openjdk-devel&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#008299&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; color: rgb(0, 0, 0);&quot;&gt;( 추가적으로 /etc/profile에서 환경변수까지 잡아주도록 합니다. )&lt;/span&gt;&lt;/font&gt;&lt;/p&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이제 Kafka를 구동시킬 준비가 되었습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;2. Zookeeper 실행&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Apache Zookeeper는 Kafka 클러스터의&amp;nbsp;최신 설정정보 관리, Kafka 서버들의 동기화, 리더 채택 등 클러스터의 서버들이 공유하는 데이터들을&amp;nbsp;관리하기 위해 사용됩니다.&lt;/p&gt;&lt;p&gt;따라서 Kafka server를 실행하기 전에,&amp;nbsp;Zookeeper를 먼저 실행해줘야 합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(0, 130, 153); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;# cd {압축 풀었던 kafka 폴더&lt;/span&gt;&lt;span style=&quot;color: rgb(0, 130, 153); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;}&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(0, 130, 153); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;# &lt;/span&gt;&lt;span style=&quot;color: rgb(0, 130, 153); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;.\bin\windows\zookeeper-server-start.bat .\config\zookeeper.properties&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;font face=&quot;맑은 고딕, sans-serif&quot;&gt;&lt;span style=&quot;font-family: Menlo, Monaco, Consolas, monospace;&quot;&gt;[2020-03-12 20:51:36,024] INFO Reading configuration from: .\config\zookeeper.properties (org.apache.zookeeper.server.quorum.QuorumPeerConfig)&lt;/span&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face=&quot;맑은 고딕, sans-serif&quot;&gt;&lt;span style=&quot;font-family: Menlo, Monaco, Consolas, monospace;&quot;&gt;[2020-03-12 20:51:36,032] WARN \tmp\zookeeper is relative. Prepend .\ to indicate that you're sure! (org.apache.zookeeper.server.quorum.QuorumPeerConfig)
[2020-03-12 20:51:36,034] INFO clientPortAddress is 0.0.0.0/0.0.0.0:2181 (org.apache.zookeeper.server.quorum.QuorumPeerConfig)
...
[2020-03-12 20:51:36,233] INFO binding to port 0.0.0.0/0.0.0.0:2181 (org.apache.zookeeper.server.NIOServerCnxnFactory)
[2020-03-12 20:51:36,252] INFO zookeeper.snapshotSizeFactor = 0.33 (org.apache.zookeeper.server.ZKDatabase)
[2020-03-12 20:51:36,266] INFO Reading snapshot \tmp\zookeeper\version-2\snapshot.1e (org.apache.zookeeper.server.persistence.FileSnap)
[2020-03-12 20:51:36,308] INFO Snapshotting: 0xa6 to \tmp\zookeeper\version-2\snapshot.a6 (org.apache.zookeeper.server.persistence.FileTxnSnapLog)
[2020-03-12 20:51:36,335] INFO Using checkIntervalMs=60000 maxPerMinute=10000 (org.apache.zookeeper.server.ContainerManager)&lt;/span&gt;&lt;/font&gt;&lt;/p&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;윈도우는 /bin/windows 폴더 밑에 batch 파일을 사용하고, 리눅스라면 /bin&amp;nbsp;디렉토리 하위에&amp;nbsp;shell 파일을 사용하면 됩니다.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;zookeeper-server-start.bat&lt;/span&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;zookeeper 서버를 실행하는 파일입니다.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;zookeeper.properties&lt;/span&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;zookeeper 서버 설정 파일입니다.&lt;/li&gt;&lt;li&gt;위의 로그를 보시면 zookeeper를 실행했을 때 포트가 2181로 바인딩 되는데 그 이유는, zookeeper.properties에서&amp;nbsp;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;clientPort=2181&lt;/span&gt;으로 기본 설정되어 있기 때문입니다.&lt;/li&gt;&lt;li&gt;기본 설정외에 추가적인 설정은 &lt;u&gt;&lt;a href=&quot;https://zookeeper.apache.org/doc/r3.1.2/zookeeperAdmin.html#sc_configuration&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;공식문서&lt;/a&gt;&lt;/u&gt;를 참고하시면 도움이 될듯합니다.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(241, 95, 95);&quot;&gt;*** error ***&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(241, 95, 95);&quot;&gt;입력 줄이 너무 깁니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(241, 95, 95);&quot;&gt;명령 구문이 올바르지 않습니다.&lt;/span&gt;&lt;/p&gt;&lt;div&gt;=&amp;gt; 위와 같은 에러가 발생한다면&amp;nbsp;폴더 경로가 길어서 그런 것이니, kafka 폴더를 C드라이브 바로 밑으로 옮긴후에&amp;nbsp;실행하시면 됩니다.&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;3. Kafka 실행&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Zookeeper 서버가 실행된 상태에서 CMD창을 하나 더 열어&amp;nbsp;Kafka 서버를 실행합니다.&lt;/p&gt;
&lt;pre&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(0, 130, 153); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;# .\bin\windows\kafka-server-start.bat .\config\server.properties&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: Menlo, Monaco, Consolas, monospace;&quot;&gt;[2020-03-12 21:25:53,568] INFO Registered kafka:type=kafka.Log4jController MBean (kafka.utils.Log4jControllerRegistration$)
[2020-03-12 21:25:53,857] INFO starting (kafka.server.KafkaServer)
[2020-03-12 21:25:53,858] INFO Connecting to zookeeper on localhost:2181 (kafka.server.KafkaServer)
[2020-03-12 21:25:53,874] INFO [ZooKeeperClient Kafka server] Initializing a new session to localhost:2181. (kafka.zookeeper.ZooKeeperClient)
...
[2020-03-12 21:28:27,881] INFO [GroupMetadataManager brokerId=0] Scheduling loading of offsets and group metadata from __consumer_offsets-48 (kafka.coordinator.group.GroupMetadataManager)
[2020-03-12 21:28:27,882] INFO [GroupMetadataManager brokerId=0] Finished loading offsets and group metadata from __consumer_offsets-42 in 0 milliseconds. (kafka.coordinator.group.GroupMetadataManager)
[2020-03-12 21:28:27,884] INFO [GroupMetadataManager brokerId=0] Finished loading offsets and group metadata from __consumer_offsets-45 in 0 milliseconds. (kafka.coordinator.group.GroupMetadataManager)
[2020-03-12 21:28:27,888] INFO [GroupMetadataManager brokerId=0] Finished loading offsets and group metadata from __consumer_offsets-48 in 0 milliseconds. (kafka.coordinator.group.GroupMetadataManager)&lt;/span&gt;&lt;/p&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;kafka-server-start.bat&lt;/span&gt;는 kafka 서버 실행 파일이며, &lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;server.properties&lt;/span&gt;는 설정 파일입니다.&lt;/li&gt;&lt;li&gt;Kafka 서버를 튜닝하는 작업은 꽤 어려운 일이고, 저도 잘 모르니... 어떤 설정이 있는지에 대한 정보는&amp;nbsp;&lt;u&gt;&lt;a href=&quot;https://kafka.apache.org/documentation/#configuration&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;공식문서&lt;/a&gt;&lt;/u&gt;로 대체하겠습니다.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(241, 95, 95);&quot;&gt;*** error ***&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(241, 95, 95);&quot;&gt;[2020-03-12 21:24:45,131] ERROR Shutdown broker because all log dirs in c:\tmp\kafka-logs have failed (kafka.log.LogManager)&lt;/span&gt;&lt;/p&gt;&lt;p&gt;=&amp;gt; 위와 같은 에러가 발생한다면,&amp;nbsp;c:\tmp 폴더 하위에 있는 kafka-logs 폴더와 zookeeper 폴더를 제거하시길 바랍니다. 서버 실행시 재생성되므로 상관 없습니다. ( &lt;u&gt;&lt;a href=&quot;https://stackoverflow.com/questions/47168342/kafka-1-0-stops-with-fatal-shutdown-error-logs-directory-failed&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;참고&lt;/a&gt;&lt;/u&gt; )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;hr&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p id=&quot;Quick Start&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;4. Quick Start&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Zookeeper와 Kafka 서버를 실행했으니, Topic을 만들어서 메시지를 생산/소비하는 예제를 진행해보겠습니다.&lt;/p&gt;&lt;p&gt;아래의 예제는 &lt;u&gt;&lt;a href=&quot;https://kafka.apache.org/quickstart&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;공식문서 Quicks&lt;/a&gt;&lt;a href=&quot;https://kafka.apache.org/quickstart&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;tart&lt;/a&gt;&lt;/u&gt;를 참고하였습니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;윈도우 : .bat 파일&lt;/li&gt;&lt;li&gt;리눅스 환경 : .sh 파일&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1) Topic&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;⭐&amp;nbsp;Topic 관련 옵션 한번에 보기 (help 명령어)&lt;/b&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; color: rgb(0, 130, 153);&quot;&gt;# &lt;/span&gt;&lt;font color=&quot;#008299&quot; face=&quot;맑은 고딕, sans-serif&quot;&gt;.\bin\windows\kafka-topics.bat --help&lt;/font&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Topic 생성하기&lt;/b&gt;&lt;/p&gt;&lt;p&gt;CMD창을 하나 더 열어서 Topic을 만듭니다.&lt;/p&gt;
&lt;pre&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(0, 130, 153); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;# .\bin\windows\kafka-topics.bat --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic test&lt;/span&gt;&lt;/p&gt;&lt;/pre&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;--create&lt;/span&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;새로운 토픽을 만들 때 사용하는 옵션&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;--bootstrap-server&lt;/span&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;연결할 Kafka&amp;nbsp;서버( host:port )&lt;/li&gt;&lt;li&gt;이 옵션이 추가되면, 직접 Zookeeper에 연결하지 않아도 됩니다.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;--replication-factor&lt;/span&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Partition 복제 수&lt;/li&gt;&lt;li&gt;이 옵션을 사용하지 않으면, 기본 값을 사용합니다.&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;기본 값은 server.properties 파일에서&amp;nbsp;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;default.replication.factor&lt;/span&gt; 항목으로 설정 가능합니다. ( 설정되어 있지 않을 경우, 추가 작성 )&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;--partitions&lt;/span&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Topic이 생성되거나 변경될 때의 Partition 수&lt;/li&gt;&lt;li&gt;이 옵션을 사용하지 않으면, 기본 값을 사용합니다.&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;기본 값은 server.properties 파일에서&amp;nbsp;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;num.partitions&lt;/span&gt; 항목으로 설정 가능합니다.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;--topic&lt;/span&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;create, alter, describe, delete 옵션에 사용할 Topic 명&lt;/li&gt;&lt;li&gt;Topic 이름은 큰따옴표(&quot;)로 묶고, 정규식 사용이 가능하므로&amp;nbsp;\로 escape 합니다.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Topic 목록&lt;/b&gt;&lt;/p&gt;
&lt;pre&gt;&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; color: rgb(0, 130, 153);&quot;&gt;# &lt;/span&gt;&lt;font color=&quot;#008299&quot; face=&quot;맑은 고딕, sans-serif&quot;&gt;.\bin\windows\kafka-topics.bat --list --bootstrap-server localhost:9092&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#008299&quot; face=&quot;맑은 고딕, sans-serif&quot;&gt;&lt;span style=&quot;color: rgb(0, 0, 0);&quot;&gt;test&lt;/span&gt;&lt;/font&gt;&lt;/p&gt;&lt;/pre&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;--list&lt;/span&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;사용 가능한 Topic 목록&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Topic 상세정보&lt;/b&gt;&lt;/p&gt;
&lt;pre&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(0, 130, 153); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;# &lt;/span&gt;&lt;span style=&quot;color: rgb(0, 130, 153); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;.\bin\windows\kafka-topics.bat --describe --topic test --bootstrap-server localhost:9092&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;
Topic: test     PartitionCount: 1       ReplicationFactor: 1    Configs: segment.bytes=1073741824
        Topic: test     Partition: 0    Leader: 0       Replicas: 0     Isr: 0&lt;/span&gt;&lt;/p&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;--describe&lt;/span&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;운영상에 필요한 Topic의 상세정보를 보여줍니다.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Topic 삭제&lt;/b&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; color: rgb(0, 130, 153);&quot;&gt;# .\bin\windows\kafka-topics.bat --delete --topic test --bootstrap-server localhost:9092&lt;/span&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;--delete&lt;/span&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Topic을 삭제하기 위해서는 server.properties 파일에서 &lt;font color=&quot;#008299&quot;&gt;delete.topic.enable=true&lt;/font&gt;&amp;nbsp;설정을 추가해줘야 합니다. ( 서버 재시작 필요 )&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;2) Producer&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;⭐ Producer 관련 옵션 한번에 보기 (help 명령어)&lt;/b&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span style=&quot;font-family: Menlo, Monaco, Consolas, monospace; color: rgb(0, 130, 153);&quot;&gt;# &lt;/span&gt;&lt;span style=&quot;color: rgb(0, 130, 153); font-family: Menlo, Monaco, Consolas, monospace;&quot;&gt;.\bin\windows\kafka-console-producer.bat&lt;/span&gt;&lt;font color=&quot;#008299&quot; face=&quot;맑은 고딕, sans-serif&quot;&gt;&lt;span style=&quot;color: rgb(0, 130, 153); font-family: Menlo, Monaco, Consolas, monospace;&quot;&gt; --help&lt;/span&gt;&lt;/font&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Topic을 다시 생성한 상태에서 해당 Topic으로 메시지를 보냅니다. ( 메시지 생산 )&lt;/p&gt;
&lt;pre&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;# .\bin\windows\kafka-console-producer.bat --broker-list localhost:9092 --topic test&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&amp;gt;hello kafka
&amp;gt;victolee&lt;/p&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;두 개의 메시지를 입력합니다.&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&quot;hello kafka&quot;&lt;/li&gt;&lt;li&gt;&quot;victolee&quot;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;3) Consumer&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;⭐&amp;nbsp;&lt;/b&gt;&lt;b&gt;Consumer&lt;/b&gt;&lt;b&gt;관련 옵션 한번에 보기 (help 명령어)&lt;/b&gt;&lt;/p&gt;&lt;pre&gt;&lt;span style=&quot;font-family: Menlo, Monaco, Consolas, monospace; color: rgb(0, 130, 153);&quot;&gt;# &lt;/span&gt;&lt;span style=&quot;color: rgb(0, 130, 153); font-family: Menlo, Monaco, Consolas, monospace;&quot;&gt;.\bin\windows\kafka-console-consumer.bat&lt;/span&gt;&lt;font color=&quot;#008299&quot; face=&quot;맑은 고딕, sans-serif&quot;&gt;&lt;span style=&quot;font-family: Menlo, Monaco, Consolas, monospace;&quot;&gt; --help&lt;/span&gt;&lt;/font&gt;&lt;/pre&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;test Topic에 &quot;hello kafka&quot;, &quot;victolee&quot; 메시지를 생산했으니 CMD 창을 하나 더 열어서 이를 확인해봅니다. ( 메시지 소비 )&lt;/p&gt;
&lt;pre&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;# .\bin\windows\kafka-console-consumer.bat &lt;/span&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;--bootstrap-server localhost:9092 --topic test --from-beginning&lt;/span&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p class=&quot;MsoNoSpacing&quot;&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;--from-beginning&lt;/span&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Consumer에게 설정된 offset이 없으므로 가장 최신의 메시지 대신 가장 먼저 도착한 메시지부터 읽도록 하는 옵션입니다.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Producer에서 메시지를 보내면 Consumer에서 바로 확인이 가능합니다.&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99BD1D465E6DC0B017&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99BD1D465E6DC0B017&quot; width=&quot;900&quot; height=&quot;200&quot; filename=&quot;1.PNG&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이상으로 Kafka 설치 및 간단한 기본 명령어에 대해 알아보았습니다.&lt;/p&gt;&lt;p&gt;각 스크립트에서 제공하는 옵션들은 &lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;--help&lt;/span&gt; 옵션을 통해 확인하시면 도움이 됩니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;[참고자료]&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://kafka.apache.org/quickstart&quot;&gt;https://kafka.apache.org/quickstart&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>OpenSource/Kafka</category>
      <author>빅토리_</author>
      <guid isPermaLink="true">https://victorydntmd.tistory.com/345</guid>
      <comments>https://victorydntmd.tistory.com/345#entry345comment</comments>
      <pubDate>Sun, 15 Mar 2020 14:53:43 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] 기본 개념잡기</title>
      <link>https://victorydntmd.tistory.com/344</link>
      <description>&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;대표적인 메시징 시스템으로&amp;nbsp;&lt;u&gt;&lt;a href=&quot;https://kafka.apache.org/intro&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;Kafka&lt;/a&gt;&lt;/u&gt;,&amp;nbsp;&lt;u&gt;&lt;a href=&quot;https://www.rabbitmq.com/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;RabbitMQ&lt;/a&gt;&lt;/u&gt;,&amp;nbsp;&lt;a href=&quot;https://activemq.apache.org/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot; style=&quot;text-decoration-line: underline;&quot;&gt;Active MQ&lt;/a&gt;가&amp;nbsp;있는데요.&lt;/p&gt;&lt;p&gt;각 메시징 시스템마다&amp;nbsp;장단점이 있겠지만, 하도 Kafka가 좋다하고 도입하길래&amp;nbsp;어떤 녀석인지 궁금했습니다.&lt;/p&gt;&lt;p&gt;Kafka에 대해 알아보니 빠르고, 안정성있게 설계가 되어있는 것을 확인할 수 있었는데요, 이 글에서 입문자 입장에서 Kafka를 이해하기 위해 나름대로 정리를 해보았습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;메시징 시스템에 대한 이해가 없으면 이해가 어려울 수 있으니, Kafka에 대해 알아보기 전에 간단하게&amp;nbsp;&lt;u&gt;&lt;a href=&quot;https://victorydntmd.tistory.com/343&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;메시징 시스템에 대해 정리한 글&lt;/a&gt;&lt;/u&gt;을 참고하시길 바랍니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;1. Kafka 아키텍쳐&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Kafka가 어떻게 이루어져 있는지 큰 크림을 그려&amp;nbsp;아키텍쳐를 먼저 살펴보겠습니다.&lt;/p&gt;&lt;p&gt;그리고 세부적인 구조와&amp;nbsp;용어들을 이해하면서 특징들을 살펴보도록 하겠습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 643px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99745A4B5E633AF321&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99745A4B5E633AF321&quot; width=&quot;643&quot; height=&quot;459&quot; filename=&quot;kafka.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Kafka의 클러스터를 큰 덩어리로 표현해보았습니다.&lt;/p&gt;&lt;p&gt;큰 집합으로 순서대로 간략하게 정리하면 다음과 같습니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;b&gt;Zookeeper ( &lt;u&gt;&lt;a href=&quot;https://zookeeper.apache.org/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;Apache Zookeeper&lt;/a&gt;&lt;/u&gt; )&lt;/b&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;본래 Zookeeper의 용도는 클러스터 최신 설정정보 관리, 동기화, 리더 채택 등 클러스터의&amp;nbsp;서버들이&amp;nbsp;공유하는 데이터를 관리하기 위해 사용됩니다.&lt;/li&gt;&lt;/ul&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;복잡하니까 그냥 Broker에&amp;nbsp;분산 처리된&amp;nbsp;메시지 큐의 정보들을 관리한다고만 이해합시다...&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;클러스터를 관리하는 Zookeeper 없이는 Kafka&amp;nbsp;구동이 불가능합니다.&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;즉, Kafka 서버를 가동하려면 Zookeeper를&amp;nbsp;먼저 가동해줘야 합니다.( 그래서 Kafka 다운로드시 Zookeeper도 함께 제공해줍니다.)&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Broker&lt;/b&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Kafka Server를 의미합니다.&lt;/li&gt;&lt;li&gt;한 클러스터 내에서 Kafka server를&amp;nbsp;여러 대&amp;nbsp;띄울수 있습니다.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Topic&lt;/b&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;메시지가 생산되고 소비되는 주제입니다.&lt;/li&gt;&lt;/ul&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;예를 들어, 카톡 단체방&amp;nbsp;A, B가 있는데, A 방으로&amp;nbsp;보낸 메시지가 B 방에 노출되면 안되겠죠? A 방에서 &lt;b&gt;생산&lt;/b&gt;된&amp;nbsp;메시지는 A 방에 존재하는 사람들(&lt;b&gt;구독&lt;/b&gt;한 사람)에게만 보여져야 합니다.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;주제에 따라 여러 topic을 생성하면 됩니다.&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;ex) email topic, sms topic, push topic ...&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Partition&lt;/b&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Topic 내에서 메시지가 분산되어 저장되는 단위입니다.&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;한 Topic에 Partition이 3개 있다면, 3개의 Partition에 대해서 메시지가 분산되어 저장이됩니다.&lt;/li&gt;&lt;li&gt;이 때 Queue 방식으로&amp;nbsp;저장되므로&amp;nbsp;Partition의&amp;nbsp;끄트머리에 저장이 되어&amp;nbsp;Partition 내에서는 순서를 보장해주지만, Partition끼리는 메시지 순서를 보장해주지 않습니다.&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;그래서 Topic 내에 하나의 Partition이 존재할 때와 여러 개의 Partition이 존재할 때는 차이점이 있습니다. ( 무슨 말인지는.. 뒤에서 다룹니다 ㅎㅎ )&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Log&lt;/b&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Partition의 한 칸을 Log라 합니다.&lt;/li&gt;&lt;li&gt;Log는 key, value, timestamp로 구성됩니다.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Offset&lt;/b&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Partition의 각 메시지를 식별할 수 있는&amp;nbsp;유니크한 값입니다.&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;메시지를 소비하는 Consumer가 읽을 차례를 의미하므로 Partition마다 별도로 관리됩니다.&lt;/li&gt;&lt;li&gt;0부터 시작하여 1씩 증가합니다.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Kafka Cluster를 구성하는 요소들에&amp;nbsp;대해&amp;nbsp;알아보았습니다.&lt;/p&gt;&lt;p&gt;다음으로 Partition에 메시지를 어떻게 기록하고 사용하는지에 대해 알아보겠습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;2. Producer와 &lt;/span&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;Consumer Group&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1) 메시지 생산/소비&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 477px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/998728405E6370AA1F&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F998728405E6370AA1F&quot; width=&quot;477&quot; height=&quot;677&quot; filename=&quot;partition.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;b&gt;Producer&lt;/b&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Producer는 정해진 Topic으로 메시지를 기록합니다.&lt;/li&gt;&lt;li&gt;Partition이 여러 개 있을 경우, 기록 될 Partition의 선택은 기본적으로 Round-Robin 방식을 따릅니다.&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Partition이 여러 개 있으면&amp;nbsp;병렬 처리라는 이점이 있지만, Partition 개수는 주의해서 잘 설정해줘야 합니다.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;각 Partition 내에서는 가장 마지막 offset 뒤에 신규 메시지가 저장되므로, Partition 내에서는 순서가 보장되며 기록이됩니다.&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;하지만 실제 메시지가&amp;nbsp;사용되는 순서는 순서가 보장되지 않는데요, 그 이유는 Consumer의 동작 방식을 이해해야 합니다.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Consumer Group&lt;/b&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Consumer Group은 하나의 Topic을 담당합니다.&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;즉, Topic은 여러 개의 Consumer Group이 접근할 수 있지만, 하나의 Consumer Group은 하나의 Topic에만 접근할 수 있습니다.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;왜 존재하는가?&lt;/li&gt;&lt;/ul&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;b&gt;1) Partition 접근하는 Consumer 관리&lt;/b&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Consumer Group 내에서 Consumer 인스턴스들은 Topic내에 Partition에서 다음에 소비할 offset이 어디인지 공유하면서 메시지를 소비합니다. 그렇기 때문에 다음에 소비할 offset을 잘 관리할 수 있습니다.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;예를 들어 Consumer Group이 없을 경우, 하나의 Partition에 2개의 Consumer가 동시에 접근한다면 어떤 Consumer가 몇 번의 offset을 소비해야 하는지 알 수 없게 됩니다.&lt;/li&gt;&lt;li&gt;즉, Consumer Group을 통해 하나의 Partition에는 하나의 Consumer 인스턴스만 접근할 수 있도록 관리합니다.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;2) offset을 공유하여 고가용성을 확보&lt;/b&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Partition에는 하나의 Consumer 인스턴스만 접근할 수 있기 때문에, 특정 Consumer 인스턴스에&amp;nbsp;에러가 발생했을 시&amp;nbsp;다른 Consumer 인스턴스는&amp;nbsp;에러가 발생한 Consumer 인스턴스가 소비하던 Partition을 소비하게 됩니다.&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;즉, Consumer가 다운될 때를 대비해 Consumer Group의 Consumer 인스턴스들은&amp;nbsp;offset을 공유하고 있으며, 이를 통해 고가용성이 확보됩니다.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;개인적으로는 Kafka를 이해할 때 Consumer Group을 이해하는것이 중요하다고 생각됩니다.&lt;/p&gt;&lt;p&gt;이에 대한 좋은 글이 있으니 참고하시길 바랍니다. ( &lt;u&gt;&lt;a href=&quot;https://www.popit.kr/kafka-consumer-group/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;링크&lt;/a&gt;&lt;/u&gt; )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2) Partition과 Consumer의 개수&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Partition은&amp;nbsp;하나의 Consumer만&amp;nbsp;접근이 가능합니다.&lt;/p&gt;&lt;p&gt;반대로 Consumer는 여러 개의 Partition을 소비할 수 있죠.&lt;/p&gt;&lt;p&gt;대량의 메시지가 Kafka에 쓰여진다고 가정해보겠습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;(1) Partition 1개 / Consumer 인스턴스 1&lt;/b&gt;&lt;/p&gt;&lt;p&gt;메시지가 대량으로 막 생산되고 있는데, 처리할 수 있는 Consumer가 1개 밖에 없네요.&lt;/p&gt;&lt;p&gt;그래서 Consumer를 늘리기로 했습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;(2) Partition 1개 /&amp;nbsp;&lt;/b&gt;&lt;b&gt;Consumer 인스턴스 4&lt;/b&gt;&lt;b&gt;개&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Consumer를 4개로 늘렸지만, Consumer Group에서&amp;nbsp;Partition은 하나의 Consumer 밖에&amp;nbsp;접근을 못하는 구조입니다.&lt;/p&gt;&lt;p&gt;즉, Consumer를 늘리나 마나인 상황이 되었네요.&lt;/p&gt;&lt;p&gt;그래서 이번에는 Partition을 늘려보겠습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;(3) Partition 4개 /&amp;nbsp;&lt;/b&gt;&lt;b&gt;Consumer 인스턴스 4&lt;/b&gt;&lt;b&gt;개&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Consumer는 하나의 Partition에 접근할 수 있으므로, Partition과 Consumer는 1:1 구성이 되었습니다.&lt;/p&gt;&lt;p&gt;이상적인 상황이네요.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;(4) Partition 4개 /&amp;nbsp;&lt;/b&gt;&lt;b&gt;Consumer 인스턴스 3&lt;/b&gt;&lt;b&gt;개&lt;/b&gt;&lt;/p&gt;&lt;p&gt;잘 운영이 되다가, 갑자기 Consumer&amp;nbsp;하나가 죽어버렸습니다.&lt;/p&gt;&lt;p&gt;그래도 문제는 없습니다.&lt;/p&gt;&lt;p&gt;Consumer Group에서 offset이 공유되고 있으므로 Consumer가 하나 죽더라도 다른 Consumer가 해당 Partition에 접근하면 되니까요.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;(5) Partition 3개 /&amp;nbsp;&lt;/b&gt;&lt;b&gt;Consumer 인스턴스 3&lt;/b&gt;&lt;b&gt;개&lt;/b&gt;&lt;/p&gt;&lt;p&gt;메시지가 잘 처리되고 있고, 상황을 보니 Partition을 3개로 줄여도 될 것 같습니다.&lt;/p&gt;&lt;p&gt;그래서 Partition을 줄이려고 했지만, Partition은 한 번 늘리면 줄일수가 없습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;위 상황의 결론을 말씀드리면, Partition의 개수 &amp;gt;= Consumer 인스턴스의 갯수를 유지하는 것이 좋습니다.&lt;/p&gt;&lt;p&gt;( Consumer &amp;gt;&amp;nbsp;Partition은 불가능합니다. )&lt;/p&gt;&lt;p&gt;하나의 Partition에 하나의 Consumer가 담당하는 것이 좋지만 딱 맞출수는 없으므로,&amp;nbsp;Consumer 수가 모자라도 상관은 없습니다.&lt;/p&gt;&lt;p&gt;주의할 점은 한 번 Partition을 늘리면 다시 줄일 수 없기 때문에,&amp;nbsp;처리량을 잘 고려하여 Partition과 Consumer의 개수를 선택해야 할 것입니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;3. Consumer Design&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Kafka와 마찬가지로 대표적인 메시징 시스템인&amp;nbsp;RabbitMQ, ActiveMQ 역시&amp;nbsp;분산 큐 시스템(Distributed Queue System)입니다.&lt;/p&gt;&lt;p&gt;분산이니까 성능이 다 빠를것 같은데.. 왜 유독 Kafka가 빠르다고 할까요?&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;성능이 좋으려면 소비자가 최대의 효율을 내는 것을 목표로 해야합니다.&lt;/p&gt;&lt;p&gt;분산처리가 된다한들, 소비자가 메시지를 처리못하면 전체적인 성능이 느려지겠죠?&lt;/p&gt;&lt;p&gt;즉, 메시지를 소비하는 방식에 대한 차이가 성능의 차이를 보이는 것 같습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Kafka는 Single Consumer가 아닌, Multi Consumer를 염두에 두고 설계되었기 때문에 Consumer를 잘 살펴볼 필요가 있습니다.&lt;/p&gt;&lt;p&gt;다음은 RabbitMQ와 Kafka의 Consumer Design을 비교한 것입니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;RabbitMQ&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Message Broker가 Consumer에게 메시지를 &lt;b&gt;push&lt;/b&gt;하는 방식&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Broker는 Consumer의 처리여부에 관계없이 push를 하므로, 메시지 소비 속도보다 생산 속도가 빠를 경우 Consumer에 부하를 주게됩니다.&lt;/li&gt;&lt;li&gt;RabbitMQ는 DRAM을 사용하므로 buffer를 사용하지만, DRAM을 다 사용하면 disk에 저장합니다. 따라서 batch 같이 큰 작업에서는 disk로 메시지를 읽어올 경우 지연이 발생합니다.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;Kafka&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Consumer가 Broker로부터 메시지를 &lt;b&gt;pull&lt;/b&gt;하는 방식&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Consumer가 처리할 수 있을 때 메시지를 가져오므로 자원을 효율적으로 사용합니다.&lt;/li&gt;&lt;li&gt;Kafka는 애초에 메시지를 disk에 저장하고, 이미 처리한 과거의 offset으로&amp;nbsp;자유롭게 움직일 수 있으므로&amp;nbsp;batch 작업에서 자원의 낭비라던지 지연이 발생하지 않습니다.&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;메시지를 쌓아두었다가 처리하는 batch Consumer&amp;nbsp;구현도&amp;nbsp;가능합니다.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;항상 trade off가 있듯이, pull 방식에도 단점은 있습니다.&lt;/p&gt;&lt;p&gt;데이터가 없음에도 정기적인 polling으로 인해 자원을 낭비하는 문제인데요, 이러한 단점을 보완하기 위해 실제 데이터가 도착할&amp;nbsp;때까지 &lt;b&gt;long poll&amp;nbsp;대기&lt;/b&gt;를 할 수 있는 parameter를 지원합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;자세한 정보는&amp;nbsp;&lt;u&gt;&lt;a href=&quot;https://kafka.apache.org/documentation/#design_pull&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;공식문서&lt;/a&gt;&lt;/u&gt;를 참고하시면&amp;nbsp;좋습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;4. Replication&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Topic을 생성할 때, &lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;--replication-factor&lt;/span&gt; 옵션을 부여하면 복제본(replication)을 생성할 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Replication이란 Zookeeper가 leader가 되는 Partition을 정하고, Partition을&amp;nbsp;각 broker마다&amp;nbsp;복제를 하는 것을 말합니다.&lt;/p&gt;&lt;p&gt;이 때 leader를 복제하는 partition을 follower라 합니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;leader&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;메시지를 생산하고 소비하는 작업은&amp;nbsp;모두 leader broker에서 이뤄집니다.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;follower&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;나머지 follower들은 leader를 복제하기만 합니다.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;이는 고가용성을 위한것이며, 혹시 leader가 죽었을 경우 follower 중 하나가 leader가 되어야 하기 때문에, follower는 leader와 싱크를 맞추고 있는 것입니다. ( In-Sync Replica, &lt;b&gt;ISR&lt;/b&gt; )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 610px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99BB734B5E63C87925&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99BB734B5E63C87925&quot; width=&quot;610&quot; height=&quot;312&quot; filename=&quot;replication.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;위의 그림은 3대의 Broker Server에 대하여, topic-1에 4개의 Partition이 존재할 때,&amp;nbsp;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;--replication-factor=2&lt;/span&gt;&amp;nbsp;로 설정한 것입니다.&lt;/p&gt;&lt;p&gt;4개의 Partition이 factor 설정 값만큼 Broker에 분배된 것을 확인할 수 있습니다.&lt;/p&gt;&lt;p&gt;( Zookeeper가 Partition을&amp;nbsp;골고루 분배합니다. )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;예를 들어, Partition1에 메시지를 쓰는 상황일 때, leader partition이 존재하는 Broker2에서 메시지가&amp;nbsp;생산됩니다.&lt;/p&gt;&lt;p&gt;그리고 follower인 Broker3에 존재하는 Partition은&amp;nbsp;leader를 복제합니다. ( ISR )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;만약 leader가 되는 Broker가 다운이 되면, follower가 leader로 선출됩니다.&lt;/p&gt;&lt;p&gt;예제는 follower가 1개지만 여러 개의 follower가 있다면, Zookeeper가 leader를 알아서 선출해줍니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;5. 그 밖의 정보들&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;주제로 잡기엔&amp;nbsp;애매하고... 버리기엔 아까운&amp;nbsp;짤막한 정보들을 모아봤습니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;메시지 보존기간&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;클러스터는 쓰여진 메시지를 보존기간동안 유지합니다.&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;보존 기간 정책은&amp;nbsp;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;log.retention.hours&lt;/span&gt; 설정을 통해 가능하며, 기본값이 7일입니다.&lt;/li&gt;&lt;li&gt;예를 들어, 보존 정책이 2일이면, 2일 뒤에는 공간 확보를 위해 해당 메시지를&amp;nbsp;폐기합니다.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;데이터 크기에 상관없이 카프카의 성능은 일정하기 때문에&amp;nbsp;장기간 저장해도 문제는 없으므로, 보존기간을 짧게 잡을 필요는 없습니다.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;즉, Consumer가 메세지를 소비한다고 해서 메시지가 없어지는게 아니라 보존기간이 지나야&amp;nbsp;사라집니다.&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;그래서 Consumer가 과거의 offset에 대한 접근을 할 수 있는것입니다.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;Partition이 1개이냐, 2개 이상이냐에 따른&amp;nbsp;메시지 순서 보장에 대한 이해 ( &lt;a href=&quot;https://www.popit.kr/kafka-%EC%9A%B4%EC%98%81%EC%9E%90%EA%B0%80-%EB%A7%90%ED%95%98%EB%8A%94-%EC%B2%98%EC%9D%8C-%EC%A0%91%ED%95%98%EB%8A%94-kafka/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;&lt;u&gt;참고&lt;/u&gt;&lt;/a&gt; )&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이상으로 메시징&amp;nbsp;시스템과 Kafka에 대해 알아보았습니다.&lt;/p&gt;&lt;p&gt;Kafka를 잘 안써봐서&amp;nbsp;잘못된&amp;nbsp;내용이 있을 수 있습니다 ^^; ( 많이 어렵네요... ㅎㅎ )&lt;/p&gt;&lt;p&gt;많은 참고 자료들을 읽고 공부하는 차원에서 정리한 글이니, 이를 통해 다른 더 좋은 자료를 읽는데 기반이 되었으면&amp;nbsp;합니다.&lt;/p&gt;&lt;p&gt;( 지적사항 및&amp;nbsp;새로 알게된 사실이&amp;nbsp;있으면 틈틈히&amp;nbsp;수정하겠습니다&amp;nbsp;)&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;[참고 자료]&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://kafka.apache.org/&quot;&gt;https://kafka.apache.org/&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://kafka.apache.org/documentation/#design_pull&quot;&gt;https://kafka.apache.org/documentation/#design_pull&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.confluent.io/blog/hands-free-kafka-replication-a-lesson-in-operational-simplicity/&quot;&gt;https://www.confluent.io/blog/hands-free-kafka-replication-a-lesson-in-operational-simplicity/&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.popit.kr/kafka-%EC%9A%B4%EC%98%81%EC%9E%90%EA%B0%80-%EB%A7%90%ED%95%98%EB%8A%94-%EC%B2%98%EC%9D%8C-%EC%A0%91%ED%95%98%EB%8A%94-kafka/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;https://www.popit.kr/kafka-&lt;/a&gt;&lt;a href=&quot;https://www.popit.kr/kafka-%EC%9A%B4%EC%98%81%EC%9E%90%EA%B0%80-%EB%A7%90%ED%95%98%EB%8A%94-%EC%B2%98%EC%9D%8C-%EC%A0%91%ED%95%98%EB%8A%94-kafka/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;운영자가-말하는-처음-접하는-kafka&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.popit.kr/kafka-consumer-group/&quot;&gt;https://www.popit.kr/kafka-consumer-group/&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://epicdevs.com/17&quot;&gt;https://epicdevs.com/17&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://medium.com/@umanking/%EC%B9%B4%ED%94%84%EC%B9%B4%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%9D%B4%EC%95%BC%EA%B8%B0-%ED%95%98%EA%B8%B0%EC%A0%84%EC%97%90-%EB%A8%BC%EC%A0%80-data%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%9D%B4%EC%95%BC%EA%B8%B0%ED%95%B4%EB%B3%B4%EC%9E%90-d2e3ca2f3c2&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;https://medium.com/@umanking/카&lt;/a&gt;&lt;a href=&quot;https://medium.com/@umanking/%EC%B9%B4%ED%94%84%EC%B9%B4%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%9D%B4%EC%95%BC%EA%B8%B0-%ED%95%98%EA%B8%B0%EC%A0%84%EC%97%90-%EB%A8%BC%EC%A0%80-data%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%9D%B4%EC%95%BC%EA%B8%B0%ED%95%B4%EB%B3%B4%EC%9E%90-d2e3ca2f3c2&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;프카에-대해서-이야기-하기전에-먼저-data에-대해서-이야기해보자-d2e3ca2f3c2&lt;/a&gt;&lt;/p&gt;</description>
      <category>OpenSource/Kafka</category>
      <author>빅토리_</author>
      <guid isPermaLink="true">https://victorydntmd.tistory.com/344</guid>
      <comments>https://victorydntmd.tistory.com/344#entry344comment</comments>
      <pubDate>Thu, 12 Mar 2020 00:09:26 +0900</pubDate>
    </item>
    <item>
      <title>메시징 시스템( Messaging System )의 이해</title>
      <link>https://victorydntmd.tistory.com/343</link>
      <description>&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;요즘 관심있게 살펴보는 주제가 MSA인데요, MSA에서는 데이터 송수신 방법으로 메시징 시스템을 사용합니다.&lt;/p&gt;&lt;p&gt;메시징 시스템은 &lt;u&gt;&lt;a href=&quot;https://kafka.apache.org/intro&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;Kafka&lt;/a&gt;&lt;/u&gt;,&amp;nbsp;&lt;u&gt;&lt;a href=&quot;https://www.rabbitmq.com/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;RabbitMQ&lt;/a&gt;&lt;/u&gt;,&amp;nbsp;&lt;u&gt;&lt;a href=&quot;https://activemq.apache.org/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;Active MQ&lt;/a&gt;&lt;/u&gt;,&amp;nbsp;&lt;u&gt;&lt;a href=&quot;https://aws.amazon.com/ko/sqs/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;AWS SQS&lt;/a&gt;&lt;/u&gt;,&amp;nbsp;&lt;u&gt;&lt;a href=&quot;https://spring.io/guides/gs/messaging-jms/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;Java&amp;nbsp;&lt;/a&gt;&lt;a href=&quot;https://spring.io/guides/gs/messaging-jms/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;JMS&lt;/a&gt;&lt;/u&gt;&amp;nbsp;등이 있는데요.&lt;/p&gt;&lt;p&gt;MSA에서는 시스템 간의 호출이 많기 때문에&amp;nbsp;서비스간 결합도를 낮추기 위해서, 비동기 요청, 성능, 안정성 등 여러가지 이점이 있어서 메시징 시스템을 사용하게 됩니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이 글에서는 Kafka, RabbitMQ를 배우기 전에 알아두면 좋은&amp;nbsp;전반적인 메시징 시스템에 대해 알아보겠습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;1. 용어 정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;b&gt;MOM&lt;/b&gt;&amp;nbsp;( Message Oriented Middleware, 메시지 지향 미들웨어 )&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;독립된 애플리케이션 간에 데이터를 주고받을 수 있도록 하는 시스템 디자인&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;함수 호출, 공유메모리 등의 방식이 아닌, 메시지 교환을 이용하는 중간 계층에 대한&amp;nbsp;인프라 아키텍쳐 개념&lt;/li&gt;&lt;li&gt;분산 컴퓨팅이 가능해지며, 서비스간의 결합성이 낮아짐&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;비동기로 메시지를 전달하는 것이 특징&lt;/li&gt;&lt;li&gt;Queue, Broadcast, Multicast 등의 방식으로 메시지 전달&lt;/li&gt;&lt;li&gt;&lt;b&gt;Pub/Sub&lt;/b&gt;&amp;nbsp;구조&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;메시지를 발행하는 Publisher(Producer), 메시지를 소비하는&amp;nbsp;Subscribe(Consumer)로 구성&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Message Broker&lt;/b&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;메시지 처리 또는 메시지 수신자에게 메시지를 전달하는 시스템이며, 일반적으로 MOM을 기반으로 구축됨&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;MQ&lt;/b&gt;&amp;nbsp;( Message Queue, 메시지 큐 )&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Message Broker와 MOM을 구현한 소프트웨어 ( RabbitMQ, ActiveMQ, kafka 등.. )&lt;/li&gt;&lt;li&gt;MOM은 메시지 전송 보장을 해야하므로 AMQP를 구현함&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;AMQP&lt;/b&gt;&amp;nbsp;(&amp;nbsp;Advanced Message Queueing Protocol )&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;메시지를 안정적으로 주고받기&amp;nbsp;위한 인터넷 프로토콜&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;정리하면 흔히 알고 있는 소프트웨어&amp;nbsp;RabbitMQ, kafka 등은&amp;nbsp;&quot;&lt;b&gt;AMQP를 구현한 MOM 시스템이다&lt;/b&gt;&quot;고 생각하면 되겠습니다.&lt;/p&gt;&lt;p&gt;각 소프트웨어마다 아키텍쳐, 지원하는 기능 등의 차이가 있으므로 장단점이 존재할 수 밖에 없지만요.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;2. 메시징&amp;nbsp;시스템이란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;&lt;span style=&quot;font-size: 10pt;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;메시징&amp;nbsp;시스템이라하면, &quot;문자, 이메일 같은 메시지들을 처리하는 시스템인가?&quot;라고 생각할 수 있는데, 꼭 그런건 아닙니다.&lt;/p&gt;&lt;p&gt;로그 데이터, 이벤트 메시지 등 API로 호출할 때 보내는 데이터들을 처리하는 시스템이라고 생각하면 됩니다.&lt;span style=&quot;text-align: center;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 704px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99C2F2345E68FF1529&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99C2F2345E68FF1529&quot; width=&quot;704&quot; height=&quot;346&quot; filename=&quot;mom.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-align: center;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;예를 들어, 다음과 같이 자동 메일을 발송 시스템이 있다고 가정하겠습니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;회원가입을 했을 때,&amp;nbsp;이메일을 발송하는&amp;nbsp;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;MemberService&lt;/span&gt;&lt;/li&gt;&lt;li&gt;주문완료가 되었을 때, 이메일을 발송하는&amp;nbsp;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;OrderService&lt;/span&gt;&lt;/li&gt;&lt;li&gt;메일을 실제 발송하는&amp;nbsp;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;MailService&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이렇게 서비스가 분리되었을 때&amp;nbsp;프로세스는 다음과 같습니다.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;MemberService에서 회원가입, OrderService에서 주문완료 이벤트가 발생&lt;/li&gt;&lt;li&gt;Messaging Client로 메일 전송에 필요한 데이터( 받는/보내는 사람 이메일 주소, 메시지 제목/내용 등.. )를 API&amp;nbsp;호출&lt;/li&gt;&lt;li&gt;Messaging Client에서 MOM을 구현한 소프트웨어(ex. kafka)로&amp;nbsp;&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;메시지를 생산&lt;/span&gt;&lt;/li&gt;&lt;li&gt;MailService에서 메시지가 존재하는지 구독하고 있다가 메시지가 존재하면&lt;span style=&quot;color: rgb(0, 130, 153);&quot;&gt;&amp;nbsp;메시지를 소비&lt;/span&gt;&lt;/li&gt;&lt;li&gt;MailService에서 API 정보들을 통해 User에게 메일 발송&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;이러한 구조를 Publish/Subscribe 또는 Producer/Consumer라고 합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;3. 장/단점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;위 구조의 장단점은 다음과 같습니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;서비스간의 결합성이 낮아지므로 각자의 비즈니스 로직에만 집중&lt;/li&gt;&lt;li&gt;메시지 처리 방식은&amp;nbsp;Message Broker에 위임&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;각 서비스는 Client를 통해 메시지를 보내고 받기만 하면 됨&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;각 서비스는 비동기 방식으로 메시지를 보내기만 하면, Message Broker에서 순서 보장, 메시지 전송 보장등을 처리&lt;/li&gt;&lt;li&gt;메시징 시스템이 잠깐 다운되어도 각 서비스에는 직접적인 영향을 미치지 않음&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Message Broker 구축, 예를 들면&amp;nbsp;kafka 클러스터 구축에 필요한 금전, 인적자원에 대한 비용&lt;/li&gt;&lt;li&gt;비동기의 양면성 - 정말 메시지가 잘 전달되었는가?&lt;/li&gt;&lt;li&gt;함수 호출, 공유메모리 사용 방식보다 메시징 시스템을 사용했을 때 호출 구간이 늘어나므로 네트워크 비용 발생&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이상으로 메시징 시스템에 대해 알아보았습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;[참고자료]&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://stackoverflow.com/questions/13202200/message-broker-vs-mom-message-oriented-middleware/36999850#36999850&quot;&gt;https://stackoverflow.com/questions/13202200/message-broker-vs-mom-message-oriented-middleware/36999850#36999850&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>개발 지식</category>
      <author>빅토리_</author>
      <guid isPermaLink="true">https://victorydntmd.tistory.com/343</guid>
      <comments>https://victorydntmd.tistory.com/343#entry343comment</comments>
      <pubDate>Thu, 12 Mar 2020 00:09:19 +0900</pubDate>
    </item>
  </channel>
</rss>