<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>full-boram.log</title>
        <link>https://velog.io/</link>
        <description>공유로 가득찬 사람이 되고싶습니다.</description>
        <lastBuildDate>Tue, 06 May 2025 14:16:17 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>full-boram.log</title>
            <url>https://velog.velcdn.com/images/bo-ram-bo-ram/profile/22fcd5fb-7817-4770-a58b-01781a43c7ee/image.JPG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. full-boram.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/bo-ram-bo-ram" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[MVC에서 WebFlux로, 트러블슈팅과 성능 비교]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/jootalkpia-mvc-webflux</link>
            <guid>https://velog.io/@bo-ram-bo-ram/jootalkpia-mvc-webflux</guid>
            <pubDate>Tue, 06 May 2025 14:16:17 GMT</pubDate>
            <description><![CDATA[<h1 id="배경">배경</h1>
<p>앞서 난 Spring Cloud Gateway를 활용해 비동기 인증 시스템을 설계했고, Gateway는 WebFlux 기반으로 안정적인 요청 처리를 수행하고 있었다. 하지만 인증 서버는 여전히 Spring MVC 기반으로 구성되어 있었고, 이는 시스템 전반의 병목을 유발하는 주요 원인이었다.</p>
<p>특히 인증 요청이 증가함에 따라 <strong>Gateway → Auth 서버 간 통신에서 발생하는 블로킹 처리</strong>는 전체 서비스의 응답 속도 저하로 이어졌고, 이는 사용자 경험에도 영향을 미치기 시작했다. 이러한 문제를 해결하고자 인증 서버를 <strong>완전한 WebFlux 기반 비동기 구조로 리팩토링</strong>하게 되었다.</p>
<p>이번 글에서는 MVC → WebFlux로 전환하며 마주한 문제들과 그 해결 방식, 그리고 구조적으로 어떤 변화가 있었는지를 상세히 정리해본다.</p>
<hr>
<h1 id="내용">내용</h1>
<h2 id="주요-변경-사항">주요 변경 사항</h2>
<h3 id="1-mvc-기반-한계-제거">1. MVC 기반 한계 제거</h3>
<p>기존에는 <code>@Transactional</code>, <code>JpaRepository</code>, <code>WebMvcConfigurer</code> 등 MVC 전용 기술 스택을 사용하고 있었다. 하지만 WebFlux 환경에서는 다음과 같은 제약이 존재했다:</p>
<ul>
<li><code>@Transactional</code>은 동기 방식에서만 작동 → Reactor의 <code>TransactionalOperator</code> 등으로 대체 필요</li>
<li><code>JpaRepository</code>는 동기 처리 방식 → <code>ReactiveCrudRepository</code>로 대체</li>
<li><code>WebMvcConfigurer</code>는 작동하지 않음 → <code>WebFluxConfigurer</code>로 전환 필요</li>
</ul>
<p>이러한 구조적 제약들을 해결하며 모든 계층을 비동기로 재구성했다. 처음엔 마치 전체 코드를 다시 짜야 하는 듯한 막막함이 있었지만, 핵심적인 로직을 흐름 단위로 쪼개고 <code>Mono</code>, <code>Flux</code>로 구성하니 오히려 더 명확하게 구조가 정리되었다.</p>
</br>

<hr>
</br>


<h3 id="2-feignclient-→-webclient-전환">2. FeignClient → WebClient 전환</h3>
<p>Kakao 로그인은 기존에 <code>FeignClient</code> 기반으로 구현되어 있었으나, 이는 blocking 방식이기 때문에 WebFlux 구조에 맞지 않았다. 따라서 <code>WebClient</code>를 활용한 비동기 방식으로 전면 교체하였다.</p>
<pre><code class="language-java">public Mono&lt;KakaoUserResponse&gt; getUserInfo(String accessToken) {
    return webClient.get()
        .uri(&quot;/v2/user/me&quot;)
        .header(HttpHeaders.AUTHORIZATION, &quot;Bearer &quot; + accessToken)
        .retrieve()
        .bodyToMono(KakaoUserResponse.class);
}</code></pre>
</br>



<hr>
</br>


<h3 id="3-서비스-전반에-mono-적용">3. 서비스 전반에 Mono 적용</h3>
<ul>
<li><code>@Controller</code>, <code>@Service</code>, <code>@Repository</code> 전 계층에서 모든 리턴 타입을 <code>Mono&lt;T&gt;</code> 로 통일</li>
<li>DB 저장 및 조회, 사용자 인증, 토큰 발급 등 모든 로직을 비동기 스트림으로 처리</li>
<li>기존 MVC의 <code>ResponseEntity&lt;T&gt;</code>를 <code>Mono&lt;ResponseEntity&lt;T&gt;&gt;</code>로 변환</li>
</ul>
<pre><code class="language-java">public Mono&lt;ResponseEntity&lt;UserInfo&gt;&gt; getUserInfo(@CurrentUser UserInfo userInfo) {
    return Mono.just(ResponseEntity.ok(userInfo));
}</code></pre>
<p>이 과정에서 가장 신경 썼던 건 예외 처리였다. try-catch가 아닌 <code>.onErrorResume()</code>이나 <code>.switchIfEmpty()</code>를 활용해 흐름 안에서 에러를 유연하게 처리하는 방식은 리액티브 스타일에 익숙해지는 데 큰 도움이 되었다.</p>
</br>


<hr>
</br>


<h3 id="4-jwt-인증-필터-재구현">4. JWT 인증 필터 재구현</h3>
<p>Spring MVC에서는 <code>OncePerRequestFilter</code>를 활용했지만, WebFlux에서는 <code>WebFilter</code>로 대체해야 한다.</p>
<ul>
<li><code>JwtAuthenticationFilter</code>를 <code>WebFilter</code>로 전면 재작성</li>
<li>인증 로직을 Reactor Context에 주입 (<code>ReactiveSecurityContextHolder</code> 활용)</li>
<li><code>ReactiveAuthenticationManager</code>, <code>ServerAuthenticationConverter</code>도 함께 구현</li>
</ul>
<pre><code class="language-java">public Mono&lt;Void&gt; filter(ServerWebExchange exchange, WebFilterChain chain) {
    String token = extractToken(exchange);
    ...
    return chain.filter(exchange)
             .contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication));
}</code></pre>
<p>리액티브 환경에서는 인증 컨텍스트를 명시적으로 contextWrite를 통해 넘겨줘야 한다는 점이 새로웠고, 초반엔 생소했지만 체계적으로 접근하니 이해할 수 있었다.</p>
</br>


<hr>
</br>


<h2 id="5-currentuser-커스텀-어노테이션도-리액티브하게-전환">5. @CurrentUser 커스텀 어노테이션도 리액티브하게 전환</h2>
<p>WebFlux에서는 기존 MVC의 <code>HandlerMethodArgumentResolver</code>가 동작하지 않는다. 이를 해결하기 위해 WebFlux 전용 인터페이스로 전환했다:</p>
<ul>
<li><code>HandlerMethodArgumentResolver</code> → <code>org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver</code> 로 변경</li>
<li><code>resolveArgument()</code> 메서드를 Mono 기반으로 리팩토링</li>
</ul>
<pre><code class="language-java">@Override
public Mono&lt;Object&gt; resolveArgument(...) {
    String userJson = exchange.getRequest().getHeaders().getFirst(&quot;X-Passport-User&quot;);
    return Mono.just(objectMapper.readValue(userJson, UserInfo.class));
}</code></pre>
<p>처음에는 WebFlux에서 이런 커스텀 어노테이션을 그대로 쓸 수 있을지 걱정이 많았지만, 다행히도 Spring에서의 확장성과 모듈화가 잘 되어 있어 이 방식으로 무리 없이 이식이 가능했다.</p>
</br>


<hr>
</br>


<h2 id="6-예기치-않은-응답-포맷과-yml-설정-누락-트러블슈팅">6. 예기치 않은 응답 포맷과 YML 설정 누락 트러블슈팅</h2>
<p>초기에는 API 응답이 예상한 형태가 아닌, 아래 이미지처럼 <strong>단순 JSON</strong> 포맷으로 내려오는 문제가 발생했다.</p>
<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/8af4a36f-7890-4867-9a68-a99e37409d73/image.png" alt="scanAvailable 응답 예시"></p>
<p>이는 내가 커스텀한 응답 구조가 아닌, WebFlux 기본 JSON 렌더링 방식이 활성화된 결과였다. 디버깅 과정에서 알게 된 핵심 원인은 <strong><code>application.yml</code> 내 WebFlux 관련 설정이 누락되어 있었던 점</strong>이었다.</p>
<pre><code class="language-yaml">spring:
  main:
    web-application-type: reactive</code></pre>
<p>이 설정을 추가한 후, 기대한 형태의 커스텀 응답 포맷이 정상적으로 동작하였고, 기존 필터 및 예외 처리 로직도 예상대로 반영되었다. 이 경험을 통해 WebFlux에서는 YML 설정 하나가 전체 응답 흐름에 큰 영향을 미친다는 사실을 다시 한번 체감할 수 있었다.</p>
</br>


<hr>
</br>


<h2 id="트러블슈팅-및-고려사항">트러블슈팅 및 고려사항</h2>
<h3 id="✅-redis는-여전히-reactive-미지원">✅ Redis는 여전히 Reactive 미지원</h3>
<ul>
<li>Spring Data Redis는 완전한 리액티브를 지원하지 않음</li>
<li>따라서 <code>TokenRepository</code>는 기존 <code>CrudRepository</code>로 유지 (단, 호출부는 Mono 흐름 내에서 wrapping 처리)</li>
</ul>
<h3 id="✅-swagger">✅ Swagger</h3>
<ul>
<li>기존 <code>springdoc-openapi-starter-webmvc-ui</code> → <code>springdoc-openapi-starter-webflux-ui</code> 로 변경</li>
<li>WebFlux 환경에서는 일부 어노테이션의 동작 방식이 달라지므로 문서화 시 주의가 필요함</li>
</ul>
<h3 id="✅-spring-security">✅ Spring Security</h3>
<ul>
<li><code>SecurityFilterChain</code> 설정도 WebFlux 방식 (<code>ServerHttpSecurity</code>)으로 전환</li>
<li><code>AccessDeniedHandler</code>, <code>AuthenticationEntryPoint</code>도 Reactive 방식으로 재작성</li>
<li>기존 HttpSecurity 설정들과 충돌이 발생하지 않도록 설정 분리 필요</li>
</ul>
</br>


<hr>
<h1 id="성능-비교">성능 비교</h1>
<h2 id="webflux-구조-전환-후-성능-분석">WebFlux 구조 전환 후 성능 분석</h2>
<blockquote>
<p><em>Test tool: K6, 환경: AWS EC2(t2.micro), RDB, 테스트 API: <code>/api/v1/user/me</code></em></p>
</blockquote>
<h3 id="테스트-시나리오">테스트 시나리오</h3>
<ul>
<li>Warm-up: 1분 (VU 50)</li>
<li>증가 단계 1: 2분 (VU 200)</li>
<li>증가 단계 2: 1분 (VU 300)</li>
<li>종료 단계: 1분</li>
</ul>
<h3 id="주요-결과-요약-3회-테스트-평균-기준">주요 결과 요약 (3회 테스트 평균 기준)</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>Spring MVC</th>
<th>WebFlux</th>
<th>개선 여부</th>
</tr>
</thead>
<tbody><tr>
<td>총 요청 수</td>
<td>14,108</td>
<td>19,742</td>
<td><strong>+39.8%</strong> ✅</td>
</tr>
<tr>
<td>평균 응답 시간</td>
<td>2.32초</td>
<td>1.60초</td>
<td><strong>-31.4%</strong> ✅</td>
</tr>
<tr>
<td>p95 응답 시간</td>
<td>11.6초</td>
<td>5.4초</td>
<td><strong>-52.9%</strong> ✅</td>
</tr>
<tr>
<td>최대 응답 시간</td>
<td>평균 38.6초</td>
<td>평균 25.5초</td>
<td>✅</td>
</tr>
<tr>
<td>iteration 평균 시간</td>
<td>2.94초</td>
<td>2.11초</td>
<td>✅</td>
</tr>
<tr>
<td>경고 발생</td>
<td>다수</td>
<td>13회 이하</td>
<td>✅</td>
</tr>
</tbody></table>
<h3 id="결론">결론</h3>
<ul>
<li>WebFlux 전환은 단순 리팩토링 그 이상으로, <strong>성능 최적화와 안정성 확보에 실질적인 효과</strong>를 가져왔다.</li>
<li>특히 평균 처리량 40% 증가, 응답 속도 30% 개선은 인증 서버와 같이 요청이 집중되는 서비스에서 큰 의미를 가진다.( 최대치로는 평균 처리량 48% 증가, 응답 속도 38% 개선되었다.)</li>
</ul>
<hr>
<h1 id="마치며">마치며</h1>
<p>이번 리팩토링은 단순히 MVC 코드를 Mono로 바꾸는 작업이 아니었다. 구조적인 전환, 기술 스택의 교체, API 응답 구조의 통일 등 전반적인 비동기 아키텍처로의 재설계였다.</p>
<p>리액티브 프로그래밍은 분명 러닝 커브가 존재했다. 오류 메시지 한 줄, 디버깅 한 단계에 몇 시간을 소비하기도 했다. 하지만 그 과정을 거치며 얻은 것은 단순한 코드 최적화가 아니라 <strong>서비스 구조 전체에 대한 깊은 고민</strong>이었다.</p>
<p>특히 MSA 구조에서 인증 서버는 병목을 유발하기 쉬운 중요한 지점이기 때문에, 이 구조적 전환은 시스템 전반의 확장성과 안정성을 확보하는 데 큰 의미가 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[운영 중 발생한 RCE 공격 대응기: GeoIP 기반 차단 적용]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/rce-geoip</link>
            <guid>https://velog.io/@bo-ram-bo-ram/rce-geoip</guid>
            <pubDate>Thu, 03 Apr 2025 08:45:08 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.velcdn.com/images/bo-ram-bo-ram/post/b4834970-d6e7-4dc1-9415-918877122aa1/image.png"></a></p>
<h1 id="상황">상황</h1>
<p>운영 중인 서비스 픽플에 어느 날부터 <strong>지속적인 비정상 요청</strong>이 발생하기 시작했다. 로그를 확인해보니 아래와 같은 에러가 반복적으로 찍히고 있었다</p>
<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/ba13032e-c13d-4a4b-a5c0-c9d31189ad43/image.png" alt=""></p>
<pre><code>java.lang.IllegalArgumentException: Invalid character found in the request target [/index.php?s=/index/think\app/invokefunction&amp;function=call_user_func_array&amp;vars[0]=md5&amp;vars[1][]=Hello].
valid characters are defined in RFC 7230 and RFC 3986</code></pre><blockquote>
<p>반복적으로 <code>call_user_func_array</code>, <code>md5</code>, <code>vars[]</code> 등이 포함된 요청이 수천 건씩 찍히는 상황.</p>
</blockquote>
<p> 이는 외부에서 PHP 기반 RCE(Remote Code Execution) 공격 시도였고, 특히 <strong>ThinkPHP</strong> 프레임워크의 취약점을 타겟으로 한 패턴이었다.</p>
<p>해당 서비스는 Java/Spring 기반이라 직접적인 영향은 없었지만, 지속적인 요청으로 인해 <strong>로그가 과도하게 쌓이고, 서버 리소스가 낭비되는 현상</strong>이 발생하고 있었다.</p>
</br>

<hr>
</br>

<h1 id="대응">대응</h1>
<p>공격 요청의 공통점은 <strong>국내에서 발생하지 않은 트래픽</strong>이라는 점이었다. 이에 따라 다음과 같은 결정했다.</p>
<blockquote>
<p><strong>&quot;KR 외의 IP로부터의 접근은 모두 차단하자.&quot;</strong></p>
</blockquote>
<p>운영 중인 웹 서버는 <code>Nginx</code>였고, 우리는 GeoIP 설정을 통해 국가 기반의 차단 정책을 도입했다.</p>
<hr>
<h2 id="nginx-geoip-설정">Nginx GeoIP 설정</h2>
<ol>
<li><strong>GeoIP 데이터 파일 설치</strong></li>
</ol>
<pre><code>apt install geoip-database</code></pre><ol start="2">
<li><strong>GeoIP 설정 추가 (nginx.conf 또는 site 설정 내)</strong></li>
</ol>
<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/6642c345-9f9a-4363-a3b1-89981331b155/image.png" alt=""></p>
<pre><code class="language-nginx">http {
  geoip_country /usr/share/GeoIP/GeoIP.dat;
  map $geoip_country_code $allowed_country {
    default no;
    KR yes;
  }

  server {
    if ($allowed_country = no) {
      return 444;
    }
    ...
  }
}</code></pre>
<blockquote>
<p><code>return 444</code>는 nginx 특유의 응답 코드로, <strong>응답 없이 연결을 끊어</strong> 서버 리소스를 아끼는 방식이다.</p>
</blockquote>
<ol start="3">
<li><strong>Nginx reload 및 모니터링</strong></li>
</ol>
<pre><code class="language-bash">nginx -s reload</code></pre>
<p>적용 이후, 비정상 요청은 급격히 줄어들었고 서버 리소스 사용률과 로그 저장량도 안정화되었다.</p>
<hr>
<h2 id="느낀-점">느낀 점</h2>
<ul>
<li>처음엔 단순히 Invalid character 에러만 보고 단순한 클라이언트 실수인 줄 알았지만, <strong>요청 패턴과 개수를 분석하면서 공격임을 인지</strong>하게 되었다.</li>
<li>Java 기반이라 직접적인 코드 실행 위험은 없었지만, <strong>로그와 네트워크 자원 소모가 시스템 전체에 영향을 줄 수 있다는 점</strong>을 체감했다.</li>
<li>GeoIP 차단은 빠르고 효과적인 선제 방어 방법이지만, <strong>국내 사용자만을 타겟으로 한 서비스에만 적합</strong>하다는 제약이 있다.</li>
<li>RCE 시도 외에도 <code>/wp-login.php</code>, <code>/index.php</code> 등의 요청도 계속 유입되고 있어, 장기적으로는 <strong>WAF 또는 클라우드 기반 방어 체계</strong>도 검토 중이다.</li>
</ul>
<hr>
<h2 id="마무리">마무리</h2>
<p>운영 시스템은 언제든 예상하지 못한 위협에 노출될 수 있다. 비록 실제 피해는 없었지만, 이번 RCE 시도 대응을 통해 <strong>로그 모니터링의 중요성과 방화벽의 유연한 구성</strong>의 가치를 다시 한번 깨달았다.</p>
<blockquote>
<p>기술적 보완도 중요하지만, &quot;이상징후를 빠르게 감지할 수 있는 시스템&quot;이 결국 가장 강력한 방어다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot에서의 스레드 처리와 WebFlux 기반 비동기 시스템 이해하기]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/spring-boot-asynchronous</link>
            <guid>https://velog.io/@bo-ram-bo-ram/spring-boot-asynchronous</guid>
            <pubDate>Tue, 25 Mar 2025 13:49:18 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/a827e570-52f8-4da2-afc2-5c38c6ab5842/image.png" alt=""></p>
<h1 id="배경">배경</h1>
<p>앞서 스마일게이트 부트캠프를 진행하며 MSA 환경에서 Passport 기반 인증 시스템을 설계하며, <strong>Spring Cloud Gateway를 중심으로 비동기 구조</strong>로 구현했다.
이 과정에서 가장 중요한 전환점은 바로 <strong>동기(Synchronous)에서 비동기(Asynchronous)로의 패러다임 전환</strong>이었다.</p>
<p>하지만 서비스 규모가 커지고 인증 요청이 많아지면서, 기존 <strong>Spring MVC 기반 인증 서버에서 병목 문제가 발생했다.</strong> 이는 시스템 전체의 응답 속도 저하로 이어졌고, 나는 자연스럽게 <strong>인증서버를 WebFlux 기반의 구조로 전환</strong>을 고민하게 되었다.</p>
</br>

<p>이번 글에서는 이러한 전환 과정을 바탕으로</p>
<ul>
<li>Spring Boot의 스레드 처리 방식</li>
<li>동기와 비동기의 차이</li>
<li>Spring MVC vs WebFlux 패턴 비교</li>
<li>Spring Cloud Gateway가 비동기로 동작해야 하는 이유</li>
</ul>
<p>를 중심으로 구조적 이해를 정리하고자 한다.
현 시점에서 <strong>주톡피아의 인증 서버를 WebFlux 기반으로 리팩토링하게 된 기술적 배경과 판단 근거</strong>를 담았다.</p>
</br>


<hr>
</br>


<h2 id="1-spring-boot의-스레드-처리-방식">1. Spring Boot의 스레드 처리 방식</h2>
<h3 id="tomcat과-스레드풀">Tomcat과 스레드풀</h3>
<p>Java에서 스레드는 프로세스 내에서 실행되는 하나의 흐름이며, Spring Boot는 기본적으로 이를 효율적으로 처리하기 위해 <strong>Tomcat의 스레드풀(Thread Pool)</strong> 을 활용한다.</p>
<p>스레드를 매 요청마다 생성하는 방식은 비용이 크기 때문에, Tomcat은 일정 수의 스레드를 미리 만들어두고 요청마다 할당하는 방식으로 동작한다.<br>이로 인해 다수의 요청도 안정적으로 처리할 수 있으며, 이는 Spring Boot 애플리케이션에서 성능을 좌우하는 중요한 요소가 된다.
</br>
 기본 설정은 다음과 같다. <a href="https://docs.spring.io/spring-boot/appendix/application-properties/index.html">공식문서</a>에서도 확인이 가능하다.
 <img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/66f74a09-38f9-4b58-a66e-a09b2da638bd/image.png" alt=""></p>
<blockquote>
<pre><code class="language-yaml">server:
  tomcat:
    threads:
      max: 200         # 최대 스레드 수
      min-spare: 10    # 항상 유지되는 idle 스레드 수
    max-connections: 8192
    accept-count: 100</code></pre>
</blockquote>
</br>


<p>Tomcat의 이러한 스레드풀 구조 덕분에 우리는 별도의 멀티스레딩 처리를 하지 않아도,<br>Spring MVC 구조에서 <strong>다중 요청 처리</strong>를 손쉽게 구현할 수 있었던 것이다.</p>
</br>


<p>하지만 이 구조는 한계도 명확하다.<br>요청이 많아질수록 스레드 수가 급격히 증가하게 되고, 결국 <strong>스레드풀의 한계에 도달하면서 서버 성능이 저하</strong>되는 문제가 발생한다.<br>특히 데이터베이스 조회, 외부 API 호출과 같은 <strong>블로킹 작업이 포함된 요청이 많아질수록</strong> 이 문제는 더 두드러진다.</p>
<p>결국 우리는 아래와같이 고민하게 된다.  </p>
<blockquote>
<p>&quot;매 요청마다 스레드를 점유하지 않으면서도, 많은 요청을 동시에 처리할 수는 없을까?&quot;</p>
</blockquote>
<p>이 질문의 답이 바로 <strong>비동기(Asynchronous) 처리 방식</strong>이다.</p>
</br>


<hr>
</br>


<h2 id="2-동기synchronous-vs-비동기asynchronous">2. 동기(Synchronous) vs 비동기(Asynchronous)</h2>
<p>앞서 살펴본 것처럼 Spring Boot(MVC 구조)는 요청이 들어오면 <strong>Tomcat의 스레드풀에서 하나의 스레드를 할당받아 요청을 처리</strong>한다.
이 방식은 구조가 단순하고 익숙하다는 장점이 있지만, <strong>모든 요청이 응답이 끝날 때까지 스레드를 계속 점유</strong>한다는 근본적인 한계가 있다.</p>
</br>


<p>특히 다음과 같은 상황에서는 심각한 성능 병목으로 이어질 수 있다</p>
<ul>
<li>데이터베이스 I/O가 느린 경우</li>
<li>외부 API 호출(예: 인증 서버, 결제 API 등)이 응답을 지연시키는 경우</li>
<li>MSA 구조에서 서비스 간 호출이 잦은 경우</li>
</ul>
</br>


