<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>in_ho_.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 21 Nov 2023 23:01:39 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>in_ho_.log</title>
            <url>https://velog.velcdn.com/images/in_ho_/profile/d3c55a0d-563a-4cdb-a623-2fcb0740905a/image.JPG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. in_ho_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/in_ho_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Docker] Docker Image를 만드는 방법]]></title>
            <link>https://velog.io/@in_ho_/Docker-Docker-Image%EB%A5%BC-%EB%A7%8C%EB%93%9C%EB%8A%94-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@in_ho_/Docker-Docker-Image%EB%A5%BC-%EB%A7%8C%EB%93%9C%EB%8A%94-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Tue, 21 Nov 2023 23:01:39 GMT</pubDate>
            <description><![CDATA[<h1 id="1-docker-image">1. Docker Image</h1>
<ul>
<li>Docker Image는 애플리케이션의 코드와 환경을 담은 것을 이미지라고 합니다.<blockquote>
<p>Git에서 Source Code를 Clone 받았을 때 <strong>환경을 포함하고 있지 않기 때문에</strong> 환경적인 요소로 인해 정상적으로 실행이 안될 수도 있습니다.</p>
</blockquote>
</li>
</ul>
<h2 id="1-1-이미지를-만드는-방법build">1-1. 이미지를 만드는 방법(Build)</h2>
<ul>
<li>Docker는 이미지를 <strong>어떻게</strong> Build할지 <strong>작업 지시서(Dockerfile)</strong>가 필요합니다.</li>
<li>Dockerfile은 컨테이너를 만드는 설계도<blockquote>
<p>Docker는 설계도를 사용하여 컨테이너를 만드는 툴입니다.
Docker 이미지를 만드는 행위 = build</p>
</blockquote>
<pre><code>docker build</code></pre><img src="https://velog.velcdn.com/images/in_ho_/post/a587605e-f8a8-4f93-95a8-07be28cfef90/image.png" alt=""></li>
</ul>
<h2 id="1-2-dockerfile의-내용">1-2. Dockerfile의 내용</h2>
<ol>
<li>어떤 OS를 사용할 것인지</li>
<li>어떤 명령어를 실행할 것인지</li>
<li>어떤 파일들을 컨테이너 안에 가져갈 것인지<ul>
<li>로컬 </li>
</ul>
</li>
</ol>
<blockquote>
<p>Docker 컨테이너의 내부는 완전히 <strong>격리</strong>된 상태입니다.
컨테이너의 초기 상태에는 비어 있습니다.</p>
</blockquote>
<h2 id="1-3-dockerfile-작성법">1-3. Dockerfile 작성법</h2>
<blockquote>
<p><strong>Docker는 이미지를 만들고 이 이미지를 실행하는 기능이 전부라고 생각하면 된다.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring][부록] Profile 사용]]></title>
            <link>https://velog.io/@in_ho_/Spring-Profile-%EC%82%AC%EC%9A%A9</link>
            <guid>https://velog.io/@in_ho_/Spring-Profile-%EC%82%AC%EC%9A%A9</guid>
            <pubDate>Sun, 12 Nov 2023 12:42:31 GMT</pubDate>
            <description><![CDATA[<h1 id="1-profile이란">1. Profile이란?</h1>
<ul>
<li>Spring을 사용할 때 properties, yaml 파일과 같은 설정 파일을 마주치게 됩니다.</li>
<li>보통의 코드들은 로컬, 개발, 스테이징, 운영 등의 환경에서 동작하게 되는데 이때마다, 설정 파일을 바꿔주는 것은 비효율적입니다.</li>
<li>이 비효율을 개선하기 위해 Spring은 Profile이라는 기능을 제공합니다.</li>
</ul>
<h1 id="2-applicationyaml">2. application.yaml</h1>
<blockquote>
<p>application.properties도 동일합니다. 예시를 위해 yaml로 설정합니다.</p>
</blockquote>
<ul>
<li><strong>여기에는 공통 구성이 포함됩니다.</strong></li>
</ul>
<h1 id="3-각-환경을-위한-파일-생성">3. 각 환경을 위한 파일 생성</h1>
<ul>
<li>로컬 환경<ul>
<li>application-local-yaml</li>
</ul>
</li>
<li>개발 환경<ul>
<li>application-dev.yaml</li>
</ul>
</li>
<li>운영 환경<ul>
<li>application-prod.yaml</li>
</ul>
</li>
</ul>
<h1 id="4-profile-활성화">4. Profile 활성화</h1>
<pre><code>java -jar [jar 파일명] --spring.profiles.active=local</code></pre><h1 id="5-intellij에서-적용">5. Intellij에서 적용</h1>
<p><img src="https://velog.velcdn.com/images/in_ho_/post/59f6f8b3-043b-4b5a-8ea3-8309744ed0f5/image.png" alt=""></p>
<blockquote>
<p>Test Code가 정상적으로 동작하지 않는다고 당황하지 말자 아래의 방법이 있다.</p>
</blockquote>
<h2 id="5-1-클래스-단위의-profile-적용">5-1. 클래스 단위의 Profile 적용</h2>
<pre><code>@ActiveProfiles(&quot;dev&quot;)
@SpringBootTest
class ProfilesTest {
    ...
}</code></pre><h2 id="5-2-메서드-단위의-profile-적용">5-2. 메서드 단위의 Profile 적용</h2>
<pre><code>@Test
@Profile(&quot;dev&quot;)
@DisplayName(&quot;Profile Test&quot;)
void testCheck() {
    ...
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Redis] Redis]]></title>
            <link>https://velog.io/@in_ho_/Spring-Redis-Redis</link>
            <guid>https://velog.io/@in_ho_/Spring-Redis-Redis</guid>
            <pubDate>Sun, 12 Nov 2023 12:23:03 GMT</pubDate>
            <description><![CDATA[<h1 id="1-redis란">1. Redis란?</h1>
<ul>
<li>Redis는 <strong>고성능, 인메모리 키-값 데이터 저장소</strong>입니다.</li>
<li>빠른 성능과 유연한 데이터 구조로 데이터를 캐싱하는 등의 역할을 주로 담당합니다.</li>
<li>Redis는 인메모리 데이터베이스임에도 불구하고 데이터를 디스크에 저장하므로, 시스템 재시작 후에도 데이터를 복구할 수 있습니다.</li>
</ul>
<h1 id="2-redis-설치">2. Redis 설치</h1>
<ul>
<li>해당 설치 방법은 docker를 이용하였습니다.<pre><code>docker run --name redis -p 8379:6379 -d redis redis-server --requirepass yourpassword</code></pre></li>
</ul>
<h1 id="3-redis-설치-확인">3. Redis 설치 확인</h1>
<pre><code>docker exec -it 컨테이너명 /bin/bash

# redis shell 접근 후
redis-cli -p 6379 -a [password]</code></pre><h1 id="4-spring-설정">4. Spring 설정</h1>
<h2 id="4-1-yaml-파일-설정">4-1. yaml 파일 설정</h2>
<pre><code>spring:
    redis:
        host: localhost
        port: 8379
        password: [password]</code></pre><blockquote>
<p>현재 Deprecated되어 이를 아래와 같이 수정해줍니다.(Spring Boot 3.X 버전)</p>
</blockquote>
<pre><code>  data:
    redis:
      host: localhost
      port: 8379
      password: [password]</code></pre><h2 id="4-2-java-configuration">4-2. Java Configuration</h2>
<pre><code>@Configuration
@EnableRedisRepositories
public class RedisConfiguration {
    private Logger log = LoggerFactory.getLogger(RedisConfiguration.class);
    @Value(&quot;${spring.data.redis.host}&quot;)
    private String host
    @Value(&quot;${spring.data.redis.port}&quot;)
    private int port;
    @Value(&quot;${spring.data.redis.password}&quot;)
    private String password;
    /**
     * Redis와 연결
     * @return RedisConnectionFactory
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisConfiguration = new RedisStandaloneConfiguration();
        redisConfiguration.setHostName(host);
        redisConfiguration.setPort(port);
        redisConfiguration.setPassword(password);
        return new LettuceConnectionFactory(redisConfiguration);
    }
    /**
     * RedisConnection에서 넘겨준 byte값 객체 직렬화
     * @return RedisTemplate&lt;?, ?&gt;
     */
    @Primary
    @Bean
    public RedisTemplate&lt;String, String&gt; redisTemplate() {
        RedisTemplate&lt;String, String&gt; redisTemplate = new RedisTemplate&lt;&gt;();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}</code></pre><ul>
<li>redis를 <strong>Lettuce</strong>를 사용했습니다.</li>
<li>redisTemplate을 통해 redis를 사용할 수 있습니다.<blockquote>
<p><strong>@Primary</strong>는 동일한 타입의 여러 빈이 존재할 경우 기본 Bean으로 정의할 것을 지정합니다.</p>
</blockquote>
</li>
</ul>
<h1 id="5-인터페이스-생성">5. 인터페이스 생성</h1>
<ul>
<li><p>RedisService라는 인터페이스를 생성합니다.</p>
<pre><code>public interface RedisService {
  /**
   * Redis 연결 여부 확인
   * @return boolean
   */
  boolean isConnected();

  /**
   * 특정 데이터를 저장
   * @param key 키
   * @param value 값
   * @param duration 만료 시간
   */
  void setDataExpire(String key, String value, long duration);

  /**
   * 특정 키 값 삭제
   * @param key 키
   */
  void deleteData(String key);

  /**
   * 특정 키의 값을 가져옴
   * @param key 키
   * @return 값
   */
  String getData(String key);

  /**
   * 특정 키에 해당하는 값의 존재 유무
   * @param key 키
   * @return 값의 존재 유무
   */
  boolean existData(String key);
}</code></pre></li>
</ul>
<h1 id="6-구현체-생성">6. 구현체 생성</h1>
<pre><code>@RequiredArgsConstructor
@Service
public class RedisServiceImpl implements RedisService {

    private final RedisTemplate&lt;String, String&gt; redisTemplate;
    private final Logger log = LoggerFactory.getLogger(RedisService.class);

    @Override
    public boolean isConnected() {
        try {
            String pingResponse = redisTemplate.getConnectionFactory().getConnection().ping();

            return &quot;PONG&quot;.equals(pingResponse);
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public void setDataExpire(String key, String value, long duration) {
        ValueOperations&lt;String, String&gt; valueOperations = redisTemplate.opsForValue();
        Duration expireDuration = Duration.ofSeconds(duration);
        valueOperations.set(key, value, expireDuration);
    }

    @Override
    public void deleteData(String key) {
        redisTemplate.delete(key);
    }

    @Override
    public String getData(String key) {
        ValueOperations&lt;String, String&gt; valueOperations = redisTemplate.opsForValue();
        return valueOperations.get(key);
    }

    @Override
    public boolean existData(String key) {
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }
}</code></pre><h1 id="7-test-code-작성">7. Test Code 작성</h1>
<ul>
<li><p>RedisServiceImplTest 파일을 생성합니다.</p>
<blockquote>
<p>테스트 코드를 습관을 들이는 중이기 때문에 다소 부족할 수 있습니다.</p>
</blockquote>
<pre><code>@SpringBootTest
class RedisServiceImplTest {

  @Autowired
  RedisService redisService;

  Logger log = LoggerFactory.getLogger(RedisServiceImplTest.class);

  @Test
  @DisplayName(&quot;연결 확인 - isConnected&quot;)
  void isConnected() {
      boolean connected = redisService.isConnected();

      // 연결 확인
      assertTrue(connected, &quot;Redis와 연결하지 못했습니다.&quot;);
  }

  @Test
  @DisplayName(&quot;만료기간 있는 데이터를 저장 - setDataExpire&quot;)
  void setDataExpire() throws InterruptedException {
      redisService.setDataExpire(&quot;TEST&quot;, &quot;1234&quot;, 10);

      String redisData = redisService.getData(&quot;TEST&quot;);

      // 데이터 검증
      assertNotNull(redisData, &quot;Redis에서 데이터를 검색하지 못했습니다.&quot;);
      assertEquals(&quot;1234&quot;, redisData, &quot;검색된 데이터가 예상과 다릅니다.&quot;);

      // 만료 시간 후 데이터 검증
      Thread.sleep(10000); // 10초 대기
      redisData = redisService.getData(&quot;TEST&quot;);
      assertNull(redisData, &quot;데이터가 만료 시간 후에도 여전히 존재합니다.&quot;);
  }

  @Test
  @DisplayName(&quot;특정 데이터를 삭제 - deleteData&quot;)
  void deleteData() {
      redisService.setDataExpire(&quot;TEST&quot;, &quot;1234&quot;, 10);

      redisService.deleteData(&quot;TEST&quot;);

      // 데이터 검증
      String redisData = redisService.getData(&quot;TEST&quot;);
      assertNull(redisData, &quot;Redis에서 데이터를 삭제하지 못했습니다.&quot;);
  }

  @Test
  @DisplayName(&quot;특정 데이터 조회 - getData&quot;)
  void getData() {
      redisService.setDataExpire(&quot;TEST&quot;, &quot;1234&quot;, 10);
      String redisData = redisService.getData(&quot;TEST&quot;);

      assertNotNull(redisData, &quot;Redis에서 데이터를 검색하지 못했습니다.&quot;);
  }

  @Test
  @DisplayName(&quot;특정 데이터가 존재하는지 여부 - existData&quot;)
  void existData() {
      redisService.setDataExpire(&quot;TEST&quot;, &quot;1234&quot;, 10);
      boolean existData = redisService.existData(&quot;TEST&quot;);

      assertTrue(existData, &quot;Redis에서 데이터를 검색하지 못했습니다.&quot;);
  }
}</code></pre></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Rest Docs] API 문서화 도구]]></title>
            <link>https://velog.io/@in_ho_/Spring-Rest-Docs-API-%EB%AC%B8%EC%84%9C%ED%99%94-%EB%8F%84%EA%B5%AC</link>
            <guid>https://velog.io/@in_ho_/Spring-Rest-Docs-API-%EB%AC%B8%EC%84%9C%ED%99%94-%EB%8F%84%EA%B5%AC</guid>
            <pubDate>Sun, 12 Nov 2023 10:03:08 GMT</pubDate>
            <description><![CDATA[<h1 id="1-spring-rest-docs-swagger">1. Spring Rest Docs, Swagger</h1>
<ul>
<li><p>개발을 할 때 엔드포인트, 요청 방법, 응답 방법을 전달하는 방식에 이점을 얻기 위해 Spring Rest Docs와 Swagger라이브러리를 사용합니다.</p>
</li>
<li><p>Spring Rest Docs는 테스트 코드를 기반으로 문서화를 진행하기 때문에 코드를 수정하지 않아도 된다는 이점이 있습니다.</p>
</li>
<li><p>또한 테스트 코드가 통과되어야만 문서화가 진행되므로 신뢰할 수 있는 문서입니다.</p>
</li>
<li><p>MSA 환경에서 두 라이브러리 중 어떤 방식으로 사용해도, 각자의 스프링 서버에 접근해서 확인해야 하기 때문에 원활하지 않을 수 있습니다.</p>
</li>
<li><p><strong>MSA 환경에서 각 서버의 Spring Rest Docs를 추출하여 Swagger UI 서버로 보내 API 문서를 통합하는 방식을 사용할 것입니다.</strong></p>
</li>
</ul>
<h1 id="2-restdocs-api-spec">2. restdocs-api-spec</h1>
<ul>
<li>Spring MSA 각 컴포넌트에 spring-rest-docs를 이용하여 API 문서화를 진행한 후, 통합적으로 API 문서를 관리하기 위한 라이브러리는 아래와 같습니다.</li>
</ul>
<h2 id="2-1-gradle">2-1. Gradle</h2>
<ul>
<li><a href="https://github.com/ePages-de/restdocs-api-spec">https://github.com/ePages-de/restdocs-api-spec</a></li>
</ul>
<h2 id="2-2-maven">2-2. Maven</h2>
<ul>
<li><a href="https://github.com/BerkleyTechnologyServices/restdocs-spec">https://github.com/BerkleyTechnologyServices/restdocs-spec</a></li>
</ul>
<h1 id="3-각-서버에-spring-rest-docs-사용">3. 각 서버에 Spring Rest Docs 사용</h1>
<ul>
<li>먼저 Spring Rest Docs를 사용하여 각 서버를 문서화하도록 하겠습니다.</li>
</ul>
<h2 id="3-1-spring-rest-docs-의존성-추가">3-1. Spring Rest Docs 의존성 추가</h2>
<pre><code>plugins {
    id &#39;java&#39;
    id &#39;org.springframework.boot&#39; version &#39;2.7.17&#39;
    id &#39;io.spring.dependency-management&#39; version &#39;1.0.15.RELEASE&#39;
    id &quot;org.asciidoctor.jvm.convert&quot; version &quot;3.3.2&quot; /* spring-rest-docs 추가 */
}

group = &#39;...&#39;
version = &#39;...&#39;

java {
    sourceCompatibility = &#39;17&#39;
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

ext {
    set(&#39;springCloudVersion&#39;, &quot;2021.0.8&quot;)
    snippetsDir = file(&#39;build/generated-snippets&#39;)  /* spring-rest-docs 추가 */
}

asciidoctor {  /* spring-rest-docs 추가 */
    dependsOn test
    attributes &#39;snippets&#39;: snippetsDir
    inputs.dir snippetsDir
}

asciidoctor.doFirst {  /* spring-rest-docs 추가 */
    println(&quot;Delete present asciidoctor.&quot;)
    delete file(&#39;src/main/resources/static/docs&#39;)
}

bootJar {  /* spring-rest-docs 추가 */
    dependsOn asciidoctor
    copy {
        from &quot;${asciidoctor.outputDir}&quot;
        into &#39;src/main/resources/static/docs&#39;
    }
}

dependencies {
    /* spring-data-jpa */ implementation &#39;org.springframework.boot:spring-boot-starter-data-jpa&#39;
    /* spring-security */ implementation &#39;org.springframework.boot:spring-boot-starter-security&#39;
    /* jwt */ implementation &#39;io.jsonwebtoken:jjwt:0.9.1&#39;
    /* spring-validation */ implementation &#39;org.springframework.boot:spring-boot-starter-validation&#39;
    /* spring-web */ implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;

    /* eureka-client */ implementation &#39;org.springframework.cloud:spring-cloud-starter-netflix-eureka-client&#39;


    /* mariadb-client */ runtimeOnly &#39;org.mariadb.jdbc:mariadb-java-client&#39;
    /* spring-configuration-processor */ annotationProcessor &#39;org.springframework.boot:spring-boot-configuration-processor&#39;
    /* restdocs-mockmvc */ testImplementation &#39;org.springframework.restdocs:spring-restdocs-mockmvc&#39;  /* spring-rest-docs 추가 */
    /* spring-test */ testImplementation &#39;org.springframework.boot:spring-boot-starter-test&#39;
    /* spring-security */ testImplementation &#39;org.springframework.security:spring-security-test&#39;
    /* coolsms*/ implementation &#39;net.nurigo:javaSDK:2.2&#39;

    /* lombok */
    annotationProcessor &#39;org.projectlombok:lombok&#39;
    compileOnly &#39;org.projectlombok:lombok&#39;
}

dependencyManagement {
    imports {
        mavenBom &quot;org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}&quot;
    }
}

tasks.named(&#39;test&#39;) {
    outputs.dir snippetsDir  /* spring-rest-docs 추가 */
    useJUnitPlatform()
}
</code></pre><ul>
<li>위의 코드에서  /* spring-rest-docs 추가 */라는 주석을 사용한 부분을 추가하면 된다.</li>
</ul>
<h2 id="3-2-spring-security-예외-설정">3-2. Spring Security 예외 설정</h2>
<ul>
<li>Spring Security를 사용하는 프로젝트라면 경로 예외를 해주어야 합니다.<pre><code>.and()
.authorizeRequests()
.antMatchers(&quot;/docs/*).permitAll()</code></pre></li>
</ul>
<h2 id="3-3-test-코드-구현">3-3. Test 코드 구현</h2>
<pre><code>@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureRestDocs
class CertificationControllerTest {

    @Autowired
    private MockMvc mockMvc;
    @Autowired
    private ObjectMapper objectMapper;
    private final static Logger log = LoggerFactory.getLogger(CertificationControllerTest.class);

    @Test
    @DisplayName(&quot;Bean 주입 체크&quot;)
    void isCheckOfBean() {
        log.info(&quot;=====================================================&quot;);
        log.info(&quot;mockMvc Dependency Injection : {}&quot;, mockMvc);
        log.info(&quot;objectMapper Dependency Injection : {}&quot;, objectMapper);
        log.info(&quot;=====================================================&quot;);
    }

    @Test
    @DisplayName(&quot;유레카 서버 연결 확인&quot;)
    void connectedTest() throws Exception {
        Map&lt;String, String&gt; input = new HashMap&lt;&gt;();
        mockMvc.perform(RestDocumentationRequestBuilders.get(&quot;/certification/connected&quot;)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andDo(print())
                .andDo(document(&quot;connected-test&quot;,
//                        requestFields(),
                        responseFields(
                                fieldWithPath(&quot;isConnected&quot;).description(&quot;유레카 서버 연결 여부&quot;)
                        )
                ))
                .andExpect(jsonPath(&quot;$.isConnected&quot;).value(is(true)));
    }
}</code></pre><h2 id="3-5-adoc-파일-생성">3-5. adoc 파일 생성</h2>
<ul>
<li>src/docs/asciidoc 폴더 하위에 아래 파일을 생성한다.<h3 id="3-5-1-connectedadoc">3-5-1. connected.adoc</h3>
<pre><code>[[connected]]
== 유레카 서버 연결 여부
유레카 서버와 연결이 정상적으로 되고 있는지 여부를 확인한다.
</code></pre></li>
</ul>
<p>====== 요청 형식
include::{snippets}/connected-test/http-request.adoc[]
// include::{snippets}/connected-test/request-fields.adoc[]
====== Request Body
include::{snippets}/connected-test/request-body.adoc[]
====== 응답 형식
include::{snippets}/connected-test/http-response.adoc[]
include::{snippets}/connected-test/response-fields.adoc[]
====== Response Body
include::{snippets}/connected-test/response-body.adoc[]
====== Try with curl
include::{snippets}/connected-test/curl-request.adoc[]</p>
<pre><code>
### 3-5-2. index.adoc</code></pre><p>= RESTful Notes API Guide
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 4
:sectnums:
:sectlinks:
:sectanchors:</p>
<p>== My Api Docs
API 명세서 index.html이다. gradle예제
include::src/docs/asciidoc/connected.adoc[]</p>
<pre><code>&gt; 인텔리제이에서 에러를 보여주겠지만 신경 쓰지 말도록 하자

## 3-6. bootJar
- Gradle 명령어인 bootJar를 사용하여 build하도록 한다.
![](https://velog.velcdn.com/images/in_ho_/post/0cf83bd7-4a4e-4967-837d-51232e9c1bcc/image.png)
- 위와 같은 파일이 생성된 것을 확인할 수 있습니다.

## 3-7. 요청 학인
- eureka 서버에 접속하여 직접 Port로 접속하여 확인을 할 수 있습니다.
- 요청 URI : http://192.168.45.200:52691/docs/index.html
![](https://velog.velcdn.com/images/in_ho_/post/c17f7cb1-5720-40d1-928c-bf1b0aa11d67/image.png)

# 4. Spring Cloud Gateway 연결
- 2에서 말한 openapi spec을 사용하여 Swagger UI 서버로 보내 API 문서를 통합하여 관리할 것입니다.

## 4-1. 의존성 추가</code></pre><p>plugins {
    ...
    id &#39;com.epages.restdocs-api-spec&#39; version &#39;0.16.2&#39;
}</p>
<p>dependencies {
    ...
    /* open-api spec */testImplementation &#39;com.epages:restdocs-api-spec-mockmvc:0.16.2&#39;
}</p>
<pre><code>
## 4-2. 작성한 테스트 코드에서 Import문 수정
- 기존의 MockMvcRestDocumentation.document를 MockMvcRestDocumentationWrapper.document로 수정
</code></pre><p>import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document;</p>
<p>```</p>
<p>TODO 후에 이어서 작성</p>
<h1 id="reference">Reference</h1>
<p><a href="https://velog.io/@suhongkim98/spring-REST-Docs%EB%A1%9C-API-%EB%AC%B8%EC%84%9C-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0">https://velog.io/@suhongkim98/spring-REST-Docs%EB%A1%9C-API-%EB%AC%B8%EC%84%9C-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Eureka & Gateway] Gateway와 Eureka 연동]]></title>
            <link>https://velog.io/@in_ho_/Eureka-Gateway-Gateway%EC%99%80-Eureka-%EC%97%B0%EB%8F%99</link>
            <guid>https://velog.io/@in_ho_/Eureka-Gateway-Gateway%EC%99%80-Eureka-%EC%97%B0%EB%8F%99</guid>
            <pubDate>Tue, 07 Nov 2023 08:25:54 GMT</pubDate>
            <description><![CDATA[<h1 id="1-predicate">1. Predicate</h1>
<ul>
<li>요청이 들어오면 Gateway Handler Mapping을 통해 어떤 요청이 들어왔는지 정보를 받고, 요청에 대한 조건을 분기해주는 곳이 <strong>Predicate</strong>입니다.</li>
</ul>
<h1 id="2-filter">2. Filter</h1>
<ul>
<li>디스패처 서블릿에 요청이 전달되기 전, 후에 URL 패턴에 맞는 모든 요청에 대해 부가작업을 처리할 수 있습니다.</li>
<li>필터는 스프링 밖에서 처리됩니다.</li>
</ul>
<h2 id="2-1-filter-적용-방법">2-1. Filter 적용 방법</h2>
<ol>
<li>yaml 파일을 통한 정의</li>
<li>Java Configuration</li>
</ol>
<h2 id="2-2-filterconfig-설정-방법">2-2. FilterConfig 설정 방법</h2>
<pre><code>@Configuration
public class FilterConfig {

    @Bean
    public RouteLocator gatewayRoutes(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(r -&gt; r.path(&quot;/certification-service/**&quot;)
                        .filters(f -&gt; f.addRequestHeader(&quot;certification-request&quot;, &quot;certification-request-header&quot;)
                                .addResponseHeader(&quot;certification-response&quot;, &quot;certification-response-header&quot;))
                        .uri(&quot;http://localhost:2020&quot;))
                .build();
    }
}</code></pre><ul>
<li>certification-service에 들어오는 요청, 응답의 Request, Response Header에 certification-request: certification-request-header, certification-response: certification-response-header라는 값을 추가합니다.</li>
</ul>
<h2 id="2-3-yaml-설정-방법">2-3. yaml 설정 방법</h2>
<pre><code>spring:
  main:
    web-application-type: reactive
  application:
    name: futur
  cloud:
    gateway:
      routes:
        - id: certification # 인증 서버
          uri: http://localhost:2020
          predicates:
            - Path=/certification/**
        - filters:
          - AddRequestHeader=certification-request, certification-request-header
          - AddResponseHeader=certification-response, certification-response-header</code></pre><ul>
<li>FilterConfig와 동일한 기능을 수행합니다.</li>
</ul>
<h1 id="3-gateway와-eureka-연동">3. Gateway와 Eureka 연동</h1>
<ol>
<li>클라이언트로부터 요청이 들어오면 Gateway는 Eureka 서버를 통해 엔드포인트의 <strong>실제 주소값</strong>을 반환 받습니다.</li>
<li>실제 주소 값으로 요청을 전달하고, 해당 응답을 클라이언트에게 전달합니다.</li>
</ol>
<ul>
<li><em>*여기서 유의해야 할 점은 Gateway 서버와 Eureka 서버는 서로 다르다는 것입니다.</em></li>
</ul>
<h2 id="3-1-yaml-설정">3-1. yaml 설정</h2>
<ul>
<li>application.yaml 파일을 아래와 같이 수정합니다.<pre><code>server:
port: 2020
</code></pre></li>
</ul>
<p>spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      routes:
        - id: certification-service
          uri: lb://certification-service
          predicates:
            - Path=/certification/**
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: <a href="http://127.0.0.1:8761/eureka">http://127.0.0.1:8761/eureka</a></p>
<p>```</p>
<ul>
<li>Gateway 서버는 Eureka 서버의 클라이언트 중 하나로 설정되어야 한다.</li>
<li>라우트를 설정할 때 uri를 직접 기재해야 하지만 Eureka 서버의 도움을 받을 경우 동적으로 매핑할 수 있다.<blockquote>
<p>Eureka Client 서버의 spring.application.name 값이 uri에 들어가야 한다.</p>
</blockquote>
</li>
<li>Client는 2020 port로 요청을 보내면 서버에 도달할 수 있다.</li>
</ul>
<h1 id="4-연결-확인">4. 연결 확인</h1>
<ul>
<li>연결하고자 하는 서버에 아래와 같이 코드를 추가합니다.
<img src="https://velog.velcdn.com/images/in_ho_/post/5eff7607-b611-49f5-9b78-ef4ed8ecd1d7/image.png" alt=""></li>
<li>Postman으로 요청을 하면 정상적으로 리턴 받을 수 있는 것을 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/in_ho_/post/42fef585-5231-482e-8c6a-5ac409bd7d4e/image.png" alt=""></li>
</ul>
<h1 id="5-spring-cloud-gateway와-eureka-서버-연동하는-이유">5. Spring Cloud Gateway와 Eureka 서버 연동하는 이유</h1>
<ul>
<li>Spring Cloud Gateway를 사용하면 <strong>로드 밸런싱 기능</strong>을 사용할 수 있습니다.</li>
</ul>
<blockquote>
<p><strong>Load Balancing</strong></p>
</blockquote>
<ul>
<li>로드 밸런싱은 애플리케이션을 지원하는 리소스 풀 전체에 네트워크 <strong>트래픽을 균등하게 배포</strong>하는 방법입니다.</li>
</ul>
<h1 id="reference">Reference</h1>
<p><a href="https://velog.io/@korea3611/Spring-Boot-Spring-Cloud-Gateway-Eureka-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0-MSA4">https://velog.io/@korea3611/Spring-Boot-Spring-Cloud-Gateway-Eureka-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0-MSA4</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Gateway] Spring Cloud Gateway]]></title>
            <link>https://velog.io/@in_ho_/Gateway-Spring-Cloud-Gateway</link>
            <guid>https://velog.io/@in_ho_/Gateway-Spring-Cloud-Gateway</guid>
            <pubDate>Mon, 06 Nov 2023 05:14:08 GMT</pubDate>
            <description><![CDATA[<h1 id="1-gateway의-역할">1. Gateway의 역할</h1>
<ul>
<li>라우팅 설정에 따라서 각각의 <strong>엔드포인트</strong>로 들어오는 요청과 응답을 전달해주는 <strong>프록시</strong> 역할을 하게 됩니다.</li>
<li>Spring Cloud Gateway를 사용하는 이유는 마이크로서비스가 추가되거나 기존에 있었던 주소 변경, 파라미터 변경 시 클라이언트도 수정 배포가 되어야 하므로 <strong>단일 진입점</strong>을 갖도록 하기 위해 만들어졌습니다.
<img src="https://velog.velcdn.com/images/in_ho_/post/edf5c2a1-202c-40eb-8a43-aa63b5928c79/image.png" alt=""></li>
</ul>
<h1 id="2-의존성-추가">2. 의존성 추가</h1>
<pre><code>dependencies {
    implementation &#39;org.springframework.cloud:spring-cloud-starter-gateway&#39;
}</code></pre><h1 id="3-설정-추가">3. 설정 추가</h1>
<ul>
<li>application.properties 또는 application.yaml(yml) 파일에 추가합니다.<pre><code>spring:
application:
  name: spring-cloud-gateway-service
cloud:
  gateway:
    routes:
      - id: certification # 인증 서버
        uri: http://localhost:2000/
        predicates:
          - Path=/api/v1/user/**</code></pre></li>
<li>위처럼 작성할 경우 <a href="http://localhost:2000/api/v1/user/">http://localhost:2000/api/v1/user/</a>** 요청이 해당서버로 인계되게 됩니다.</li>
</ul>
<h1 id="reference">Reference</h1>
<ul>
<li><a href="https://velog.io/@korea3611/Spring-Boot-Spring-Cloud-Gateway-%EB%A7%8C%EB%93%A4%EA%B8%B0-MSA2">https://velog.io/@korea3611/Spring-Boot-Spring-Cloud-Gateway-%EB%A7%8C%EB%93%A4%EA%B8%B0-MSA2</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Eureka] Eureka Project 생성]]></title>
            <link>https://velog.io/@in_ho_/Eureka-Eureka-Project-%EC%83%9D%EC%84%B1</link>
            <guid>https://velog.io/@in_ho_/Eureka-Eureka-Project-%EC%83%9D%EC%84%B1</guid>
            <pubDate>Tue, 31 Oct 2023 12:00:52 GMT</pubDate>
            <description><![CDATA[<h1 id="1-eureka-서버란">1. Eureka 서버란?</h1>
<ul>
<li><p>클라우드 환경의 다수의 서비스들의 <strong>로드 밸런싱</strong> 및 <strong>장애 조치</strong> 목적을 가진 <strong>미들웨어서버</strong>이다</p>
<blockquote>
<p>로드 밸런싱 : 특정 서비스를 제공하는 서버가 여러대 있을 때 트래픽을 . 한 서버에 몰리지 않도록 하는 기술
미들웨어 : 양쪽의 서비스 중간에 위치해 매개 역할을 하는 소프트 웨어</p>
</blockquote>
</li>
<li><p>Eureka Server에 등록된 서비스를 Eureka Client라고 부른다.</p>
</li>
</ul>
<h2 id="1-1-의존성-추가">1-1. 의존성 추가</h2>
<pre><code>    implementation &#39;org.springframework.cloud:spring-cloud-starter-netflix-eureka-server&#39;</code></pre><h2 id="1-2-eureka-설정">1-2. Eureka 설정</h2>
<pre><code>server:
  port: 8761

spring:
  application:
    name: disocovery-service

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false</code></pre><pre><code>@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }
}</code></pre><h2 id="1-3-eureka-server-설정">1-3. Eureka Server 설정</h2>
<p><img src="https://velog.velcdn.com/images/in_ho_/post/1e3c0d2f-33a5-4972-94ca-5fe6b721180f/image.png" alt=""></p>
<ul>
<li>지정한 port로 접속하면 해당 화면을 확인할 수 있습니다.</li>
<li><strong>Instances currently registered with Eureka</strong> 항목을 보면 연결된 Eureka Client를 확인할 수 있습니다.</li>
</ul>
<h2 id="1-4-eureka-client-서비스-등록">1-4. Eureka Client 서비스 등록</h2>
<h3 id="1-4-1-의존성-추가">1-4-1. 의존성 추가</h3>
<pre><code>    implementation &#39;org.springframework.cloud:spring-cloud-starter-netflix-eureka-client&#39;</code></pre><h3 id="1-4-2-eureka-client-설정">1-4-2. Eureka Client 설정</h3>
<pre><code>@EnableDiscoveryClient
@SpringBootApplication
public class CertificationApplication {
    public static void main(String[] args) {
        SpringApplication.run(CertificationApplication.class, args);
    }
}
</code></pre><pre><code>server:
  port: 0 # 랜덤 포트

spring:
  application:
    name: certification

eureka:
  instance:
    instance-id: ${spring.cloud.client.hostname}:${spring.application.instance_id:${random.value}}
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka</code></pre><h2 id="1-5-연결-확인">1-5. 연결 확인</h2>
<p><img src="https://velog.velcdn.com/images/in_ho_/post/a5755dbd-68db-4837-ba25-d5561cf2bd4f/image.png" alt=""></p>
<hr>
<h1 id="reference">Reference</h1>
<ul>
<li><a href="https://cjw-awdsd.tistory.com/52">https://cjw-awdsd.tistory.com/52</a></li>
<li><a href="https://velog.io/@korea3611/Spring-BootSpring-Cloud-Netflix-Eureka-%EC%84%9C%EB%B2%84-%EB%A7%8C%EB%93%A4%EA%B8%B0-MSA1">https://velog.io/@korea3611/Spring-BootSpring-Cloud-Netflix-Eureka-%EC%84%9C%EB%B2%84-%EB%A7%8C%EB%93%A4%EA%B8%B0-MSA1</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] Spring 기본]]></title>
            <link>https://velog.io/@in_ho_/Spring-Spring-%EA%B8%B0%EB%B3%B8</link>
            <guid>https://velog.io/@in_ho_/Spring-Spring-%EA%B8%B0%EB%B3%B8</guid>
            <pubDate>Tue, 31 Oct 2023 11:31:42 GMT</pubDate>
            <description><![CDATA[<h1 id="1-의존성-주입-di">1. 의존성 주입, DI</h1>
<ul>
<li>스프링 트라이앵글 중 가장 핵심적인 개념은 <strong>의존성 주입</strong>입니다. 의존성 주입은 <strong>객체 간 결합을 낮추는 유용한 방법</strong>입니다.</li>
<li>스프링 프레임워크는 스프링 빈을 선언하고, 스프링 빈 컨테이너를 사용하여 빈들 사이에 의존성을 주입합니다.</li>
</ul>
<h1 id="2-빈-bean">2. 빈, Bean</h1>
<ul>
<li><p><strong>애플리케이션에서 공통으로 사용할 경우 정의</strong></p>
</li>
<li><p><strong>스프링 빈은 스프린 빈 컨테이너가 관리하는 순수 자바 객체</strong>를 의미합니다.</p>
<blockquote>
<p>스프링 빈 컨테이너는 <strong>스프링 빈 정의</strong>를 읽고, 스프링 빈 객체를 생성한 후, 서로 의존성이 있는 스프링 빈 객체들을 주입한 후 애플리케이션 실행 준비 상태가 됩니다.</p>
</blockquote>
<blockquote>
<p>애플리케이션이 종료되기 전 스프링 빈 컨테이너는 관리하고 있던 스프링 빈들을 종료합니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/in_ho_/post/e0ea4a27-f938-4ee5-b604-a28162fa3687/image.png" alt=""></p>
<ol>
<li>스프링 빈 컨테이너 구현체에 따라 정해진 포맷의 설정 파일을 로딩합니다.<blockquote>
<p>설정 파일 포맷은 자바 클래스, XML, 그루비 등 종류가 다양합니다.
스프링 부트의 기본 스프링 빈 컨테이너 구현체는 ConfigurableApplicationContext이며 설정 포맷은 자바 클래스입니다.</p>
</blockquote>
</li>
<li>설정 파일에 정의된 스프링 빈 정의를 로드하고, 지정된 클래스패스에 위치한 클래스들을 스캔하고, 스프링 빈 정의가 있으면 로딩한다.</li>
<li>로딩을 마친 스프링 빈 컨테이너는 정의된 대로 스프링 빈을 생성하고 컨테이너에서 관리한다.</li>
<li>스프링 빈들 사이에 서로 의존성이 있는 객체들은 <strong>스프링 빈 컨테이너가 조립한다.</strong></li>
<li>스프링 빈 컨테이너 구현 클래스에 따라 추가 작업을 한다.</li>
<li>작업이 완료되면 애플리케이션은 실행 준비를 완료한다.</li>
</ol>
<ul>
<li><strong>스프링 빈을 관리하는 스프링 빈 컨테이너는 스프링 애플리케이션을 실행하면 가장 먼저 실행됩니다.</strong></li>
<li>스프링 빈 객체들은 생성한 주체와 정의 방법에 따라 크게 세 가지로 분류할 수 있습니다.<ol>
<li><strong>스프링 프레임워크의 기능을 스프링 빈으로 정의한 것</strong> <ul>
<li>Environment, ApplicationContext 등 </li>
</ul>
</li>
<li><strong>스프링 빈 컨테이너가 로딩하는 설정 파일에 정의된 것</strong><ul>
<li>@Bean</li>
</ul>
</li>
<li><strong>클래스패스에 있는 스테레오 타입 어노테이션</strong><ul>
<li>@Component</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
</ul>
<h2 id="2-1-spring-boot에-bean을-정의하는-방법">2-1. Spring Boot에 Bean을 정의하는 방법</h2>
<ol>
<li>자바 설정 클래스에서 @Bean 어노테이션을 사용해 정의</li>
<li>스트레오 타입 어노테이션을 사용하여 정의</li>
<li>BeanDefinition 인터페이스를 구현하여 정의</li>
<li>XML 설정 방식을 사용하여 정의</li>
</ol>
<h2 id="2-2-configuration">2-2. @Configuration</h2>
<ul>
<li><p><strong>자바 설정 클래스</strong></p>
<pre><code>@Configuration
public class ThreadPollConfig {
  @Bean
  public ThreadPoolTaskExecutor threadPoolTaskExecutor() {

  }
}</code></pre><h2 id="2-3-componentscan">2-3. @ComponentScan</h2>
</li>
<li><p><strong>설정된 패키지 경로에 포함된 자바 설정 클래스들과 스테레오 타입 어노테이션을 스캔한다.</strong></p>
<blockquote>
<p>스테레오 타입 어노테이션</p>
<pre><code>1. @Repository
2. @Component
3. @Controller
4. @Service
5. @RestController</code></pre></blockquote>
<pre><code>@Configuration
@ComponentScan(
  basePackages = {
      &quot;com.springboot.config&quot;,
      &quot;com.springboot.domain&quot;
  },
  basePackageClasses = {
      ThreadPoolConfig.class,
      ServerConfig.class
  }
)</code></pre></li>
<li><p><strong>basePackages</strong>을 설정한 패키지 하위의 클래스들을 모두 스캔합니다.</p>
</li>
<li><p><strong>basePacakgeClasses</strong>로 특정 클래스를 스캔할 수 있습니다.</p>
</li>
<li><p><strong>@SpringBootApplication</strong>이 선언된 클래스의 상위 패키지나 다른 경로의 클래스는 @ComponentScan의 대상이 되지 않습니다.</p>
</li>
</ul>
<h2 id="2-3-import">2-3. @Import</h2>
<ul>
<li><strong>@Import는 명시된 여러 개의 자바 설정 클래스를 하나의 그룹으로 묶는 역할</strong>을 합니다.</li>
<li>@Import 어노테아션이 정이된 자바 설정 클래스가 다른 자바 설정 클래스들을 임포트하는 개념입니다.<pre><code>@Configuration
@Import(value = {ThreadPoolConfig.class, MailConfig.class}
public class ServerConfig {
  ...
}</code></pre></li>
<li>value에 입력받는 값은 @Configuration이 선언된 자바 설정 클래스입니다.<blockquote>
<p>@ComponentScan과 동일한 기능입니다.</p>
</blockquote>
</li>
</ul>
<h2 id="2-4-스테레오-타입-빈">2-4. 스테레오 타입 빈</h2>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] Spring Boot란?]]></title>
            <link>https://velog.io/@in_ho_/Spring-Spring-Boot%EB%9E%80</link>
            <guid>https://velog.io/@in_ho_/Spring-Spring-Boot%EB%9E%80</guid>
            <pubDate>Mon, 30 Oct 2023 14:33:42 GMT</pubDate>
            <description><![CDATA[<h1 id="1-spring-boot">1. Spring Boot</h1>
<ul>
<li>스프링 부트는 스프링 프레임워크의 범위를 포함한다.</li>
<li>스프링 부트는 스프링 프레임워크를 기반으로 개발된 프레임워크이다.</li>
<li>아래의 항목은 스프링 부트가 추가적으로 제공하는 기능이다.</li>
</ul>
<h2 id="1-1-단독-실행-가능한-스프링-애플리케이션">1-1. 단독 실행 가능한 스프링 애플리케이션</h2>
<ul>
<li>스프링 부트 프로젝트는 빌드 플러그인을 제공하고, 이를 실행하면 <strong>단독 실행 가능한 JAR 파일</strong>을 만들 수 있다.</li>
</ul>
<h2 id="1-2-간편-설정을-위한-스타터-의존성-제공">1-2. 간편 설정을 위한 &#39;스타터&#39; 의존성 제공</h2>
<ul>
<li>스타터 내부에 라이브러리 의존성 설정을 포함하고 있어, 기능을 사용하는데 필요한 모든 라이브러리를 한 번에 추가할 수 있다.</li>
</ul>
<h2 id="1-3-스프링-기능을-자동-설정하는-자동-구성-제공">1-3. 스프링 기능을 자동 설정하는 &#39;자동 구성&#39; 제공</h2>
<ul>
<li>스프링 부트는 <strong>자동 구성</strong> 기능을 제공한다.</li>
<li>특정 조건이 충족되면 미리 설정된 자바 설정 클래스가 동작하고, 애플리케이션을 구성한다.</li>
</ul>
<h2 id="1-4-액추에이터">1-4. 액추에이터</h2>
<ul>
<li>스프링 부트는 spring-boot-actuator 모듈을 제공하는데 이를 사용하여 서버를 모니터링할 수 있다.</li>
</ul>
<h2 id="1-5-xml-설정을-위한-일이-없음">1-5. XML 설정을 위한 일이 없음</h2>
<ul>
<li>스프링 프레임워크를 이용할 때 ApplicationContext 설정을 XML로 작성했습니다.</li>
<li>하지만 스프링 프레임워크 3.0부터 Java 클래스를 이용하여 설정하는 Java Config 방식의 등장하였습니다.</li>
</ul>
<h2 id="1-6-내장-was">1-6. 내장 WAS</h2>
<ul>
<li>spring-boot-starter-web에 Tomcat이 내장되어 있기 때문에 Dev, Stage, Production 환경에서 일관된 실행 환경을 가질 수 있습니다.<blockquote>
<p>Tomcat이 아닌 Jetty나 UnderTow와 같은 것들로 쉽게 변경할 수 있습니다.</p>
</blockquote>
</li>
</ul>
<h1 id="2-spring-boot-생산성에-도움을-주는-라이브러리">2. Spring Boot 생산성에 도움을 주는 라이브러리</h1>
<ol>
<li>spring-boot-devtools<ul>
<li>애플리케이션 실행 후 클래스패스에 포함된 파일이 변경되면 <strong>자동으로 애플리케이션을 재시작</strong></li>
<li>리소스가 변경되면 자동으로 새로고침</li>
<li>웹 서비스에서 사용하는 캐시를 자동으로 막아줌.<blockquote>
<p>실제 환경에는 배포되지 않도록 해야함.</p>
</blockquote>
</li>
</ul>
</li>
<li>spring-boot-configuration-processor<ul>
<li>application.yml이나 application.properties 파일은 편집할 때 IDE에서 자동으로 완성해줌.</li>
</ul>
</li>
<li>lombok<ul>
<li>반복해서 생성해야 하는 코드들을 줄여줌.</li>
</ul>
</li>
</ol>
<h1 id="3-pomxml-분석">3. pom.xml 분석</h1>
<pre><code>&lt;groupId&gt;...&lt;/groupId&gt;
&lt;artifactId&gt;...&lt;/artifactId&gt;
&lt;version&gt;1.0.0-SNAPSHOT&lt;/version&gt;
&lt;name&gt;...&lt;/name&gt;

&lt;!-- 부모 POM 가져오기 --&gt;
&lt;parent&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
    &lt;version&gt;2.6.9&lt;/version&gt;
    &lt;relativePath/&gt;
&lt;/parent&gt;

&lt;!-- 의존성 추가 --&gt;
&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframwork.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;

&lt;!-- Build Tool 관련 영역 --&gt;
&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;</code></pre><ul>
<li>자식 pom.xml은 부모 pom.xml 파일에 설정된 의존 관계를 그대로 상속 받을 수 있습니다.(<strong>자식 pom.xml에 설정된 의존관계가 우선권을 가집니다.</strong>)</li>
<li><blockquote>
<p>이러한 코드들을 나타내는 부분이 &amp;ltparent&amp;gt ~ &amp;lt/parent&amp;gt입니다.</p>
</blockquote>
</li>
</ul>
<h1 id="4-springbootapplication">4. @SpringBootApplication</h1>
<ul>
<li>스프링 부트는 엔트리 포인트를 제공합니다.<pre><code>@SpringBootApplication
public class ApiApplication {
 public static void main(String[] args) {
     SpringApplication.run(ApiApplication.class, args);
 }
}</code></pre></li>
<li>@SpringBootApplication은 <strong>3개의 주요한 어노테이션을 포함합니다.</strong></li>
</ul>
<h2 id="4-1-springbootconfiguration">4-1. @SpringBootConfiguration</h2>
<ul>
<li>내부에는 <strong>@Configuration</strong>을 포함하고 있습니다. @Configuration이 명시된 클래스는 자바 설정 클래스로 여겨지며, <strong>별도의 스프링 빈을 정의할 수 있습니다.</strong></li>
</ul>
<h2 id="4-2-enableautoconfiguration">4-2. @EnableAutoConfiguration</h2>
<ul>
<li>스프링 부트 프레임워크의 <strong>자동 설정 기능</strong>을 활성화하는 기능을 제공합니다.</li>
</ul>
<h2 id="4-3-componentscan">4-3. @ComponentScan</h2>
<ul>
<li><strong>클래스패스에 포함되어 있는 @Configuration으로 정의된 자바 설정 클래스와 스테리오 타입 어노테이션(@Component)로 정의된 클래스를 스캔합니다.</strong></li>
<li><strong>스프링 빈 설정을 스캔하며 찾아낸 것들은 스프링 빈 컨테이너가 스프링 빈으로 로딩하고 관리합니다.</strong></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] Spring이란?]]></title>
            <link>https://velog.io/@in_ho_/Spring-Spring%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@in_ho_/Spring-Spring%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Mon, 30 Oct 2023 14:08:23 GMT</pubDate>
            <description><![CDATA[<h1 id="1-spring-framework의-특징">1. Spring Framework의 특징</h1>
<ol>
<li>POJO(Plain Old Java Object) 기반의 경량 컨테이너 제공</li>
<li>복잡한 비즈니스 영역의 문제를 쉽게 개발하고 운영하기 위한 철학</li>
<li>여러 개의 개별 단위로 구성되어 있는 모듈식 프레임워크</li>
<li>높은 확장성 및 범용성, 광범위한 생태계 시스템</li>
<li>엔터프라이즈 애플리케이션에 적합한 경량급 오픈 소스 프레임워크</li>
</ol>
<h2 id="1-1-pojo-기반의-경량-컨테이너">1-1. POJO 기반의 경량 컨테이너</h2>
<ul>
<li>개발자는 POJO 클래스를 개발하고, 스프링 컨테이너는 POJO 객체들을 관리한다.<ul>
<li>스프링 컨테이너가 관리하는 객체를 <strong>Spring Bean</strong>이라고 한다.<blockquote>
<p>스프링 컨테이너는 ApplicationContext 인터페이스를 구현한 클래스를 의미한다.
넓은 의미로 <strong>Spring Container = ApplicationContext</strong>라고 한다.</p>
</blockquote>
</li>
<li>POJO 객체는 특정 기술에 종속되지 않는 <strong>순수 자바 객체</strong>를 의미한다.<blockquote>
<p>스프링 프레임워크를 사용하면 개발자의 코드에 최대한 적은 영향을 줌으로, 개발하는 클래스와 프레임워크의 코드가 분리되어 <strong>복잡도를 낮출 수 있다.</strong></p>
</blockquote>
</li>
</ul>
</li>
</ul>
<h2 id="1-2-복잡한-비즈니스-영역의-문제를-쉽게-개발하고-운영하기-위한-철학">1-2. 복잡한 비즈니스 영역의 문제를 쉽게 개발하고 운영하기 위한 철학</h2>
<p><img src="https://velog.velcdn.com/images/in_ho_/post/413b22aa-9fa3-4322-9cf4-c1d893a8eeae/image.png" alt=""></p>
<ul>
<li>위의 그림은 <strong>스프링 트라이앵글</strong>이라고 한다.<ul>
<li>스프링 프레임워크의 핵심 요소는<ol>
<li><strong>의존성 주입(DI)</strong></li>
<li><strong>관점 지향 프로그래밍(AOP)</strong></li>
<li><strong>서비스 추상화(PSA)</strong></li>
</ol>
</li>
<li>이며, POJO 객체를 기반으로 동작하도록 설계되었다. 이러한 핵심 요소를 통해 개발자가 도메인 영역의 문제에 집중할 수 있도록 한다.</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>도메인</strong></p>
</blockquote>
<ul>
<li>소프트웨어상에서 해결해야 할 비즈니스 영역의 문제를 도메인이라고 한다.</li>
</ul>
<h2 id="1-3-모듈식-프레임워크">1-3. 모듈식 프레임워크</h2>
<ul>
<li>스프링 프레임워크는 엔터프라이즈급 애플리케이션을 개발할 수 있는 여러 기능을 포함하고 있다.</li>
<li>기능들은 분류되어 모듈이라는 단위로 관리된다.</li>
</ul>
<h2 id="1-4-높은-확장성과-범용성-생태계-시스템">1-4. 높은 확장성과 범용성, 생태계 시스템</h2>
<ul>
<li>다양한 스프링 프레젝트(모듈)을 선택할 수 있고 하나의 프로젝트 안에는 수많은 제품에 대한 확장성과 생태계를 적용할 수 있다.</li>
</ul>
<h2 id="1-5-엔터프라이즈급-애플리케이션-개발에-적합한-오픈-소스-경량급-프레임워크">1-5. 엔터프라이즈급 애플리케이션 개발에 적합한 오픈 소스 경량급 프레임워크</h2>
<ul>
<li>스프링 프레임워크를 이용하면 일관성 있게 엔터프라이즈급 애플리케이션을 개발할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[MSA] 마이크로 서비스 아키텍처란?]]></title>
            <link>https://velog.io/@in_ho_/MSA-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EB%9E%80</link>
            <guid>https://velog.io/@in_ho_/MSA-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EB%9E%80</guid>
            <pubDate>Sun, 29 Oct 2023 13:46:10 GMT</pubDate>
            <description><![CDATA[<h1 id="1-모놀로식-아키텍처">1. 모놀로식 아키텍처</h1>
<ul>
<li><strong>하나의 시스템이 서비스 전체의 기능을 처리하도록 설계</strong></li>
</ul>
<h2 id="1-1-장점">1-1. 장점</h2>
<ul>
<li><strong>하나의 WAS(Web Application Server)에서 모든 기능을 처리하도록 구성</strong>한다.<ul>
<li>네트워크로 인한 지연이나 데이터 유실을 걱정할 필요가 없다.</li>
</ul>
</li>
<li>데이터를 저장하기 위한 <strong>하나의 DB</strong>를 사용한다.<ul>
<li>DB가 하나이므로 트랜잭션을 쉽게 사용할 수 있다.</li>
</ul>
</li>
<li>간단한 구조 덕분에 시스템 운영과 개발이 편리한 장점이 있습니다.</li>
<li>테스트 코드를 작성하기 용이함.</li>
<li>소규모 개발 팀이 비교적 간단하고 작은 기능을 제공하는 서비스를 개발한다면 모놀로식 아키텍처가 효율적입니다.</li>
</ul>
<h2 id="1-2-단점">1-2. 단점</h2>
<ul>
<li>하나의 서버가 여러 기능을 제공하므로 서비스 기능이 많아지면 복잡해질 수 있다.<ul>
<li>스파게티 코드가 되기 쉬움.</li>
</ul>
</li>
<li>모놀로식 아키텍처 서버 같은 경우 정적(static) 파일뿐만 아니라 서비스 기능까지 웹 서비스에 필요한 모든 기능을 제공합니다.<ul>
<li>이럴 경우 <strong>서버 기능과 클라이언트 기능이 뒤 섞인채 개발할 수 밖에 없습니다.</strong></li>
</ul>
</li>
<li>특정 기능만 트래픽이 늘어날 경우 서버를 scale-out(확장)할 때 서버를 하나 추가하는 방식밖에 없으므로 용이하지 않습니다.</li>
<li>서비스의 규모가 커질 경우 확장이 용이하지 않습니다.</li>
</ul>
<h1 id="2-마이크로서비스-아키텍처">2. 마이크로서비스 아키텍처</h1>
<ul>
<li>마이크로서비스 아키텍처는 <strong>기능이 나뉜 여러 애플리케이션이 있고, 각각의 독립된 데이터 저장소를 사용한다.</strong><h2 id="2-1-키워드">2-1. 키워드</h2>
</li>
</ul>
<ol>
<li><strong>대규모 시스템</strong></li>
<li><strong>분산 처리 시스템</strong></li>
<li><strong>컴포넌트들의 집합</strong></li>
<li><strong>시스템 확장</strong></li>
</ol>
<ul>
<li><p>이러한 특징들은 서비스 지향 아키텍처(Sercice Oriented Architecture, SOA)와 공통점이 많다.</p>
<blockquote>
<p>서비스 지향 아키텍처는 대규모 시스템을 설계할 때, 서비스 기능 단위로 시스템을 묶어 시스템 기능을 구현한 것을 말한다.</p>
</blockquote>
</li>
</ul>
<h2 id="2-2-느슨한-결합">2-2. 느슨한 결합</h2>
<ul>
<li>마이크로서비스스는 독립적으로 동작해야 하고, 다른 마이크로 서비스에 <strong>의존성을 최소화</strong>해야 합니다.</li>
<li>이러한 느슨한 결합을 구현하기 위해 <strong>마이크로서비스마다 각각의 독립된 데이터 저장소가 필요합니다.</strong></li>
</ul>
<h2 id="2-3-설계-시-유의사항">2-3. 설계 시 유의사항</h2>
<ul>
<li><p>마이크로서비스들은 기능과 성격에 맞게 잘 분리되어야 한다.</p>
<ul>
<li>기능이 너무 작지도, 너무 크지도 않게 설계되어야 하기 때문입니다.</li>
</ul>
</li>
<li><p>네트워크 프로토콜이 가벼워야 함.</p>
<ul>
<li><p>각 마이크로서비스 컴포넌트들은 기능을 연동할 때 API를 통해 서로 데이터를 주고 받습니다.</p>
<ul>
<li><strong>네트워크 프로토콜의 성능 저하가 장애가 될 수 있습니다.</strong></li>
</ul>
</li>
<li><p>네트워크를 통해 마이크로서비스 사이에 데이터를 전달하려면 <strong>객체는 바이트 형태로 변경</strong>되어야 합니다.</p>
<blockquote>
<ul>
<li><strong>직렬화</strong></li>
</ul>
</blockquote>
<ul>
<li>객체 -&gt; 바이트</li>
</ul>
<blockquote>
<ul>
<li>** 역직렬화**</li>
</ul>
</blockquote>
<ul>
<li>바이트 데이터 -&gt; 객체</li>
</ul>
<ul>
<li><p>직렬된 바이트가 원래의 객체보다 훨씬 크다면 네트워크에서 성능 저하가 발생합니다.</p>
</li>
<li><p>또한 직렬화/역직렬화 과정에서 CPU와 메모리와 같은 시스템 리소스를 많이 사용하면 시스템의 전반적인 성능 저하가 발생합니다.</p>
<ul>
<li><p>보통은 JSON을 많이 주고 받으며, HTTP 기반의 REST API를 많이 사용합니다.</p>
<blockquote>
<p>이외에도 gRPC, Thrift, Avro, Protobuffer 등을 사용합니다.</p>
</blockquote>
<blockquote>
<p>그리고 <strong>비동기 처리</strong>를 위해 AMQP 프로토콜을 사용하는 <strong>메시징 큐 시스템</strong>기반으로 데이터를 주고받을 수 있습니다. 대표적으로는 <strong>RabbitMQ</strong>가 있습니다. 최근에는 메세지 스트리밍 시스템인 <strong>Kafaka</strong>가 있습니다.</p>
</blockquote>
<blockquote>
<p>메세징 큐 시스템은 REST API보다 통신에 높은 신뢰성을 제공한다는 장점이 있지만, 마이크로서비스 사이에 메세징 큐 시스템에 의존성이 생긴다는 단점이 있습니다.</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="2-4-장점">2-4. 장점</h2>
<ol>
<li><strong>독립성</strong><ul>
<li>하나의 마이크로서비스는 하나의 비즈니스 기능을 담당하므로 다른 마이크로서비스와 간섭이 최소화됩니다.</li>
</ul>
</li>
<li><strong>대용량 데이터를 저장하고 처리하는데 자유로움</strong><ul>
<li>독립된 데이터 저장소를 갖고 있기 때문에 대용량 데이터를 마이크로서비스마다 나누어 저장할 수 있습니다.</li>
</ul>
</li>
<li><strong>시스템 장애에 견고</strong><ul>
<li>마이크로서비스는 느슨하게 서로가 결합되어 있고, 독립적이기 때문에 서로간에 미치는 영향이 적습니다.</li>
</ul>
</li>
<li><strong>서비스 배포 주기가 빠름</strong><ul>
<li>배포를 자주하려면 CI/CD 시스템이 구축되어 있어야 합니다.</li>
</ul>
</li>
<li><strong>확장성</strong><ul>
<li>마이크로서비스 단위로 확장할 수 있습니다.</li>
</ul>
</li>
<li><strong>사용자 반응에 민첩하게 대응</strong><ul>
<li>새로운 서비스를 마이크로서비스로 분리하여 설계하고 시스템이 포함하면 사용자 반응에 따라 시스템을 고도화하거나 빠르게 시스템에서 제외할 수 있습니다.</li>
</ul>
</li>
</ol>
<h2 id="2-5-단점">2-5. 단점</h2>
<ol>
<li><p><strong>개발하기 어려운 아키텍처</strong></p>
<ul>
<li>시스템이 네트워크상에 분산되어 있기 때문에 발생한다.</li>
<li>데이터 저장소가 분리되어 있으므로 트랜잭션을 사용할 수 없다.<blockquote>
<p>개발자는 여러 장애 상황을 대비하는 폴백(fallback) 기능을 고려해야 합니다.</p>
</blockquote>
<ul>
<li>다른 마이크로서비스가 운영이 불가능한 상태일 때를 대비하는 기능을 의미한다. 이 기능을 사용하여 어떤 경우라도 사용자에게 서비스되도록 해야 한다.     </li>
</ul>
</li>
</ul>
</li>
<li><p><strong>운영하기 매우 어려운 아키텍처</strong></p>
<ul>
<li>어디서 에러가 발생했는지 어려움.</li>
<li>데이터가 분산되어 있기 때문에 분산 트랜잭션을 사용하지 않는다면 데이터의 일관성을 유지하기 어려움.</li>
</ul>
</li>
<li><p><strong>설계하기 어려운 아키텍처</strong></p>
<ul>
<li>잘 설계된 마이크로서비스 아키텍처 시스템은 복잡한 비즈니스 로직을 개발해서 보다 쉽게 유지보수할 수 있고, 동시에 많은 사용자 요청을 처리할 수 있다. 하지만 잘못된 방향으로 설계한다면 이 장점들이 단점이 될 수 있습니다.</li>
</ul>
</li>
<li><p><strong>마이크로서비스 아키텍처로 설계된 서비스 운영에는 여러가지 자동화된 시스템이 필요하다.</strong></p>
<blockquote>
<p>마이크로서비스를 도입하는 이유</p>
</blockquote>
<ul>
<li>빠른 서비스 개발, 운영, 대규모 서비스를 처리하기 위하여.</li>
</ul>
<ul>
<li>빠르게 배포하기 위해서 CI/CD 시스템이 필요합니다.</li>
<li>수십, 수백개의 인스턴스를 제어해야 하므로 모니터링 시스템도 필요합니다.(알림, 로그)</li>
</ul>
</li>
</ol>
<h1 id="3-마이크로서비스-아키텍처의-설계">3. 마이크로서비스 아키텍처의 설계</h1>
<h2 id="3-1-서비스-세분화의-법칙">3-1. 서비스 세분화의 법칙</h2>
<ul>
<li>서비스 세분화의 법칙은 서비스 지향 아키텍처의 여러 핵심 원칙 중 하나입니다.</li>
<li>서비스 세분화 원칙은 4개의 요소로 구성되어 있습니다.</li>
</ul>
<ol>
<li>비즈니스 기능<ul>
<li><strong>비즈니스 기능으로 서비스를 나눈다.</strong></li>
</ul>
</li>
<li>성능<ul>
<li>성능이 떨어지는 마이크로서비스는 너무 많은 기능을 처리하고 있는지 파악해서 설계 필요</li>
</ul>
</li>
<li>메세지 크기<ul>
<li>메세지 크기가 너무 크면 직렬화/역직렬화하는데 성능 문제가 발생합니다.</li>
<li><strong>단, 비즈니스 기능이나 일관성을 유지하는 트랜잭션에 문제가 없다면 이는 무시해도 좋습니다.</strong></li>
</ul>
</li>
<li>트랜잭션<ul>
<li>데이터를 기준으로 서비스를 분리하는 방법</li>
</ul>
</li>
</ol>
<h2 id="3-2-바운디드-컨텍스트">3-2. 바운디드 컨텍스트</h2>
<ul>
<li><p>도메인 주도 개발의 핵심 개념 중 하나.</p>
<blockquote>
<p>도메인</p>
</blockquote>
<ul>
<li><strong>비즈니스 전체나 조직이 행하는 일</strong></li>
</ul>
</li>
<li><p>도메인은 서브 도메인들로 구분할 수 있는데, 도메인과 다른 도메일이 확연되는 경계를 <strong>바운디드 컨텍스트</strong>라고 합니다.</p>
</li>
<li><p>바운디드 컨텍스트는 다른 도메인 모델과 독립적인 영역으로, 바운디드 컨텍스트를 기준으로 설계하면 중복될 확률이 매우 줄어들게 됩니다.</p>
</li>
</ul>
<h2 id="3-3-단일-책임의-원칙single-responsibility-principle">3-3. 단일 책임의 원칙(Single Responsibility Principle)</h2>
<ul>
<li>단일 책임 원칙은 모든 클래스는 하나의 책임을 가지며, 그 클래스의 기능은 이 책임을 기반으로 개발되어야 함을 의미한다.</li>
<li>이를 마이크로서비스에 적용하면 하나의 책임을 갖는 마이크로서비스도 바운디드 컨텍스트처럼 독립 영역을 갖게되고 이처럼 설계하면 다른 마이크로서비스들과 간섭이 줄어들고 변경이 줄어듭니다.</li>
</ul>
<h2 id="3-4-가벼운-통신-프로토콜">3-4. 가벼운 통신 프로토콜</h2>
<ul>
<li>모놀로식 아키텍처는 성능 면에서는 뛰어나다.</li>
<li>마이크로서비스 아키텍처는 시스템을 아무리 이상적으로 설계하더라도 마이크로서비스들이 서로 데이터를 참조하거나, 수정, 변경하는 상황이 발생합니다.</li>
<li>주로 HTTP를 사용합니다.</li>
</ul>
<h2 id="3-5-외부-공개-인터페이스">3-5. 외부 공개 인터페이스</h2>
<ul>
<li>마이크로서비스들은 네트워크를 이용하여 서로 통신한다. 메세지 포맷과 같은 경우는 변경이 용이하지 않으므로 신중해야 한다.</li>
</ul>
<h2 id="3-6-마이크로서비스마다-독립된-데이터-저장소">3-6. 마이크로서비스마다 독립된 데이터 저장소</h2>
<ul>
<li>마이크로서비스마다 독립된 데이터 저장소를 갖는 이유는 Join이나 FK를 사용하는 순간 두 테이블간의 독립성이 무효화되기 때문이다.</li>
<li>독립된 데이터 저장소를 갖을 경우 <strong>데이터 정합성</strong>을 유지할 수 있다.</li>
</ul>
<h2 id="3-7-단일-장애-지점">3-7. 단일 장애 지점</h2>
<ul>
<li>시스템을 구성하는 컴포넌트 중 하나에 장애가 발생하면 전체 서비스에 문제가 생기는 것을 단일 장애 지점이라고 부릅니다.</li>
<li>아키텍처를 설계할 때 가장 기본은 단일 장애 지점을 줄이고, 하나의 컴포넌트에 의존하지 않는 것입니다.</li>
</ul>
<h2 id="3-8-고가용성-확보">3-8. 고가용성 확보</h2>
<ul>
<li>고가용성을 확보하기 위해서는 주로 <strong>로드밸런서</strong>를 활용합니다.</li>
<li>로드 밸런서는 가상 서버 역할을 하고, 사용자 요청을 모두 받습니다.</li>
<li>로드 밸런서에 연결된 애플리케이션 서버에 알고리즘에 따라 사용자 요청을 분산합니다.</li>
<li>로드 밸런서에 연결된 애플리케이션 서버를 늘리는 것을 스케일아웃(scale-out)이라고 합니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/in_ho_/post/c2261bf2-d4b8-41df-9419-fec903cfd2fe/image.png" alt=""></p>
<h2 id="3-9-모니터링-시스템">3-9. 모니터링 시스템</h2>
<ul>
<li>객관적인 자료로 시스템을 확장할 근거를 확인할 수 있고, 시스템에 문제가 생기면 알람도 받을 수 있기 때문이다.<blockquote>
<p>자빅스, 문인, 나기오스, APM</p>
</blockquote>
</li>
</ul>
<h2 id="3-10-마이크로서비스의-예시">3-10. 마이크로서비스의 예시</h2>
<p><img src="https://velog.velcdn.com/images/in_ho_/post/ee14929c-0c9d-4fe6-a6e4-474dd1b85208/image.png" alt=""></p>
<h2 id="3-11-요소-애플리케이션">3-11. 요소 애플리케이션</h2>
<ul>
<li>마이크로서비스 아키텍처가 대중화되면서 기존의 인프라에서 클라우드로 인프라로 이동하고 있다.</li>
<li>요소 애플리케이션은 컴퓨팅 환경에 적합한 애플리케이션 개발하는 방법론이다.</li>
<li>클라우드 환경에 적합한 애플리케이션을 개발할 때 고려해야할 12가지 항목을 선정한 것이다.</li>
</ul>
<h3 id="3-11-1-버전-관리되는-하나의-코드베이스와-하나의-배포">3-11-1. 버전 관리되는 하나의 코드베이스와 하나의 배포</h3>
<blockquote>
<p><strong>코드베이스</strong></p>
</blockquote>
<ul>
<li><p>프로젝트에 담긴 코드.</p>
</li>
<li><p>properties와 profile을 화룡해 쉽게 여러 환경을 대상으로 배포할 수 있다.</p>
</li>
</ul>
<h3 id="3-11-2-명시적으로-선언할-수도-있고-분리할-수도-있는-의존성">3-11-2. 명시적으로 선언할 수도 있고, 분리할 수도 있는 의존성</h3>
<ul>
<li>스프링 프레임워크는 애플리케이션의 의존성을 관리하기 위해 Maven과 Gradle을 사용합니다.</li>
</ul>
<h3 id="3-11-3-환경-변수를-이용한-설정">3-11-3. 환경 변수를 이용한 설정</h3>
<ul>
<li>하나의 코드 베이스는 여러 환경에서 동작할 수 있습니다. 하지만 외부에서 값을 지정해주어야 할 경우가 있습니다.</li>
</ul>
<ol>
<li>애플리케이션마다 고유 이름이 필요하다.</li>
<li>애플리케이션을 실행할 때 애플리케이션의 프로파일 이름이 필요하다.<pre><code>java -Dspring.profiles.active=dev -jar ./spring-tour-1.0.0.jar</code></pre><blockquote>
<p>자바는 -D[환경변수]=[환경변수 값]으로 지정할 수 있습니다.</p>
</blockquote>
</li>
</ol>
<ul>
<li>환경 변수의 이름은 spring.profiles.active입니다.</li>
</ul>
<h3 id="3-11-4-지원-서비스는-연결된-리소스로-처리">3-11-4. 지원 서비스는 연결된 리소스로 처리</h3>
<blockquote>
<p>지원 서비스</p>
</blockquote>
<ul>
<li>애플리케이션이 네트워크를 이용해서 사용하는 모든 서비스를 의미.</li>
<li>예를들어 JDBC로 연결된 DB 같은 데이터 저장소나 결제 시스템 같은 다른 도메인 영역을 담당하는 마이크로 서비스들도 지원 서비스라고 합니다.</li>
<li>이런 지원 서비스들은 네트워크로 연결되어 언제든지 애플리케이션과 연결하고 분리할 수 있어 리소스라고 합니다.</li>
</ul>
<ul>
<li>리소스 간의 연결을 위해 RabbitMQ, Kafaka, RestTemplate, WebClient를 사용합니다.</li>
</ul>
<h3 id="3-11-5-소스-빌드와-실행은-완전히-분리되어야-한다">3-11-5. 소스 빌드와 실행은 완전히 분리되어야 한다.</h3>
<ul>
<li>코드 베이스는 빌드, 릴리스, 실행 이 3단계로 시스템에 배포된다. 각 단계는 완전히 분리되어야 합니다.</li>
</ul>
<ol>
<li>빌드<ul>
<li>먼저 의존성이 있는 라이브러리들과 코드 베이스를 조합하여 컴파일한다.</li>
</ul>
</li>
<li>릴리스<ul>
<li>스프링 프로파일 혹은 메이븐 프로파일 설정을 이용하여 resources에 위치한 설정 파일들을 조합한다.</li>
<li>컴파일된 코드와 설정 파일을 조합하여 실행 가능한 JAR 파일을 생성한다.</li>
</ul>
</li>
<li>실행<ul>
<li>JAR 파일을 java 명령어를 이용하여 실행한다.</li>
</ul>
</li>
</ol>
<h3 id="3-11-6-애플리케이션은-하나-이상의-무상태-프로세스로-실행되어야-한다">3-11-6. 애플리케이션은 하나 이상의 무상태 프로세스로 실행되어야 한다.</h3>
<ul>
<li>애플리케이션을 구성하는 프로세스들은 하나 혹은 그 이상의 프로세스들로 구성됩니다. 하지만 이 프로세스들은 <strong>무상태</strong>이며 <strong>공유하는 것이 없어야</strong>합니다.</li>
</ul>
<h3 id="3-11-7-포트-바인딩을-통한-서비스-공개">3-11-7. 포트 바인딩을 통한 서비스 공개.</h3>
<ul>
<li>모든 애플리케이션은 특정 포트가 바인딩되도록 설계한다.</li>
</ul>
<h3 id="3-11-8-동시성">3-11-8. 동시성</h3>
<ul>
<li>자바 프로그래밍에서는 동시성을 다루는데 스레드를 사용합니다.</li>
<li>Jetty, Tomcat과 같은 WAS가 내부적으로 스레드를 관리해서 자연스럽게 멀티 스레드 프로그램이 동작합니다.</li>
</ul>
<h3 id="3-11-9-프로세스는-빠르게-시작해야-하고-안정적으로-종료해야-한다">3-11-9. 프로세스는 빠르게 시작해야 하고, 안정적으로 종료해야 한다.</h3>
<h3 id="3-11-10-dev-환경과-production-환경-일치">3-11-10. Dev 환경과 Production 환경 일치</h3>
<ul>
<li>마이크로서비스를 운영하면 개발 완료된 프로그램이나 기능은 수시로 배포된다. 그러므로 우리는 Dev 환경과 실제 Production 환경을 가능한 비슷하게 설정하고 유지해야 한다.</li>
</ul>
<h3 id="3-11-11-로그는-이벤트-스트림으로-다룬다">3-11-11. 로그는 이벤트 스트림으로 다룬다.</h3>
<ul>
<li>ELK Stack 구축<ul>
<li>ELK 스택은 로그의 저장 및 검색을 담당하는 엘라스틱 서치, 로그를 나르고 저장하는 로그 스테시, 뷰를 처리하는 키바나로 구성한다.</li>
</ul>
</li>
</ul>
<h3 id="시스템-유지-보수를-위한-일회성-프로세스">시스템 유지 보수를 위한 일회성 프로세스</h3>
<ul>
<li>서비스를 운영하다보면 데이터를 마이크레이션하는 프로그램이 필요하다. 이러한 유지보수 프로그램을 admin 또는 maintenance 프로세스라고 한다.</li>
<li>여기서는 커맨드라인에서 바로 실행할 수 있는 일회성프로그램이다.<ul>
<li>Spring Boot의 CommandLineRunner, ApplicationRunner를 사용할 수 있습니다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Security] 실습]]></title>
            <link>https://velog.io/@in_ho_/Spring-Security-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@in_ho_/Spring-Security-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Tue, 24 Oct 2023 15:00:44 GMT</pubDate>
            <description><![CDATA[<h1 id="1-의존성-추가">1. 의존성 추가</h1>
<pre><code>// spring-boot-starter-security
implementation &#39;org.springframework.boot:spring-boot-starter-security&#39;     
/* jjwt */
implementation &#39;io.jsonwebtoken:jjwt:0.9.1&#39;</code></pre><ul>
<li>스프림 시큐리티는 <strong>기본적으로 UsernamePasswordAuthenticationFilter를 통해 인증을 수행</strong></li>
<li>참고로 이 필터에서는 인증이 실패하면 로그인 폼이 포함된 화면을 전달하게 됩니다.<blockquote>
<p>REST API에는 이러한 화면이 없으므로 UsernamePasswordAuthenticationFilter <strong>앞에 인증 필터를 배치해서 인증 주체를 변경하는 작업 방식</strong>으로 구현하겠습니다.</p>
</blockquote>
</li>
</ul>
<h1 id="2-userdetails-구현">2. UserDetails 구현</h1>
<pre><code>@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table
@Entity
public class User implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

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

    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Column(nullable = false)
    private String password;

    @Column(nullable = false)
    private String name;

    @ElementCollection(fetch = FetchType.EAGER)
    @Builder.Default
    private List&lt;String&gt; roles = new ArrayList&lt;&gt;();

    @Override
    public Collection&lt;? extends GrantedAuthority&gt; getAuthorities() {
        return this.roles.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }


    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Override
    public String getUsername() {
        return this.uid;
    }

    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }


    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }


    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }


    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Override
    public boolean isEnabled() {
        return true;
    }
}</code></pre><ul>
<li><p><strong>getAuthorities()</strong></p>
<ul>
<li>계정이 가지고 있는 권한 목록을 리턴합니다.</li>
</ul>
</li>
<li><p><strong>getPassword()</strong></p>
<ul>
<li>계정의 비밀번호를 리턴합니다.</li>
</ul>
</li>
<li><p><strong>getUsername()</strong></p>
<ul>
<li>계정의 아이디를 리턴합니다.</li>
</ul>
</li>
<li><p><strong>isAccountNonExpired()</strong></p>
<ul>
<li>계정이 만료됐는지 리턴합니다.(true : 만료안됌)</li>
</ul>
</li>
<li><p><strong>isAccountNonLocked()</strong></p>
<ul>
<li>계정이 잠겨있는지 리턴합니다.(true : 잠겨 있지 않음)</li>
</ul>
</li>
<li><p><strong>isCredentialNonExpired()</strong></p>
<ul>
<li>비밀번호가 만료됐는지 리턴합니다.(true : 만료안됌)</li>
</ul>
</li>
<li><p><strong>isEnabled()</strong></p>
<ul>
<li>계정이 활성화돼 있는지 리턴합니다.(true : 활성화)</li>
</ul>
</li>
<li><p>UserDetails는 스프링 시큐리티에서 제공하는 개념입니다. UserDetails의 username은 각 사용자를 구분할 수 있는 IDfmf dmlalgkqslek.</p>
</li>
</ul>
<h1 id="3-userrepository-구현">3. UserRepository 구현</h1>
<pre><code>@Repository
public interface UserRepository extends JpaRepository&lt;User, Long&gt; {
    User getByUid(String uid);
}</code></pre><ul>
<li>User 엔티티의 PK는 인덱스 값이기 때문에 사용자 아이디를 토큰 생성 정보로 사용하기 위해 getByUid 메서드를 사용합니다.</li>
</ul>
<h1 id="4-userdetailsserviceimple-구현">4. UserDetailsServiceImple 구현</h1>
<pre><code>@RequiredArgsConstructor
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    private final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);

    private final UserRepository userRepository;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.getByUid(username);
    }
}</code></pre><h1 id="5-jwttokenprovider-구현">5. JwtTokenProvider 구현</h1>
<pre><code>@RequiredArgsConstructor
@Component
public class JwtTokenProvider {
    private final Logger log = LoggerFactory.getLogger(JwtTokenProvider.class);
    private final UserDetailsService userDetailsService;

    @Value(&quot;${springboot.jwt.secret}&quot;)
    private String secretKey;
    private final long tokenValidMillisecond = 1000L * 60 * 60;

    @PostConstruct
    protected void init() {
        log.info(&quot;[JwtTokenProvider] secretKey 초기화 시작&quot;);
        secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes(StandardCharsets.UTF_8));
        log.info(&quot;[JwtTokenProvider] secretKey 초기화 완료&quot;);
    }

    public String createToken(String userUid, List&lt;String&gt; roles) {
        log.info(&quot;[JwtTokenProvider/createToken] 토큰 생성 시작&quot;);
        Claims claims = Jwts.claims().setSubject(userUid);
        claims.put(&quot;roles&quot;, roles);

        Date now = new Date();

        String token = Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(now)
                .setExpiration(new Date(now.getTime() + tokenValidMillisecond))
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();

        log.info(&quot;[JwtTokenProvider/createToken] 토큰 생성 완료&quot;);
        return token;
    }

    public Authentication getAuthentication(String token) {
        log.info(&quot;[JwtTokenProvider/getAuthentication] 토큰 조회 시작&quot;);
        UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUsername(token));
        log.info(&quot;[JwtTokenProvider/getAuthentication] 토큰 조회 완료 Username : {}&quot;, userDetails.getUsername());

        return new UsernamePasswordAuthenticationToken(userDetails, &quot;&quot;, userDetails.getAuthorities());
    }

    public String getUsername(String token) {
        log.info(&quot;[JwtTokenProvider/getUsername] 토큰에서 회원 정보 추출 시작&quot;);
        String info = Jwts.parser().setSigningKey(secretKey).parseClaimsJwt(token).getBody().getSubject();

        log.info(&quot;[JwtTokenProvider/getUsername] 토큰에서 회원 정보 추출 완료 info : {}&quot;, info);

        return info;
    }

    public String resolveToken(HttpServletRequest request) {
        log.info(&quot;[JwtTokenProvider/resolverToken] HTTP 헤더에서 Token 추출&quot;);
        return request.getHeader(&quot;X-AUTH-TOKEN&quot;);
    }

    public boolean validateToken(String token) {
        log.info(&quot;[JwtTokenProvider/validateToken] 토큰 유효성 체크 시작&quot;);
        try {
            Jws&lt;Claims&gt; claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);

            return !claims.getBody().getExpiration().before(new Date());
        } catch (Exception e) {
            log.error(&quot;[JwtTokenProvider/validateToken] 토큰 유효성 체크 실패&quot;);
            return false;
        }
    }
}</code></pre><ul>
<li>application.yaml에 아래의 값을 추가합니다.<pre><code>springboot:
jwt:
  secret: ~~~</code></pre><blockquote>
