<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>y-sun010331.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 05 May 2026 14:51:41 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>y-sun010331.log</title>
            <url>https://velog.velcdn.com/images/y-sun010331/profile/abeda1a4-e7c2-4485-ac8e-2464565b8ad3/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. y-sun010331.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/y-sun010331" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[카프카 간보기]]></title>
            <link>https://velog.io/@y-sun010331/%EC%B9%B4%ED%94%84%EC%B9%B4-%EA%B0%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@y-sun010331/%EC%B9%B4%ED%94%84%EC%B9%B4-%EA%B0%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Tue, 05 May 2026 14:51:41 GMT</pubDate>
            <description><![CDATA[<p>세미나 발표를 위해 이곳저곳 헤집다가 
발견한 유튭 자료를 정리하고 발표 형식으로 바꾸었다</p>
<blockquote>
<p>내가 이해가 안되는 부분들은 이렇게 작은 박스에 내 생각과 결론을 찾아 적었다</p>
</blockquote>
<h1 id="1-monolithic-vs-msa">1. monolithic vs MSA</h1>
<p><img src="https://velog.velcdn.com/images/y-sun010331/post/04dc7de3-c19e-45b0-9d89-9156ecb82ccf/image.png" alt=""></p>
<p>과거 서버들은 하나로 구성돼있는 단일구조였다.
그 구조들은 monolithic이라고 부른다.</p>
<p>이 구조는 하나의 서버에 하나의 데이터베이스를 연결해
모든 서비스를 처리하는 구조이다.</p>
<p>개발 초기에는 관리와 배포가 간단하고
단일 애플리케이션으로 모든걸 처리하기 때문에 인프라 복잡도와 비용이 낮다</p>
<p>하지만 서비스가 커질수록 문제가 생긴다</p>
<ul>
<li><p>확장과 배포의 단점</p>
<ul>
<li>서비스가 커지면 커질수록 애플리케이션 전체를 확장해야한다.</li>
<li>트래픽은 계속 하나의 서버로 들어오므로 특정 기능만 확장하기가 어렵다</li>
</ul>
</li>
<li><p>기술 스택의 제한성</p>
<ul>
<li>기술 스택을 통일시키는건 간단하지만, 새로운 기술 도입은 어렵다.</li>
<li>자바에서 의존성을 추가했을때 충돌하게 되는 경우가 이 예시에 해당된다</li>
</ul>
</li>
<li><p>복잡성 증가</p>
<ul>
<li>전체 구조를 이해해야만 안전하게 코드를 수정할 수 있기 때문에 개발자의 진입 장벽이 높아진다.</li>
<li>예를들어 대규모 서비스를 유지보수하기 위해 신입 개발자를 투입할 경우 단순한 기능 수정조차 전체 시스템 구조를 파악하는 데 많은 시간이 소요된다.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/y-sun010331/post/b5d289e4-fcfb-421e-96d3-e91e1c75b103/image.png" alt=""></p>
<h3 id="카프카란-무엇인가">카프카란 무엇인가</h3>
<p>고성능 분산 이벤트 스트리밍 플렛폼</p>
<h2 id="기본-구조">기본 구조</h2>
<p><img src="https://velog.velcdn.com/images/y-sun010331/post/bc852f3a-1df1-498c-9a0a-9203f318fcc0/image.png" alt=""></p>
<h3 id="4개의-구성요소">4개의 구성요소</h3>
<ul>
<li>카프카 클러스터</li>
<li>주키퍼 클러스터(앙상블)</li>
<li>프로듀서</li>
<li>컨슈머</li>
</ul>
<h3 id="카프카-클러스터">카프카 클러스터</h3>
<p>메시지를 저장하는 저장소</p>
<ul>
<li>여러개의 브로커를 갖고 있음 각각의 서버라고 보면 됨</li>
<li>브로커들이 메시지를 나눠서 저장함</li>
<li>장애가 나면 대체도 함</li>
</ul>
<h3 id="주키퍼-클러스터">주키퍼 클러스터</h3>
<p>카프카 클러스터를 관리하는 역할</p>
<ul>
<li>주키퍼 내부에 카프카 클러스터에 대한 정보를 갖고있음</li>
</ul>
<h3 id="프로듀서">프로듀서</h3>
<p>메세지를 카프카에 넣는 역할</p>
<h3 id="컨슈머">컨슈머</h3>
<p>메세지를 카프카에서 읽음</p>
<h2 id="토픽과-파티션">토픽과 파티션</h2>
<p>토픽 : 메세지를 구분하는 논리적 단위
파티션 : 메세지를 저장하는 물리적 파일</p>
<ul>
<li>한 개의 토픽은 한 개 이상의 파티션으로 구성됨</li>
<li>토픽은 폴더라고 보면 됨</li>
<li>파티션은 데이터가 실제로 저장되는 장소</li>
</ul>
<h2 id="파티션과-오프셋-메시지-순서">파티션과 오프셋, 메시지 순서</h2>
<p><img src="https://velog.velcdn.com/images/y-sun010331/post/9dd34141-fd21-4f21-9c7e-c37829f18551/image.png" alt=""></p>
<p>파티션은 추가만 가능한(append-only) 파일</p>
<ul>
<li>각 메세지 저장 위치를 오프셋(offset) 이라고 함</li>
<li>프로듀서가 넣은 메세지는 파티션의 맨 뒤에 추가</li>
<li>컨슈머는 오프셋 기준으로 메시지를 순서대로 읽음</li>
<li>메세지는 삭제되지 않음 (설정에 따라 일정 시간이 지난뒤 삭제) </li>
</ul>
<h2 id="여러-파티션과-프로듀서">여러 파티션과 프로듀서</h2>
<ul>
<li>프로듀서는 라운드 로빈 또는 키로 파티션 선택<ul>
<li>같은 키를 갖는 메세지는 같은 파티션에 저장됨 -&gt; 같은 키는 순서를 유지</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/y-sun010331/post/f12d8150-7ec7-4cc0-af4f-24fdad1a03b3/image.png" alt=""></p>
<ul>
<li><p>컨슈머는 컨슈머 그룹에 속함</p>
</li>
<li><p>한 개 파티션은 컨슈머 그룹의 한 개 컨슈머만 연결 가능</p>
<ul>
<li>컨슈머 그룹에 속한 컨슈머들은 한 파티션을 공유할 수 없음</li>
<li>한 컨슈머그룹 기준으로 파티션의 메시지는 순서대로 처리함</li>
</ul>
<blockquote>
<p>이해가 안된부분
Q.왜 그룹은 파티션을 공유하지 않는가 ?
A.예를 들어서
로그인 처리 그룹A가 있고
그룹 안에는 처리담당1,2가 있다고 가정해보자
 처리담당 1은 브로커 0의 토픽1-파티션1 을 담당하고
 처리담당 2는 브로커 0의 토픽1-파티션2 를 담당하면 분산처리가 가능하므로
 같은 그룹끼리는 한개의 파티션을 공유하지 않는다</p>
</blockquote>
</li>
</ul>
<h2 id="성능">성능</h2>
<p>파티션 파일은 OS 페이지 캐시 사용</p>
<ul>
<li>파티션에 대한 FILE IO를 메모리단에서 처리함<blockquote>
<p>일반적으로는
[어플리케이션 -&gt; OS -&gt; 디스크] 과정을 거쳐 저장한다
근데 카프카가 메시지를 저장할때는 OS의 빈 공간에 먼저 기록하고 나중에 처리함
읽을때도 마찬가지로 OS캐시를 먼저 보고 처리하므로 속도가 빠르다</p>
</blockquote>
</li>
</ul>
<p>Zero Copy</p>
<ul>
<li>디스크 버퍼에서 네트워크 버퍼로 직접 복사함</li>
</ul>
<blockquote>
<p>일반적인 데이터 전송 과정
디스크 -&gt; 커널 영역 -&gt; 유저영역 -&gt; 커널영역-&gt; 네트워크 버퍼</p>
</blockquote>
<blockquote>
<p>카프카는 OS의 sendfile 시스템 콜을 이용
디스크 -&gt; 커널 -&gt; 네트워크 영역
CPU가 데이터를 유저영역으로 복사하지 않으므로 일이 줄어듦 그래서 Zero Copy</p>
</blockquote>
<ul>
<li>브로커가 하는 일이 비교적 단순함<ul>
<li>메시지 필터, 메시지 재전송과 같은 일은 브로커가 안하고 프로듀서, 컨슈머가 직접 함</li>
<li>브로커는 컨슈머와 파티션 간 매핑 관리만 해줌</li>
</ul>
</li>
<li>배치처리<ul>
<li>프로듀서 : 일정 크기만큼 메시지를 모아서 전송 가능</li>
<li>컨슈머 : 최소 크기만큼 메시지를 모아서 조회 가능</li>
</ul>
</li>
<li>수평 확장이 용이함<ul>
<li>브로커 , 파티션 추가가 쉬움</li>
<li>컨슈머가 느림 -&gt; 컨슈머 추가</li>
</ul>
</li>
</ul>
<h2 id="리플리카---복제">리플리카 - 복제</h2>
<p>리플리카 : 파티션의 복제본</p>
<ul>
<li>복제수 만큼 파티션의 복제본이 각 브로커에 생김
리더와 팔로워로 구성</li>
<li>프로듀서와 컨슈머는 리더를 통해서만 메시지 처리</li>
<li>팔로워는 리더로부터 복제함
장애 대응<ul>
<li>리더가 속한 브로커 장애시 다른 팔로워가 리더가 됨</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 - 6주차(N+1문제)]]></title>
            <link>https://velog.io/@y-sun010331/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-6%EC%A3%BC%EC%B0%A8N1%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@y-sun010331/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-6%EC%A3%BC%EC%B0%A8N1%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Thu, 12 Feb 2026 16:40:40 GMT</pubDate>
            <description><![CDATA[<p>N+1 문제에 대해 써보자</p>
<h1 id="1n-문제">1+N 문제</h1>
<p>1번 요청을 날렸는데 추가로 N번 나간 상황</p>
<h2 id="왜-이런일이">왜 이런일이?</h2>
<p>친구 3명과 점심을 먹으러 갔다고 가정하자.
총 4명이 앉아 있다.</p>
<p><img src="https://velog.velcdn.com/images/y-sun010331/post/2ae71bc2-c3e5-4780-bf6a-b93e646c30f8/image.png" alt=""></p>
<p>“감자탕 파스타 4개 주세요.”
주문은 한 번에 끝난 것처럼 보인다.
그런데 웨이터가 다시 묻는다.</p>
<p>“맛은 어떤 맛으로 드릴까요?”
그래서 한 명씩 대답한다.</p>
<p>나는 매운맛
친구 1은 보통맛
친구 2는 보통맛
친구 3은 매운맛
주문은 한 번이었지만
세부 선택은 인원 수만큼 다시 확인했다.</p>
<h2 id="이걸-db로-바꾼다면">이걸 DB로 바꾼다면</h2>
<p>파스타를 주문했다고 생각해보자</p>
<pre><code class="language-java">
주문서 = 주문서Repository.findAll();</code></pre>
<p>이 코드의 의미는 
&quot;주문 내역을 가져와라&quot; 이다.</p>
<p>DB에서는 이렇게 나간다</p>
<pre><code class="language-sql">주문 목록을 조회하라!
select * from order;</code></pre>
<p>결과 : 파스타 4</p>
<h3 id="근데-우리가-궁금한거">근데 우리가 궁금한거</h3>
<p>“각 주문은 어떤 맛으로 주문했지?”</p>
<p>코드를 추가해줘야 한다</p>
<pre><code class="language-java">for (주문서 : 주문내역들) {
    System.out.println(주문서.get메뉴().get맛());
}</code></pre>
<p>결과 : 
파스타1- 보통맛
파스타2- 매운맛 ...</p>
<h3 id="db에서-벌어지는-일">DB에서 벌어지는 일</h3>
<pre><code class="language-sql">select * from orders;         -- 1번
select * from taste where id=1;  -- 1번
select * from taste where id=2;  -- 1번
select * from taste where id=3;  -- 1번
...
</code></pre>
<p>주문서가 N개라면
맛 조회 쿼리가 N번 실행된다.</p>
<h2 id="왜-이런-일이-생길까">왜 이런 일이 생길까?</h2>
<p>핵심은 이것이다.</p>
<pre><code class="language-java">주문서.get메뉴().get맛()</code></pre>
<p>이 시점에 ORM은</p>
<p>“아, 이 연관 객체를 실제로 써야 하는구나.”</p>
<p>라고 판단하고 그때 쿼리를 날린다.</p>
<p>이걸 지연 로딩(Lazy Loading) 이라고 한다.</p>
<h2 id="해결책">해결책</h2>
<h3 id="1fetch-join">1.fetch join</h3>
<p>처음 주문할 때부터 말하면 된다</p>
<p>“감자탕 파스타 4개 주세요.
그리고 매운맛 2개, 보통맛 2개로 주세요.”</p>
<p>웨이터가 다시 물어볼 필요가 없다.</p>
<p>이미 필요한 정보를 한 번에 다 전달했다.</p>
<h4 id="그런데-여기서-끝이-아니다">그런데 여기서 끝이 아니다~?</h4>
<p>“그럼 무조건 fetch join 쓰면 됨?&quot;</p>
<p>그건 아니다.</p>
<p>만약 주문에 연관된 정보가 여러 개라면?</p>
<p>“감자탕파스타4개주세요그리고매운맛2개보통맛2개로주시고요결제는KB국민카드로할건데카드번호는804802000015498725812이고유효기간은2609CVC는뒤에세자리인데말해도되나요비밀번호는비밀이긴한데혹시몰라서힌트는제생일이고앞자리는공개가능합니다영수증은이메일로보내주시고리뷰이벤트있으면참여할게요적립도해주세요포인트는통합으로묶어주시고혹시몰라서현금영수증도같이해주세요창가자리비어있으면옮기고싶고물은미지근하게얼음은두개만넣어주시고파스타면은조금덜익혀주시고감자탕고기는살코기많은부위로부탁드립니다아그리고혹시오늘주방휴무는아니죠”</p>
<p>...</p>
<p>전부 join fetch로 가져오면
쿼리가 커지고, 중복 데이터가 늘어나고,
오히려 성능이 더 나빠질 수 있다.</p>
<p>아무튼 fetch join은 한 번에 다 가져오는 전략이다.</p>
<h3 id="2batch-size">2.Batch size</h3>
<p>웨이터가 한 명씩 묻는 게 비효율적이라는 걸 깨달았다.</p>
<p>그래서 이렇게 말한다.</p>
<p>“매운맛 드실 분 손 들어주세요.”
“보통맛 드실 분 손 들어주세요.”</p>
<p>그리고 한 번에 정리한다.</p>
<p>“매운맛 37개, 보통맛 63개 맞으시죠?”</p>
<p>이제 확인은 여러 번이 아니라
묶어서 몇 번으로 줄어든다.</p>
<h4 id="단점">단점</h4>
<p>그래도 완전한 1번은 아니다.</p>
<p>웨이터가</p>
<p>“매운맛 손!”
“보통맛 손!”
“안 매운맛 손!”
.
.
.</p>
<p>이렇게 몇 번은 묻는다.</p>
<p>즉,</p>
<p>N번이 1번이 되지는 않는다.
하지만 N번이 “몇 번”으로 줄어든다.</p>
<p>우리팀은 DTO 전략을 사용했다
왜냐하면 지금 참조하는 속성이 많아
fetch join을 사용 할 수 없기때문이다...</p>
<p>추후 참조하는 속성 제거 후 fetch join으로 변경하려고 한다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 - 5주차(post과 get차이,http 구조,Page Slice, Redis단순나열)]]></title>
            <link>https://velog.io/@y-sun010331/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-5%EC%A3%BC%EC%B0%A8post%EA%B3%BC-get%EC%B0%A8%EC%9D%B4http-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@y-sun010331/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-5%EC%A3%BC%EC%B0%A8post%EA%B3%BC-get%EC%B0%A8%EC%9D%B4http-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Sun, 01 Feb 2026 17:12:07 GMT</pubDate>
            <description><![CDATA[<p>목차</p>
<ol>
<li>Post vs get</li>
<li>HTTP 요청은 어떻게 구성될까</li>
<li>Page vs Slice</li>
<li>Redis (단순 나열)</li>
</ol>
<h1 id="1-http-구조를-다시-보게-된-이유post-vs-get">1. HTTP 구조를 다시 보게 된 이유(Post vs get)</h1>
<p>HTTP를 처음 배울 때 GET과 POST는 이렇게 배웠다.
GET은 조회, POST는 생성.</p>
<p>그런데 실제 코드를 보다 보니 이상했다.
GET의 query parameter와 POST의 request body는 모양이 거의 같다.
키-값 쌍이고, 서버에서는 결국 같은 DTO로 바인딩된다.</p>
<p>그렇다면 정말 차이는 URI에 노출되느냐, 안 되느냐뿐일까?
이 의문에서 이 글을 쓰기 시작했다.</p>
<h2 id="http-요청의-기본-구조부터-다시-보기">HTTP 요청의 기본 구조부터 다시 보기</h2>
<p>HTTP 요청은 크게 세 부분으로 나뉜다.</p>
<ul>
<li>Request Line (Method + URI + Version)</li>
<li>Headers</li>
<li>Body</li>
</ul>
<p>URI는 요청 장소
Body는 요청 내용</p>
<h2 id="get-요청에서-query는-왜-uri에-붙을까">GET 요청에서 Query는 왜 URI에 붙을까</h2>
<p><strong>GET 요청은 “오늘 학식 뭐임?”과 비슷하다.</strong>
본질적으로 조회하기 위한 요청이란 뜻이다.</p>
<p>학식이 있을 수도 있고, 없을 수도 있지만
질문 자체는 <em>정보 확인</em>에 초점이 맞춰져 있다.</p>
<p>만들어줘 x
이미 있는거 확인 해줘 o
URI 자체가 캐시의 키가 되고, 히스토리가 된다.
Query parameter는 호출 대상을 특정하기 위한 조건 표현에 가깝다.</p>
<blockquote>
<p>URI로 캐시가 이뤄지는건 처음알았다.
Body도 이론적으로는 캐시가 가능하지만,
HTTP 캐시가 신뢰하도록 설계된 조건은 아니라고 함.</p>
</blockquote>
<h2 id="post-요청에서-body는-어떤-의미를-가질까"><strong>POST 요청에서 Body는 어떤 의미를 가질까</strong></h2>
<p>POST는 조회가 아니라 <strong>처리</strong> 를 요청하는 방식이다.</p>
<p>“오늘 학식 뭐임?”을 물어 보고
맛이 없으면
*<em>“싸이버거 하나요” *</em>라고 주문하는 상황에 가깝다.</p>
<p>이때 중요한 건 <strong>주문 내용 그 자체</strong>다.
그래서 POST 요청의 핵심 정보는 URI가 아니라 Body에 담긴다.
Body는 “무엇을 어떻게 처리해 달라는지”를 설명한다.
이 Body 안에는 생성할 데이터가 들어갈 수도 있고,
복잡한 조건이나 상태 변경 요청이 들어갈 수도 있다.</p>
<blockquote>
<p>이런 요청은 결과가 매번 같을 필요도 없음
같은 요청을 다시 보낸다고 해서 같은 결과가 나온다는 보장없음
그래서 POST 요청은 기본적으로 캐시를 전제로 하지 않는다.
Body가 캐시의 기준이 되지 않는 이유도 여기에 있다.
Body는 리소스를 <em>식별</em>하기 위한 정보라기보다
서버에 전달되는 <em>행위의 내용</em>에 가깝기 때문이다.</p>
</blockquote>
<h1 id="2-http-요청은-어떻게-구성될까">2. HTTP 요청은 어떻게 구성될까</h1>
<p>post과 get 를 헷갈리는 나에게 
GPT는 http에 대해 좀 더 공부하라 가이드 해줬다
API 명세서를 작성하면서 명확하지 않은 부분을 명확하게 하려 노력했다</p>
<h2 id="get-예시">GET 예시</h2>
<pre><code class="language-php">GET /학식?date=2026-02-01 HTTP/1.1
Host: 학교식당[.com](http://school.example.com/)
Accept: application/json </code></pre>
<h2 id="post-예시">POST 예시</h2>
<pre><code class="language-php">POST /orders HTTP/1.1
Host: 맘스터치.com
Content-Type: application/json

{
  &quot;menu&quot;: &quot;싸이버거&quot;,
  &quot;quantity&quot;: 1
}</code></pre>
<p>전체적인 구조는 다음과 같다</p>
<ul>
<li>Request Line (요청의 정체) : [Method] [URI] [HTTP Version]</li>
<li>Headers (부가 정보 설명서) :content-type,Authorization, Cookie, Cache-Control,Origin / Host 등등 ,,,</li>
<li>Body (실제 데이터) :</li>
</ul>
<h3 id="headers">Headers</h3>
<p>Body를 어떻게 처리할지 설명해주는 곳임</p>
<p>Accept: application/json → json 형태로 응답 받을꺼임
Content-Type: application/json → json 형태로 요청 보낼꺼임
Cache-Control : 이 요청/응답이 캐시되어도 됨or 안됨
Origin : 이 요청은 여기서 왔다
Authorization : &quot;나는 인증된 요청임&quot; [토큰(JWT, OAuth 등) 기반]
Cookie : 서버가 이전에 브라우저에게 맡겨 둔 상태 정보</p>
<ul>
<li>세션 로그인 할때 쓰는거임</li>
<li>JSESSIONID=abc123</li>
</ul>
<blockquote>
<p>CORS 에러가 나는 이유
크롬에서 이 요청이 신뢰할 수 있는지 판단하기 위해
OPTIONS 요청(Preflight) 을 서버에 먼저 보낸다
Authorization , Content-Type: application/json ,Origin 다를때 등등
지겨운 에러의 원인을 하나 알아간다</p>
</blockquote>
<h1 id="3page-vs-slice">3.Page vs Slice</h1>
<p>한 번에 많은 데이터를 조회 -&gt; 속도 이슈
이 문제를 찾아보다 보면 보통 두 가지 선택지가 나온다.</p>
<p>Page vs Slice</p>
<p>Page는 
학과 공지사항 홈페이지 처럼 page로 구분된다</p>
<p>Slice는 
인스타그램 처럼 무한 스크롤 가능하다</p>
<ul>
<li>Page
→ 전체 개수를 알아야 해서 count 쿼리가 필요</li>
<li>Slice
→ 전체 개수 몰라도 되고, 다음 페이지 존재 여부만 판단</li>
</ul>
<blockquote>
<p>그럼 대용량에서는 count 쿼리 오래걸리니 무조건 Slice가 좋은 거 아님?</p>
</blockquote>
<h2 id="page-쓰는이유">Page 쓰는이유</h2>
<p>Page의 목적은 정확한 정보 제공이다.</p>
<ul>
<li>데이터의 전체 규모 파악</li>
<li>정교한 탐색</li>
<li>검색 엔진 최적화</li>
</ul>
<p>이게 중요한 서비스에서 Page가 사용된다</p>
<p>예를 들어
3달 전에 사진을 다시 보고 싶은 상황을 생각해보자.</p>
<p>Page라면
페이지를 기준으로 빠르게 이동할 수 있다.</p>
<p>반면 Slice라면
무한 스크롤을 계속 내려야 하고,
중간에 새로고침이라도 되면 처음부터 다시 내려야 한다.</p>
<p>또 무조건 느리지 않다
카운트 쿼리를 특정 주기마다 캐싱하거나,
인덱스 활용하면 충분히 빠르게 검색 가능하다고 한다(출처gpt,젬미니)</p>
<h2 id="slice">Slice</h2>
<ul>
<li>전체가 몇 개인지는 중요하지 않다</li>
<li>다음 데이터가 있느냐만 중요하다</li>
</ul>
<h2 id="정리">정리</h2>
<p>Page는 정확한 탐색을 위한 선택
Slice는 성능을 위한 선택</p>
<p>데이터가 많아진다고 해서
무조건 Slice를 써야 하는 건 아니다.</p>
<p>사용자가 무엇을 알고 싶어 하는지에 따라 다름</p>
<h1 id="4-redis-단순-나열">4. Redis (단순 나열)</h1>
<h2 id="1-in-memory-database">1. In-memory database</h2>
<p>주요특징</p>
<ul>
<li>1ms 단위 → 디스크 대비 빠른 응답속도</li>
<li>휘발성 데이터</li>
<li>다양한 데이터 타입 제공</li>
</ul>
<h3 id="memcached">Memcached</h3>
<ul>
<li>cache</li>
</ul>
<h3 id="redis">redis</h3>
<ul>
<li>캐쉬</li>
<li>세션</li>
<li>Leader board </li>
</ul>
<h2 id="2-remote-dictionary-server">2. Remote Dictionary Server</h2>
<p>key - value 시스템</p>
<h3 id="백업-방식">백업 방식</h3>
<ul>
<li><p>RDB ( Snapshot )</p>
<ul>
<li>특정 시간에 DB에 저장 / 실행 주기가 긴 백업 방식 </li>
</ul>
</li>
<li><p>AOF (Append Only File) : </p>
<ul>
<li>모든 쓰기 명령에 로그를 남김</li>
<li>디스크 쓰기작업에 빈번함 </li>
</ul>
</li>
</ul>
<p>RDB 생성할때 or AOF rewrite(재작성) 과정에서:</p>
<p>process fork 방식 
백업 시점 프로세스를 복제(fork)해서 중간마다 저장<br>메모리 사용률 급등할수있음 모니터링 반드시 필요</p>
<p>기본은 RDB를 사용
단순 캐쉬작업 RDB , AOF 둘다 끄고 사용함</p>
<h2 id="single-thread">Single Thread</h2>
<p>여러 처리를 동시에 받아도 하나씩 처리함</p>
<p>데이터 일관성 보장 , Lock 사용 안함</p>
<p>(Redis : 초당 10만건 까지 가능)</p>
<blockquote>
<p>Redis 6.0부터 Threaded I/O 도입되어 네트워크 입출력은 멀티 스레드로 처리함
하지만 명령어 처리는 여전히 싱글 스레드</p>
</blockquote>
<h2 id="활용-사례">활용 사례</h2>
<p>Cache : </p>
<p>Session Store : TTL 사용함 , was에 세션 저장 x</p>
<p>Pub/Sub : 1 메시지 → 여러 Subscriber (확성기)</p>
<p>Message Queue : 지금 당장 처리하지 않아도 되는 일을 줄 세워 두는 중간 저장소</p>
<p>Geospatial : </p>
<p>Leader board : 순위 정보를 빠르게 얻을 수 있음</p>
<blockquote>
<p>Message Queue , Pub/Sub 패턴 내용은 너무 많아서 패쓰
공부할게 또 늘었다</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 - 4주차(DIP)]]></title>
            <link>https://velog.io/@y-sun010331/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-4%EC%A3%BC%EC%B0%A8DIP</link>
            <guid>https://velog.io/@y-sun010331/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-4%EC%A3%BC%EC%B0%A8DIP</guid>
            <pubDate>Wed, 28 Jan 2026 05:36:22 GMT</pubDate>
            <description><![CDATA[<h2 id="dip">DIP</h2>
<p>평소처럼 코드를 작성하다가 다음코드를 읽고 의문이 들었다</p>
<pre><code class="language-java">private final PaymentService service;</code></pre>
<p>이 패턴이 DIP를 고려한 코드라는 건</p>
<p>무적영한킴의 강의를 통해 알고 있었다.</p>
<p>하지만 막상 DIP가 뭐였지?라고 생각하니</p>
<p>명확하게 설명할 수 없었다.</p>
<p>그래서 이 코드가 왜 DIP와 연결 되는지</p>
<p>실제로 어떤 문제를 해결해주는지 정리하려 한다.</p>
<h2 id="이-코드가-자연스럽게-쓰이게-된-이유">이 코드가 자연스럽게 쓰이게 된 이유</h2>
<p>만약 코드가 다음과 같으면 어떤 문제가 생길까</p>
<pre><code class="language-java">private final  joinTheMilitary 입대 = new 해군입대();
</code></pre>
<p>이 구조에서 클래스는</p>
<p>“입대한다” 라는 정책이 아니라</p>
<p>“해군으로 입대 한다”는 세부사항까지 결정하게 된다.</p>
<p>당장 동작하는데 문제는 없지만</p>
<p>요구사항이 육군으로 바뀌는 순간</p>
<p>클래스의 코드를 바꿔야 하는 문제가 생긴다</p>
<p>이 문제를 해결하기 위해 등장한 원칙이 DIP다 </p>
<h2 id="고수준-저수준-모듈">고수준 저수준 모듈</h2>
<p>고수준/저수준 이라는 용어를 들으면 상하 관계가 있다는건 알겠지만</p>
<p>어떻게 상하로 나뉘는지 직관적으로 이해하기 어려웠다</p>
<p>고수준 모듈 </p>
<p>→ <strong>무엇</strong>을 할 것인가</p>
<p>정책,규칙,흐름</p>
<p>저수준 모듈</p>
<p>→ <strong>어떻게</strong> 할 것인가</p>
<p>세부사항을 의미한다</p>
<h2 id="그래서-dip란">그래서 DIP란?</h2>
<p>DIP(Dependency Inversion Principle)는 </p>
<p>고수준 모듈은 저수준 모듈에 의존하면 안 된다.</p>
<p>둘 다 추상화에 의존해야된다</p>
<p>라는 의미이다</p>
<p>다시 코드로 돌아가서 본다면</p>
<pre><code class="language-java">private final  joinTheMilitary 입대 = new 해군으로입대();</code></pre>
<p>위의 코드는 고수준(입대)이 저수준(해군)을 의존하고 있으므로</p>
<p>DIP원칙을 위반한다</p>
<p>반면에</p>
<pre><code class="language-java">private final PaymentService service;</code></pre>
<p>이 구조에서 Controller는</p>
<p>Service 추상화에 의존하고있다.</p>
<p>그리고 구체적으로 어떤 내용이 들어있는지도 모른다</p>
<p>클래스는 구현체가 무엇인지 알 지 못한다.</p>
<h2 id="왜-dependency-inversion-인가">왜 Dependency <em>Inversion</em> 인가</h2>
<p>DIP(Dependency Inversion Principle) 에서 </p>
<p>실제로 뒤집히는 건 의존성의 방향이다 </p>
<pre><code class="language-java">    private final  joinTheMilitary 입대 = new 해군입대();</code></pre>
<p>이 코드는 </p>
<pre><code class="language-jsx">입대정책  →  해군입대
고수준       저수준</code></pre>
<p>정책이 구현을 의존하고 있다</p>
<p>DIP를 적용하면</p>
<pre><code class="language-java">public class joinTheMilitary {
    private final   입대인터페이스      입대;
                    (인터페이스)     (변수이름)
}
</code></pre>
<pre><code class="language-java">public class 해군_입대_클래스 implements 입대인터페이스{
}</code></pre>
<pre><code class="language-jsx">joinTheMilitary -&gt; 입대인터페이스  &lt;- 해군_입대_클래스
(정책)            (추상화)         (구현)</code></pre>
<p>고수준과 저수준이</p>
<p>모두 추상화를 의존하게 된다.</p>
<p>그래서 <strong>Dependency Inversion</strong>이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 - 3주차(DB, JPA)]]></title>
            <link>https://velog.io/@y-sun010331/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-3%EC%A3%BC%EC%B0%A8DB-JPA</link>
            <guid>https://velog.io/@y-sun010331/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-3%EC%A3%BC%EC%B0%A8DB-JPA</guid>
            <pubDate>Sun, 18 Jan 2026 16:46:11 GMT</pubDate>
            <description><![CDATA[<p>목차 </p>
<ol>
<li>양방향DB / 단방향 DB</li>
<li>왜 JPA에서는 userId 대신 User를 넣어야 할까</li>
<li>테스트 환경 쉽게 구축하는 법 구합니다 </li>
</ol>
<h1 id="1양방향db--단방향-db">1.양방향DB / 단방향 DB</h1>
<p>우리 팀은 여러 개의 노트를 사람이 사고를 확장하듯 서로 연결할 수 있는 기능을 만들고 있다.</p>
<p>각 노트는 독립된 데이터이지만, 필요에 따라 다른 노트와 관계를 맺는다.</p>
<p>내가 노트의 DB를 설계할때,</p>
<p>DB의 양방향과 단방향의 차이점을 깊게 생각하지 않았다,</p>
<p>막연하게 ‘양방향이면 더 편하겠지’ 라고 생각했다</p>
<p>노션에서 관계형 속성을 연결하는 것 처럼</p>
<p>DB나 JPA에서 양방향으로 구현하면 간단할 줄 알았다.</p>
<p>근데 커피챗에서 피치님께서</p>
<p>“양방향은 사용하지 않는다”</p>
<p>라는 말씀을 하셨다.</p>
<p>양방향 DB에게 단점이 있는것은 알았지만 ,</p>
<p>아예 사용이 금지일 정도로 문제가 될 수 있다고 생각해 보지 않았다.</p>
<p>그래서 내가 만들려고 한 구조에 양방향이 정말 필요한지 알아보려한다.</p>
<h2 id="db의-양방향">DB의 양방향</h2>
<p>먼저 내가 알고있는 양방향에 대해 이야기 하자면 다음과 같다</p>
<p>A에서도 B를 볼 수 있고</p>
<p>B에서도 A를 볼 수 있는 관계</p>
<p>하지만 DB 입장에서는 처음부터 <strong>방향 관계</strong> 라는 개념이 <strong>없다</strong></p>
<p>A→ B로 가는 단방향 관계 가 없는데</p>
<p>A를 알면 B의 값을 알 수 있다는 <strong>화살표 개념(→)</strong> 이 없다</p>
<p>왜냐하면</p>
<p>관계형 DB에서 관계는 외래 키(FK)로 표현되고 논리적인 연결만 된다</p>
<p>Sql문에서 FK을 이용해 A↔b을 만들 수 있다</p>
<pre><code class="language-sql">SELECT * FROM A a JOIN B b ON a.b_id = b.ID;
SELECT * FROM B b JOIN A a ON a.b_id = b.ID;</code></pre>
<p><code>외래키 == 양방향</code>  이 성립한다.</p>
<h2 id="jpa의-양방향">JPA의 양방향</h2>
<p>그렇다면 내가 알고 있던 노션의 양방향은 뭘까?</p>
<p>이것은 DB의 개념이 아니라</p>
<p><strong>JPA와 같은 ORM에서</strong> 객체를 다루기 위한 개념에 가깝다</p>
<p>JPA에서 양방향 관계란 DB의 외래 키가 두 개 생긴다는 뜻이 아니다.</p>
<p>단순히 객체 입장에서 </p>
<ul>
<li>A 에서 B를 참조 할 수 있고</li>
<li>B 객체에서도 A를 참조 할 수 있도록</li>
</ul>
<p>필드를 양쪽에 모두 만들어 둔 상태를 말한다.</p>
<ul>
<li>ItemLink<ul>
<li><code>from_id</code> (FK) : 시작 링크 (인덱스)</li>
<li><code>to_id</code> (FK) : 종료 링크</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/y-sun010331/post/4d918c30-2aaa-4451-b64c-e255cd64ad73/image.png" alt=""></p>
<p><code>ItemLink(from_id, to_id)</code> 테이블은</p>
<p>JPA에서 말하는 양방향 관계와는 아무 상관이 없다.</p>
<p>이 테이블은 단순히</p>
<p>“어떤 아이템이 어떤 아이템을 가리킨다”는</p>
<p><strong>방향 있는 링크를 저장하는 역할</strong>만 한다.</p>
<p>DB 입장에서는 이게 끝이다.</p>
<p>중요한건</p>
<p>이 지점에서 JPA 엔티티를 어떻게 설계하느냐에 따라 생긴다</p>
<p><code>item</code>  엔티티에 “연결된 링크 목록(<code>ItemLink</code>)” 을 추가하는 순간</p>
<p>이 구조는 JPA 기준에서 양방향 관계가 된다.</p>
<p>이때부터 객체는 <strong>테이블 처럼</strong></p>
<p><code>Item → ItemLink</code></p>
<p><code>ItemLink → Item</code></p>
<p>서로를 참조할 수 있는 구조를 갖게 된다.</p>
<h2 id="객체-양방향의-위험성">객체 양방향의 위험성</h2>
<h3 id="1-연관관계-주인-문제">1. 연관관계 주인 문제</h3>
<p>JPA에서 양방향 관계를 맺는다고 해서</p>
<p>두 객체가 <strong>동등하게 관계를 관리하는 건 아니다.</strong></p>
<p>DB의 외래 키를 실제로 관리하는 쪽은 항상 한 곳이고,</p>
<p>JPA에서는 이를 <strong>연관관계의 주인</strong>이라고 부른다.</p>
<p>문제는 <strong>객체 입장에서는 양쪽 모두 필드를 가지고 있기 때문에</strong></p>
<p>어디가 진짜 주인인지 쉽게 헷갈린다는 점이다.</p>
<p><code>Item</code>에서 링크를 추가했는데</p>
<p>DB에는 반영되지 않거나,</p>
<p>반대로 <code>ItemLink</code>만 수정했는데</p>
<p>객체 상태가 어긋나는 상황이 생긴다.</p>
<hr>
<h3 id="2-동기화-책임">2. 동기화 책임</h3>
<p>양방향 관계를 사용하면</p>
<p>관계를 추가하거나 삭제할 때</p>
<p><strong>항상 두 객체의 상태를 동시에 맞춰야 한다.</strong></p>
<pre><code>item.getLinks().add(link);
link.setItem(item);</code></pre><p>둘 중 하나라도 빠지면</p>
<p>객체 그래프와 DB 상태가 어긋난다.</p>
<p>이 책임은 프레임워크가 대신 지지 않는다.</p>
<p><strong>전부 개발자의 몫</strong>이다.</p>
<p>처음엔 헬퍼 메서드로 감싸서 해결한 것처럼 보이지만,</p>
<p>엔티티가 커지고 사용 지점이 늘어날수록</p>
<p>이 규칙은 점점 지켜지기 어려워진다.</p>
<p>이외에도</p>
<ol start="3">
<li><p>조회 편의 때문에 모델이 비대해지는 문제</p>
</li>
<li><p>JSON 직렬화와 순환 참조 문제</p>
</li>
</ol>
<p>가 있는데 이해가 안돼서 패스한다.</p>
<h1 id="2-왜-jpa에서는-userid-대신-user를-넣어야-할까">2. 왜 JPA에서는 userId 대신 User를 넣어야 할까</h1>
<p>MySQL에서는 외래 키 컬럼에 <code>user_id</code>  넣으면 끝인데,</p>
<p>왜 스프링(JPA)에서는 굳이 <code>User</code>객체를 넣어야 할까.</p>
<h2 id="mysql-에서의-fk">MySql 에서의 FK</h2>
<p>위에서 서술한 바와 같이 관계에서 방향이 없다</p>
<p>DB는 객체를 모르고 user_id의 의미를 모른다.</p>
<p>DB는 이 값이 저 테이블의 값과 일치하는지만 확인한다.</p>
<h2 id="스프링jpa에서-fk를-직접-쓰지-않는-이유">스프링(JPA)에서 FK를 직접 쓰지 않는 이유</h2>
<h3 id="jpa는-테이블-매핑-도구가-아니다">JPA는 테이블 매핑 도구가 아니다</h3>
<p>JPA에서 엔티티는</p>
<p>테이블을 그대로 옮겨 놓은 구조물이 아니라,</p>
<p>도메인 모델이다.</p>
<p>도메인 모델에서 중요한 건</p>
<p>컬럼 값보다 <strong>객체 사이의 관계</strong>다.</p>
<p>예를 들어서 우리  Linking 시스템중 folder 클래스에서</p>
<pre><code class="language-java">class folder{
    Long userId;// DB 관점
}</code></pre>
<p>이렇게 id로 관계를 표현하면</p>
<p>아이템 과 유저 관계가 표현되지 않는다</p>
<p>그래서 JPA는</p>
<p>관계를 id가 아니라 <strong>객체 참조로 표현</strong>하게 만든다.</p>
<pre><code class="language-java">class folder {

@ManyToOne
    User user;
}
</code></pre>
<p>한명의 <strong>유저</strong>는 여러개의 <strong>폴더</strong>를 가질 수 있다</p>
<p>이번 주차에는</p>
<p>JPA를 단순히 테이블을 자바 객체로 옮기는 도구</p>
<p>로만 이해하고 있었던 내 생각이 틀렸다는 걸 알게 됐다.</p>
<h1 id="로컬-테스트와-배포환경-문제">로컬 테스트와 배포환경 문제</h1>
<p>로컬에서 잘 돌아가던 코드가 배포만 하면 깨지는 경우가 있다.</p>
<p>그래서 나는 배포하기 전마다</p>
<p>application.yml을 직접 수정하고,</p>
<p>테스트가 끝나면 다시 원래 상태로 되돌리는 방식으로 대응하고 있었다.</p>
<p>수정하는 내용은 대략 이렇다.</p>
<ul>
<li><p>로컬 MySQL 연결 정보</p>
</li>
<li><p>배포용 DB id / password / 주소</p>
</li>
</ul>
<p>당연히 번거롭고,</p>
<p>무엇보다 실수하기 쉬운 방식이다.</p>
<p>특히 이거 되돌리는거 까먹고 git push 입력했을때 매우 귀찮아진다</p>
<p>아래 글을 참고해서 Test환경을 구축하려 했다</p>
<p><a href="https://tecoble.techcourse.co.kr/post/2020-09-21-application-properties/">https://tecoble.techcourse.co.kr/post/2020-09-21-application-properties/</a></p>
<p>Test 코드에서 프로파일을 어떻게 쓰는지를 전제로 설명하고 있었는데</p>
<p>나는 테스트 코드에서의 설정 적용 방식을 제대로 이해 하지 못한 상태라서</p>
<p>다른방법을 찾는중이다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 - 2주차(AOP ,CORS)]]></title>
            <link>https://velog.io/@y-sun010331/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-2%EC%A3%BC%EC%B0%A8AOP-CORS</link>
            <guid>https://velog.io/@y-sun010331/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-2%EC%A3%BC%EC%B0%A8AOP-CORS</guid>
            <pubDate>Sun, 11 Jan 2026 16:59:08 GMT</pubDate>
            <description><![CDATA[<h1 id="2주차-느낀점">2주차 느낀점</h1>
<p>나 정말 뭘 해야 되지...?
모르겠다.</p>
<p>그래도 일단 공부하려고 컴퓨터를 키면
그때부터 유튜브와의 전쟁이 시작된다.
영상 하나 보고
관련 영상 하나 더 보고
그렇게 계속 보고…</p>
<p>정작 머릿속에는 아무것도 남지 않는다.
그 상태로 하루가 끝난다.
돌이켜보니 의지의 문제가 아닌것 같다
무엇을 해야 하는지가 너무 추상적이었다.
AOP 리팩토링 하기 CORS 개념 적용하기 등등</p>
<p>목표를 읽으면 바로 무언가를 해야될지 떠오르지 않는다.
시작 지점이 보이지 않기 때문에 어렵다.</p>
<p>마감이니까 임박해서 허겁지겁 WIL을 쓰고
코드를 추가하는 이유도 사실은 쉬운데 
하기 싫고 어렵다고 생각해서 그렇다.</p>
<p>그러니까 앞으로 할 일들은
구체적으로, 쉽게 만들기 !</p>
<ol>
<li>CORS</li>
<li>AOP</li>
</ol>
<h1 id="1-cors">1. CORS</h1>
<p>프론트 API 요청을 보냈는데 이상한 상황을 겪었다.</p>
<ul>
<li>서버 로그 : 정상</li>
<li>Postman 테스트 : 정상</li>
<li>브라우저 : 요청 실패</li>
</ul>
<p>크롬 브라우저 콘솔에는 빨간색 에러 메시지가 찍혀있다.</p>
<p>CORS<del>!</del>!</p>
<p>처음엔 프론트 엔드가 요청을 잘못 보낸줄 알았다.</p>
<p>하지만 문제는 브라우저 문제였고</p>
<p>백엔드에서 처리 가능한 문제였다</p>
<p>CORS는 흔히 보안 정책처럼 설명된다</p>
<p>정확하게는 <strong>브라우저가 요청을 처리하는 규칙</strong> 이다</p>
<p>핵심을 정리하면 이렇다.</p>
<ul>
<li>서버는 요청을 <strong>받고 응답까지 이미 보낸 상태</strong></li>
<li>브라우저가 응답을 <strong>버릴지 말지 판단</strong></li>
<li>기준은 <strong>응답 헤더에 포함된 CORS 관련 정보</strong></li>
</ul>
<p>이걸 이해하지 못하면 계속 서버 코드만 뒤진다.</p>
<h2 id="왜-postman에서는-되는데-브라우저에서는-안-될까">왜 Postman에서는 되는데 브라우저에서는 안 될까?</h2>
<blockquote>
<p>CORS는 브라우저에만 적용되는 규칙이다.</p>
</blockquote>
<p>Postman은 브라우저가 아니기 때문에</p>
<p>CORS 검사를 하지 않는다.</p>
<h2 id="preflight-요청은-허락을-구하는-과정이다">Preflight 요청은 허락을 구하는 과정이다</h2>
<p>브라우저는 어떤 요청을 보내기 전에 먼저 묻는다.</p>
<blockquote>
<p>“이 요청 보내도 괜찮아?”</p>
</blockquote>
<p>이게 <strong>Preflight 요청</strong>이다.</p>
<p>흐름을 순서대로 정리하면:</p>
<ol>
<li>브라우저가 OPTIONS 요청을 먼저 보낸다</li>
<li>서버가 허용 정책을 응답 헤더로 알려준다</li>
<li>브라우저가 그 내용을 보고 판단한다</li>
<li>허용되면 실제 요청을 보낸다</li>
</ol>
<p>중요한 점은 이것이다.</p>
<blockquote>
<p>서버는 차단하지 않는다.
판단자는 항상 브라우저다.</p>
</blockquote>
<p>서버는 이런 요청은 허용해 라고 알려줄 뿐...</p>
<h1 id="spring-aop로-로그인-체크를-분리하면서-겪은-시행착오-정리">Spring AOP로 로그인 체크를 분리하면서 겪은 시행착오 정리</h1>
<p>로그인 체크는 간단하게 보였다.</p>
<p>세션 하나만 꺼내서 값만 확인하면 끝이다.</p>
<p>문제는 <strong>USER / ADMIN</strong> 으로 갈라지면서 시작됐다</p>
<h2 id="1주차-코드의-실제-문제점">1주차 코드의 실제 문제점</h2>
<h3 id="1-1-requestcontextholder를-무조건-신뢰함">1-1. RequestContextHolder를 무조건 신뢰함</h3>
<pre><code class="language-java">RequestContextHolder.currentRequestAttributes()
</code></pre>
<ul>
<li>웹 요청이 아닐 경우 바로 예외</li>
<li>스케줄러, 테스트, 비동기 작업에서 깨짐<ul>
<li>작업이 깨질때 이게 로그에서 보면 비동기 때문인지 스케줄러 때문인지 알 수가 없음</li>
<li>디버깅 비용 증가</li>
</ul>
</li>
</ul>
<blockquote>
<p>간단하게 RequestContextHolder는 
HTTP 요청을 처리 중이라는 전제 하에 요청 내용을 가져온다.
아니면 그냥 NPE 던짐 → 나 : ??? 넌 어디서 왔니</p>
</blockquote>
<hr>
<h3 id="1-2-세션-구조가-aop에-침투함">1-2. 세션 구조가 AOP에 침투함</h3>
<pre><code class="language-java">getLoginMemberId()
getLoginAdminId()</code></pre>
<p>AOP가 <strong>세션 내부 구조를 알고 있다</strong>는 건 설계적으로 좋지 않다.</p>
<ul>
<li><p>역할이 늘면? → 역할에 맞춰서 추가해되야됨</p>
</li>
<li><p>API 타입이 늘면?</p>
<p>AOP가 계속 수정된다.</p>
</li>
</ul>
<hr>
<h3 id="1-3-문자열-기반-분기">1-3. 문자열 기반 분기</h3>
<p>초기 구현에서는 다음과 같이 분기하고 있었다.</p>
<pre><code class="language-java">switch (loginCheck.type().toString())</code></pre>
<p>당시에는 이 코드가 문제라고 생각하지 않았다.</p>
<p><code>USER</code>, <code>ADMIN</code> 두 값만 비교하면 되니 충분해 보였다.</p>
<p>이 부분이 문제라는 걸 인지하게 된 건,</p>
<p> <code>enum</code> 타입에 대한 설명을 들은 이후였다.</p>
<p><code>@LoginCheck</code>의 <code>type</code>은 단순한 문자열이 아니라</p>
<p><strong>의미를 가진 타입</strong>이라는 점을 그때 처음 제대로 이해했다.</p>
<p><code>enum</code>을 사용한다는 건,</p>
<ul>
<li>허용 가능한 값의 범위를 타입으로 제한하고</li>
<li>잘못된 값은 아예 컴파일 단계에서 차단하며</li>
<li>분기 누락이나 변경을 컴파일러가 추적해준다는 의미였다.</li>
</ul>
<p>하지만 이를 <code>String</code>으로 변환해 비교하는 순간,</p>
<p>이런 장점들은 전부 사라진다.</p>
<p>즉, 이 코드는 동작은 하지만</p>
<p><strong>타입이 제공하던 보호를 스스로 포기한 상태</strong>였다.</p>
<p>이 점을 이해한 뒤에는,</p>
<p>분기 기준을 굳이 문자열로 바꿀 이유가 없다는 게 분명해졌다.</p>
<p>그래서 이후 코드에서는 <code>enum</code> 자체를 기준으로 분기하도록 수정했다.</p>
<pre><code class="language-java">switch (loginCheck.type()) {
case USER -&gt; ...
case ADMIN -&gt; ...
}</code></pre>
<p>이렇게 변경하면서,</p>
<p>로그인 정책의 분기 기준은 다시</p>
<p><strong>컴파일 타임에 검증 가능한 형태</strong>로 돌아왔다.</p>
<p><del>간단하게 정리하면</del></p>
<p><del>나만의 커스텀 단어 오타 채점기</del> </p>
<hr>
<h2 id="2-구조를-다시-잡은-두-번째-버전">2. 구조를 다시 잡은 두 번째 버전</h2>
<p>그래서 코드를 전면 수정했다.</p>
<pre><code class="language-java">@Aspect
@Component
publicclassLoginCheckAspect {

@Around(&quot;@annotation(loginCheck)&quot;)
public ObjectcheckLogin(
            ProceedingJoinPoint pjp,
            LoginCheck loginCheck
    )throws Throwable {

// 1. 웹 요청인지 확인
ServletRequestAttributesattrs=
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

if (attrs ==null) {
thrownewIllegalStateException(&quot;No request context&quot;);
        }

// 2. request 획득
HttpServletRequestrequest= attrs.getRequest();

// 3. 세션 획득 (없으면 null)
HttpSessionsession= request.getSession(false);

if (session ==null) {
thrownewHttpStatusCodeException(
                HttpStatus.UNAUTHORIZED,&quot;No session&quot;) {};
        }

// 4. 타입별 로그인 정보 조회
        String userId;
switch (loginCheck.type()) {
case USER -&gt; userId = SessionUtil.getLoginUserId(session);
case ADMIN -&gt; userId = SessionUtil.getLoginAdminId(session);
default -&gt;thrownewIllegalStateException(&quot;Unknown type&quot;);
        }

if (userId ==null) {
thrownewHttpStatusCodeException(
                HttpStatus.UNAUTHORIZED,&quot;NO_LOGIN&quot;) {};
        }

return pjp.proceed();
    }
}
</code></pre>
<hr>
<h2 id="2-1-이-구조에서-달라진-핵심-포인트">2-1. 이 구조에서 달라진 핵심 포인트</h2>
<h3 id="requestcontextholder는-항상-있다는-가정을-버렸다">RequestContextHolder는 “항상 있다”는 가정을 버렸다</h3>
<ul>
<li>웹 요청이 아닐 경우 즉시 차단</li>
<li>실패 지점을 명확히 함</li>
</ul>
<hr>
<h3 id="어노테이션-값은-자동으로-바인딩된다">어노테이션 값은 자동으로 바인딩된다</h3>
<pre><code class="language-java">@Around(&quot;@annotation(loginCheck)&quot;)
public ObjectcheckLogin(..., LoginCheck loginCheck)</code></pre>
<p>이 <code>loginCheck</code>는:</p>
<ul>
<li>리플렉션(?)으로 직접 꺼낸 게 아니다</li>
<li>Spring AOP가 <strong>자동으로 주입</strong>해준다</li>
</ul>
<blockquote>
<p>리플랙션이 뭐지?</p>
</blockquote>
<hr>
<h3 id="3-그래도-아직-남은-문제">3. 그래도 아직 남은 문제</h3>
<p>솔직히 말하면 이 구조도 완벽하지 않다.</p>
<pre><code class="language-java">getLoginUserId()
getLoginAdminId()</code></pre>
<p>여전히:</p>
<ul>
<li>역할이 세션 구조에 묶여 있고</li>
<li>OAuth나 JWT로 가면 전부 버려야 한다</li>
</ul>
<h3 id="더-나은-방향은">더 나은 방향은?</h3>
<ul>
<li>세션에는 <strong>User 객체 하나</strong></li>
<li>role은 속성으로 관리</li>
<li>AOP는 role만 비교</li>
</ul>
<p>이렇게 가야 한다.</p>
<hr>
<h1 id="정리">정리</h1>
<ul>
<li><p>인증은 단순히 세션이나 토큰 하나로 끝나는 문제가 아니었다.</p>
</li>
<li><p>세션 기반 인증을 이해했다고 생각했지만</p>
<p>  그 위에 JWT가 있고</p>
<p>  그 위에 OAuth가 얹히며</p>
<p>  결국 Spring Security라는 프레임워크로 수렴된다.</p>
</li>
<li><p>인증 로직은 기능이 아니라 <strong>구조의 문제</strong>라는 걸 알게 됐다.</p>
</li>
</ul>
<p>다음 주에 공부할 범위가 자연스럽게 늘어났다.</p>
<p><a href="https://www.youtube.com/watch?v=xsmKOo-sJ3c&amp;list=PLJkjrxxiBSFALedMwcqDw_BPaJ3qqbWeB">https://www.youtube.com/watch?v=xsmKOo-sJ3c&amp;list=PLJkjrxxiBSFALedMwcqDw_BPaJ3qqbWeB</a></p>
<h3 id="다음주-할-일">다음주 할 일</h3>
<blockquote>
<p>Throwable,JWT 에 대한 블로그 딱 1개 찾아보기
Linking에 적용된 역 정규화 테이블 찾고 정규화 해보기
-&gt; join 얼마나 차이나는지 AI한테물어보기
유효성 검사, 전역 예외처리 코드 추가하기</p>
</blockquote>
<p>할 일 구체적으로 정하기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 - 1주차 (CI/CD , Git convention , Session과 AOP)]]></title>
            <link>https://velog.io/@y-sun010331/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@y-sun010331/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 04 Jan 2026 11:19:58 GMT</pubDate>
            <description><![CDATA[<h1 id="개발-1주차">개발 1주차</h1>
<p>근데 기획을 곁들인...</p>
<p>이번 주차에는 확정된 기능만 구현하기로 하고 본격적인 개발에 앞서 협업에 필요한 기본 구조를 먼저 정리했다.
이 과정에서 나는 API 설계와 데이터베이스 설계를 맡아 진행했다.</p>
<ol>
<li>CI/CD?</li>
<li>GitHub 세팅</li>
<li>Session 로그인과 쿠키 로그인
3-1 세션 로그인 AOP로 구현하기</li>
</ol>
<h1 id="1cicd">1.CI/CD</h1>
<ul>
<li>CI : Continuous Integration<ul>
<li>지속적인 통합, 코드 커밋하면 자동으로 충돌/에러 테스트</li>
</ul>
</li>
<li>CD : Continuous Deployment<ul>
<li>자동으로 통합된 코드를 사용자 환경에 자동으로 배포함, 자동 업데이트</li>
</ul>
</li>
</ul>
<p>만약에 쇼핑몰 사이트를 제작하고 있다 생각했을때
검색 기능을 깃허브에 커밋만 하면, 자동으로 사용자 웹 페이지에서 검색 기능을 사용 할 수 있음</p>
<p>지금까지는 수동으로 모든 작업을 하는게 당연하다고 생각했다.
CI/CD 도구를 통해 빌드와 배포 과정을 자동화 한다면 개발 외적인 반복 작업에 사용되는 시간을 줄일 수 있다고 느꼈고 바로 도구들을 찾아봤다</p>
<h2 id="젠킨스--깃허브-액션">젠킨스 &amp; 깃허브 액션</h2>
<p>젠킨스와 깃허브 액션은는 모두 CI/CD 도구지만 서버 관리 방식에서 명확한 차이가 있다.</p>
<p>젠킨스는 사용자가 직접 서버를 구축하고 관리해야 한다.
EC2 인스턴스에 젠킨스를 설치하고 보안 설정/플러그인 관리/에이전트 구성 등을 직접 해야 하므로 초기 설정과 운영 난이도가 높은 편이다. 
대신 파이프라인을 자유롭게 구성할 수 있어 복잡한 배포 로직이나 커스터마이징이 필요한 환경에 적하다.</p>
<p>반면 GitHub Actions는 GitHub에서 제공하는 관리형 CI/CD 서비스다.
별도의 서버를 운영할 필요 없이 리포지토리에 워크플로우 파일만 작성하면 바로 사용할 수 있어 인프라 관리 부담이 거의 없다. 다만 GitHub 생태계에 종속되며, 외부 시스템과의 복잡한 연동이나 세밀한 제어에는 한계가 있다.</p>
<p>정리하면:</p>
<p>젠킨스: 서버 직접 관리 필요, 설정은 어렵지만 높은 자유도와 확장성</p>
<p>GitHub Actions: 서버 관리 불필요, 빠른 도입 가능하지만 GitHub 의존적</p>
<p>이 차이 때문에 우리 프로젝트는 GitHub Actions을 추천받았다</p>
<blockquote>
<p>젠킨스: Self-hosted 방식입니다. 직접 서버(AWS EC2, 온프레미스 등)를 구축하고 젠킨스를 설치해야 합니다. 서버 사양을 자유롭게 정할 수 있지만, 보안 업데이트나 플러그인 관리를 직접 해야 하는 운영 부담이 큽니다.</p>
</blockquote>
<blockquote>
<p>깃허브 액션: SaaS(클라우드) 기반입니다. 깃허브가 인프라를 관리하므로 별도의 설치 없이 .github/workflows 폴더에 설정 파일만 넣으면 바로 실행됩니다. 관리가 매우 편하지만, 깃허브 서비스 상태에 의존하게 됩니다.</p>
</blockquote>
<blockquote>
<p>주영님의 답변
젠킨스는 사용하기 어려운데 깃허브 액션은 사람들이 정의한 action으로 쉽게 설정이 가능하기 때문에
젠킨스보다는 깃허브 액션 사용하는 것을 추천한다.</p>
</blockquote>
<h1 id="2github-세팅">2.Github 세팅</h1>
<h2 id="컨벤션과-템플릿">컨벤션과 템플릿</h2>
<p>팀 프로젝트에서 협업 효율을 높이기 위해 GitHub 컨벤션과 소울치킨님이 주신 템플릿을 연습용 깃에 설정해봤다.</p>
<p>각자 다른 방식으로 커밋 메시지나 이슈를 작성하면 기록이 정리되지 않고 변경 이력을 한눈에 파악하기 어려워진다. 
이를 방지하기 위해 형식을 통일한다.</p>
<p>커밋 컨벤션은 타입과 바디로 구성 되며 기능 추가 버그 수정 문서 수정 등을 구분함으로써 커밋 로그만 보더라도 변경 내용을 빠르게 파악할 수 있다.</p>
<p>컨벤션과 템플릿 설정은 협업 과정에서 발생하는 시간 낭비와 혼선을 줄이기 위한 최소한의 규칙이다.</p>
<h2 id="gitmessagetxt">gitmessage.txt</h2>
<p>.gitmessage.txt 터미널에 출력되는 템플릿이다</p>
<p>.gitmessage.txt 파일을 생성하고</p>
<pre><code>git config  commit.template .gitmessage.txt</code></pre><p>를 입력해 모든 사용자에게 적용시킨다</p>
<h3 id="사용법">사용법</h3>
<ol>
<li>git add . </li>
<li>git commit </li>
<li>vi 모드로 진입하게 되는데 i,a,o를 누르고 형식에 맞게 입력한다</li>
<li>작성을 완료했다면 :wq! 로 commit을 완료할 수 있다
<img src="https://velog.velcdn.com/images/y-sun010331/post/69782026-c740-4cc3-8bdc-3f47516682c6/image.png" alt=""></li>
</ol>
<p>하지만 VS Code를 사용하는 입장에서</p>
<p><img src="https://velog.velcdn.com/images/y-sun010331/post/4b45a189-7394-4283-91c3-431c4154e8f7/image.png" alt=""></p>
<p>이게 더 편하다... 안쓸것 같아서 보류</p>
<p>출처 : <a href="https://sungwookoo.tistory.com/1">https://sungwookoo.tistory.com/1</a>
<a href="https://duektmf34.tistory.com/206">https://duektmf34.tistory.com/206</a></p>
<h1 id="3세션과-쿠키">3.세션과 쿠키</h1>
<p>HTTP는 무상태 특성을 가지기 때문에 요청이 끝나면 이전 요청의 상태를 기억 못한다.
그래서 로그인과 같은 사용자 정보를 저장할 수단이 필요하다.
대표적으로 세션과 쿠키다.</p>
<h3 id="쿠키">쿠키</h3>
<p>쿠키는 클라이언트(브라우저)에 저장되는 정보다.
서버가 응답 시 쿠키를 발급하면, 브라우저는 이후 요청마다 해당 쿠키를 함께 전송한다. 구현이 단순하고 서버 부담이 적다는 장점이 있지만, 클라이언트에 저장되기 때문에 보안에 취약하며 저장할 수 있는 정보의 크기에 제한이 있다.</p>
<h3 id="세션">세션</h3>
<p>세션은 서버에 사용자 정보를 저장하고, 클라이언트에는 해당 세션을 식별하기 위한 세션 ID만 전달하는 방식이다.
민감한 정보를 서버에서 관리할 수 있어 보안 측면에서는 유리하지만, 사용자가 많아질수록 서버 자원을 사용하게 되어 부하가 발생할 수 있다.</p>
<h2 id="세션-처리-순서">세션 처리 순서</h2>
<p>생성과 사용 순서는 다음과 같다</p>
<ol>
<li>로그인 성공시 세션 하나를 만든다 <ul>
<li>암호문 하나를 만들고 사용자 쿠키에 넣어서 전송</li>
<li>사용자 정보는 서버에 저장</li>
</ul>
</li>
<li>사용자가 다른 요청을 할 때 마다 서버는 암호문 값을 확인하고, 세션 저장소에서 검색함</li>
<li>해킹 당해도 암호문만 있으니 실제 사용사의 개인정보를 탈취 불가능</li>
</ol>
<blockquote>
<p>그러면 세션을 실시간으로 탈취하고 요청 보내면 되지 않나?
-&gt; 이게 세션 하이재킹</p>
</blockquote>
<p>쿠키를 사용하면 2번 Key 값에 사용자 정보를 입력한다</p>
<h3 id="연습용세션-생성-코드">(연습용)세션 생성 코드</h3>
<p><strong>HttpSession</strong>의  setAttribute를 통해 암호문과 사용자 정보를 저장한다
아래 코드에서는 LOGIN_MEMBER_ID라는 이름의 Id를 저장</p>
<p>setAttribute </p>
<pre><code class="language-java">public static String setLoginMemberId (
    HttpSession session , String id) {

    session.setAttribute(&quot;LOGIN_MEMBER_ID&quot;, id); 

    }

</code></pre>
<p>저장 결과</p>
<pre><code class="language-json">{
  &quot;LOGIN_MEMBER_ID&quot; : &quot;user_Id&quot;
}</code></pre>
<h3 id="세션-확인-코드">세션 확인 코드</h3>
<pre><code class="language-java">   public static String getLoginMemberId(
   HttpSession session) {
        return (String) session.getAttribute(LOGIN_MEMBER_ID);
    }</code></pre>
<blockquote>
<p>아래부터 잘못된 정보가 매우 많습니다.</p>
</blockquote>
<h1 id="3-1-세션로그인-aop-구현">3-1 세션로그인 AOP 구현</h1>
<p>세션 로그인 검증은 하나의 기능이지만 거의 모든 API에서 반복적으로 필요하다. 
컨트롤러마다 직접 로그인 여부를 확인하는 방법은 코드 중복을 만드므로 AOP를 사용해 로그인 검증을 한번에 처리했다.</p>
<h2 id="커스텀-어노테이션-logincheck">커스텀 어노테이션 (@LoginCheck)</h2>
<p>세션 로그인 검증을 AOP로 분리하더라도
모든 컨트롤러 메서드에 적용하는건 불필요하다
(그럴일은 없겠지만 컨트롤러 어노테이션을 수정할 수 있나?)
아무튼,
필요한 메서드에만 적용하면 되므로 
커스텀 어노테이션<code>@LoginCheck</code>을 만들었다.</p>
<h3 id="retentionretentionpolicyruntime">@Retention(RetentionPolicy.RUNTIME)</h3>
<p>해당 어노테이션은 <strong>런타임 시점까지 유지되도록</strong> 설정 했다</p>
<h3 id="targetelementtypemethod">@Target(ElementType.METHOD)</h3>
<p><strong>컨트롤러 메서드 단위</strong>로 로그인 검증을 적용하기 위해
메서드에만 사용할 수 있도록 제한했다.</p>
<h3 id="public-interface-logincheck">public @interface LoginCheck</h3>
<p>사용할 어노테이션 인터페이스를 추가한다.
<code>@LoginCheck</code> 마커 역할 뿐만 아니라
사용자 권한을 함께 검사할 수 있도록 열거형을 포함해서,
역할 기반 제어 구조로 확장할 수있도록 했다.</p>
<pre><code class="language-java">@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LoginCheck {

    public static enum UserType{
        USER,ADMIN
    }
    UserType type();
}</code></pre>
<h2 id="aop를-빈으로-등록하기-위한-클래스-작성">AOP를 빈으로 등록하기 위한 클래스 작성</h2>
<h3 id="클래스-단위-어노테이션">클래스 단위 어노테이션</h3>
<p>@Aspect : 나 Aspect 부분으로 사용할 로직임 !
@Component : 빈으로 등록하겠음 !</p>
<h3 id="aop-관련-어노테이션">AOP 관련 어노테이션</h3>
<p>Aspect가 적용될 어노테이션의 범위 설정 가능</p>
<p>@Around : 해당 Aspect 어노테이션이 사용되는  <code>ElementType</code>에서 실행 전, 실행 후에 동작하겠다고 명시함.
++ @Before , @After</p>
<h3 id="around-내부-이해">@Around 내부 이해</h3>
<pre><code class="language-java">
@Around(&quot;@annontation(LoginCheck) &amp;&amp; @annontation(loginCheck)&quot;)
</code></pre>
<p>해당 <code>@Around</code> 표현식은 Pointcut 조건과 바인딩을 동시에 수행한다.</p>
<p>먼저</p>
<p><code>@annotation(LoginCheck)</code>는
<code>@LoginCheck</code> 어노테이션이 선언된 메서드만 AOP 적용 대상으로 제한한다 (필터링).</p>
<p>그리고
<code>@annotation(loginCheck)</code>는
해당 메서드에 선언된 <code>@LoginCheck</code> 어노테이션 인스턴스를
loginCheck라는 이름으로 바인딩한다.</p>
<p>이를 통해 AOP 메서드 내부에서 </p>
<pre><code class="language-java">loginCheck.userType()</code></pre>
<p>와 같이 어노테이션에 정의된 값을 직접 사용 가능하다
그래서</p>
<p><code>@annotation(LoginCheck) &amp;&amp; @annotation(loginCheck)</code> 구조를 사용했다.</p>
<p>Pointcut는 조건 대상을 고르고,
바인딩은 어노테이션 내부 값을 사용하기 위해 존재한다
두 <code>@annontation</code>의 역할이 달라 이해할때 헷갈렸다.</p>
<h3 id="proceedingjoinpoint">proceedingJoinPoint</h3>
<p>AOP가 가로챈 실제 메서드 호출 정보를 담고있는 객체다.
실행할지 말지 어떻게 실행할지 결정한다</p>
<p><code>proceedingJoinPoint.getArgs()</code>는
실제 컨트롤러 메서드에 전달될 파라미터 배열을 반환한다.</p>
<pre><code class="language-java">public ResponseEntity&lt;?&gt; changePw(Long userId,..)</code></pre>
<p>위와 같은 메서드를 가로챘다면
<code>getArgs()</code>는 해당 메서드의 인자들을 순서대로 담은 <code>Object[]</code> 를 제공한다</p>
<blockquote>
<p>초기 구현에서는 index = 0으로 사용자 식별자를 주입했기 때문에 @LoginCheck가 적용된 메서드는 첫 번째 파라미터로 userId를 받아야 한다.
다만 이 방식은 메서드 시그니처에 제약을 주므로
이후 파라미터 어노테이션 기반 방식으로 개선할 여지가 있다.</p>
</blockquote>
<pre><code class="language-java">@Aspect
@Component
public class LoginCheckAspect {

    @Around(&quot;@annotation(com.linking.backend.linking_backend.aop.LoginCheck) &amp;&amp; @annotation(loginCheck)&quot;)
    public Object checkLogin(ProceedingJoinPoint proceedingJoinPoint, LoginCheck loginCheck)

    HttpSession session = (HttpSession)((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest().getSession();

    String userId = &quot;&quot;;
    int index = 0;

    switch(loginCheck.type().toString()) {

        case &quot;USER&quot; :
            userId = SessionUtil.getLoginMemberId(session);
            break;

        case &quot;ADMIN&quot; :
            userId = SesstionUil.getLoginAdminId(session);
            break;

    }
    if( userId == null) {
          throw new HttpStatusCodeException(HttpStatus.UNAUTHORIZED, &quot;NO_LOGIN&quot;) {};

    }


    Object[] modifiedArgs = proceedingJoinPoint.getArgs();
    if(proceedingJoinPoint.getArgs()!=null)
        modifiedArgs[index] = userId;
    return proceedingJoinPoint.proceed(modifiedArgs);

}
</code></pre>
<blockquote>
<p>문제점
1.index를 하드 코딩으로 잡았다.
-&gt; 모든 컨트롤러마다 첫번째 인자값을 userId로 받아야됨
수정 방안 : </p>
</blockquote>
<h2 id="메서드에-aop-적용">메서드에 aop 적용</h2>
<p>설계한 <code>@LoginCheck</code> 어노테이션은
로그인 검증이 필요한 메서드에 직접 선언해 사용할 수 있다.</p>
<pre><code class="language-java">@PatchMapping(&quot;password&quot;)
@LoginCheck(type = LoginCheck.UserType.USER)
public ~~~~</code></pre>
<blockquote>
<p><a href="https://devuna.tistory.com/53">https://devuna.tistory.com/53</a>
<a href="https://chb2005.tistory.com/174">https://chb2005.tistory.com/174</a>
<a href="https://deveric.tistory.com/67">https://deveric.tistory.com/67</a>
<a href="https://mynameiskgw.tistory.com/24">https://mynameiskgw.tistory.com/24</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[GDG] 프로젝트 트랙 - 기획코스 ]]></title>
            <link>https://velog.io/@y-sun010331/GDG-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%8A%B8%EB%9E%99-%EA%B8%B0%ED%9A%8D%EC%BD%94%EC%8A%A4</link>
            <guid>https://velog.io/@y-sun010331/GDG-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%8A%B8%EB%9E%99-%EA%B8%B0%ED%9A%8D%EC%BD%94%EC%8A%A4</guid>
            <pubDate>Mon, 29 Dec 2025 14:58:09 GMT</pubDate>
            <description><![CDATA[<h1 id="기획코스">기획코스</h1>
<p><em>기획은 처음이라 ...</em></p>
<p>목차</p>
<ol>
<li>페르소나 만들기</li>
<li>문제 정의</li>
<li>아이디어</li>
</ol>
<h1 id="페르소나-만들기">페르소나 만들기</h1>
<p><img src="https://velog.velcdn.com/images/y-sun010331/post/109908de-1e96-4618-96d2-69d885e1ad37/image.png" alt=""></p>
<p>팀원이 모두 모여 처음으로 만든 페르소나.
페르소나 기획시 다음 내용들을 놓쳐서 여러번 만들었다.</p>
<ul>
<li>페르소나는 포괄적이면 안된다 구체적으로 적을것</li>
<li>결과를 상상하고 만들면 시야가 좁아진다</li>
<li>맥락의 현실성을 고려하자 - 서비스를 활용하는 실제 환경을 반영해야 된다</li>
<li>팀 공유가 중요하다 - 모든 팀원이 같은 사용자를 상상하고 같은 상황을 공민할 때 일관성 있는 서비스가 나온다</li>
</ul>
<p>처음 만든 페르소나엔 결과를 상상하고 만든 인물이다.
그래서 결과만 구체적이고 인물의 특징이나 다른 모습이 드러나지 않았다.
피드백을 받운 이후 폐기됐다</p>
<h3 id="두번째-페르소나">두번째 페르소나</h3>
<p>이후 두번째 페스소나를 기획했다
기획 과정은 다음과 같다</p>
<ol>
<li>각자 페르소나를 5개씩 만들어 온다</li>
<li>20개의 페르소나중 그룹화를 시키고 섞는다</li>
<li>브레인스토밍을 통해 인물을 구체화 시킨다</li>
</ol>
<p>이렇게 2개를 만들었는데, 이 전보다 깊이 있고 보편적인 인물이 나왔다. 
하지만 두개의 방향이 살짝 달라, 폐기를 해야될까 생각중
소울치킨님께 도움을 받아 살리기로 결정했다.</p>
<p>이번 도움으로 팀이 얻은 것은 공감하기와 문제 정의다.
1단계인 공감하기를 최대치로 끌어 올린 다음 문제정의를 해주시니 기획의 방향성이 보였다.
-&gt; 공감하기가 정말 중요하다고 느꼈다. 앞이 무너지면 왜 뒤에가 무너지는지 체감했음</p>
<p> 두 페르소나의 근본적인 문제를 다음과 같이 정의했다</p>
<h3 id="문제정의">문제정의</h3>
<p>5why 기법을 활용하여 페르소나에서 근본적인 문제를 정의했으며 다음과 같다</p>
<ul>
<li>지속적인 성장을 갈망하지만 즉각적인 쾌락(도파민) 앞에 실행력을 잃어버린 사용자들이, 압도적인 정보량속 수집한 정보를 가치 있는 경험으로 전환하지 못해 느끼는 무력감을 어떻게 해결할 것인가</li>
</ul>
<p>** 왜 페르소나 단계에서 문제 정의 단계로 진행이 안될까? **
예시 페르소나랑 우리랑 차이가 뭘까를 생각해보면 다음과 같다</p>
<ul>
<li>선행 단계가 완수되지 않았지만 다음 단계로 넘어갔다 <strong>(중요)</strong></li>
<li>두개의 페르소나 방향이 달랐다 / 메인을 먼저 잡았어야 했다</li>
<li>페르소나 공유가 제대로 안된거 같다(아마?)</li>
</ul>
<h3 id="최종-페르소나">최종 페르소나</h3>
<p><strong>김생산(28세 / IT업무 종사 / 입사 1년 차)</strong></p>
<p>김생산 씨는 오늘 아침도 출근길 2호선에 올라타서 스마트폰을 켠다.</p>
<p>인스타 오른쪽 위에 떠 있는 숫자들… 확인해보니 친구한테서 받은 디엠이다.</p>
<p>화면을 눌러 들어가보니 ‘26년 서울 청약예정 22곳 총정리’라는 피드가 와있다.</p>
<p>아침부터 프레시한 기분을 잃고 싶지 않아 ‘나중에 봐야지’라고 생각하며 유튜브를 킨다.</p>
<p>헬스에 관심이 많은 김생산 씨의 유튜브 홈 화면에는 운동 자세 영상들로 가득했다.</p>
<p>김생산 씨는 퇴근 후 들릴 헬스장에 가서 보기 위해서 ‘사람들이 자주 실수하는 벤치 프레스 자세 7가지’를 ‘나중에 볼 영상’에 저장해둔다. </p>
<p>회사 톡방에 부장님이 ‘20대 직장인 부동산에 빠져라’라는 책을 추천해줘서 책 제목을 나와의 톡방에 저장해둔다.</p>
<p>점심 시간 때 ‘애교뿜뿜 무지’한테서 ‘마포구 오늘도 달려요 💨💨’ 오픈채팅방에서 2026 새해 일출런에 대한 링크를 공유받았다.
퇴근 후 집에 가서 일정을 살피고 신청하기 위해 링크를 카카오톡 나에게 보낸다.</p>
<p>퇴근 후 집에 와보니, 친구한테 받은 청약 DM과, 부장님이 추천한 책이 각각 인스타그램 저장됨과 카카오톡 나와의 채팅에 있어서 부동산 관련한 정보들을 다시 찾아보기에 불편했다.</p>
<blockquote>
<p>아… 어디에 저장했지..? 기억이 안나네.. 청약 관련 정보였던 거 같은데…</p>
</blockquote>
<p>운동 가방을 들고 헬스장에 도착해 벤치프레스를 하기 전 미리 저장해둔 영상을 보려고 유튜브 저장 목록에 들어간다.</p>
<p>” 흑백요리사 몰아보기 말고.. 룩북 말고.. 아바타3 리뷰 말고… 아까 벤치프레스 영상이랑… 아 자리 뺏겼다 하… 아니 엊그제 저장한 거 같은데 어디에 해뒀더라? 😢 “</p>
<p>결국 기구도 빼앗기고 우왕좌앙 운동하다 귀가한다.</p>
<p>귀가 후 잠시 쉬다보니 벌써 자정이 지났다.</p>
<p>링크만 보내둔 새해 일출런은 이미 모집이 마감되어서 춘식이와 네오가 일출런에 대한 이야기를 할 때 입맛을 다시는 수 밖에 없었다.</p>
<h2 id="아이디어">아이디어</h2>
<p>명확해진 문제를 바탕으로 아이디어를 제출했다
이제서야 어떻게 개발하지? 에 대한 생각을 할 수 있었고 
각자 8개씩 가져오기로 했다.
맨 처음 3~4개는 보편적이지만 이후 가면 갈수록 말이 안될것 이란 조언과 오히려 그런 과정에서 괜찮은 아이디어가 나온다는 조언을 듣고 막 던졌다.</p>
<p>문제가 생겼다
아이디어만으로 와이어 프레임까지 만들지 못했다.
발표 전날까지 아이디어를 정하는 중 이였다.</p>
<p>지금 생각해 보면 다음과 같은 이유 때문인것 같다</p>
<ul>
<li>제시한 아이디어를 읽고 서로 다른 생각을 했다</li>
<li><blockquote>
<p>주요 컨셉이 있지만 구체적이지 않아 다른 생각의 여지가 있었다</p>
</blockquote>
</li>
<li><blockquote>
<p>다음부터는 아이디어가 뽑히면, 즉시 간단한 와이어 프레임을 그리는게 좋을것 같다</p>
</blockquote>
</li>
</ul>
<p>&quot;도파민 중독자&quot;
에 대한 해결책을 3가지로 분류했다</p>
<ol>
<li>도파민을 강제로 제거한다</li>
<li>다른 재미있는 것을 주입한다</li>
<li>재미 없는걸 재미있게 만든다</li>
</ol>
<p>내가 생각하기에
1번은 스크린 타임 제어로 해결하고
2번과 3번은 게이미피케이션을 활용하는데 현실을 반영해주는
** 아바타 ** 가 있으면 정신을 차릴수 있을것 같다 느꼈다</p>
<p><a href="https://www.youtube.com/watch?v=DllyHf4jWLA">https://www.youtube.com/watch?v=DllyHf4jWLA</a>
(내용은 버튜버 와이프지만 이를 활용해 교수님이나 친구 , 혹은 자신의 얼굴을 넣어 이용하면 동기부여가 될것같다 생각했다 하지만 구현상의 문제로 넘어갔음 )</p>
<h3 id="아이디어-정리">아이디어 정리</h3>
<ol>
<li><p><strong>링크 추가</strong></p>
<ol>
<li>태그(카테고리) 분류</li>
<li>메모, 제목, 중요도, 일정 (데드라인) 추가</li>
</ol>
</li>
<li><p><strong>링크 검색</strong></p>
<p> 저장해둔 링크를 검색해서 쉽게 다시 보고, 메모할 수 있다.</p>
</li>
<li><p><strong>사용자 통계(프로필 페이지)</strong></p>
<ol>
<li>수집 링크 확인 완료율<ol>
<li>당일 저장 링크 확인률</li>
</ol>
</li>
<li>스트릭<ol>
<li>링크 추가</li>
<li>링크 완료<ol>
<li>기준 필요(체크 or 읽음)</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
<li><p><strong>캘린더</strong></p>
<ol>
<li>데드라인 알림(D-7, D-3, D-1, D-day)</li>
<li>날짜별 마감 링크를 모아 볼 수 있음</li>
</ol>
</li>
</ol>
<h2 id="정리">정리</h2>
<p>결론: &quot;저장이 문제가 아니라, 탐색과 실행이 문제다&quot;</p>
<p>지난 2주간의 치열한 기획 과정을 통해 우리가 내린 결론은 다음과 같다.</p>
<blockquote>
<p>저장한 정보가 내 삶의 &#39;경험&#39;으로 전환되지 못할 때 무력감을 느낀다</p>
</blockquote>
<p>우리팀은 사용자의 도파민 체계를 생산적인 방향으로 재설계하는 도구를 만들려고 한다</p>
<p>팀원들의 싱크를 잘 맞추는게 중요한거같다</p>
<p><img src="https://velog.velcdn.com/images/y-sun010331/post/966d7598-9e5f-4e98-89ee-5e6454f150ce/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>