<h3 id="동기synchronous">동기(Synchronous)</h3>
<p><strong>동기 방식</strong>은 요청-응답을 하나의 흐름으로 처리한다.
즉, 한 작업이 완료되어야 다음 작업이 진행되며, <strong>요청-응답 사이의 블로킹이 발생할 경우 스레드가 낭비</strong>된다.</p>
<h4 id="특징">특징</h4>
<ul>
<li>요청을 처리하는 동안 스레드 점유</li>
<li>처리 시간이 긴 요청이 전체 처리량을 저하시킴</li>
<li>구현은 단순하고 이해하기 쉬움</li>
<li>예: <code>RestTemplate</code>, 기존 <code>Spring MVC</code> 방식</li>
</ul>
<h4 id="예시">예시</h4>
<pre><code class="language-java">@GetMapping(&quot;/user&quot;)
public String getUser() {
    String result = restTemplate.getForObject(&quot;http://auth-service/user&quot;, String.class);
    return result;
}</code></pre>
<p>위 코드에서 <code>restTemplate</code>은 블로킹 방식으로 동작하며, <strong>응답이 돌아올 때까지 현재 스레드는 아무 작업도 못하고 대기</strong>하게 된다.</p>
<hr>
<h3 id="비동기asynchronous">비동기(Asynchronous)</h3>
<p><strong>비동기 방식</strong>은 요청과 처리를 분리하여, 처리가 완료될 때까지 <strong>스레드를 점유하지 않는다.</strong>
대신 콜백 또는 리액티브 스트림을 통해 결과를 처리한다.
이 방식은 <strong>동시 요청 처리에 훨씬 유리하며</strong>, 고성능 API 서버, Gateway, 인증 서버 등에 특히 적합하다.</p>
<h4 id="특징-1">특징</h4>
<ul>
<li>요청 처리 중 스레드를 다른 작업에 재할당 가능</li>
<li>적은 수의 스레드로 많은 요청 처리 가능</li>
<li>복잡한 흐름과 예외 처리를 잘 설계해야 함</li>
<li>예: <code>WebClient</code>, <code>Mono</code>, <code>Flux</code>, <code>Spring WebFlux</code></li>
</ul>
<h4 id="예시-1">예시</h4>
<pre><code class="language-java">@GetMapping(&quot;/user&quot;)
public Mono&lt;String&gt; getUser() {
    return webClient.get()
        .uri(&quot;http://auth-service/user&quot;)
        .retrieve()
        .bodyToMono(String.class);
}</code></pre>
<p>WebClient는 논블로킹 방식으로 동작하기 때문에, <strong>외부 요청을 보내고 응답을 기다리는 동안 스레드를 반환</strong>하여 다른 요청을 처리할 수 있게 해준다.
즉, <strong>리소스를 효율적으로 사용하며 확장성 높은 구조</strong>를 제공한다.</p>
<hr>
<h3 id="🆚-어떤-환경에-적합할까">🆚 어떤 환경에 적합할까?</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>동기 (MVC)</th>
<th>비동기 (WebFlux)</th>
</tr>
</thead>
<tbody><tr>
<td>처리 모델</td>
<td>요청당 스레드 1개</td>
<td>이벤트 루프 기반</td>
</tr>
<tr>
<td>I/O 대기 처리</td>
<td>블로킹</td>
<td>논블로킹</td>
</tr>
<tr>
<td>구조</td>
<td>단순</td>
<td>복잡 (초기 학습 필요)</td>
</tr>
<tr>
<td>적합한 환경</td>
<td>적은 요청, 내부 서비스</td>
<td>고트래픽, MSA, API Gateway</td>
</tr>
<tr>
<td>성능</td>
<td>요청 증가 시 병목</td>
<td>높은 확장성과 처리량</td>
</tr>
</tbody></table>
<p>스레드풀 기반의 동기 방식은 구조가 단순하고 구현이 쉬우며, 소규모 단일 서비스에서는 효율적일 수 있다.
하지만 MSA 환경, 고트래픽 API 서버, 인증 서버 등 <strong>초당 수천 건 이상의 요청을 처리해야 하는 상황에서는 명확한 한계</strong>가 존재한다.</p>
<blockquote>
<p>이 한계를 극복하기 위한 구조가 바로 <strong>비동기 방식</strong>, 그리고 그것을 구현하는 <strong>Spring WebFlux</strong>다.</p>
</blockquote>
<p>이제부터는 WebFlux 기반 구조가 실제로 어떻게 동작하는지, Spring Cloud Gateway와 어떻게 연결되는지를 살펴보자.</p>
</br>


<hr>
</br>


<h2 id="3-spring-mvc-vs-webflux-비교">3. Spring MVC vs WebFlux 비교</h2>
<p>앞서 우리는 스레드풀 기반의 동기 처리 방식이 많은 요청을 감당하기 어렵다는 한계를 확인했다.
이제는 구조적으로 그 차이를 명확히 이해해볼 필요가 있다. 아래는 <strong>Spring MVC와 WebFlux의 주요 차이점</strong>을 정리한 표다.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>Spring MVC</th>
<th>Spring WebFlux</th>
</tr>
</thead>
<tbody><tr>
<td>실행 모델</td>
<td>동기, 블로킹</td>
<td>비동기, 논블로킹</td>
</tr>
<tr>
<td>스레드 처리</td>
<td>요청당 스레드 1개</td>
<td>이벤트 루프 기반</td>
</tr>
<tr>
<td>서버</td>
<td>Tomcat, Jetty</td>
<td>Netty, Undertow</td>
</tr>
<tr>
<td>요청 처리 방식</td>
<td><code>@RestController</code> + <code>RestTemplate</code></td>
<td><code>@RestController</code> + <code>WebClient</code>, <code>Mono</code>, <code>Flux</code></td>
</tr>
<tr>
<td>성능</td>
<td>요청 수 증가 시 병목 발생</td>
<td>고성능, 높은 확장성</td>
</tr>
<tr>
<td>사용 난이도</td>
<td>상대적으로 쉬움</td>
<td>리액티브 개념 학습 필요</td>
</tr>
</tbody></table>
</br>


<p>기존 MVC 방식은 익숙하고 구현이 단순하여 작은 서비스나 내부 API 처리에는 적합하다.
하지만 MSA 구조처럼 <strong>서비스 간 연동이 잦고, 인증/권한 검증처럼 블로킹 포인트가 많은 환경</strong>에서는
<strong>WebFlux 기반의 비동기 구조가 훨씬 더 유리</strong>하다.</p>
</br>


<p>특히 WebFlux는 Netty 기반 이벤트 루프를 활용하여, <strong>소수의 스레드로 수천 건의 요청을 동시에 처리할 수 있는 구조</strong>이기 때문에
<strong>높은 확장성과 안정성</strong>을 보장할 수 있다.</p>
<blockquote>
<p><a href="https://docs.spring.io/spring-framework/reference/web/webflux.html">공식 문서: Spring WebFlux 개요</a></p>
</blockquote>
</br>


<hr>
</br>


<h2 id="4-왜-spring-cloud-gateway는-비동기로-설계되었는가">4. 왜 Spring Cloud Gateway는 비동기로 설계되었는가?</h2>
<p>Spring Cloud Gateway는 API Gateway 역할을 하며,
모든 요청이 이 관문을 통과하므로 <strong>시스템의 병목 지점을 만들지 않는 구조가 필수</strong>다.</p>
</br>


<p>이러한 이유로 Gateway는 <strong>Spring WebFlux 기반</strong>으로 설계되었으며,
내부적으로 <strong>Netty를 사용하는 논블로킹 이벤트 루프 방식</strong>을 채택하고 있다.
이는 다음과 같은 구조적 특성에서 확인할 수 있다.</p>
</br>


<h3 id="구조적-특징">구조적 특징</h3>
<ul>
<li>Netty 기반 이벤트 루프 처리</li>
<li>필터 체인 구조로 라우팅, 인증, 로깅 등 요청 흐름 처리</li>
<li><code>WebClient</code>를 활용한 외부 API 호출 시 완전한 논블로킹 방식 유지</li>
</ul>
</br>


<h3 id="실제-인증-처리-예시">실제 인증 처리 예시</h3>
<pre><code class="language-java">webClient.get()
    .uri(&quot;http://auth-service/validate&quot;)
    .header(HttpHeaders.AUTHORIZATION, token)
    .retrieve()
    .bodyToMono(UserInfo.class);</code></pre>
<p>위 코드는 인증 요청을 <strong>WebClient로 비동기 전송</strong>하고,
<code>Mono&lt;UserInfo&gt;</code>로 반환받은 값을 필터 체인에서 후속 처리에 활용한다.
즉, Gateway는 <strong>하나의 요청 처리 중에도 다른 요청을 처리할 수 있는 구조</strong>이기 때문에,
<strong>고트래픽 상황에서도 병목 없이 안정적인 인증 흐름</strong>을 제공할 수 있다.</p>
<blockquote>
<p> <a href="https://docs.spring.io/spring-cloud-gateway/reference/">공식 문서: Spring Cloud Gateway Reference</a></p>
</blockquote>
</br>


<hr>
</br>


<h2 id="마무리">마무리</h2>
<p>우리는 아래와 같은 구조적 차이를 통해,
왜 Spring MVC 기반 시스템에서 WebFlux 기반 시스템으로의 전환이 필요한지를 확인할 수 있었다.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>동기 (Spring MVC)</th>
<th>비동기 (WebFlux + Gateway)</th>
</tr>
</thead>
<tbody><tr>
<td>스레드 효율성</td>
<td>요청당 스레드 1개 소모</td>
<td>이벤트 루프 기반</td>
</tr>
<tr>
<td>확장성</td>
<td>낮음</td>
<td>매우 높음</td>
</tr>
<tr>
<td>적합한 환경</td>
<td>단순 API, 소규모 서비스</td>
<td>고트래픽 MSA, 인증 시스템</td>
</tr>
<tr>
<td>전환 난이도</td>
<td>쉬움</td>
<td>리액티브 개념 학습 필요</td>
</tr>
</tbody></table>
<p>비동기 구조는 단순한 성능 개선 수준을 넘어,
<strong>서비스의 확장성과 장애 대응 능력을 구조적으로 보장하는 중요한 선택지</strong>라는 것을 깨달았다.</p>
</br>


<hr>
</br>



<p>주톡피아의 인증 서버는 처음엔 Spring MVC 기반으로 구축되어 있었고,
Gateway → Auth 서버 간 통신은 전형적인 블로킹 구조였다.
이로 인해 <strong>전체 요청 처리 속도가 느려지고 병목 현상이 발생</strong>하는 문제를 마주하게 되었다.</p>
<p>이를 해결하기 위해, 나는 <strong>WebFlux 기반으로 인증 서버를 리팩토링</strong>했고,
이 과정에서 어떤 선택과 고민이 있었는지를 다음 글에서 자세히 풀어보려 한다.</p>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li><a href="https://velog.io/@myspy/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%EC%8A%A4%EB%A0%88%EB%93%9C">스프링부트 스레드</a></li>
<li><a href="https://docs.spring.io/spring-framework/reference/web/webflux.html">Spring Framework Docs - WebFlux</a></li>
<li><a href="https://docs.spring.io/spring-cloud-gateway/reference/">Spring Cloud Gateway Reference</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Cloud Gateway에서 Passport기반 내부 인증 시스템 구현하기]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/Spring-Cloud-Gateway-Passport</link>
            <guid>https://velog.io/@bo-ram-bo-ram/Spring-Cloud-Gateway-Passport</guid>
            <pubDate>Tue, 11 Mar 2025 12:50:07 GMT</pubDate>
            <description><![CDATA[<h1 id="배경">배경</h1>
<h3 id="아키텍처">아키텍처</h3>
<p>스마일게이트 부트캠프를 진행하며 주식 기반 채팅 서비스 주톡피아를 구현했다. 당시 MSA구조로 아래와같이 아키텍처 설계를 진행했다.</p>
<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/5d41bed2-33a8-4ec3-9c48-3e8302156bd1/image.png" alt=""></p>
<h3 id="고민">고민</h3>
<p>MSA 환경에서 각 마이크로서비스는 독립적으로 동작하지만, 인증과 인가는 공통된 요구 사항이었고, 인증서버를 담당한 나는 <strong>“어떻게 하면 효율적으로 인증/인가를 진행할 수 있을까?”</strong>하는 고민이 생겼다! </p>
<p>또한 현재는 모든 서버가 Spring Boot 기반으로 운영되고 있었지만, <strong>향후 서비스가 확장되면서 다른 프레임워크가 도입될 가능성</strong>도 있었기 때문에, <strong>기존 JWT 기반 인증 방식을 어떻게 유연하게 적용할 수 있을지에 대한 확장성도 함께 고민하게 되었다.</strong></p>
<h3 id="처음-생각">처음 생각</h3>
<p>처음에는 두가지 방식이 생각났다.</p>
<ol>
<li><p>각 서비스에서 인증/인가를 개별적으로 처리하자
<img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/0b45897f-ca9d-4700-85ea-090ed7905e45/image.png" alt=""></p>
</li>
<li><p>인증 서버를 매번 호출하자
<img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/8ff685ad-2ce2-4e6a-99ed-c8e610e38a7c/image.png" alt=""></p>
</li>
</ol>
<p>하지만 아무리 생각해봐도 인증 로직이 중복되어 있어 유지보수성과 확장성이 떨어지고, 보안 측면에서도 허가되지 않은 요청이 서비스에까지 도달할 위험이 있다는 문제가 느껴졌다.</p>
<h3 id="구현">구현</h3>
<p>이를 해결하기 위해 <a href="https://toss.tech/article/slash23-server">토스에서 사용하는 방식</a>을 참고하여 Gateway와 인증 서버에 Passport를 도입하여 인증 및 인가를 수행하였다.<del>(여담이지만 캠프 당시 기술 블로그, 유튜브를 참 많이 참고했던것같다..!!!)</del></p>
<p><strong>Passport</strong>는 MSA 환경에서 통합된 인증 정보를 관리하는 방식으로, JWT 기반으로 사용자의 정보를 포함하며 Gateway에서 인증을 수행하고 이후의 요청에서는 Passport를 활용하여 인가를 처리한다. 이 과정에서 Spring Cloud Gateway의 비동기 방식을 고려하여 <strong>ConcurrentHashMap + Mono</strong> 방식으로 구현하였다.</p>
<p>또한 Gateway에서는 JWT 검증 및 인가 과정을 자동화하기 위해 <strong>세 개의 필터 (JwtAuthenticationFilter, PassportAuthorizationFilter, PassportRelayFilter)</strong> 를 구현하였다. 이로써 인증 및 인가 과정을 통합적으로 관리할 수 있게 되었고, 그 결과 유지보수성과 확장성이 크게 향상되었다. 추가적으로, 팀 내 생산성을 높이기 위해 <strong>@CurrentUser</strong> 커스텀 어노테이션을 제공하여, 컨트롤러에서 사용자 정보를 쉽게 주입받을 수 있도록 하였다.</p>
<p>아래에선 구현과정을 상세히 설명해보겠다!</p>
<hr>
<h1 id="주제">주제</h1>
<blockquote>
<p>Spring Cloud Gateway 기반 passport 개념을 활용한 내부 인증 시스템 구현기</p>
</blockquote>
<hr>
<h1 id="내용">내용</h1>
<h2 id="1-도입--passport-개념과-중요성-msa-구조에서의-필요성"><strong>1. 도입 : Passport 개념과 중요성, MSA 구조에서의 필요성</strong></h2>
<h3 id="11-인증authentication과-인가authorization의-중요성"><strong>1.1 인증(Authentication)과 인가(Authorization)의 중요성</strong></h3>
<p>앞에 설명한 배경을 갖고 Passport 개념을 활용한 내부 인증 시스템을 구현했다! 구현으로 이어지기전에 난 우선 인증과 인가의 개념을 정리했고 각 역할에 맞게 인증과 인가 로직을 분리하여 설계하고자 했다. 개념을 알아야 gateway에서 진행해야할지 인증서버에서 진행해야할지 정할 수 있다고 생각했기 때문이다.</p>
</br>

<p>인증(Authentication)은 사용자의 신원을 확인하는 과정이며, 인가(Authorization)는 해당 사용자가 특정 리소스에 접근할 수 있는지를 결정하는 과정이며 자세히 알아보자.</p>
</br>


<p><strong>인증(Authentication)은 누가 담당할까?</strong>
<strong>&quot;인증&quot;이란?</strong>
사용자가 올바른 로그인 정보를 입력했는지 확인하고, JWT를 생성해 사용자 정보를 Passport 형태로 관리하는 기반을 마련하는 과정이다.</p>
<p><strong>담당: &quot;Auth 서버&quot;</strong></p>
<ul>
<li>사용자가 로그인하면 Auth 서버가 JWT를 생성하여 반환한다.</li>
<li>JWT에는 사용자의 정보 (예: userId, role, 권한 등)가 포함된다.</li>
<li>이후 API 요청을 할 때, 클라이언트는 JWT를 Authorization 헤더에 담아서 요청한다.
즉, <strong>Auth 서버는 사용자의 신원을 확인하는 인증의 역할</strong>을 한다.</li>
</ul>
</br>


<p><strong>인가(Authorization)는 누가 담당할까?</strong>
<strong>&quot;인가&quot;란?</strong>
인증된 사용자가 요청한 리소스에 접근할 권한이 있는지 검증하는 과정이다.
예를 들면, 일반 사용자가 관리자 페이지에 접근하면 안되고, 올바른 사용자가 아닐 경우 서버에 접근을 할 수 없어야한다.</p>
<p><strong>담당: &quot;Gateway + 개별 서비스&quot;</strong></p>
<p>(1) <strong>Gateway에서 1차 인가 수행</strong></p>
<ul>
<li>JWT와 JWT로 만들어진 Passport를 검증하고, 사용자의 기본적인 권한을 확인한다.</li>
<li>예를 들어, 로그인이 필요한 API인지 확인하거나 토큰이 유효한지 검증하는 역할이다.</li>
<li>Passport의 주요 데이터를 HTTP 헤더에 추가하여 개별 서비스로 전달함으로써, 이후 서비스들이 별도로 사용자 정보를 조회하지 않고도 인가 처리를 수행할 수 있게 한다.
✅ 즉, <strong>Gateway는 기본적인 접근 제한을 담당하고, Passport를 개별 서비스로 전달하는 역할</strong>을 한다.</li>
</ul>
<p>(2) <strong>개별 서비스에서 2차 인가 수행</strong></p>
<ul>
<li>서비스별로 세부적인 권한을 다시 한 번 검증해야 한다.</li>
<li>예를 들어, 게시글을 수정하려고 할 때, 해당 게시글의 작성자인지 확인하는 과정이 필요할 수 있다.</li>
<li>이 단계에서 Passport의 정보를 활용하여 세부적인 권한을 검증한다.
✅ 즉, <strong>최종적으로 개별 서비스가 세부적인 권한을 확인하여 리소스 접근을 결정한</strong>다.</li>
</ul>
</br>


<table>
<thead>
<tr>
<th>단계</th>
<th>담당</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><strong>1. 인증 (Authentication)</strong></td>
<td>Auth 서버</td>
<td>로그인 시 JWT 또는 Passport 발급</td>
</tr>
<tr>
<td><strong>2. 1차 인가 (Authorization)</strong></td>
<td>Gateway</td>
<td>JWT/Passport 검증 및 기본적인 접근 제한</td>
</tr>
<tr>
<td><strong>3. 2차 인가 (Authorization)</strong></td>
<td>개별 서비스</td>
<td>서비스별 세부적인 권한 확인 (예: 리소스 소유 여부 등)</td>
</tr>
<tr>
<td><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/7b5579fe-2b8a-450b-a7e3-2469b3825ad4/image.png" alt=""></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<p><strong>결론적으로 &quot;인증&quot;은 Auth 서버가 담당하고, &quot;인가&quot;는 Gateway와 개별 서비스가 함께 담당하는 구조로 구현하였다!</strong></p>
</br>


<hr>
</br>


<h3 id="12-passport란"><strong>1.2 Passport란?</strong></h3>
<p>앞서 설명한 인증과 인가의 역할을 명확히 구분하고, 이를 효율적으로 처리하기 위해 도입한 것이 바로 <strong>Passport</strong>다.</p>
<p>처음에는 단순히 JWT만으로 인증/인가를 처리하려 했지만, 서비스가 많아지고 각 서비스에서 JWT를 검증하거나 파싱하는 로직이 중복되면서 점차 구조가 복잡해지고 유지보수가 어려워졌다.</p>
<p>그래서 생각한 것이 바로 <strong>&quot;Passport라는 단일한 인증 정보 관리 단위&quot;를 만들어 모든 서비스를 관통하는 인증 기준점으로 삼는 것</strong>이었다.</p>
</br>


<p><strong>Passport는 MSA 환경에서 통합된 사용자 인증 정보를 관리하는 구조적 방식</strong>이다. JWT 기반으로 만들어지며, 한 번 인증된 사용자 정보를 Gateway를 통해 Passport 형태로 변환하고, 이후 요청에서는 해당 Passport를 기반으로 인가 과정을 처리한다.</p>
<blockquote>
<p>쉽게 말하면, 여권(Passport)을 한 번 발급받으면, 입국심사(Gateway)에서 확인하고 나서부터는 어느 나라(서비스)를 방문해도 더 이상 매번 신분증을 꺼낼 필요가 없는 구조인 셈이다.</p>
</blockquote>
<p>기술적으로는 JWT에 담긴 사용자 정보(예: <code>userId</code>, <code>role</code>, <code>권한 등</code>)를 Passport 객체로 변환하고, 이를 Gateway에서 1차 검증 및 Relay 처리한 뒤, 개별 서비스에서는 해당 정보를 바탕으로 2차 인가를 수행하도록 설계하였다.</p>
</br>



<hr>
<h3 id="13-msa-구조에서-passport가-필요한-이유"><strong>1.3 MSA 구조에서 Passport가 필요한 이유</strong></h3>
<p>그렇다면 왜 굳이 Passport라는 구조를 도입해야 했을까? 처음엔 단순히 JWT만으로 충분하지 않을까 생각했다. 하지만 실제로 MSA 환경에서 아래와 같은 문제들이 발생하며 Passport의 필요성이 더욱 명확해졌다.</p>
</br>



<p><strong>1. 중앙 집중형 인증 및 인가 구조 필요</strong></p>
<p>MSA 구조에서 각 서비스가 독립적으로 운영되는 만큼, 인증/인가 로직까지 각자 처리하게 되면 코드가 중복되고 일관성이 사라지게 된다.</p>
<p>Passport를 도입하면서 우리는 <strong>Gateway에서 인증을 단일화</strong>하고, 그 결과로 생성된 Passport를 모든 서비스에서 <strong>공통적으로 활용</strong>할 수 있게 되었다.</p>
<p>이를 통해 인증은 한 곳에서 관리되고, 각 서비스는 Passport를 통해 필요한 인가 정보만 검증하면 되므로 <strong>로직이 단순화되고 유지보수가 쉬워졌다.</strong></p>
</br>


<p><strong>2. 성능 최적화</strong></p>
<p>앞서 생각한 방식으로 구현했다면 서비스마다 JWT를 파싱하거나, 사용자의 권한을 검증하기 위해 DB를 조회하는 일이 빈번했을 것이다. 하지만 Passport를 도입할 경우에는 JWT 기반 Passport를 <strong>Gateway에서 한 번만 검증</strong>하고, <strong>이후 요청은 캐싱된 Passport 정보를 바탕으로 처리가 가능해져 성능적 이점이 예상되었다</strong>.</p>
</br>


<p><strong>3. 보안성 강화</strong></p>
<p>JWT는 내부에 사용자 정보를 담고 있고, 서명을 통해 변조를 방지할 수 있다. 이를 기반으로 한 Passport는 <strong>위·변조 방지와 사용자 검증</strong>이라는 두 가지 목적을 동시에 만족시킨다.</p>
<p>Gateway에서 JWT의 유효성과 서명을 검증한 뒤, <strong>유효한 사용자 정보만 Passport로 변환</strong>하여 하위 서비스로 전달되도록 했기 때문에, 허가되지 않은 요청이 내부 서비스까지 도달하는 것을 방지할 수 있고, 전체 서비스의 신뢰성을 높일 수 있었다.</p>
</br>

<hr>
<hr>
</br>

<h2 id="2-구현-방식-비교-왜-concurrenthashmap--mono였을까"><strong>2. 구현 방식 비교: 왜 ConcurrentHashMap + Mono였을까?</strong></h2>
<p>Passport 개념을 실제로 구현하기 위해 여러 방식을 검토하게 되었다.</p>
<p>초기에는 단순하게 <code>ThreadLocal</code>을 활용하는 구조로 접근했지만, WebFlux 기반의 비동기 환경이란 점을 기준으로 <strong>ConcurrentHashMap + Mono</strong> 조합이 가장 안정적이고 확장 가능한 방식이라는 결론에 도달했다.</p>
</br>


<h3 id="21-threadlocal-기반-인증-시스템"><strong>2.1 ThreadLocal 기반 인증 시스템</strong></h3>
<p>처음에는 기존의 Spring MVC 스타일처럼, <code>ThreadLocal</code>을 활용해 인증 정보를 저장하는 구조를 고려했다.</p>
<p>이를 위해 별도의 <code>JootalkpiaAuthenticationContext</code> 클래스를 만들어 인증 정보를 담았고, 이후 각 요청에서 이 Context를 통해 사용자 정보를 꺼내 쓸 수 있도록 했다.</p>
<p>하지만 이 방식은 MSA 환경, 특히 <strong>Spring WebFlux의 비동기 구조</strong>와 맞지 않는다는 한계를 예상했다.</p>
<ul>
<li><strong>WebFlux와의 비호환성</strong>: <code>ThreadLocal</code>은 스레드 단위로 데이터를 저장하기 때문에, 이벤트 루프 기반의 비동기 처리 모델에서는 일관된 데이터를 유지하기 어렵다.</li>
<li><strong>동시성 이슈</strong>: 여러 요청이 하나의 스레드를 공유하거나, 동일한 Context를 참조하게 되면 예상치 못한 인증 정보 누수 혹은 충돌이 발생할 수 있다.</li>
<li><strong>유지보수 및 확장성 문제</strong>: 각 서비스마다 인증 컨텍스트를 관리해야 하기 때문에, 구조가 복잡해지고 수정이 어려워진다.</li>
</ul>
<p>이러한 문제들을 때문에 WebFlux 환경에 <strong>완벽히 호환되면서도 안전한 인증 정보 공유 방식</strong>을 고민하게 되었다.</p>
</br>


<hr>
<h3 id="22-왜-concurrenthashmap--mono-방식이-최적일까"><strong>2.2 왜 ConcurrentHashMap + Mono 방식이 최적일까?</strong></h3>
<p>결국 선택한 방식은 <strong>ConcurrentHashMap + Mono 조합</strong>이었다.</p>
<p>Gateway에서 JWT를 검증한 후, 사용자 정보를 기반으로 생성한 Passport를 <strong>ConcurrentHashMap에 캐싱</strong>하고,</p>
<p>서비스 간 전달 시에는 <strong>Mono를 통해 비동기적으로 처리</strong>하는 구조로 구현했다.</p>
<p>이 방식은 WebFlux의 비동기 흐름을 그대로 따르면서도, 인증 정보를 안전하게 공유할 수 있는 구조였다.</p>
</br>


<h3 id="방식-비교">방식 비교</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th><strong>ThreadLocal 기반 (Spring MVC)</strong></th>
<th><strong>ConcurrentHashMap + Mono (Spring WebFlux, MSA)</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Passport 저장 방식</strong></td>
<td><code>ThreadLocal</code> (스레드 단위 저장)</td>
<td><code>ConcurrentHashMap</code> + <code>Mono</code> (전역 캐싱 + 비동기 처리)</td>
</tr>
<tr>
<td><strong>데이터 검증 방식</strong></td>
<td>HMAC 서명 (SHA-256)</td>
<td>HMAC 서명 (SHA-256)</td>
</tr>
<tr>
<td><strong>Passport 검증 위치</strong></td>
<td>각 서비스 내부</td>
<td>Gateway 필터에서 검증 후 전달</td>
</tr>
<tr>
<td><strong>JWT 검증 방식</strong></td>
<td>미구현 (직접 구현 필요)</td>
<td>Auth 서버에 <code>WebClient</code>로 비동기 요청</td>
</tr>
<tr>
<td><strong>Reactive 지원 여부</strong></td>
<td>❌ (Spring MVC 기반, 블로킹)</td>
<td>✅ (Reactor 기반 완전 지원)</td>
</tr>
</tbody></table>
</br>


<h3 id="정리">정리</h3>
<ul>
<li><strong>비동기 처리에 최적화</strong>: Mono를 활용해 인증 정보도 완전히 비동기 흐름 안에서 처리 가능</li>
<li><strong>전역 공유 구조</strong>: ConcurrentHashMap으로 인증 정보(Passport)를 캐싱하여 효율적인 접근 보장</li>
<li><strong>Spring Cloud Gateway와의 자연스러운 호환성</strong>: 필터 체인에서 인증 정보 조회 및 전달이 간결해짐</li>
<li><strong>보안성 확보</strong>: JWT를 Auth 서버와 연동하여 서명 기반 검증 → 신뢰할 수 있는 Passport 생성 가능</li>
</ul>
<p>결과적으로 이 구조를 통해 <strong>비동기 환경에서 안정적이고 확장 가능한 인증 시스템</strong>을 갖출 수 있었고,</p>
<p>나아가 각 서비스가 인증 정보를 직접 다루지 않아도 되어 <strong>인증 로직의 일관성과 재사용성</strong>까지 확보할 수 있었다.
</br></p>
<hr>
<hr>
</br>


<h2 id="3-passport-기반-인증-및-인가-시스템"><strong>3. Passport 기반 인증 및 인가 시스템</strong></h2>
<h3 id="31-passport-인증-과정"><strong>3.1 Passport 인증 과정</strong></h3>
<p>Passport 기반 시스템에서는 인증 흐름이 다음과 같이 구성된다:</p>
<ol>
<li><strong>Gateway가 클라이언트 요청의 Authorization 헤더 확인</strong>
  클라이언트가 JWT를 담아 요청하면 Gateway에서 이를 추출하여 유효성을 판단한다.</li>
<li><strong>Passport가 없거나 만료된 경우</strong>
 Gateway는 Auth 서버의 <code>/validate</code> API를 호출하여 JWT를 검증하고, 그 결과로부터 Passport를 생성한다.</li>
<li><strong>Passport 기반 인가 수행</strong>
발급된 Passport 정보를 캐싱하고, 이를 바탕으로 사용자 권한을 확인하며 인가 처리에 활용한다.
 최종적으로 사용자 정보를 <code>UserInfo</code> 객체에 저장하여 이후 컨트롤러나 서비스에서 활용 가능하게 한다.</li>
</ol>
</br>

<hr>
<hr>
</br>

<h2 id="4-custom-annotation-currentuser-도입"><strong>4. Custom Annotation: @CurrentUser 도입</strong></h2>
<h3 id="41-currentuser-구현-방식"><strong>4.1 @CurrentUser 구현 방식</strong></h3>
<p>Passport 기반 인증 시스템을 도입하면서, 컨트롤러 단에서 사용자 정보를 보다 <strong>간결하고 일관된 방식</strong>으로 주입받기 위해 커스텀 어노테이션인 <code>@CurrentUser</code>를 구현했다.</p>
<ul>
<li>Spring MVC의 <code>HandlerMethodArgumentResolver</code>를 확장한 <code>CurrentUserArgumentResolver</code>를 정의하여,</li>
<li>Gateway에서 전달한 <code>X-Passport-User</code> 헤더를 파싱하고, 이를 <code>UserInfo</code> 객체로 변환한 뒤 컨트롤러 파라미터에 자동 주입한다.</li>
</ul>
<p>이를 통해 각 컨트롤러마다 별도로 JWT 파싱 로직을 구현할 필요 없이, 단순히 어노테이션만 붙이면 Passport 정보를 활용할 수 있게 되었다.</p>
<hr>
<h3 id="42-currentuser-사용-예시"><strong>4.2 @CurrentUser 사용 예시</strong></h3>
<pre><code class="language-java">@GetMapping(&quot;/api/v1/user/me&quot;)
public ResponseEntity&lt;UserInfo&gt; getUserInfo(@CurrentUser UserInfo userInfo) {
    return ResponseEntity.ok().body(userInfo);
}
</code></pre>
<p>이처럼 <code>@CurrentUser</code>를 활용하면 사용자 인증 정보가 필요한 모든 API에서 코드 중복 없이 간결하게 사용자 정보를 주입받을 수 있다.</p>
</br>

<hr>
<hr>
</br>

<h2 id="5-gateway-필터-구성-passport-기반-흐름"><strong>5. Gateway 필터 구성: Passport 기반 흐름</strong></h2>
<h3 id="51-gateway-필터의-역할-및-흐름"><strong>5.1 Gateway 필터의 역할 및 흐름</strong></h3>
<p>Passport 인증 시스템의 핵심은 Spring Cloud Gateway에서 동작하는 <strong>세 개의 필터</strong>이다. 각 필터는 인증과 인가 과정을 책임지는 역할로 나뉘며, 필터 체인 순서대로 다음과 같이 동작한다:</p>
<ol>
<li><strong>JwtAuthenticationFilter</strong>
 클라이언트 요청에서 Authorization 헤더(JWT)를 추출하고, Auth 서버로 검증 요청을 보낸다.
 검증이 성공하면 Passport를 생성하고 이후 필터로 전달한다.</li>
<li><strong>PassportAuthorizationFilter</strong>
 생성된 Passport를 기반으로 사용자의 인증 정보를 추출하고, 이를 <code>UserInfo</code> 형태로 가공하여 요청 컨텍스트에 저장한다.</li>
<li><strong>PassportRelayFilter</strong>
 가공된 Passport 사용자 정보를 <code>X-Passport-User</code> 헤더에 실어 downstream 서비스로 전달한다.</li>
</ol>
<p>이 구조 덕분에 <strong>모든 인증 로직은 Gateway에서 사전 처리</strong>되고, 개별 서비스는 인가와 도메인 로직에만 집중할 수 있게 되었다.
</br></p>
<hr>
<hr>
</br>

<h2 id="6-기대-효과-및-결론"><strong>6. 기대 효과 및 결론</strong></h2>
<h3 id="61-기대-효과"><strong>6.1 기대 효과</strong></h3>
<ul>
<li><p><strong>비동기 WebFlux 환경에 최적화</strong>되어 높은 성능을 유지할 수 있다.</p>
</li>
<li><p>인증/인가 로직이 통합되어 <strong>유지보수가 쉬워지고 확장성 확보</strong>가 가능하다.</p>
</li>
<li><p><code>@CurrentUser</code> 어노테이션 도입으로 <strong>컨트롤러 코드가 간결해지고 개발 생산성이 향상</strong>되었다.</p>
</li>
</ul>
<p>Spring Cloud Gateway를 중심으로 한 인증 구조 덕분에 <strong>서비스 간 일관된 인증 체계</strong>가 정립되었다.</p>
<hr>
<h3 id="62-결론"><strong>6.2 결론</strong></h3>
<p>Passport 기반 인증 시스템은 MSA 환경에서 <strong>중앙 집중화된 인증 관리, 높은 성능, 보안성, 그리고 확장성</strong>을 모두 만족시킬 수 있었다.</p>
<p>이 시스템을 통해 인증/인가 구조를 체계적으로 정비할 수 있었고,</p>
<p>향후 새로운 서비스가 추가되더라도 <strong>공통된 인증 흐름 속에서 손쉽게 통합</strong>될 수 있도록 기반을 마련했다.</p>
<p><strong>앞으로도 사용자 경험을 해치지 않으면서도 더욱 안전하고 유연한 인증 체계를 만들어가는 것이 목표</strong>이다.</p>
</br>

<ul>
<li><p>참고 레퍼런스
  <a href="https://toss.tech/article/slash23-server">https://toss.tech/article/slash23-server</a></p>
<p>  <a href="https://byunsw4.tistory.com/35">https://byunsw4.tistory.com/35</a></p>
</li>
</ul>
<pre><code>https://velog.io/@kimsei1124/Passport-%EC%9D%B8%EC%A6%9D-%EA%B0%9D%EC%B2%B4</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[HttpStatus code 204 NO CONTENT]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/204-NO-CONTENT</link>
            <guid>https://velog.io/@bo-ram-bo-ram/204-NO-CONTENT</guid>
            <pubDate>Thu, 31 Oct 2024 14:59:31 GMT</pubDate>
            <description><![CDATA[<h2 id="문제-상황">문제 상황</h2>
<blockquote>
<p><a href="https://pick-ple.com">픽플</a>에서는 게스트가 원하는 클래스를 신청 후 참가비를 입금해야 지식 공유자인 스픽커에게 명단이 보일 수 있게 되어있다. 이때 운영진이 입금을 확인하고 신청 상태값을 승인대기로 변경해주는 api가 필요해서 구현하게 되었다.
우리는 클래스를 신청한 사람들에 대한 정보를 db에 테이블로 관리하고 있었는데 승인 대기 요청이 성공했을 때 db에서의 변화만 발생하기 때문에 이 의미를 담아 응답코드를 204(No Content)로 내려주고자 했다.
근데 이때 기본적으로 구현해놓은 ApiResponseDto형태로도 나타나지 않는 상황이 발생하였다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/5f7ea043-53e6-4816-82a1-143565bb8703/image.png" alt="스크린샷 2024-08-29 오후 5.41.46.png">
)</p>
<p>문제 상황에서 설명한 것 처럼 구현이 완료되었고 api가 정상적으로 작동하였지만 postman에 위와같이 응답이 아무것도 담겨있지 않는것을 확인할 수 있다.</p>
<h3 id="코드">코드</h3>
<p>해당 상황을 자세히 살펴보기 위해 구현되어있던 코드를 먼저 살펴보겠다.</p>
<ul>
<li><p>controller</p>
<pre><code class="language-java">      @PatchMapping(&quot;v2/moimSubmission/{moimSubmissionId}&quot;)
      public ApiResponseDto updateMoimSubmissionStateToPendingApproval(@PathVariable Long moimSubmissionId) {
          moimSubmissionCommandService.updateMoimSubmissionStateToPendingApproval(moimSubmissionId);
          return ApiResponseDto.success(SuccessCode.MOIM_SUBMISSION_STATE_UPDATE_SUCCESS);
      }</code></pre>
</li>
</ul>
<ul>
<li><p>ApiResponseDto</p>
<pre><code class="language-java">  @Builder
  public record ApiResponseDto&lt;T&gt;(
          @JsonIgnore HttpStatus httpStatus,
          int status,
          @NonNull String message,
          @JsonInclude(value = NON_NULL) T data
  ) {
      public static &lt;T&gt; ApiResponseDto&lt;T&gt; success(final SuccessCode successCode) {
          return ApiResponseDto.&lt;T&gt;builder()
                  .httpStatus(successCode.getHttpStatus())
                  .status(successCode.getCode())
                  .message(successCode.getMessage())
                  .data(null)
                  .build();
      }
  }</code></pre>
</li>
</ul>
<ul>
<li><p>SuccessCode</p>
<pre><code class="language-java">  @Getter
  @AllArgsConstructor
  public enum SuccessCode {
      MOIM_SUBMISSION_STATE_UPDATE_SUCCESS(20400, HttpStatus.NOCONTENT, &quot;모임 신청 내역 승인 대기로 변경 성공 성공&quot;);

      private final int code;
      private final HttpStatus httpStatus;
      private final String message;
  }</code></pre>
</li>
</ul>
<p>당시 내가 기대한 응답의 body는 아래와 같았다.</p>
<pre><code class="language-json">{
        &quot;status&quot; : 20400,
        &quot;mesage&quot; : &quot;모임 신청 내역 승인 대기로 변경 성공&quot;
}</code></pre>
<hr>
<h2 id="해결-과정">해결 과정</h2>
<h3 id="왜-안나올까">왜 안나올까?</h3>
<p>이때 평소와 다르게 구현한 부분은 <code>HttpStatus</code>를 NOCONTENT로 내려준 것 밖에 없었고 혹시나하는 마음에 아래와같이 OK로 바꿔줬다.</p>
<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/eee312bd-3902-45d5-8fdf-ee1cbde17124/image.png" alt="스크린샷 2024-08-29 오후 5.41.14.png"></p>
<p>그랬더니 나의 기대값과 동일하게 body에 응답이 담기는 것을 확인할 수 있었다.</p>
<h3 id="204-no-content">204 No Content</h3>
<blockquote>
<p>204 상태코드는 성공 상태 응답 코드로 요청이 성공했으나 content가 없다는 코드이다. 이는 클라이언트가 현재 페이지에서 벗어나지 않아도 된다는 것을 나타낸다. 사용되는 경우는 <code>저장 후 편집 계속</code>되는 것처럼 현재 페이지를 다른 페이지로 대체해서는 안되는, 즉 새로 데이터를 보여줄 필요가 없을 때 사용한다.</p>
</blockquote>
<p>참고했던 <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Status/204">사이트</a>에선 <strong><code>서버에서 잘못된 본문을 포함한 응답을 전달하는 경우</code></strong>가 존재할 수 있기 때문에 사용에 주의해야한다고 한다. </p>
<h3 id="나의-결론">나의 결론</h3>
<ul>
<li><p>204 No Content가 유리한 경우는 아래와 같다</p>
<ol>
<li><p><code>비동기 처리 완료 확인</code> : 클라이언트가 상태 확인을 <code>주기적으로</code> 서버에 요청하고 이에 대한 응답이 단순한 완료 이후에 전달할 내용이 없는 경우</p>
</li>
<li><p><code>PUT/PATCH 요청</code>  : 클라이언트가 리소스를 수정했을 때 변경사항을 서버에 반영하고, 추가적인 확인이나 결과가 필요하지 않은 경우</p>
<p>위와같은 경우에 클라이언트와 서버 간의 불필요한 데이터 전송을 줄여 네트워크 성능에 이점이 있다고 생각한다.</p>
</li>
</ol>
</li>
</ul>
<blockquote>
<p>위와같이 204 응답코드에 대해 알아보았다.  고민해본 결과 우리가 내려주는 응답 코드에 따라 클라이언트가 추가 작업이 진행했기 때문에 <code>HttpStatus</code>를 204 <code>No Content</code>가 아닌 200 <code>Ok</code>로 변경해주었다! 사소하게 지나칠 수 있는 응답에 대해 알아볼 수 있는 시간이었다.</p>
</blockquote>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li><a href="https://velog.io/@server30sopt/204-NOCONTENT%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%84%EC%8B%9C%EB%82%98%EC%9A%94">https://velog.io/@server30sopt/204-NOCONTENT에-대해-아시나요</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Status/204">https://developer.mozilla.org/ko/docs/Web/HTTP/Status/204</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[N+1문제 해결의 모든 것]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/N1%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%EC%9D%98-%EB%AA%A8%EB%93%A0-%EA%B2%83</link>
            <guid>https://velog.io/@bo-ram-bo-ram/N1%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%EC%9D%98-%EB%AA%A8%EB%93%A0-%EA%B2%83</guid>
            <pubDate>Thu, 24 Oct 2024 12:54:05 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/1d23dadf-8537-400d-b344-312c059cb648/image.png" alt=""></p>
<h1 id="배경">배경</h1>
<blockquote>
<p>현재 사이드 프로젝트로 진행중인 <a href="https://pick-ple.com/">픽플</a>이 베타서비스 중에 있는데 동작을 확인하기 위해 <code>jpa:show_sql: true</code>로 설정이 되어있다! 그러던 와중 서버에 이상이 생길 때 마다 로그를 확인하는데 Hibernte가 생성한 sql 문이 너무 많이 찍혀있어서  확인이 어려웠다.(exception만 안잡고 docker logs 하면 백만년..) 아무튼 그래서 해당 문제의 원인을 파악하던 중 N+1 문제가 확인되었고 자세히 알아보려고 한다.</p>
</blockquote>
<hr>
<h1 id="내용">내용</h1>
<blockquote>
<p>N+1문제 분석, 해결방법, 프로젝트 적용</p>
</blockquote>
<h2 id="n1문제-정의">N+1문제 정의</h2>
<blockquote>
<p>객체를 DB에서 불러올 때 (JPA Repository를 활용해 인터페이스 메소드를 호출할 때)  <strong>1개의 쿼리가 아닌 연관관계 객체를 불러오기 위한 N개의 <code>추가적인 쿼리</code>가 생성되는 문제</strong>
즉, JPA의 Entity 조회시 Query 한번 내부에 존재하는 다른 연관관계에 접근할 때 또 다시 한번 쿼리가 발생하는 비효율적인 상황</p>
</blockquote>
<ul>
<li><p>예시</p>
<p>   게시글이랑 댓글이랑 <code>1대N</code>으로 연관관계 및 JPA Fetch 전략을 <code>LAZY 전략</code>으로 해놓은 상황에서 게시글에 해당하는 댓글을 조회할 때 하나의 게시글에 3개의 댓글이 들어있으면 (db에서) article.getComment().getTitle() 상황에서 3번의 추가 쿼리가 발생한다.</p>
<p>  ⇒ <code>성능저하 이슈</code> 발생!!</p>
  </br>

</li>
</ul>
<h2 id="발생-원인-분석">발생 원인 분석</h2>
<h3 id="언제"><code>언제?</code></h3>
<ul>
<li><p>1:N 또는 N:1 관계를 가진 엔티티를 JPA Repository를 활용해 조회할때</p>
</li>
<li><p><strong><code>즉시 로딩</code></strong>으로 데이터를 가져오는 경우</p>
<p>  JPA에서 Fetch 전략(즉시 로딩)을 가지고 해당 데이터의 연관 관계인 하위 엔티티들을 추가 조회 ⇒ <code>N + 1</code> 문제 발생</p>
</li>
<li><p><strong><code>지연 로딩</code></strong>으로 데이터를 가져온 이후에 가져온 데이터에서 하위 엔티티를 다시 조회하는 경우 
비즈니스 로직에서 하위 엔티티를 가지고 작업하게 되면 추가 조회가 발생 ⇒ <code>N + 1</code> 문제 발생</p>
</li>
</ul>
<h3 id="왜"><code>왜?</code></h3>
<ul>
<li>근본적인 원인은 <code>자동화된 쿼리</code>들이 날아가고 (픽플의 경우 JPA사용) select 문을 통해서만 연관 객체에 접근할 수 있는  <code>RDB</code>와 연관관계를 통해 레퍼런스를 가지고있으면 메모리 내에서 Random Access를 통해 접근할 수 있는 <code>객체지향 언어</code>간의 패러다임으로 인해 발생한다.</li>
<li>즉 JPQL 입장에서는 연관관계 데이터를 무시하고 해당 엔티티 기준으로 쿼리를 조회하기 때문에 해당 엔티티를 조회하는 <code>SELECT * FROM article</code> 쿼리만 실행하게 된다. 그렇기 때문에 연관된 엔티티 데이터가 필요한 경우, FetchType으로 지정한 시점에 조회를 추가적으로 호출하게 되는것! ( 즉시로딩일 경우 바로 호출, 지연 로딩일 경우 하위 엔티티에서 호출 시 호출)  </br>
## 해결 방법

</li>
</ul>
<h3 id="1-fetch-join">1. <code>FETCH JOIN</code></h3>
<p>사실 N+1 자체가 발생하는 이유는 두 테이블이 연결되어 있을 때 한쪽 테이블을 조회하고 다른 테이블을 따로 조회하기 때문에 발생하는 문제다. 이를 해결하기 위해 직접 최적회된 쿼리, 즉 <code>FETCH JOIN</code>를 직접 작성해주면 된다! </p>
<pre><code class="language-sql">SELECT a FROM article c JOIN FETCH a.comment</code></pre>
<ul>
<li><p>fetch join이 무엇인가요?</p>
<blockquote>
<p>SQL에서 사용하는 조인의 종류는 아니고 JPQL에서 성능 최적화를 위해 제공하는 기능으로 기본 <code>JOIN</code>은 연관된 엔티티의 정보를 가져오지 않고, 단순히 조건에 맞는 데이터를 필터링하는 역할을 한다. 반면, <code>JOIN FETCH</code>는 관련된 엔티티도 함께 조회(Fetching) 하여 데이터베이스에서 한 번에 불러온다.</p>
</blockquote>
</li>
<li><p>항상 fetch join으로 해결하면 되나요? <code>NO</code></p>
<p>  아니다! fetch join에도 단점이 존재하기 때문에 특정 상황에선 사용이 <code>불가능</code>하다</p>
<ol>
<li><p><strong>우리가 설정해놓은 FetchType을 사용할 수 없다.</strong></p>
<p>데이터 호출 시점에 모든 연관 관계 데이터를 가져오기 때문에 FetchType을 lazy로 해놓은 것이 무의미</p>
</li>
<li><p><strong>Pagination이 불가능하다</strong></p>
<p> 하나의 쿼리문으로 가져오다보니 페이징 단위로 데이터를 가져오는 것이 불가능</p>
<p> 실제로 구현하게되면 하나의 쿼리가 나가긴하지만 모든 값을 select해와서 인메모리에 저장하고 application단에서 필요한 페이지만큼만 변환해줌</p>
<pre><code class="language-java"> WARN 79170 --- [    Test worker] o.h.h.internal.ast.QueryTranslatorImpl   : HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!</code></pre>
</li>
</ol>
</li>
</ul>
</br>


<h3 id="2--entity-graph">2.  <code>@Entity Graph</code></h3>
<p>@EntityGraph 의 attributePaths에 같이 조회할 연관 엔티티명을 적어서 사용한다. ,(콤마)를 통해 여러 개를 줄 수도 있다! Fetch join과 동일하게 JPQL을 사용해 Query문을 작성하고 필요한 연관관계를 EntityGraph에 설정한다.</p>
<pre><code class="language-java">@EntityGraph(attributePaths = {&quot;comments&quot;})
@Query(&quot;select DISTINCT a from Article a&quot;)
List&lt;Member&gt; findAllEntityGraph();</code></pre>
<p>이렇게 해주고 결과를 보면 쿼리가 1번만 발생하고 미리 comment와 Order를 조인(outer Join)해서 가져오는 것을 볼 수 있다.</p>
<pre><code class="language-sql">SELECT DISTINCT a.* from article a LEFT JOIN comments c ON a.id = c.article_id
</code></pre>
<blockquote>
</blockquote>
<p>❓ DISTINCT는 무엇인가요?  </p>
<blockquote>
<p>  <strong><code>Fetch Join</code></strong>과 <strong><code>EntityGraph</code></strong>는 공통적으로 카테시안 곱(Cartesian Product)을 하기 때문에 결과가 늘어나서 중복된 결과가 나타날 수 있다. 이때 해결할 수 있는 방법으로 함께 사용해준다!</p>
<blockquote>
<p><strong><code>카테시안 곱을 한다 ?</code></strong> 
: 두 개 이상의 테이블을 조인할 때, <strong>각각의 행이 서로 조합된 모든 경우의 수</strong>를 반환하는 현상으로 <strong>일대다 관계</strong>에서 <strong>조인된 테이블</strong>의 데이터가 여러 개일 경우, 부모 엔티티의 데이터가 중복되어 나타날 수 있음</p>
</blockquote>
</blockquote>
<ol>
<li>JPQL에 <strong><code>DISTINCT</code></strong> 를 추가하여 중복 제거</li>
</ol>
<pre><code class="language-java">@Query(&quot;SELECT DISTINCT a FROM article a JOIN FETCH a.comment&quot;)
List&lt;Member&gt; findAllJoinFetch();
////////////////////////////////////////////////////////////////
@EntityGraph(attributePaths = {&quot;comments&quot;})
@Query(&quot;select DISTINCT a from article a&quot;)
List&lt;Member&gt; findAllEntityGraph();</code></pre>
<ol start="2">
<li>OneToMany 필드 타입을 <strong><code>Set</code></strong>으로 선언하여 중복 제거</li>
</ol>
<pre><code class="language-java">@Entity
public class Article {
@Id @GeneratedValue
private Long id;
    @OneToMany(mappedBy = &quot;article&quot;, fetch = FetchType.LAZY) 
        private Set&lt;Comments&gt; comments = new LinkedHashSet&lt;&gt;();
}</code></pre>
<p>이때 Set을 사용하게 된다면 HashSet으로는 순서가 중요한 데이터에는 순서를 보장할 수 없기 때문에 <code>LinkedHashSet</code>을 사용해야한다!!</p>
  <br>
</aside>

<h3 id="3-hibernate의-batchsize">3. Hibernate의 <strong><code>@BatchSize</code></strong></h3>
<p>JPA 의 성능 개선을 위해 하이버네이트가 제공하는 옵션 중 하나로 org.hibernate.annotations.BatchSize 어노테이션을 사용하면 연관된 엔티티를 조회할 때 지정한 size만큼 SQL의 IN 절을 사용해서 조회한다. </p>
<pre><code class="language-java">@Entity
public class Article {
    @Id @GeneratedValue
    private Long id;
        @BatchSize(size = 100)
        @OneToMany(mappedBy = &quot;article&quot;, fetch = FetchType.LAZY) // 지연 로딩
        private List&lt;Comment&gt; comment = new ArrayList&lt;Comment&gt;();
}</code></pre>
<blockquote>
<p>앞선 <code>FETCH JOIN</code>의 페이징이 불가능한 문제를 해결할 수 있다. </p>
</blockquote>
<p><strong>하지만 <code>주의해야할 점</code>은 batch size에 fetch join을 걸면 안된다.</strong></p>
<p>fetch join이 우선시되어 적용되기 때문에 batch size가 무시되고 fetch join을 인메모리에서 먼저 진행하여 List가 MultipleBagFetchException가 발생하거나, Set을 사용한 경우에는 Pagination의 인메모리 로딩을 진행한다.</p>
<blockquote>
<p><code>MultipleBagFetchException</code>
fetch join을 할 때 ToMany의 경우 한번에 fetch join을 가져오기 때문에 collection join이 2개이상이 될 경우 너무 많은 값이 메모리로 들어와 exception 발생</p>
<blockquote>
<p><code>collection join이 2개이상?</code>
여러 개의 자식 엔티티를 담고 있는 필드가 또 여러개의 자식 엔티티를 담고 있는 경우
예를 들어 User가 여러 개의 Post와 여러 개의 Comment를 가질 수 있는 경우</p>
</blockquote>
</blockquote>
</aside>

<hr>
<h1 id="적용">적용</h1>
<h3 id="현재-상황">현재 상황</h3>
<p>모임에 해당하는 공지사항 전체 조회 API에 적용하여 N+1문제를 직접 해결해보겠다</p>
<pre><code class="language-java">    public List&lt;NoticeListGetByMoimResponse&gt; getNoticeListByMoimId(Long moimId, Long guestId) {

        boolean isAppliedUser = isUserAppliedToMoim(moimId, guestId);

        return noticeList.stream()
                .filter(notice -&gt; canAccessNotice(notice, isAppliedUser))
                .map(oneNotice -&gt; NoticeListGetByMoimResponse.builder()
                        .noticeId(oneNotice.getId())
                        .hostNickName(moim.getHost().getNickname())
                        .hostImageUrl(moim.getHost().getImageUrl())
                        .title(oneNotice.getTitle())
                        .content(oneNotice.getContent())
                        .date(DateTimeUtil.refineDateAndTime(oneNotice.getCreatedAt()))
                        .noticeImageUrl(oneNotice.getImageUrl())
                        .hostId(moim.getHost().getId())
                        .commentNumber(oneNotice.getComments().size())
                        .isPrivate(oneNotice.isPrivate())
                        .build())
                .collect(Collectors.toList());
    }</code></pre>
<pre><code class="language-jsx">List&lt;Notice&gt; findNoticesByMoimIdOrderByCreatedAtDesc(Long moimId);</code></pre>
<p>이렇게 구현을 진행했고 요청 시 아래와 같은 SQL문이 작성됐다.</p>
<pre><code class="language-SQL">Hibernate: 
    select
        g1_0.id,
        g1_0.created_at,
        g1_0.image_url,
        g1_0.nickname,
        g1_0.updated_at,
        g1_0.user_id 
    from
        guests g1_0 
    where
        g1_0.user_id=?
Hibernate: 
    select
        n1_0.id,
        n1_0.content,
        n1_0.created_at,
        n1_0.image_url,
        n1_0.is_private,
        n1_0.moim_id,
        n1_0.title,
        n1_0.updated_at 
    from
        notices n1_0 
    where
        n1_0.moim_id=?
Hibernate: 
    select
        m1_0.id,
        m1_0.account_list,
        m1_0.category_list,
        m1_0.created_at,
        m1_0.date_list,
        m1_0.description,
        m1_0.fee,
        m1_0.host_id,
        m1_0.image_list,
        m1_0.is_offline,
        m1_0.max_guest,
        m1_0.moim_state,
        m1_0.question_list,
        m1_0.spot,
        m1_0.title,
        m1_0.updated_at 
    from
        moims m1_0 
    where
        m1_0.id=?
Hibernate: 
    select
        h1_0.id,
        h1_0.created_at,
        h1_0.description,
        h1_0.image_url,
        h1_0.link,
        h1_0.nickname,
        h1_0.updated_at,
        h1_0.user_id,
        h1_0.user_keyword 
    from
        hosts h1_0 
    where
        h1_0.id=?
Hibernate: 
    select
        g1_0.id,
        g1_0.created_at,
        g1_0.image_url,
        g1_0.nickname,
        g1_0.updated_at,
        g1_0.user_id 
    from
        guests g1_0 
    where
        g1_0.id=?
Hibernate: 
    select
        ms1_0.id 
    from
        moim_submissions ms1_0 
    where
        ms1_0.moim_id=? 
        and ms1_0.guest_id=? 
    fetch
        first ? rows only
Hibernate: 
    select
        ms1_0.id,
        ms1_0.account_list,
        ms1_0.answer_list,
        ms1_0.created_at,
        ms1_0.guest_id,
        ms1_0.moim_id,
        ms1_0.moim_submission_state,
        ms1_0.updated_at 
    from
        moim_submissions ms1_0 
    where
        ms1_0.moim_id=? 
        and ms1_0.guest_id=?
Hibernate: 
    select
        c1_0.notice_id,
        c1_0.id,
        c1_0.comment_content,
        c1_0.user_id,
        c1_0.created_at,
        c1_0.updated_at 
    from
        comments c1_0 
    where
        c1_0.notice_id=?
Hibernate: 
    select
        c1_0.notice_id,
        c1_0.id,
        c1_0.comment_content,
        c1_0.user_id,
        c1_0.created_at,
        c1_0.updated_at 
    from
        comments c1_0 
    where
        c1_0.notice_id=?
Hibernate: 
    select
        c1_0.notice_id,
        c1_0.id,
        c1_0.comment_content,
        c1_0.user_id,
        c1_0.created_at,
        c1_0.updated_at 
    from
        comments c1_0 
    where
        c1_0.notice_id=?
Hibernate: 
    select
        c1_0.notice_id,
        c1_0.id,
        c1_0.comment_content,
        c1_0.user_id,
        c1_0.created_at,
        c1_0.updated_at 
    from
        comments c1_0 
    where
        c1_0.notice_id=?
Hibernate: 
    select
        c1_0.notice_id,
        c1_0.id,
        c1_0.comment_content,
        c1_0.user_id,
        c1_0.created_at,
        c1_0.updated_at 
    from
        comments c1_0 
    where
        c1_0.notice_id=?</code></pre>
<ul>
<li><p><strong>쿼리 최적화 전</strong> (총 쿼리 수: 12)</p>
<ol>
<li><p><code>guests</code> 테이블 조회 (1회)</p>
</li>
<li><p><code>notices</code> 테이블에서 공지사항 조회 (1회)</p>
</li>
<li><p><code>moims</code> 테이블에서 모임 정보 재조회 (1회)</p>
</li>
<li><p><code>hosts</code> 테이블 조회 (1회)</p>
</li>
<li><p><code>guests</code> 테이블에서 다른 게스트 정보 재조회 (1회)</p>
</li>
<li><p><code>moim_submissions</code> 테이블 조회 (1회)</p>
</li>
<li><p><code>moim_submissions</code> 테이블에서 다시 조회 (1회)</p>
</li>
<li><p><code>comments</code> 테이블에서 공지사항에 대한 댓글 수 조회 (5회)</p>
</br></li>
</ol>
</li>
<li><p>문제 포인트</p>
<blockquote>
<p><strong>호스트 정보 조회</strong> (<code>oneNotice.getMoim().getHost().getNickname()</code> 및 <code>moim.getHost().getImageUrl()</code>):
  &amp; <strong>댓글 수 조회</strong> (<code>oneNotice.getComments().size()</code>):</p>
</blockquote>
<p> 각 공지사항(Notice)에 대해 호스트, 댓글 정보를 가져오는데, 이 과정에서 호스트, 댓글 테이블에 대해 쿼리가 여러 번 실행됐다. 이는 각 공지사항에 대해 매번 정보가 개별적으로 조회되기 때문이다
즉, <strong>N+1 발생!</strong></p>
</li>
</ul>
</br>

<blockquote>
<p>근데 왜 HOST는 1번의 쿼리가 나가나요?</p>
<p>  <strong>호스트(Host)와 모임(Moim)의 관계</strong>:
    -  모임(Moim)과 호스트(Host)는 <code>1:1</code> 관계다. 즉, 하나의 모임에는 하나의 호스트만 존재한다.
    - <code>JOIN FETCH m.host</code>로 모임의 호스트 정보를 한 번만 가져오면 된다. 모임과 관련된 공지사항이 여러 개라 하더라도, 동일한 모임에 속한 공지사항이므로 동일한 호스트 정보가 공유된다.
    - 따라서 <code>Host</code>에 대한 쿼리는 한 번만 실행된다.</p>
<p>  <strong>댓글(Comment)과 공지(Notice)의 관계</strong>:
    - 공지사항 하나에 여러 개의 댓글이 달릴 수 있으므로 <code>1:N</code> 관계다.
    - <code>LEFT JOIN FETCH n.comments</code>는 각 공지사항에 달린 댓글을 모두 조회한다. 이 경우 공지사항마다 여러 댓글이 있을 수 있어, 공지사항마다 중복된 댓글 조회가 발생할 수 있다.
    - 따라서 공지사항마다 댓글에 대해 여러 쿼리가 발생하여 공지사항 수만큼(N번) 쿼리가 발생한다.</p>
</blockquote>
</br>


<hr>
<h3 id="join-fetch-적용"><code>JOIN FETCH</code> 적용</h3>
<pre><code class="language-java">    @Query(&quot;SELECT DISTINCT n FROM Notice n JOIN FETCH n.moim m JOIN FETCH m.host &quot; +
            &quot;LEFT JOIN FETCH n.comments WHERE m.id = :moimId ORDER BY n.createdAt DESC&quot;)
    List&lt;Notice&gt; findNoticesByMoimId(Long moimId);</code></pre>
<p>위와같이 최적화를 위해 <code>JOIN FETCH</code>를 적용해준다.</p>
<pre><code class="language-jsx">Hibernate: 
    select
        g1_0.id,
        g1_0.created_at,
        g1_0.image_url,
        g1_0.nickname,
        g1_0.updated_at,
        g1_0.user_id 
    from
        guests g1_0 
    where
        g1_0.user_id=?
Hibernate: 
    select
        distinct n1_0.id,
        c1_0.notice_id,
        c1_0.id,
        c1_0.comment_content,
        c1_0.user_id,
        c1_0.created_at,
        c1_0.updated_at,
        n1_0.content,
        n1_0.created_at,
        n1_0.image_url,
        n1_0.is_private,
        m1_0.id,
        m1_0.account_list,
        m1_0.category_list,
        m1_0.created_at,
        m1_0.date_list,
        m1_0.description,
        m1_0.fee,
        h1_0.id,
        h1_0.created_at,
        h1_0.description,
        h1_0.image_url,
        h1_0.link,
        h1_0.nickname,
        h1_0.updated_at,
        h1_0.user_id,
        h1_0.user_keyword,
        m1_0.image_list,
        m1_0.is_offline,
        m1_0.max_guest,
        m1_0.moim_state,
        m1_0.question_list,
        m1_0.spot,
        m1_0.title,
        m1_0.updated_at,
        n1_0.title,
        n1_0.updated_at 
    from
        notices n1_0 
    join
        moims m1_0 
            on m1_0.id=n1_0.moim_id 
    join
        hosts h1_0 
            on h1_0.id=m1_0.host_id 
    left join
        comments c1_0 
            on n1_0.id=c1_0.notice_id 
    where
        n1_0.moim_id=? 
    order by
        n1_0.created_at desc
Hibernate: 
    select
        m1_0.id,
        m1_0.account_list,
        m1_0.category_list,
        m1_0.created_at,
        m1_0.date_list,
        m1_0.description,
        m1_0.fee,
        m1_0.host_id,
        m1_0.image_list,
        m1_0.is_offline,
        m1_0.max_guest,
        m1_0.moim_state,
        m1_0.question_list,
        m1_0.spot,
        m1_0.title,
        m1_0.updated_at 
    from
        moims m1_0 
    where
        m1_0.id=?
Hibernate: 
    select
        g1_0.id,
        g1_0.created_at,
        g1_0.image_url,
        g1_0.nickname,
        g1_0.updated_at,
        g1_0.user_id 
    from
        guests g1_0 
    where
        g1_0.id=?
Hibernate: 
    select
        ms1_0.id 
    from
        moim_submissions ms1_0 
    where
        ms1_0.moim_id=? 
        and ms1_0.guest_id=? 
    fetch
        first ? rows only
Hibernate: 
    select
        ms1_0.id,
        ms1_0.account_list,
        ms1_0.answer_list,
        ms1_0.created_at,
        ms1_0.guest_id,
        ms1_0.moim_id,
        ms1_0.moim_submission_state,
        ms1_0.updated_at 
    from
        moim_submissions ms1_0 
    where
        ms1_0.moim_id=? 
        and ms1_0.guest_id=?</code></pre>
</br>

<ul>
<li><p><strong>최적화 후 쿼리</strong> (총 쿼리 수: 6)</p>
<ul>
<li><p>제거된 쿼리</p>
<pre><code class="language-SQL">  Hibernate: 
      select
          h1_0.id,
          h1_0.created_at,
          h1_0.description,
          h1_0.image_url,
          h1_0.link,
          h1_0.nickname,
          h1_0.updated_at,
          h1_0.user_id,
          h1_0.user_keyword 
      from
          hosts h1_0 
      where
          h1_0.id=?</code></pre>
<pre><code class="language-SQL">  Hibernate: 
      select
          c1_0.notice_id,
          c1_0.id,
          c1_0.comment_content,
          c1_0.user_id,
          c1_0.created_at,
          c1_0.updated_at 
      from
          comments c1_0 
      where
          c1_0.notice_id=?</code></pre>
</li>
</ul>
<ol>
<li><strong><code>Guests</code> 테이블 조회</strong> (1회)</li>
<li><strong><code>Notices</code>, <code>Moims</code>, <code>Hosts</code>, <code>Comments</code>를 조회</strong> (1회)</li>
<li><strong><code>Moims</code> 테이블 재조회</strong> (1회)</li>
<li><strong><code>Guests</code> 테이블에서 ID로 조회</strong> (1회)</li>
<li><strong><code>Moim Submissions</code> 테이블 조회</strong> (1회)</li>
<li><strong><code>Moim Submissions</code> 테이블 재조회</strong> (1회)</li>
</ol>
</li>
</ul>
</br>
---

<h3 id="join-fetch-vs-left-join-fetch"><code>JOIN FETCH</code> vs <code>LEFT JOIN FETCH</code></h3>
<ul>
<li><strong><code>JOIN FETCH</code></strong><ul>
<li><strong><code>JOIN FETCH</code>의 동작</strong>: 연관된 엔티티가 <strong>반드시</strong> 존재해야 한다. 즉, <code>JOIN FETCH</code>는 조인된 테이블에 일치하는 데이터가 있어야만 결과가 반환된다.</li>
</ul>
</li>
<li><strong><code>LEFT JOIN FETCH</code></strong><ul>
<li><strong><code>LEFT JOIN</code>과의 차이</strong>: <code>LEFT JOIN FETCH</code>는 조인된 테이블에 <strong>데이터가 없더라도</strong> 주 테이블의 데이터를 반환한다. 즉, 왼쪽 테이블(<code>Notice</code>)의 데이터는 모두 반환하고, 관련된 엔티티(<code>Comments</code> 등)가 없는 경우에는 <code>null</code> 값을 반환한다.</li>
<li><strong><code>LEFT JOIN FETCH</code>의 동작</strong>: 연관된 엔티티가 <strong>없어도</strong> 상관없이 주 엔티티의 데이터를 가져오고, 관련된 엔티티는 <code>null</code>로 채워질 수 있다.</li>
</ul>
</li>
</ul>
<blockquote>
</blockquote>
<p>없어도 되는 관계가 허용될 경우(공지사항에 댓글이 안달려도 될 경우)엔 무조건 <code>LEFT JOIN FETCH</code>로 N+1을 해결하자!</p>
</aside>

</br>

<h1 id="결론">결론</h1>
<blockquote>
<p>N+1문제는 데이터가 많이 쌓일수록 성능에 많은 차이를 발생시킬 것이다. 
안정성 있는 서버를 위해 신경써서 구현하자.</p>
</blockquote>
<ul>
<li><p>참고 레퍼런스</p>
<ul>
<li><p><a href="https://velog.io/@jinyoungchoi95/JPA-%EB%AA%A8%EB%93%A0-N1-%EB%B0%9C%EC%83%9D-%EC%BC%80%EC%9D%B4%EC%8A%A4%EA%B3%BC-%ED%95%B4%EA%B2%B0%EC%B1%85">JPA-모든-N1-발생-케이스과-해결책</a></p>
</li>
<li><p><a href="https://velog.io/@xogml951/JPA-N1-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EC%B4%9D%EC%A0%95%EB%A6%AC">JPA-N1-문제-해결-총정리</a></p>
</li>
<li><p><a href="https://s-y-130.tistory.com/184">N+1 문제 원인 및 해결방법</a></p>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[pytest 사용법]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/pytest</link>
            <guid>https://velog.io/@bo-ram-bo-ram/pytest</guid>
            <pubDate>Tue, 08 Oct 2024 01:55:40 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/54732032-fdf7-449c-952d-44099e3e3ded/image.png" alt=""></p>
<blockquote>
<p>pytest로 테스트 코드를 작성해보자</p>
</blockquote>
<h2 id="1-pytest">1. <strong>pytest</strong></h2>
<ul>
<li><code>pytest</code>는 Python의 테스팅 프레임워크 중 하나로, 단위 테스트(unit tests)부터 기능 테스트(functional tests)까지 다양한 종류의 테스트를 쉽게 작성할 수 있습니다.</li>
<li>간결하고 강력한 테스트 코드를 작성할 수 있도록 다양한 기능을 제공하며, 플러그인 시스템을 통해 확장이 가능합니다.</li>
<li><strong>테스트는 독립적</strong>이어야 하고 각 테스트는 다른 테스트의 결과에 영향을 받지 않아야 합니다.</li>
<li>테스트 함수 이름은 <strong>명확</strong>하고, 어떤 동작을 테스트하는지 알 수 있게 작성합니다.</li>
<li><strong>Fixtures</strong>를 활용하여 설정 코드를 재사용하고 가독성을 높입니다.</li>
<li><code>pytest</code>는 <code>test_</code>로 시작하거나 <code>_test</code>로 끝나는 함수들을 자동으로 발견하고 실행합니다.</li>
</ul>
<hr>
<h2 id="2-설치-방법">2. <strong>설치 방법</strong></h2>
<pre><code class="language-bash">pip install pytest</code></pre>
<hr>
<h2 id="3-테스트">3. 테스트</h2>
<h3 id="31-작성">3.1 작성</h3>
<ul>
<li>테스트 함수는 <code>test_</code>로 시작해야 합니다.</li>
<li><code>assert</code> 구문을 사용하여 테스트가 통과되었는지 여부를 확인합니다.</li>
</ul>
<p>예제 코드:</p>
<pre><code class="language-python"># test_sample.py

def test_addition():
    assert 1 + 1 == 2

def test_subtraction():
    assert 2 - 1 == 1</code></pre>
<ul>
<li><p>모듈, 클래스, 함수 단위로 테스트를 구성하여 테스트 코드를 체계화합니다. 이렇게 하면 특정 모듈이나 기능을 선택적으로 테스트할 수 있습니다.</p>
<p>  예제: 모듈 내부의 클래스별로 테스트를 그룹화</p>
<pre><code class="language-python">  python
  코드 복사
  # test_calculator.py
  class TestAddition:
      def test_add_positive(self):
          assert add(1, 2) == 3

      def test_add_negative(self):
          assert add(-1, -1) == -2

  class TestSubtraction:
      def test_subtract(self):
          assert subtract(5, 3) == 2
</code></pre>
</li>
</ul>
<h3 id="32-실행">3.2 실행</h3>
<ul>
<li>테스트를 실행하려면 터미널에서 <code>pytest</code> 명령을 사용합니다.</li>
<li>특정 파일만 테스트하려면:</li>
</ul>
<pre><code class="language-bash">pytest test_sample.py</code></pre>
<ul>
<li><p><strong>테스트 실행 옵션</strong></p>
<ul>
<li><p><code>v</code>: 자세한 테스트 진행 상황 출력</p>
</li>
<li><p><code>q</code>: 간략한 테스트 결과만 출력</p>
</li>
<li><p><code>pytest -k &quot;expression&quot;</code>: 지정한 키워드와 일치하는 테스트만 실행. 예: <code>k &quot;addition&quot;</code>.</p>
</li>
<li><p><code>-maxfail=N</code>: N개의 테스트가 실패하면 실행 중단.</p>
</li>
<li><p><code>-tb=short</code>: 에러 발생 시 간략한 traceback 표시.</p>
<ul>
<li><code>pytest --pdb</code>: 테스트 실패 시 디버깅 모드로 진입.</li>
<li><code>pytest --fixtures</code>: 사용할 수 있는 fixture 목록 표시.</li>
<li><code>pytest --durations=N</code>: 실행 시간이 오래 걸리는 상위 N개의 테스트를 표시.</li>
</ul>
</li>
<li><p><code>-junitxml</code> 옵션을 사용하면 테스트 결과를 XML 형식으로 저장.</p>
<p>예제:</p>
<pre><code class="language-bash">pytest -v --maxfail=2 --tb=short</code></pre>
<pre><code class="language-bash">pytest --junitxml=report.xml</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="33-해석">3.3 <strong>해석</strong></h3>
<ul>
<li><code>.</code>: 테스트가 성공한 경우</li>
<li><code>F</code>: 테스트가 실패한 경우</li>
<li>테스트 결과 출력에서 실패한 테스트의 상세 내용과 어디에서 오류가 발생했는지 볼 수 있습니다.</li>
</ul>
<hr>
<h2 id="4-기능">4. 기능</h2>
<h3 id="41-pytestmarkparametrize">4.1 <code>@pytest.mark.parametrize</code></h3>
<ul>
<li>동일한 테스트 함수에 여러 입력 값을 테스트하려면 <code>@pytest.mark.parametrize</code>를 사용합니다.</li>
</ul>
<p>예제:</p>
<pre><code class="language-python">import pytest

@pytest.mark.parametrize(&quot;a, b, expected&quot;, [(1, 2, 3), (2, 3, 5), (3, 5, 8)])
def test_addition(a, b, expected):
    assrt a + b == expected</code></pre>
<h3 id="42-pytestraises">4.2 <code>pytest.raises</code></h3>
<ul>
<li>예외가 발생하는지 테스트하려면 <code>pytest.raises</code> 컨텍스트 관리자를 사용합니다.</li>
</ul>
<p>예제:</p>
<pre><code class="language-python">import pytest

def test_divide_by_zero():
    with pytest.raises(ZeroDivisionError):
        1 / 0</code></pre>
<h3 id="43pytestfixture">4.3<code>@pytest.fixture</code></h3>
<ul>
<li><strong>Fixtures</strong>는 반복적으로 사용하는 준비 코드를 재사용할 수 있게 해줍니다.</li>
<li><code>@pytest.fixture</code> 데코레이터를 사용하며, 테스트 함수에 인자로 넘겨주어 사용할 수 있습니다.</li>
</ul>
<p>예제:</p>
<pre><code class="language-python">import pytest

@pytest.fixture
def sample_list():
    return [1, 2, 3, 4, 5]

def test_list_sum(sample_list):
    assert sum(sample_list) == 15</code></pre>
<ul>
<li><p><strong>Fixture의 스코프 조절</strong></p>
<ul>
<li><p><code>scope</code> 매개변수를 사용하여 fixture의 생명주기를 조절할 수 있습니다 (<code>function</code>, <code>module</code>, <code>class</code>, <code>session</code>).</p>
</li>
<li><p>데이터베이스 연결 같은 자원을 효율적으로 사용하려면 적절한 스코프를 선택합니다.</p>
<pre><code class="language-python">@pytest.fixture(scope=&quot;module&quot;)
def db_connection():
  conn = create_db_connection()
  yield conn
  conn.close()</code></pre>
</li>
</ul>
</li>
<li><p><strong>Fixture를 다른 Fixture에서 사용하기</strong></p>
<ul>
<li><p>복잡한 테스트 환경에서는 여러 fixture를 조합하여 사용할 수 있습니다.</p>
<pre><code class="language-python">@pytest.fixture
def user_data():
  return {&quot;name&quot;: &quot;John&quot;, &quot;age&quot;: 30}

@pytest.fixture
def user(user_data):
  return User(**user_data)

def test_user_age(user):
  assert user.age == 30</code></pre>
</li>
<li><p>서로 연관된 fixture를 분리하여 모듈화하면 테스트를 더욱 유연하고 가독성 있게 만들 수 있습니다.</p>
</li>
</ul>
</li>
</ul>
<h3 id="44-pytestmark">4.4 <code>@pytest.mark</code></h3>
<ul>
<li><code>@pytest.mark.&lt;name&gt;</code>를 사용해 테스트를 그룹화하고 특정 그룹만 실행할 수 있습니다. 예를 들어, 느린 테스트나 특정 조건이 필요한 테스트를 분리할 수 있습니다.</li>
</ul>
<p>예제:</p>
<pre><code class="language-python">import pytest

@pytest.mark.slow
def test_large_computation():
    assert large_computation() == expected_value</code></pre>
<p>실행 시 특정 마커의 테스트만 실행:</p>
<pre><code class="language-bash">pytest -m slow</code></pre>
<ul>
<li><p>Custom Marker</p>
<ul>
<li><p>프로젝트 수준에서 마커를 정의하여 특정 유형의 테스트를 구분하고 관리할 수 있습니다. <code>pytest.ini</code> 또는 <code>pyproject.toml</code> 파일에서 마커를 정의합니다.</p>
<p><code>pytest.ini</code> 파일 예제:</p>
<pre><code>markers =
  slow: 느린 테스트
  db: 데이터베이스 관련 테스트</code></pre></li>
</ul>
</li>
</ul>
<hr>
<h2 id="5-플러그인">5. <strong>플러그인</strong></h2>
<ul>
<li><p><code>pytest</code>는 플러그인 시스템을 통해 확장 가능합니다. 일부 유용한 플러그인은 다음과 같습니다:</p>
<ul>
<li><p><code>pytest-cov</code>: 테스트 코드의 커버리지를 확인할 수 있습니다.</p>
<pre><code class="language-bash">  pip install pytest-cov
  pytest --cov=my_module</code></pre>
</li>
<li><p><code>pytest-xdist</code>: 멀티코어를 활용하여 테스트 병렬 실행을 지원합니다.</p>
<pre><code class="language-bash">  pip install pytest-xdist
  pytest -n auto</code></pre>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[unit test - mock]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/unit-test-mock</link>
            <guid>https://velog.io/@bo-ram-bo-ram/unit-test-mock</guid>
            <pubDate>Wed, 02 Oct 2024 09:23:17 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/745c2462-1e76-4de2-a0a9-47078c917c78/image.png" alt=""></p>
<h2 id="1-mock">1. <strong>Mock</strong></h2>
<ul>
<li><strong>Mock</strong>은 실제 객체 대신 가짜 객체를 사용해 테스트할 수 있는 도구.</li>
<li>네트워크 요청, DB 연결 등 <strong>외부 종속성</strong>을 빠르게 처리할 수 있음.</li>
</ul>
<hr>
<h2 id="2-mock를-사용하는-이유">2. <strong>Mock를 사용하는 이유</strong></h2>
<h3 id="21-오래-걸리는-작업을-빠르게-처리">2.1 <strong>오래 걸리는 작업을 빠르게 처리</strong></h3>
<ul>
<li>문제: <code>sleep</code>, HTTP 요청 같은 작업이 실제 실행 시 시간이 오래 걸림.</li>
<li>해결: Mock 처리로 빠르게 테스트 완료, 정확한 결과 확인 가능.</li>
</ul>
<pre><code class="language-python">from unittest.mock import patch
@patch(&#39;time.sleep&#39;, return_value=None)  # 즉시 반환
def test_function(mock_sleep):
    time.sleep(10)  # 실제로 sleep하지 않음
</code></pre>
<h3 id="22-외부-종속성으로부터-독립적인-테스트">2.2 <strong>외부 종속성으로부터 독립적인 테스트</strong></h3>
<ul>
<li>문제: 함수 A에 의존하는 함수 B를 테스트하려면 A의 동작에 영향받음.</li>
<li>해결: A를 Mock으로 대체하여 B만 독립적으로 테스트 가능.</li>
</ul>
<pre><code class="language-python">@patch(&#39;__main__.a&#39;, return_value=&quot;Mock A&quot;)
def test_b(mock_a):
    result = b()
    assert result == &quot;Mock A and I&#39;m B&quot;
</code></pre>
<hr>
<h2 id="3-mock의-사용">3. <strong>Mock의 사용</strong></h2>
<h3 id="31-주요-활용-예시"><strong>3.1 주요 활용 예시</strong></h3>
<ul>
<li><strong>네트워크 요청</strong>: HTTP 요청을 Mock으로 대체해 가짜 응답 반환.</li>
<li><strong>시간 지연 작업</strong>: <code>time.sleep()</code>을 Mock 처리해 지연 없이 테스트.</li>
<li><strong>DB 연동</strong>: 실제 DB에 연결하지 않고 가짜 응답을 사용.</li>
</ul>
<h3 id="32-mock-객체-사용-단계"><strong>3.2 Mock 객체 사용 단계</strong></h3>
<ol>
<li><strong>종속성 식별</strong>: 네트워크, 외부 함수, 시간 지연 등.</li>
<li><strong>Mock 처리</strong>: <code>patch()</code> 또는 <code>MagicMock()</code>으로 종속성 Mock 처리.</li>
<li><strong>검증</strong>: Mock 호출 및 반환 값 검증.</li>
</ol>
<h3 id="33-mock을-사용할-때-주의할-점">3.3 <strong>Mock을 사용할 때 주의할 점</strong></h3>
<ul>
<li><strong>과도한 Mock 사용</strong>: 모든 것을 Mock으로 처리하면 테스트가 실제 시스템 동작을 제대로 반영하지 못할 수 있습니다.</li>
<li><strong>Mock이 테스트하는 코드와 너무 결합</strong>: Mock을 설정할 때 특정 내부 구현에 의존하게 되면, 실제 코드 변경 시 Mock 설정도 변경해야 할 수 있습니다</li>
<li><strong>실제 의존성을 테스트하지 않음</strong> : Mock은 외부 종속성을 제거하는 데 유용하지만, 가짜 객체만을 테스트하면 실제 환경에서의 문제가 발견되지 않을 수 있습니다. 따라서 Mock을 사용하지 않은 통합 테스트도 함께 고려해야 합니다.</li>
<li><strong>속도 개선</strong>: Mock을 사용하면 오래 걸리는 작업을 빠르게 대체할 수 있지만, 실제 의존성을 완전히 제거해선 안 됩니다.</li>
</ul>
<h3 id="34--vs-magicmock">3.4  vs <strong><code>MagicMock</code></strong></h3>
<p><code>MagicMock</code>은 <code>Mock</code>의 확장 버전으로, 특별한 매직 메서드(<code>__getitem__</code>, <code>__setitem__</code> 등)를 자동으로 처리하는 기능을 포함합니다.</p>
<pre><code class="language-python">from unittest.mock import MagicMock

mock = MagicMock()
mock[0] = &quot;value&quot;
assert mock[0] == &quot;value&quot;</code></pre>
<hr>
<h2 id="4--mock-객체의-주요-기능">4.  <strong>Mock 객체의 주요 기능</strong></h2>
<h3 id="41-메서드-및-속성-대체">4.1 <strong>메서드 및 속성 대체</strong></h3>
<p>Mock 객체는 메서드나 속성 호출을 대체할 수 있습니다. 이는 테스트 시 실제 객체를 호출하지 않도록 하고, 원하는 동작을 정의할 수 있습니다.</p>
<pre><code class="language-python">mock = Mock()

# 메서드 호출 시 원하는 값을 반환하도록 설정
mock.method.return_value = &quot;mocked result&quot;
assert mock.method() == &quot;mocked result&quot;</code></pre>
<h3 id="42-특정-예외-발생시키기">4.2 <strong>특정 예외 발생시키기</strong></h3>
<p>테스트 중 특정 상황에서 예외를 발생시키고 싶은 경우 <code>side_effect</code> 속성을 사용할 수 있습니다.</p>
<pre><code class="language-python">mock = Mock()
mock.method.side_effect = ValueError(&quot;Invalid value&quot;)
try:
    mock.method()
except ValueError as e:
    assert str(e) == &quot;Invalid value&quot;</code></pre>
<h3 id="43-호출-기록-추적">4.3 <strong>호출 기록 추적</strong></h3>
<p>Mock 객체는 호출 기록을 자동으로 저장하므로, 테스트 중 어떤 메서드가 호출되었는지, 몇 번 호출되었는지 검증할 수 있습니다.</p>
<pre><code class="language-python">mock = Mock()
mock.method(1, 2, 3)

# 메서드가 호출된 적이 있는지 확인
mock.method.assert_called()

# 호출 시 특정 인자로 호출되었는지 확인
mock.method.assert_called_with(1, 2, 3)

# 호출 횟수 확인
assert mock.method.call_count == 1
</code></pre>
<hr>
<h2 id="5-mock-객체의-속성">5. <strong>Mock 객체의 속성</strong></h2>
<ul>
<li><code>return_value</code>: Mock된 함수나 메서드가 호출되었을 때 반환되는 값을 설정합니다.</li>
<li><code>side_effect</code>: 함수 호출 시 예외를 발생시키거나 호출된 값에 따라 다른 동작을 정의할 수 있습니다.</li>
<li><code>call_count</code>: Mock된 함수가 호출된 횟수를 반환합니다.</li>
<li><code>assert_called_with()</code>: 특정 인자로 호출되었는지 확인할 수 있습니다.</li>
<li><code>assert_not_called()</code>: 호출되지 않았는지 확인할 수 있습니다.</li>
</ul>
<hr>
<h2 id="6-patch의-추가-활용">6. <strong><code>patch</code>의 추가 활용</strong></h2>
<h3 id="61-patch와-context-manager">6.1 <strong>patch와 context manager</strong></h3>
<p><code>patch()</code>는 컨텍스트 관리자로 사용할 수 있어, 특정 블록 내에서만 Mock 처리를 적용할 수 있습니다.</p>
<pre><code class="language-python">from unittest.mock import patch

def some_function():
    return time.time()

# 특정 함수에서만 time.time()을 Mock 처리
with patch(&#39;time.time&#39;, return_value=12345):
    assert some_function() == 12345

# 블록 밖에서는 다시 원래 함수가 동작
assert some_function() != 12345</code></pre>
<h3 id="62-클래스와-메서드-mock">6.2 <strong>클래스와 메서드 Mock</strong></h3>
<p>클래스 내부의 특정 메서드를 Mock 처리하는 것도 가능합니다.</p>
<pre><code class="language-python">class MyClass:
    def method(self):
        return &quot;original result&quot;

with patch.object(MyClass, &#39;method&#39;, return_value=&quot;mocked result&quot;):
    obj = MyClass()
    assert obj.method() == &quot;mocked result&quot;</code></pre>
<ul>
<li><p>참고 레퍼런스</p>
<p>  <a href="https://velog.io/@heyoni/mock">https://velog.io/@heyoni/mock</a>
  <a href="https://www.daleseo.com/python-unittest-mock/">https://www.daleseo.com/python-unittest-mock/</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Observer Pattern]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/Observer-Pattern</link>
            <guid>https://velog.io/@bo-ram-bo-ram/Observer-Pattern</guid>
            <pubDate>Fri, 20 Sep 2024 05:26:31 GMT</pubDate>
            <description><![CDATA[<h1 id="배경">배경</h1>
<blockquote>
<p>인턴활동을 하며 실 업무에 들어가기 위해 기존 코드를 학습하는 과정 중에 있다. 기존 코드를 학습하기 위해 기존 코드에 사용되어있는 디자인 패턴을 학습하려고 한다. 오늘은 옵저버 패턴이 사용된 부분을 이해하기 위해 Oberver Pattern을 학습해보았다.</p>
</blockquote>
<hr>
<h1 id="주제">주제</h1>
<blockquote>
<p>Oberver Pattern</p>
</blockquote>
<hr>
<h1 id="내용">내용</h1>
<h2 id="의미">의미</h2>
<blockquote>
<p>옵저버 패턴(Observer Pattern) : 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고, 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의</p>
</blockquote>
<p>옵저버 패턴(Observer Pattern)은 객체 간의 <strong>일대다(One-to-Many)</strong> 의존 관계를 정의하는 디자인 패턴입니다. 한 객체의 상태가 변경되면, 그 상태에 의존하는 다른 객체들(옵저버)에게 자동으로 통지되고, 그 결과 이들 객체의 상태도 자동으로 갱신됩니다. 주로 <strong>이벤트 기반 시스템</strong>에서 많이 사용됩니다.</p>
<hr>
<div style="display: flex;">
  <div style="flex: 1; padding-right: 10px;">
    <img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/4a699cab-6b74-4268-b712-e646e21d03a6/image.png" alt="이미지1" width="100%" />
  </div>
  <div style="flex: 1; padding-left: 10px;">
    <img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/79aa3c94-a76c-4857-9da3-af2413fe2987/image.png" alt="이미지2" width="100%" />
  </div>
</div>



<p>간단한 예로, 아파트 중개를 생각해볼 수 있습니다. 사용자가 특정 아파트에 관심이 있을 때, 매번 방이 있는지 없는지를 확인하기 위해 지속적으로 중개업체에 연락을 해야 한다면 비효율적일 것입니다. 대신, 옵저버 패턴을 적용하면, 사용자는 한 번만 관심 아파트를 등록해두면 중개업체(주제 객체)가 방이 생길 때 알아서 연락을 해줍니다.</p>
<h3 id="옵저버-패턴-전과-후-비교">옵저버 패턴 전과 후 비교</h3>
<p><strong>옵저버 패턴 적용 전 )</strong></p>
<ul>
<li>사용자가 반복적으로 &quot;방 있나요?&quot;라고 질문해야 합니다.</li>
</ul>
<p><strong>옵저버 패턴 적용 후 )</strong> </p>
<ul>
<li>사용자는 한 번만 관심을 등록하면, 중개업체가 방이 생길 때 알아서 연락을 줍니다.</li>
</ul>
<h3 id="옵저버-패턴의-세-가지-핵심-단계">옵저버 패턴의 세 가지 핵심 단계</h3>
<ol>
<li><strong>등록:</strong> 사용자가 중개업체에 특정 아파트에 관심이 있다고 등록합니다.</li>
<li><strong>해제:</strong> 더 이상 아파트에 관심이 없으면 등록을 해제할 수 있습니다.</li>
<li><strong>알림:</strong> 방이 생기면 중개업체가 사용자에게 자동으로 알립니다.</li>
</ol>
<hr>
<h2 id="옵저버-패턴의-구조">옵저버 패턴의 구조</h2>
<p>옵저버 패턴의 구조는 크게 두 가지 주요 인터페이스로 나뉩니다: <strong>주제(Subject)</strong> 와 <strong>옵저버(Observer)</strong>.</p>
<h3 id="1-주제subject-인터페이스">1. 주제(Subject) 인터페이스</h3>
<p><code>Subject</code> 인터페이스는 옵저버를 등록, 제거하고 상태 변경 시 옵저버에게 알리는 역할을 담당합니다. 주제는 상태가 변경될 때마다 옵저버에게 상태 변경 사실을 통지합니다.</p>
<pre><code class="language-java">
interface Subject {
    void registerObserver(Observer o); // 옵저버 등록
    void removeObserver(Observer o);   // 옵저버 삭제
    void notifyObservers();            // 옵저버에게 업데이트 알림
}

class SubjectImpl implements Subject {
    private List&lt;Observer&gt; observers = new ArrayList&lt;&gt;();
    private String state;

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(state);
        }
    }

    public String getState() {
        return state;
    }

    public void setState(String newState) {
        this.state = newState;
        notifyObservers(); // 상태 변경 시 옵저버들에게 알림
    }
}
</code></pre>
<ul>
<li><code>registerObserver()</code>: 옵저버를 리스트에 등록합니다.</li>
<li><code>removeObserver()</code>: 리스트에서 옵저버를 제거합니다.</li>
<li><code>notifyObservers()</code>: 상태가 변경되면 등록된 모든 옵저버에게 알림을 보냅니다.</li>
</ul>
<h3 id="2-옵저버observer-인터페이스">2. 옵저버(Observer) 인터페이스</h3>
<p><code>Observer</code> 인터페이스는 주제의 상태가 변경되었을 때 이를 감지하고 자신의 상태를 업데이트하는 메소드를 포함합니다. 옵저버는 주제의 상태 변화에 따라 필요한 행동을 정의할 수 있습니다.</p>
<pre><code class="language-java">interface Observer {
    void update(String newState); // 주제의 상태가 변경되었을 때 호출됨
}

class ObserverImpl implements Observer {
    private String observerState;

    @Override
    public void update(String newState) {
        this.observerState = newState;
        display();
    }

    public void display() {
        System.out.println(&quot;옵저버 상태가 업데이트 되었습니다: &quot; + observerState);
    }
}
</code></pre>
<ul>
<li><code>update()</code>: 주제로부터 새로운 상태를 전달받아 자신의 상태를 갱신합니다.</li>
</ul>
<hr>
<h2 id="예제-시나리오-날씨-모니터링-시스템">예제 시나리오: 날씨 모니터링 시스템</h2>
<p>옵저버 패턴은 날씨 모니터링 시스템과 같은 실시간 정보 전달 시스템에서 많이 사용됩니다. 날씨 센서(주제)가 온도, 습도 등의 정보를 업데이트하면, 이를 구독한 디스플레이 장치(옵저버)가 상태를 자동으로 갱신합니다.</p>
<pre><code class="language-java">
class WeatherData implements Subject {
    private List&lt;Observer&gt; observers;
    private float temperature;
    private float humidity;

    public WeatherData() {
        observers = new ArrayList&lt;&gt;();
    }

    public void measurementsChanged() {
        notifyObservers();
    }

    public void setMeasurements(float temperature, float humidity) {
        this.temperature = temperature;
        this.humidity = humidity;
        measurementsChanged();
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature, humidity);
        }
    }
}

class CurrentConditionsDisplay implements Observer {
    private float temperature;
    private float humidity;

    @Override
    public void update(float temperature, float humidity) {
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }

    public void display() {
        System.out.println(&quot;현재 상태: 온도 &quot; + temperature + &quot;도, 습도 &quot; + humidity + &quot;%&quot;);
    }
}
</code></pre>
<p>이 예제에서는 <code>WeatherData</code> 객체가 주제 역할을 하고, <code>CurrentConditionsDisplay</code> 객체가 옵저버 역할을 합니다. 날씨 데이터가 갱신되면, 자동으로 옵저버들에게 알림을 보내고 상태가 업데이트됩니다.</p>
<hr>
<h2 id="옵저버-패턴의-장단점">옵저버 패턴의 장단점</h2>
<h3 id="장점">장점:</h3>
<ol>
<li><strong>느슨한 결합(Loose Coupling):</strong> 주제와 옵저버가 느슨하게 결합되어 있습니다. 즉, 주제는 옵저버가 누구인지 또는 옵저버가 무엇을 하는지 알 필요가 없습니다. 이로 인해 시스템 확장이 용이해집니다.</li>
<li><strong>유연한 설계:</strong> 새로운 옵저버를 쉽게 추가하거나 제거할 수 있으며, 각 옵저버는 독립적으로 동작합니다.</li>
<li><strong>실시간 정보 갱신:</strong> 주제의 상태가 바뀔 때마다 옵저버들이 자동으로 갱신되어, 실시간으로 정보를 반영할 수 있습니다.</li>
</ol>
<h3 id="단점">단점:</h3>
<ol>
<li><strong>성능 저하:</strong> 주제에 등록된 옵저버가 많을 경우, 주제가 상태를 변경할 때 모든 옵저버에게 알림을 보내는 데 시간이 오래 걸릴 수 있습니다.</li>
<li><strong>복잡성 증가:</strong> 작은 시스템에서는 패턴 적용이 불필요할 수 있으며, 잘못 사용하면 코드가 더 복잡해질 수 있습니다.</li>
</ol>
<hr>
<h2 id="결론">결론</h2>
<p>옵저버 패턴은 이벤트 기반 시스템이나 실시간 데이터 전달이 필요한 시스템에서 매우 유용한 디자인 패턴입니다. 여러 객체가 동일한 주제의 상태에 의존해야 하거나, 상태 변경을 알림받아야 할 때 유용합니다. 느슨한 결합과 유연성을 제공하는 반면, 사용 범위에 따라 성능 저하나 복잡성을 초래할 수 있으므로 적절한 상황에 맞게 적용하는 것이 중요합니다.</p>
<ul>
<li><p>참고 레퍼런스</p>
<ul>
<li><p><a href="https://velog.io/@hanna2100/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-2.-%EC%98%B5%EC%A0%80%EB%B2%84-%ED%8C%A8%ED%84%B4-%EA%B0%9C%EB%85%90%EA%B3%BC-%EC%98%88%EC%A0%9C-observer-pattern">https://velog.io/@hanna2100/디자인패턴-2.-옵저버-패턴-개념과-예제-observer-pattern</a></p>
</li>
<li><p><a href="https://www.youtube.com/watch?v=boXNtyeOzuc">https://www.youtube.com/watch?v=boXNtyeOzuc</a></p>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[통신] MQTT]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/%ED%86%B5%EC%8B%A0-MQTT</link>
            <guid>https://velog.io/@bo-ram-bo-ram/%ED%86%B5%EC%8B%A0-MQTT</guid>
            <pubDate>Fri, 06 Sep 2024 06:16:34 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/e45e89b6-3a35-4b05-ae17-791f719337ba/image.png" alt=""></p>
<h1 id="배경">배경</h1>
<blockquote>
<p>인턴을 진행하며 장비 시뮬레이션 프로그램에 MQTT 클라이언트와 브로커를 구축해야하는 상황이 생겨 MQTT에 대해 공부를 진행하였다</p>
</blockquote>
<hr>
<h3 id="1-mqtt란">1. <strong>MQTT란?</strong></h3>
<ul>
<li><strong>경량 메시지 통신 프로토콜</strong>, 주로 IoT와 저전력 장치에서 사용</li>
<li><strong>발행/구독(Pub/Sub)</strong> 모델 기반으로 브로커가 메시지를 중계</li>
<li><strong>비동기 통신</strong>으로 실시간이 아닌 느슨한 연결 제공</li>
<li><strong>ISO 표준</strong>(ISO/IEC PRF 20922)으로, IoT(Internet of Things) 시스템에서 <strong>센서</strong>와 <strong>장치 간 통신</strong>에 자주 사용</li>
</ul>
<h3 id="2-특징">2. <strong>특징</strong></h3>
<ul>
<li><p><strong>발행/구독 모델</strong>로 메시지 중계</p>
</li>
<li><p><strong>QoS (Quality of Service)</strong>: 0, 1, 2의 3가지 전송 신뢰 수준</p>
<blockquote>
<p><code>0</code> : 최대 1회 전송. Topic을 통해 메시지를 전송할 뿐 보장X (보낸 다음 잊어버림)
  <code>1</code> : 최소 1회 전송. 구독하는 클라이언트가 메시지를 받았는지 불확실하면 정해진 횟수만큼 재전송한다. 메시지의 핸드셰이킹 과정을 엄밀하게 추적하지는 않으므로 중복의 위험성이 있다. (확인 응답을 거치는 전달)
  <code>2</code> : 구독하는 클라이언트가 요구된 메시지를 정확히 한 번 수신할 수 있도록 보장한다. 메시지의 핸드셰이킹 과정을 추적한다. 높은 품질을 보장하지만 성능의 희생이 따른다. (보장된 전달)</p>
</blockquote>
<p>  0에 가까울수록 메시지 처리에 대한 부하가 줄어들고, 메시지 손실의 위험이 높아진다. 반대로 2에 가까울수록 메시지 손실 위험을 줄어들지만 메시지 처리 부하가 급격히 늘어난다.</p>
<blockquote>
</blockquote>
</li>
<li><p><strong>경량성</strong>: 작은 헤더로 저전력, 저대역폭에서 효율적</p>
</li>
<li><p><strong>연결 유지(Keep-alive)</strong>: PING 메시지를 통해 상태 확인</p>
</li>
<li><p><strong>지속성</strong>: 메시지 영구 저장 가능</p>
</li>
</ul>
<h3 id="3-구성-요소">3. <strong>구성 요소</strong></h3>
<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/e2548091-179b-44ab-b64d-b4d35c82bd1a/image.png" alt=""></p>
<ul>
<li><p><strong>브로커(Broker)</strong>: 메시지 중계 서버, 클라이언트가 발행한 메시지를 구독하는 다른 클라이언트에게 전달</p>
</li>
<li><p><strong>발행자(Publisher)</strong>: 메시지를 주제(Topic)에 발행</p>
</li>
<li><p><strong>구독자(Subscriber)</strong>: 특정 주제를 구독해 메시지 수신</p>
</li>
<li><p><strong>주제(Topic)</strong>: 메시지 전송의 논리적 채널, 계층적 구조로 이루어져 있으며, 슬래시(<code>/</code>)로구분</p>
<p>  예: <code>home/livingroom/temperature</code></p>
</li>
</ul>
<h3 id="4-동작-방식">4. <strong>동작 방식</strong></h3>
<ul>
<li>발행자는 <strong>주제</strong>에 메시지 발행, 브로커가 구독자에게 전달</li>
<li>하나의 클라이언트가 발생자와 구독자 역할 동시 수행 가능</li>
<li><strong>QoS</strong>에 따라 메시지 신뢰성 보장</li>
</ul>
<h3 id="5-장점">5. <strong>장점</strong></h3>
<ul>
<li><strong>경량성</strong>: 저대역폭 환경에서 사용 가능</li>
<li><strong>유연성</strong>: 비동기 통신으로 다수의 클라이언트 비동기 통신 지원</li>
<li><strong>안정성</strong>: QoS를 통해 메시지 전송 보장</li>
</ul>
<h3 id="6-단점">6. <strong>단점</strong></h3>
<ul>
<li><strong>브로커 의존성</strong>: 브로커 장애 시 전체 통신에 영향</li>
<li><strong>보안 문제</strong>: 기본 암호화 없음, TLS 필요</li>
<li><strong>느림</strong>: 실시간 통신 요구에는 부적합</li>
</ul>
<h3 id="7-사용-사례">7. <strong>사용 사례</strong></h3>
<ul>
<li><strong>IoT 통신</strong>: 센서와 스마트 홈 기기</li>
<li><strong>원격 모니터링</strong>: 기상 센서, 스마트 시티</li>
<li><strong>알림 시스템</strong>: 푸시 알림, 경보 시스템</li>
</ul>
<h3 id="8-mqtt-vs-http">8. <strong>MQTT vs HTTP</strong></h3>
<ul>
<li>MQTT는 <strong>경량, 비동기 통신</strong>, HTTP는 <strong>동기 요청/응답</strong> 구조</li>
<li>MQTT는 <strong>저전력/저대역폭</strong> 장치에 적합, HTTP는 웹에 적합</li>
</ul>
<h3 id="9-보안">9. <strong>보안</strong></h3>
<ul>
<li><strong>TLS</strong>로 통신 암호화 지원</li>
<li><strong>사용자 인증</strong>(username/password) 제공</li>
</ul>
<h3 id="10-브로커-예시">10. <strong>브로커 예시</strong></h3>
<ul>
<li><strong>Mosquitto</strong>: 경량 오픈소스 브로커</li>
<li><strong>EMQX</strong>: 대규모 배포에 적합한 고성능 브로커</li>
</ul>
<h3 id="11-관련-라이브러리">11. <strong>관련 라이브러리</strong></h3>
<ul>
<li><strong>paho-mqtt</strong>: 파이썬에서 MQTT 사용하는 라이브러리</li>
<li><strong>Eclipse Mosquitto</strong>: MQTT 브로커와 클라이언트</li>
</ul>
</br>
<details>
  <summary>참고 레퍼런스</summary>

<ul>
<li><p><a href="https://aws.amazon.com/ko/what-is/mqtt/">https://aws.amazon.com/ko/what-is/mqtt/</a></p>
</li>
<li><p><a href="https://www.eclipse.org/community/eclipse_newsletter/2014/october/article2.php">https://www.eclipse.org/community/eclipse_newsletter/2014/october/article2.php</a></p>
</li>
<li><p><a href="https://underflow101.tistory.com/22">https://underflow101.tistory.com/22</a></p>
</li>
</ul>
</details>

]]></description>
        </item>
        <item>
            <title><![CDATA[코드잇 JavaScript 수강 후기]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/%EC%BD%94%EB%93%9C%EC%9E%87-JavaScript-%EC%88%98%EA%B0%95-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@bo-ram-bo-ram/%EC%BD%94%EB%93%9C%EC%9E%87-JavaScript-%EC%88%98%EA%B0%95-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sun, 26 May 2024 18:02:01 GMT</pubDate>
            <description><![CDATA[<img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/952027c4-8744-4ca9-9056-1621fcf81d6f/image.png" width="500" />


<p>코드잇에서 수강권을 제공 받아 작성한 글입니다.</p>
<p>활동하고 있는 대학생 연합 IT 벤처 창업 동아리 SOPT에서 좋은 기회로 코드잇과 협업하게 되어 코드잇의 수강권을 제공받아 다양한 강의를 들을 수 있게 되었다!</p>
<p>Git을 비롯해 많은 강의가 있었고 그 중 다양한 개발 분야를 배워보고자 JavaScript관련 강의들을 수강하고 수료까지 할 수 있었다 !</p>
<p>우선 강의가 짧게 여러 강의로 구성되어 있어서 이동중에도 편하게 들을 수 있었다! 그리고 커리큘럼도 알차게 구성되어 있어서 기본적인 자료형부터 피보나치수열에 이르기까지  기초부터 탄탄하게 배울 수 있어서 좋았다 !</p>
<p>강의 내용도 참 좋았고 실습이 중간중간 구성되어있는것도 좋았다!</p>
<p>궁금했던 JavaScript를 배웠으니 코드잇에 있는 다른 토픽들도 공부해봐야겠다! (데이터분석, 파이썬, 알고리즘 등등) </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CS] TCP UDP SCTP]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/TCP-UDP-SCTP</link>
            <guid>https://velog.io/@bo-ram-bo-ram/TCP-UDP-SCTP</guid>
            <pubDate>Sat, 11 May 2024 02:00:58 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/79e6ec10-7937-4bbe-9afc-0633c56559eb/image.png" alt=""></p>
<h1 id="tcp">TCP</h1>
<ul>
<li><p><span style="background-color:#fff5b1">TCP(Transmission Control Protocol) : 전송 제어 프로토콜</p>
<ul>
<li><p>특징</p>
<ul>
<li>가장 보편적</li>
<li>Process to Process</li>
<li>가상 연결</li>
<li>non real time 서비스에 적합</li>
<li>전이중 통신(버퍼)-피기배킹</li>
<li>다중화/역다중화(분할<strong>Fragmentation</strong>/조립<strong>assemble</strong>)</br></li>
</ul>
</li>
<li><p>목적 : 신뢰성</p>
<ul>
<li>GBN+SR</li>
<li>검사합(checksum)을 통한 오류검출</li>
<li>재전송</li>
<li>Acknowledgement</li>
<li>타이머</li>
<li>노력 : (가상 연결, 재전송, 버퍼, 세그멘테이션, 넘버링)을 통한 오류/흐름제어</br>
</li>
</ul>
</li>
<li><p>가상 연결(Virtual Connection)</p>
<p>  : 가상 경로를 통해 데이터 전송</p>
<ul>
<li>연결지향</li>
<li>Stream Delivery Service</li>
<li>오류제어 흐름제어 가능</br></li>
</ul>
</li>
<li><p>송수신 버퍼</p>
<ul>
<li>목적 : 생산과 소비 프로세스 간 속도의 불균형 처리</li>
<li>특징 : 송신용 수신용 2개의 버퍼사용, a와 b 통신시 버퍼 4개 필요</br></li>
</ul>
</li>
<li><p>세그먼트 : 다수의 바이트를 묶어 그룹화 ↔ <strong>Fragmentation</strong></p>
</br></li>
<li><p>Numbering System</p>
<ul>
<li><p>특징</p>
<ol>
<li><p>Byte No.사용 </p>
<p> : TCP연결 상태에서 전송되는 모든 데이터 바이트에 번호 부여</p>
<ul>
<li>각 방향에 독립적</li>
<li>송신 버퍼에 저장시 TCP에 의해 부여, 임의로 생성된 번호로 시작</li>
<li>0~$2^{32}-1$</li>
<li>시퀀스번호와 에크번호는 바이트 번호 참조</li>
</ul>
</li>
<li><p>Sequence No.</p>
<p>  : 바이트넘버 선정 후 세그먼트에게 순서 할당</p>
<ul>
<li>첫번째 세그먼트의 번호 : 임의번호 (ISN-초기 순서 번호)</li>
<li>어떤 세그먼트의 순서번호 = 이전 S의 번호 + 이전 S가 운반된 바이트 수</li>
<li>연결 설정,종료,중단을 위한 세그먼트느 1개 순서 번호 소비</li>
</ul>
</li>
<li><p>Acknowledgement No. 
 : 상대방이 수신하기를 기대하는 다음 바이트 번호</p>
<ul>
<li>누적됨</br></li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
<li><p>TCP 헤더</p>
<ul>
<li>구조 : source port, destnation port, sequence No, ack No, data offset(length), reserved, window size, tcp checksum, control bit, urgent pointer, options, 패딩</br></li>
</ul>
</li>
<li><p>동작</p>
<ol>
<li><p>연결 설정</p>
<p> : 양쪽이 통신을 초기화하고 데이터를 전송하기 전 승인을 얻는 과정</p>
<ul>
<li><p>3 way handshaking</p>
<ul>
<li>SYN : 데이터 운반불가, 순서번호(Seq No) 1개 소비</li>
<li>SYN+ACK : 데이터 운반 불가. 순서번호 1개 소비</li>
<li>ACK : 데이터 운반가능, 운반x 순서번호 소비 x</li>
</ul>
</li>
<li><p>SYN 플로딩 공격</p>
<p>  : D-DOS 공격의 일종, 대량의 SYN 세그먼트 서버에 송신하여 시스텀 붕괴</p>
</li>
</ul>
</li>
<li><p>데이터 전송</p>
<ul>
<li><p>PSH</p>
<p>  : 송신 TCP 윈도우가 채워지는 것을 기다려서는 안됨&gt;&gt;바로전송</p>
</li>
<li><p>URG</p>
<p>  : urgent data에 대해 선택사항, 처리방법은 응용 프로그램이 결정</p>
</li>
</ul>
</li>
<li><p>연결 종료</p>
<p> : 서버 or 클라이언트가 연결종료</p>
<ul>
<li>3 way handshaking(일반적)<ul>
<li>FIN : 데이터 운반 불가. 순서번호(Seq No) 1개 소비</li>
<li>FIN+ACK : 데이터 운반 가능, 운반x 순서번호 1개 소비</li>
<li>ACK : 데이터 운반 불가, 순서번호 소비x</li>
</ul>
</li>
<li>half-close : 4 way handshaking<ul>
<li>데이터를 받고 있는 도중 한쪽에서 데이터 전송 중단</li>
<li>FIN+ACK: 데이터 운반 불가, 순서번호 1개 소비</li>
<li>서버가 처리된 모든 데이터를 보내면 FIN,ACK 송수신 및 최종 종료</li>
</ul>
</li>
</ul>
</li>
<li><p>연결 재설정(리셋)</p>
<p> : 한 종단의 Tcp가 연결 요구 거부,중지,유휴연결</p>
<ul>
<li>RST flag</br></li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
<li><p>TCP window</p>
<ul>
<li>양방향 통신에선 4개 필요(송신2, 수신2)</li>
<li>송신 window<ul>
<li>vs SR protocol</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code>        |  | SR protocol | 송신 window |
        | --- | --- | --- |
        | 제어변수 | 패킷 번호 | 바이트번호(tcp) |
        | 타이머개수 | 전송 패킷마다 각각 | 1개 |
    - shrink &amp; open &amp; close 존재이유 : 수신 윈도우 크기에 맞춰 조절하기 위해서

- 수신 window
    - vs SR protocol


        |  | SR protocol | 송신 window |
        | --- | --- | --- |
        | 수신윈도우 | 2^(m-1) | ≤ 버퍼크기 |
        | 확인 응답 방식 | 선택적 방식 | 누적 확인응답 방식 ≒ GBN |

        새로운 TCP : 누적 선택적 확인응답 방식 모두 사용

    - 축소 xx</code></pre><ul>
<li><p>Flow Control</p>
<p>  : 생산자의 데이터 생성과 소비자의 데이터 소비의 속도의 균형을 맞추는 것</p>
<ul>
<li><p>push &amp; pull로 처리</p>
</li>
<li><p>문제점</p>
<ul>
<li><p><strong>Silly Window Syndrome :</strong> 비효율적 데이터 전송</p>
<ul>
<li><p>원인 (&gt;&gt; 여러개씩 할 수 있는데 1개씩 처리)</p>
<ol>
<li><p>전송측에서 데이터를 천천히 발생</p>
</li>
<li><p>수신측에서 데이터를 천천히 소비 </p>
</li>
</ol>
</li>
<li><p>해결방안</p>
<p>  송신측 : Nagle’s algorithm</p>
<p>  수신측 : Clark’s solution, delayed acknowledgement</p>
</li>
<li><p>Nagle’s algorithm</p>
<ul>
<li>해결요인 : 비효율적 데이터 전송과 지연 문제 해결</li>
<li>특징 :</li>
</ul>
</li>
<li><p>Clark’s solution</p>
<ul>
<li>데이터 도착하자마자 확인응답 0으로 전송</li>
</ul>
</li>
<li><p>Delayed acknowledgement</p>
<ul>
<li>세그먼트가 도착하더라도 즉시 ack 보내지 않고 버퍼에 충분한 공간이 생길때까지 응답 보류</br></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p>Error Control</p>
<ul>
<li>신뢰성 제공 : 훼손 세그먼트 감지 및 재전송</li>
<li>방법<ol>
<li>CheckSum</li>
<li>Ack</li>
<li>Time out : 만료시 재전송</li>
<li>Retransmission : 오류제어 메커니즘 핵심<ul>
<li>동작 : 재전송 타이머 만료시, 첫번째 세그먼트에 대한 3개의 중복 ACK 수산</li>
<li>RTO</li>
<li>Fast retransmission</li>
</ul>
</li>
</ol>
</li>
<li>교착상태 : 확인응답의 손실로 인해 발생 ACK의 ACK가 없어서<ul>
<li>해결방법 : Persistence Timer</br></li>
</ul>
</li>
</ul>
</li>
<li><p>Congestion Control</p>
<ul>
<li><p>혼잡 = time-out &amp; 3개의 중복 ack 수신한 상태</p>
</li>
<li><p>실제 윈도우 크기 = rwnd(수신윈도우사이즈)랑 cwnd(혼잡윈도우사이즈) 중 작은 값</p>
</li>
<li><p>해결방법</p>
<ol>
<li><p>Slow start algorithm</p>
<p> : 임계값(slow start threshold)까지 지수적 증가</p>
</li>
</ol>
</li>
</ul>
</li>
</ul>
<pre><code>    3. Congestion avoidance algorithm

    :  임계값(slow start threshold)까지는 $2^n$ , 이후 +1+1+1-1이런식(봐가면서)
    &lt;/br&gt;</code></pre><ul>
<li>Timer<ol>
<li>Retransmission timer : 한 세그먼트에 대한 확인 응답을 기다리는 시간</li>
<li>Persistemce timer <ul>
<li>교착 상태 해결 타이머</li>
<li>구동 조건 : 윈도우 크기 0을 갖는 ack 수신</li>
<li>Probe Packet 사용</li>
</ul>
</li>
<li>Keep alive timer : 연결이 오랜기간동안 휴지상태에 있는것을 방지 <ul>
<li>전송할 데이터 없을 때 사용</li>
<li>얘만 서버에서 동작</li>
<li>응답 미수신시 클라이언트 다운으로 간주 &gt;&gt; 종료</li>
</ul>
</li>
<li>Timer wait timer : 연결 종료 동안 사용<ul>
<li>값 : MSL(세그먼트가 버려지기 전에 네트워크가 존재할 수 있는 시간)x2</li>
</ul>
</li>
</ol>
</li>
</ul>
<hr>
<h1 id="udp">UDP</h1>
<ul>
<li><p><span style="background-color:#fff5b1">UDP(User Datagram Protocol) : 사용자 데이터그램 프로토콜</p>
</li>
<li><p>특징</p>
<ul>
<li>Process to Process</li>
<li>Real time service : voice, video</li>
<li>Connectionless service : 연결/종료과정 없음</li>
<li>비신뢰성</li>
<li>캡슐화</li>
<li>다중화 / 역다중화</li>
<li>제어<ul>
<li>오류제어 - Checksum만함</li>
<li>혼잡제어 x</li>
<li>흐름제어 x window x 수신측 : 오버플로 발생가능</li>
</ul>
</li>
<li>vs simple protocol<ul>
<li>checksum : 오류가 있으면 feddback 없이 폐기</li>
<li>overflow : 심플 - 버퍼 오버플로 발생 x , udp - 수신측 오버플로 발생 가능</br></li>
</ul>
</li>
</ul>
</li>
<li><p>장점</p>
<ul>
<li>최소한의 오버헤드</li>
<li>단순함</li>
<li>서버로 짧은 요구를 전송하고 짧은 응답을 수신하는 경우 유용</br></li>
</ul>
</li>
<li><p>구조</p>
<ul>
<li>헤더 : 8byte<ul>
<li>Source Port No, Destination Port No, total length, CheckSum</li>
</ul>
</li>
<li>data</br></li>
</ul>
</li>
<li><p>데이터 전송단위 : 데이터그램</p>
</li>
</ul>
<hr>
<h1 id="sctp">SCTP</h1>
<ul>
<li><p><span style="background-color:#fff5b1">SCTP(Stream Control TRansmission Protocol) : 스트림 제어 전송 프로토콜</p>
</li>
<li><p>특징</p>
<ul>
<li>Multimedia Communication</li>
<li>Udp +Tcp</li>
<li>Process to Process</li>
<li>전이중 통신</li>
<li>연결지향 서비스 - sctp에서의 연결 : 결합(association)</li>
<li>신뢰성 - ACK</li>
</ul>
</li>
<li><p>데이터 전송단위 : chunk</p>
</li>
</ul>
<pre><code>|  | TCP | UDP | SCTP |
| --- | --- | --- | --- |
| 데이터 전송 단위 | 세그먼트 | 데이터그램 | Chunk |</code></pre>  </br>
- Multimedia Communication : 다중 스트림 서비스를 각 연결에 허용
</br>
- Multihomming : 종단간 다수의 ip 주소 정의 가능
    - 하는 이유 : 하나의 경로 실패 시 다른 경로를 통해 전송
</br>
- 식별자
    - TSN : 전송 순서 번호 ≒ TCP sequence No (32bit)
        - 데이터 단위 : Chunk(vs TCP : byte)
    - SI : 스트림 식별자 (16bit)
    - SSN : 스트림 순서번호
</br>
- ACK
    - chunk지향적 TSN참조
    - data chunk :  sack에 의해 확인응답 가능
    - control chunk : 제어정보 운반 TSN 필요 x 순서번호, 확인응답 번호 필요 x
        - init 제어청크 >> init ACK 청크로 확인응답
</br>

<ul>
<li><p>패킷 구성</p>
<ul>
<li>header : Source Port No, Destination Port No, Checksum</li>
<li>control chunks</li>
<li>data chunks</br></li>
</ul>
</li>
<li><p>SCTP association : 연결 지향 프로토콜 ( 멀티홈잉 강조를위해 association지칭)</p>
<ul>
<li>Assotication dstalishment<ul>
<li>4 way handshake (vs TCP : 3way handshake)</li>
<li>VT(Verification Tag) : assotication별 할당하며 세션 식별자로 활용</li>
<li>쿠키 : 세션에 대한 사용자 인증 기능 제공</li>
</ul>
</li>
<li>Data transmission<ul>
<li>피기배킹 지원</li>
<li>data Chunk : TSN 사용, SACK 청크에 의해 확인응답</li>
<li>멀티홈잉 데이터 전송</li>
<li>멀티스트림 전달</li>
<li>단편화 - Maximum Transfer Unit</li>
</ul>
</li>
<li>Assotication termination<ul>
<li>클라이언트 서버 모두 active close 가능</li>
<li>half-close 허용 x : tcp와의 차이</li>
<li>Shutdown ACK</br></li>
</ul>
</li>
</ul>
</li>
<li><p>Flow Control</p>
<ul>
<li>단위<ul>
<li>rwnd/ cwnd : 바이트단위</li>
<li>TSN / ACK No : Chunk</li>
</ul>
</li>
<li>수신측 :1개의 버퍼와 3가지 변수 유지<ul>
<li>cunTSN : 수신된 마지막 TSN</li>
<li>winSize : 이용가능한 버퍼 크기</li>
<li>lastAVK : 마지막 누적된 확인응답</li>
</ul>
</li>
<li>송신측:1개의 버퍼와 3가지 변수 유지<ul>
<li>curTSN : 송신이 되는 다음 청크</li>
<li>rwnd: 수신기에 의해 공개된 마지막 값</li>
<li>inTransit: 확인 응답 되지 않은 바이트 수</br></li>
</ul>
</li>
</ul>
</li>
<li><p>Error Control</p>
<ul>
<li>수신측 : sack 송신</li>
<li>송신측 : sack 수신, timer</li>
<li>데이터 청크 송신<ul>
<li>재전송(이발생하는 상황, 손실로 간주되는 상황) : 재전송 타이머 만료 sack4번 수신</li>
</ul>
</li>
<li>sack 청크 생성</li>
<li>vs TCP : ack, timer checksum</li>
</ul>
</li>
<li><p>Congestion Control : tcp와 동일</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective JAVA] Item8. finalizer와 cleaner사용을 피하라]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/Effective-JAVA-Item8</link>
            <guid>https://velog.io/@bo-ram-bo-ram/Effective-JAVA-Item8</guid>
            <pubDate>Sat, 04 May 2024 14:29:26 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/e1d16892-570f-4167-af9b-fd5675625764/image.png" alt=""></p>
<h3 id="자바가-제공하는-객체-소멸자">자바가 제공하는 객체 소멸자</h3>
<ul>
<li><p><code>finalizer</code></p>
<ul>
<li>최상위 Object 클래스에 포함된 메서드이기 때문에  finalize 메서드 재정의(Overriding) 가능</li>
<li>finalize메서드를 재정의하면 해당 객체가 Garbege Collection 대상이 되었을 때 finalize메서드가 호출된다. 단, 즉시 호출을 보장받을 수는 없다.</li>
<li>즉시 호출이 보장되지 않기 때문에 한정적 자원 해제시 해제하는 작업을 finalizer로 구현하면 안된다. 이런 경우 try-with-resource 또는 try-finally를 이용해 구현해야 한다.</li>
<li>finalize 메서드는 <strong>자바9부터 deprecated</strong> 되었고 이에 대한 대안으로 자바에서는 <strong>Cleaner를 지원</strong>하게 되었다.</li>
</ul>
</li>
<li><p><code>cleaner</code></p>
<p>cleaner는 API로 제공했던 finalizer처럼 재정의하는 것과 달리 구성을 통해 cleaner를 사용해야 한다. 하지만, cleaner 역시 예측할 수 없고, 느리고, 일반적으로 불필요하다.</p>
</li>
</ul>
</br>

<h2 id="finalizer와-cleaner의-사용을-지양하라">finalizer와 cleaner의 사용을 지양하라.</h2>
<h3 id="첫-번째-finalizer-와-cleaner-는-즉시-실행된다는-보장이-없다">첫 번째, finalizer 와 cleaner 는 즉시 실행된다는 보장이 없다.</h3>
<blockquote>
<p>finalizer 나 cleaner 는 즉시 실행된다는 보장이 없어 제때 실행되어야하는 작업은 절대 할 수 없다.</p>
</blockquote>
<ul>
<li>언제 실행될지 알 수가 없다. 어떤 객체가 필요 없어진 시점에 <code>finlizer</code> 또는 <code>cleaner</code>가 바로 실행되지 않을 수도 있다. 그 시간이 얼마나 걸릴지는 아무도 모른다. 따라서 타이밍이 중요한 작업을 절대로 <code>finalizer</code> 또는 <code>cleaner</code>로 해서는 안된다.</li>
<li>예를 들어, 파일 리소스를 반납하는 작업을 <code>finalizer</code> 또는 <code>cleaner</code>로 처리한다면 실제로 그 파일 리소스가 언제 처리될지 알 수 없고 자원 반납이 안되서 더 이상 새로운 파일을 열 수 없는 상황이 발생할 가능성이 있다.</li>
</ul>
</br>


<h3 id="두-번째-finalizer-처리는-인스턴스의-자원-회수가-제멋대로-지연될-수-있다">두 번째, finalizer 처리는 인스턴스의 자원 회수가 제멋대로 지연될 수 있다.</h3>
<blockquote>
<p>인스턴스 회수가 지연될 뿐만 아니라 제대로 실행되지 않을수도 있다.</p>
</blockquote>
<ul>
<li><code>finalizer</code> 스레드는 다른 애플리케이션 스레드보다 우선 순위가 낮아서 실행될 기회를 제대로 얻지 못할수도 있다. (다른 작업이 바쁘면 뒤로 미뤄진다, 언제 호출될지 모른다는 의미이다.) 따라서 <code>finlizer</code> 안에 어떤 작업이 있고, 그 작업을 쓰레드가 처리 못해서 대기하고 있다면 해당 인스턴스는 <code>GC</code>가 되지 않고 계속해서 쌓이다가 결국 <code>OOM(Out Of Memory Exception)</code>이 발생할 수 있다.</li>
<li><code>cleaner</code> 는 자신을 수행할 스레드를 제어할 수는 있지만, 역시나 가비지 컬렉터에 의존하므로 즉시 사용된다는 보장은 없다.</li>
</ul>
</br>

<h3 id="세-번째-수행-여부조차-보장되지-않는다">세 번째, 수행 여부조차 보장되지 않는다.</h3>
<blockquote>
<p>상태를 영구적으로 수정하는 작업에서는 절대 finalizer 나 cleaner 에 의존해서는 안된다.</p>
</blockquote>
<ul>
<li><p>자바 언어 명세는 <strong>finalizer</strong> 나 <strong>clenaer</strong> 의 수행 여부를 보장하지 않는다. 접근할 수 없는 일부 객체에 딸린 종료작업을 수행하지 못한채 프로그램이 종료될 수도 있다.</p>
</li>
<li><p>만약 데이터 베이스 같은 자원의 락을 이것들로 반환하는 작업을 한다면 전체 분산 시스템이 멈춰 버릴 수도 있다. 따라서 <strong>finalizer</strong> 나 <strong>clenaer</strong>로 저장소 상태를 변경하는 일을 하지 말아야 한다.</p>
<pre><code class="language-java">public class SampleRunner {
    public static void main(String[] args) {
        final SampleRunner sampleRunner = new SampleRunner();
        sampleRunner.run();
        System.gc(); // 실행이 안될수도 있다!
    }
}</code></pre>
<ul>
<li><code>System.gc</code> 나 <code>System.runFinalization</code> 메서드에 속지 말자 이들은 실행 가능성을 높여줄 순 있으나, 보장하지는 않는다.</li>
<li><code>System.runFinalizationOnExit</code> 와 <code>Runtime.runFinalizationOnExit</code> 는 실행을 보장해주려 만들었지만 심각한 결함으로 수십년간 <code>deprecated</code> 상태이다.</li>
<li></li>
</ul>
</li>
</ul>
</br>

<h3 id="네-번째-예외가-무시된다">네 번째, 예외가 무시된다.</h3>
<blockquote>
<p>finalizer 동작 중 발생한 예외는 무시되며, 처리할 작업이 남았더라도 그 순간 종료된다.</p>
</blockquote>
<p>  잡지못한 예외때문에 객체가 훼손될 수 있고, 다른 스레드가 이 훼손된 객체에 접근하게 될 수 있다. <strong>finalizer</strong> 에서 예외가 발생할 경우 경고조차 출력하지 않는다. 그나마 <strong>cleaner</strong> 를 사용하는 라이브러리는 자신의 스레드를 통제하기 때문에 이러한 문제가 발생하지 않는다.</p>
</br>

<h3 id="다섯-번째-심각한-성능-문제도-동반한다">다섯 번째, 심각한 성능 문제도 동반한다.</h3>
<ul>
<li><code>finalizer</code> 가 가비지 컬렉터의 효율을 떨어뜨리기 때문에 <code>try-with-resource</code> 와 비교했을 때 굉장히 느리다. <code>cleaner</code> 역시 마찬가지이다.</li>
</ul>
</br>


<h3 id="여섯-번째-finalizer는-심각한-보안문제를-일으킬-수-있다">여섯 번째, finalizer는 심각한 보안문제를 일으킬 수 있다.</h3>
<ul>
<li><p><code>finalizer</code> 를 사용한 클래스는 <code>finalizer</code> 공격에 노출되어 심각한 보안 문제를 일으킬 수 있다.</p>
</li>
<li><p>A, B 클래스가 있다고 가정했을 때, B클래스가 A클래스를 상속받는다. 그리고 <code>finalize</code> 를 오버라이딩한 B 클래스의 인스턴스를 생성하는 도중에 예외가 발생하거나, 직렬화 과정에서 예외가 발생하면, 원래 죽었어야 할 객체의 <code>finalizer</code>가 실행될 수 있다.</p>
<p>  그럼 <code>finalize</code> 메서드 안에서 이 인스턴스가 가진 <code>static</code> 필드에 접근할 수 있어서 해당 인스턴스의 레퍼런스를 기록할 수 있고 GC에 의해 수거되지 못하게 할 수도 있다.</p>
<p>  원래는 생성자가 예외를 발생시켜 존재하지 않았어야 하는 인스턴스인데 <code>finalizer</code> 때문에 살아남아 있는 것이다.</p>
</li>
<li><p>이 문제에 대한 해결방법은 <code>final</code> 클래스들은 하위 클래스를 만들 수 없으니 공격으로부터 안전하다. 따라서 아무일도 하지 않는 <code>finalizer</code> 메서드를 만들고 <code>final</code> 로 선언하자.</p>
</li>
</ul>
</br>

<h3 id="정리">정리</h3>
<blockquote>
<p><code>cleaner</code>과 <code>finalizer</code>는 안전망 역할이나 중요하지 않은 네이티브 자원 회수용으로만 사용하자. 이런 경우라도 불확실성과 성능 저하에 주의해야 한다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective JAVA] Item7. 다 쓴 객체 참조를 해제하라]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/Effective-JAVA-Item7</link>
            <guid>https://velog.io/@bo-ram-bo-ram/Effective-JAVA-Item7</guid>
            <pubDate>Sat, 04 May 2024 14:23:00 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/bfa49a35-5a12-499f-9e5f-c179310efda7/image.png" alt=""></p>