<p>해당 값은 임의로 지정한 값입니다.</p>
</blockquote>
</li>
</ul>
<h1 id="6-jwtauthenticationfilter-구현">6. JwtAuthenticationFilter 구현</h1>
<ul>
<li>JwtAuthenticationFilter는 JWT 토큰으로 인증하고, SecurityContextHolder에 추가하는 필터를 설정하는 클래스입니다.</li>
</ul>
<pre><code>@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final Logger log = LoggerFactory.getLogger(JwtAuthenticationFilter.class);

    private final JwtTokenProvider jwtTokenProvider;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = jwtTokenProvider.resolveToken(request);
        log.info(&quot;[JwtAuthenticationFilter/doFilterInternal] token 추출 완료 : {}&quot;, token);


        log.info(&quot;[JwtAuthenticationFilter/doFilterInternal] token 유효성 체크 시작&quot;);

        if (token != null &amp;&amp; jwtTokenProvider.validateToken(token)) {
            Authentication authentication = jwtTokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);

            log.info(&quot;[JwtAuthenticationFilter/doFilterInternal] token 유효성 체크 완료&quot;);
        }

        filterChain.doFilter(request, response);
    }
}</code></pre><ul>
<li>스프링 부트에서 필터를 여러 방법으로 구현할 수 있지만, 가장 편한 방법은 필터를 상속받아 사용하는 것입니다.</li>
<li>위의 코드는 <strong>doFilter()를 기준으로 서블릿이 실행되기 전의 코드와 서블릿이 실행된 후에 실행되는 코드로 나뉩니다.</strong></li>
</ul>
<h1 id="7-securityconfiguration-구현">7. SecurityConfiguration 구현</h1>
<pre><code>@RequiredArgsConstructor
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final JwtTokenProvider jwtTokenProvider;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.httpBasic().disable()
                .csrf().disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers(&quot;/sign-api/sign-in&quot;, &quot;/sign-api/sign-up&quot;, &quot;/sign-api/exception&quot;).permitAll()
                .antMatchers(HttpMethod.GET, &quot;/product**&quot;).permitAll()
                .antMatchers(&quot;**exception**&quot;).permitAll()
                .anyRequest().hasRole(&quot;ADMIN&quot;)
                .and()
                .exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler())
                .and()
                .exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())
                .and()
                .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    public void configure(WebSecurity webSecurity) {
        webSecurity.ignoring().antMatchers(&quot;/v2/api-docs&quot;, &quot;/swagger-resources/**&quot;, &quot;/swagger-ui.html&quot;, 
                &quot;/webjars/**&quot;, &quot;/swagger/**&quot;, &quot;/sign-api/exception&quot;);
    }
}</code></pre><h2 id="7-1-configurehttpsecurity">7-1. configure(HttpSecurity)</h2>
<ul>
<li><strong>스프링 시큐리티의 대부분 설정을 여기서 함.</strong></li>
</ul>
<ol>
<li>리소스 접근 권한 설정</li>
<li>인증 실패 시 발생하는 예외 처리</li>
<li>인증 로직 커스터마이징</li>
<li>csrf, cors 등의 스프링 시큐리티 설정</li>
</ol>
<ul>
<li><strong>httpBasic().disable()</strong><ul>
<li>UI를 사용하는 것을 기본값으로 가진 시큐리티 설정 비활성화</li>
</ul>
</li>
<li><strong>csrf().disable()</strong><ul>
<li>REST API에서는 csrf 보안이 필요 없기 때문에 비활성화</li>
</ul>
</li>
<li><strong>sessinManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)</strong><ul>
<li>세션을 사용하지 않음.</li>
</ul>
</li>
<li><strong>authorizeRequest()</strong><ul>
<li>애플리케이션에 들어오는 요청에 대한 사용 권한을 체크</li>
<li><strong>antMatchers()</strong>는 antPattern을 통해 권한을 설정할 수 있습니다.<ol>
<li>/sign-api/sign-in, /sign-api/sign-up, /sing-api/exception 경로는 모두에게 허용합니다.</li>
<li>/product로 시작하는 GET 요청은 모두 허용합니다.</li>
<li>exception이라는 단어가 들어간 경로는 모두 허용합니다.</li>
<li>다른 요청은 인증된 권한을 가진 사용자에게 허용합니다.</li>
</ol>
</li>
</ul>
</li>
<li><strong>exceptionHandling().accessDeniedHandler()</strong><ul>
<li>권한을 확인하는 과정에서 통과하지 못할 경우 예외를 전달</li>
</ul>
</li>
<li><strong>exceptionHandling().authenticationEntryPoint()</strong><ul>
<li>인증 과정에서 예외가 발생할 경우 예외를 전달.</li>
</ul>
</li>
<li>addFilterBefore()<ul>
<li>어느 필터 앞에 추가할 지 지정할 수 있음.</li>
<li>지금의 코드는 UsernamePasswordAuthenticationFilter 앞에 필터를 추가하겠다는 의미입니다.</li>
</ul>
</li>
</ul>
<h2 id="7-2-configurewebsecurity">7-2. configure(WebSecurity)</h2>
<ul>
<li>WebSecurity를 핸들링하는 configure는 HttpSecurity를 핸들링 하는 configure 메서드 보다 먼저 동작합니다.</li>
<li>그렇기 때문에 인증, 인가가 필요하지 않는 리소스 접근에 대해서만 주로 허용합니다.</li>
</ul>
<h1 id="8-customaccessdeniedhandler-customauthenticationentrypoint-구현">8. CustomAccessDeniedHandler, CustomAuthenticationEntryPoint 구현</h1>
<h2 id="8-1-customaccessdeniedhandler">8-1. CustomAccessDeniedHandler</h2>
<pre><code>@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    private final Logger log = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        log.info(&quot;[CustomAccessDeniedHandler/handle] 접근이 막혔을 경우 경로 리다이렉트&quot;);
        response.sendRedirect(&quot;/sign-api/exception&quot;);
    }
}</code></pre><ul>
<li>AccessDeniedException은 액세스 권한이 없는 리소스에 접근할 때 발생하는 예외입니다.</li>
</ul>
<h2 id="8-2-customauthenticationentrypoint">8-2. CustomAuthenticationEntryPoint</h2>
<pre><code>@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    private Logger log = LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class);

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        ObjectMapper objectMapper = new ObjectMapper();
        log.info(&quot;[CustomAuthenticationEntryPoint/commence] 인증 실패로 error 발생&quot;);

        EntryPointErrorResponse entryPointErrorResponse = new EntryPointErrorResponse();

        entryPointErrorResponse.setMsg(&quot;인증이 실패하였습니다.&quot;);
        response.setStatus(401);
        response.setContentType(&quot;application/json&quot;);
        response.setCharacterEncoding(&quot;UTF-8&quot;);
        response.getWriter().write(objectMapper.writeValueAsString(entryPointErrorResponse));
    }
}</code></pre><ul>
<li>EntryPointErrorResponse<pre><code>@Data
public class EntryPointErrorResponse {
  private String msg;
}</code></pre></li>
</ul>
<h1 id="9-회원가입과-로그인-구현">9. 회원가입과 로그인 구현</h1>
<ul>
<li><p>SignService를 구현한 후 signUp, signIn 메서드를 생성하겠습니다.</p>
<pre><code>public interface SignService {
  SignUpResultDTO signUp(String id, String password, String name, String role);

  SignInResultDTO signIn(String id, String password) throws RuntimeException;
}</code></pre></li>
</ul>
<h2 id="9-1-signupresultdto">9-1. SignUpResultDTO</h2>
<pre><code>@AllArgsConstructor
@Data
public class SignUpResultDTO {
    private boolean isSuccess;
    private int code;
    private String msg;
}</code></pre><h2 id="9-2-signinresultdto">9-2. SignInResultDTO</h2>
<pre><code>@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SignInResultDTO {
    private String token;
    private boolean isSuccess;
    private String msg;
    private int code;
}</code></pre><h2 id="9-3-회원가입-로직-구현">9-3. 회원가입 로직 구현</h2>
<pre><code>@Service
@RequiredArgsConstructor
public class SignServiceImpl implements SignService {

    private final UserRepository userRepository;

    private final JwtTokenProvider jwtTokenProvider;

    private final PasswordEncoder passwordEncoder;

    private final Logger log = LoggerFactory.getLogger(SignServiceImpl.class);

    @Override
    public SignUpResultDTO signUp(String id, String password, String name, String role) {
        log.info(&quot;[SignServiceImpl/signUp] 회원 가입 로직 시작&quot;);
        User user = null;

        if (userRepository.getByUid(id) != null) {
            throw new DuplicateUserIdException(&quot;동일한 아이디의 사용자가 있습니다.&quot;);
        }


        /* Step 1. 권한별 엔티티 객체 생성 */
        if (&quot;admin&quot;.equalsIgnoreCase(role)) {
            user = User.builder()
                    .uid(id)
                    .name(name)
                    .password(passwordEncoder.encode(password))
                    .roles(Collections.singletonList(&quot;ROLE_ADMIN&quot;))
                    .build();
        } else {
            user = User.builder()
                    .uid(id)
                    .name(name)
                    .password(passwordEncoder.encode(password))
                    .roles(Collections.singletonList(&quot;ROLE_USER&quot;))
                    .build();
        }

        /* Step 2. DB에 저장 */
        User savedUser = userRepository.save(user);
        SignUpResultDTO signUpResultDTO = null;

        /* Step 3. 저장이 맞게 되었는지 검증 */
        if (savedUser.getName().isEmpty()) {
            log.error(&quot;[SignServiceImpl/signUp] 회원가입 실패&quot;);

            signUpResultDTO = SignUpResultDTO.builder()
                    .isSuccess(Status.FAIL.value())
                    .code(HttpStatus.BAD_REQUEST.value())
                    .msg(&quot;회원가입을 성공했습니다.&quot;)
                    .build();
        } else {
            log.info(&quot;[SignServiceImpl/signUp] 회원가입 성공&quot;);

            signUpResultDTO = SignUpResultDTO.builder()
                    .isSuccess(Status.SUCCESS.value())
                    .code(HttpStatus.OK.value())
                    .msg(&quot;회원가입을 성공했습니다.&quot;)
                    .build();

        }

        return signUpResultDTO;
    }

    ...
}</code></pre><blockquote>
<p>임의로 Status라는 enum을 만들어 처리했다.</p>
</blockquote>
<h2 id="9-4-passwordencoder-빈-정의">9-4. PasswordEncoder 빈 정의</h2>
<ul>
<li><strong>패스워드는 암호화해서 저장해야 하기 때문에 PasswordEncoder를 활용해 저장합니다.</strong><pre><code>@Configuration
public class PasswordEncoderConfiguration {
  @Bean
  public PasswordEncoder passwordEncoder() {
      return PasswordEncoderFactories.createDelegatingPasswordEncoder();
  }
}</code></pre></li>
</ul>
<h2 id="9-5-로그인-로직-구현">9-5. 로그인 로직 구현</h2>
<ul>
<li><p>로그인 로직은 아래와 같이 동작합니다.</p>
<ol>
<li>Client가 입력한 id를 통해 DB에서 User를 조회합니다.<ul>
<li>없을 경우 로그인 실패</li>
<li>있을 경우 다음 스텝</li>
</ul>
</li>
<li>조회된 User 엔티티의 password와 입력된 password를 비교합니다. 단, 이때 비밀번호가 암호화 되어 있기 때문에 PasswordEncoder를 이용해 일치 여부를 확인합니다.<ul>
<li>패스워드 불일치 시 로그인 실패</li>
<li>패스워드 일치 시 다음 스텝</li>
</ul>
</li>
<li>JwtTokenProvider를 통해 id와 role로 토큰을 생성한 후 전달합니다.</li>
</ol>
<pre><code>    @Override
  public SignInResultDTO signIn(String id, String password) throws RuntimeException {
      log.info(&quot;[SignServiceImpl/signIn] 로그인 시도&quot;);

      String loginFailMsg = &quot;입력한 정보가 일치하지 않습니다.&quot;;
      User user = userRepository.getByUid(id);

      /* id와 맞는 User가 있는지 조회 */
      if (user == null) {
          throw new LoginFailedException(loginFailMsg);
      }

      /* Step 2. 비밀번호 일치여부 */
      if (!passwordEncoder.matches(password, user.getPassword())) {
          throw new LoginFailedException(loginFailMsg);
      }

      /* Step 3. 토큰 생성 및 전달 */
      String token = jwtTokenProvider.createToken(user.getUid(), user.getRoles());

      return SignInResultDTO.builder()
              .token(token)
              .build();
  }</code></pre><ul>
