<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sally.dev</title>
        <link>https://velog.io/</link>
        <description>sally의 법칙을 따르는 bug Duck</description>
        <lastBuildDate>Sat, 13 Jul 2024 09:37:11 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sally.dev</title>
            <url>https://images.velog.io/images/sally_devv/profile/5b03ab53-686e-43a7-8408-4e0cae93361f/sally.PNG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sally.dev. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sally_devv" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[블로그 옮겼습니다]]></title>
            <link>https://velog.io/@sally_devv/%EB%B8%94%EB%A1%9C%EA%B7%B8-%EC%98%AE%EA%B2%BC%EC%8A%B5%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@sally_devv/%EB%B8%94%EB%A1%9C%EA%B7%B8-%EC%98%AE%EA%B2%BC%EC%8A%B5%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Sat, 13 Jul 2024 09:37:11 GMT</pubDate>
            <description><![CDATA[<p>미디엄
<a href="https://medium.com/@sally.devz">https://medium.com/@sally.devz</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Springboot 세팅]]></title>
            <link>https://velog.io/@sally_devv/Springboot-%EC%84%B8%ED%8C%85</link>
            <guid>https://velog.io/@sally_devv/Springboot-%EC%84%B8%ED%8C%85</guid>
            <pubDate>Mon, 01 Jan 2024 11:55:48 GMT</pubDate>
            <description><![CDATA[<p>Spring Initializr 에는 더이상 2.X 대 생성이 없다보니, 
아직 3.X는 미루고 있는 상황에서 gradle 이나 모듈화를 공부 할 좋은 기회가 됐다고도 생각(?..)으로 직접 gradle 을 세팅해보다, 
사실 그렇게 복잡하지도 않다는 생각으로 간략히 정리해 봅니다.</p>
<blockquote>
<p>복잡하고, 자세히 알아야 하는 부분들은 알려주시면 감사하겠습니다. 🧡</p>
</blockquote>
<hr>
<h3 id="ps">p.s.</h3>
<ul>
<li><p>intellij Ultimate 기반으로 작업 합니다.
(Community 버전만 사용했던 경험으로 알게 모르게 작은 부분들에서 헤맸었어서, 다를 수도 있을 거 같습니다.)</p>
</li>
<li><p>intellij도 버전마다 차이 있는 걸 요즘 많이 느끼는 데요. (-_-)
사실, 개인적으로 최신 버전은 멈춤 현상도 잦아서 버전을 낮추기도 했었던 경험으로 개인적으로 권장하지 않기도 하며, 차이가 있을 수 있습니다.</p>
</li>
</ul>
<hr>
<h2 id="intellij-프로젝트-생성하기">intellij 프로젝트 생성하기</h2>
<p>File &gt; New &gt; Project </p>
<ul>
<li>창이 뜨면, 가장 상단의 <code>New Project</code> 에서 Name 부분만 설정 해서 <code>create</code> 합니다.</li>
</ul>
<blockquote>
<p>모듈화 기반으로 가장 최상위 모듈설정 예시 입니다.
모듈화가 아닌 경우 해당 build.gradle 에 모든 dependency를 추가하시면 됩니다.</p>
</blockquote>
<h3 id="buildgradle-setting">build.gradle setting</h3>
<p>SpringBoot 공식 사이트에서 (2023.01.01 기준) <code>GA</code> 로 2.X 대 버전으로 2.7.18이길래 참고해서 작업 했습니다.</p>
<p><a href="https://docs.spring.io/spring-boot/docs/2.7.18/reference/html/dependency-versions.html">Dependency Versions</a></p>
<ul>
<li>gradle 및 dependency 버전 정보를 얻을 수 있습니다. <ul>
<li>해당 버전 정보를 <a href="https://mvnrepository.com/">Maven Repository</a> 에서 검색해서 복붙 합니다.<ul>
<li>더 좋은 사이트 공유 환영 합니다. ^^ .. 계속 지원 되기도 하고, 전 여기가 편한 거 같아요...</li>
</ul>
</li>
<li>검색 되지 않는 그외 버전들은 Maven Repository에서 최신 아닌 버전들을 선택하거나, 검색하면서 참고 했습니다.</li>
</ul>
</li>
<li>Java 버전 등은 Sprigboot 공식문서에서 허용 가능한 범위들을 확인해서 자유롭게 추가해도 됩니다. (전 요즘 17 😅)<ul>
<li>Springboot 장점이죠. Spring framework와 다르게 간편! 세팅 할 수 있습니다!</li>
</ul>
</li>
</ul>
<p><a href="https://docs.spring.io/spring-boot/docs/2.7.18/reference/html/getting-started.html#getting-started.installing">springboot Docs</a>
<img src="https://velog.velcdn.com/images/sally_devv/post/4fd473ad-8360-4fd5-804c-7148f10f8856/image.png" alt=""></p>
<p>build.gradle의 기본 frame 인데요.. 정말 기본적입니다. ㅎㅎ</p>
<pre><code>buildscript {
    dependencies {

    }
}

plugins {

}

compileJava {
    sourceCompatibility = 17
    targetCompatibility = 17
}

apply plugin: &#39;java&#39;
apply plugin: &#39;java-library&#39;
apply plugin: &#39;org.springframework.boot&#39;
apply plugin: &#39;io.spring.dependency-management&#39;

group = &#39;org.example&#39;
version = &#39;1.0-SNAPSHOT&#39;

repositories {
    mavenCentral()
}

dependencies {

}

test {
    useJUnitPlatform()
}
</code></pre><p>해당 모듈 정상 동작하는지 간단히 확인 해 봅니다.</p>
<ul>
<li>build.gradle 내 group 경로에 해당 모듈에 맞는 애플리케이션 클래스와 간단한 컨트롤러 생성해서 확인 합니다.<ul>
<li>기본은 Main 클래스만 있어서 스프링 동작시 주요한 @SpringBootApplication, SpringApplication.run(TestApplication.class, args); 가 없습니다.<ul>
<li>관련 정보를 알고 싶으시면, <a href="https://www.inflearn.com/course/%ED%86%A0%EB%B9%84-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%EC%9D%B4%ED%95%B4%EC%99%80%EC%9B%90%EB%A6%AC/dashboard">내돈내산 - 토비의 스프링 부트 - 이해와 원리</a> 추천 합니다.</li>
</ul>
</li>
<li>아직 DB 연동이 안 된 경우, JPA 등은 주석해서 확인 합니다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">@SpringBootApplication
public class TestApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }
}</code></pre>
<blockquote>
<p>그 외 모듈화로 bootJar 등 추가 할 수 있는데, 이 부분은 다른 블로그 등을 참고해 주세요. (-_- 배포시 달라질 수도 있어서 ㅎㅎㅎㅎㅎ 전 아직 부족한게 많은 거 같습니다. 개인프로젝트로 급하시면 그냥 프로세스 다 돌리세요 ㅎㅎㅎㅎ 비밀..)</p>
</blockquote>
<hr>
<h2 id="하위-모듈">하위 모듈</h2>
<p>프로젝트명에서 마우스+우 클릭 </p>
<blockquote>
<p>Open Module Settings &gt; Project Settings &gt; Modules</p>
</blockquote>
<p><code>+</code> 버튼을 클릭 &gt; New Module 로 하위 모듈을 생성 합니다.</p>
<ul>
<li>Name : 생성할 모듈명</li>
<li>Parent : 상위 모듈 (보통 앞서 생성한 프로젝트로 둡니다.)</li>
<li>Advanced Settings &gt; GroupId : 생성된 하위 모듈의 작업 패키지명</li>
</ul>
<p>생성된 해당 모듈의 오른 쪽에 Sources, Paths, Dependencies 등</p>
<ul>
<li>Paths 등은 Springframework 라면 톰캣 등 했던 거 같아요</li>
<li>Dependencies에서 자바 버전을 미리 확인 해도 좋습니다.</li>
</ul>
<h3 id="buildgradle-setting-1">build.gradle setting</h3>
<p>정리? </p>
<ul>
<li>기존 build.gradle 을 복사해 와서 부분 수정하고 제거하고 추가하며 생성한 모듈에 맞게 작업 합니다.</li>
<li>생성한 하위 모듈의 groupid의 패키지와 맞게 build.gradle 의 group을 정의 합니다.<ul>
<li>초반에는 너무 엄격하게 중복 되지 않게 하려 고민하지 않는 게 좋은 거 같습니다.</li>
</ul>
</li>
<li>최상위 <code>src</code> 를 사용하지 않는다면 삭제 하셔도 됩니다. </li>
</ul>
<p>앞서 확인 했던 방식으로 스프링 애플리케이션 클래스와 간단한 컨트롤러로 동작 확인 하면 됩니다.</p>
<p>자세하게 라이브러리 별로 설명하지 않았는데요.
여러 테스트 해보면서 확인 해보시면 된다고 생각 했습니다.</p>
<p>처음에 gradle, 모듈화 구조, 아키텍처 ..
이렇게 가다가 계속 가고(?)
배포 환경에서 달라지기도 하고, 공부할 건 늘어나고 그랬는데요, 
간략히 모듈화 방식만 익혀 두고, 천천히 필요한 부분들만 추가 해보는 게 좋다는 생각이 들었습니다.</p>
<ul>
<li>관련 강의들도 많이 있는 거 같아요. 기본적인 거 해보고 학습 하시면 좋을 거 같습니다.</li>
<li>자세한 정보들은 검색으로도 가능 했었어요. </li>
</ul>
<p>제가 간략히 확인하려 남겨둬서 부족한 부분이 많더라도 양해 부탁드립니다.
감사합니다.</p>
<hr>
<h2 id="with-version-3xx">with version 3.x.x</h2>
<p>springboot 3.X 버전에서 몇 가지 차이점들 추가 해 봅니다. </p>
<ul>
<li>swagger<pre><code>  implementation &#39;org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0&#39;</code></pre></li>
<li>JDK V17 이라면, jakarta ! (no javax)</li>
</ul>
<p>reference.  <a href="https://docs.spring.io/spring-boot/appendix/dependency-versions/coordinates.html">springboot <code>v3.3.2</code> : Managed Dependency Coordinates</a></p>
<hr>
<p>참고 사이트</p>
<p>그 외, 참고 사이트</p>
<ul>
<li><a href="https://github.com/spring-projects/spring-boot/releases/tag/v2.7.18">github.com/spring-projects/spring-boot/releases/tag/v2.7.18</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[결제 연동]]></title>
            <link>https://velog.io/@sally_devv/%EA%B2%B0%EC%A0%9C-%EC%97%B0%EB%8F%99</link>
            <guid>https://velog.io/@sally_devv/%EA%B2%B0%EC%A0%9C-%EC%97%B0%EB%8F%99</guid>
            <pubDate>Sun, 17 Dec 2023 06:03:11 GMT</pubDate>
            <description><![CDATA[<p>10월 쯤 부터 대략 2개월 정도 작업 했던 부분을 간략히 정리 해보고자 한다.</p>
<h2 id="결제-연동-">결제 연동 ?</h2>
<p>대부분 결제 연동을 모르셨다. 
나도 몰랐다</p>
<p>주문과 결제는 분리 되어 있다.
그리고 토스 페이먼츠의 위젯은 기존 보다 1단계를 더 추가 했다.</p>
<ul>
<li>결제 처리 전 필요한 인증과 결제 승인 처리가 분리 되었다.</li>
<li>카드 결제의 경우 입력된 정보를 카드사에 요청해서 인증을 먼저 거친다.<ul>
<li>분리가 안 되어 있다면, 카드 인증 단계에서 에러 발생시 결제 후 연결되는 주문 처리, 상품 지급 처리, 회원 관련 처리 등이 함께 묶여 있게 된다.</li>
<li>분리로 인한 이점은 여러 try 가 가능 해진다. </li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>주문 → 결제 요청 → 결제</strong></p>
</blockquote>
<h3 id="결제-연동은-뭘까-">결제 연동은 뭘까 ?</h3>
<p>주문을 했다고 해서 모든 주문들이 바로 결제로 연결 되지 않는다.</p>
<p>결제 시점에 사용자는 주문 정보를 삭제하거나 변경 할 수 있다.</p>
<p>무언가 기본적으로 제공되지 않을까 싶은 주문의 삭제, 변경은 회사 주문 서비스별로 달라질 수 있다. </p>
<ul>
<li>주문 정보를 일회성으로 둔다면 ?</li>
<li>주문과 결제 처리가 정말 일관된 작업 프로세스로 있는 서비스는 의외로 없을 확률이 높다. <ul>
<li>이 점은 새롭고 재밌어지기 시작 했다.</li>
</ul>
</li>
</ul>
<h3 id="주제-목표-보다-분석이-중요하다">주제, 목표 보다 분석이 중요하다.</h3>
<p>(이건 내 개인적인 생각이다. -_-)</p>
<p>끝까지 혼란이 많았다. </p>
<p>지나고 보니 분석해 두라고 봤던 코드는, 의미가 없었고,
중요하다 생각해서 분석 시작한 코드는 안 봐도 된다거나 등 (정말 중요해보여서 걍 봤다)
그리고 토스 결제 연동과 서비스 비즈니스 로직, 시간 내 해결하기 위한 전체 로직의 변경 등</p>
<p>새롭게 추가하고자 했던 프로토콜 관련 검증 로직은 쓰이지 않게 되었다.</p>
<ul>
<li>설정에 지정된 값들을 정규식으로 그룹핑으로 묶어 스프링 초기화 단계에 검증 패턴을 생성시켜서, 호출 시점마다 검증되게 했다.<ul>
<li>안 쓰이는 코드 여기 올려도 될까요, 안 될까요</li>
<li>쨌든, 당시 작업에만 집중했는데, 돌아보고 싶던 내용들을 복습하면서 개인적으로 개선점을 찾으면 좋겠다</li>
</ul>
</li>
</ul>
<p>분석의 중요점은 작업하면서 계속 느꼈던 점 이다.
정리한 글은 보편적으로 고려할 점들을 적어 본다.</p>
<h4 id="결제-처리-하기-전-결제-연동-작업에서-중요한-것">결제 처리 하기 전, 결제 연동 작업에서 중요한 것</h4>
<p>검증</p>
<ul>
<li><p>주문 정보 검증</p>
<ul>
<li>회사 상품 가격이 시세정보가 반영 됐는지 나중에 알면서 해당 부분을 추가 작업 했다.</li>
<li>이미 결제 처리가 안 된 주문 이어야 한다<ul>
<li>주문의 상태값이 어떻게 정의 되어 있는가</li>
<li>결제 상태값이 어떻게 정의 되어 있는가</li>
</ul>
</li>
</ul>
</li>
<li><p>가격 검증</p>
<ul>
<li>서비스 별 상품에 대한 가격 정의는 어떻게 되어 있는가</li>
<li>할인 정책은 어떻게 지원 되는가</li>
</ul>
</li>
</ul>
<p>가격 검증이 레거시 코드로 인 해, 하나의 역할로 묶어내기 위한 지구력이 필요 했었다.</p>
<h3 id="레거시가-왜-불편-했을까">레거시가 왜 불편 했을까?</h3>
<ul>
<li>할인 정책에 대한 고유값을 공통으로 쓰지 않는다<ul>
<li>이건 할인 계산 인가? 아니었다 -_-</li>
<li>가격이 맞지 않습니다. 나중에 알 거예요. 일단 작업 하세요.<ul>
<li>정말 맞지 않습니다.... 3주 지연 됐다 ㅠ (먼저 정리해서 보여드렸어야 했는데, 나도 혼동되는 점이 많다보니 아쉬웠다)</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>여러 할인 계산 로직들이 있었고, 하나의 메서드 로직이 답이었다.
하지만 이는 나중에 결제 처리시 할인 검증 로직에서 또 다른 부분이 있으면서, 끝까지 신경 쓰이게 했다.</p>
<ul>
<li><strong>틀리더라도 일단 하나로 개념화 하고자 했다.</strong><ul>
<li><strong>고치기 쉽다</strong> : 잘못된 결과 확률이 높아지면서, ...이런 이유로 추상화 작업을 ㅎㅎㅎ</li>
<li>결제 요청 전 가격 검증과 결제 처리시 가격 검증 로직이 다른점이 생겼는데, abstract factory pattern ? 생각하면서 작업 했다.<ul>
<li>이는 전체 로직이 파악 되고, 토스 페이먼츠 결제 처리 로직이 정리 되고서야 작업 할 수 있었다. </li>
<li>작은 Bigdecimal 반환 처리 코드 실수로 에러가 있었지만, 로직 아닌 포인트 에러사항이라는 문제점에 대해, 빠르게 찾고 수정 가능 했던 점에서 괜찮게 한 거 같단 생각이 들긴 했다.</li>
<li>중요한 건 각 호출 시점 별 필요한 추상체 calculator 와 validator 각각을 연결하고 불러오는 것이 었는데, 이는 내가 아니라 다른 개발자 분이 같은 추상 타입이어도 다른 로직이라는 점을 알 수 있게 해야 한다는 생각 이었다.</li>
<li>별도의 서비스 layer 추가하여 DI 로 작업 했다</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>... 쨌든 레거시 코드의 절대적이었던, 모순</p>
<blockquote>
<p>자세히 보아야 예쁘다
오래 보아야 사랑 스럽다
너도 그렇다</p>
<p>&quot;자세히 보아야 예쁘다&quot;, 나태주</p>
</blockquote>
<p>(끝내 슬픈 건 나도 레거시)</p>
<hr>
<h2 id="결제-요청">결제 요청</h2>
<p>다시 적어 본다</p>
<blockquote>
<p>주제, 목표 보다 분석이 중요하다.</p>
</blockquote>
<h3 id="결제란">결제란?</h3>
<p>가격만 결제 처리 하면 되는가? </p>
<ul>
<li>회사에서 제공 되는 모든 서비스가 지급 적용 된다.</li>
<li>회사에서 제공하는 회원 서비스도 지급 적용 된다.</li>
<li>회사에서 필요한 수익, 세금, 정산과 연결 된다.</li>
<li>결제 처리는 회사에서 제공하는 서비스/상품에 대한 로직으로 동작 해야 한다.</li>
<li>etc...</li>
</ul>
<p>나는 토스 결제 처리 부분만 작업 했다.
어떤 서비스 단위 결제작업만 하면 된다 하셨지만,
해당 서비스는 고객 관점에서만 정의된 개념이고, 회사 도메인이 정의되거나 로직에 정의된 서비스가 아니었다.</p>
<p>또 다시 적어본다</p>
<h3 id="레거시가-왜-불편-했을까-1">레거시가 왜 불편 했을까?</h3>
<p>왜 고객에게 제공되는 해당 서비스만 작업 할 수가 없었을까 ?
정의된 도메인이란 무엇인가 ?</p>
<p>먼저, 회사 내에서도 상품 서비스 정의에 대한 문제점이 있어 개선 작업 중이었다.
나는 작은 부분이었지만, 상품의 일부를 고객 서비스화 했달까?</p>
<p>이게 레거시 코드에서는 오래전에 사라진 할인관련 정책인지, 상품 정의상 기본으로 제공되는 정책인지 모르는 절차지향적 코드와 기존 이용중이던 PG 처리 파라미터 의도된 절차지향적 코드는 .... </p>
<ul>
<li>의미를 아는 사람이 없었다 -_- (작업자는 퇴사, 프로시저는 진짜 ... ㅎㅎ)</li>
</ul>
<h4 id="절차지향적-코드와-결제">절차지향적 코드와 결제?</h4>
<p>예를 들어 무통장 입금하지 않은 가상계좌 번호를 할당 받은 상태라면, 결제 처리에서 어떻게 동작할까?</p>
<ul>
<li><p>아무것도 작업 할 게 없다? ㅎㅎㅎ</p>
<ul>
<li>일정 기간 동안 일정양의 서비스를 사용할 수 있는 이용권을 구입 했다면, 해당 이용권 이용한 주문 상품에 대한 지급 적용이 있다면?</li>
<li>이게... 무통장 입금하지 않은 가상계좌 번호를 할당 받은 상태로 연결 되어 있었다.<ul>
<li>절차지향적 코드는 뒤에 이어지는 할인이나 포인트로 완전 결제 처리 되는 경우와 실제 결제 처리지 지급/적용 되는 경우, 포인트 지급까지 묶여 있어서 무통장 입금하지 않은 가상계좌 번호를 할당 받은 상태와의 연관성은 .....</li>
</ul>
</li>
</ul>
</li>
<li><p>디버깅이 안 됐다 ㅎㅎㅎ</p>
<ul>
<li>코드만 보면서 분리 작업을 시작 했다</li>
</ul>
</li>
<li><p>내가 임의로 넣고 빼고 할 로직이 아니었다</p>
</li>
<li><p>테스트도 안 되고 디버깅도 안 되고</p>
</li>
<li><p>기존 결제 방식의 파라미터 값 의존한 로직은 토스와도 달랐고, 의미 확신은 더욱...</p>
</li>
<li><p>결제 파라미터가 앞단에서 별도로 무통장, 카드 결제처리 후 전달 되는 경우란 걸 나중에야 파악 됐었다 ㅠ</p>
</li>
</ul>
<p>기존 코드는 디버깅이나 테스트가 불가 했지만,
내가 작업한 API로 되면서 테스트와 디버깅이 가능 해졌으나</p>
<p>나는 ...지금 생각하면 그냥 복붙하지 왜 그랬는가..... </p>
<ul>
<li>토스 결제 승인 처리</li>
<li>토스 결제 결과(결제방식)에 따른 결제 처리</li>
<li>서비스 지급/적용</li>
<li>회원 관련 포인트 등 지급/적용</li>
</ul>
<p>위 로직을 분리 했다.</p>
<p>그리고 될까 싶은 추상화가 가능 해졌다
마지막 날 내 지저분한 코드로 마무리를 지었지만 ㅠ ㅠ (너무 싫다)</p>
<p>처음에는 디자인 패턴을 생각하고 작업 했었는데, 
점차 조용호님의 오브젝트 뒷 챕터 코드와 유사하게 갔었다 (CH. 14 ?)</p>
<ul>
<li>condition<ul>
<li>크게 2가지로 분리된다.</li>
<li>토스 결제 방식에 따른 조건 아니면, 회사 제공 서비스 지원 조건 (레거시 기반이다 보니 .. 이는 수정 되더라도 별도의 클래스로 분리되어서 수월 해질 수 있게 하는게 중요해졌다)</li>
</ul>
</li>
<li>process<ul>
<li>이게 좀 생각처럼 안 됐었는 데, 예시로 정리 해 본다</li>
</ul>
</li>
</ul>
<p>예로, 무통장 가상계좌 할당 condition, 무통장 입급 condition, 카드결제 condition 일 경우 </p>
<table>
<thead>
<tr>
<th></th>
<th>결제 처리</th>
<th>서비스 지급/적용</th>
</tr>
</thead>
<tbody><tr>
<td>무통장 가상계좌 할당 condition</td>
<td>❌</td>
<td>⭕</td>
</tr>
<tr>
<td>무통장 입급 condition</td>
<td>⭕</td>
<td>⭕</td>
</tr>
<tr>
<td>카드결제 condition</td>
<td>⭕</td>
<td>⭕</td>
</tr>
</tbody></table>
<ul>
<li><p>결제 처리는 모두 동일 한가 ? 아뇽</p>
<ul>
<li>무통장 입금은 PG 사 저장, 별도 무통장 관리 테이블 저장, 주문과 결제 상태 처리 등</li>
<li>카드 결제 처리는 PG 사 저장 (무통장 처럼 별도 테이블이 없을 수 있다), 주문과 결제 상태 처리 등</li>
</ul>
<br>


</li>
</ul>
<p>처리 로직이 겹치는 게 있으면서 달라진다. </p>
<ul>
<li>재사용성과 조합이 중요해 보였다</li>
</ul>
<p>설계된 하나의 process로 어떻게 각각 다른 필요 인자값들을 전달할 것인가가 난관 이었는데 ,</p>
<ul>
<li>Process 구현 타입들을 각각 DI </li>
<li>정보 전문가</li>
<li>command</li>
</ul>
<p>처음에는 이렇게 가야 할 거 같은데, 구체적으로 정리되지 않은 것들이 점점 그 방향으로 가고 있어서 오히려 걱정이 됐었다.
사실 난 여기서 지급/적용 로직까지 연결 시켜서 
결제 실패나 중간 API 호출시 실패 등에 대한 트랜잭션 롤백 처리에 대해 각 서비스별로 동작하게 하자는 생각 이었다. (독립 보단....)</p>
<p>이를 위해서 2가지 준비작업이 필요 했다.</p>
<ul>
<li>API 호출시 에러 정의와 추상화</li>
<li>에러 타입과 파라미터에 의한 원복</li>
<li>아니면, 2PC, 사가 패턴 등 있다</li>
</ul>
<hr>
<h2 id="안정성-정확성">안정성, 정확성</h2>
<h3 id="재시도">재시도</h3>
<p>결제시 에러 등으로 인한 고객 이탈률을 고려하게 되었다.
처음에는 모듈화 등 외부 호출을 재시도 하는 것에만 초점을 맞췄는데,</p>
<ul>
<li>호출하는 모듈의 지속적인 에러 발생으로 인한 번복되는 재시도 호출</li>
<li>외부 서비스 장애로 인한 지연 현상과 지속된 호출</li>
</ul>
<p>재시도 호출은 위험했다.</p>
<ul>
<li>실패 케이스 세분화가 중요하다<ul>
<li>시스템 전체 설정을 손대기 버거울 때 메서드 별로 재시도 해주는 Spring-retry 가 좋아 보였다. </li>
<li>예외 클래스와 전달 매개변수 기반 동작<ul>
<li>에러 발생 건에 대한 세부적인 예외 클래스 정의로 retry 케이스를 구축 할 수 있다</li>
<li>매개변수를 공통화 하면 좋다</li>
</ul>
</li>
<li>try-catch 문을 공통화<ul>
<li>Function 과 generic 기반으로 호출하는 여러 adapter 들을 공통으로 이용할 수 있게 했다.<ul>
<li>특정 예외 발생 클래스만 이용하게 해서 Spring-retry 와 연결하고, try-catch 문 코드 간편성도 높이고자 했다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="가독성-협업">가독성, 협업</h3>
<p>코드 리뷰로 토스 구현체를 넣은 점을 추상화 해서 변경 영향 줄이는게 어떻냐는 의견이 있었다.</p>
<ul>
<li>현 시스템은 여러 서비스 들 중 1개씩 단계별로 결제사 변경 중이다.</li>
<li>다른 팀의 개발자들은 어떤 서비스가 새 결제 방식을 이용하는지 모른다.</li>
<li>혹시 코드 타고 오더라도 빠르게 판단하게 하는게 좋다고 생각했다.</li>
</ul>
<p>트레이드 오프로 인한 장점, 전략 등의 차이점을 느꼈었다.
나는 성능보다는 가독성, 협업을 중시 한다는 생각 이었다.</p>
<ul>
<li>성능은 개인적으로 시도할 수 있는 영역내에서만 좋다고 생각된다. (역할과 책임 분리로 SRP 내에서 동작 등)</li>
</ul>
<hr>
<h2 id="ps-토스페이먼츠">p.s 토스페이먼츠</h2>
<p><a href="https://docs.tosspayments.com/guides/payment-widget/integration">토스페이먼츠</a></p>
<p>PG 사가 달라지면 ?</p>
<p>이런 부분은 미리 알면 먼저 정리해서 빠른 작업이 가능 했을 듯 싶다</p>
<ul>
<li>은행/증권사 코드가 달라진다.<ul>
<li>금융결제원 공식 코드가 있는데, 토스는 토스의 코드로 전달</li>
</ul>
</li>
<li>다른 PG 사와 다르게 결제 상태값이 달라진다<ul>
<li>기존 PG 사 연관된 코드, 타입 등 정리 필요</li>
<li>결제 상태 값에 따른 로직으로 연결되니, 정의가 명확하면 검증, 로직이 수월 해진다.</li>
</ul>
</li>
</ul>
<hr>
<p>좀 씁쓸하다. 지루하고 ...
일단, 개인적으로 잘 채워나가며 한 해 마무리 하고자 한다
새로운 방향을 본 거면 된 거겠지. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 이팩티브 자바 읽다가 리팩토링]]></title>
            <link>https://velog.io/@sally_devv/TIL-%EC%9D%B4%ED%8C%A9%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-%EC%9D%BD%EB%8B%A4%EA%B0%80-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@sally_devv/TIL-%EC%9D%B4%ED%8C%A9%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-%EC%9D%BD%EB%8B%A4%EA%B0%80-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</guid>
            <pubDate>Sun, 27 Aug 2023 05:04:32 GMT</pubDate>
            <description><![CDATA[<p>다른 뭔가를 하기 싫어서 책을 훑어보다가 시작</p>
<p>item 1<del>10 까지 보면서 관련되어 생각나는 코드들 수정하다가..
(이팩티브 책은 이런식으로 쭉</del> 못 읽는)
item6 에서 그렇게 다른 길로 갔습니다. ㅎㅎㅎ</p>
<p>의식의 흐름대로 리팩토링 과정이니 ... 
좋은 지식 알려주시면 감사하겠습니다. 😅</p>
<h1 id="item-6-불필요한-객체-생성을-피하라">ITEM 6. 불필요한 객체 생성을 피하라</h1>
<p>(사실 ITEM 6 가 주제는 아닙니다.... 🙄)
<a href="https://github.com/sally-ksh/issue-tracker/blob/team-36/BE/src/main/java/com/sh/issuetracker/issue/search/IssueSearchParam.java">기존 문제의 코드 🌱</a></p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/45fa2bb5-62d4-4ce0-adf1-ecbd07c3a951/image.png" alt=""></p>
<p>부담되나 봐요 
요즘은 내부 좀 보고 써야 하는 클래스들이 보이네요.(어렵다)</p>
<p>( Pattern 만 기억 나고 … 다른 부분도 생각 났다면 좋았을 텐데(?)…. 🙈)</p>
<h2 id="추상-클래스">추상 클래스</h2>
<p>인터페이스에 Pattern 초기화 시켜놓는 게 불편하고,</p>
<p>default 메서드 쓰면서 하나의 parsing을 추상 클래스 놓고 완성한 searchParameter 반환 위해 create 위임 하자니 억지로 마춘 듯 하고
그냥 추상클래스로 리팩토링 했습니다</p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/543195b5-8454-4b65-a1c5-166b04bccf1e/image.png" alt=""></p>
<pre><code class="language-java">public abstract class IssueSearch {
    public IssueSearchParameter from(IssueSearchRequest request) {
        final String text = request.getText();
        final int firstKeySeparatorIdx = text.indexOf(&#39;+&#39;);
        final IssueStatus status = toIssueStatus(text.substring(0, firstKeySeparatorIdx));
        final String behindSearchWords = text.substring(firstKeySeparatorIdx);
        final Map&lt;SearchKeyType, String&gt; parameters = this.parsing(behindSearchWords);
        return IssueSearchParameter.of(status, parameters);
    }

    protected abstract Map&lt;SearchKeyType, String&gt; parsing(String searchWords);</code></pre>
<pre><code class="language-java">public class IssueSearchParsing extends IssueSearch {
    private static final Pattern PATTERN_OF_KEY = Pattern.compile(&quot;[+](.*?)[:]&quot;);
    private static final Pattern PATTERN_OF_VALUE = Pattern.compile(&quot;[:](.*?)[+]&quot;);

    @Override
    protected Map&lt;SearchKeyType, String&gt; parsing(String searchWords) {
        final Matcher matcherOfKey = PATTERN_OF_KEY.matcher(searchWords);
        final Matcher matcherOfValue = PATTERN_OF_VALUE.matcher(searchWords);
        SearchKeyMap searchKeyMap = SearchKeyMap.newInstance();

        while (matcherOfKey.find() &amp;&amp; matcherOfValue.find()) {
            String key = matcherOfKey.group(1);
            String value = matcherOfValue.group(1);
            if (Strings.isBlank(key) || Strings.isBlank(value)) {
                break;
            }
            checkSearchKey(key, value);
            searchKeyMap.updateValue(SearchKeyType.getKey(key), trimComma(value));
        }

        final String lastKey = matcherOfKey.group(1);
        final String lastValue = searchWords.substring(searchWords.lastIndexOf(&#39;:&#39;) + 1);
        searchKeyMap.updateValue(SearchKeyType.getKey(lastKey), trimComma(lastValue));
        return searchKeyMap.parameters();
    }</code></pre>
<h2 id="generic">generic</h2>
<p>(아마 .. 여기서 끝냈을 텐데 .. 오늘 왠지 시간은 남고, 다른 건(?) 하기 싫고 …..)</p>
<p>요기조기 고치고 보니 크게 3가지가 변경 될 수 있겠다 싶어 졌어요</p>
<ul>
<li>요청한 검색키의 default 값<ul>
<li>서비스 별 검색 처리</li>
<li>검색 조건</li>
</ul>
</li>
<li>parsing 로직</li>
<li>반환 파라미터들<ul>
<li>IssueStatus 재활용 문제</li>
</ul>
</li>
</ul>
<p>반환 타입이 명확하지 않아서 방법 없나 고민하다 털고 일어나 세수하다가 스쳐지나갔어요. 다시 앉기 😑 </p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/a6ab65ca-f8ac-4554-8e52-c6c5186c1120/image.png" alt=""></p>
<pre><code class="language-java">public interface Searching&lt;T&gt; {
   default &lt;T&gt; T from(RequestedSearchTerms text) {
      return parseIntoParameters(text.getText());
   }

   &lt;T&gt; T parseIntoParameters(String text);
}</code></pre>
<p>당시 기억이 새록새록 납니다.
갑자기 변경됐고, 시간은 여유치 않았던 ... 걍 util 클래스만 생각하면서 작업 했었는데, 검색 로직이 공통으로 쓰일 수 있지 않을까 싶어졌어요.</p>
<p>유동적으로 키와 값을 담을 <code>SearchKeyMap</code> 이 제 입장에서는 명확하지 않아 인터페이스는 부담됐고, 
기존 SearchKeyType 을 SearchKey 인터페이스로 하고 각 검색어 별 enum 클래스 사용하게 해봤습니다. (뭐 알고 하는 거 아니라.. 의식의 흐름 ... )</p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/8b540fef-8312-46e3-b688-6054affc2b63/image.png" alt=""></p>
<pre><code class="language-java">// SearchKeyMap 
public Map&lt;SearchKey, String&gt; parameters() {
   //return new HashMap&lt;&gt;(searchWords);
   return searchWords.keySet().stream()
            .collect(toUnmodifiableMap(searchKey -&gt; searchKey, searchWords::get));
}


// Searching&lt;IssueSearchParameter&gt;  toParameters() 리턴
IssueSearchParameter.of(status, this.parsing(behindSearchWords).parameters());</code></pre>
<ul>
<li>SearchKeyMap parameters() 는 조회 대상으로 불변타입으로 반환하고자 했는데, Guava 가 좋아보였습니다. 
(저는 단순 자바 프로그래밍 기준이라 추가하진 않았는데 .. 나중에 다른걸.. ㅎㅎㅎ)</li>
</ul>
<h3 id="record">record</h3>
<p>기존 프로젝트와 자바 버전 다르게 해서 새로운 클래스도 써 봤습니다.
(😑 나는 왜 일을 만드는가)</p>
<p>당시 받은 검색값이 null 이 더라도 지정된 기본 검색어 반환 하게 했는데, 
record 쓰면서 이러한 부분이 고민 됐습니다.</p>
<ul>
<li>인터페이스 주석 통해 getText() 주요 역할 알더라도 외부에서 record 제공 paramters() 사용 가능</li>
</ul>
<pre><code class="language-java">public record IssueSearchRequest(String parameters) implements RequestedSearchTerms {
   @Override
   public String getText() {
      if (Strings.isBlank(parameters)) {
         return &quot;is:open&quot;;
      }
      return parameters;
   }
}</code></pre>
<ul>
<li><p>parameters() 는 결국 default 값없이 반환 되버리는 결과 ..</p>
<pre><code class="language-java">@Test
void record_parameterIsNull_defaultParameter() {
  IssueSearchRequest issueSearchRequest = new IssueSearchRequest(null);
  String actual = issueSearchRequest.getText();

  assertThat(actual).isNotBlank();
}
</code></pre>
</li>
</ul>
<p>@Test
void record_parameterIsNull_returnNull() {
   IssueSearchRequest issueSearchRequest = new IssueSearchRequest(null);
   String actual = issueSearchRequest.parameters();</p>
<pre><code> assertThat(actual).isBlank();</code></pre><p>}</p>
<pre><code>
- **compact constructor**
    - 왠지 좀 아쉽네요 ..
    - https://www.baeldung.com/java-record-keyword

``` java
public record IssueSearchRequest(String parameters) implements RequestedSearchTerms {
   public IssueSearchRequest {
      Objects.requireNonNull(parameters);
   }

   @Override
   public String getText() {
      if (Strings.isBlank(parameters)) {
         return &quot;is:open&quot;;
      }
      return parameters;
   }
}
</code></pre><h2 id="parse-알고리즘-교체">parse() 알고리즘 교체</h2>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/a98462d6-9540-4535-841c-240096fcbd9c/image.png" alt=""></p>
<p>parsing 부분만 생각하면 전략패턴이 적절해 보였습니다.</p>
<ul>
<li>반환타입 추상화가 아직 명확하지 않아 그냥 구현체 유지 - SearchKeyMap</li>
</ul>
<pre><code class="language-java">public class IssueSearching implements Searching&lt;IssueSearchParameter&gt; {
    private final ParsingStrategy parsingStrategy;

    public IssueSearching(ParsingStrategy parsingStrategy) {
        this.parsingStrategy = parsingStrategy;
    }

    @Override
    public IssueSearchParameter parseIntoParameters(String text) {
        final int firstKeySeparatorIdx = text.indexOf(&#39;+&#39;);
        final IssueStatus status = toIssueStatus(text.substring(0, firstKeySeparatorIdx));
        final String behindSearchWords = text.substring(firstKeySeparatorIdx);
        return IssueSearchParameter.of(status, this.parsingStrategy.parse(behindSearchWords).parameters());</code></pre>
<p>리팩토링 시작하게 된 Pattern.compile() 이나 세부적인 문자열 처리 로직들이 분리 되어져서 보다 간편해 졌어요
(처음에 한 번에 생각 나면 좋았을 텐데 ..)</p>
<h2 id="mapstruct">Mapstruct</h2>
<p>처음부터 parsingStrategy 가 반환하는 SearchKeyMap 이나, 제네릭을 쓰면서 반환 타입 고치고 싶은데 손이 안 대어 지고, 또 파라미터도 내가 원하는 enum 이나, 형식이 자유롭지 않을까 싶고, IssueSearchParameter 같이 일일이 객체 안에 넘어온 값별 매핑 처리가 불편하지 않을까 싶었어요.</p>
<p>(집으로 걸어오는 길에 생각나서 ... 아, 내일도 해봐야 겠네 😑 안 끝났어)</p>
<ul>
<li>IssueSearchParameter → IssueSearchArgument: record 로 변경<ul>
<li>본래 IssueSearchParameter 역할 IssueSearchArgument 로 변경하고, 기존 IssueSearchParameter 내 중 SearchKeyMap 변환한 멤버 변수들은  IssueSearchParameter(IssueSearchArgument&#39;s property) 별도의 클래스로 분리 
(naming...sorry)</li>
<li>Mapstruct 에서 default 메서드 맞춰주려다 보니 하게 된 작업 이예요 ㅎㅎㅎ</li>
</ul>
</li>
<li>SearchKeyMap.parameters() 호출 하든, SearchKeyMap 반환 하든 클라이언트에 노출 될 필요는 없지 않을까 싶었습니다.</li>
</ul>
<pre><code class="language-java">public class IssueSearching implements Searching&lt;IssueSearchArgument&gt; {
    private final ParsingStrategy parsingStrategy;

    public IssueSearching(ParsingStrategy parsingStrategy) {
        this.parsingStrategy = parsingStrategy;
    }

    @Override
    public IssueSearchArgument parseIntoParameters(String text) {
        final int firstKeySeparatorIdx = text.indexOf(&#39;+&#39;);
        final IssueStatus status = toIssueStatus(text.substring(0, firstKeySeparatorIdx));
        final String behindSearchWords = text.substring(firstKeySeparatorIdx);
        return IssueSearchParamMapper.INSTANCE.toIssueSearchArgument(
            status,
            this.parsingStrategy.parse(behindSearchWords));
    }</code></pre>
<ul>
<li>IssueSearchParamMapper 를 SearchMapper 로 추상화 한다면, 
파라미터 변경 외에는 변경영향이 줄어들거 같았습니다. </li>
</ul>
<pre><code class="language-java">@Mapper
public interface IssueSearchParamMapper {
    IssueSearchParamMapper INSTANCE = Mappers.getMapper( IssueSearchParamMapper.class );

    @Mapping(source = &quot;issueStatus&quot;, target = &quot;status&quot;)
    @Mapping(source = &quot;map&quot;, target = &quot;parameter&quot;)
    IssueSearchArgument toIssueSearchArgument(IssueStatus issueStatus, SearchKeyMap map);

    default IssueSearchParameter mapToObject(SearchKeyMap map) {
        final Map&lt;SearchKey, String&gt; parameters = map.parameters();
        return new IssueSearchParameter(
            parameters.get(IssueSearchKey.ASSIGNEE),
            parameters.get(IssueSearchKey.AUTHOR),
            parameters.get(IssueSearchKey.MILESTONE),
            parameters.get(IssueSearchKey.LABEL),
            parameters.get(IssueSearchKey.NONE)
        );
    }
}</code></pre>
<p>(Mapstruct는 처음 써봐서 .. ㅎㅎ)
<a href="https://mapstruct.org/documentation/dev/reference/html/#adding-custom-methods">mapstruct.org</a>
Example 8. Mapper which defines a custom mapping with a default method</p>
<pre><code class="language-java">@Mapper
public interface CarMapper {

    @Mapping(...)
    ...
    CarDto carToCarDto(Car car);

    default PersonDto personToPersonDto(Person person) {
        //hand-written mapping logic
    }
}</code></pre>
<p>The class generated by MapStruct implements the method carToCarDto(). The generated code in carToCarDto() will invoke the manually implemented personToPersonDto() method when mapping the driver attribute.</p>
<p>이제는 생각 않기 - 끝 -</p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/4ce681d1-a58b-420f-a77f-879f664c76cf/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AOP & 타입 ?]]></title>
            <link>https://velog.io/@sally_devv/AOP-%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@sally_devv/AOP-%ED%83%80%EC%9E%85</guid>
            <pubDate>Mon, 31 Jul 2023 08:34:28 GMT</pubDate>
            <description><![CDATA[<p>이번 달 글 주제 선정이… 자신 없고, 조심스럽습니다 ㅎㅎ 
(다른 주제는 더 못 쓰겠네요 😑)</p>
<p>AOP 에 대한 기반은 토비님의 스프링 3.0 vol1,2 입니다.</p>
<blockquote>
<p>코드 수준에서 주요 관점, 학습 포인트, 리팩토링 과정, 고민거리 들을 스텝마다 알려주는 책은 흔하지 않다고 생각하며, 매우 좋아요~ 👍</p>
</blockquote>
<p>트랜잭션 적용 클래스가 늘면서 중복을 줄이고 자동화 하려는 과정이 진행 됩니다.</p>
<h3 id="처음-reflection-을-사용하는-코드">처음 reflection 을 사용하는 코드</h3>
<p>2가지가 중요하게 보였는데요.</p>
<ul>
<li>Method</li>
<li>target</li>
</ul>
<p>근데 target 이 이상하게 전역변수로 있더라구요. </p>
<p>거기에 target은 Object 타입,  별도로 interface 타입을 알려줘야 하고 … (물론 DI와 데코레이터 패턴 개념으로 이유도 있고, 이해도 되지만, 그래도 등록 과정이 낯설었습니다.)</p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/8ba6e9bf-d06f-4a19-8468-2df544261f1d/image.png" alt=""></p>
<p>빈 등록시 중복 요소가 늘어나니
<img src="https://velog.velcdn.com/images/sally_devv/post/870f6b63-95c3-4c72-97f9-d5ead7affea3/image.png" alt=""></p>
<p>이런 점들을 개선하기 위해 Spring ProxyFactoryBean 방식에서는 </p>
<p>Advice가 MethodInvocation 전달로 target 을 전달하게 하면서 보다 독립적으로 분리 됩니다.</p>
<ul>
<li>Advisor 의 Advice, pointcut 이 보이기 시작
<img src="https://velog.velcdn.com/images/sally_devv/post/c20af964-2c42-4c0b-bac3-ce8801915ae7/image.png" alt=""></li>
</ul>
<br>


<p>과정을 코드레벨에서 비교해보면 (아래 코드 이미지)
1 번째는 리플렉션 기반 Dynamic proxy로 InvocationHandler 
2 번째는 Spring ProxyFactoryBean 방식으로 → 녹색 target 없어졌죠 ? 
(속 내용은 단순하지 않으니 전 여기까지 … 코..코…콜)</p>
<p>1 번째
<img src="https://velog.velcdn.com/images/sally_devv/post/0ac25e50-cc39-4fc0-acb9-095e932b62f6/image.png" alt=""></p>
<p>2 번째
<img src="https://velog.velcdn.com/images/sally_devv/post/a0ad6cd3-9358-4f47-b1d0-74fbf56425cc/image.png" alt=""></p>
<blockquote>
<p>제가 왜 Object target 에 집착하게 됐는지는 …. 
DAO 구현으로 인터페이스와 그 구현체 Impl 등록이 낯설었거든요. 
제가 스프링을 전혀 몰..몰…모르는게 많긴 하겠지만 .. 어쨌든 그런 내적 고민이 있을 수도… 그럴수도…</p>
</blockquote>
<p>아무튼, 그렇게 ProxyFactoryBean 이 어차피 만들어 주게 했고, 적용할 클래스의 메서드에 대해 프록시 교체 방향으로 가게 됩니다.</p>
<ul>
<li>빈 후처리기 : 제목 보고서 … 설마 빈 등록후 ? 넵, (이름을 잘 지어야 한다)</li>
</ul>
<p>인터페이스 타입과 데코레이터 패턴 특성상 위임 대상을 알아야 하다보니 프로퍼티로 가지고, 그 코드 기반으로 어떤 리플렉션 작업 후 타입으로 인한 빈 등록 문제, 비슷한 빈 등록 중복 문제 들을 해결해 나가는 과정들이 이어 집니다.</p>
<ul>
<li>메서드를 찾아가는 건 시작이었을 뿐</li>
<li>빈과 타입은 밀접한 관계 ?</li>
</ul>
<br>

<h3 id="빈과-타입-관계-">빈과 타입 관계 ?</h3>
<p>registerBean() 매개변수가 여러 유형이 있는데, 이름을 지정해서 전달해도 
중심에는 beanDefinition 으로, 이름 지정 된 경우 이름도 같이 전달 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/40c0d75b-66cf-4b33-96b0-16fd51881c40/image.png" alt=""></p>
<p>동일 타입 사용경험 ? 당연히 충돌 → @Primary 든 @Qualify 지정
이름과 타입 조회 차이는 타입 안정성…? </p>
<ul>
<li>이름 통한 빈 조회는 Object 반환</li>
</ul>
<pre><code class="language-java">// HelloController helloController = (HelloController)applicationContext.getBean(&quot;helloController&quot;);
@Override
public Object getBean(String name) throws BeansException {
    assertBeanFactoryActive();
    return getBeanFactory().getBean(name);
}

// HelloController helloController = applicationContext.getBean(HelloController.class);
@Override
public &lt;T&gt; T getBean(Class&lt;T&gt; requiredType) throws BeansException {
    assertBeanFactoryActive();
    return getBeanFactory().getBean(requiredType);
}</code></pre>
<br>

<hr>
<h2 id="스프링-사용하면서-di-활용--안-하는-건-나쁜짓-">스프링 사용하면서 DI 활용  안 하는 건 나쁜짓 ?</h2>
<p>트랜잭션 지원을 예시로 어떻게 리팩토링 했을지 과정이 토비님 책에 자세히 나옵니다.</p>
<p>위에서 언급 했듯이 Obejct target과 인터페이스 타입을 알려주는 빈 등록 방식을 보면서 
2가지를 주요하게 봤습니다.</p>
<ul>
<li>첫 번 째는 타입</li>
<li>두 번째는 DI</li>
</ul>
<h4 id="메서드만-적용과-타입">메서드만 적용과 타입</h4>
<p>메서드 레벨 관점에서 걍 이것저것 상상해 보면💭</p>
<ul>
<li>람다 (?) : excute-around-pattern</li>
<li>전략패턴 : 확장(교체)</li>
<li>템플릿 패턴 : 이런 동작하고, 이 타이밍에 너를!</li>
</ul>
<p>사실 부가기능 이지만, 뭔가 주요 기능이니 이렇게 까지 중복되고 많이 쓴다는 느낌도 부정할 수 없었을 거 같은데요. 
과정을 보며 세세한 차이점을 인지 하다보면, 포인트 컷 중심으로 타켓 찾고, 타겟은 프록시 생성과 빈 교체 대상이 됩니다.</p>
<ul>
<li>타입을 찾는게 아니라 포인트 컷이 기준</li>
<li>타입은 클라이언트 입장에서의 OCP와, 클라이언트와 target 사이에서 interceptor 하기 위한 프록시를 위해</li>
</ul>
<blockquote>
<p>트랜잭션 부가 기능이 의외로 애매모호 한게 재밌었어요.</p>
</blockquote>
<p>주요 기술 스택이면 스프링 프레임워크 단위에서 추상레벨에 의해 빈 등록 되어 관리대상이 되게 하는데, 그렇게 추상화한 PlatformTransactionManager 로도 안 되는 점</p>
<ul>
<li>메서드 별 커스터 마이징</li>
<li>메서드는 클래스 → 타입이 가지고 있고, 스프링 컨테이너 DI 도 좋으니 OOP를 준수하자</li>
<li>그렇게 변경 영향은 없는데, 중복 ?!</li>
<li>객체는 책임과 역할을 SRP 준수하며 가지는데 트랜잭션 기능이 그렇게 갖고 있어도 될텐데 쉽게 안되겠는 상황인 것 같..습니다. 크흠</li>
</ul>
<br>

<h4 id="데코레이터-패턴">데코레이터 패턴?</h4>
<p>어쨌든, 로우레벨에서 고민해보는 저라면
데코레이터 패턴이다 하고 보니까 target 이랑 보이지 않았을까,</p>
<p>작은 단위 메서드 관점에서만 보면 
트랜잭션 기능을 각자 상속 받든,
기능 가진 클래스를 DI로 던져주든, 
기능을 방문하게 하든, 
기능 구현체 빈 등록 해주면 되지 않나 싶어질 거 같은데요 
(기본적인.. ㅎㅎ)</p>
<p>과연 … 제가 어떤 방황을 할까 생각 해 봤어요. 
(네, 쓸데없는짓 한다고 당일 고생 좀 했어요 🌧)</p>
<blockquote>
<p>개인적이고 주관적인 경험으로 비지터 패턴에서 타입을 주요하게 봤던게 떠올라 시도해 봤어요 (중요 내용은 X) 🌌</p>
</blockquote>
<p>디자인 패턴 인프런 강의 선생님, 백기선님 예제 코드로 시작해 봤습니다.
(비지터 패턴이 아니라 문제 코드 ㅎㅎㅎㅎ)</p>
<h4 id="일부러-살짝-만든-나쁜-코드"><em>일부러 살짝 만든 나쁜 코드</em></h4>
<ul>
<li><p>이름만 넘겨주면 되잖아 ? 상속으로 간단하게 ~</p>
<ul>
<li>Shape 구현체에서 필요 기능만 처리 합니다.</li>
</ul>
<pre><code class="language-java">public abstract class Shape {
  private final Device device;

  public Shape(Device device) {
      this.device = device;
  }

  public void printTo() {
      final String message = String.format(&quot;print %s to %s&quot;, this.name(), device.name());
      System.out.println(message);
  }

  protected abstract String name();
}
</code></pre>
</li>
</ul>
<ul>
<li><p>그냥 그곳에 가면 그곳의 룰이 있겠지</p>
<ul>
<li><p>audience 가 각 타입 별 갈 곳을 알고 있어요</p>
<pre><code class="language-java">public class Audience implements Human {
private final Ticket ticket;

public Audience(Ticket ticket) {
    this.ticket = ticket;
}

@Override
public void move(Zootopia zootopia) {
    if (zootopia instanceof PandaWorld) {
        // 위치 확인  - 예약 -&gt; 방사장
            // 스마트 줄서기 예약 확인
            // 현장 줄서기 확인
        if (zootopia.checkReservation(this.ticket)) {
            // 관람 - 푸바오, 아이바오, 러바오
            see(zootopia.withCaution());
            // 실외 방사장 - 아이바오, 러바오
            // 실내 방사장 - 푸바오
        }
    } else if (zootopia instanceof PpuPpaTown) {
        // 관람 - 카피바라, 왈라비
        see(zootopia.withCaution());
    }
}</code></pre>
</li>
<li><p>사실 관람객이 동물원을 방문 하겠죠.</p>
<ul>
<li>예약, 동물원 내 동물들, 사육사와 권한  등<pre><code class="language-java">public class Audience implements Human {
private final Ticket ticket;
</code></pre>
</li>
</ul>
<p>public Audience(Ticket ticket) {</p>
<pre><code>this.ticket = ticket;</code></pre><p>}</p>
<p>@Override
public void move(Zootopia zootopia) {</p>
<pre><code>zootopia.visit(this);</code></pre><p>}</p>
<pre><code></code></pre></li>
<li><p>Visitor 가 트랜잭션 전담하면서 방문객들한테 트랜잭션을 부여해 준다면 ?</p>
<ul>
<li>트랜잭션 내부에서 해당 타입 받아서 동작하게 한다면 ?</li>
<li>이렇게 하다보니, 서비스 구현과 DAO 관계가 되지 않나 싶어졌어요. </li>
<li>트랜잭션 전파는 ? 🙃 어떻게 되긴 되겠지만 stop</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>같은 문제 ? ㅎㅎㅎ</p>
<p> 첫 번째 Shape 코드에서 타입 기준 로직 처리 무시하고 
기능만 보고 필요 데이터만 전달하게 하는 (절차지향적) 방식은  간단하고 좋을 수 있지만, 
바로 다음 예제 처럼 조금만 복잡해지고 로직 많아지면, 좋지 않은게 보였는데요.</p>
<p>비지터 패턴 학습 하면서 놀란게 <code>this</code> 전달 부분 이었습니다. ㅋㅋㅋ
(제가 당시 이 방법 밖에 없는데? 수정하면서도, 이래도 되나 .. 고민고민)</p>
<h4 id="상속">상속</h4>
<p>SuperClazz의 fisrt(),  second()</p>
<pre><code class="language-java">public void first() {
    log.info(&quot;super - first[{}]&quot;, this.getClass());
}

public void second() {
     log.info(&quot;super - second[{}]&quot;, this.getClass());
   third();
}</code></pre>
<p>SubClazz </p>
<pre><code class="language-java">public class SubClazz extends SuperClazz{
   @Override
   public void second() {
            log.info(&quot;sub - second[{}]&quot;, this.getClass());
   }
}</code></pre>
<p>내부에서 모두 this 를 통해 getClass()를 호출 하고, 
클라이언트에서 아래와 같이 호출 하면</p>
<pre><code>sub.first();
sub.second();
superClazz.second();</code></pre><p><img src="https://velog.velcdn.com/images/sally_devv/post/e27facf2-4fe9-4a15-8ce5-134e0ede4dbe/image.png" alt="">
<code>self 참조</code></p>
<ul>
<li>재정의 하지 않은 sub.first() 는 부모 클래스로 올라가는데 this.getClass() 는 SubClazz 가 나옵니다.</li>
<li>재정의한 sub.second()는 자신의 메서드를 호출하고 동일하게 this.getClass() 는 SubClazz 가 나옵니다.</li>
</ul>
<p>메서드 별로 적용하게 하자니 타입 별 중복이 발생하고,
인터페이스 2개 등록하고 상속하자 생각도 들 때, CGLIB 가 지원 되는 거죠.</p>
<p>저번에 알림서비스 Pull/Push 모델에서는 해당 시점에 내가 데이터를 받을 것인가, 전달 할것인가 이벤트 관점을 봤었는데,</p>
<p>오늘은 메서드 전/후 동작 처리 관점이 타입과 DI 를 통해 어떻게 구현하게 하는가 주관적 관심이었습니다.</p>
<h2 id="aspect">Aspect</h2>
<p>그래서, 애스팩트 관점이 신선 했습니다.</p>
<p>객체 지향 프로그래밍을 많은 분들이 중시하면서 지향하고 싶어 했을 텐데 벽에 부딪히는 과정들이 다가왔고 (나는 다행이다😊) 
결국 어떻게 결정을 내릴지!</p>
<h4 id="그렇게-스프링-프레임워크가-다가-옵니다">그렇게 스프링 프레임워크가 다가 옵니다.</h4>
<p>(모..모릅니다)</p>
<blockquote>
<p>스프링
객체 지향 프로그래밍 기반으로 그 특징을 살린 비즈니스 개발 프로세스를 지원해주는 범용성 엔터프라이즈 애플리케이션</p>
</blockquote>
<blockquote>
<p>AOP  aspect oriented programming</p>
<ul>
<li>aspect : 어드바이저 단위 (포인트컷, 어드바이스)</li>
<li>스프링은 가장 기본적인 aspect 단위로, 메서드 레벨의 포인트컷과 어드바이스로 구성된 프록시 방식의 AOP 를 지원 합니다.</li>
</ul>
</blockquote>
<p>리플렉션 기반으로 동작하면서 method 와 target 기준</p>
<p>각각 독립적 단위로 분리 하여도 객체 지향 프로그래밍의 한계선에서 
aspect 관점을 정의 했습니다. </p>
<p>이 부분은 결국 POJO 기반의 프로그래밍 집중하도록 프레임워크가 대신 작업하도록 생각 했습니다.</p>
<hr>
<p>제가 느낀 스프링 컨테이너는 빈등록이나 가져오는 과정에서도 타입이 매우 중요하게 느껴졌어요.</p>
<p>앞에서의 스토리를 더 이어나가면 빈 후처리기로 구현체를 등록하고 포인트컷이 맞으면 프록시 생성해서 프록시를 등록한 빈과 교체하고 DI 합니다.</p>
<ul>
<li>확장 ≠ 호출시점에 대한 핵심 비즈니스 로직에 부가적 기능을 동적으로 부여</li>
</ul>
<p>인터페이스와 타입</p>
<p>인터페이스 사용하면 보다 해당 객체의 호출되는 기능이 제한적으로 관리하면서 변경 영향을 줄일 수 있습니다.</p>
<ul>
<li>정보은닉, 캡슐화, 저는 여기에 +알파</li>
</ul>
<blockquote>
<p>자세한 내용은 책을 참고해주세요 😅</p>
</blockquote>
<blockquote>
<p>부족하거나 틀린 부분은 알려주시면 감사하겟습니다.</p>
</blockquote>
<p>감사합니다~</p>
<hr>
<p>오늘 왠지 길어지면서 잘 정리 못 한듯 ...</p>
<blockquote>
<p>날이 더워 카페에 갔다. 
(더위에 약함) 분명 일기예보에는 비가 그렇게 많이 오지 않았었다. 
비오더라도 버티고 오면 되겠지 가볍게 나갔고, 
블로그 글 쓰다 비지터 패턴 예제를 구현하며 잠시.. 푸바오 동물원 상상에 빠져 예약과 티켓 끊으며 이거까지 해야하나 ? 하면서 못 헤어나오고 있었는데 …
카페 창가쪽에 비 셀 때는 비가 많이 오긴 오네 싶었는데 … 
고개 드니 어느새 10 군데가 넘고, 바닥이 흥건 해지고, 폭염의 날씨는 비가 오면서 쌀쌀해지고, 아이스를 들이 붙던 저는 살짝 저체온증을 느끼며 
정전을 피해 카페 밖 테라스에서 우산을 기다리다가 집에 왔는데 …
집 가까이라 괜찮을 줄 알았으나 옷과 신발은 다 젖고 .. 
이 더운 여름날 한 달 넘게 엘레베이터 교체 중이라 
(이 더위에, 이 습기에… 한 달 충분히 적응 안 됨  -_- ) 
꾸역꾸역 계단을 올라오며 집에오니 갑작스런 폭우로 뉴스에 나오고 있더라구요. ㅎㅎㅎ
정온 동물인 저는 … 이만 쉬어야 겠습니다.  ?</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 테스트 데이터]]></title>
            <link>https://velog.io/@sally_devv/%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0</link>
            <guid>https://velog.io/@sally_devv/%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0</guid>
            <pubDate>Sat, 10 Jun 2023 07:19:31 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>인덱스와 조회 성능 테스트라면, 
100 만건의 Post 글을 어떻게 만들어 놓을까?</p>
</blockquote>
<p>가장 간편하게 시작하자 싶었지만 최소 작성자인 User 필요</p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/b9f93bc0-1189-46a2-b69b-9d6da1a9e455/image.png" alt=""></p>
<br>


<h3 id="post-random-data-생성하기">Post random data 생성하기</h3>
<p>JPA auto ddl 로 먼저 구현 후 테스트 데이터 생성 하려 하니 문제가 많았다. 
별도의 프로젝트로 JDBC 이용한 테스트 데이터를 생성해서 저장하게 했다.
(DataSource 설정을 여러개 두자니, 그렇게 쓸거 같진 않고, 모듈화 하자니, 애플리케이션 테스트도 아니고, batch 랑 같이 쓰기도 어색하고 .... 그렇지만 결국 나중에 그냥 추가해볼 수도 있을 거 같다. ㅎㅎ)</p>
<ul>
<li>JPA 에서는 @MappedSuperclass 로 BaseEntity 로 인해 엔티티 타임 기준 객체생성만 가능<ul>
<li>AOP 동작이 테스트 케이스 위한 엔티티 생성시 X, 특히 날짜 등 임의 데이터가 안된다.</li>
<li>bulk insert는 MySQL의 식별자 전략 때문에 미지원</li>
</ul>
</li>
<li>임의 데이터 생성해서 csv 파일 제공 사이트<ul>
<li>무료로 만 건 정도, csv 파일 내 날짜 등 생성 타입하고 달라짐</li>
<li>workbench import 기능이 매우 단순해서 쉬운 만큼 정정은 클릭으로는 X<ul>
<li>Oracle 토드 때 다양한 방식이 있어서 WorkBench도 있을까 싶지만, 쓸 일이 없었던 만큼 stop, 다른 방식으로 해야 그나마 끝날 거 같다.</li>
</ul>
</li>
</ul>
</li>
<li>JDBC<ul>
<li>JPA 엔티티와 별개의 객체 타입 생성해서 임의 데이터를 보다 자유롭게 넣을 수 있다.</li>
<li>JPA로 auto ddl로 생성해 놓은 테이블은 문제가 생길 수 있다.<ul>
<li>bulk insert 시 외래키 제약 조건으로 문제 발생<ul>
<li>외래키 없는 테이블로 생성 후 bulk 데이터 넣고, 외래키 생성😅</li>
<li><code>SET foreign_key_checks=0</code> 로 일시적 체크 해제</li>
</ul>
</li>
</ul>
</li>
<li><code>Instancio</code> : 랜덤하게 타입별 값 넣어줄 라이브러리<ul>
<li>메서드 종류가 적어 간편하게 사용하기 쉬움, 자바 만으로 데이터 지정 쉬움</li>
</ul>
</li>
<li>JDBC의 BeanPropertySqlParameterSource 는 타입, 컬럼명 등 문제 생겨서 취소 → MapSqlParameterSource</li>
<li>Insert SQL 문에 <code>:</code> 빼먹는 오타로 백만건에 컬럼명이 데이터로 들어갔었다. 코드 개선하기 😑 </li>
</ul>
</li>
</ul>
<hr>
<h3 id="data-구성">Data 구성</h3>
<blockquote>
<p>Post 작성글 작성자, 날짜 기준 목록 조회</p>
</blockquote>
<p>인덱스 추가 한다면</p>
<ul>
<li>생성날짜 : 변경 X</li>
<li>작성자는 FK</li>
</ul>
<blockquote>
<p>작성자 분포도 , 날짜 기간 다양</p>
</blockquote>
<p>FK 사용자는 10명으로 적게 생성</p>
<ul>
<li>작성자 기준 검색시 unique 값 개수 적은 것과 늘어난 경우 비교하기 위해</li>
<li>생각보다 member_id 가 굉장히 균일하게 들어갔다.<ul>
<li>나중에 늘어난 사용자 추가시 사용자 많고, post 수는 적게 하면 빈도 차이는 날 것이다.</li>
<li>컬럼명 user_id 로 바꿔야 겠다. ㅎㅎ</li>
</ul>
</li>
<li>updated_at 을 created_at 이후로 하려고 범위를 시작기간을 뒤로 했는데 미래 날짜가 많이 나왔다 ㅎㅎ 테스트상 updated_at 쓸 일 없으니 skip </li>
</ul>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/dc3bdcf4-578a-4e53-8173-8aa818b00f98/image.png" alt=""></p>
<blockquote>
<p>테스트 생성 시간: 7.1540994
DB bulk insert 시간: 45.8880974</p>
</blockquote>
<p>p.s. 역시 이번에도 쉽겠지, 간단하겠지... 싶었지만, 삽질 많았다. 이 글은 .. 사라질지도 ㅎㅎㅎㅎ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[알림 서비스 pull/push]]></title>
            <link>https://velog.io/@sally_devv/TIL-%EC%95%8C%EB%A6%BC-%EC%84%9C%EB%B9%84%EC%8A%A4-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@sally_devv/TIL-%EC%95%8C%EB%A6%BC-%EC%84%9C%EB%B9%84%EC%8A%A4-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Fri, 21 Apr 2023 05:43:30 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>TIL로 좀더 편하게 정리 한 글입니다. 
여러 관점, 생각 과정으로 봐주시면 감사하겠습니다.</p>
</blockquote>
<p>TMI
알림 서비스 개발하면서 생각보다 볼게 많고, 확인할 게 많고, 그러다 혼자 좀 빠져있다가  ... 사알짝 번아웃인가 ... 이번 주 많이 내려놓으며, 오늘 좀 다시 봐볼까 싶어 졌습니다 ㅎㅎ</p>
<p>어떻게 정리해야 할지 고민이 많았습니다. 지금도...</p>
<ul>
<li>Long-Polling과 SSE 차이에서 느낀점은 실시간 DB 조회와 갱신로직이 아예 삭제 된 점이었습니다. <strong>너무 매력적이었어요.💛</strong></li>
<li>Redis 구현 하며 본 Pub/Sub 패턴<ul>
<li>토비님 유투브로 Observable과 Pub/Sub 구현을 보며 pull와 push 차이점이 나왔고, 짧게 Reactive stream 을 보았습니다. (유투브는 재밌게 보고 이해한 듯 한데, 왜 모르겠지? 🤫)</li>
<li>백기선님 디자인 패턴 Obserble을 봐 봤어요. 채팅 구현 해보며 Redis 가 이런가? (싱글스레드와 이벤트 루프-_-, 카프카는 어떻게 보관하고 있지? stop).. 스프링 예시에서 stop (모르겠어요. 묻지 말아주세요.🤫)</li>
</ul>
</li>
<li>Pull 모델과 Push 모델을 알게 됐어요.<ul>
<li>Fan Out On Read, Fan out on write 로 페이스북과 트위터 요구사항과 로직들 stop ?🤫</li>
<li>거기서 인덱스, 트랜잭션 ...MySQL 다시보고 DB I/O 처리방식 등 트레이드 오프 stop ?🤫</li>
</ul>
</li>
<li>알림 서비스와 뉴스 피드 설계 예시들 🤖</li>
<li>HTTP 커넥션, 웹소켓 ???🤫</li>
<li>그 외 ... stop</li>
</ul>
<blockquote>
<p>패턴이나 방식들이 있는거 같은데, 관련하여 좋은 책이나 정보 알려주시면 감사하겠습니다.  💛</p>
</blockquote>
<h2 id="polling">Polling</h2>
<p>Polling(Long-Polling)을 보면 요청하고 응답하는 웹요청의 일반적인 거 같은데 왜 폴링이라 할까? 실시간이 관련있나? 의문이 들었습니다.</p>
<p>구현 해보자로 시작 했었습니다.</p>
<ul>
<li>생각보다 할 게 많았다.</li>
<li>구현하며 느낌점들은 폴링의 문제 였던 거 같습니다. 그렇게 보게 된 다른 예시는 <code>인터럽트</code> 였습니다.</li>
</ul>
<p><a href="https://xmctutorial.readthedocs.io/ko/latest/Interrupt/index.html">인터럽트와 폴링</a></p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/82cabecb-7c4b-4a75-9b79-4de9e4f7dc6b/image.png" alt=""></p>
<ul>
<li>반복<ul>
<li>알림이 없어도 클라이언트는 요청 합니다.</li>
<li>알림이 없어도 조회해서 체크 합니다.</li>
<li>알림이 없어도 커넥션을 가지고 있어요.</li>
<li>계속 반복 </li>
</ul>
</li>
</ul>
<p>서버하나 = 프로세스 하나가 작업하는 걸로 생각하면 CPU에 일정 시간 위의 반복과정이 진행 돼야 합니다.</p>
<ul>
<li>스레드가 여러개라 하더라도, CPU 하나만 있다 생각하면 ... 
OS만 봐도 주요 작업은 늦춰 집니다.</li>
</ul>
<p>주요 로직과 분리가 중요하게 생각 됐습니다.</p>
<ul>
<li>DB 알림 테이블을 다른 도메인에서 사용하지 않기</li>
<li>MySQL의 경우 인덱기반 잠금 처리 되고, 읽기 공유로 잠기지는 않지만, 변경시 쓰기잠금 발생<ul>
<li>쓰기 잠금에 대한 읽기 트랜잭션도 대기 상태</li>
</ul>
</li>
</ul>
<p>그래서 </p>
<ul>
<li>스케줄러는 별도로 동작하게 했는데, CPU 하나면 어떻게 ? 의문 (필요없는..? 구현하면서... 공포 요소가 많아요)</li>
<li>트랜잭션 단위를 최소화 하는 리팩토링 작업을 했습니다. (정리해서 블로그 올리려고 했는데,.. 모르겠네요)</li>
</ul>
<p>위의 과정을 운영체제는 어떻게 했을까?</p>
<ul>
<li>CPU는 작업에 집중하면서 외부에서 알려주게 해서 전달한 정보를 확인하면서 처리하는 방식으로 진행 합니다.</li>
</ul>
<p>(개인적으로 느낀점이라, 맞는지 모르겠지만,) SSE 로 구현하면서, 클라이언트 요청이 subcribe로 기록되어있고, 외부에서 댓글이 달렸을 때 알림 발생할 때 subcribe 를 확인하여 응답 합니다.</p>
<hr>
<h2 id="pull-모델-push-모델">Pull 모델, Push 모델</h2>
<p>자세한 내용은 <a href="https://www.youtube.com/watch?v=8fenTR3KOJo">토비님 유툽</a></p>
<blockquote>
<p>pull 🆚 push 에 대해서 Java의 Iterable 과 Observable 로 설명해 주십니다.</p>
</blockquote>
<p>제가 봤던 키포인트 1가지 : 메서드로 DATA 를 받는지(가져오는지), 전달하는지(밀어내는지) 설명해 주셨는데요. (잼잼잼)</p>
<blockquote>
<p>DATA method( void) 🆚 void method ( DATA )
DATA 를 받는지 or 전달하는지</p>
</blockquote>
<p>Iterable : pull</p>
<ul>
<li>next()</li>
<li>pull ? 리소스 사용하는 쪽에서 가져온다</li>
</ul>
<p>Observable : push</p>
<ul>
<li>publisher : Observable - 이벤트 or 데이터 갱신</li>
<li>subscriber : Observer</li>
</ul>
<p>p.s.
<img src="https://velog.velcdn.com/images/sally_devv/post/b02e74a8-a882-47ab-a1da-826de291a36d/image.png" alt=""></p>
<h3 id="observer-pattern">Observer pattern</h3>
<p>채팅 서버에서 구독 요청을 통해 Observer(subscriber)들을 등록하고, 이벤트 발생시 순회하며 갱신 정보를 Observer 들에게 전달
(간단히 코드를 올리자니,... 자세한 설명과 코드 예시는 백기선님 디자인패턴 강의를 참고 했습니다.)</p>
<pre><code class="language-java">public interface Observer {
    void publish(String message);
}</code></pre>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/926b5862-d06a-464d-aa0f-70d4eb1d798b/image.png" alt=""></p>
<h3 id="구현-코드는-">구현 코드는 ..</h3>
<p>다시 Polling 과 SSE 로 pull 과 push 를 봐보면
(부족함이 많습니다... Pub/Sub 패턴전에 SSE 정도 까지만 정리 했습니다)</p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/b6292123-1f71-429c-a4c8-e5d337542908/image.png" alt=""></p>
<p>Long-Polling은 요청 시점에 데이터를 조회 합니다.</p>
<ul>
<li>Post 작성 시점에 저장</li>
<li>조회 결과 데이터 있어서 전송시 재조회 막기 위해 데이터 갱신<ul>
<li>이 부분도 조회 요구사항에 따라 달라질 수 있지만, 저는 목록화 해서 해봤습니다. bulk 인점 등 고려해보고 싶었습니다.</li>
<li>최신 데이터 기준이라면, 페이징처리도 커서 기반으로 하는 등 달라질 게 많아서 ... 제 코드 기준으로 가보자 싶었습니다. (고해의 시간은 피하려 했으나 ... 안 되네요)</li>
</ul>
</li>
</ul>
<p>SSE 는 이벤트 발생 시점에 메시지 보냅니다. (send Alarm)</p>
<ul>
<li>Post 작성 시점에 전송</li>
<li>전송 시점에 저장<ul>
<li>Post 작성과 Alarm 저장을 분리할 수 있습니다.</li>
</ul>
</li>
</ul>
<p>모델링시엔 저장한 데이터 활용하는게 좋아 보일 수도 있고, 
애플리케이션 계층에서 DB I/O를 줄일 수 있는 면도 좋습니다.</p>
<p>DB I/O 감소로 캐싱 등 생각할 수 있고, 조회도 안 하는 알람 왜 저장할까란 생각은… </p>
<ul>
<li>알람 중복 방지와 서비스상 알람 1번은 필수 전송 이라면, 체크하기 위한 알람 로그기록 등에 쓰일 수 있을 거 같아요. (사용자 데이터 분석 등)</li>
</ul>
<h3 id="long-polling">Long-Polling</h3>
<p>알람의 경우 저장과 갱신이 동시에 일어날 거 같았습니다.</p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/8ed7ca64-7a9d-421b-9eea-f851391b18ff/image.png" alt=""></p>
<ul>
<li>게시글 비활성화 하거나 삭제라도 하면, 변경 작업 등도 고민 되드라구요. (많은 것을 skip...)</li>
</ul>
<p>사실 트랜잭션은 ...
일정시간 마다 요청으로 일정 시간 동안의 알람 전송 가정과 최신 알람이 아닌 목록 전송으로 중간에 요구사항 변경하면서 RDB 이고, bulk update 작업으로 ... 잠시 다른 트라이를 💬</p>
<h4 id="어떤-상황일까">어떤 상황일까?</h4>
<p>팔로워 등록까진 아니더라도, 회원들의 댓글 등록시 알람 부분에 대해 생각해 보겠습니다. </p>
<ul>
<li>포스트 작성자가 인기가 많다면?<ul>
<li>포스트 게시 되는 순간부터 많은 사용자들의 호응으로 댓글들이 넘실넘실</li>
<li>기대하며 기다리는 작성자에게 알람도 넘실넘실</li>
</ul>
</li>
</ul>
<p>게시글 등록 시점에 댓글 많이 등록되는 상황이라면, </p>
<ul>
<li>댓글 등록 로직에<ul>
<li>게시글 검증</li>
<li>게시글 FK 로 댓글 저장</li>
<li>게시글 FK 로 알람 저장</li>
</ul>
</li>
<li>방금 포스트 등록한 사용자의 클라이언트가 알람 요청<ul>
<li>다른 알람 등록 기능 + ♾️ → 알람♾️</li>
</ul>
</li>
</ul>
<h3 id="sse">SSE</h3>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/b9b08ff6-7ebb-4009-ae58-6653a5aeda7f/image.png" alt=""></p>
<p>Long-Polling 구현하면서 스케줄러 돌리면서 실시간 조회하고 갱신로직이, 사라졌습니다. 💛</p>
<ul>
<li>하지만, 그림에서 처럼 저장함 Alarm 데이터는 사용하지 않아요. 처음에도 캐시 정도로 생각했지만, 기존 로직에서 큰 차이 외에는 끝까지 남겨봤습니다. (이것이 문제다 ....)</li>
</ul>
<p>머리속에 멤돌았던 것들</p>
<ul>
<li>실시간 이지만, 시간 지나면 의미 없는 데이터</li>
<li>하지만 그 과정에서 체크가 필요한 데이터, 어딘가 쓸수도 있다면?</li>
</ul>
<h4 id="기능은-동일-할까">기능은 동일 할까?</h4>
<ul>
<li>실시간 이란 ? better ?</li>
<li>구독 정보 : 서버측에서는 클라이언트 단말이나 브라우저 정보가 필요합니다.</li>
</ul>
<p>Post 에서는 알람 전송만 관심사 일 뿐, 어디 저장하는지 등 알 필요가 없어졌습니다. </p>
<h4 id="📬-mail-box">📬 Mail Box</h4>
<p>Long-Polling 은 Queue 구조로 구현 했습니다. 실시간에 가까우려면 요청 순서대로 처리해주고자 FIFO 방식을 이용했습니다.</p>
<ul>
<li>즉, 실시간은 요청에 의존적입니다.</li>
</ul>
<p>SSE</p>
<ul>
<li>subscribe 로 알림 받을 사용자들을 보관합니다. <code>목록</code><ul>
<li>알림 관련 이벤트 발생시 전송 위임</li>
</ul>
</li>
<li>Observable : push <ul>
<li>Post 에 댓글 등록</li>
<li>Post 작성자 subscriber에게 알람 전송 - publisher</li>
<li>subscrition 목록의 subscriber 에게 전달</li>
</ul>
</li>
<li>실시간은 이벤트 발생시점에 맞춰 집니다.</li>
</ul>
<p>DB 외에도,
제가 지금 구현한 프로젝트는 layerd architecher 기반 입니다.
모듈화든, 도메인 별이든 패키지 구조를 볼 때 어떨 때 분리할까를 좀더 생각 해보지만 모르지만 ...</p>
<h4 id="요청-≠-알람-">요청 ≠ 알람 ?</h4>
<ul>
<li>Long-Polling 은 요청 부터가 알람 대상 (controller → service → DB, 요청 + 스케줄링)<ul>
<li>클라이언트 알람 요청 ≠ 댓글 등록 시점에 알람 전송 메시지</li>
<li>요청이 없거나, 스케줄링 순서가 안 되거나, 데이터가 없으면 알람은 가지 않는다고 생각하면 PostService로 인한 관련 데이터가 있다는 알람 저장이 주요해 보였지만,  AlarmService 인터페이스 사용 순간부터 Post와 분리 된건가 싶기도 했습니다.</li>
</ul>
</li>
<li>SSE 구현하면서 이벤트가 일단 알람 전송 = 전송의 실시간<ul>
<li>중간에 사용자 알람 상태 체크 등</li>
<li>SseEmitter를 로컬 내에서는 언제 해제할지 애매 했었습니다.(대충..🙄) 
TTL 등 시간 별 리소스 관리해주면 좋겠다 싶었는데, Observer 패턴도 리소스 관리가 중요 한 것 같았습니다.  </li>
</ul>
</li>
</ul>
<hr>
<h2 id="fan-out">FAN-OUT</h2>
<ul>
<li>fan in: 게이트에 연결될 수 있는 최대입력수</li>
<li>fan out: 게이트의 출력에 연결될 수 있는 입력게이트의 최대 수<ul>
<li>팬 아웃이 크다는 말은 하나의 출력이 많은 논리게이트의 입력으로 사용된다는 뜻이다.
(뭔가 많네요. 대단하시다. … 모르겠습니다. 호롤롤롤 )</li>
</ul>
</li>
</ul>
<p>fanout-on-read = pull model</p>
<ul>
<li>읽는 시점에 fanout</li>
<li>읽는 데 시간 소모</li>
</ul>
<p>fanout-on-write = push model</p>
<ul>
<li>쓰기 시점에 fanout</li>
<li>쓰기 시점에 시간 소모</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/68761f57-d5d6-432d-ac69-2d4f5467e77e/image.png" alt=""></p>
<ul>
<li>LongPolling 은 요청 시점에 읽기 작업을 통해 전송</li>
<li>SSE 는 작성 시점에 전송</li>
</ul>
<p>부족한 그림 자꾸 내밀고, 좀 조심스러워져서… ㅎㅎ 여기까지만 하겠습니다. 👻</p>
<h2 id="pull-모델은-어떻게-볼까">Pull 모델은 어떻게 볼까?</h2>
<p>지금까지 Polling을 제 모자른 코드와 연결 시키면서 안 좋은 예만 보인 거 같은데요.
사실 Push/Pull 모델은 다른 예시가 적당해 보였습니다.</p>
<ul>
<li>SNS 에서 계정 비활성화 됐거나 활발하지 않은 사용자에게 실시간 서비스란?<ul>
<li>정보 관심 없는 사용자에게 무분별한 알람은 오히려 부담스럽고 앱 삭제 욕구를.. </li>
<li>알람 off : 알람 전송 전 사용자 상태 확인 필요</li>
<li>클라이언트에서 사용자 정보 통해 polling 요청을 안 하면, 서버에서 조회대상도, 전송도 일어나지 않지만, SSE는 주기적 subscriber 관리 해야 하지 않을까...</li>
</ul>
</li>
<li>사용자 활성도에 따른 별도 관리 Pull model<ul>
<li>타임라인이라면, 팔로우 신청한 사용자 이지만, 활동이 매우 적은 사용자인 경우는 빠른 읽기를 위한 조회를 줄이고자 쓰기 비용을 감안하여 보관해두기 보다, 접속하여 요청 할 때 보여주기</li>
<li>채팅 알림 off 상태로, 채팅방 나왔다가 다시 들어 갔을 때 개인별 목록<ul>
<li>페이스북은 팔로우 제한, TAO, mem-cache 등 보완 등 (사실 Push model도 쓰기 비용에 대한 보완 등)</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>요청 당시에 맞춰 보여줘야 할 때가 해당 사용자에게 맞춘 실시간 서비스 일 수 있겠구나 싶었습니다. 
개인적으로 이런 부분이 중요하지 않을까 생각합니다. </p>
</blockquote>
<p>위에서 본 차이점들이 실시간에 대한 차이로 귀결시켜 보고 싶었는데요..... 실시간에 대해 좋은 경험이었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[알람 서비스와 SSE]]></title>
            <link>https://velog.io/@sally_devv/%EC%95%8C%EB%9E%8C-%EC%84%9C%EB%B9%84%EC%8A%A4%EC%99%80-SSE</link>
            <guid>https://velog.io/@sally_devv/%EC%95%8C%EB%9E%8C-%EC%84%9C%EB%B9%84%EC%8A%A4%EC%99%80-SSE</guid>
            <pubDate>Wed, 15 Mar 2023 14:51:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>학습 하며 정리한 내용들로, 부족한 점은 양해 부탁드립니다. 
알려주시면 감사하겠습니다. 🍀</p>
</blockquote>
<p>앞서 LongPolling 방식으로 구현 후,
다른 URL 통한 SSE 방식의 알람 서비스 방식을 추가적으로 구현 해보며,
주요 학습 했던 것들과 느낀 차이점들을 정리해보고자 합니다.</p>
<h2 id="longpolling-🆚--sse">LongPolling 🆚  SSE</h2>
<p>기본적 차이는</p>
<ul>
<li>LongPolling 은 클라이언트가 상태 체크위한 요청으로 서버 측에서 일정시간 알람 목록을 확인하면서 이벤트 발생시 1번의 응답으로 마무리 될 수 있습니다.<ul>
<li>알람 빈도가 올라가면, 알람의 실시간 전송이 중요할 수록 Polling 과 유사하게 됩니다. (빈번한 커넥션 및 데이터 조회)</li>
</ul>
</li>
<li>SSE는 클라이언트가 발생한 이벤트를 받고자 구독 요청을 하며, 커넥션이 일정시간 유지되어, 서버측에서 알람 이벤트 발생시 여러 차레 응답 하는 형태 입니다.</li>
</ul>
<blockquote>
<p>당시 구현할 때는 큰 차이 보단 좀 불편한 느낌이었는데 
Pub/Sub 으로 가면서 .. 주요하게 느낀 점이 있었습니다.
Polling 🆚 Push ?</p>
</blockquote>
<p>나의 구현은 ...SSE 통한 여러 차례 응답 처리를 하지 않았습니다. 🙃
응답 후 응답을 위한 정보를 바로 삭제 해서 다음 응답을 할 수가 없게 했어요.</p>
<ul>
<li><code>sseAlarmLocalInMemory.delete(recipientId);</code></li>
<li>sseAlarmLocalInMemory 가 맡아야 할 역할이 많은 거 같았고, 추가 자료구조 등 필요해 보였는데, 빠르게 다음 Redis Pub/Sub 으로 넘어가자 생각 했습니다</li>
</ul>
<pre><code class="language-java">    sseAlarmLocalInMemory.get(recipientId)
            .ifPresentOrElse(session -&gt; {
                session.send(Alarm.from(alarmEntity), SseSession.Error.SEND);
                sseAlarmLocalInMemory.delete(recipientId);
            }, () -&gt; log.info(&quot;The alarm is missed. [to: {}]&quot;, recipientId));</code></pre>
<h4 id="고민했던-점들">고민했던 점들</h4>
<ul>
<li>sseAlarmLocalInMemory의 Map 기반 자료구조<ul>
<li>만일, 수신자에 대한 여러 유형의 알람들이 추가 된다면?<ul>
<li>빈번한 변경</li>
</ul>
</li>
</ul>
</li>
<li>수신자에 대한 Queue 형식의 자료구조로 담아 처리하더라도, 동일한 사용자가 댓글을 달고 좋아요와 구독 신청을 한 번에 하면, 동일 수신자에게 동일 발신자의 다른 종류의 알람이 3번 가는게 좋을 것인지 고려해보면, 수정사항들이 많아졌습니다.<ul>
<li>클라이언트는 일정 간격으로만 구독 요청을 하게 하고, 한 번만 전송하자로 ... 단순화 해보니, 응답 후 삭제처리가 진행 됐습니다. ㅎㅎ</li>
</ul>
</li>
<li>여기서 고민 했던 점들은 다음 구현한 Redis Pub/Sub 으로 가면서 sseAlarmLocalInMemory에 많은 역할들이 있었던 점들이 보였고, 분리될 수 있는 점들을 볼 수 있었습니다. <strong>GOOD 💚</strong></li>
</ul>
<h4 id="개인적으로-차이">개인적으로 차이</h4>
<ul>
<li>SSE 는 SSE 정보를 네트워크 통한 캐시 저장소 활용시 분산 서버에서의 이용이 가능하다</li>
<li>분리 될 수록 외부 요청처리로 트랜잭션 분리를 위해 PostService 에서의 포스트 댓글 등록 로직과 AlarmService 호출 처리를 별개로 분리 위한 별도의 layer 추가 등 리팩토링으로 성능 개선이 가능할 거 같다</li>
</ul>
<br>

<h3 id="💭-if-분산서버-예상한다면-">💭 IF 분산서버 예상한다면 ?</h3>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/5531ad47-9dc4-4b26-87d0-d11903357966/image.png" alt=""></p>
<ul>
<li>클라이언트에서 EventSource 통해 요청</li>
<li>요청(1번 수신자)을 받은 서버가 응답 할 정보를 네트워크 통한 캐시 레이어의 InMemory 저장소에 저장</li>
<li>다른 클라이언트가 (1번 수신자가 작성한 게시글에) 댓글을 등록하면(8번 사용자) 해당 요청 처리를 다른 서버가 하게 한다면</li>
<li>그림에서 CACHE라 명한 공통 저장소를 통해 댓글 등록시 해당 응답 정보로 알람 전송 처리도 일어나게 할 거 같아요</li>
</ul>
<hr>
<h2 id="class-sseemitter">Class SseEmitter</h2>
<p>사실 적용까진 하지 않았지만, 정리 해 봅니다.</p>
<pre><code class="language-java">public SseEmitter()
public SseEmitter(Long timeout)</code></pre>
<ul>
<li><p>timeout 설정 안하면 , the underlying server 에 의해 결정 된다고 하는데요.. ?</p>
<ul>
<li>톰캣 기준이 될 거 같습니다.<ul>
<li>스프링부트 내장 톰캣의 경우 HTTP 커넥션을 30초 동안 연결</li>
</ul>
</li>
<li><a href="https://medium.com/@bethecodewithyou/server-sent-events-concept-f5d34b3c2ecc">Server Sent Events — Concept</a><ul>
<li><a href="https://extremeportal.blogspot.com/2019/02/server-sent-events-development-test.html">테스트 예시</a> 도 있습니다.</li>
</ul>
</li>
</ul>
</li>
<li><p>톰캣 설정을 조금 봐보면</p>
<ul>
<li><a href="https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties.server">docs.spring.io</a></li>
<li><strong>server.tomcat.connection-timeout</strong><ul>
<li>연결 수락 후 커넥터 대기 시간</li>
</ul>
</li>
<li><strong>server.tomcat.keep-alive-timeout</strong><ul>
<li>미 설정시 Connection Timeout으로 세팅</li>
<li>커넥션 종료 전 다른 HTTP 요청 대기시간</li>
<li>-1 : no timeout</li>
</ul>
</li>
</ul>
</li>
<li><p><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.html">javadoc-api</a></p>
<pre><code class="language-java">public void send(Object object) throws IOException

</code></pre>
</li>
</ul>
<p>public void send(Object object, @Nullable MediaType mediaType)
          throws IOException
// static import of SseEmitter.*
 SseEmitter emitter = new SseEmitter();
 emitter.send(event().data(myObject, MediaType.APPLICATION_JSON));</p>
<p>public void send(SseEmitter.SseEventBuilder builder)
          throws IOException
// static import of SseEmitter
SseEmitter emitter = new SseEmitter();
emitter.send(event().name(&quot;update&quot;).id(&quot;1&quot;).data(myObject));</p>
<p>public static SseEmitter.SseEventBuilder event()</p>
<pre><code>
- MediaType 힌트로 HttpMessageConverter 선택

    &gt; 스프링 부트는 요청 타입 설정시 자동으로 응답 타입 결정된다.
    - 별도의 응답 타입 지정 없이 아래와 같은 결과의 응답 헤더 확인 할 수 있었다.
    &gt; 
    &gt; 
    &gt; ![](https://velog.velcdn.com/images/sally_devv/post/b8bdadaf-9f23-4a0c-bbba-a6fa79187fd1/image.png)

- SseEventBuilder 는 이밴트를 포맷하기 위한 이용
- 예외 발생은 **ResponseBodyEmitter 의 send() 기준**
    - 스프링 MVC 가 예외 처리하는 메커니즘 통해 전달할 앱 서버로 dispatch
    - 이는 별도 처리 위한 completeWithError(Throwable) 호출하거나 서블릿 컨테이너 통한 처리등 필요없다.
- 완료 or 타임아웃시
    - 콜백 패턴 위임받는 내부 클래스 이용한(ResponseBodyEmitter) 리팩토링
  ``` java
      sseSession.checkEmitter(
          () -&gt; sseAlarmLocalInMemory.delete(sseSession.recipientId()));</code></pre><pre><code>![](https://velog.velcdn.com/images/sally_devv/post/b430a8fd-22d8-42d7-bd24-659cf0533bbb/image.png)</code></pre><hr>
<h2 id="그외-배포시-여러-에러-사항들">그외 배포시 여러 에러 사항들</h2>
<p>저는 배포하지 않아서 정리만 해봅니다.
자세한 내용은 아래 블로그를 참고 하면 좋을 거 같습니다.</p>
<p><a href="https://tecoble.techcourse.co.kr/post/2022-10-11-server-sent-events/">Spring에서 Server-Sent-Events 구현하기</a></p>
<h3 id="요약">요약</h3>
<p>( 자세한 내용은 해당 블로그를 참고해 주세요.)</p>
<ul>
<li><p>Client</p>
<ul>
<li>이벤트 구독 위한 요청 = 알람 전송할 정보</li>
<li>Request<ul>
<li>Request-line : ~ HTTP 1.1 ( for 지속적 연결 )</li>
<li>Accept: text/event-stream</li>
<li>Cache-Control: no-cache</li>
</ul>
</li>
<li><h2 id="eventsource-인터페이스-이용한-sse-연결-요청">EventSource 인터페이스 이용한 SSE 연결 요청</h2>
</li>
</ul>
</li>
<li><p>Server</p>
<ul>
<li><p>client 로 부터 받은 브라우저 정보를 연결된 시간 동안 응답</p>
</li>
<li><p>Response</p>
<ul>
<li>Response-line : HTTP/1.1 200</li>
<li>Content-Type: text/event-stream;charset=UTF-8<ul>
<li>데이터 전송시 데이터는 <code>UTF-8</code>로 인코딩된 텍스트 데이터만 가능<ul>
<li>바이너리 데이터는 전송 불가능</li>
</ul>
</li>
</ul>
</li>
<li>Transfer-Encoding: chunked<ul>
<li>스트리밍 때문에 본문의 크기를 알 수 없기 때문에 필요한 설정</li>
</ul>
</li>
</ul>
</li>
<li><p>spring framework 4.2부터 SSE 통신을 지원하는 <code>SseEmitter</code> API를 제공</p>
<ul>
<li>SSE 요청에 대한 응답 기능</li>
</ul>
</li>
<li><p>503 Service Unavailable 에러</p>
<ul>
<li><p>Emitter 생성 후 만료시간 까지 보낸 데이터 없으면, 클라이언트에서 재연결 요청시 에러 발생</p>
</li>
<li><p>에러 방지 위해 처음 연결시 더미 데이터를 응답</p>
<pre><code class="language-java">// SseAlarmService - connect()
sseSession.send(ALARM_CONNECTION_MESSAGE, SseSession.Error.CONNECTION);</code></pre>
</li>
</ul>
</li>
<li><p>헤더 통한 토큰 전달</p>
<ul>
<li><code>EventSource</code> 인터페이스는 기본적으로 헤더 전달을 지원하지 않는다<ul>
<li><a href="https://www.npmjs.com/package/event-source-polyfill">event-source-polyfill</a> 사용</li>
</ul>
</li>
</ul>
</li>
<li><p>JPA 사용시 Connection 고갈 문제</p>
<ul>
<li>SSE 통신을 하는 동안은 HTTP Connection이 계속 열려 있어,<ul>
<li>SSE 연결 응답 API 에서 JPA 사용시 <code>open-in-view</code> 속성이 true 이면<ul>
<li>HTTP Connection이 열려있는 동안 DB Connection도 지속</li>
<li><code>open-in-view</code> 설정 반드시 false로 설정</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p>Nginx 사용시 주의할 점</p>
<ul>
<li><p>Nginx는 기본적으로 Upstream으로 요청을 보낼때 HTTP/1.0 버전 사용</p>
<ul>
<li><p>위에서 Response-line : HTTP/1.1 200 필요</p>
</li>
<li><p>Nginx에서 백엔드 WAS로 요청시 <strong><code>Connection: close</code></strong> 헤더 사용으로 연결 지속 X</p>
<pre><code>proxy_set_header Connection &#39;&#39;;
proxy_http_version 1.1;</code></pre></li>
</ul>
<ul>
<li>Nginx의 proxy buffering 기능도 조심<ul>
<li>Request 헤더 Transfer-Encoding: chunked 사용</li>
<li>Nginx는 서버의 응답을 버퍼에 저장해두었다가 버퍼가 차거나 서버가 응답 데이터를 모두 보내면 클라이언트로 전송하게 됩니다.<ul>
<li>SSE 통신 시 원하는대로 동작하지 않거나 실시간성이 떨어지게 된다</li>
<li>SSE 응답에 대해서는 proxy buffering 설정을 비활성화 ?<ul>
<li>모든 API 응답에 대해서도 버퍼링을 하지 않기 때문에 비효율적</li>
<li>nginx의 <a href="https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/">X-accel</a> 기능을 활용</li>
<li>SSE 응답을 반환하는 API의 헤더에 <code>X-Accel-Buffering: no</code></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[알람 서비스 LongPolling으로 구현하며]]></title>
            <link>https://velog.io/@sally_devv/%EC%95%8C%EB%9E%8C-%EC%84%9C%EB%B9%84%EC%8A%A4-LongPolling%EC%9C%BC%EB%A1%9C-%EA%B5%AC%ED%98%84%ED%95%98%EB%A9%B0</link>
            <guid>https://velog.io/@sally_devv/%EC%95%8C%EB%9E%8C-%EC%84%9C%EB%B9%84%EC%8A%A4-LongPolling%EC%9C%BC%EB%A1%9C-%EA%B5%AC%ED%98%84%ED%95%98%EB%A9%B0</guid>
            <pubDate>Wed, 01 Mar 2023 11:43:11 GMT</pubDate>
            <description><![CDATA[<p>알림 서비스를 알아보면 모바일 별 FCM 등 이용방법이 있었고, 각 디바이스 정보를 보관해서 보내는 거 같았다.
예전에는 어떤 방법들이 있었을까?
효율면에서는 시작할 거 같지 않지만, 구현과정은 달랐었고, 3가지 정도 구현해보자 싶었다.</p>
<ul>
<li>Polling, LongPolling<ul>
<li>Polling : 단순하게 클라이언트 쪽에서 요청할 때 응답 통한 전달</li>
<li>LongPolling : 클라이언트에서 요청 후 일정 시간 동안 데이터가 발생하면 응답 통한 전달<ul>
<li>기존 보다는 실시간 어필</li>
</ul>
</li>
</ul>
</li>
<li>SSE <ul>
<li>클라이언트가 구독 신청하면, HTTP 커넥션 유지되어 스트림 통해 실시간으로 여러차례 응답</li>
<li>기존 보다, 더 실시간 어필</li>
</ul>
</li>
<li>Redis Pub/Sub<ul>
<li>당연 실시간, 조사 중... 현재 Redis 연동 되있기 때문에</li>
</ul>
</li>
</ul>
<blockquote>
<p>웹 소켓의 양방향성과 다르다고 봤다. 채팅등에 이용되는데, 알람 서비스는 일단 구독 요청에 대해서 추후 알람을 서버에서 클라이언트로 보내주는 점이 다르다.
그리고, 이런 단방향 성 서버 전송 방식이 구현하며 차이를 볼 때 흥미로운 점이 있었다.</p>
</blockquote>
<hr>
<blockquote>
<p>학습 하면서 구현 실습 해본 경험이라 잘 못된 부분이 있을 수 있습니다. 알려주시면 감사하겠습니다. </p>
</blockquote>
<p>Long Polling 은 서버 측에서 응답을 일정시간 동안 대기 상태에 둬야 한다고 봤다.
어떻게 대기 하지? </p>
<ul>
<li>스프링은 요청 별로 스레드 할당 한다.</li>
<li>그 스레드가 주어진 일정 시간 동안 할 일은?<ul>
<li>알람 전송 할 데이터 확인하기</li>
<li>데이터 있으면 전송</li>
</ul>
</li>
</ul>
<p>앞에서 시간 ➡️ 할 일 순서로 봤는데, 
🔙 할 일을 시간 동안 하는 순서로 보면 ?</p>
<ul>
<li>주어진 시간 동안 알람 전송 할 데이터를 여러 번 조회해서 확인 하기</li>
<li>그 시간 동안 컨트롤러는 응답 지연 중</li>
<li>데이터 있으면 전송, 없으면 응답</li>
</ul>
<p>좀 더 세분화 해서 정리해보면 😢
✅ 주어진 시간 동안 알람 전송 할 데이터를 여러 번 조회 어떻게?</p>
<ul>
<li>실시간 조회 ➜ Crontab = 스프링 스케줄러 </li>
</ul>
<p>✅ 그 시간 동안 컨트롤러는 응답 지연 중</p>
<ul>
<li>다른 클라이언트가 알람 조회 요청 ➜ 스프링에서 관리되는 스레드 사용</li>
<li>그런데 이 스레드 사용이 다른 컨트롤러 요청과 성격이 다르다.<ul>
<li>바로 응답하여 확인이 아닌, 데이터가 없을 수도 있다. 서버 측에서 데이터가 있다면 전달하는게 더 큰 역할 느낌 </li>
<li>새로고침이나 사용자가 직접 요청이 아니다.</li>
</ul>
</li>
<li>그렇게 좀 다른 의미로 의미가 적은 요소에 스프링의 웹 스레드를 회원 수 만큼 계속 처리한다면 ? (이미 불가능 ㅎㅎㅎ)<ul>
<li>트래픽이 너무 많지 않고, 변경이 잦지 않은 환경이란 가정하에</li>
<li><span style="background-color: #C8FFFF">5초 동안 10명의 요청을 가정했다.</span> 😶</li>
</ul>
</li>
</ul>
<p>✅ 데이터 있으면 전송, 없으면 그냥 응답</p>
<ul>
<li>그렇구나</li>
</ul>
<hr>
<p>가장 중요한 걸 미루고 있었다. (모르는데? 모르는데? ...) </p>
<blockquote>
<p>주어진 시간 동안 컨트롤러가 대기 후 응답</p>
</blockquote>
<ul>
<li>시간 지연 + 동작</li>
</ul>
<p>요청별 스레드 할당 해서 스케줄러로 5초 동안 1초 씩 돈다면
DB 트랜잭션까지 이용하며 조회 해와서 데이터 없으면 그냥 응답인데,
데이터 있으면 그 순간 데이터 담아서 전송해야 한다.</p>
<ul>
<li>이쯤부터, Redis 생각이 많이 들었다. 알람을 영속성 데이터로 저장 할 필요 있을까 생각해보니, 전송 후 없어도 될 거 같은게 알람 이었다. 하지만, 더 새로운 것보다는 집중해보기로 했다. (아직 생각 못 한 요구사항이 떠오를 지도) </li>
</ul>
<blockquote>
<p>일단 구현하기 ㅎㅎㅎ</p>
</blockquote>
<p>요청 받고, 스케줄러 도는데, 계속 돌면서 계속 조회 결과 없고 응답은 없다. 나의 약한 노트북을 이렇게 과거 괴롭힌 적 없는데 ... 😬
하지만, 문제들을 보며, 이 때 부터 로직들을 잘 정리 할 수 있었고, 구현까지 갔다.</p>
<hr>
<h2 id="scheduled">@Scheduled</h2>
<p>여러 옵션 설정이 있다. </p>
<ul>
<li>외부 응답 보내기, DB 조회, 변경 으로 꽤 시간 소요되는 작업들이 많았다. </li>
<li><code>fixedRate</code> : 이전 작업 시작 시간 부터 고정 시간 설정</li>
</ul>
<p><span style="background-color: gold">@Scheduled 적용 메서드는 전달할 파라미터나 반환 값이 없어야 한다.</span></p>
<ul>
<li><p>클라이언트가 조회 요청하면, 그 정보를 별도 자료구조에 보관해야 한다.</p>
</li>
<li><p>1명을 5초 동안 반복해서 확인 하는게 처음 생각한 LongPolling 개념 이이었다. </p>
<ul>
<li><p>자료구조에는 스케줄러에서 이용할 클라리언트 정보가 담긴다</p>
<ul>
<li>5초가 짧은 시간 이라면, 지연되더라도 다음 사용자 위한 처리로 1초씩 돌때 응답 대기 시간 5초 동안 5명의 순회는 보장은 어떨까 싶었다.🙃</li>
</ul>
</li>
<li><p><span style="background-color: #C8FFFF">5초 동안 10명의 요청 이란 ? </span></p>
<ul>
<li>5초 내 : 1초 단위로 5명 확인<ul>
<li>1초 간격 마다 사용자 요청? </li>
<li>1초에 5명 요청 후 4초 때 5명 요청?</li>
<li>만일 더 길어진 요청 대기 시간 동안 1초 간격 순회?</li>
<li>10 초에 스케줄러는 10명 체크 보장</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li>요청 별도로 받을 자료구조 : 시간순서대로 처리하자 FIFO</li>
<li>5명의 요청을 어떻게 보관할까?<ul>
<li>스케줄러 1초 마다 별도 처리라 생각하면 <span style="background-color: gold">비동기</span>가 떠올랐다.</li>
</ul>
</li>
</ul>
<h2 id="deferredresult">DeferredResult</h2>
<p>비동기 처리 하면 무언가 많은데
LongPolling은 요즘 안 쓰여서 인지 설명이나 구현 예제들이 적어보였지만, DeferredResult 가 기본인 거 같았다.</p>
<ul>
<li><a href="https://www.baeldung.com/spring-mvc-long-polling">벨덩 Spring MVC Long Polling</a></li>
</ul>
<blockquote>
<p>번역 해서 간력히 써보면</p>
</blockquote>
<p>Long polling 은 서버 애플리케이션이 정보를 이용할수 있을 때까지 클라이언트와의 연결을 유지하는 방식으로, 서버가 정보를 얻거나 결과를 기다리기 위해 다운스트림 서비스를 요청해야 할 때 사용된다.</p>
<p>DeferredResult</p>
<ul>
<li>에러와 타임아웃을 어떻게 다룰지와 테스트 방식 알기</li>
</ul>
<p>Long Polling과 DeferredResult</p>
<ul>
<li>Spring MVC 에서 인바운드 HTTP 요청을 비동기적으로 다루고자 할 때 사용</li>
<li>들어오는 요청을 다루기 위한 HTTP 작업 스레드가 다른 작업 스레드로 오프로드 할 수 있게 하여 대기시간이나 긴 컴퓨팅 시간을 좀더 효율적으로 이용할 수 있다.</li>
<li>worker가 setResult 를 호출 할 때, 컨테이너 스레드는 요청한 클라이언트한테 응답을 허용한다</li>
<li>다운 스트림 시스템으로부터 절대 응답 받지 못할 경우를 위한 타임아웃 메커니즘은 필수이다.</li>
</ul>
<pre><code class="language-java">  DeferredResult&lt;Response&lt;List&lt;Alarm&gt;&gt;&gt; output = new DeferredResult&lt;&gt;(LONG_POLLING_TIMEOUT);
  output.onTimeout(() -&gt; {
      output.setErrorResult(String.format(&quot;TimeOut: %dms&quot;, LONG_POLLING_TIMEOUT));
      alarmInMemory.remove();
  });</code></pre>
<ul>
<li><p>타임아웃 임계치 도달시 컨테이너 스레드에 의한 입력을 Runnable 로 하여,</p>
<ul>
<li>타임아웃 되면 에러나 <em>setErrorResult 로 다룬다.</em></li>
</ul>
</li>
<li><p>타입아웃 확인 위한 테스팅 예제</p>
<pre><code class="language-java">  private static void enableTimeout(MvcResult asyncListener) throws IOException {
      ((MockAsyncContext)asyncListener
          .getRequest()
          .getAsyncContext())
          .getListeners()
          .get(0)
          .onTimeout(null);
  }</code></pre>
</li>
</ul>
<p><a href="https://www.baeldung.com/spring-deferred-result">벨덩 DeferredResult Callbacks</a></p>
<ul>
<li><p>비동기 요청 완료시</p>
<pre><code class="language-java">  output.onCompletion(() -&gt; log.info(&quot;The alarm publish is completed.&quot;));</code></pre>
</li>
<li><p>타임 아웃 발생시</p>
<pre><code class="language-java">output.onTimeout(() -&gt; {
          output.setErrorResult(String.format(&quot;TimeOut: %dms&quot;, LONG_POLLING_TIMEOUT));
          alarmInMemory.remove();
      });</code></pre>
</li>
<li><p>에러 발생시</p>
<pre><code class="language-java">deferredResult.onError((Throwable t) -&gt; {
  deferredResult.setErrorResult(
    ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
      .body(&quot;An error occurred.&quot;));
});
</code></pre>
</li>
</ul>
<p>```</p>
<hr>
<h2 id="alarmentity">AlarmEntity</h2>
<p>현재는 많은 기능 없고 댓글 등록시 알람 전송만 하지만, 좋아요나 팔로우 등 추가한다면 생각하고 구현 해봤습니다.
확실히 ... 기능적인 부분보다 복잡 하니 잼잼잼...</p>
<blockquote>
<p>설계 과정으로 아주 주관적인 이야기</p>
</blockquote>
<p>메시지? : <em><code>누가</code> <code>-의</code>  <code>-에</code>  <code>-을</code></em>   했습니다. <em>(sender →  recipient)</em></p>
<p>검색 ? : <em>-의</em></p>
<ul>
<li>누가 :  댓글 작성자, 좋아요 클릭한 회원, 팔로우 신청자</li>
<li>-에 :<ul>
<li>포스트명? ➜ 포스트명이 길면?<ul>
<li>포스트에 (postId) (default : 내가 작성한 포스트) → <code>님</code>이 댓글을 달았습니다.</li>
<li><strong>user flow 상 짧아도 되지 않을까?</strong> <code>댓글이 달렸습니다</code></li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>user flow ? ** 
  **click</strong> alarm&#39;s message  → show 포스트 or 댓글<br>  → <strong>click</strong> the part of content  →  sender 조회</p>
</blockquote>
<ul>
<li><p>댓글은 제목도 없다.</p>
<ul>
<li>짧게 메시지 전달하고 클릭시 해당 <strong>키워드</strong> (포스트, 댓글, 팔로워)  를 보러 가게 하면 좋을 거 같다.</li>
</ul>
</li>
<li><p>키워드 ? <strong>AlarmType</strong></p>
<table>
<thead>
<tr>
<th>AlarmType</th>
<th>메시지</th>
</tr>
</thead>
<tbody><tr>
<td>COMMENT</td>
<td>댓글을 달았습니다.</td>
</tr>
<tr>
<td>POST_LIKE</td>
<td>포스트에 좋아요를 눌렀습니다.</td>
</tr>
<tr>
<td>COMMENT_LIKE</td>
<td>댓글에 좋아요를 눌렀습니다.  (댓글 작성자가 recipient)</td>
</tr>
<tr>
<td>FOLLOW</td>
<td>팔로우/구독? 합니다.</td>
</tr>
<tr>
<td>- 댓글</td>
<td></td>
</tr>
<tr>
<td>- 댓글 보러가기 → 포스트 or 댓글</td>
<td></td>
</tr>
<tr>
<td>- postId</td>
<td></td>
</tr>
<tr>
<td>- commentId + 댓글 목록 ( <em><code>/api/v2/sns/posts/{postId}/comments</code> )</em></td>
<td></td>
</tr>
<tr>
<td>- 좋아요</td>
<td></td>
</tr>
<tr>
<td>- 포스트 보러가기</td>
<td></td>
</tr>
<tr>
<td>- postId</td>
<td></td>
</tr>
<tr>
<td>- 댓글 보러가기</td>
<td></td>
</tr>
<tr>
<td>- commentId + 댓글 목록 ( <em><code>/api/v2/sns/posts/{postId}/comments</code> )</em></td>
<td></td>
</tr>
<tr>
<td>- 팔로우</td>
<td></td>
</tr>
<tr>
<td>- senderId (userId) 포스트 목록 보러가기</td>
<td></td>
</tr>
</tbody></table>
</li>
</ul>
<h3 id="🚨-문제는-">🚨 문제는 ...</h3>
<p>각 키워드 별(AlarmType) 별 저장 할 데이터들이 달랐다.</p>
<p>처음에는 아래처럼 단순화 할 수 있을까 싶었는데, 요구사항이 위처럼 4가지라면 변경 발생시 적절하지도 못 했다.
<img src="https://velog.velcdn.com/images/sally_devv/post/eb2ff5ba-2cca-4783-9c07-29f4f737e534/image.png" alt=""></p>
<p>💡 그래서 모든 값들을 담아보자 생각해본 방법 4가지</p>
<ul>
<li>테이블 내 컬럼 별 값을 그냥 null 로 대체한다.</li>
<li>별도의 각 타입별 저장할 데이터 목록화한 테이블을 만들어 PK 를 FK로 주입</li>
<li>MySQL 의 JSON 타입</li>
<li>NoSQL</li>
</ul>
<p>순서대로 보자면</p>
<ul>
<li>앞서 <em>알람 테이블은 영속성 데이터 보관이 필요할까</em> 란 고민이 잠시 있었다. 짧은 식견으로는 다른 테이블 보다는 보관되지 않고 삭제 작업이 주요할 거 같았다.
그런 면에서 null 은 무시할 수도 있을 수 있지만, null 에 대해 다르게 설계해보자가 제 주관이라서 pass</li>
<li>역시 위와 동일한 이유로 조인까지 하면서 조회하고 관리 해야 할까 싶었다. 게다가 스케줄러의 반복 조회되는 상황이라 배제해보자 생각</li>
<li>마지막 NoSQL은 처음부터 미뤘으니까 ...</li>
</ul>
<blockquote>
<p>MySQL의 JSON 타입 사용 하기로 했다.</p>
</blockquote>
<ul>
<li>의존성 추가
<code>implementation &#39;com.vladmihalcea:hibernate-types-52:2.20.0\&#39;</code></li>
<li>엔티티에 Json 애노테이션 추가
<code>@TypeDef(name = &quot;json&quot;, typeClass = JsonStringType.class)</code>
<code>@Type(type = &quot;json&quot;)</code><ul>
<li>typeClass가 여러가지 이니 추가로 알아보면 좋다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="longpollingalarmservice">LongPollingAlarmService</h2>
<ul>
<li>요구사항 2가지를 생각 해 봤다.<ul>
<li>조회 시간 이후 발생된 알람만 전송</li>
<li>클라이언트가 접속으로 요청시 미발송 알람들 모두 전송</li>
</ul>
</li>
</ul>
<p>첫 번째는 쿼리 조회 조건으로 시간만 추가하면 돼서 간단해 보였었다.
2번째로 구현하면 알람이 너무 많은 경우 모아서 전달 해 버린다.</p>
<ul>
<li>최근 10개 중 delete 안 된 것만 ?<ul>
<li>만일 30개가 쌓이면.. 나머진 ?</li>
<li>모두 조회해와서 10개만 응답으로 보내고 나머진 delete 진행 ?</li>
</ul>
</li>
<li>페이징이라도 추가 할 수 도 있지만 50개 있는데 10개씩 5번 전송...<ul>
<li>잦은 변경이 아닌 상황 설정상 알람이 별로 없을 거라는 가정이었으니까....(🛫 물론 지금처럼 목록 보낼 경우 페이징 또는 개수 제한이 있는게 좋다)</li>
</ul>
</li>
</ul>
<br>

<h4 id="알람-요구사항-정리">알람 요구사항 정리</h4>
<ul>
<li>알람 전송은 얼마나 필수 일까?<ul>
<li>필수라면, 반복되더라도 꼭 보내줘야 할까?<ul>
<li>필수라면, 반복 되는 중복 알람도 허용?</li>
</ul>
</li>
<li>필수가 아니라면 보완이나 대책은?<ul>
<li>없어도 되는가? .. 극악은 알람 없어도?</li>
<li>다른 방식으로 확인 방법이 있을까?</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>결국은, 알람 목록 확인 페이지 등이 별도로 있다면, 몇 건 미스되더라도 현재 시간 이후의 실시간 알람이 나을 거 같다.</p>
</blockquote>
<ul>
<li>서비스 마다 다르겠지만, SNS 피드 형식에서는 알람 끄기 기능이 제공되기도 하고, 너무 많은 알람을 보통 선호하진 않을...거 같은데, 중복은 더 큰 문제인 거 같았다.</li>
<li>알람 미전송 보다 중복 알람 더 오류로 느껴질 거 같다고 생각 했습니다.<blockquote>
</blockquote>
p.s.주관적 생각입니다.</li>
</ul>
<p>... 생각과 다르게 구현은 일단 모두 조회해서 전송하고 변경 로직으로 했습니다. 그래서 이곳에 나름 자세히 정리해 봅니다. ㅎㅎㅎ </p>
<ul>
<li>현 상태로는 @Transactional 필수</li>
</ul>
<h3 id="그-외에도-수정한다면">그 외에도 수정한다면</h3>
<ul>
<li><p>처음에는 응답 시간 동안 한 사용자의 5번 조회 였지만, 5초의 시간 동안 확인하는 조회되는 사용자의 확실성으로 변경 했고, 데이터 없는 경우 타임아웃 응답이 그대로였다.</p>
<ul>
<li>조회 데이터가 없는 경우 타임아웃 콜백패턴의 응답은 커널 통한 동작 처리라면 커널 영역으로 오버헤드가 있을 거 같다 생각이다.</li>
<li>확인 시마다 데이터 유무 무관하게 바로 응답 하자는 생각이다.</li>
<li>그럴 경우 클라이언트에서 일정 시간 간격으로 요청이 보장되야 한다.<ul>
<li>그렇지 않으면, 응답 후 바로 요청한다면 사용자가 늘수록 요청도 많아진다.</li>
</ul>
</li>
</ul>
</li>
<li><p>실시간 조회 보다 댓글 등록 시점에 해당 포스트 정보 가진 조회로 응답 한다면, LongPollingAlarmInMemory 를 해시로 구현 한다면 어떨까?</p>
<ul>
<li>요구사항은 5초 동안 사용자 조회 였었다. 이는 댓글 등록되지 않는다면 타임아웃 발생으로 응답을 보낸다.</li>
<li>이런 점이 SSE 의 여러 응답 처리나 이벤트 발생 기준 발행 방식과의 차이인 것 같다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 리팩토링]]></title>
            <link>https://velog.io/@sally_devv/TIL-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@sally_devv/TIL-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</guid>
            <pubDate>Tue, 21 Feb 2023 12:46:27 GMT</pubDate>
            <description><![CDATA[<h2 id="til">TIL</h2>
<blockquote>
<p>찝찝하니 힘든 하루였던거 같다.</p>
</blockquote>
<p>어제 댓글 달기 구현이 포스트하고 유사하여, 좀 더 흥미로운 걸 찾아다녔다.</p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/ff45fd91-b06c-4d03-8e47-64862145d5e8/image.png" alt=""></p>
<p>User</p>
<ul>
<li>JWT 토큰 필터 구현하면서 인증 객체</li>
<li>캐시에 저장 되는 객체</li>
<li>컨트롤러에서 인증객체로서 캐스팅 되어 id 등 전달</li>
</ul>
<p>Redis에 저장하기 위해 @JsonIgnore 를 추가 된 부분 ?</p>
<ul>
<li>스프링 시큐리티의 인증 타입 위해 <code>AuthenticatedPrincipal</code>  구현부</li>
<li>@Getter 에 맞춰 저장되다 보니 중복된 내용이 캐싱에 저장되는 걸 막기 위해서</li>
<li>getter 롬복은 쓰고 .. 머 그런 (당시 좀 빠듯하게 열심히 했던 거지만, 엉망인거 같네요)</li>
</ul>
<blockquote>
<p>SRP 위반 ? 보다는 사실 분리 해볼까? ㅎㅎ</p>
</blockquote>
<hr>
<h2 id="실수-1-쌓인-생각들이-많았다">실수 1. 쌓인 생각들이 많았다.</h2>
<blockquote>
<p>내가 레거시...</p>
</blockquote>
<ul>
<li><p>Post 와 Comment 구현하면서 인증객체가 가진 role 뿐이지만, 노출을 최소화 하면서 구현하자 생각으로 Member DTO 이용</p>
</li>
<li><p>User는 인증 객체로만 인지 되었고</p>
<ul>
<li>컨트롤러 계층에서 Authentication 으로만 이용하게 선을 그으면 어떻까?</li>
<li>도메인까지는 연결하지 말자</li>
<li>캐싱도 동시성 이슈 등 생각하면 여러곳에서 조회 요청보다 토큰 필터로서 앞에서만 요청하는게 적당해 보였다.<ul>
<li>UserService 의 loadUserByUserName() ➜ getThatIfMember() 로 변경</li>
</ul>
</li>
</ul>
</li>
<li><p>서비스에서 연관관계로 조회해올 때 UserEntity 에서 role 까지 가져오는 로직이 필요 ?</p>
<ul>
<li>현재는 없다, 생겨도 분리할 거 같다.</li>
<li>role 이 시스템에서 권한 등 관련 있다면 노출하지 않는게 좋지 않을까 생각</li>
<li>필요없는 데이터는 줄이는 방향으로 해보자<ul>
<li>이 방향은 JPA에서 view 개념의 PostView, CommentView 를 이용하면서</li>
<li>별도의 Member 타입으로의 반환을 생각하게 했다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="실수2-불분명한-">실수2. 불분명한 ?</h2>
<blockquote>
<p>내가 딱 하나 기억하는, 마틴파울러님이 리팩토링은 ... 확실할 때 ~ </p>
</blockquote>
<p>두 부분을 각각 분리해 봤다.</p>
<blockquote>
<p>AuthenticatedPrincipal  →  SecurityUser
 Redis → User</p>
</blockquote>
<p>컨트롤러에서 SprignSecurity 의 Authentication 타입 2가지 이용</p>
<ul>
<li>AuthenticatedPrincipal  의 getName()</li>
<li>User 로 타입 캐스팅 통한 getId() 등</li>
</ul>
<p>(이때쯤부터 시야가 좁아진 거 같다.)
AuthenticatedPrincipal  을 구현한 SecurityUser 자체로 getName() 은 변동사항 없었지만,
<span style="background-color: paleturquoise">User 로의 타입 캐스팅이 문제였다.</span></p>
<pre><code class="language-java">SecurityUser securityUser = (SecurityUser)authentication.getPrincipal();
securityUser.toUser().getId();</code></pre>
<blockquote>
<p><code>.toUser().getId();</code> 괜히 쓰고 싶지 않은 코드 였다…..</p>
</blockquote>
<p>우선 SecurityUser.class 타입으로 형변환하고 싶다는 의미를 담아 메서드 분리하니 </p>
<p>Class 타입으로 전달 됐다. (IDE ❤️)
<code>clazz.cast(obj)</code> 💕 통해서 형변환 했지만,   <span style="background-color: paleturquoise">User로의 반환이 계속 고민</span></p>
<p>IDE로 두근 대던 심장은 금세 두통이 됐다 …
캐스팅을 여러 컨트롤러에서 해줘야 하기 때문에, 클래스 분리가 좋을 거 같은데 …
정의를 어떻게 내려야 할지 …. ( 이럴 때 클래스명 짓기는 더 어렵다. )</p>
<pre><code class="language-java">private User toUserFrom(
        Authentication authentication,
        Class&lt;SecurityUser&gt; clazz, 
        Class&lt;User&gt; userClass) {
    SecurityUser securityUser = clazz.cast(authentication);
    return securityUser.toUser();
}</code></pre>
<ul>
<li>내가 다른 컨트롤러 만들면 어떻게 쓸까? <ul>
<li>2번 째 스텝이 고민 중이었고, 다른 컨트롤러가 생길 것 같다.</li>
<li>클래스로 정적 메서드 통해 toUserForm() 그대로 사용이 좋아 보였는데, 좀만 더 세분화 해볼까 싶었다. 🔙</li>
</ul>
</li>
</ul>
<h2 id="공통화는-좀더-나중에-">공통화는 좀더 나중에 ?</h2>
<blockquote>
<p>제네릭을 이용해서 util 성으로 분리</p>
</blockquote>
<p>컨트롤러에서 호출시</p>
<pre><code class="language-java">User user = TypeCastingUtil.fromAndSecTo(authentication, SecurityUser.class, User.class);</code></pre>
<p>TypeCastingUtils</p>
<pre><code class="language-java">public static &lt;E, T&gt; E fromAndSecTo(Object obj, Class&lt;T&gt; clazz, Class&lt;E&gt; secClass) {
    return secClass.cast(fromAndTo(obj, clazz));
}

public static &lt;T&gt; T fromAndTo(Object obj, Class&lt;T&gt; clazz) {
    return clazz.cast(obj);  // need to object,  TODO ClassCastException
}</code></pre>
<p>( 나는 혼자 왜 이러고 있을까... )
컴파일 오류 집착하다가 실제 동작이 어떻게 되는지는 놓치고, 그렇게 실행하면서 에러가 발생했다.</p>
<ul>
<li>앞서 메서드에서, SecurityUser 내부의 User 반환 위해 toUser() 메서드 사용한 걸, 그냥 clazz.cast(obj) 를 이용했던 탓이다.<ul>
<li>그냥 SecurityUser 에서 만족 하거나 …<ul>
<li>…. 처음과 똑같다. …</li>
</ul>
</li>
<li><span style="background-color: gold">타입을 넘겨주거나  ← 이거라도 해봐야 겠다.  🔙</span></li>
</ul>
</li>
</ul>
<p>User 를 Redis 저장 위해 쓰던 @Getter 를 AuthenticationUser 로</p>
<ul>
<li>User는 캐시용, SecurityUser는 시큐리티용</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/ab150aa9-856d-47a9-ac20-ad6515269870/image.png" alt=""></p>
<p>컨트롤러에서 TypeCastingUtils 호출부분 toAuthenticationUser 메서드로 분리</p>
<pre><code class="language-java">private AuthenticationUser toAuthenticationUser(Authentication authentication) {
    return TypeCastingUtils.fromAndSecTo(
                authentication.getPrincipal(),
                SecurityUser.class,
                AuthenticationUser.class);
}</code></pre>
<p>AuthenticationUser으로 추상화 하면서, 메서드명이 맘에 걸리는 TypeCastingUtils fromAndSecTo 사용하지 말까 생각이 들었지만,</p>
<pre><code class="language-java">private AuthenticationUser toAuthenticationUser(Authentication authentication) {
        SecurityUser securityUser = TypeCastingUtils.fromAndTo(authentication.getPrincipal(), SecurityUser.class);
        return (AuthenticationUser)securityUser;
}</code></pre>
<ul>
<li>이왕... 제네릭의 컴파일 안정성을 생각해보면, 제네릭 이용한 형변환 방식을 적용하는게 좋지 않을까 생각했습니다.</li>
</ul>
<p>SecurityUser 가 좀 찝찝 
<img src="https://velog.velcdn.com/images/sally_devv/post/486cb27e-4a29-4936-bd58-5d2297cedd60/image.png" alt="">
코드로 보면 그냥 더 찝찝</p>
<ul>
<li>간편하게 @Getter 쓰던 부분이 늘어난게 눈으로 보이니 ... (전이 적절했었을 수도-)<pre><code class="language-java">public class SecurityUser implements AuthenticatedPrincipal, AuthenticationUser {</code></pre>
</li>
</ul>
<hr>
<h2 id="ps-securityusersec-">P.S. SecurityUserSec ...</h2>
<p>SecurityUser 가 여러 메서드 정의를 하게 된게 아닌가, 줄여볼까 싶어서 상속받아 구현 해봤습니다.</p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/f6595d57-03a2-4b2a-8236-821fd88d7d8c/image.png" alt=""></p>
<p>코드는 훨씬 간편해집니다.</p>
<pre><code class="language-java">public class SecurityUserSec extends User implements AuthenticatedPrincipal {
    @Override
    public String getName() {
        return super.getNickname();
    }
}</code></pre>
<p>캐스팅 전달 파라미터도 기존보다 간편 (이상한 메서드명 안써도 된다)</p>
<pre><code class="language-java">AuthenticationUser user = TypeCastingUtils.fromAndTo(authentication.getPrincipal(), AuthenticationUser.class);</code></pre>
<p><em>그렇지만 ...</em>
User 를 상속 받는건, 어쨌든 캐싱으로 저장할 객체의 모든 역할까지 상속 받는 거고,
AuthenticationUser을 구현하는게 더 좋지 않을까 생각했습니다.</p>
<blockquote>
<p>TIL … 찝찝하니.. 쉬어야 겠다. </p>
</blockquote>
<hr>
<p>GitHub</p>
<ul>
<li><a href="https://github.com/sally-ksh/SNS.V2/commit/b04e9f0ede19e0e20eea4fa4d1ff4df616384611">PostApiController</a></li>
<li><a href="https://github.com/sally-ksh/SNS.V2/commit/fd1030c2d3daaa7329672b444854e41a5eb8fe01#diff-4aad7a2deaa93a42f96806a87968708d3f827e0827736e5776f11c30a6b3799d">JwtTokenFilter</a></li>
<li><a href="https://github.com/sally-ksh/SNS.V2/blob/main/src/main/java/com/sally/sns/util/TypeCastingUtils.java">TypeCastingUtils</a></li>
<li><a href="https://github.com/sally-ksh/SNS.V2/blob/main/src/main/java/com/sally/sns/filter/AuthenticationUser.java">AuthenticaitonUser</a></li>
<li><a href="https://github.com/sally-ksh/SNS.V2/blob/main/src/main/java/com/sally/sns/filter/SecurityUser.java">SecurityUser</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT 토큰만 이용해보려 한 로그인의 고찰?]]></title>
            <link>https://velog.io/@sally_devv/JWT-%ED%86%A0%ED%81%B0%EB%A7%8C-%EC%9D%B4%EC%9A%A9%ED%95%B4%EB%B3%B4%EB%A0%A4-%ED%95%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8</link>
            <guid>https://velog.io/@sally_devv/JWT-%ED%86%A0%ED%81%B0%EB%A7%8C-%EC%9D%B4%EC%9A%A9%ED%95%B4%EB%B3%B4%EB%A0%A4-%ED%95%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8</guid>
            <pubDate>Thu, 12 Jan 2023 13:29:18 GMT</pubDate>
            <description><![CDATA[<h1 id="jwt-와-로그인-">JWT 와 로그인 ?</h1>
<ul>
<li>지난 프로젝트에서 로그인을 미루었습니다.<ul>
<li>ArgumentResolver로 대체하려니 파라미터라도 넘겨주는게 좋은데, 클라이언트에겐 불편해서 FakeAuthUser로 컨트롤러 마다 DI 해서 서비스로 인터페이스 통해 넘겨줬는데요. 컨트롤러는 많아지고 .. 미루었습니다.</li>
</ul>
</li>
<li>세션 로그인은 stateful 해서 제가 생각한 서버 환경과는 맞지 않았어요.</li>
<li>임시 프로젝트로 이번에는 시큐리티를 최소화로 사용하면서 구현해보고자 생각 이었습니다.<ul>
<li>필터 통해 컨트롤러의 파라미터로 전달해주는 Authentication 딱 1개.<ul>
<li>😮‍💨 Authentication이 시큐리티 인 걸.. (잘못된 시작은...로드맵이 펼쳐지더라구요.)</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h1 id="에러-시작">에러 시작</h1>
<h2 id="이전의-임시-대처-흔적들">이전의 임시 대처 흔적들</h2>
<ul>
<li>가입, 로그인 하면서 토큰 전달까지 확인 했습니다.</li>
<li>뒤에 나오지만, 이 프로젝트는 임시였거든요. <code>ddl-auto</code> 를 재미나게🎵 사용 했습니다.<ul>
<li>UserRole 로 변경 하면서 <code>@Enumerated(EnumType.STRING)</code>를 빠뜨렸고, DB는 int 컬럼으로 생성 되면서 </li>
<li>시큐리티 사용하면서 JwtTokenFilter 에서 role 전달에 문제가 생겼습니다.</li>
<li>보면 알 수 있지만, 구체적으로 생각해본 적 없는 연결이었습니다. (그렇게 시작 되더군요.)</li>
</ul>
</li>
</ul>
<br>


<h2 id="로그인한-사용자-authentication">로그인한 사용자 Authentication</h2>
<p>Post 작성 구현하면서 끝난 거 같다 생각하던 때 </p>
<blockquote>
<p>로그인한 사용자만 포스트 작성한다.</p>
</blockquote>
<p>위와 같은 요구사항을 위해서, 
제가 처음 목표한 Authentication 을 컨트롤러 파라미터로 전달 하고자 필터 하나 구현 했습니다.</p>
<h3 id="jwttokenfilter">JwtTokenFilter</h3>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/5bcdf40c-185e-4499-b143-a34a349896ec/image.png" alt=""></p>
<p>JwtTokenFilter 는 위와 같이 HttpServletRequest 의 헤더값을 이용해서 토큰을 가져옵니다.
그 다음은 토큰을 통해 토큰에 담긴 내용으로부터 검증 로직을 거쳐 SecurityContext에 Authentication 객체로 보관합니다.</p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/0f28ad9c-f3ad-4cd1-8a7e-ed65deaf215f/image.png" alt=""></p>
<p>간략히 그린 시퀀스 다이어그램 보이는 그대로(이길 바라며...) 
JwtTokenFilter 에서</p>
<ul>
<li>JwtTokenUtile 통해 토큰에 보관한 nickName을 반환해주고</li>
<li>UserService 통해서 nickName 통한 서버 쪽 DB 조회로 사용자 정보를 가져옵니다.</li>
<li>SecurityContext 에 보관합니다.</li>
</ul>
<p>SecurityContextHolder 관련 외에는 제가 임의로 구현했습니다.</p>
<ul>
<li>loadUserByUserName 은 UserDetailsService 구현 방법이 있는데, 그냥 메서드명만 동일합니다. (그렇게 문제로 연결이 되었죠... )</li>
</ul>
<br>

<h3 id="authentication">Authentication</h3>
<p>PostController 에서 <code>authentication.getName()</code> 으로 전달</p>
<pre><code class="language-java">  @PostMapping
  public Response&lt;Post&gt; register(PostCreationRequest postCreationRequest, 
                                  Authentication authentication) {
     Post post = postService.create(postCreationRequest, authentication.getName());
     return Response.success(post);
  }</code></pre>
<p>에러 발생합니다. 
에러는 PostService 에서 전달받은 nickName 통한 UserEntity 조회 중 발생합니다.</p>
<p>디버깅 해보니
먼저 PostController 에서 Authentication 의 principal 안 nickName 이 잘 보입니다.
<img src="https://velog.velcdn.com/images/sally_devv/post/81c4b2dd-65b9-456a-870c-41088eb61b97/image.png" alt=""></p>
<p>문제는 PostService 로 전달 되는 객체값이 참조값으로 String 문자열로 반환되지 않았습니다.
<img src="https://velog.velcdn.com/images/sally_devv/post/54cb75f5-fc7a-4a91-89af-921bdfe95ba2/image.png" alt=""></p>
<p>디버깅을 다시 돌리며 앞에서의 로직을 확인하는데
<img src="https://velog.velcdn.com/images/sally_devv/post/6eed4f97-8c54-45bd-836e-8b844f353afd/image.png" alt="">
PostController 에서의 <code>authentication.getName()</code> 순간 위와 같은 조건문에서 타입 체크를 다 넘어가고 null 은 아닌데, toString()이 참조값으로 넘어갔습니다.</p>
<ul>
<li>없는 값으로 조회요청까지 간 거죠.</li>
</ul>
<p>즉 앞서 말했던, loadUserByUserName 을 제가 구현하면서 제가 만든 객체 타입으로 SecurityContext에서 Authentication 통해 이용할 시그니처만 허용되는 상황 인거 같았어요.</p>
<ul>
<li>getName()이 시큐리티에서 보통 username 으로 쓰는 키값도 저는 <code>nickNme</code>로 커스텀하게 썼으면서 어떻게 가져올지 생각하면 당연한 결과 였어요.</li>
<li>제가 구현한 User 객체에 Authentication에서 이용할 타입과 오퍼레이션을 갖게 했습니다.</li>
<li>UserDetails 는 메서드가 많아서 AuthenticationPrincipal 을 구현했고, 문제 해결 됐습니다. 추상화를 제대로 쓸 때의 장점<ul>
<li>JwtTokenFilter 코드 변경 없다.</li>
<li>DTO 지만 익명 클래스 등 쓰면서 불변 객체로 구현</li>
</ul>
</li>
</ul>
<hr>
<h2 id="시큐리티-알고-가야-하나">시큐리티 알고 가야 하나...</h2>
<h3 id="authentication-을-어떻게-이용할-수-있을까">Authentication 을 어떻게 이용할 수 있을까?</h3>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/f6673670-4535-4f0d-9435-26946cf9b27d/image.png" alt=""></p>
<p><code>UsernamePasswordAuthenticationToken</code> 통해서 연결되었더라구요.
위의 코드에 나온 authentication.getName() 은 AbstractAuthencationToken 에서 오버라이딩해서 타입체크로 이용하고 있었습니다. </p>
<ul>
<li>제가 구현한 코드나 방식과 무관하고, 제가 오류 수정하면서 검색하면서 부분만 봤지만, 나중에 다시 봐볼 겸 자세한 거 같아서 참조해 봅니다.<ul>
<li>저는 UsernamePasswordAuthenticationToken 만 쓰려 했고, 아래 블로그는 시큐리티 기반으로, 자세한 거 같아요.</li>
<li><a href="https://velog.io/@jkijki12/Spirng-Security-Jwt-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0">Spirng Security + Jwt 로그인 적용하기</a></li>
</ul>
</li>
</ul>
<p>이분 블로그 보니 포스트맨 Authorizaion 을 직접 등록하신 것 같은데,..
겸사겸사 첨부하면, 포스트맨에 타입 별 토큰 전달 방식이 있습니다.</p>
<ul>
<li>Authorization 은 타입과 credentials 을 담습니다.<ul>
<li>이는 타입과 토큰으로 타입은 기본적으로 Basic 과 Bearer 이 있는 거 같아요.</li>
<li>Bearer 는 OAuth 관련 있는 듯 한데, 목록에는 OAuth 버전별 타입이 별도로 있습니다.</li>
<li>저는 비밀키 등 넣어놔서 일반적으로 많이 사용하는 듯한 Bearer 를 사용해봤습니다.<ul>
<li><code>Bearer</code> 가 위에서 헤더값 통해 가져온 토큰 판별에도 사용합니다.</li>
</ul>
</li>
<li><a href="https://velog.io/@hyex/HTTP-Authorization-header%EC%97%90-Bearer%EC%99%80-jwt-%EC%A4%91-%EB%AC%B4%EC%97%87%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C">HTTP Authorization header에 Bearer와 jwt 중 무엇을 사용할까?</a></li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/b94d39bf-f8b4-439c-8b72-6ae3b4923061/image.png" alt=""></p>
<h3 id="끝인-줄-알았지-">끝인 줄 알았지 ...</h3>
<p>에러 메시지가 제가 만든 Response 형식이 아니었습니다. <em>Restful...</em></p>
<ul>
<li>처음에는 PostController 까지 넘어간 에러 였지만,</li>
<li>토큰 제외하고 확인하는 과정에서 다른 에러를 보게 됐네요.<ul>
<li>&#39;에러 발생 맞네&#39; 하고 넘어갈 뻔 했습니다.
<img src="https://velog.velcdn.com/images/sally_devv/post/b24b3ebf-b611-45ec-ae3a-109a85cf22e6/image.png" alt=""></li>
</ul>
</li>
</ul>
<p>무지성이라 시큐리티 필터 부분은 점점 자신이 작아지드라구요. 
블로그 참고 하면서 넘어 갈 수 있었습니다. </p>
<ul>
<li>감사감사 <a href="https://sas-study.tistory.com/362">Restful API를 위한 Spring Security 설정해보기 #4</a></li>
</ul>
<hr>
<h1 id="securitycontext-">SecurityContext ?</h1>
<p>에러 핸들링까지 별개로 처리하는 걸 보며 왜지.. 란 궁금증이 등 돌리면 떠올라서 정리 해봤습니다.</p>
<p>먼저 시큐리티 필터 관련한 내용을 간략히만 하고 넘어갈께요.
많은 블로그 예제에서의 WebSecurityConfigureAdpter 는 deprecated 됐고 체인방식으로 변경 됐습니다.</p>
<ul>
<li><a href="https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter">벨덩이 보러가기</a><pre><code class="language-java">@Configuration
@EnableWebSecurity
public class AuthenticationConfiguration {
 @Bean
 public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
    return httpSecurity....
            .exceptionHandling()
          .authenticationEntryPoint(new MyAuthenticationEntryPoint())
          .build();
  }
}</code></pre>
</li>
</ul>
<p>위와 같이 filterChain 통해서 SecurityFilterChain로 반환되게만 해주면 되고, 
체이닝 방식은 거의 유사 합니다.
여기서 위와 같이 제가 JwtTokenFilter 에 대한 에러 핸들링을 추가 했습니다.</p>
<p>security의 filterChain 에서 authenticationEntryPoint() 로 연결 시킵니다.</p>
<ul>
<li>스프링 시큐리티 컨텍스트 인증과정에서  에러 발생시 <strong>AuthenticationEntryPoint</strong> 가 예외 핸들링 하는 인터페이스 라고 하네요.</li>
<li>저는 위의 블로그 내용처럼 AuthenticationEntryPoint 구현하면서 응답형태를 반환하게 해요.</li>
</ul>
<p>제가 좀더 봐보고자 싶어진 부분 !</p>
<pre><code class="language-java">    public abstract class AuthenticationException extends RuntimeException { 
    }</code></pre>
<p>RuntimeException?
제가 <code>@RestControllerAdvice</code> 통해서 RuntimeException을 추가해놨거든요.
필터니까 당연하다 생각되지만, 
시큐리티 컨텍스트가 스프링의 앞단에서 임의 처리 한다면,
그러면 스프링의 필터하고는 어떤 순..서? 어떻게 시큐리티는 라이브러리 추가로 이렇게 작동하게 했지?
(알기 힘들거 같지만 용기를...)</p>
<p>스프링의 순서는 아래와 같습니다. 
(MVC 기준입니다. 김영한님의 MVC2 강의 중 일부입니다. ㅎㅎ)</p>
<blockquote>
<p>HTTP 요청 → WAS → 필터 → 서블릿 → 컨트롤러</p>
</blockquote>
<ul>
<li>필터 제한으로 필터에서 적절하지 않은 요청이라고 판단하면 서블릿 호출 안하고 필터에서 끝낼 수 있습니다.</li>
<li>스프링 부트에서는 ExceptionHandlerExceptionResolver 제공으로 @ExceptionHandler 통한 처리를 합니다.</li>
</ul>
<p>그러면, 스프링 시큐리티와 스프링의 필터 제한 방식과 코드 차이는 ?</p>
<ul>
<li>제 기억으로는…. MVC 모델에서는 <code>filterChain.doFilter(request, response);</code> 를 던지지 않아요.</li>
<li>저의 JwtTokenFilter 코드는 에러든 성공이든 <strong>모든 경우에</strong> <code>filterChain.doFilter(request, response);</code> <strong>계속</strong> 던집니다. (내가 잘못 했나?ㅎㅎ)</li>
</ul>
<p>시큐리티 검색하면 크게 3종류 이미지들이 많이 보이는데 그 중 하나 입니다.
<img src="https://velog.velcdn.com/images/sally_devv/post/e826d51c-2b7c-4e62-949f-049b2bb82e9f/image.png" alt=""></p>
<ul>
<li>스프링 시큐리티가 제공하는 서블릿 필터로 DelegatingFilterProxy 에 의해 위임되어 동작합니다.</li>
<li>DelegatingFilterProxy → FilterChainProxy → SecurityFilterChain순으로 Filter가 진행 되나 봐요.</li>
</ul>
<blockquote>
<p>p.s. 
스프링과 달리 스프링 부트는 내장 웹서버 형식으로 필터 빈 등록과정이 다를 수 있습니다.</p>
</blockquote>
<h2 id="역시-공식-문서">역시, 공식 문서</h2>
<ul>
<li><a href="https://godekdls.github.io/Spring%20Security/servletsecuritythebigpicture/">Servlet Security, The Big Picture</a> - 많이들 아실거 같지만, 이분 Sprint Batch 등 모두 한글 번역 해주셨어요. 👍</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/5e0423a5-0bab-466a-97d0-a2a66cfa3719/image.png" alt=""></p>
<p>ExceptionTranslationFilter는 FilterChainProxy  에 의해 추가된 보안필터로서
AuthenticationException 던져져도 filterChain.doFilter(request, response) 을 계속 호출하며 작업을 이어 처리 합니다. 
 이후, 인증 처리를 시작해 SecurityContextHolder 에서 <strong>AuthenticationEntryPoint</strong>  까지 연결 됩니다.</p>
<ul>
<li>인증 관련 오류까지도 SecurityContextHolder 에서 관리하는 것 같습니다.</li>
</ul>
<br>


<h3 id="이-과정에서-securitycontextholder-를-비웁니다">이 과정에서 SecurityContextHolder 를 비웁니다.</h3>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/22f812c9-dec3-4f8c-a3dd-1d0ec9fc4870/image.png" alt=""></p>
<p><strong>2번</strong>의 SecurityContextHolder.clearContext()가 실행 되더라구요.</p>
<blockquote>
<p>SecurityContextPersistenceFilter → ThreadLocalSecurityContextHolderStrategy
FilterChainProxy →  ThreadLocalSecurityContextHolderStrategy </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/8bce9816-5821-4844-8b4c-74ff937d7621/image.png" alt=""></p>
<p>JwtTokenFilter 에서 빈 토큰으로 인해 if문 블럭으로 넘어가서, 에러 발생으로 <code>filterChain.doFilter(request, response)</code> 넘겨지고, ExceptionTranslationFilter 에서 SecurityContext 를 새로 생성합니다.</p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/670fb44d-5b24-4036-87ae-06e8f12834d4/image.png" alt=""></p>
<p>이후 다시 JwtTokenFilter의 catch 블락에서 <code>filterChain.doFilter(request, response);</code> 호출 합니다.</p>
<p>SecurityContextPersistenceFilter 에서 지웁니다.</p>
<ul>
<li>이는 ThreadLocalSecurityContextHolderStrategy 까지 가서 ThreadLocal 에서 remove() 합니다.
<img src="https://velog.velcdn.com/images/sally_devv/post/680bba35-219d-420f-a4ab-48c402d8a797/image.png" alt=""></li>
</ul>
<p>FilterChainProxy 에서의 clearContext() 로 ThreadLocalSecurityContextHolderStrategy 까지 갑니다.
<img src="https://velog.velcdn.com/images/sally_devv/post/f3844a52-f0df-43f3-807e-e3ef7065d4ae/image.png" alt=""></p>
<h2 id="threadlocalsecuritycontextholderstrategy">ThreadLocalSecurityContextHolderStrategy?</h2>
<p>공식문서에서 스레드 경합을 피하기 위해서는 SecurityContextHolder.createEmptyContext() 형태로 생성하라고 합니다.</p>
<ul>
<li>삭제 과정으로 예상하면, <code>SecurityContextHolderStrategy</code> 인터페이스 통해서 <code>ThreadLocalSecurityContextHolderStrategy</code> 의 <code>ThreadLocal</code> 로 생성하는 거 같아요.<ul>
<li>SecurityContextHolder.getContext().setAuthentication(authentication) 는 안 좋다네요. (제가 이렇게 구현을 해써요..)</li>
</ul>
</li>
</ul>
<ul>
<li><p>에러 처리에서 SecurityContextHolder.createEmptyContext() 를 호출?</p>
<ul>
<li>SecurityContext 에 Authentication 객체를 담기전에 JwtTokenFilter 에서 에러가 발생 한 상황으로 생성하면서 까지 관리하는 가 싶었는데요.</li>
<li>구현 코드로는 Filter 인터페이스는 스프링인데, 시큐리티로 연결 된 점이 신기 했습니다.</li>
</ul>
</li>
<li><p>그런데 2번의 삭제?</p>
<ul>
<li>스프링의 Filter 체인 속에서 DelegatingFilterProxy를 호출 하고, FilterChainProxy 는 빈으로 관리되는 객체로서 SecurityFilterChain 으로 연결됩니다. 스프링 필터체인을 빠져나와 시큐리티 필터체인들 중 SecurityContextPersistenceFilter 는 스프링 세션에 있던 SecurityContext 를 SecurityContextHolder로 넘기면서 영속화 하는 거 같아요.</li>
<li>그리고 시큐리티 필터 체인 중에서 에러 발생하거나 인증성공시 각각 SecurityContextHolder로 넘어갑니다.  </li>
<li>그렇게 2 영역에서 clear 가 일어나는 걸까 .. 생각해 봅니다. (오늘도... 저는 모르겠네요 ㅎㅎㅎㅎ)</li>
<li><a href="https://velog.io/@yaho1024/Spring-Security-SecurityContextPersistenceFilter-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0">SecurityContextPersistenceFilter 알아보기</a></li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/446eb622-248b-45e4-89dc-6c480430a23e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/3fd901e0-6a55-4171-8346-c207bda7b664/image.png" alt=""></p>
<ul>
<li>스레드 경합을 피하기 위해 SecurityContextHolder.getContext().setAuthentication(authentication) 는 안 좋다 ?<ul>
<li>사용자 마다 Authenticaiton 객체를 사용하고자 한다면 SecurityContextHolder.createEmptyContext() 로서 스레드 별 Authentication 을 관리해야 하는지에 대해서는 로그인 후면 시스템에서의 인증 절차에 따라 다를거 같지만, 
보통 사용자 정보 변경 보다는 조회가 많을 거 같고, 그러면 스레드 병합을 고려해야 할까 싶은데,...</li>
<li>ThreadLocal 해제는 중요하고, SecurityContext의 clearContext() 가 통째로 일어나는 거 보면, 로직상  SecurityContextHolder.createEmptyContext() 로 해줘야 하는 건가 싶기도 합니다.<ul>
<li>SecurityContextHolderStrategy 구현체마다 다르게 진행되겠죠? (모르겠어요 😢)</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li>SecurityContextHolder 3가지 전략<ul>
<li>SecurityContextHolder.MODE_GLOBAL<ul>
<li>JVM 내 모든 스레드들이 같은 Security Context를 이용</li>
</ul>
</li>
<li>SecurityContextHolder.MODE_INHERITABLETHREADLOCAL<ul>
<li>스레드 전파 ? OS 에서 권한이 자식스레드 한테도 던달 되는 것과 유사한 거 같아요.</li>
</ul>
</li>
<li>(default) SecurityContextHolder.MODE_THREADLOCAL</li>
</ul>
</li>
</ul>
<br>


<p>_ ..... 긴 글 읽어주신 분들은 감사합니다._</p>
<br>

<hr>
<p><a href="https://docs.spring.io/spring-security/reference/servlet/architecture.html">Spring Security</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Integer.toString(), String.valueOf()]]></title>
            <link>https://velog.io/@sally_devv/Integer.toString-String.valueOf</link>
            <guid>https://velog.io/@sally_devv/Integer.toString-String.valueOf</guid>
            <pubDate>Sun, 18 Dec 2022 11:55:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>부족한 부분은 알려주시면 감사하겠습니다.</p>
</blockquote>
<p>먼저, 형변환에 관한 내용이 아닙니다.</p>
<p>스레드 관련하여 메서드 내에서 객체 타입들이 호출 될 때 동기화 처리 안 된 메서드들의 로직을 확인하다가 적게 됐습니다.</p>
<p>스레드 관련한 내용은 간단히 정리하면,</p>
<p>int는 primitive, Integer 는 Wrapper 클래스로 객체 입니다.
자동 형변환도 되고 크게 신경쓰면서 쓸 일이 없지만, 
스레드 입장에서는 차이가 있을 수 있습니다.
메서드 내에서 생성하여 사용시 primitive type은 스택 한정이지만,
Wrapper 클래스의 객체(참조형으로만 보면)는 외부 반환시 스택 한정이 깨집니다.</p>
<p>멀티스레스 환경에서 공유변수는 여러 이유로 thread-safe 하지 않습니다.</p>
<p>객체 타입을 공유변수일 때 thread-safe 하기 위해서 타입 별 지원하는 방법이나 스레드 별 등 여러 방법이 있을 수 있는데요. 여기서는 다른 내용이 될 거 같고, 어려운 내용 인 것 같습니다. ㅎㅎ</p>
<hr>
<p>자바에서 불변객체로 String, Integer 등 있고, 
저는 불변객체들이 어떻게 서로 상호작용하는지 소스를 보러 갔습니다.</p>
<p>저는 가볍게 접근할 수 있는 String 와 Integer로 생각하다가 기능가진 메서드를 String.valudOf( int 타입 ) 과 Integer.toString( int 타입 ) 을 봤습니다.</p>
<pre><code class="language-java">    public static String valueOf(int i) {
        return Integer.toString(i);
    }</code></pre>
<p>내부적으로 Integer.toString() 호출을 하고 있고, 들어가니 생각보다... 숫자에 쌍따옴표 붙이지 않아요. ㅋㅋㅋ</p>
<ul>
<li>구현한다면 생각한 타입 확인 로직도 없습니다.</li>
</ul>
<p>new String 반환으로 매개변수에 대해 순수해졌어요.</p>
<p>toString() 은 오버라이딩 되는 메서드로 외부 공개되기 때문에 객체 내에서 사용시 thread-safe 하지 않을 수 있는데, static 메서드로 새 객체를 반환하고 있습니다.</p>
<pre><code class="language-java">    public static String toString(int i) {
        int size = stringSize(i);
        if (COMPACT_STRINGS) {
            byte[] buf = new byte[size];
            getChars(i, size, buf);
            return new String(buf, LATIN1);
        } else {
            byte[] buf = new byte[size * 2];
            StringUTF16.getChars(i, size, buf);
            return new String(buf, UTF16);
        }
    }</code></pre>
<p>그냥 바라본 로직은 좀 복잡하네요.
 버퍼 따라가면 속도위해 썼고, COMPACT_STRINGS 은 String 문자열에서 온 상수이고, defualt true 인데 .. 복잡해서 좀더 간편하게 따라가보고자 제 방식대로 테스트 예시로 이럴 수 있겠구나 하고 넘어가 보겠습니다. (답이 아닙니다. )</p>
<p><code>숫자에 쌍따옴표 붙이지 않아요.</code> 
다음의 테스트를 실행해 봤습니다. 로직이 왜 복잡할지, Integer 와 String 이 불변객체로 서로 상호작용하면서 어떻게 구현됐는지가 재미있었습니다.</p>
<ul>
<li>중간에 살짝 char 로 연산자를 넣어줬습니다.</li>
</ul>
<pre><code class="language-java">        int addtion = 1 + &#39;+&#39; + 2;
        System.out.println(&#39;+&#39;);
        System.out.println(addtion);
        System.out.println(1 + &#39;+&#39; + 2 +&quot;=&quot;+3);
        System.out.println(number + &#39;+&#39; + 2 +&quot;=&quot;+3);
        System.out.println(number.toString() + &#39;+&#39; + 2 +&quot;=&quot;+3);
        System.out.println(1 + &quot;+&quot; + 2 +&quot;=&quot;+3);</code></pre>
<ul>
<li>결과</li>
</ul>
<pre><code>+
46
46=3
46=3
1+2=3
1+2=3</code></pre><p>간단하게 개발자가 &quot;+&quot; 과 &#39;+&#39; 을 혼동한다면 그 결과차이는 크게 날 거 같아요.
(답은 아닙니다. 내부적으로 +가 어떻게 되는지(연산자 우선순위, 컴파일), Integer.toString()(자바의 인코딩, java9) 이 변환시키는지 등...몰라요.)</p>
<p>String.valueOf() 는 내부에 불변객체로 반환해주는 Integer.toString() 을 통해 syschronized 없이 동작한다고 생각했습니다.</p>
<p>또 String.valueOf() 와 Integer.toString() 차이는 String.valudOf() 가 오버로딩 되어 있지만, 혹시나 매개변수로 null 이 들어올 경우 Object 타입을 받는 메서드 호출로 NPE 발생하지 않습니다.</p>
<ul>
<li><a href="https://www.baeldung.com/java-tostring-valueof">벨덩 참고</a></li>
</ul>
<p>사용자 입장에서는 문자열로 변환하고자 할 때, String 을 더 생각하기 쉬울 거 같아요.
파라미터로 넘어온 여러 타입별로 오버로딩 통한 메서드들이 String에 마련되어서
Object 를 받으면서 NPE 까지 방어 하고, 각각 타입 별 형변환은 위임하고 있습니다.
그리고 불변객체로 public 한 메서드 임에도 thread-safe 하다고 봤습니다.
GOOD</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MinStack 과 Vector, Generics]]></title>
            <link>https://velog.io/@sally_devv/MinStack</link>
            <guid>https://velog.io/@sally_devv/MinStack</guid>
            <pubDate>Fri, 02 Dec 2022 06:54:38 GMT</pubDate>
            <description><![CDATA[<h2 id="leetcode-155-minstack">LeetCode 155. MinStack</h2>
<p><a href="https://leetcode.com/problems/min-stack/">LeetCode 155.</a></p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/ffa41228-55cf-44f4-a48d-3b9e9c319cdb/image.png" alt=""></p>
<blockquote>
<p>처음에 생각한 구현은 Stack에 최소값만 담는 방식 이었습니다.... 그리하여,...</p>
</blockquote>
<p>input</p>
<pre><code class="language-java">[&quot;MinStack&quot;,&quot;push&quot;,&quot;push&quot;,&quot;push&quot;,&quot;push&quot;,&quot;pop&quot;,&quot;getMin&quot;,&quot;pop&quot;,&quot;getMin&quot;,&quot;pop&quot;,&quot;getMin&quot;]
[[],[512],[-1024],[-1024],[512],[],[],[],[],[],[]]</code></pre>
<p>expected</p>
<pre><code class="language-java">[null,null,null,null,null,null,-1024,null,-1024,null,512]</code></pre>
<ul>
<li>512 도 담길 수 있다.<ul>
<li>이 때는, 문제 해결만 하자 생각으로 논리적으로 다시 확인 않고 일단 풀었습니다.</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>작은 값만 stack에 담으면 왜 안 될까?</strong></p>
<ul>
<li>작은 값만 minStack에 담고, peek() 비교 결과 동일한 값이 stack에서 pop 되면 minStack도 pop 되게 했었다.</li>
<li>결국 작은 값이 제거된 후에는 stack은 값이 있지만, minStack은 먼저 비게 되어 문제에서는 의도하지 않은 예외 케이스가 발생 합니다.</li>
<li>여기서 예외케이스라고 생각하면서 empty 의 경우를 null 로 반환하는 등으로 생각하면 문제를.... 못 풀 수 있습니다. 😂 </li>
<li><table>
<thead>
<tr>
<th></th>
<th>0</th>
<th>1</th>
<th>2</th>
<th>3</th>
</tr>
</thead>
<tbody><tr>
<td>stack</td>
<td>512</td>
<td>-1024</td>
<td>-1024</td>
<td>512</td>
</tr>
<tr>
<td>min</td>
<td></td>
<td>-1024</td>
<td>-1024</td>
<td></td>
</tr>
</tbody></table>
</li>
</ul>
</li>
<li><p>새로 넣을 때, 기존 값과 비교해서 작은 값을 계속 넣습니다.</p>
<ul>
<li><table>
<thead>
<tr>
<th></th>
<th>0</th>
<th>1</th>
<th>2</th>
<th>3</th>
</tr>
</thead>
<tbody><tr>
<td>stack</td>
<td>512</td>
<td>-1024</td>
<td>-1024</td>
<td>512</td>
</tr>
<tr>
<td>min</td>
<td>512</td>
<td>-1024</td>
<td>-1024</td>
<td>-1024</td>
</tr>
</tbody></table>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>시간복잡도 O(1) 이니 stack을 2개 대신 다르게 한다면?</p>
<ul>
<li><p>클래스 만들어서 하면, 실수도 많고, 코테나 인터뷰 때는 배열이 덜 복잡하고 편리하다...</p>
<pre><code class="language-java">class MinStack {
Stack&lt;Element&gt; stack = new Stack&lt;&gt;();

public MinStack() {
    stack = new Stack&lt;&gt;();
}

public void push(int val) {
    if(stack.isEmpty()) {
        stack.push(Element.of(val, val));
        return;
    }
    stack.push(Element.of(val, stack.peek().getMinVal(val)));
}

public void pop() {
    stack.pop();
}

public int top() {
    return stack.peek().getValue();
}

public int getMin() {
    return stack.peek().getMinValue();
}

private Element toArr(int stack, int minStack) {
    return Element.of(stack, minStack);
}

static class Element {
    private int stackVal;
    private int minVal;

    private Element(int stackVal, int minVal){
        this.stackVal = stackVal;
        this.minVal = minVal;
    }

</code></pre>
</li>
</ul>
</li>
</ul>
<pre><code>    public static Element of(int stackVal, int minVal){
        return new Element(stackVal, minVal);
    }

    public int getMinVal(int newVal){
        return Math.min(this.minVal, newVal);
    }

    public int getValue(){
        return this.stackVal;
    }

    public int getMinValue(){
        return this.minVal;
    }
}</code></pre><p>}</p>
<pre><code>

&gt; 처음에 생각한 구현은 Stack에 최소값만 담는 방식 이었습니다.
&gt;  ➜ 단순한 논리적 오류 : minStack 이 비었을 때는 stack 값 동일하게 넣어주고, 최소값을 유지하면 된다.


그래서 
- minStak.isEmpty 이거나 minStack &lt;= val 이면 push(val)

하지만... 오류 😮‍💨
- 초보적인 실수였습니다.
- IDE로 옮겨서 확인하니 비교값이 true 여야 하는데 false 가 나왔습니다.
  - 반환 타입은 정의한 타입 `Stack&lt;Integer&gt; minStack = new Stack&lt;&gt;();` 으로 Integer로 객체간 비교해야 했습니다.


- 사실, Wrapper 클래스 primitive로 자동 형변환 해주니까요. 그냥 썼는데, Stack의 peek() 반환타입으로 `==` 는 안 되네요 ?

``` java
    @Test
    void primitiveAndWrapper() {
        Integer number = 1;
        int toInt = number;
        int intNumber = 1;
        System.out.println(toInt);
        System.out.println(toInt == number);
        System.out.println(intNumber == number);
    }</code></pre><ul>
<li><p>혹시나 싶어 반환 타입 확인하러 가봤다가 <strong><em>제네릭을</em></strong> ... primitive는 안 되겠네요.</p>
<ul>
<li><p>반환 시점에 비교시 자동 형변환은 안 되나 봐요.</p>
<pre><code class="language-java">public class Stack&lt;E&gt; extends Vector&lt;E&gt; {
  public synchronized E peek() {
      int len = size();

      if (len == 0)
          throw new EmptyStackException();
      return elementAt(len - 1);
  }
}</code></pre>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>구현 코드는 <a href="https://github.com/sally-ksh/algorithm/tree/main/study_%EC%8A%A4%ED%83%9D/MinStack">GitHub</a></p>
</blockquote>
<br>


<hr>
<h2 id="stack">Stack</h2>
<p>그렇게 Stack을 보게 되었 습니다. ㅎㅎ</p>
<p>Vector 클래스를 상속 받고 있습니다.</p>
<p>Queue (interface) 와 다르게 Stack은 구현 클래스로,
Vector 에는 indexOf() 위의 elementAt() 과 같이 자료구조 관련 기능들이 있었고, 
Stack은 Stack으로서의 기능들을(LIFO 형태의 자료구조 방식)담당하고 있었습니다.</p>
<pre><code class="language-java">public class Vector&lt;E&gt;
    extends AbstractList&lt;E&gt;
    implements List&lt;E&gt;, RandomAccess, Cloneable, java.io.Serializable
{

    protected Object[] elementData;

    /**
     * The number of valid components in this {@code Vector} object.
     * Components {@code elementData[0]} through
     * {@code elementData[elementCount-1]} are the actual items.
     *
     * @serial
     */
    protected int elementCount;

    /**
     * The amount by which the capacity of the vector is automatically
     * incremented when its size becomes greater than its capacity.  If
     * the capacity increment is less than or equal to zero, the capacity
     * of the vector is doubled each time it needs to grow.
     *
     * @serial
     */
    protected int capacityIncrement;


    public synchronized E elementAt(int index) {
        if (index &gt;= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + &quot; &gt;= &quot; + elementCount);
        }

        return elementData(index);
    }

    private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    } 

    public synchronized E firstElement() {
        if (elementCount == 0) {
            throw new NoSuchElementException();
        }
        return elementData(0);
    }</code></pre>
<p>그러다가 제 의식은 <strong><em>제네릭에</em></strong> ... </p>
<blockquote>
<p><code>Object[] elementData;</code>가 어떻게 반환은 <code>E</code> 로 할 수 있지?</p>
</blockquote>
<hr>
<h2 id="generic-타입-배열은-생성할-수-없다">Generic 타입 배열은 생성할 수 없다.</h2>
<p><a href="https://hwan33.tistory.com/24">Java에서 배열은 공변(covariant)로 만든 이유는 무엇인가?</a> 🙂 감사합니다.</p>
<p>저는 자바의 정석부터 읽어 봤습니다.
제네릭스가 제한되는 상황이 크게 2가지가 있습니다. </p>
<ul>
<li>static 멤버는 인스턴스 변수를 <u>참조</u> 할 수 없다.</li>
<li>제네릭 타입의 배열을 생성하는 것도 허용되지 않는다.</li>
</ul>
<p>사실 저는 외우진 않고, IDE에서 알려주면 &#39;아, 맞다맞다&#39; 하면서 static 등 고쳤습니다. 
(실수도 많이 하면 빨리 고칠 수 있고, 그게 손이 동작하기 전에 고치면 아는 듯이 보이고... 말로 표현하지 못하는 어떤 Dreams Come True )</p>
<p>이 참에 정리라도 해보자 싶었습니다.
먼저 제 코드의 잘못을 IDE를 통해서 알 수 있었습니다. <strong>컴파일 에러!</strong></p>
<blockquote>
<p>제네릭은 컴파일 시 타입 체크와 형변환을 통해 타입 안정성과 편리성을 제공</p>
</blockquote>
<pre><code class="language-java">  class Stack&lt;T&gt; {
      T[] elemendts;

      T[] extendsArray() {
          T[] arr = new T[elements.length * 2];
          return arr;
      }

      // Vector 클래스 예시
      private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
     } 

      public synchronized E firstElement() {
        if (elementCount == 0) {
            throw new NoSuchElementException();
        }
        return elementData(0);
     }
  }</code></pre>
<ul>
<li><p>위에서 <code>T[] elemendts;</code> 는 괜찮지만, <code>new T[elements.length * 2];</code>에서 컴파일 에러 발생합니다.</p>
<ul>
<li><strong>new 연산자는 컴파일 시점에 타입을 알아야 하기 때문입니다.</strong></li>
<li>위에 참고한 링크의 내용에서 <code>Object[] objArr = new Integer[10];</code>는 컴파일 에러가 발생하지 않습니다. 컴파일하면서 Integer로 형변환 됐겠지 싶은데요.<ul>
<li>다만, <code>objArr[1] = &quot;test&quot;;</code> 처럼 컴파일 과정에서는 다른 타입으로 대입도 괜찮지만, <strong><em>실행 하면 에러 발생</em></strong> 합니다. - <strong>타입 안정성 X</strong></li>
</ul>
</li>
</ul>
<pre><code class="language-java">        Object[] objArr = new Integer[10];
      objArr[0] = 1;
      objArr[1] = &quot;test&quot;;  // 실행 후 ArrayStoreException

      System.out.println(objArr[0]);
      System.out.println(objArr[1]);</code></pre>
</li>
<li><p>Vector 클래스의 <code>return elementData(0);</code> 은 Obejct 타입이고 메서드는 E가 반환타입 입니다.</p>
<ul>
<li>컴파일시 E는 Vector<E> 의 타입에 맞춰 변경되고, Object 반환하는 elementData(0)의 해당 타입으로 형변환 해줍니다.</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>정리</strong>
  제네릭은 컴파일을 통해 형변환을 대신 해주고, 그러기 위해서 타입 체크를 합니다.
  다만, 배열은 제네릭 타입의 선언은 되지만 new 연산자로 생성은 불가합니다.</p>
</blockquote>
<h4 id="1-new-로-object배열로-생성해주고-형변환을-일일이-선언해준다면">1. new 로 Object배열로 생성해주고 형변환을 일일이 선언해준다면?</h4>
<ul>
<li>ClassCastException 에러 발생 (그러지 말래요...)</li>
<li>심지어 반환을 arr 하는데 반환 타입이 T[] 가 아니어도 컴파일 에러가 안 떠요.
( 물론 T[] 로 변경해도 동일 에러 발생 합니다.)</li>
</ul>
<pre><code class="language-java">  public class TestGeneric&lt;T&gt; {
    private T[] arr;

    public TestGeneric() {
        this.arr = (T[])new Object[10];
    }

    public T testGeneric() {
        // arr = (T[])new Integer[10];
        // arr[0] = (T)&quot;문자열 넣기&quot;;
        arr = (T[])new Object[10];
        return (T)arr;
    }

      @Test
    void testGenericArr() {
        TestGeneric&lt;String&gt; stringTest = new TestGeneric&lt;&gt;();
        String s = stringTest.testGeneric();
        System.out.println(s);
    }
}</code></pre>
<ul>
<li>ClassDefiner 클래스에서 unsafe로 반환</li>
</ul>
<pre><code class="language-java">    static Class&lt;?&gt; defineClass(String name, byte[] bytes, int off, int len,
                                final ClassLoader parentClassLoader)
    {
        ClassLoader newLoader = AccessController.doPrivileged(
            new PrivilegedAction&lt;ClassLoader&gt;() {
                public ClassLoader run() {
                        return new DelegatingClassLoader(parentClassLoader);
                    }
                });
        return unsafe.defineClass(name, bytes, off, len, newLoader, null);
    }</code></pre>
<ul>
<li>unsafe 여서 끝나는 줄 알았는데 리플렉션(Proxy - newProxyInstance()) 통해서 <code>T inst = (T) ca.newInstance(initargs);</code> 가 진행 됩니다.<ul>
<li>형변환 까지 가는 거 같은 데, 변환하면서 에러가 하는 거 같아요.</li>
<li>이게 컴파일과 실행의 에러 차이? ( 저는 여기까지인 것 같습니다.... 저는 오늘도 결국 몰라요로 마무리를.. 😑 )</li>
</ul>
</li>
</ul>
<pre><code class="language-java"> @CallerSensitive
    @ForceInline // to ensure Reflection.getCallerClass optimization
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            Class&lt;?&gt; caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, clazz, modifiers);
        }
        if ((clazz.getModifiers() &amp; Modifier.ENUM) != 0)
            throw new IllegalArgumentException(&quot;Cannot reflectively create enum objects&quot;);
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings(&quot;unchecked&quot;)
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }</code></pre>
<h4 id="그러면-vector-클래스에서-elementdata-의-초기화는">그러면 Vector 클래스에서 elementData 의 초기화는?</h4>
<ul>
<li>멤버 변수로 배열의 타입을 제네릭이 아닌 Obejct로 해야 초기화도 가능</li>
</ul>
<pre><code class="language-java">      protected Object[] elementData;

    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity &lt; 0)
            throw new IllegalArgumentException(&quot;Illegal Capacity: &quot;+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }</code></pre>
<h4 id="2-코드-예시-하나더">2. 코드 예시 하나더!</h4>
<ul>
<li>컴파일 에러는 없지만, 실행시 ParameterResolutionException 발생</li>
</ul>
<pre><code class="language-java">public class TestGeneric&lt;T&gt; {
    private Object arr;

    public TestGeneric(T element) {
        this.arr = element;
    }

    public T toTestGeneric() {
        return (T)arr;
    }

    @Test
    void testGenericArr() {
        Integer[] expected = {1, 2, 3, 4};
        TestGeneric&lt;Integer[]&gt; arrTest = new TestGeneric(expected);
        Integer[] actual = arrTest.toTestGeneric();
    }
}                                                  
</code></pre>
<p><br><br></p>
<blockquote>
<p>그리고 Vector 가 상속받은 RandomAccess 인터페이스</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[DI와 순환참조]]></title>
            <link>https://velog.io/@sally_devv/DI%EC%99%80-%EC%88%9C%ED%99%98%EC%B0%B8%EC%A1%B0</link>
            <guid>https://velog.io/@sally_devv/DI%EC%99%80-%EC%88%9C%ED%99%98%EC%B0%B8%EC%A1%B0</guid>
            <pubDate>Sun, 13 Nov 2022 14:18:10 GMT</pubDate>
            <description><![CDATA[<p>기존에 개발 했었던 이슈트래커를 설계, 조회방식 등을 바꾸면서 리팩토링도 하면서, 기능도 좀더 추가 해보면서 다른 문제를 발견하게 됐습니다.</p>
<blockquote>
<p>스프링의 빈 객체간 DI로 순환참조 문제</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/576de4c8-54e8-4eec-ba7e-898d0da768a4/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/49fa72e1-c61e-4674-96c7-29e38ebe02f9/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/fd45d819-ce75-4916-ab13-1e124a656c1d/image.png" alt=""></p>
<ul>
<li>IssuerService 와 MilestoneService 각각이 서로간에 의존하며 참조하고 있었습니다. 테스트로만 확인하며 작업하다가 실제 애플리케이션 실행시키면서 발견했는데요. </li>
<li>테스트와 실제 동작은 다른 걸 느끼기도 한 과정이었습니다.
(라벨 서비스 의존관계는 나중에 없애려고 ...) </li>
</ul>
<p>저는 첫 번째로 왜 서로 참조하게 되었는지 로직을 다시 확인 해 봤습니다.
기존에는 이슈트래커 개발 당시 조회 로직 중심으로 개발했었는데요. 라벨과 마일스톤은 등록도 했었지만, 이슈는 통합검색 기능 구현하면서 나중에 하자며 제외 했던 기능이었습니다.</p>
<blockquote>
<p>새 기능 추가로 문제 발생</p>
<ul>
<li>예상 가능 한 기능 이었지만, 좀더 고민하면 좋은 점은 등록된 빈간의 의존관계를 확인 해보기</li>
</ul>
</blockquote>
<p>조회만 해오던 서비스의 비즈니스로직에서 등록이 생기면서 발생한 문제였습니다.
그러면 라벨과 마일스톤에서는 이슈를 등록하지 않았었나? 
(리팩토링 하며 개발한 것도 두 달? 전이라 ㅎㅎ 바로 떠오르진 않고 다시 확인해봤습니다.)</p>
<p>라벨 등록시에는 라벨 정보만 입력하여 저장하고, 마일스톤도 동일 했습니다.
이슈 등록시에는 새 라벨 등록이 아닌 저장되어 있는 라벨과 마일스톤 목록을 통해 저장하고, 이는 관계로 인한 데이터이지 새로 추가 저장되는게 아니었습니다.</p>
<p>저는 이번에 작업하면서 다른 도메인과 관련 없으면 하나의 도메인 패키지 내로 엔티티들을 모아 봤는데요.
 기존에 서비스에서의 의존관계에 대해 동일 패키지 이면 Repository로 생성자 주입 받게 하고, 외부 패키지 이면 서비스를 통해서 주입 받게 했습니다.
(주관적인 생각입니다. 어떤 지식이나 방식이 아니라, 이유는 다르게해서 차이를 알면, 개발경험이 되니까요 ㅎㅎㅎㅎ 이래서 면접 준비가 bad ... )</p>
<blockquote>
<p>동일 패키지 내 의존관계는 Repository로,
다른 패키지 도메인간의 의존관계는 Service를 참조하게 합니다.</p>
</blockquote>
<p>다시 정리하면,
기존에 이슈 서비스에서 없던 저장 로직 추가로 마일스톤 참조관계 발생</p>
<ul>
<li>마일스톤 조회 결과가 필요</li>
</ul>
<p>마일스톤은 조회시 이슈 상태별 개수를 위해 이슈 서비스를 참조</p>
<ul>
<li>이슈 조회 결과가 필요</li>
</ul>
<br>

<h3 id="요구사항-다시-분석-해보기">요구사항 다시 분석 해보기</h3>
<ul>
<li>이슈 등록시 라벨과 마일스톤을 목록 중 선택합니다.<ul>
<li>이슈 등록하면서 라벨 등록X, 마일스톤 등록X</li>
</ul>
</li>
<li>라벨 등록은 라벨 정보만 사용합니다.</li>
<li>마일스톤 등록시 마일스톤 정보만 사용합니다.</li>
</ul>
<p>그 외...</p>
<ul>
<li>라벨 목록은 라벨만 조회합니다.</li>
<li>마일스톤 목록은 이슈 상태별 개수 조회 정보가 필요합니다.</li>
</ul>
<p>제가 한 번 이지만...ㅎㅎㅎ
기획하면서 개발을 해보니 좀 다른 관점으로 보게 된 것 같아요.
검색하니 @Lazy 사용이나 세터 등의 방식들도 스프링스럽게 생각 해도 되는 구나 싶었습니다. (복습이라도 꾸준히 .. 😑)</p>
<p>저는 저장은 도메인 내에서만 발생하고, 조회는 다른 도메인에서 참조가 많다고 보았습니다.
엔티티 자체도 무관한 데이터를 하나의 클래스 안에 갖고 있지 않을 것이고,
관계를 통해 불러올 데이터들을 FK로 가지고 있으면서 조회해오지, 동시에 분리된 도메인 저장해서 분리한 인터페이스 간에 순환참조하게 될 일은 없었습니다.</p>
<ul>
<li>이렇게 설계하는구나.</li>
<li>제가 말한 건 패키지 단위로 관계가 먼 경우 분리된 상태에서, 생성만 보였는데요.</li>
<li>혹시나, 수정은... 더 생각해 봐야 할 것 같아요. 😖</li>
</ul>
<p>그래서 Repository를 store와 reader 인터페이스로 사용하기로 했습니다.
동일 도메인의 Service에서 store와 reader를 참조하지만,
다른 도메인에서는 reader 만을 참조 할 거라는 생각이었습니다.</p>
<p>Repository를 2개의 역할로 나누기 위해 인터페이스를 추가하면서 Repository의 변경에 대한 영향도 줄일 수 있어 좋은 것 같아요.</p>
<blockquote>
<p>이번 경험으로 다른 관점도 얻고, 다양한 방식들도 보면서 재미있어서 정리해 봤습니다.
좋은 의견이나 지식 첨언은 감사히 받겠습니다. 🙂</p>
</blockquote>
<p>재미는 있었는데.. 패키지 바꾸고, 클래스 바꾸고, 메서드명 새로 고민하고, 테스트 바꾸고.. ㅎㅎ</p>
<ul>
<li>간단한 경우라면, 추천하지 않는다지만 @Lazy나 세터를..</li>
</ul>
<p><a href="https://github.com/stan-lily/BE">GitHub</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[RDB vs NoSQL]]></title>
            <link>https://velog.io/@sally_devv/RDB-vs-NoSQL</link>
            <guid>https://velog.io/@sally_devv/RDB-vs-NoSQL</guid>
            <pubDate>Tue, 20 Sep 2022 03:39:32 GMT</pubDate>
            <description><![CDATA[<p>데이터를 저장하는 용도로 쓰이는 RDB, NoSQL의 차이점을 <span style="background-color: gold">데이터</span> 관점에서 정리해보고자 합니다.</p>
<p><span style="background-color: gold">데이터를 저장 합니다.</span></p>
<p>파일시스템부터 RDB, NoSQL 까지의 스토리를 볼 수 있다고 생각하는데요.
그런 점에서 RDB의 필요이유, 사용이유들도 있는 만큼 NoSQL도 등장 배경이 있습니다.</p>
<p>각자 여러 장점이 있고, NoSQL과 데이터 관점에서 비교하면서 정리해 보고자 합니다.
(학습자로서 작성한 글입니다. 첨언도 감사히 받겠습니다.)</p>
<blockquote>
<p><strong>_NoSQL과 차이 관점에서 RDB는 _</strong></p>
</blockquote>
<ul>
<li>트랜잭션을 통해 데이터의 무결성을 보장합니다.<blockquote>
<ul>
<li>테이블 간 관계를 통한 조인 등의 복잡한 조건이 포함된 데이터 검색이 가능합니다.</li>
</ul>
</blockquote>
</li>
</ul>
<br>
<br>


<p>데이터에도 종류가 있습니다.</p>
<blockquote>
<p>RDB는 정형 데이터, NoSQL은 비정형 데이터를 저장 합니다.</p>
</blockquote>
<p>정형 데이터는 구조화된 데이터로 정해진 구조에 맞춰 저장되는 데이터입니다.
여기서 정해진 구조는 스키마로, 스카마는 쉽게 보면, 테이블의 컬럼에 데이터 타입, NN 허용 여부 등이 정해진 걸 볼 수 있습니다.</p>
<p>비정형 데이터는 이미지, 동영상 등이 있을 수 있습니다.
영상의 길이가 다를 수 있겠지요. </p>
<p>RDB에서 스키마에 따른 정해진 데이터만 저장하기에 비정형 데이터는 비효율적일 수 있습니다.
*<em>SNS 등의 활발한 사용 증가 하는 요즘, 비정형 데이터가 증가한 상황에서 NoSQL이 RDB 보다 효율적으로 사용할 수 있습니다.
*</em>
스키마와 트랜잭션 등을 포기했기 때문에 RDB의 강점인 안정성, 일관성 등을 유지하기 위한 기능들이 없이 비정형의 데이터를 저장하고 처리합니다.
RDB와 다른 기능으로 인한 NoSQL의 장점은 트랜잭션 등의 기능 없다보니 빠른 검색속도를 가지고 분산 저장 처리 등의 scale out이 장점입니다.
(NoSQL에서도 네오포제이 등은 RDB와 유사한점들이 있어서 제외하고 정리 하겠습니다.)</p>
<br>
<br>

<h3 id="rdb는-nosql과-다르게-안정성과-일관성을-어떻게-유지-할까요">RDB는 NoSQL과 다르게 안정성과 일관성을 어떻게 유지 할까요?</h3>
<blockquote>
<p>스키마 : 데이터 베이스 구조와 제약조건에 관한 전반적인 명세를 기술한 정보입니다.</p>
</blockquote>
<p>정형데이터가 저장된다는 의미는, 저장 될 데이터에 대해 유효한 값만을 허용한다고 봅니다.
(예시는 이해 용도로만 생각해 주세요. 기술적으로 보완하는 등의 방법들이 가능하거나, RDB를 써도 추가 보완점들이 있을 수 있습니다.)</p>
<ul>
<li><p>varchar(50) : 1000개의 영어알파벳이 저장 될 수 없습니다.</p>
<ul>
<li>게시글 등에서 다양한 글과 정보들이 입력 될 수 있는데요.</li>
<li>이 때 악의적인 프로그래밍 연산관련된 스크립트가 입력되어 DB나 시스템에서 글작성 외의 의미 갖거나 시스템 연산에 적용되어 시스템 장애나 보호되야 할 사용자의 정보 노출등의 위험도가 높아집니다.</li>
<li>간단하게 짧은 단위의 데이터 입력값만을 허용하면, 그런 위험도는 낮아지겠죠.</li>
</ul>
</li>
<li><p>유효한 값이 들어옴으로써, 정해진 데이터들이 유지될 때, 일관성을 가질 수 있습니다.</p>
<ul>
<li>만일, 제가 계좌 잔액 10000원이 있고, 로또 당첨되어 10억 입금 요청을 했을 때, 계좌 잔액은 10억 10000원이 됩니다.</li>
<li>그런데 시스템 장애로 1000원만 남아있다면 어떨까요?</li>
<li>트랜잭션은 중간 연산결과 과정에서의 불확실성을 없애거나, 장애 발생시 원복 상태로 우선 돌리거나, 입력된 데이터의 지속성을 위해 장애 발생시 후에도 저장된 데이터를 유지하게 해줍니다.</li>
</ul>
</li>
</ul>
<p>RDB에서의 의미가 있어서 사용되는 기능들이 비정형 데이터를 빠르게 처리하기 위해 감안되어 NoSQL을 사용합니다.</p>
<br>


<p>NoSQL은 데이터의 무결성의 보장이 안 되어 중복이 많다 보니, 조회 외의 update 등의 처리작업에는 비효율적입니다.</p>
<blockquote>
<p>중복된 데이터로 데이터 자체도 비효율적으로 담고 있을 수 있지만, 정규화를 통해 관계를 갖고 분리된 테이블의 JOIN을 사용하고, 이상현상을 방지합니다.</p>
<ul>
<li>이상현상은 삽입이상, 갱신이상, 삭제이상이 있습니다.</li>
</ul>
</blockquote>
<p>위 현상이 발생하면, 불필요하게 null이 추가 입력되야 한다거나, 중복이 발생한다거나, 다른 데이터 삭제로 인한 필요 데이터 까지 삭제되는 일이 발생합니다.</p>
<p>정규화를 통해 데이터들을 효율적으로 관리하고, 중요 데이터를 보관할 수 있습니다.</p>
<br>
<br>

<p>이런 기능들이 의미가 있어 정형데이터를 저장하는 RDB를 사용하지만, 
비정형 데이터 처리면에서는 NoSQL의 장점이 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[String compareTo( String )]]></title>
            <link>https://velog.io/@sally_devv/String-compareTo-String</link>
            <guid>https://velog.io/@sally_devv/String-compareTo-String</guid>
            <pubDate>Tue, 06 Sep 2022 08:16:54 GMT</pubDate>
            <description><![CDATA[<p>java 의 String 클래스</p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/9e886230-bab2-4771-a0ba-2776b9cf0066/image.png" alt=""></p>
<display>
    <img src="https://velog.velcdn.com/images/sally_devv/post/b6d5c7d8-4b39-4efb-93b7-6e67230b734f/image.png">
      <img src="https://velog.velcdn.com/images/sally_devv/post/36f577d7-1cd0-4f1e-b5f0-ee178c3e8aa6/image.png">
</display>


<p>알아보고자 들어갔다가, 잘 모르겠지만.. </p>
<p>COMPACT_STRINGS true 로 해놓고서, String 생성 과정에서 Latin인지 UTF16인지 구분 처리에 계속 이용되어지는 상수 인 것 같다.</p>
<pre><code class="language-java">static final boolean COMPACT_STRINGS;

static {
    COMPACT_STRINGS = true;
}</code></pre>
<p>StringLatin1.compareTo( byte[] , byte[]) 를 가보면</p>
<p>하나씩 순회하며 다를 경우, char 간의 차이만큼의 수를 반환한다.</p>
<p>동일 할 경우 마지막으로 길이의 차이값을 반환한다. </p>
<ul>
<li>작은 길이를 우선순위로 순회하여 비교먼저 했기 때문에</li>
<li>abcd &lt; abcde</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/16bba5e9-273f-4614-9428-1edccb869d4d/image.png" alt=""></p>
<p> <img src="https://velog.velcdn.com/images/sally_devv/post/dc19924a-ed9b-4f04-9321-a00b090d5adb/image.png" alt=""></p>
<p>하지만 실제 디버깅해서 돌리다보면</p>
<p>내부적으로는 이진탐색 과정이 나온다.</p>
<ul>
<li>영문자 비교인데 ko-KR 이.. 모르겠다 ㅎㅎ
<img src="https://velog.velcdn.com/images/sally_devv/post/6fd1d8db-4719-48e0-b041-42ae23413087/image.png" alt=""></li>
</ul>
<hr>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/ed89f7ce-36b4-4f46-8110-dd9f2502a38b/image.png" alt=""></p>
<p>a를 기준으로 비교한 결과값들이다.</p>
<p>a 와 c 의 결과와 a와 g의 비교 결과값이 같다.</p>
<ul>
<li>소문자와 대문자 차이</li>
<li>ASCII 기준 :  A = 65, a = 95 →  97-65 = 32</li>
</ul>
<p>a의 abcd에서 b의 위치에 대한 비교결과를 보면</p>
<ul>
<li>b &lt; z , c : 음수  (ex. 1-2 = -1)</li>
<li>b &gt; a : 양수</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[이슈트래커 - JPA, 테이블]]></title>
            <link>https://velog.io/@sally_devv/%EC%9D%B4%EC%8A%88%ED%8A%B8%EB%9E%98%EC%BB%A4-JPA-%ED%85%8C%EC%9D%B4%EB%B8%94</link>
            <guid>https://velog.io/@sally_devv/%EC%9D%B4%EC%8A%88%ED%8A%B8%EB%9E%98%EC%BB%A4-JPA-%ED%85%8C%EC%9D%B4%EB%B8%94</guid>
            <pubDate>Sun, 03 Jul 2022 06:39:46 GMT</pubDate>
            <description><![CDATA[<p>코드스쿼드 마스터프로젝트 중 이슈트래커 작업 중 테이블 설계와 JPA로 구현하면서 관계맵핑 관련한 부분들을 정리 해봤습니다.</p>
<h2 id="설계와-테이블-관계-맵핑">설계와 테이블 관계 맵핑</h2>
<p>이번에는 설계가 한번에 바로 나오지 않았습니다.
JDBC 라면 크케 고민 하지 않았을 부분이었지만, JPA 로 관계맵핑 하기 좋은 테이블 구조는 무엇일지 고민하는 시간을 밤새 가져 보았습니다.</p>
<p> 이전 작업에서 팀원이 단순하게 설계해놓은 ERD를 그냥 작업했지만, 이번에 혼자 진행하는 만큼 쓰여야 할 부분들을 제외시키고 싶진 않았고, 3주 기간 동안 할 수 있는 정도의 복잡성에 대해 고민이 있었습니다.</p>
<h3 id="이슈트래커는-뭐지">이슈트래커는 뭐지?</h3>
<ul>
<li><p>처음에는 단순하게 이슈 목록들을 나열하는 걸로 생각하고 시작하였는데요, 사용 용도를 생각하면서 이슈목록에서 보여져야 하는 관계되는 테이블들을 생각안 할 수 없었습니다.</p>
</li>
<li><p>여러개가 있는 라벨, 하나만 있는 마일스톤</p>
<ul>
<li>라벨은 예시로 팀별 작업단위에 쓰입니다.</li>
<li>마일스톤은 해당 이슈의 작업기간으로 다른 작업기간은 다른 이슈로 생성하면서 구분지어 작업할 수 있거나, 하나의 마일스톤으로 정해진 작업 기간내에 여러 이슈들이 생성되어 목표단위로 작업될 수 있습니다.</li>
</ul>
</li>
<li><p>초반에 살짝 캘린더 구현을 시도해 봤습니다. 일정관리와 차이가 무엇일지 좀더 구체적으로 보고서 이슈트래커의 비즈니스 로직을 들여다 봐야겠다 싶었습니다.</p>
<ul>
<li>구글 캘린더를 보면, 날짜를 작업기간으로 공유 할 수 있습니다.<ul>
<li>23일,24일 내가 배포한다면, 23일날 FE에서 UI header 작업을 진행하는 등, 날짜 중심으로 보여집니다.</li>
</ul>
</li>
</ul>
</li>
<li><p>이슈 트래커는 작업단위 중심으로 팀별, 기간별 구분 하여 이용하고, 처리되는 이슈는 open 상태에서 close 하여 처리할 작업에 집중도를 높일 수 있다고 생각했습니다.  (🤡..이런게 새로운 서비스 구현 재미)</p>
</li>
<li><p>그래서, 라벨, 마일스톤이 쓰이는 상황이라면...</p>
<ul>
<li>이슈는 팀별로 별도로 각자가 쓰일 수 있다.</li>
<li>라벨과 마일스톤은 공유 될 수 있다.</li>
<li>팀이라는 개념이 각각의 사람들이 접근하고 작업하는데 있어야 한다.</li>
</ul>
</li>
<li><p>어려웠던게 팀이란게 보통 생각하는 개념과 달랐던 점이예요. 라벨 하나로 팀을 구별할 수 있는 점이죠.</p>
<ul>
<li>프로젝트 단위로 멤버 구성되고, 전체 이슈 관리, 라벨, 마일스톤이 나뉘어 진다고 정리했습니다.</li>
</ul>
</li>
</ul>
<p>  <img src="https://velog.velcdn.com/images/sally_devv/post/d27d28c6-7bf7-4472-b316-c53b6625467e/image.png" alt=""></p>
<ul>
<li>이때는 로그인을 나중으로 미루면서 세션로그인을 생각한 user 테이블만있다.<ul>
<li>사용자 관련 부분이 애매하다보니 AuthUser 클래스를 생성해서 userId, projectId를 담아 모든 요청시 이용하도록 작업했다.</li>
</ul>
</li>
</ul>
<h2 id="마일스톤과-라벨의-차이">마일스톤과 라벨의 차이</h2>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/ba819dbc-8792-45b3-965b-b417092ef723/image.png" alt=""></p>
<blockquote>
<p>테이블에서 n:m의 관계를 풀기 위해 <code>label_has_issue</code>라는 테이블을 추가했다. 여기서는 부모키를 참조한 외래키를 기본키로 하지 않고, 기본키를 추가하면서 비식별자 관계로 맵핑 했다.</p>
</blockquote>
<ul>
<li>JPA에서 이슈 목록 조회시 라벨을 어떻게 가져올까?<ul>
<li><code>issue_tracker_label</code> 테이블에는 필수관계인 project만을 FK로 참조하고 있따.</li>
<li><code>label_has_issue</code>를 통해 라벨을 가져온다.</li>
<li>엔티티 <code>Label</code>은 <code>Issue</code>와 연관관계 갖지 않는다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">    @OneToMany(mappedBy = &quot;issue&quot;)
    private List&lt;IssueLabel&gt; issueLabels = new ArrayList&lt;&gt;();</code></pre>
<blockquote>
<p>마일스톤은 이슈와는 1:n의 관계로 <code>issue_tracker_issue</code> 테이블이 mileston를 FK로 참조하고 있다.</p>
</blockquote>
<ul>
<li>JPA에서는 <code>Issue</code>에서 다대일 단방향 연관관계 매핑 했다.</li>
<li>나중에 마일스톤 조회 목록에서 이슈의 상태별 진행과정 처리히 <code>Issue</code>를 갖게 오고자 한다면 다대일 양방향도 가능하다.</li>
</ul>
<pre><code class="language-java">    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;milestone_id&quot;)
    private Milestone milestone;</code></pre>
<blockquote>
<p>이슈 중심으로 관계맵핑들을 정리하고 그외는 분리되도록 했습니다. 마일스톤 조회시 이슈 상태별 마일스톤 progress bar 위한 이슈 상태 개수 조회는 어떻게 할지 고민이었습니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/91205890-c394-4a38-b829-c4df3cbfa2a9/image.png" alt=""></p>
<h4 id="1-일대다-양방향-연관관계-맵핑">1. 일대다 양방향 연관관계 맵핑</h4>
<ul>
<li>right outer join 으로 가져오면 중복 많고, 개수외에는 Issue의 정보가 필요하지도 않고 다른 방법을 찾아 봤습니다.</li>
</ul>
<h4 id="2-쿼리-작업해보니-jpql-">2. 쿼리 작업해보니, JPQL ?</h4>
<pre><code class="language-sql">select 
    issue_status, count(issue_status) as status_count
from
    (
        select issue_status, milestone_id from issue_tracker_issue a where milestone_id in (3,4)
    ) b
group by b.issue_status, milestone_id;
</code></pre>
<p><a href="https://www.inflearn.com/questions/29673">JPA의 최대 걸림돌이 from절의 서브쿼리인 인라인뷰(inlineview)</a></p>
<ul>
<li>그래도 답이 안나오면 네이티브 쿼리를 사용한다. 😮 (잘못 온걸까?)</li>
</ul>
<h4 id="3-그럼-서브쿼리-없애보자-jpa-group-by는-어떻게-">3. 그럼 서브쿼리 없애보자, JPA Group by는 어떻게 ?</h4>
<pre><code class="language-sql">select 
    issue_status, count(issue_status) as status_count, milestone_id
from
    issue_tracker_issue
where milestone_id in (3,4)
group by issue_status, milestone_id;</code></pre>
<ul>
<li><p><a href="https://medium.com/@odysseymoon/spring-data-jpa%EC%97%90%EC%84%9C-groupby-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0-82cddc6e5d4a">Spring Data JPA에서 Group by 처리하기</a></p>
<ul>
<li>결국, 네이티브 쿼리 🙄</li>
</ul>
</li>
<li><p>잘쓴 건지 모르겠지만 생각보다 간단했습니다.</p>
<ul>
<li>내가 조회해올 컬럼에 대한 메서드를 interface로 정의해서 dto로 가져와 사용하면 돼요.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">  public interface NumberOfIssueStatus {
      String getIssueStatus();

      Long getStatusCount();

      Long getMilestoneId();
  }</code></pre>
<pre><code class="language-java">@Query(value =
        &quot;select issue_status as issueStatus, count(issue_status) as statusCount, milestone_id as milestoneId&quot;
            + &quot; from issue_tracker_issue&quot;
            + &quot; where milestone_id in :milestoneIds&quot;
            + &quot; group by issue_status, milestone_id&quot;, nativeQuery = true)
    List&lt;NumberOfIssueStatus&gt; findGroupBy(@Param(&quot;milestoneIds&quot;) List&lt;Long&gt; milestoneId);</code></pre>
<h3 id="조회-→-끝-😭">조회 → 끝? 😭</h3>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/bea58bab-11e6-4bb4-b198-8e96669a7445/image.png" alt=""></p>
<blockquote>
<p>마일스톤 목록에 필요한 IssueStatus 정보는 milestonId별 OPEN 상태의 IssusStatus 개수와 CLOSE 상태의 IssueStatus 개수</p>
</blockquote>
<ul>
<li>IssueService<ul>
<li>milestone_id 별로 OPEN과 CLOSE의 개수를 묶어 전달할 필요가 생겼습니다. (설계는 멀어졌고, 앞만본다...)<ul>
<li>상태별 개수를 담을 클래스를 하나 생성 : <code>NumberOfIssueStatusDto</code></li>
<li>milestoneId를 키로 각각의 값들을 Map에 담았습니다. : <code>NumberOfIssueStatusAndMilestoneDto</code><pre><code class="language-java">public NumberOfIssueStatusAndMilestoneDto readByMilestones(List&lt;Long&gt; milestoneIds) {
  List&lt;NumberOfIssueStatus&gt; numberOfIssueStatuses = issueRepository.findGroupBy(milestoneIds);
  return NumberOfIssueStatusAndMilestoneDto.from(numberOfIssueStatuses);
}</code></pre>
</li>
</ul>
</li>
</ul>
</li>
<li>MilestoneService<ul>
<li>조회해온 Milestone들의 id별 IssueStatus 개수를 가져와서 application 단에서 join<pre><code class="language-java">@Transactional(readOnly = true)
public List&lt;MilestoneResponse&gt; readAll(AuthUser authUser) {
    List&lt;Milestone&gt; milestoneInfo = milestoneRepository.findAllByProjectId(authUser.getProjectId());
    Milestones milestones = Milestones.from(milestoneInfo);
    NumberOfIssueStatusAndMilestoneDto numberOfIssueStatusMap = issueService.readByMilestones(
        milestones.getMilestoneIds());
    return milestones.toResponses(numberOfIssueStatusMap);
}</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="검색시에는-어떻게-할까">검색시에는 어떻게 할까?</h3>
<p>사실 검색부터 구현 했었는데, 이때부터 제가 outer join을 하지 않는 방향에서의 구현을 시도해보게 됐습니다. 🙃 </p>
<blockquote>
<p>검색기능은 이슈목록에서 각각 필터 버튼을 누르면 검색어 입력란에 추가되어지면서 검색 조회 요청합니다. <a href="https://github.com/sally-ksh/issue-tracker/wiki/%EA%B8%B0%EB%8A%A5-%EB%B0%8F-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%A0%95%EB%A6%AC">ref</a>
ex. <code>is:open+author:sally+label:&quot;feature&quot;</code> </p>
</blockquote>
<ul>
<li>검색키들은 생략 가능하다.</li>
<li><code>Issue</code> : <code>label</code> = 1:n 으로 <u>null 이거나 여러개</u> 가져올 수 있다. + <code>none</code> 이라는 검색이 가능하다.<ul>
<li><code>none</code>? <ul>
<li>라벨이 없는 이슈조회 검색에 해당</li>
</ul>
</li>
<li>여러개의 이슈목록 조회결과를 가져오면서 <code>label</code>의 개수가 각각 다를 때, 이슈의 중복 개수가 늘어날 수 있다. (거기에 마일스톤 등)</li>
<li>페이징 처리하면서 일정개수만 조회해온다면 성능 이점을 생각해 중복도 괜찮지 않을까 싶기도 합니다. </li>
<li>결론은 ❕❔</li>
</ul>
</li>
</ul>
<ul>
<li>outer join을 안써본다면 구현은 먼저 해당 검색어에대한 <code>Issue</code>의 id 목록들을 통해 관련된 <code>label</code>을 조회 해오게 하는데,<ul>
<li>앞서 말한 m:n 관계 풀기위해 비식별자로 기본키를 가지는 <code>label_has_issue</code> 를 이용해 조회해오게 했습니다.<ul>
<li>라벨이 검색어로 <code>none</code>이거나 없다면 null, label 검색어가 있다면 검색어를 넣어 조회</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="language-java">    public List&lt;IssueResponse.Row&gt; search(AuthUser authUser, IssueSearchRequest request) {
        IssueSearchParam searchParam = IssueSearchParam.from(request);
        List&lt;IssueLabelDto&gt; issueLabels = issueSearchRepository.findIssueLabels(searchParam.labelName());

        List&lt;IssueSearchDto&gt; resultOfSearch = issueSearchRepository.search(
            authUser,
            searchParam,
            toIssueIds(issueLabels));

        IssueLabelMapper issueLabelMapper = IssueLabelMapper.from(issueLabels);
        return resultOfSearch.stream()
            .parallel()
            .map(issue -&gt; IssueResponse.Row.from(issue, issueLabelMapper.getValue(issue.getIssueId())))
            .collect(Collectors.toList());
    }</code></pre>
<ul>
<li>그 후에는 <code>none</code> 이면 차집합으로, 검색어가 없다면 null, 있다면 해당 검색어가 포함된 label과 관련된 issueId만 조회하게 한다.</li>
</ul>
<pre><code class="language-java">    private BooleanExpression labelsFrom(String labelName, List&lt;Long&gt; issueIds) {
        if (SearchKeyType.isNone(labelName)) {
            return issue.id.notIn(issueIds);
        }
        return Strings.isBlank(labelName) ? null : issue.id.in(issueIds);
    }</code></pre>
<blockquote>
<p>결론은, 구현과정은 재미있었지만,  검색어에 따른 조회로직을 간단히 하고자 querydsl을 썼는데, 복잡해져서 그 장점이 사라진 것도 같다. </p>
</blockquote>
<ol>
<li>코드를 개선 😐</li>
<li>outer join도... 😑</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[@Builder 패턴, Dto]]></title>
            <link>https://velog.io/@sally_devv/Builder-%ED%8C%A8%ED%84%B4-Dto</link>
            <guid>https://velog.io/@sally_devv/Builder-%ED%8C%A8%ED%84%B4-Dto</guid>
            <pubDate>Sat, 25 Jun 2022 20:01:36 GMT</pubDate>
            <description><![CDATA[<h2 id="builder-패턴과-dto">@Builder 패턴과 Dto</h2>
<ul>
<li>IssueTracker 클론 프로젝트 진행 중 PR 보낸 코드 일부에 리뷰가 달렸다. <a href="https://github.com/sally-ksh/issue-tracker/tree/dev-be">GitHub</a></li>
</ul>
<pre><code class="language-java">    @Builder
    @Data
    public class MilestoneResponse {
        private final Long id;
        private final String title;
        private final String description;
        private final String completionDate;
    }</code></pre>
<blockquote>
<p>from. 리뷰어 왕민
  dto 클래스에 builder 패턴은 오버스펙으로 보입니다.
빌더패턴 사용 이유에 대해서 알아봅시다 :)</p>
</blockquote>
<p><a href="https://velog.io/@bey1548/JAVA-%EB%B9%8C%EB%8D%94-%ED%8C%A8%ED%84%B4Builder-Pattern%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0">빌더 패턴 사용 이유</a></p>
<ol>
<li>필요한 데이터만 설정할 수 있음</li>
<li>유연성을 확보할 수 있음</li>
<li>가독성을 높일 수 있음</li>
<li>불변성을 확보할 수 있음</li>
</ol>
<ul>
<li>@Data 안에 Setter, RequiredArgsConstructor가 있는데, @Builder 까지 선언하니 오버스팩이라 생각 됐다.<ul>
<li>오버스팩을 생각하니 @Data도 <u>안쓸 것들을</u> 많이 들고 있는 것 같았다.</li>
<li>그러면 쓸 것만 남겨야지.</li>
</ul>
</li>
</ul>
<blockquote>
<p>@Data
Getter, Setter, RequiredArgsConstructor, ToString, EqualsAndHashCode, Value</p>
</blockquote>
<p>😶 @Data ➜ @Getter, @RequiredArgsConstructor</p>
<pre><code class="language-java">    @RequiredArgsConstructor
    @Getter
    public class MilestoneResponse {
        private final Long id;
        private final String title;
        private final String description;
        private final String completionDate;
    }</code></pre>
<ul>
<li>이번에는 예전과 다르게 <code>dto</code> 패키지를 별도로 둬서 작업하고 있었다.<ul>
<li>로직관련 클래스들만 도메인 패키지 내에서 볼 수 있어서 좋을 것 같았다.</li>
<li>패키지 하나 옮겨두면, 접근제어자 고민이 새로워 진다.</li>
</ul>
</li>
<li>그래서, 새롭게 하여 새 문제를 만들었다.🙃<ul>
<li>패키지가 다르니 접근제어나 default로 안되고 getter를 쓰지 않고자 함이었는데....</li>
</ul>
</li>
</ul>
<blockquote>
<p>from. 리뷰어 왕민
toDto 메소드에서는 엔티티가 dto클래스를 알고있어야 하네요.
dto는 도메인 객체에서 알고 있을 필요는 없다고 생각합니다.
현재 구조는 dto 클래스 변경이 있을 경우 entity객체도 변경이 필요하죠.
domain.toDto가 아닌 dto.from(entity)같은 형식으로 도메인 객체에서 dto 의존성을 제거하는건 어떨까요?</p>
</blockquote>
<ul>
<li>맞아요.😳</li>
</ul>
<pre><code class="language-java">    @Entity
    public class Milestone {
        MilestoneResponse toDto() {
            return MilestoneResponse.builder()
                .id(this.milestoneId)
                .title(this.milestoneTitle)
                .description(this.description)
                .completionDate(this.completionDate.toString())
                .build();
        }
    }</code></pre>
<ul>
<li>...접근 제어자 고민이 새로워 졌다.<ul>
<li>public과 getter<ul>
<li>최대한 엔티티 노출을 적게 해보려면, 생성을 제한하고, 필수 값들만 전달하게 해보자.</li>
<li>변수가 3개 이상이니, Builder 패턴이 가독성도 좋아보인다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="language-java">    @Getter
    @Builder(access = AccessLevel.PRIVATE)
    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
    public class MilestoneResponse {
        private final Long id;
        private final String title;
        private final String description;
        private final String completionDate;
    }

    public static MilestoneResponse from(Milestone milestone) {
        return MilestoneResponse.builder()
            .id(milestone.getId())
            .title(milestone.getTitle())
            .description(milestone.getDescription())
            .completionDate(milestone.getCompletionDate())
            .build();
    }</code></pre>
<ul>
<li>근데, @Builder 를 알고 있을까? 😃</li>
</ul>
<hr>
<h2 id="builder">@Builder</h2>
<ul>
<li>아무값도 들어가지 않는다면? <a href="https://projectlombok.org/features/Builder">projectlombok.org</a></li>
</ul>
<blockquote>
<p>If a certain field/parameter is never set during a build session, then it always gets <strong>0 / null / false</strong>. </p>
</blockquote>
<ul>
<li><p>@Builder.Default - @Builder 사용하면서 필드별로 기본값으로 초기화 시킬 수 있다.</p>
<ul>
<li><p>재밌는 예시를 발견했다. <a href="https://velog.io/@hsbang_thom/Lombok-Builder.Default">https://velog.io/@hsbang_thom</a></p>
<pre><code class="language-java">@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Pojo {

    @Builder.Default
    private String name = &quot;짱구엄마&quot;;
    private String nickname;
    private List&lt;PojoTwo&gt; pojoTwos = new ArrayList&lt;PojoTwo&gt;();
}

  public static void main(String[] args) {
    Pojo pojo = Pojo.builder().nickname(&quot;짱구친구&quot;).build();
    System.out.println(pojo.toString());

    Pojo pojo1 = new Pojo();
    System.out.println(pojo1.toString());

}</code></pre>
</li>
</ul>
</li>
</ul>
<pre><code>    Pojo(name=짱구엄마, nickname=짱구친구, pojoTwos=null)
    Pojo(name=짱구엄마, nickname=null, pojoTwos=[])</code></pre><ul>
<li>리스트로 초기화 한 pojoTwos는 대해, 빌더를 이용해 생성한 pojo의 pojoTwos는 null 의 결과가 나왔다.<ul>
<li>@Builder 클래스를 들어가면, 어떻게 패턴이 적용되는지 예시가 나오는데, 초기화를 하지 않다보니, 객체의 기본값 null 로 결과가 나왔다.</li>
</ul>
</li>
</ul>
<ul>
<li>@Builder(toBuilder = true) <a href="https://www.baeldung.com/lombok-builder-default-value">ref. baeldung</a><ul>
<li>만일 모든 필드가 초기화 되어 있다면, 간단하게 생성할 수 있다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">    @Builder(toBuilder = true)
    public class Pojo {
        private String name = &quot;foo&quot;;
        private boolean original = true;
    }</code></pre>
<ul>
<li><p>위에서 언급했던 @Builder 패턴은 왜 사용할까 중 아래 2가지에 대해 다시 생각해보면...</p>
<pre><code>  1. 필요한 데이터만 설정할 수 있음
  4. 불변성을 확보할 수 있음</code></pre></li>
<li><p><code>0 / null / false</code></p>
<ul>
<li>Dto를 통해 임의로 값이 들어가고, DB 저장되게 한다면</li>
<li>중간에서 빠져버린 필드에 의해 하나의 값이 들어가지 않아 null로 변환 된다면</li>
<li>이런 경우들을 조심해야 될 것 같다. (더 많은 정보들을 알려주세요...)</li>
</ul>
</li>
</ul>
<hr>
<p>👍 refernce</p>
<ul>
<li><a href="https://github.com/sally-ksh/issue-tracker/tree/dev-be">GitHub - 리뷰 코드</a></li>
<li><a href="https://velog.io/@bey1548/JAVA-%EB%B9%8C%EB%8D%94-%ED%8C%A8%ED%84%B4Builder-Pattern%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0">빌더 패턴 사용 이유</a></li>
<li><a href="https://projectlombok.org/features/Builder">projectlombok.org</a></li>
<li><a href="https://velog.io/@hsbang_thom/Lombok-Builder.Default">velog : @hsbang_thom - Lombok-Builder.Default</a></li>
<li><a href="https://www.baeldung.com/lombok-builder-default-value">baeldung - lombok-builder-default-value</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS ec2] 스프링 부트 배포]]></title>
            <link>https://velog.io/@sally_devv/AWS-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EC%9D%B4%EC%8A%88-%ED%8A%B8%EB%9E%98%EC%BB%A4-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@sally_devv/AWS-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EC%9D%B4%EC%8A%88-%ED%8A%B8%EB%9E%98%EC%BB%A4-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Thu, 16 Jun 2022 19:11:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>ec2 프리티어에 java 설치, 시간 설정, git 설치, springboot 배포하기까지의 내용입니다.</p>
</blockquote>
<p>( ubuntu의 apt-get 과는 다르게 Linux는 yum 사용 )</p>
<ol>
<li><p><code>sudo yum update</code></p>
</li>
<li><p><code>sudo yum install java-11-amazon-corretto</code> 설치 <a href="https://docs.aws.amazon.com/ko_kr/corretto/latest/corretto-11-ug/amazon-linux-install.html">docs.aws</a></p>
</li>
</ol>
<pre><code class="language-bash">    yum list java*
    sudo yum list | grep jdk // 자바 7,8버전 목록들만 나온다.</code></pre>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/819cefb9-b3fa-40ac-9d1b-3fb03736bc9f/image.png" alt=""></p>
<ol start="3">
<li><code>date</code> 타임존 확인(보통 UTC 기준) ➜ 한국시간(KST)으로 변경</li>
</ol>
<pre><code class="language-bash">    sudo rm /etc/localtime
    sudo ln -s /usr/share/zoneinfo/Asia/Seoul /etc/localtime</code></pre>
<p>-
<img src="https://velog.velcdn.com/images/sally_devv/post/329f34b9-c039-4137-a77b-47763fb9eb32/image.png" alt=""></p>
<ol start="4">
<li><code>sudo yum install git</code> 깃 설치<pre><code class="language-bash"> git --version</code></pre>
</li>
</ol>
<ul>
<li><p>ps. 환경변수</p>
<ul>
<li><p><code>i</code> 입력</p>
</li>
<li><p><code>wq</code> 쓰기 저장 후 종료</p>
<pre><code class="language-bash">cd ~
vi .bashrc

export profile=deploy
export MYSQL_DATABASE_URL=RDS엔드포인트
export MYSQL_DATABASE_USERNAME=db계정
export MYSQL_DATABASE_PASSWORD=db계정PW

source .bashrc
echo $profile</code></pre>
</li>
</ul>
</li>
</ul>
<ol start="5">
<li><p><code>mkdir ~/app</code> git clone으로 프로젝트 저장할 디렉토리 생성, 이동</p>
<pre><code class="language-bash"> cd app</code></pre>
</li>
<li><p><code>git clone https://github.com/sally-ksh/issue-tracker.git</code> </p>
<pre><code class="language-bash"> git switch deploy   // 배포 branch
 cd BE 
 chmod -x gradlew    // 실행권한</code></pre>
</li>
<li><p><code>vi ~/app/deply.sh</code> 배포 스크립트 작성</p>
</li>
</ol>
<pre><code class="language-bash">    chmod +x ./deploy.sh
    ./deploy.sh
    vim nohup.out   // 실행 확인
    ps -ef | grep issuetracker   // 실행 확인</code></pre>
<ul>
<li><p><code>cat /dev/null &gt; nohup.out</code> nohup.out 파일 초기화</p>
</li>
<li><p><code>./gradlew build --exclude-task test</code> 빌드 시 테스트 제외 <a href="https://www.devkuma.com/docs/gradle/%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A0%9C%EC%99%B8%ED%95%98%EA%B3%A0-%EC%8B%A4%ED%96%89/">ref</a></p>
<p><img src="https://velog.velcdn.com/images/sally_devv/post/2686eb77-ff8b-4139-a4f3-09aa3e1603d0/image.png" alt=""></p>
</li>
</ul>
<ol start="8">
<li>ec2 인스턴스 보안그룹 - 포트개방(8080)</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[OAuth]]></title>
            <link>https://velog.io/@sally_devv/OAuth</link>
            <guid>https://velog.io/@sally_devv/OAuth</guid>
            <pubDate>Sun, 12 Jun 2022 16:07:17 GMT</pubDate>
            <description><![CDATA[<p><a href="https://datatracker.ietf.org/doc/html/rfc6749">Oauth - rfc6749</a></p>
<blockquote>
<p>Oauth 인증방법 중 grant-types 번역 내용입니다.</p>
</blockquote>
<h2 id="roles">Roles</h2>
<h4 id="resource-owner">resource owner</h4>
<ul>
<li>end-user (최종 사용자)</li>
<li>보호되는 리소스로의 접근이 허용되는 엔티티</li>
</ul>
<h4 id="resource-server">resource server</h4>
<ul>
<li>액세스 토큰을 이용한 보호 되는 자원 요청을 수락하고 응답하여 호스팅하는 서버</li>
</ul>
<h4 id="client">client</h4>
<ul>
<li>어떤 특성을 의미하는게 아니라, 자원 소유자의 지지와 자원 사용 허가를 받는 요청으로 보호된 자원을 만드는 애플리케이션</li>
</ul>
<h4 id="authorization-server">authorization server</h4>
<ul>
<li>자원 소유자임을 성공적으로 인증하고 권한을 받아 클라이언트에게 액세스 토큰을 발행하는 서버</li>
</ul>
<hr>
<p><a href="https://developer.okta.com/blog/2018/04/10/oauth-authorization-code-grant-type">grant-type</a></p>
<p>grant-type : 애플리케이션이 access token 얻는 방식</p>
<h2 id="the-authorization-code-flow">The Authorization Code Flow</h2>
<ul>
<li>user를 OAuth 서버로 보내기 위해 application은 browser를 보여준다.</li>
<li>user는 인증 창을 보고, 앱의 요청을 승인한다</li>
<li>user는 쿼리 스트링 안에 인증 코드를 가지고 애플리케이션으로 되돌아간다.(리다이렉트)</li>
<li>애플리케이션은 인증코드를 access token으로 교환한다.</li>
</ul>
<h3 id="get-the-users-permission">Get the User’s Permission</h3>
<p>OAuth는 사용자에게 애플리케이션으로의 제한된 접근시킬 수 있다.</p>
<p>애플리케이션은 처음에 OAuth로 요청할 허락이 필요하기 때문에, 사용자를 브라우저로 보낸다.</p>
<p>authorization flow를 시작하기 위해서, 애플리케이션은 URL을 구성하고, URL의 브라우저를 보여준다.</p>
<pre><code>https://authorization-server.com/auth
 ?response_type=code
 &amp;client_id=29352915982374239857
 &amp;redirect_uri=https%3A%2F%2Fexample-app.com%2Fcallback
 &amp;scope=create+delete
 &amp;state=xcoiv98y2kd22vusuye3kch</code></pre><ul>
<li><p>response_type : 애플리케이션이 authorization code flow를 초기화하는 authorization server</p>
</li>
<li><p>client_id : 애플리케이션을 위한 공개된 식별자 (개발자가 처음 애플리케이션을 등록할 때 얻은)</p>
</li>
<li><p>redirect_uri : 요청을 승인한 이후 user를 돌려보낼 authorization server</p>
</li>
<li><p>scope : 애플리케이션이 요청하는 허용을 암시하는 1개 이상의 구분되는 문자열</p>
</li>
<li><p>state : 애플리케이션이 요청을 포함하여 만든 무작위 문자열로, user가 app 에 권한을 부여한 후 동일한 값이 반환되는 지 확인</p>
<ul>
<li>CSRF 공격 예방위해 사용</li>
</ul>
</li>
</ul>
<h3 id="redirect-back-to-the-application">Redirect Back to the Application</h3>
<p>user가 요청을 승인하면,
authorization server 는 브라우저를 redirect_uri(code, state가 추가된)로 돌려보낸다.</p>
<pre><code>https://example-app.com/redirect
 ?code=g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3
 &amp;state=xcoiv98y2kd22vusuye3kch</code></pre><ul>
<li>code : authorization server가 만든 <strong>인증 코드</strong>(1~10분 정도 짧게 이용)</li>
</ul>
<h3 id="exchange-the-authorization-code-for-an-access-token">Exchange the Authorization Code for an Access Token</h3>
<p>인증토큰 ➜ 접근 토큰</p>
<p>애플리케이션이 인증코드를 가진 상태로 접근 토큰을 얻는데 사용한다.</p>
<p>애플리케이션이 다음의 파라미터들과 함께 POST 요청한다.</p>
<ul>
<li>grant_type=authorization_code<ul>
<li>애플리케이션이 인증코드 grant type으로 사용하는 토큰 endpoint</li>
<li>만료 안된</li>
</ul>
</li>
<li>code<ul>
<li>redirect로부터 받은 인증 코드 포함</li>
</ul>
</li>
<li>redirect_uri<ul>
<li>요청시와 같은 uri (제외되기도 한다)</li>
</ul>
</li>
<li>client_id<ul>
<li>애플리케이션의 clinet ID</li>
</ul>
</li>
<li>client_secret<ul>
<li>애플리케이션의 client secret</li>
<li>요청이 오직 애플리케이션에 의해 만들어진 접근 토큰을얻기 위함임을 보증</li>
</ul>
</li>
</ul>
<p>access token 생성후 반환되는 응답</p>
<pre><code>HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache

{
  &quot;access_token&quot;:&quot;MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3&quot;,
  &quot;token_type&quot;:&quot;bearer&quot;,
  &quot;expires_in&quot;:3600,
  &quot;refresh_token&quot;:&quot;IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk&quot;,
  &quot;scope&quot;:&quot;create delete&quot;
}</code></pre><ul>
<li>이제부터 애플리케이션이 접근토큰을 API 요청에 사용할 수 있다.</li>
</ul>
<h3 id="when-to-use-the-authorization-code-flow">When to use the Authorization Code Flow</h3>
<ul>
<li><p>모마일 앱 등클라이언트 비밀을 저장할 수 없다면 <a href="https://www.oauth.com/oauth2-servers/pkce/?_ga=2.27112317.1754297633.1658545671-548409433.1658545671">PKCE extension</a>을 사용</p>
</li>
<li><p>뒷단에서 애플리켕션과 OAuth 서버를 통해서 액세스 토큰이 보내지기 때문에, 코드 교환 과정은 공격자가 액세스 토큰을 가로채지 못함을 보장한다.</p>
</li>
</ul>
<hr>
<p>reference</p>
<ul>
<li><p><a href="https://datatracker.ietf.org/doc/html/rfc6749#section-1.1">rfc6749 - ROLE</a></p>
</li>
<li><p><a href="https://developer.okta.com/blog/2018/04/10/oauth-authorization-code-grant-type">What is the OAuth 2.0 Authorization Code Grant Type?</a></p>
</li>
<li><p>추천 <a href="https://www.youtube.com/watch?v=hm2r6LtUbk8&amp;list=PLuHgQVnccGMA4guyznDlykFJh28_R08Q-">생활코딩 OAuth2</a></p>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>