<h3 id="배경">배경</h3>
<p>자바에서는 가비지 컬렉터가 다쓴 객체를 알아서 회수해간다 하지만 그렇다고해서 메모리 관리에 신경쓰지 않으면 안된다. <strong>메모리 누수</strong>가 발생하는 프로그램을 오래 실행하다보면 점차 <strong>가비지 컬렉션 활동</strong>과 <strong>메모리 사용량</strong>이 늘어나 결국 <strong>성능이 저하되거나 메모리초과(<code>OufOfMemory</code>) 오류가 발생</strong>할 수 있다. 그러므로 메모리 누수를 주의해야 한다.</p>
</br>

<h3 id="메모리-누수-방지-방법">메모리 누수 방지 방법</h3>
<p>메모리 누수를 방지하는 방법은 간단하다. 해당 참조를 다 사용했을 때 <code>null</code> 처리(참조 해제)하면 된다.</p>
<p><strong>[메모리 누수를 방지하는 스택 pop메서드]</strong></p>
<pre><code class="language-java">    public Object pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }

        Object result = elements[--size];
        elements[size] = null;

        return result;
    }</code></pre>
<p>다 쓴 참조를 <code>null</code> 처리하면 따라오는 부수적인 이점이 있다. 만약 <code>null</code> 처리한 참조를 사용하려하면 <code>NullpointerException</code> 이 발생하며 종료된다.</p>
</br> 