<li>RuntimeException을 발생시켜 처리할 수 있지만 CustomException을 사용하여 에러를 핸들링하는 방식을 선호해서 커스텀 익셉션을 사용하였습니다.</li>
</ul>
</li>
</ul>
<h1 id="10-회원가입-로그인-controller">10. 회원가입, 로그인 Controller</h1>
<pre><code> @RestController
@RequestMapping(&quot;/sign-api&quot;)
@RequiredArgsConstructor
public class SignController {
    private final Logger log = LoggerFactory.getLogger(SignController.class);

    private final SignService signService;

    /**
     * 로그인
     * @return SignInResultDTO
     */
    @PostMapping(&quot;/sign-in&quot;)
    public SignInResultDTO signIn(@Valid SignInRequestDTO signInRequestDTO) {
        return signService.signIn(signInRequestDTO.getId(), signInRequestDTO.getPassword());
    }

    /**
     * 회원가입
     * @return SignUpResultDTO
     */
    @PostMapping(&quot;/sign-up&quot;)
    public SignUpResultDTO signUp(@Valid SignUpRequestDTO signUpRequestDTO) {
        return signService.signUp(signUpRequestDTO.getId(), signUpRequestDTO.getPassword(), signUpRequestDTO.getName(), signUpRequestDTO.getRole());
    }
}</code></pre><ul>
<li>우리는 이전에 SignService에서 LoginFailedException, DuplicateUserIdException을 정의했습니다. 또한 여기서 @Valid를 사용하여 spring validation 라이브러리를 통해 <strong>유효성 검사</strong>를 하고 있습니다.</li>
<li>Global하게 유효성 검사를 하기 위해 CustomExceptionHandler를 정의합니다.</li>
</ul>
<h2 id="10-1-customexceptionhandler-구현">10-1. CustomExceptionHandler 구현</h2>
<pre><code>@RestControllerAdvice
public class CustomExceptionHandler {