<h3 id="모든-객체를-null처리-해야할까">모든 객체를 null처리 해야할까?</h3>
<p>사실 모든 객체를 다 쓰자마자 <code>null</code> 처리하는 것은 별로 바람직하지 않다. 객체 참조를 <code>null</code> 처리하는 일은 예외적인 경우여야 한다.</p>
<blockquote>
<p>다 쓴 참조를 해제하는 가장 좋은 방법
그 참조를 담은 변수를 유효 범위 밖으로 밀어내는 것이다.</p>
</blockquote>
<p>참조를 변수 유효범위 밖으로 밀어내는 일은 변수의 범위를 최소로 정의했다면 자연스럽게 이루어진다.</p>
</br>

<h3 id="그렇다면-null처리는-언제해야-할까">그렇다면 null처리는 언제해야 할까?</h3>
<p><code>Stack</code>클래스가 메모리 누수에 취약한 이유는 자기 메모리를 직접 관리하기 때문이다.</p>
<blockquote>
<p>일반적으로 자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항시 메모리 누수에 주의해야하고 원소를 다 사용하면 참조한 객체를 다 null 처리해줘야 한다.</p>
</blockquote>
<p>위에서 예를 들었던 스택은 객체 자체가 아니라 객체 참조를 담는 <code>elements</code> 배열로 메모리를 관리한다. 문제는 배열의 비활성 영역은 쓰이지 않는다는 것이고 가비지 컬렉터는 이 사실을 알지 못한다.</p>
<p>그러므로 프로그래머는 비활성 영역이 되는 순간 <code>null</code> 처리를 통해 해당 객체가 더 이상 쓰이지 않는다는 사실을 가비지 컬렉터에게 알려주어야 한다.</p>
</br>

<h3 id="캐시-메모리-누수의-해결-방법"><strong>캐시 메모리 누수의 해결 방법</strong></h3>
<ol>
<li><code>WeakHashMap</code> 을 사용해 캐시 외부에서 키를 참조하는 동안만 엔트리가 살아있는 캐시를 사용</li>
<li><code>ScheduledThreadPoolExecutor</code> 같은 백그라운드 스레드를 활용해 엔트리를 청소</li>
<li><code>LinkedHashMap</code> 의 <code>removeEldesEntry</code> 메서드를 활용해 엔트리를 청소</li>
<li><code>java.lang.ref</code> 패키지를 이용해 더 복잡한 캐시를 만들기</li>
</ol>
</br>

<h3 id="추가적으로-주의해야-할-메모리-누수-사례">추가적으로 주의해야 할 메모리 누수 사례</h3>
<ul>
<li><p>리스터 / 콜백</p>
<p>클라이언트가 콜백을 등록만하고 명확히 해지하지 않는다면, 콜백은 계속 쌓이게 된다. 이럴 때 콜백을 약한 참조로 저장하면 <strong>가비지 컬렉터</strong> 가 즉시 수거해 간다.</p>
</li>
</ul>
</br>

<h3 id="정리">정리</h3>
<blockquote>
<p>메모리 누수는 겉으로 잘 드러나지 않아 시스템에 수년간 잠복하는 사례도 있다. 이런 누수는 철저한 코드 리뷰나 힙 프로파일러 같은 디버깅 도구를 동원해야만 발견되기도 한다. 그래서 이런 종류의 문제는 예방법을 익혀두는 것이 중요하다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective JAVA] Item6. 불필요한 객체 생성을 피하라]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/Effective-JAVA-Item6</link>
            <guid>https://velog.io/@bo-ram-bo-ram/Effective-JAVA-Item6</guid>
            <pubDate>Sat, 04 May 2024 14:17:53 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/ef483906-264c-459b-a682-88e58f9d47fc/image.png" alt=""></p>