    private final Logger log = LoggerFactory.getLogger(CustomExceptionHandler.class);

    @ExceptionHandler({BindException.class})
    public ResponseEntity&lt;ErrorResponseDTO&gt; handleBindException(BindException e) {
        String errorMessage = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();

        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(ErrorResponseDTO.builder()
                        .msg(errorMessage)
                        .build());
    }

    @ExceptionHandler({DuplicateUserIdException.class, LoginFailedException.class})
    public ResponseEntity&lt;ErrorResponseDTO&gt; handleCustomException(RuntimeException e) {
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(ErrorResponseDTO.builder()
                        .msg(e.getMessage())
                        .build());
    }
}</code></pre><h1 id="11-정리">11. 정리</h1>
<ul>
<li>Spring Security의 간단한 사용법을 알아봤다. <strong>WebSecurityConfigurerAdapter가 Deprecated</strong>가 되었지만 이 방법을 통해 간단한 원리를 알아가면 좋을 것 같다.</li>
<li>다음에는 WebSecurityConfigurerAdapter를 어떻게 변경해야 하는지에 대해서 알아보겠습니다. :)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] 객체 지향]]></title>
            <link>https://velog.io/@in_ho_/Java-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5</link>
            <guid>https://velog.io/@in_ho_/Java-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5</guid>
            <pubDate>Tue, 24 Oct 2023 06:17:14 GMT</pubDate>
            <description><![CDATA[<h1 id="1-객체-지향이란">1. 객체 지향이란?</h1>
<ul>
<li>실제 사물을 프로그램으로 표현하기 위한 사고방식</li>
</ul>
<h2 id="1-1-객체">1-1. 객체</h2>
<ul>
<li>사물이 가진 <strong>속성</strong>과 <strong>행위</strong>를 설정해 객체(object)를 만들 수 있습니다.<ul>
<li>속성<ul>
<li>사물이 가진 정보(= 멤버, 필드)</li>
</ul>
</li>
<li>행위<ul>
<li>사물이 일으키는 행동(= 메서드)</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="1-2-캡슐화-encapsulation">1-2. 캡슐화, encapsulation</h2>
<ul>
<li><strong>외부에서 객체의 속성이나 행위를 호출하는 것을 제한</strong><ul>
<li>속성 값을 정해진 방법으로만 수정이 가능하게 하거나, 행위를 호출할 수 있는 곳을 제한<ul>
<li><strong>접근 제한자</strong>를 통해 수행</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="1-3-상속-inheritance">1-3. 상속, inheritance</h2>
<ul>
<li><strong>이미 존재하는 클래스의 속성이나 행위를 물려받아 새롭게 정의</strong></li>
<li>클래스 선언부에 <strong>final</strong>을 사용하면 그 클래스는 상속을 받을 수 없게됩니다.<pre><code>class final Employee {...}</code></pre></li>
<li>부모 클래스의 필드와 메서드 호출<pre><code>public void sayHello() {
 super.name;
  super.work();
}</code></pre></li>
<li>생성자<ul>
<li>자식 클래스의 생성자가 호출될 때는 부모 생성자가 먼저 호출된 후 자식 생성자가 호출됩니다.(컴파일 시 추가됨)<ul>
<li>그러므로 super()를 명시할 때는 반드시 자식 생성자의 첫 번째에 작성해야 함<pre><code>public class Child extends Parent {
 Child() {
     super();
     ...
 }
}</code></pre></li>
</ul>
</li>
<li>이 순서는 Java 명세에 포함되어 있기 때문에 수정할 수 없습니다.</li>
<li>생성자는 상속되지 않습니다.</li>
</ul>
</li>
</ul>
<h3 id="1-3-1-상속의-장점">1-3-1. 상속의 장점</h3>
<ol>
<li>공통 부분을 다시 정의할 필요가 없다.</li>
<li>공통 부분이 변경되더라도 한 번에 수정할 수 있다.</li>
</ol>
<h2 id="1-4-다형성-polymorphism">1-4. 다형성, polymorphism</h2>
<ul>
<li><strong>여러 객체의 공통된 속성과 행위를 추출해 하나의 객체로 만드는 것</strong></li>
<li>다형성은 상속과 하나의 묶음으로 사용이 되는데, <strong>다형성을 사용함으로써 공통된 속성과 행위를 가진 객체를 하나로 모아 사용할 수 있습니다.</strong><ul>
<li><strong>is-a</strong> 관계<ul>
<li>도형-원, 동물-고양이</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] 부호 없는 자료형]]></title>
            <link>https://velog.io/@in_ho_/Java-%EB%B6%80%ED%98%B8-%EC%97%86%EB%8A%94-%EC%9E%90%EB%A3%8C%ED%98%95</link>
            <guid>https://velog.io/@in_ho_/Java-%EB%B6%80%ED%98%B8-%EC%97%86%EB%8A%94-%EC%9E%90%EB%A3%8C%ED%98%95</guid>
            <pubDate>Tue, 24 Oct 2023 03:11:02 GMT</pubDate>
            <description><![CDATA[<ul>
<li>Java에서의 자료형은 크게 둘로 나눌 수 있습니다.<ol>
<li>기본 자료형<ul>
<li>값을 직접 가짐. </li>
</ul>
</li>
<li>참조 자료형 <ul>
<li>값을 가진 인스턴스의 <strong>참조 주소</strong>를 가진다.</li>
</ul>
</li>
</ol>
</li>
</ul>
<h1 id="1-기본-자료형">1. 기본 자료형</h1>
<table>
<thead>
<tr>
<th align="left">자료형</th>
<th align="left">크기(bit)</th>
<th align="left">기본값</th>
</tr>
</thead>
<tbody><tr>
<td align="left">byte</td>
<td align="left">8</td>
<td align="left">0</td>
</tr>
<tr>
<td align="left">short</td>
<td align="left">16</td>
<td align="left">0</td>
</tr>
<tr>
<td align="left">int</td>
<td align="left">32</td>
<td align="left">0</td>
</tr>
<tr>
<td align="left">long</td>
<td align="left">64</td>
<td align="left">0</td>
</tr>
<tr>
<td align="left">float</td>
<td align="left">32</td>
<td align="left">0.0</td>
</tr>
<tr>
<td align="left">double</td>
<td align="left">64</td>
<td align="left">0.0</td>
</tr>
<tr>
<td align="left">char</td>
<td align="left">16</td>
<td align="left">\u0000(공백문자)</td>
</tr>
<tr>
<td align="left">boolean</td>
<td align="left">1</td>
<td align="left">false</td>
</tr>
<tr>
<td align="left">- 기본 자료형에서 주의할 점은 <strong>자료형의 범위를 넘더라도 오류가 발생하지 않는다.</strong>는 것입니다.</td>
<td align="left"></td>
<td align="left"></td>
</tr>
<tr>
<td align="left">- <strong>양수라면 음수로, 음수라면 양수로 자동 변환되기 때문입니다.</strong></td>
<td align="left"></td>
<td align="left"></td>
</tr>
<tr>
<td align="left">- 이를 <strong>오버플로우</strong>라고 합니다.</td>
<td align="left"></td>
<td align="left"></td>
</tr>
</tbody></table>
<h2 id="1-2-부호-없는-integer-long">1-2. 부호 없는 Integer, Long</h2>
<ul>
<li><p>우리가 Integer나 Long을 사용할 때 음수의 값을 사용하지 않는데 자료형의 범위로 지정되어 있다면 이를 낭비라고 생각할 수도 있을 것입니다.</p>
</li>
<li><p>Java 8 이후로는 int와 long에서 <strong>부호 없는 값</strong>을 가질 수 있게 되었습니다.</p>
<ul>
<li><p>부호가 없다는 말은 <strong>음수값을 가지지 않는다.</strong>는 뜻입니다.</p>
<pre><code>    int i = Integer.MAX_VALUE;
    System.out.println(&quot;Integer의 최대값 : &quot; + i);

    String maxIntegerValue = Integer.toUnsignedString(i + 1);
    System.out.println(&quot;toUnsignedString을 이용하여 Integer 최대값 + 1 확인 : &quot; + maxIntegerValue);

    i = Integer.MAX_VALUE + Integer.MAX_VALUE + 1; /* 1을 더 더하면 0으로 바뀜  */
    maxIntegerValue = Integer.toUnsignedString(i); 
    System.out.println(&quot;toUnsingerString을 이용하여 overflow 확인 : &quot; + maxIntegerValue);

</code></pre></li>
</ul>
</li>
</ul>
<pre><code>    long l = Long.MAX_VALUE;
    System.out.println(&quot;Long의 최대값 : &quot; + l);

    String maxLongValue = Long.toUnsignedString(l, i);
    System.out.println(&quot;toUnsignedString을 이용하여 Long 최대값 + 1 확인 : &quot; + maxLongValue);

    l = Long.MAX_VALUE + Long.MAX_VALUE + 1; /* 1을 더 더하면 0으로 바뀜  */
    maxLongValue = Long.toUnsignedString(l, i);
    System.out.println(&quot;toUnSignedString을 이용하여 overflow 확인 : &quot; + maxLongValue);</code></pre><p>```</p>
<ul>
<li>Integer와 Long 클래스의 <strong>toUnsignedString</strong>을 호출하면 int와 long 값의 부호 없는 문자열 표현을 얻을 수 있습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] BigDecimal]]></title>
            <link>https://velog.io/@in_ho_/Java-BigDecimal</link>
            <guid>https://velog.io/@in_ho_/Java-BigDecimal</guid>
            <pubDate>Tue, 24 Oct 2023 01:01:46 GMT</pubDate>
            <description><![CDATA[<h1 id="1-부동-소수점">1. 부동 소수점</h1>
<ul>
<li>컴퓨터는 2진수를 사용하기 때문에 소수를 표현할 때 <strong>근사치</strong>로 보여줍니다.</li>
<li>이러한 근사치는 소수를 이용한 계산에서 <strong>오차</strong>를 발생시킬 수 있습니다.</li>
<li>Java에서 <strong>float</strong>, <strong>double</strong>을 사용할 때 의도치 않은 오차가 발생할 수 있습니다.
<img src="https://velog.velcdn.com/images/in_ho_/post/ad0c85ff-ac37-4888-8fbc-6772ad6c01c0/image.png" alt=""><blockquote>
<p>결과로 0.799999999... 이러한 값을 받게된다.</p>
</blockquote>
</li>
<li>정확한 수치를 계산하기 위한 애플리케이션의 경우 <strong>BigDecimal</strong>을 사용한다.</li>
</ul>
<h1 id="2-bigdecimal">2. BigDecimal</h1>
<pre><code>        /*
         * BigDecimal 사용
         */
        BigDecimal value1 = new BigDecimal(&quot;0.7&quot;);
        BigDecimal value2 = new BigDecimal(&quot;0.1&quot;);

        // 더하기
        BigDecimal addResult = value1.add(value2);

        System.out.println(&quot;더하기 : &quot; + addResult);

        // 빼기
        BigDecimal subtractResult = value1.subtract(value2);

        System.out.println(&quot;빼기 : &quot; + subtractResult);

        // 곱하기
        BigDecimal multiplyResult = value1.multiply(value2);

        System.out.println(&quot;곱하기 : &quot; + multiplyResult);

        // 나누기
        BigDecimal divideResult = value1.divide(value2);

        System.out.println(&quot;나누기 : &quot; + divideResult);

        // 나머지
        BigDecimal remainderResult = value1.remainder(value2);

        System.out.println(&quot;나머지 : &quot; + remainderResult);</code></pre><ul>
<li>하지만 여기서 주의할 것이 있습니다. BigDecimal을 이용하여 <strong>나눗셈</strong>을 할 때 <strong>나누어떨어지지 않는 수치로 계산</strong>할 경우 <strong>ArithmeticException</strong>이 발생합니다.</li>
<li>그러므로 나눗셈을 할 때는 <strong>끝수 처리</strong>를 해야 합니다.<blockquote>
<p>java.math.RoundingMode에 정의되어 있습니다.</p>
</blockquote>
</li>
</ul>
<table>
<thead>
<tr>
<th align="left">상수명</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left">HALF_UP</td>
<td align="left">반올림</td>
</tr>
<tr>
<td align="left">UP</td>
<td align="left"><strong>0에서 멀어지도록 올림</strong> -.5.5의 경우 -6으로</td>
</tr>
<tr>
<td align="left">DOWN</td>
<td align="left"><strong>0에서 가까워지도록 내림</strong>, -5.5의 경우 -5로</td>
</tr>
<tr>
<td align="left">CEILING</td>
<td align="left">양의 무한대에 근접하도록, 양수인 경우 UP과 같고,<br> -5.5인 경우 소수점 이하 첫째자리에서 동작할 경우 -5가 됨</td>
</tr>
<tr>
<td align="left">FLOOR</td>
<td align="left">음의 무한대에 근접하도록, 양수인 경우 DOWN과 같고,<br> -.5.5인 경우 소수점 이하 첫째자리에서 동작할 경우 -6이 된다.</td>
</tr>
<tr>
<td align="left">```</td>
<td align="left"></td>
</tr>
<tr>
<td align="left">// 소수점 버리는 나머지</td>
<td align="left"></td>
</tr>
<tr>
<td align="left">BigDecimal value3 = new BigDecimal(&quot;7.0&quot;);</td>
<td align="left"></td>
</tr>
<tr>
<td align="left">BigDecimal value4 = new BigDecimal(&quot;3.0&quot;);</td>
<td align="left"></td>
</tr>
</tbody></table>
<pre><code>    divideResult = value3.divide(value4, RoundingMode.DOWN);

    System.out.println(&quot;소수점 버리는 나머지 : &quot; + divideResult);

    divideResult = value3.divide(value4, 2, RoundingMode.HALF_UP);</code></pre><p>```</p>
<ul>
<li><strong>BigDecimal의 divide 함수가 파라미터를 3개를 받게 될 경우 두 번째 파라미터는 몇 번째 자리에서 올림, 반올림, 내림을 동작할지 지정하는 int 값입니다.</strong></li>
</ul>
<h1 id="3-strictmath">3. StrictMath</h1>
<ul>
<li>Math 클래스는 <strong>실행 환경의 연산 처리를 이용해 연산 결과를 반환합니다.</strong></li>
<li>StrictMath는 <strong>모든 플랫폼의 자바 환경에서 동일한 결과를 반환합니다.</strong></li>
<li>그렇기 때문에 환경에 따라 두 클래스 간에 결과 값이 상이할 수 있습니다.</li>
<li>실행 속도는 Math 클래스가 실행 환경의 연산 처리를 사용하므로 조금 더 빠릅니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 중앙값]]></title>
            <link>https://velog.io/@in_ho_/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A4%91%EC%95%99%EA%B0%92</link>
            <guid>https://velog.io/@in_ho_/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A4%91%EC%95%99%EA%B0%92</guid>
            <pubDate>Sun, 22 Oct 2023 14:16:58 GMT</pubDate>
            <description><![CDATA[<h1 id="1-중앙값">1. 중앙값</h1>
<ul>
<li>최대값, 최소값과 달리 중앙값을 구하는 절차는 매우 복잡합니다.</li>
<li>중앙값을 구할 수 있는 수많은 알고리즘을 생각할 수 있습니다.</li>
</ul>
<pre><code>/*
* 매개변수 3개의 중앙값
* */
static int med3(int a, int b, int c) {
    if (a &gt;= b) {
        if (b &gt;= c) {
            return b;
        } else if (a &lt;= c) {
            return a;
        } else {
            return c;
        }
    } else if (b &gt; c) {
        return c;
    } else {
        return b;
    }
}</code></pre><ul>
<li>하지만 위의 방식이 아닌 아래의 방식으로 작성할 수도 있습니다.</li>
</ul>
<pre><code>
/* 
* 매개변수 3개의 중앙값
* */
static int med3_2(int a, int b, int c) {
    if ((b &gt;= a &amp;&amp; c &lt;= a) || (b &lt;= a &amp;&amp; c &gt;= a)) {
        return a;
    } else if ((a &gt; b &amp;&amp; c &lt; b) || (a &lt; b &amp;&amp; c &gt; b)) {
        return b;
    }

    return c;
}</code></pre><ul>
<li>하지만 이 방법은 앞선 중앙값 구하는 코드보다 비효율적입니다. 왜냐하면 b&gt;= a와  b &lt;= a를 반대로 뒤집은 a &gt; b, a &lt; b 코드가 있어 조건식을 한 번 더 검사하므로 비효율적이게 됩니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 최대값, 최소값 구하기]]></title>
            <link>https://velog.io/@in_ho_/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%B5%9C%EB%8C%80%EA%B0%92-%EC%B5%9C%EC%86%8C%EA%B0%92-%EA%B5%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@in_ho_/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%B5%9C%EB%8C%80%EA%B0%92-%EC%B5%9C%EC%86%8C%EA%B0%92-%EA%B5%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 22 Oct 2023 13:52:38 GMT</pubDate>
            <description><![CDATA[<pre><code>/*
* 3개의 정수 중 최대값을 구하는 함수
* */
static int max3m(int a, int b, int c) {
    int max = a;

    if(b &gt; max) max = b;
    if(c &gt; max) max = c;

    return max;
}

/* 
* 매개변수가 4개에서 최대값 구하기
* */
static int max4(int a, int b, int c, int d) {
    int max = a;

    if(b &gt; max) max = b;
    if(c &gt; max) max = c;
    if(d &gt; max) max = d;

    return max;
}

/* 
* 매개변수 3개 중 최소값 구하기
* */
static int min3(int a, int b, int c) {
    int min = a;

    if(b &lt; min) min = b;
    if(c &lt; min) min = c;

    return min;
}

/*
* 매개변수 4개 중 최소값 구하기
* */
static int min4(int a, int b, int c, int d) {
    int min = a;

    if(b &lt; min) min = b;
    if(c &lt; min) min = c;
    if(d &lt; min) min = d;

    return min;
}
</code></pre><ul>
<li>위의 방법은 <strong>결정 트리(decision tree)</strong>를 이용한 방식입니다. 
<img src="https://velog.velcdn.com/images/in_ho_/post/b40ee373-490a-4e96-80a2-2be63e9b1594/image.png" alt=""><blockquote>
<p>다 그리지 않았지만 <strong>결정트리는 위와 같은 방식으로 위의 가지가 성립하지 않으면 아랫가지로 이동합니다.</strong></p>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Security] 기본 개념]]></title>
            <link>https://velog.io/@in_ho_/Spring-Security-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@in_ho_/Spring-Security-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Sun, 22 Oct 2023 07:52:10 GMT</pubDate>
            <description><![CDATA[<h1 id="1-필요성">1. 필요성</h1>
<ul>
<li>애플리케이션을 개발할 때 <strong>인증</strong>과 <strong>인가</strong> 등의 보안 기능을 추가해야 할 때가 있습니다.</li>
</ul>
<hr>
<h1 id="2-인증-authentication">2. 인증, authentication</h1>
<ul>
<li><strong>사용자가 누구인지 확인하는 단계</strong></li>
<li>인증의 대표적인 예로 로그인이 있습니다.<blockquote>
<p>로그인</p>
<ul>
<li>로그인은 데이터베이스에 등록된 아이디와 패스워드를 사용자가 입력한 아이디와 비밀번호와 비교해서 일치 여부를 확인하는 과정입니다.</li>
</ul>
</blockquote>
</li>
<li>로그인을 성공하면 <strong>애플리케이션 서버는 응답으로 사용자에게 토큰을 전달</strong>합니다.</li>
<li>로그인에 실패한 사용자는 토크늘 전달받지 못해 원하는 리소스에 접근할 수 없게 됩니다.</li>
</ul>
<hr>
<h1 id="3-인가-authorization">3. 인가, authorization</h1>
<ul>
<li>인증을 통해 검증된 사용자가 애플리케이션 내부의 리소스에 접근할 때 <strong>접근할 권리</strong>가 있는지를 확인하는 과정</li>
<li>일반적으로 사용자가 인증하는 단계에서 발급받은 토큰은 <strong>인가 내용을 포함</strong>하고 있습니다.</li>
</ul>
<hr>
<h1 id="4-접근-주체-principal">4. 접근 주체, principal</h1>
<ul>
<li>애플리케이션의 기능을 사용하는 주체</li>
<li>접근 주체는 사용자가 될 수도 있고, 디바이스, 시스템 등이 될 수도 있습니다.</li>
<li><strong>애플리케이션은 인증을 통해 접근 주체를 신뢰할 수 있는지 확인하고, 인가를 통해 접근 주체에게 부여진 권한을 확인합니다.</strong></li>
</ul>
<h1 id="5-spring-security">5. Spring Security</h1>
<ul>
<li>애플리케이션의 <strong>인증, 인가 등의 보안 기능</strong>을 제공하는 스프링 하위 프로젝트입니다.</li>
</ul>
<h2 id="5-1-spring-security의-동작-구조">5-1. Spring Security의 동작 구조</h2>
<ul>
<li>스프링 시큐리티는 <strong>서블릿 필터(Servlet Filter)</strong>를 기반으로 동작하며 DispatcherServlet 앞에 필터가 배치되어 있습니다.
<img src="https://velog.velcdn.com/images/in_ho_/post/2c3ce97e-4fb3-4798-b060-5e498916a182/image.png" alt=""></li>
<li><strong>Filter Chain</strong>은 서블릿 컨테이너에서 관리하는 ApplicationFilterChain을 의미합니다.</li>
<li><strong>클라이언트에서 요청을 보내면 서블릿 컨테이너는 URI를 확인해서 필터와 서블릿을 매핑합니다.</strong></li>
<li>Spring Security는 필터 체인을 서블릿 컨테이너의 필터 사이에서 동작시키기 위해 <strong>DelegatingFilterProxy</strong>를 사용합니다.
<img src="https://velog.velcdn.com/images/in_ho_/post/aa6127e3-8cd8-4c53-a745-22108f3b1a24/image.png" alt=""></li>
<li><strong>DelegatingFilterProxy는 서블릿 컨테이너의 생명주기와 스프링 애플리케이션 컨텍스트 사이에서 다리 역할을 수행하는 필터 구현체입니다.</strong></li>
<li>DelegatingFilterProxy는 표준 서블릿 필터를 구현하고 있으며, 역할을 위임할 필터체인 프록시(FilterChainProxy)를 내부에 가지고 있습니다.<blockquote>
<p>필터체인 프록시는 스프링 부트의 자동 설정에 의해 생성됩니다.
필터체인 프록시 = <strong>스프링 시큐리티에서 제공하는 필터</strong>
필터체인 프록시에서 사용할 수 있는 보안 필터 체인은 List 형식으로 담을 수 있게 설정돼 있어 선택하여 지정할 수 있습니다. </p>
</blockquote>
</li>
<li>보안 필터체인은 WebSecurityConfigurerAdapter 클래스를 상속받아 설정할 수 있습니다.(여러 보안 필터체인을 만들기 위해서는 상속받는 클래스를 여러 개 생성하면 됩니다.)<blockquote>
<p>여러 개의 상속 클래스를 구현할 경우 @Order 어노테아션을 이용하여 우선 순위를 지정해야 합니다.(하지 않을 경우 예외 발생)
<strong>현재는 해당 클래스가 DEPRECATED됨</strong></p>
</blockquote>
</li>
</ul>
<h2 id="5-2-usernamepasswordauthenticationfilter">5-2. UsernamePasswordAuthenticationFilter</h2>
<ul>
<li><strong>별도의 설정이 없을 경우</strong> 스프링 시큐리티에서는 SecuriryFilterChain에서 필터 중 <strong>UsernamePasswordAuthenticationFilter를 통해 인증을 처리</strong>합니다.</li>
</ul>
<h3 id="5-2-1-usernamepasswordauthenticationfilter를-통한-인증-과정">5-2-1. UsernamePasswordAuthenticationFilter를 통한 인증 과정</h3>
<p><img src="https://velog.velcdn.com/images/in_ho_/post/25f765c2-e2ba-45b8-bf1f-275c8b88a30e/image.png" alt=""></p>
<blockquote>
<p>Notion으로 해당 과정을 그리다 보니 화살표가 복잡하게 보이긴 한다...!</p>
</blockquote>
<ol>
<li>HTTP 요청이 들어오면 서블릿 필터에서 <strong>SecurityFilterChain</strong>으로 작업이 위임되고 그 중 AuthenticationFileter에서 인증을 처리합니다.<blockquote>
<p>여기서는 <strong>AuthenticationFilter는 UsernamepasswordAuthenticationFilter입니다.</strong></p>
</blockquote>
</li>
<li>AuthenticationFilter는 <strong>요청 객체(HttpServletRequest)에서 username과 password를 추출해서 토큰을 생성합니다.</strong></li>
<li>AuthenticationManager에게 <strong>토큰을 전달</strong>합니다.<blockquote>
<p>AuthenticationManager는 인터페이스이며, 일반적으로 사용되는 구현체는 ProviderManager입니다.</p>
</blockquote>
</li>
<li>ProviderManager는 인증을 위해 AuthenticationProvider로 <strong>토큰을 전달</strong>합니다.</li>
<li>AuthenticationProvider는 토큰의 정보를 UserDetailsService에 전달합니다.</li>
<li>UserDetailsService는 <strong>UserDetails 객체를 생성</strong>합니다.(데이터베이스 조회)</li>
<li>생성된 UserDetails 객체는 AuthenticationProvider로 전달되며, 해당 Provider로인증을 수행하고 성공하게 되면 ProviderManager로 <strong>권한을 담은 토큰을 전달</strong>합니다.</li>
<li>ProviderManager는 검증된 토큰을 AuthenticationFilter로 전달합니다.</li>
<li>AuthenticationFilter는 <strong>검증된 토큰을 SecurityContextHolder에 있는 SecurityContext에 저장합니다.</strong></li>
</ol>
<ul>
<li>위에 있는 UsernamePasswordAuthenticationFilter는 접근 권한을 확인하고 인증 실패할 경우 로그인 폼이라는 화면을 보내는 역할을 수행합니다.<blockquote>
<p>Rest API일 경우 이를 사용하지 않고 <strong>JWT 토큰</strong>을 사용합니다. 그러므로 <strong>JWT와 관련된 필터를 생성하고 UsernamePasswordAuthenticationFilter 앞에 먼저 배치해서 먼저 인증을 수행할 수 있게 설정해야합니다.</strong></p>
</blockquote>
</li>
</ul>
<h2 id="5-3-jwt">5-3. JWT</h2>
<ul>
<li><strong>JSON Web Token</strong>는 당사자 간에 정보를 JSON 형태로 안전하게 전송하기 위한 토큰</li>
<li>JWT는 URL로 이용할 수 있는 문자열로만 구성돼 있으며, 디지털 서명이 적용돼 있어 신뢰할 수 있습니다.<blockquote>
<p>URL에서 사용할 수 있는 문자열로 구성돼 있기 때문에 HTTP 구성요소 어디든 위치가 가능하다.</p>
</blockquote>
</li>
<li><strong>JWT는 주로 서버와의 통신에서 권한 인가를 위해 사용됩니다.</strong></li>
</ul>
<h3 id="5-3-1-jwt-구조">5-3-1. JWT 구조</h3>
<pre><code>헤더.내용.서명</code></pre><ul>
<li><strong>헤더</strong><ul>
<li>검증과 관련된 내용<pre><code>{
“alg&quot;: &quot;HS256&quot;,
&quot;typ&quot;: &quot;JWT&quot;
}</code></pre></li>
<li>alg 속성에는 해싱 알고리즘을 지정합니다.<ul>
<li>해싱 알고리즘은 보통 SHA256 또는 RSA를 사용합니다.</li>
</ul>
</li>
<li>typ 속성에는 토큰의 타입으로 지정합니다.</li>
<li>이렇게 완성된 헤더는 Base64Url 형식으로 인코딩 됩니다.</li>
</ul>
</li>
</ul>
<ul>
<li><strong>내용</strong><ul>
<li>이곳에 포함된 속성들을 <strong>클레임(Claim)</strong>이라고 합니다.<ol>
<li>등록된 클레임(Registered Claims)</li>
<li>공개 클레임(Public Claims)</li>
<li>비공개 클레임(Private Claims)</li>
</ol>
<ul>
<li>iss : JWT의 발급자 주체</li>
<li>sub : JWT의 제목</li>
<li>aud : JWT의 수신인</li>
<li>exp : <strong>JWT의 만료시간</strong></li>
<li>nbf : Not Before</li>
<li>iat : JWT가 발급된 시간</li>
<li>jtl : JWT의 식별자 ID</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li><strong>서명</strong><ul>
<li>인코딩된 헤더, 인코딩된 내용, 비밀키, 헤더의 알고리즘 속성값을 가져와 생성합니다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker] Docker 기본 명령어]]></title>
            <link>https://velog.io/@in_ho_/Docker-Docker-%EA%B8%B0%EB%B3%B8-%EB%AA%85%EB%A0%B9%EC%96%B4</link>
            <guid>https://velog.io/@in_ho_/Docker-Docker-%EA%B8%B0%EB%B3%B8-%EB%AA%85%EB%A0%B9%EC%96%B4</guid>
            <pubDate>Sun, 22 Oct 2023 07:48:52 GMT</pubDate>
            <description><![CDATA[<h1 id="1-실무에서의-docker-사용">1. 실무에서의 Docker 사용</h1>
<ol>
<li>개발자 컴퓨터에서 개발한 후 소스를 커밋한다.</li>
<li>빌드와 배포를 요청한다.</li>
<li>CI/CD 서버가 커밋된 소스를 내려 받고 테스트와 빌드 한 후 war 또는 jar 같은 배포 단위의 파일 압축파일을 만든다.</li>
<li>원하는 환경의 서버에 이동시킨 후 실행한다.</li>
</ol>
<hr>
<h1 id="2-docker-image">2. Docker Image</h1>
<ul>
<li>도커 이미지는 도커를 실행하기 위해 필요한 <strong>컨테이너의 기초</strong>입니다.</li>
<li>이미지는 <strong>컨테이너 런타임 내에서 사용하기 위해 정렬된 루트 파일 시스템의 변경 사항 및 해당 실행 매개변수의 모음</strong>입니다.</li>
<li>이미지는 일반적으로 <strong>계층화된 파일 시스템</strong>을 포함합니다.</li>
<li>이미지에는 <strong>상태가 없으며, 변경되지 않습니다.</strong></li>
<li>위에 복잡한 말들이 적혀있지만 간단하게 말하면 <strong>이미지는 컨테이너의 스냅샷(스크린샷)</strong>이라고 볼 수 있습니다.</li>
</ul>
<h2 id="2-1-이미지-목록을-보는-함수">2-1. 이미지 목록을 보는 함수</h2>
<pre><code>docker images</code></pre><ul>
<li>해당 환경에 존재하는 <strong>모든 도커 이미지를 보여주는 명령어</strong></li>
</ul>
<h2 id="2-2-이미지-다운로드">2-2. 이미지 다운로드</h2>
<pre><code>docker pull [이미지명]</code></pre><ul>
<li>위의 명령어를 수행할 경우 Docker Hub라는 곳에서 이미지명과 일치하는 이미지를 다운로드 받습니다.
<img src="https://velog.velcdn.com/images/in_ho_/post/324dc25f-0300-4b24-a0cb-8310a7f6b217/image.png" alt=""></li>
<li>이미지를 다운로드 받으면 확인할 수 있습니다.</li>
</ul>
<h2 id="2-3-이미지-컨테이너화">2-3. 이미지 컨테이너화</h2>
<pre><code>docker run --name [컨테이너명] -p [호스트 포트 : 도커 내부 포트] -d [이미지명]</code></pre><ul>
<li><strong>run</strong>명려어는 이미지가 없을 경우 다운로드를 진행한 다음 명령어가 실행됩니다.</li>
<li>--name이라는 옵션으로 컨테이너의 이름을 지정할 수 있습니다.</li>
<li>-p라는 옵션으로 호스트에서 실행되는 포트와 도커에서 실행되는 포트를 연결시킨 것입니다.</li>
<li>-d 옵션은 detached mode, 즉 <strong>백그라운드</strong>로 실행하겠다는 의미입니다.</li>
</ul>
<h2 id="2-4-컨테이너-상태-확인">2-4. 컨테이너 상태 확인</h2>
<pre><code>docker ps [-a]</code></pre><ul>
<li>도커 컨테이너의 정보를 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/in_ho_/post/e947ff9e-8efb-470b-9f25-6b5b50811b85/image.png" alt=""></li>
</ul>
<h2 id="2-5-이미지를-만드는-방법">2-5. 이미지를 만드는 방법</h2>
<ul>
<li>앞서 말했듯이 <strong>이미지는 컨테이너의 스냅샷(스크린샷)</strong>이다.</li>
<li>이미지는 <strong>변경이 불가능한 파일</strong>인 반면, 컨테이너의 파일은 <strong>수정이 가능하다.</strong></li>
<li>그러므로 컨테이너의 파일을 수정하고 나서 이미지를 만들면 될 것이다.</li>
<li>연습을 위해 nginx 이미지를 다운로드 받은 후 8080 포트로 열어주는 작업을 진행한다.</li>
</ul>
<h3 id="2-5-1-호스트-pc에서-컨테이너-내부-쉘-접근-방법">2-5-1. 호스트 PC에서 컨테이너 내부 쉘 접근 방법</h3>
<ul>
<li>호스트 PC에서 컨테이너로 연결하는 명령어로 <strong>attach</strong>가 있습니다.<pre><code>docker exec -it [컨테이너명] /bin/bash
</code></pre></li>
</ul>
<p>docker exec -it nginx-container /bin/bash</p>
<pre><code>- -i 옵션은 표준입력을 활성화 시키면 attach가 안되어 있더라도 명령어를 입력할 수 있도록 해줌.
- -t 옵션은 리눅스 쉘이 표시됩니다.

### 2-5-2. nginx 폴더에서 index.html 파일 찾기</code></pre><p>find / -name index.html /dev/null</p>
<pre><code>- 위의 명령어의 반환 결과로 /usr/share/nginx/html/index.html이 나올 것입니다.

### 2-5-3. 컨테이너 내의 index.html을 호스트로 복사
- 쉘에서 exit를 입력하여 나온 후 컨테이너 내의 index.html을 로컬로 복사하겠습니다.</code></pre><p>docker cp [컨테이너명:내부 경로] [호스트 경로]
docker cp nginx-container:/usr/share/nginx/html/index.html index.html</p>
<pre><code>- 로컬로 옮긴 파일을 수정한 후 다시 컨테이너이 복사한 후 그 순간을 스냅샷으로 남겨 이미지를 만들 것입니다.
- 파일을 수정하는 방법은 여러가지가 있지만 각자의 방식으로 사용하면 됩니다.(저는 Vim을 사용했습니다.)

### 2-5-4. 수정한 호스트의 index.html 파일을 컨테이너내로 복사</code></pre><p>docker cp [호스트 경로] [컨테이너명:내부 경로]
docker cp index.html nginx-container:/usr/share/nginx/html/index.html</p>
<pre><code>
### 2-5-5. 컨테이너 스냅샷 찍기(이미지 생성)</code></pre><p>docker commit [컨테이너명] [등록할 이미지명]</p>
<p>docker coomit [nginx-container] [modified-nginx]</p>
<pre><code>- 위처럼 스냅샷을 남기게 되면 아래처럼 생성된 것을 확인할 수 있습니다.
![](https://velog.velcdn.com/images/in_ho_/post/6c1a27d7-0e90-46af-9ffe-4379fcf7c405/image.png)

### 2-5-6. 기존 이미지 삭제
- 우리가 새롭게 만든 이미지를 동작하기 위해 기존에 Docker Hub에서 다운로드 받은 이미지를 삭제하겠습니다.
1. 컨테이너 중단(**stop**)</code></pre><p>docker stop [컨테이너명]</p>
<p>docker stop nginx-container</p>
<pre><code>
2. 컨테이너 삭제</code></pre><p>docker rm [컨테이너명]</p>
<p>docker rm nginx-container</p>
<pre><code>
### 2-5-7. index.html 파일을 수정한 이미지 컨테이너화</code></pre><p>docker run --name modified-nginx-container -p 8080:80 -d modified-nginx</p>
<pre><code>![](https://velog.velcdn.com/images/in_ho_/post/e0d71368-44d2-48ba-a35b-cea1bab35567/image.png)
- 아까 복사한 파일이 스냅샷으로 이미지에 남아 새로운 컨테이너에서 확인할 수 있었습니다.

## 2-6. DockerFile
- 실제로 도커 이미지를 만들 때는 **스냅샷 방식보다는 도커파일을 더 많이 이용합니다.**
### 2-6-1. 도커파일이란?
- **이미지를 명령어 묶음으로 만들 수 있는 텍스트 파일**

### 2-6-2. 도커파일로 이미지 만드는법
1.  Dockerfile이라는 명칭의 파일을 만들도록 합니다.
2. Dockerfile 내부에 이처럼 작성합니다.</code></pre><p>FROM nginx
COPY index.html /usr/share/nginx/html</p>
<pre><code>3. 이미지 생성</code></pre><p>docker build -t [이미지명] .</p>
<p>docker build -t dockerfile_nginx .</p>
<pre><code>- 마지막에 **.**을 사용하지 않는다면 에러가 발생할 수 있습니다.
![](https://velog.velcdn.com/images/in_ho_/post/3b6782f9-3346-4dda-a870-84b36ca4f5aa/image.png)
- 정상적으로 이미지가 생성된 것을 확인할 수 있습니다.

4. 새로운 포트로 오픈</code></pre><p>docker run --name dockerfile-nginx-container -p 8000:80 -d dockerfile_nginx</p>
<pre><code>- 위의 코드가 정상적으로 실행되면 8000번 포트에서 실행된 것을 확인할 수 있습니다.
---</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker] 인프라 관리의 트렌드 변화]]></title>
            <link>https://velog.io/@in_ho_/Docker-%EC%9D%B8%ED%94%84%EB%9D%BC-%EA%B4%80%EB%A6%AC%EC%9D%98-%ED%8A%B8%EB%A0%8C%EB%93%9C-%EB%B3%80%ED%99%94</link>
            <guid>https://velog.io/@in_ho_/Docker-%EC%9D%B8%ED%94%84%EB%9D%BC-%EA%B4%80%EB%A6%AC%EC%9D%98-%ED%8A%B8%EB%A0%8C%EB%93%9C-%EB%B3%80%ED%99%94</guid>
            <pubDate>Sun, 22 Oct 2023 06:16:46 GMT</pubDate>
            <description><![CDATA[<h1 id="1-클라우드의-등장">1. 클라우드의 등장</h1>
<ul>
<li>클라우드가 등장하므로 인해 인프라 관리는 큰 변화를 맞이했습니다.</li>
<li>이 중 가장 큰 변화는 <strong>인프라를 소프트웨어처럼 사용할 수 있다.</strong>입니다.</li>
<li>인프라는 과거처럼 데이터 센터에 보관하거나, 서버 장비를 사거나 하지 않고 <strong>한 줄의 코드처럼 추가하고 제거할 수 있는 대상</strong>이 되었습니다.</li>
<li>클라우드의 등장은 아키텍처의 변화를 이끌었습니다.</li>
</ul>
<hr>

<h1 id="2-아키텍처">2. 아키텍처</h1>
<h2 id="2-1-모놀로식-아키텍처">2-1. 모놀로식 아키텍처</h2>
<ul>
<li>예전의 아키텍처는 하나의 서버에 모든 로직이 배치되어 있는 <strong>모놀로식 구조</strong>였습니다.<h3 id="2-1-1-장점">2-1-1. 장점</h3>
</li>
<li>모놀로식 구조의 장점은’ 아키텍처가 단순하다‘입니다.<h3 id="2-2-2-단점">2-2-2. 단점</h3>
</li>
<li>하지만 모든 로직이 하나의 서버로 구성되었기 때문에 장애가 발생했을 때 원인을 찾기가 어려웠습니다.</li>
<li>많은 트래픽으로 서버를 증설해야 할 때 전체를 증가해야 하기 때문에 자원이 심하게 낭비되었습니다.</li>
</ul>
<h2 id="2-2-마이크로서비스-아키텍처">2-2. 마이크로서비스 아키텍처</h2>
<ul>
<li><p>서비스들이 대규모 트래픽을 감당해야 하므로 모놀로식 아키텍처의 대안으로 마이크로서비스 아키텍처가 등장하게 되었습니다.</p>
</li>
<li><p>마이크로서비스는 <strong>한 서비스에서 기능별로 물리적인 서버의 위치를 다르게 하여 서비스를 구성한 것입니다.</strong></p>
<h3 id="2-2-1-장점">2-2-1. 장점</h3>
</li>
<li><p>기능별로 서버가 분산되어 있기 때문에 장애가 생겼을 때 발생 부분을 쉽게 찾을 수 있습니다.</p>
</li>
<li><p>물리적인 서버의 위치가 기능별로 나누어져 있기 때문에 많이 사용되는 서비스의 경우 해당 기능에 대한 리소스만 증설할 수 있어, 자원을 효율적으로 사용할 수 있습니다.</p>
</li>
</ul>
<h3 id="2-2-1-한계">2-2-1. 한계</h3>
<ul>
<li>인프라 관점에서 볼 때 기능들이 서버별로 분산되어 있기 때문에 너 많은 서버를 유지, 보수해야 한다는 단점이 있습니다.</li>
<li>기능을 추가할 경우 서버를 새로 배치할지 기존 서버에 운영할지 결정하는 부분이 이슈가 되어 아키텍처를 구성하는데 많은 시간이 소요됩니다.</li>
<li>서비스 간의 호출이 많아지면서 전체적으로 아키텍처의 복합도가 높아지는 상황이 발생하였습니다.</li>
</ul>
<hr>

<h1 id="3-여러-서버를-관리하기-위한-방법">3. 여러 서버를 관리하기 위한 방법</h1>
<ul>
<li>클라우드가 대중화됨에 따라 마이크로 서비스 아키텍처가 확산되었습니다. 하지만, 여러 서버를 운영하게 되면서 발생하는 작업들은 무수히 많아지게 되었습니다.</li>
<li>이 부분을 해결하기 위해 <strong>가상화 솔루션</strong>이라는 방법이 등장하게 되었습니다.</li>
</ul>
<h2 id="3-1-가상화-솔루션-전가상화">3-1. 가상화 솔루션, 전가상화</h2>
<ul>
<li>하나의 큰 서버를 가상화 기술을 통해 잘게 쪼개서 마이크로서비스의 기능을 <strong>격리된</strong> 가상화 공간에 배치하면 서버는 한 대만 유지보수하기지만 여러 대의 서버를 사용하는 효과를 얻을 수 있기 때문입니다. 이러한 방법을 <strong>전가상화</strong>라고 합니다.</li>
</ul>
<h3 id="3-1-1-전가상화">3-1-1. 전가상화</h3>
<ul>
<li>전가상화는 <strong>하이퍼바이저</strong>를 통해 나누어진 게스트 운영체제와 통신할 수 있습니다.</li>
<li>전가상화는 많이 사용화된 기술이지만 하이퍼바이저를 거쳐야 하기 때문에 <strong>무겁다는 단점</strong>이 있습니다.</li>
<li>유명한 VMWare에서 만든 솔루션을 사용 시 속도 문제를 다소 해결할 수 있지만, 비용 문제가 발생했습니다.</li>
<li>이러한 문제를 해결하기 위해 <strong>컨테이너 방식의 가상화</strong>가 각광받기 시작했습니다.</li>
</ul>
<h2 id="3-2-docker">3-2. Docker</h2>
<ul>
<li>컨테이너 방식을 솔루션으로 구현한 것이 바로 도커입니다.</li>
</ul>
<h3 id="3-2-1-컨테이너-가상화">3-2-1. 컨테이너 가상화</h3>
<ul>
<li>전가상화 방식의 하이퍼바이저를 <strong>운영체제의 컨테이너 기술로 대체</strong>합니다.</li>
<li>컨테이너의 장점은 기존의 가상화처럼 <strong>게스트 운영체제를 포함하지 않기</strong> 때문에 <strong>속도가 빠르고 용량이 작다</strong>는 장점이 있습니다.</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>