<h3 id="배경">배경</h3>
<blockquote>
<p>똑같은 기능의 객체를 매번 생성하기보단 객체 하나를 재사용하는 편이 나을 때가 많다.</p>
</blockquote>
</br>

<pre><code class="language-jsx">String s = new String(‘&#39;bikini&quot;); // 1번

VS

String s = &quot;bikini&quot;;  //2번</code></pre>
<p>1번의 경우 실행될 때 마다 String 인스턴스를 새로 만들고 2번은 하나의 인스턴스를 사용한다. 1번처럼 <code>new</code> 키워드를 이용해 문자열을 생성하는 것은 무의미하며 성능에 악영향을 끼친다. 2번의 방식을 사용한다면 같은 vm안에서 똑같은 문자열 리터럴을 사용하는 모든 코드가 같은 객체를 재사용함이 보장된다.</p>
</br>

<h3 id="불필요한-객체-생성-대신-정적-팩터리-메서드를-사용하여-불필요-객체의-생성을-피하자">불필요한 객체 생성 대신 정적 팩터리 메서드를 사용하여 불필요 객체의 생성을 피하자.</h3>
<pre><code class="language-java">//1번 - **Boolean.valueOf(String) 사용**
Boolean boolean1 = Boolean.valueOf(&quot;true&quot;);
Boolean boolean2 = Boolean.valueOf(&quot;true&quot;);

System.out.println(boolean1 == boolean2);

//2번 - **Boolean(String) 사용**
Boolean boolean3 = new Boolean(&quot;true&quot;);
Boolean boolean4 = new Boolean(&quot;true&quot;);

System.out.println(boolean3 == boolean4);</code></pre>
<p>여기서도 2번의 방식을 사용하는 것이 기능적으로 같은 역할을 하는 객체를 반복적으로 생성할 필요가 없기 때문에 더 좋다.</p>
</br>

<h3 id="객체-생성비용이-비싸다면-객체를-재사용하자">객체 생성비용이 비싸다면 객체를 재사용하자.</h3>
<p>객체 생성비용이 비싼 객체가 반복적으로 필요하다면 캐싱해서 재사용하는것이 좋다.</p>
<p><strong>[문제 - 재사용빈도가 높고 생성비용이 비싼 경우 - 정규표현식 사용]</strong></p>
<pre><code class="language-java">public static boolean isRomanNumeral(String s) {
        return s.matches(&quot;^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$&quot;);
    }</code></pre>
<p>위 코드의 문제점은 <code>String.matches</code> 메서드를 사용하는데 있다. 이 메서드가 내부에서 만드는 <code>Pattern</code> 인스턴스는, 한 번 쓰고 버려져서 곧바로 가비지 컬렉션 대상이 된다. <code>Pattern</code> 은 입력받은 정규표현식에 해당하는 유한상태 머신을 만들기 때문에 인스턴스 생성 비용이 높다.</p>
<p><strong>[해결]</strong></p>
<p>성능을 개선을 위해 정규표현식을 표현하는 (불변) <code>Pattern</code> 인스턴스를 클래스 초기화(정적 초기화) 과정에서 직접 생성해 캐싱해두고, 나중에 <code>isRomanNumeral</code> 메서드가 호출될 때마다 이 인스턴스를 재사용하게 하면된다.</p>
<pre><code class="language-java">private static final Pattern ROMAN = Pattern.compile(&quot;^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$&quot;);

public static boolean isRomanNumeral(String s) {
    return ROMAN.matcher(s).matches();
}</code></pre>
</br>

<h3 id="불필요한-객체-재사용은-지양하자">불필요한 객체 재사용은 지양하자.</h3>
<p><strong>[같은 인스턴스를 대변하는 여러개의 인스턴스를 생성]</strong></p>
<pre><code class="language-java">final Map&lt;String, Integer&gt; beverage = new HashMap&lt;&gt;();

beverage.put(&quot;coke&quot;, 10);
beverage.put(&quot;cider&quot;, 9);
beverage.put(&quot;water&quot;, 8);

Set&lt;String&gt; keySet1 = beverage.keySet();
Set&lt;String&gt; keySet2 = beverage.keySet();

System.out.println(&quot;beverage Size: &quot; + beverage.size());
System.out.println(&quot;keySet1 Size: &quot; + keySet1.size());
System.out.println(&quot;keySet2 Size: &quot; + keySet2.size());

keySet1.remove(&quot;water&quot;);

System.out.println(&quot;beverage Size: &quot; + beverage.size());
System.out.println(&quot;keySet1 Size: &quot; + keySet1.size());
System.out.println(&quot;keySet2 Size: &quot; + keySet2.size());
// 실행결과
beverage Size: 3
keySet1 Size: 3
keySet2 Size: 3

beverage Size: 2
keySet1 Size: 2
keySet2 Size: 2</code></pre>
<p>반환된 <code>Set</code>인스턴스가 가변일지라도 반환된 인스턴스들은 기능적으로 모두 같다. 즉, 반환한 객체 중 하나를 수정하면 위의 코드처럼 다른 모든 객체가 따라서 바뀐다.</p>
<p>따라서 <code>keySet</code>이 뷰 객체를 여러개 만들 필요도 없고 이득도 없다.</p>
</br>

<p><strong>[의도치 않은 오토박싱 객체 생성]</strong></p>
<pre><code class="language-java">public static void main(String[] args) {
    long start = System.currentTimeMillis();
    Long sumAutoboxing = sumAutoboxing();

    System.out.println(&quot;sumAutoboxing: &quot;+sumAutoboxing);
    System.out.println(System.currentTimeMillis()- start);

    start = System.currentTimeMillis();
    long sumPrimitive = sumAutoboxing();

    System.out.println(&quot;sumPrimitive: &quot;+sumPrimitive);
    System.out.println(System.currentTimeMillis()- start);
}

private static long sumAutoboxing() {
    Long sum = 0L;
    for (long i = 0; i &lt;= Integer.MAX_VALUE; i++) {
        sum += i;
    }

    return sum;
}

private static long sumPrimitive() {
    long sum = 0L;
    for (long i = 0; i &lt;= Integer.MAX_VALUE; i++) {
        sum += i;
    }

    return sum;
}
// 수행결과
sumAutoboxing: 2305843008139952128
6553
sumPrimitive: 2305843008139952128
5638</code></pre>
<p>위의 오토박싱의 경우 <code>sum</code> 변수를 <code>Long</code>으로 선언해서 불필요한 <code>Long</code> 인스턴스가 만들어지기 때문에 <code>long</code> 타입으로 선언했을떄 보다 훨씬 느리다.</p>
<p><strong>따라서, 박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어있지 않도록 주의해야 한다.</strong></p>
</br>

<blockquote>
<p><strong>핵심정리</strong></p>
<p>무조건 객체를 재사용할 것이 아니라 상황에 따라 객체 재사용 여부를 판단하여 객체를 재사용해야 한다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective JAVA] Item5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/Effective-JAVA-Item5</link>
            <guid>https://velog.io/@bo-ram-bo-ram/Effective-JAVA-Item5</guid>
            <pubDate>Sat, 04 May 2024 14:10:24 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/0b4af23b-8f56-419e-a380-b9f9a5150bc1/image.png" alt=""></p>
<h2 id="배경">배경</h2>
<blockquote>
<p>사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다.많은 클래스가 하나 이상의 자원에 의존한다. 이런 클래스들은 정적 유틸리티 클래스로 구현된다.</p>
</blockquote>
</br>

<pre><code class="language-jsx">public class Spellchecker {
    private static final Lexicon dictionary = …;

    private SpellChecker() {} // 객체 생성 방지
    public static boolean isValid(String word) { ... }
    public static List&lt;String&gt; suggestions(String typo) { ... }
}</code></pre>
<pre><code class="language-jsx">public class SpellChecker {
    private final Lexicon dictionary = ...;

    private SpellChecker(...) {}
    public static SpellChecker INSTANCE = new SpellChecker(...);
    public boolean isValid(5tring word) { ... }
    public List&lt;String&gt; suggestions(String typo) { ... }
}</code></pre>
<p>위와 같은 SpellChecker 클래스는 Dictionary를 사용하고 이를 의존하는 리소스 또는 의존성이라고 부른다. 이렇게 정적 유틸리티를 잘못 사용하거나 싱글턴을 잘못 사용한 클래스들은  적합하지 못하다.</p>
</br>

<ul>
<li><p>사전을 단 하나만 사용한다고 가정한다는 점에서 적합하지 못하다.</p>
</li>
<li><p>사전은 언어별로 여러개의 사전이 있고 이런 모든 사전의 역할을 하나의 사전으로 해결하는 것은 불가능하기 때문이다.</p>
</li>
<li><p>여러개의 사전을 사용하려면 <code>final</code>을 제거하고 <code>dictionary</code>를 변경하는 메서드를 추가한다.</p>
<p>  ⇒ but 오류가 발생할 가능성이 크고 멀티 스레드 환경에서는 사용이 불가능</p>
</li>
</ul>
<p>** 따라서 SpellChecker 클래스가 여러 자원 인스턴스를 지원해야 하며 클라이언트가 원하는 자원(dictionary)를 사용해야 한다. 즉 의존성을 분리하여 자원을 외부로부터 주입받아야 한다. 그러기 위해선 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨준다. **
</br>
</br></p>
<h3 id="의존-객체-주입-패턴">의존 객체 주입 패턴</h3>
<pre><code class="language-jsx">public class Spellchecker {
    private final Lexicon dictionary;
    **public Spellchecker(Lexicon dictionary) {
        this.dictionary = Objects.requireNonNull(dictionary);
    }**
    public boolean isValid(String word) { ... }
    public List&lt;String&gt; suggestions(String typo) { ... }
}</code></pre>
<p>위와 같이 생성자를 이용해 외부로부터 자원을 주입받는다.</p>
<p>이렇게 할 경우 외부 자원에 관계없이 객체 생성이 가능하다.</p>
<h3 id="의존-객체-주입-방식의-장점">의존 객체 주입 방식의 장점</h3>
<ul>
<li>유연성과 테스트 용이성을 높여준다.</li>
<li>자원의 개수와 의존 관계에 관계없이 잘 작동한다.</li>
<li>불변을 보장하여 여러 클라이언트가 의존 객체를 안심하고 공유할 수 있다.</li>
<li>의존 객체 주입은 생성자, 정적 팩터리, 빌더에 똑같이 응용가능하다.</li>
</ul>
</br>

<h3 id="변형---팩터리-메서드-패턴">변형 - 팩터리 메서드 패턴</h3>
</br>

<p><strong>예시 - <code>Supplier&lt;T&gt;</code> 인터페이스</strong></p>
<pre><code class="language-java">@FunctionalInterface
public interface Supplier&lt;T&gt; {
    /**
     * Gets a result.
     *
     * @return a result
     */
    T get(); // 매개변수가 없고 T를 반환하는 추상 메서드
}</code></pre>
<ul>
<li>일반적으로 한정적 와일드카드 타입을 사용해서 팩터리의 타입 매개변수를 제한해야 한다.</li>
<li>클라이언트는 자신이 명시한 타입의 하위 타입이라면 무엇이든 생성할 수 있는 팩터리를 넘길 수 있다.</li>
<li>의존 객체 주입은 큰 프로젝트에서는 코드를 어지럽게 만들기도 하므로 큰 프로젝트에서는 스프링같은 의존 객체 주입 프레임워크를 사용하자.</li>
</ul>
</br>

<blockquote>
<p><strong>핵심정리</strong></p>
<p>클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준다면 정적 유틸리티 클래스와 싱글턴은 사용하지 않는것이 좋다. 대신 생성자에 필요한 자원을 넘겨주는 의존 객체 주입 기법을 사용하자.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[GraphQL을 알아보자]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/GraphQL%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@bo-ram-bo-ram/GraphQL%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Tue, 23 Apr 2024 13:33:57 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/d2252a78-e2b9-49be-ab0d-8650e1a14dc4/image.png" alt=""></p>
</br>

<h2 id="intro">Intro</h2>
<p>나는 Spring을 배우면서 REST API를 설계하고 개발해왔다. 왜 REST API냐 하면 제일 많이 사용되니까? 라고 답했지만 나도 흠..왜 REST API를 사용할까? 하는 생각은 지울 수 없었다.</p>
<p>그런 나에게 기회가 되어 SOPT에서 GraphQL 스터디가 열려서 참여하게 되었다! 이름도 처음 들어봤지만 차근차근 알아보도록 하자! 
</br></p>
<h1 id="graphql이란">GraphQL이란?</h1>
<p><strong>페이스북에서 REST API의 한계를 극복하기 위해 만든 API 를 위한 쿼리 언어</strong>라고 할 수 있다. 
</br></br></p>
<h2 id="왜-만들었을까요">왜 만들었을까요?</h2>
<p>REST API의 한계를 극복하기 위해서 만들었는데 어떤 한계를 어떻게 극복했는지 알아보자.</p>
<h3 id="✔️-overfetching"><strong>✔️ Overfetching</strong></h3>
<p>REST API는 <strong>일부 데이터만 필요하다고 해도 resource의 전체 데이터를 불러와</strong>야하고, 프로젝트의 규모가 커지고 방대한 데이터를 다루게 되면 성능 이슈가 발생할 수 있다.</p>
<blockquote>
<p>GQL을 사용하면 <strong><code>필요한 필드만 명시적으로 요청</code></strong>할 수 있기 때문에 <strong><code>HTTP 응답 사이즈를 최소화</code></strong>해서 모바일 환경에서의 부담을 줄일 수 있습니다.</p>
</blockquote>
<h3 id="✔️-underfetching"><strong>✔️ Underfetching</strong></h3>
<p>REST API에서는 특정 데이터를 얻기 위해 <strong>여러 개의 엔드포인트를 통해 데이터를 요청</strong> 해야하는 상황이 있는데 (실제로 내가 진행하는 프로젝트에서도 한 뷰 안에서 여러 api가 호출된다) 네트워크가 좋지 않은 환경에서 <strong>여러 개의 API를 호출</strong>하게 되면 느려질 수 있고 이는 사용자에게 안좋은 인상을 줄 수 있다. </p>
<blockquote>
<p>GQL에서는 필요한 데이터 구조를 정의할 수 있기 때문에 <strong><code>하나의 쿼리로 원하는 여러 데이터들을 한 번에</code></strong> 불러올 수 있다</p>
</blockquote>
<h3 id="✔️-고정-구조-데이터-교환"><strong>✔️ 고정 구조 데이터 교환</strong></h3>
<p>REST API는 클라이언트 요청이 고정된 구조를 따라야 리소스를 수신할 수 있다. 이 엄격한 구조는 사용하기 쉽지만 필요한 데이터를 교환하는데 가장 효율적인 방법은 아니다. 밑에서 더 자세히 알아보자.</p>
</br>
----

<h2 id="뭐가-다를까요">뭐가 다를까요?</h2>
<h3 id="vs-sql">vs SQL</h3>
<p>우선 쿼리 언어하면 제일 먼저 떠오르는 SQL과 비교를 해보자. 둘은 목적에서부터 차이가 존재한다. GQL은 <strong>웹 클라이언트가 데이터를 서버로부터 효율적으로 가져오는 것이 <code>목적</code></strong>인 쿼리 언어이고, </p>
<p>SQL 은 <strong>데이터베이스 시스템에 저장된 데이터를 효율적으로 가져오는 것이 <code>목적</code></strong>인 쿼리 언어이다. 또한 GQL은 타입 시스템을 사용하여 쿼리를 실행하는 서버사이드 런타임이고 특정 DB나 플랫폼에 종속적이지 않다.</p>
<h3 id="vs-rest-api">vs REST API</h3>
<ol>
<li><p><code>Method</code>와 <code>End Point</code></p>
<p> Rest API는 HTTP 요청방식 (GET, POST, PUT DELETE 등)을 사용하여 데이터를 요청하고 응답받는 상황에 따라 다른 Method를 사용해야하고 api별로 각각의 end point를 갖는다. </p>
<p> GQL은 동일하게 HTTP 요청방식을 사용하지만 <strong>POST만 사용하고 단일 end point(<code>/graphql</code>)를 사용</strong>하며 요청 시 body에 Schema에 맞춰 Query를 사용하여 요청하기 때문에 일관성 있는 통신이 가능하다.</p>
</li>
<li><p><code>Response</code>데이터</p>
<p> Rest API의 경우 return 되는 response 데이터가 정해져 있다. 그렇게 때문에 API 응답에 다른 컬럼들이 추가되면 해당 파일을 계속 수정해줘야하고 DTO를 각각 만들어줘야한다. <del>실제로 지금 진행하는 프로젝트에서 점점 response에 하나씩 값이 추가되고 있어서 중복 코드가 포함된 DTO가 늘어나고 있다…</del> </p>
<p> GQL 쿼리로 요청하기 때문에 클라이언트가 원하는 데이터를 뽑아 커스터마이징 해서 사용이 가능하다.</p>
<pre><code class="language-jsx"> Rest API

 class BookDTO {
     Long id;
     String title;
 }

 class BookDTO2 {
     Long id;
     String title;
     String content;
 }</code></pre>
<hr>
<pre><code class="language-jsx"> GraphQL

 query {
   getBook(id: &quot;1&quot;) {
     title
     content
   }
 }</code></pre>
</li>
</ol>
</br>
---
</br>

<h2 id="graphql-무조건-사용해야하나요">GraphQL 무조건 사용해야하나요?</h2>
<p>Rest API의 단점과 능동적인 개발이 가능한 GraphQL을 생각해보면 도입 시 훨씬 효율적으로 개발이 가능하다고 느껴질 수 있지만 GraphQL의 도입 여부는 신중하게 검토해야 한다. GQL 또한 단점이 존재하기 때문이다.
</br></p>
<h3 id="graphgql의-단점">GraphGQL의 단점</h3>
<ol>
<li><p>요청이 3천건이 넘어가면 REST보다 성능이 떨어진다.</p>
</li>
<li><p>단일 End Point와 공개된 스키마로 인한 보안의 위협이 존재한다.</p>
</li>
<li><p>HTTP 내장 캐시 기능을 활용하기 어렵다</p>
</br>
### 도입 전 고민해볼 체크 리스트
</li>
<li><p>우리가 만드는 서비스는 <code>얼마나 크고 복잡한가?</code></p>
</li>
<li><p>요청하는 <code>데이터의 형태가 얼마나 다양한가?</code></p>
</li>
<li><p>데이터 <code>요청 횟수는 얼마나 많은가?</code></p>
</li>
<li><p>우리의 <code>서버 아키텍처는 GraphQL을 도입하기 적절한가?</code></p>
</li>
<li><p>만들고자 하는 서비스가 자원 통신에 집중해야 하는 것이 아닌 <code>화면 구현에 집중해야하는가?</code></p>
<ul>
<li>자원 통신에 집중하는 서비스 : UI를 그릴 필요가 없는경우 ex) 구글맵 연동..</li>
<li>화면 구현에 집중하는 서비스 : 화면을 그리는데 많은 데이터들을 필요로 하는 서비스</br>
</br>


</li>
</ul>
</li>
</ol>
<h2 id="outro">Outro</h2>
<p>간단하게 GraphQL의 배경에 대해 알아봤다. 다음부턴 실제로 사용해보자.</p>
</br>
</br>
참고

<p><a href="https://aws.amazon.com/ko/compare/the-difference-between-graphql-and-rest/">AWS 공식문서</a></p>
<p><a href="https://graphql.org/learn/">GraphQL 공식문서</a></p>
<p><a href="https://enjoydev.life/blog/frontend/11-graphql">REST API에서 GraphQL로의 패러다임 전환</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective JAVA] Item4. 인스턴스화를 막으려거든 private 생성자를 사용하라]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/Effective-JAVA-Item4</link>
            <guid>https://velog.io/@bo-ram-bo-ram/Effective-JAVA-Item4</guid>
            <pubDate>Sun, 21 Apr 2024 06:35:33 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/61bc7333-4ca1-48ef-8976-77faa0737e2f/image.png" alt=""></p>
<h3 id="배경">배경</h3>
<p>정적 메서드와 정적 필드는 OOP에서 미움 받는 형식이지만 아래와 같은 상황에선 쓰임새가 존재한다.</p>
<ol>
<li>기본 타입 값이나 배열 관련 메서드들을 모아놓을 때</li>
<li>특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드를 모아놓고 싶을 때</li>
<li>final 클래스와 관련한 메서드를 모아놓을 때<br/>

</li>
</ol>
<p>하지만 이런 형식은 다음과 같은 이슈가 발생한다.</p>
<blockquote>
</blockquote>
<p>💡 <strong>이슈</strong>
정적 멤버만 담은 위와같은 클래스는 인스턴스로 만들어 쓰려고 설계한 것이 아니지만 컴파일러가 자동으로 기본 생성자를 만들어준다. 또 사용자는 이 생성자가 자동 생성된 것인지 구분이 불가하다.</p>
<p>이때 <strong>추상 클래스</strong>로 만들면 해결이 가능할까? 
<strong>아니다</strong> 하위 클래스를 만들어서 인스턴스화를 하면 우회가 가능하다.
<br/>
그렇기 때문에 컴파일러가 기본 생성자를 만들지 못하도록 명시된 생성자가 없을 경우를 없애자.</p>
<p>이를 위해 <strong>private 생성자를 추가하자</strong></p>
<pre><code class="language-java">public class Utilityclass {
    // 기본 생성자가 만들어지는 것을 막는다（인스턴스화 방지용
    private UtilityClass() {
        throw new AssertionError();
        }
        ... // 나머지 코드는 생략
}</code></pre>
<p>클래스가 인스턴스화는 막아 주지만, 생성자가 존재하는데 호출이 불가능하니까 <strong>주석</strong>을 달아주자.</p>
<p>또한 이렇게 작성한다면 <strong>상속을 불가능</strong>하게 하는 효과도 있다. 모든 생성자는 상위 클래스의 생성자를 호출하게 되는데 이를 private로 선언했으니 상위 클래스의 생성자에 접근하는 길이 막힌다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective JAVA] Item 3. private 생성자나 열거타입으로 싱글턴임을 보증하라]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/Effective-JAVA-Item3</link>
            <guid>https://velog.io/@bo-ram-bo-ram/Effective-JAVA-Item3</guid>
            <pubDate>Sun, 21 Apr 2024 06:31:07 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/99944e65-a07a-4214-a1a1-df2f0ff5d832/image.png" alt=""></p>
<h3 id="배경--싱글턴">배경 : 싱글턴</h3>
<p>싱글턴은 인스턴스를 오직 하나만 생성할 수 있는 클래스다. 이런 싱글턴 클래스는 호출 시 매번 인스턴스를 생성하지 않고 미리 생성해둔 인스턴스를 반환하기 때문에 DB의 Connection Pool에서도 사용하여 관리하는 것이 효율적이다</p>
<p>책에서는 이런 싱글톤을 만드는 방법으로 3가지를 소개하고 있다. 각각에 대해 알아보자.
<br></p>
<ul>
<li><p>싱글톤을 만드는 방법</p>
<ol>
<li><p><strong>public static final 필드 방식의 싱글턴</strong></p>
<pre><code class="language-java"> public class Elvis {
     **public static final Elvis INSTANCE = new Elvis();**
     private Elvis() { ... }
     public void leaveTheBuilding() { ... }
 }</code></pre>
<ul>
<li><p>장점</p>
<ol>
<li><p>해당 클래스가 싱글턴임이 API에 드러남</p>
</li>
<li><p>간결함</p>
<br></li>
</ol>
</li>
</ul>
</li>
<li><p><strong>정적 팩터리 방식의 싱글턴</strong></p>
<pre><code class="language-java"> public class Elvis {
     private static final Elvis INSTANCE = new ElvisO;
     private Elvis（） { ... }
     **public static Elvis getlnstance（） { return INSTANCE; }**
     public void leaveTheBuildingO { ... }

     // 싱글턴임울 보장해주는 readResolve 메서드
     private Object readResolve() {
         // &#39;진짜‘ Elvis를 반환하고, 가짜 Elvis는 가비지 컬렉터에 맡긴다.
         return INSTANCE;
     }
 }</code></pre>
<ul>
<li>장점<ol>
<li>api를 바꾸지 않고도 싱글턴이 아니게 변경 가능</li>
<li>정적 팩터리를 제네릭 싱글턴 패턴으로 변경 가능</li>
<li>정적 팩터리의 메서드 참조를 공급자로 사용 가능<br></li>
</ol>
</li>
</ul>
</li>
<li><p><strong>열거 타입 방식의 싱글턴 &lt;&lt; 제일 추천</strong></p>
<pre><code class="language-jsx"> public enum Elvis {
     INSTANCE;
     public void leaveTheBuilding() { ... }
 }</code></pre>
<ul>
<li>장점 : 간결함 및 추가 노력 없이 직렬화 가능</li>
<li>단점 : 만들려는 싱글턴이 Enum 외의 클래스를 상속해야 한다면 사용 불가<br></li>
</ul>
</li>
</ol>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective JAVA] Item2. 생성자에 매개변수가 많다면 빌더를 고려하라]]></title>
            <link>https://velog.io/@bo-ram-bo-ram/Effective-JAVA-item2</link>
            <guid>https://velog.io/@bo-ram-bo-ram/Effective-JAVA-item2</guid>
            <pubDate>Sat, 20 Apr 2024 01:40:53 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bo-ram-bo-ram/post/285fdab8-19b1-46cf-b0bb-a8e3c1e34585/image.png" alt=""></p>
<h3 id="배경">배경</h3>
<pre><code>선택적 매개변수가 많을 때 적절하게 대응하기 어렵다
&lt;/br&gt;</code></pre><h3 id="선행적-해결-방법">선행적 해결 방법</h3>
<ol>
<li><p>** 점층적 생성자 패턴**
: 필수 매개변수만 받는 생성자 ..&gt;..&gt;필수 매개변수 +  선택 매개변수 전체를 받는 생성자</p>
<pre><code class="language-java">public class NutritionFacts {
  private final int servingSize; // （ml, 1회 제공량） 필수
  private final int servings; // （회, 총 n회 제공량） 필수
  private final int calories; // （1회 제공량당） 선택
  private final int fat; // （g/1회 제공량） 선택
  private final int sodium; // （mg/1회 제공량） 선택
  private final int carbohydrate; // （g/1회 제공량） 선택

  public NutritionFacts（int servingSize, int servings） {
      this（servingSize, servings, 0）;
  public NutritionFacts（int servingSize, int servings, int calories） {
      this（servingSize, servings, calories, 0）;
  }
  public NutritionFacts(int servingSize, int servings,int calories,
  int fat) {
      this(servingSize, servings, calories, fat, 0);
      }
  public NutritionFacts(int servingSize, int servings, int calories,
  , int fat, int sodium) {
      this(servingSize, servings, calories, fat, sodium, 0);
      }
  public NutritionFactsdnt servingSize, int servings, int calories,
      int fat, int sodium, int carbohydrate) {
      this.servingSize = servingSize;
      this.servings = servings;
      this.calories = calories;
      this.fat = fat;
      this.sodium = sodium;
      this.carbohydrate = carbohydrate; 
  }
}</code></pre>
</br>

<ul>
<li>단점<ol>
<li>설정을 원하지 않는 매개변수까지 포함해야하고 어쩔 수 없이 그런 매개변수에도 값을 지정해줘야 한다. </li>
<li>매개변수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵고 에러 발생 위험이 높다.</li>
</ol>
</li>
</ul>
<blockquote>
</blockquote>
<p> 💡 확장이 어렵다</p>
</li>
</ol>
<ol start="2">
<li><p><strong>Java Beans Pattern</strong></p>
<p>: 매개변수가 없는 생성자로 객체 생성 후 setter를 통해 매개변수 값 설정</p>
<pre><code class="language-java">public class NutritionFacts {
  // 매개변수들은 （기본값이 있다면） 기본값으로 초기화된다.
  private int servingSize = -1; // 필수; 기본값 없음
  private int servings = -1; // 필수; 기본값 없음
  private int calories = 0;
  private int fat = 0;
  private int sodium = 0;
  private int carbohydrate = 0;

  public NutritionFactsO { }
  // 세터 메서드들

  public void setServingSize(int val) { servingSize = val; }
  public void setServings(int val) { servings = val; }
  public void setCaloriesdnt(int val) { calories = val; }
  public void setFatdnt(int val) { fat = val; }
  public void setSodium(int val) { sodium = val; }
  public void setCarbohydrate(int val) { carbohydrate = val; }    
}</code></pre>
<ul>
<li>단점<ol>
<li>객체 하나를 만들기 위해 메서드를 여러개 호출해야 함</li>
<li>객체가 완전히 생성되기 전까지 일관성이 무너진 상태</li>
</ol>
</li>
</ul>
<blockquote>
</blockquote>
<p>💡 일관성이 깨지고 불변으로 만들 수 없다.</p>
</li>
</ol>
<h3 id="builder-pattern">Builder Pattern</h3>
<p>  : 필수 매개변수로 생성자를 호출해 빌더 객체를 얻음</p>
<p>   클래스 안에 정적 멤버 클래스로 생성</p>
<pre><code class="language-jsx">public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // 필수 매개변수
        private final int servingSize;
        private final int servings;
        // 선택 매개변수 - 기본값으로 초기화한다.
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;
        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }
    public Builder calories(int val)
        { calories = val; return this; }
    public Builder fat(int val)
         { fat = val; return this; }
    public Builder sodium(int val)
         { sodium = val; return this; }
    public Builder carbohydrate(int val)
        { carbohydrate = val; return this; }
    public NutritionFacts build() {
        return new NutritionFacts(this);
}
    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
        }</code></pre>
<ul>
<li>장점<ol>
<li>빌더의 setter 메서드들은 빌더 자신을 반환하기 때문에 연쇄적으로 호출할 수 있다.
이런 방식을 메서드 호출이 흐르듯 연결된다.</li>
<li>계층적으로 설계된 클래스와 함께 쓰기에 좋다</li>
</ol>
</li>
<li>단점<ol>
<li>객체 생성 전 빌더를 먼저 만들어야한다</li>
<li>매개변수가 4개 이상은 되어야 값어치가 있다.</li>
</ol>
</li>
</ul>
<blockquote>
</blockquote>
<p>☀️ 핵심정리
생성자나 정적 팩터리가 처리해야하 할 매개변수가 많다면 빌더 패턴을 선택하는게 더 낫다. 매개변수 중 다수가 필수가 아니거나 같은 타입이면 특히 더 그렇다. 빌더는 점층적 생성자보다 클라이언트 코드를 읽고 쓰기가 훨씬 간결하고 자바빈즈보다 훨씬 안전하다.</p>
]]></description>
        </item>
    </channel>
</rss>