<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>seolhxx_.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sat, 17 Jan 2026 06:33:19 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>seolhxx_.log</title>
            <url>https://velog.velcdn.com/images/seolhxx_/profile/638ccdd6-dc3f-4148-b8f0-ffd0615f3c33/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. seolhxx_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/seolhxx_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Rookies 개발 4기][마지막] 프로젝트 발표회 및 수료식 그리고 조금 늦은 후기]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EB%A7%88%EC%A7%80%EB%A7%89-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%9C%ED%91%9C%ED%9A%8C-%EB%B0%8F-%EC%88%98%EB%A3%8C%EC%8B%9D-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%A1%B0%EA%B8%88-%EB%8A%A6%EC%9D%80-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EB%A7%88%EC%A7%80%EB%A7%89-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%9C%ED%91%9C%ED%9A%8C-%EB%B0%8F-%EC%88%98%EB%A3%8C%EC%8B%9D-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%A1%B0%EA%B8%88-%EB%8A%A6%EC%9D%80-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sat, 17 Jan 2026 06:33:19 GMT</pubDate>
            <description><![CDATA[<h1 id="🎓-2026-01-14-🥀🎉">[🎓 2026. 01 .14 🥀🎉]</h1>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/7522b804-0ef4-4f84-8cb4-367833e71537/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/00e8a71f-e6fd-4673-b730-5f58ade0e4f5/image.JPG" alt=""></p>
<h1 id="sk-shieldus-rookies-4기-수료식--파이널-프로젝트-발표회-3개월의-대장정을-마치며-🎓">[SK Shieldus Rookies 4기] 수료식 &amp; 파이널 프로젝트 발표회: 3개월의 대장정을 마치며 🎓</h1>
<blockquote>
<p><strong>&quot;끝이 아닌 새로운 시작&quot;</strong>
치열했던 3개월, 그리고 2관왕(프로젝트 우수상 &amp; 블로그 우수상)의 영예와 함께한 마지막 이야기</p>
</blockquote>
<hr>
<h2 id="📅-d-day-결전의-날">📅 D-Day: 결전의 날</h2>
<p>드디어 그날이 왔습니다. 3개월 동안 동고동락하며 갈고닦았던 프로젝트를 세상에 내보이는 <strong>최종 발표회</strong> 날입니다.
아침 일찍 강의장에 도착해 발표 준비를 하면서, 지난 시간들이 주마등처럼 스쳐 지나갔습니다.</p>
<h3 id="📝-final-schedule">📝 Final Schedule</h3>
<p>제출된 일정표대로 다들 분주하게 움직였습니다.</p>
<ul>
<li><strong>09:00 - 10:20</strong>: 일정 안내 및 노트북 반납, 리허설 준비</li>
<li><strong>10:20 - 10:50</strong>: <strong>대망의 3조 &quot;PetLog&quot; 발표</strong> (저희 팀이 첫 타자라 긴장감이 두 배였습니다!)</li>
<li>... (이후 타 조 발표 및 점심시간) ...</li>
<li><strong>16:30 - 17:00</strong>: 수료식 및 시상</li>
</ul>
<hr>
<h2 id="🎤-3조-petlog-발표-우리의-노력이-빛을-발하다">🎤 3조 &#39;PetLog&#39; 발표: 우리의 노력이 빛을 발하다</h2>
<p>무대에 올라 &quot;반려동물 AI 다이어리 &amp; 리캡 서비스, PetLog&quot;를 소개했습니다.
단순히 기능을 나열하는 것이 아니라, 우리가 왜 이 서비스를 만들었는지, <strong>&#39;기록&#39;을 넘어 &#39;추억&#39;을 선물하고 싶었던 우리의 진심</strong>을 전달하려 노력했습니다.</p>
<ul>
<li><strong>MSA 아키텍처</strong>를 적용하며 겪었던 시행착오들</li>
<li><strong>PostGIS</strong>와 <strong>Kafka</strong>를 활용해 기술적 깊이를 더했던 도전들</li>
</ul>
<p>발표가 끝나고 내려오는 길, 후련함과 발표를 했던 팀원이 너무 잘 마쳐서 뿌듯하였고 발표 준비를 위해 고생한 것을 알기에 정말 칭찬해주고 싶었습니다. </p>
<hr>
<h2 id="🏆-2관왕-달성">🏆 2관왕 달성!</h2>
<h3 id="🥇-프로젝트-우수상-team">🥇 프로젝트 우수상 (Team)</h3>
<p><strong>&quot;우수상, 3조 PetLog!&quot;</strong>
MSA라는 낯선 환경에서 각자의 마이크로서비스(User, Record, Social 등)를 연결하느라 고군분투했던 밤들이 헛되지 않았음을 증명받은 기분이었습니다. 서로 부족한 점을 채워주고 이끌어주었던 우리 3조 팀워크가 만들어낸 값진 결과였습니다.</p>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/324852d6-9e90-40e6-8ede-6f95a19e3677/image.png" alt=""></p>
<h3 id="✍️-블로그-우수상-individual">✍️ 블로그 우수상 (Individual)</h3>
<p>그리고 또 하나의 놀라운 선물, <strong>개인 블로그 우수상</strong>까지 받게 되었습니다.
그저 매일 배운 것을 잊지 않기 위해, 그리고 프로젝트를 진행하며 마주친 문제들과 해결 과정을 제 나름의 방식대로 <strong>&#39;기록&#39;</strong>해두고 싶었을 뿐입니다.
그런데 그 꾸준함과 진정성을 알아봐 주시고 큰 상을 주셔서 너무나 감격스러웠습니다. <strong>&quot;기록은 기억을 지배한다&quot;</strong>는 말이 현실이 된 순간이었습니다.
<img src="https://velog.velcdn.com/images/seolhxx_/post/d8a589fa-06ed-4d70-a74a-3dceca26b6ac/image.jpg" alt=""></p>
<hr>
<h2 id="🙏-special-thanks-감사의-마음을-전하며">🙏 Special Thanks: 감사의 마음을 전하며</h2>
<p>이 모든 영광은 저 혼자만의 것이 절대 아닙니다. 3개월이라는 짧고도 긴 시간 동안, 제가 &#39;개발자&#39;라는 꿈을 향해 단단히 걸어갈 수 있도록 이끌어주신 분들께 감사의 인사를 전하고 싶습니다.</p>
<ul>
<li><strong>강사님</strong>: 막히는 코드로 답답해할 때마다 등대처럼 길을 비춰주시고, 단순히 &#39;되는 코드&#39;가 아니라 &#39;좋은 코드&#39;가 무엇인지 고민하게 해주셔서 감사합니다.</li>
<li><strong>CTO님</strong>: 현업의 시각에서 날카롭지만 뼈가 되고 살이 되는 조언들을 아끼지 않으셨던 덕분에, 학생 수준을 넘어 서비스다운 서비스를 고민할 수 있었습니다.</li>
<li><strong>멘토님</strong>: 바쁘신 와중에도 코드 리뷰와 피드백으로 프로젝트의 완성도를 높여주시고, 따뜻한 응원을 보내주셔서 큰 힘이 되었습니다.</li>
<li><strong>FT님들</strong>: 교육생들이 오직 학습에만 집중할 수 있도록 보이지 않는 곳에서 묵묵히 지원해주신 덕분에 무사히 수료할 수 있었습니다.</li>
</ul>
<p>그리고 무엇보다, <strong>우리 3조 팀원들.</strong>
부족한 나를 믿고 함께해 줘서, 힘들 때 서로 웃게 해 줘서 정말 고맙고 좋은 사람들을 만나 행복하게 프로젝트를 진행할 수 있었던 것 같습니다. 이후에도 계속 만나자고 약속했는데 서로 일정이 바쁘면 만나기가 힘들 수도 있지만 각자의 자리에서 멋진 개발자가 되어 다시 만날 거라 믿습니다.</p>
<hr>
<h2 id="🚀-epilogue-이제-진짜-시작이다">🚀 Epilogue: 이제 진짜 시작이다</h2>
<p>수료증을 받아 들고 나오는 길,
SK Shieldus Rookies 4기 과정은 끝났지만, 저의 개발 인생은 이제 막 <strong>Chapter 2</strong>를 열었습니다.</p>
<p>PetLog를 만들며 배웠던 기술, 협업의 가치, 그리고 끝까지 해냈다는 성취감을 연료 삼아
앞으로 어떤 어려움이 닥쳐도 즐겁게 코딩하는 개발자가 되겠습니다.</p>
<hr>
<h1 id="🧸-팀원들과-마지막-회식">🧸 팀원들과 마지막 회식</h1>
<p>이렇게 수료식이 끝난 후, 
팀원들과 교육과정에서의 마지막 날이기 때문에 함께 회식을 하였다.</p>
<p>무한리필 고기집을 가서 함께 밥을 먹었다.
<img src="https://velog.velcdn.com/images/seolhxx_/post/c82e7f61-79ef-452d-a9ec-a144f2461cfa/image.JPG" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/b3499a73-e02d-47fa-8579-b2d0bdfc7b73/image.JPG" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/f5286c5e-fbd2-4b26-9862-f6fc8679779b/image.jpg" alt=""></p>
<p>그리고 교육과정이 끝날 때쯤 팀원 중 한명이 &#39;티츄&#39;라는 보드게임을 사와서 함께 했는데 너무 재미있어서 아예 보드게임 카페를 끝으로 회식을 하였다.
보드게임은 역시 사람이 좀 많아야 더 재미가 있는 것 같다.
무슨 게임을 할까 고민하다가 직원분의 추천을 받고 두가지 게임을 하였다.
첫번째 게임은 이름이 잘 기억이 나지 않는데 
이야기꾼이 그림을 한장 뽑아 키워드나 문장을 말해 어떤 그림인지 이야기 하고 나머지 사람들은 그와 비슷한 그림을 내려 놓고 사진처럼 내려놓은 그림을 섞은 뒤 내려 놓고 투표를 하는 것이다.
이것도 인원이 여러 명이라 다양한 그림들이 나오게 되어서 재미있었다. 
<img src="https://velog.velcdn.com/images/seolhxx_/post/d35102ef-c521-4a59-b69d-7c4f9ccd57c4/image.jpg" alt=""></p>
<p>두번째는 &#39;노터치크라켄&#39;이라는 게임이다.
이 게임은 두명의 스파이와 4명의 시민들이 있는데 시민들은 보물상자 6개를 모두 찾아야하고 스파이는 크라켄이라는 카드가 있는데 그것을 뽑게 만드는 것이다. 
그래서 서로 속고 속이는 게임이여서 추리도 하게 되고 카드 뽑는 것에 긴장감이 생기기도 해서 정말 재밌는 게임이였다.
총 세시간을 하였는데 이 게임만 두시간을 넘게 하였다.
<img src="https://velog.velcdn.com/images/seolhxx_/post/b3924deb-8d02-4321-9182-1038eb30c512/image.JPG" alt=""></p>
<p>이렇게 보드게임카페에서 놀고 밤 11시가 넘어서 집을 가게 되었는데 
원래는 더 일찍 가겠지라고 했는데 막상 집에 갈려니 너무 아쉬웠다.
다들 집 방향이 너무 다르고 멀어서 더 놀 수도 없고 자주 만날 수도 없을 것 같아서 뭔가 더 슬펐던 것 같다.</p>
<p>팀원들에게 나를 편하게 생각해줬으면 해서 장난도 많이 치고 많이 시끄럽게 한 것 같아서 팀원들이 어떻게 생각할지는 모르겠지만,  그만큼 팀원들이 너무 좋고 편했던 것 같다.
다들 장난에도 잘 받아주고 서로 배려도 많이 해줘서 트러블없이 프로젝트를 잘 마무리하였고, 끝으로도 좋게 헤어진 것 같아서 다행이다.
나중에 또 만날 거라고 약속했는데 그때 만나면 살짝 낯가릴 수도 있을 것 같긴 한데 너무 반가울 것 같다. 다들 좋은 곳에서 건강하게 잘 지내다가 만났으면 좋겠다.
안녕👋☺️❣️</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][발표준비]문서작업, PPT, 발표 대본, 시연영상, 깃허브 README 작성]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EB%B0%9C%ED%91%9C%EC%A4%80%EB%B9%84%EB%AC%B8%EC%84%9C%EC%9E%91%EC%97%85-PPT-%EB%B0%9C%ED%91%9C-%EB%8C%80%EB%B3%B8-%EC%8B%9C%EC%97%B0%EC%98%81%EC%83%81-%EA%B9%83%ED%97%88%EB%B8%8C-README-%EC%9E%91%EC%84%B1</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EB%B0%9C%ED%91%9C%EC%A4%80%EB%B9%84%EB%AC%B8%EC%84%9C%EC%9E%91%EC%97%85-PPT-%EB%B0%9C%ED%91%9C-%EB%8C%80%EB%B3%B8-%EC%8B%9C%EC%97%B0%EC%98%81%EC%83%81-%EA%B9%83%ED%97%88%EB%B8%8C-README-%EC%9E%91%EC%84%B1</guid>
            <pubDate>Sat, 17 Jan 2026 06:31:30 GMT</pubDate>
            <description><![CDATA[<h1 id="📑-2026-01-08--13-📋">[📑 2026. 01 .08 ~ 13 📋]</h1>
<h1 id="rookies-4기-기록의-가치를-높이다-ai-반려동물-다이어리--리캡-서비스-petlog-개발기">[Rookies 4기] 기록의 가치를 높이다, AI 반려동물 다이어리 &amp; 리캡 서비스 &#39;PetLog&#39; 개발기</h1>
<blockquote>
<p>SK Shieldus Rookies 4기 프로젝트 최종 산출물 회고 </p>
</blockquote>
<p>약 3개월간의 대장정 끝에 드디어 <strong>반려동물 AI 다이어리 &amp; 리캡 서비스, PetLog</strong>의 최종 산출물을 제출하게 되었습니다.</p>
<p>수행계획서부터 WBS 일정 관리, 회의록, 아키텍처 설계(ADR), ERD, 그리고 최종 시연 영상까지... 
숨 가쁘게 달려온 개발 과정을 돌아보며, 우리가 만든 서비스가 어떤 기술적 고민을 담고 있는지, 그리고 어떤 가치를 사용자에게 전달하고자 했는지 정리해보려 합니다.</p>
<hr>
<h2 id="1-프로젝트-개요-기록의-번거로움은-덜고-추억의-가치는-더하다">1. 프로젝트 개요: &quot;기록의 번거로움은 덜고, 추억의 가치는 더하다&quot;</h2>
<p>반려동물과 함께하는 시간은 매 순간이 소중하지만, 매일 일기를 쓰고 사진을 정리하는 것은 생각보다 번거로운 일입니다. 
저희는 <strong>&quot;사용자가 기록의 번거로움에서 벗어나, 추억을 감상하는 즐거움에만 집중할 수 있다면 어떨까?&quot;</strong> 라는 질문에서 시작했습니다.</p>
<p><strong>PetLog</strong>는 AI 기술을 활용해 사진 한 장으로 특별한 일기를 생성하고, 쌓인 기록을 월간 리포트로 재구성해주는 서비스입니다.</p>
<h3 id="💡-core-values">💡 Core Values</h3>
<ol>
<li><strong>Automated Recording</strong>: 사진 분석을 통한 일기 자동 생성 &amp; 위치/날씨 자동 기록</li>
<li><strong>Emotional Connection</strong>: 반려동물의 시점에서 쓰여진 따뜻한 문체</li>
<li><strong>Visual Immersion</strong>: 3D 인터랙션과 다양한 템플릿을 통한 시각적 즐거움</li>
<li><strong>Gamification</strong>: 펫코인 리워드 시스템으로 지속적인 기록 동기 부여</li>
</ol>
<hr>
<h2 id="2-주요-기능-key-features">2. 주요 기능 (Key Features)</h2>
<h3 id="🐶-1-ai-다이어리-ai-diary">🐶 1. AI 다이어리 (AI Diary)</h3>
<p>사진을 업로드하면 AI가 상황을 분석하여 <strong>반려동물의 시점</strong>에서 귀여운 일기를 써줍니다.</p>
<ul>
<li><strong>올인원 편집</strong>: AI가 생성한 제목, 내용, 기분 태그를 자유롭게 수정할 수 있습니다.</li>
<li><strong>자동 환경 기록</strong>: <strong>PostGIS</strong>를 활용해 사용자가 선택한 위치를 DB에 저장하고, 해당 위치와 시간의 과거 기상 데이터를 API로 연동해 <strong>날씨와 위치 정보</strong>를 자동으로 기록합니다.</li>
<li><strong>커스텀 디자인</strong>: 갤러리 레이아웃, 이미지 크기, 12가지 프리셋 테마, 글꼴 등 다양한 편집 기능을 제공합니다.</li>
<li><strong>3D 대시보드</strong>: 작성한 일기는 입체감 있는 <strong>3D 캐러셀</strong>과 포토 갤러리를 통해 몰입감 있게 감상할 수 있습니다.</li>
<li><strong>리워드</strong>: 일기 저장 시 15코인, 피드 공유 시 10코인이 적립되며, 이는 꾸준한 기록을 위한 강력한 동기부여가 됩니다.</li>
</ul>
<h3 id="📅-2-ai-월간-리캡-ai-monthly-recap">📅 2. AI 월간 리캡 (AI Monthly Recap)</h3>
<p>한 달간의 기록을 모아 AI가 <strong>&#39;요약 리포트&#39;</strong>와 맞춤형 <strong>&#39;제목&#39;</strong>을 생성해줍니다.</p>
<ul>
<li><strong>이달의 하이라이트</strong>: 전체 기록 중 가장 의미 있는 순간 3가지를 AI가 선정하여 보여줍니다.</li>
<li><strong>자동/수동 생성</strong>: 서버 스케줄러가 매월 1일 자동으로 리캡을 생성해주며, 사용자가 원하는 기간을 설정해 수동으로 생성할 수도 있습니다.</li>
<li><strong>다이내믹 템플릿</strong>: 일기 수에 따라 최적화된 디자인을 제공합니다.<ul>
<li><strong>1~2장</strong>: 사진 집중형 <strong>책(Book) 템플릿</strong></li>
<li><strong>3~6장</strong>: 감성적인 <strong>스크랩북(Scrapbook) 템플릿</strong></li>
<li><strong>7장 이상</strong>: 많은 사진을 한눈에 볼 수 있는 <strong>목록형 슬라이드 템플릿</strong></li>
</ul>
</li>
</ul>
<h3 id="🌌-3-3d-인터랙션-포트폴리오">🌌 3. 3D 인터랙션 포트폴리오</h3>
<p>단순한 2D 리스트를 넘어, 추억이 담긴 우주 공간을 탐험하듯 <strong>드래그, 회전, 확대</strong>하며 기록을 감상할 수 있습니다. 폴라로이드 감성의 추억 앨범 탭도 함께 제공하여 사용자 경험(UX)을 극대화했습니다.</p>
<hr>
<h2 id="3-기술-아키텍처-technical-architecture">3. 기술 아키텍처 (Technical Architecture)</h2>
<p>저희 서비스는 데이터의 무결성과 확장성을 고려하여 <strong>MSA(Microservices Architecture)</strong> 기반으로 설계되었습니다.</p>
<h3 id="🏗️-1-hybrid-database-strategy">🏗️ 1. Hybrid Database Strategy</h3>
<p>데이터의 특성에 따라 최적의 DB를 선택하여 사용했습니다.</p>
<ul>
<li><strong>PostgreSQL</strong>: 사용자 정보, 결제 내역 등 정형 데이터의 무결성 보장.</li>
<li><strong>MongoDB</strong>: 이미지 메타데이터, 로그성 데이터 등 비정형 데이터의 유연한 처리.</li>
<li><strong>PostGIS</strong>: 반려동물의 산책 경로 및 실시간 위치 기반 데이터를 정교하게 처리하기 위한 공간 데이터베이스 활용.</li>
</ul>
<h3 id="⚡-2-event-driven-architecture-with-kafka">⚡ 2. Event-Driven Architecture with Kafka</h3>
<p>서비스 간 결합도를 낮추고 비동기 처리를 위해 <strong>Kafka</strong>를 도입했습니다.</p>
<ul>
<li>예를 들어, 헬스케어 서비스에서 활동 데이터가 수집되면 Kafka 이벤트를 발행하고, 다이어리 서비스가 이를 구독하여 자동으로 일기 소재로 활용하는 식의 유기적인 연동을 구현했습니다.</li>
</ul>
<h3 id="🚀-3-cicd--cloud">🚀 3. CI/CD &amp; Cloud</h3>
<ul>
<li><strong>Infrastructure</strong>: [3조]최종인프라구성도와 [3조]클라우드사용계획서에 기반하여 AWS 클라우드 환경을 구축했습니다.</li>
<li><strong>DevOps</strong>: Github Actions를 활용한 CI/CD 파이프라인을 구성하여 배포 자동화를 구현했습니다.</li>
</ul>
<hr>
<h2 id="4-프로젝트-산출물-및-개발-프로세스-development-process--deliverables">4. 프로젝트 산출물 및 개발 프로세스 (Development Process &amp; Deliverables)</h2>
<p>PetLog는 단순한 구현을 넘어, 엔터프라이즈급 개발 프로세스를 준수하며 체계적으로 진행되었습니다.</p>
<h3 id="📝-1-기획-및-일정-관리-planning">📝 1. 기획 및 일정 관리 (Planning)</h3>
<ul>
<li><p><strong>수행계획서 &amp; 스프린트 계획</strong>: User, Record, Social, Healthcare, PetMate 등 각 마이크로서비스별로 상세한 <strong>Agile Sprint 계획서</strong>를 수립하여 체계적으로 일감을 관리했습니다.
<img src="https://velog.velcdn.com/images/seolhxx_/post/ddc6e8b9-2ab0-4304-badf-7469b1659570/image.png" alt=""></p>
</li>
<li><p><strong>WBS (Work Breakdown Structure)</strong>: 전체 프로젝트 일정을 세분화하고, 진행 상황을 지속적으로 추적/업데이트하여 납기 준수(Time Management)에 집중했습니다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/a4e87174-165d-4da1-b6ec-1f1eab835f58/image.png" alt=""></p>
<h3 id="📐-2-아키텍처-및-설계-design--architecture">📐 2. 아키텍처 및 설계 (Design &amp; Architecture)</h3>
<ul>
<li><strong>아키텍처 정의서 (ADR)</strong>: MSA 도입 배경, DB 선정 이유(Hybrid DB), 트랜잭션 관리 전략 등을 문서화하여 기술적 의사결정의 근거를 남겼습니다.</li>
<li><strong>ERD &amp; 인프라 구성도</strong>: 데이터 모델링과 클라우드 인프라 구조를 시각화하여 팀원 간의 이해도를 높이고 최적화된 설계를 도출했습니다.</li>
</ul>
<h3 id="🗣-3-협업-및-소통-communication">🗣 3. 협업 및 소통 (Communication)</h3>
<ul>
<li><strong>정기 회의록</strong>: 매주 이슈와 진행 상황을 공유하고 기록으로 남겨, 커뮤니케이션 미스를 방지하고 히스토리를 관리했습니다.</li>
</ul>
<h3 id="🎬-4-최종-산출물-final-deliverables">🎬 4. 최종 산출물 (Final Deliverables)</h3>
<ul>
<li><p><strong>시연 영상</strong>: 실제 서비스 흐름을 한눈에 볼 수 있는 최종 시연 영상을 제작하여 서비스의 완성도를 증명했습니다.
<img src="https://velog.velcdn.com/images/seolhxx_/post/59a331f2-6bdb-4ea6-8b8e-b61d7e409292/image.png" alt=""></p>
</li>
<li><p><strong>발표 자료</strong>: 문제 정의부터 해결 방안, 기술적 챌린지까지 프로젝트의 전 과정을 논리적으로 정리했습니다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/8fa3426a-ebc1-4ecb-a555-a9893b119868/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/3784d1fb-bb94-41b2-a35f-db8e0e47065f/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/b02a74ac-7a3c-4b9b-b3e2-cdaf2d3853f0/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/713669a8-b4f7-4d74-adcc-19df3fcca4bc/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/7138e611-c640-4d50-b0cd-127df24b0391/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/1800897a-0f98-440f-8f02-96c009081cae/image.png" alt=""></p>
<hr>
<h2 id="5-트러블-슈팅-및-알게-된-점-retrospective">5. 트러블 슈팅 및 알게 된 점 (Retrospective)</h2>
<ul>
<li><strong>대용량 이미지 처리</strong>: 3D 캐러셀 구현 시 다수의 고화질 이미지를 로딩하며 발생한 성능 이슈를 썸네일 리사이징과 Lazy Loading으로 해결했습니다.</li>
<li><strong>AI 프롬프트 엔지니어링</strong>: 반려동물의 말투를 자연스럽게 구현하기 위해 LLM 프롬프트를 수십 차례 수정하며 최적의 페르소나를 찾았습니다.</li>
<li><strong>PostGIS 좌표계 변환</strong>: 지도 API와 DB 간 좌표계 차이로 인한 오차를 해결하며 공간 데이터 처리에 대한 이해도를 높였습니다.</li>
</ul>
<hr>
<h2 id="5-마치며-wrap-up">5. 마치며 (Wrap-up)</h2>
<p>작성한 내용에 대해선 제가 개발한 내용인 &#39;recordsevice&#39;에 대한 내용만 담겨져있지만 프로젝트에는 제가 담당한 <strong>Record Service</strong>를 포함해 User, Social, Healthcare, PetMate 등 <strong>총 6개의 마이크로서비스</strong>가 유기적으로 연결되어 &#39;PetLog&#39;라는 하나의 완성된 플랫폼을 이루고 있습니다.</p>
<h3 id="💡-msa-그-막막함에서-확신으로">💡 &quot;MSA, 그 막막함에서 확신으로&quot;</h3>
<p>처음 프로젝트를 시작할 때만 해도 &quot;MSA가 도대체 무엇인가?&quot;, &quot;어떻게 나누는 것이 MSA다운 것인가?&quot;에 대한 고민을 많이 했습니다. 낯선 기술 용어와 복잡한 아키텍처에 압도되기도 했지만, 팀원들과 함께 레퍼런스를 찾아보고 서로 알려주며 공부했던 그 시간들이 있었기에 끝까지 완주할 수 있었습니다.</p>
<h3 id="🤝-3개월간의-동행">🤝 &quot;3개월간의 동행&quot;</h3>
<p>짧다면 짧고 길다면 긴 3개월 동안, 부족한 점이 많았음에도 늘 옆에서 든든하게 지지해준 우리 3조 팀원들에게 진심으로 감사를 전하고 싶습니다. 단순한 &#39;기록&#39; 기능을 넘어 <strong>&#39;추억&#39;이라는 감성적 가치를 기술로 풀어내기 위해</strong> 함께 치열하게 고민했던 시간들은 앞으로의 개발 인생에 있어 무엇과도 바꿀 수 없는 큰 자산이 될 것입니다.</p>
<p>비록 Rookies 과정은 이렇게 마무리되지만, 우리가 함께 만든 PetLog의 가능성과 그 안에서 배운 협업의 가치는 영원히 남을 것입니다. 고생한 우리 팀원들, 모두 수고 많으셨습니다! 👏</p>
<hr>
<p><strong>🔗 Links</strong></p>
<ul>
<li><a href="https://github.com/orgs/skRookies3team/repositories">GitHub Repository (Link)</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발]41. [BE] 백엔드 마지막 code refactoring. [FE] UI/UX 리테마(AI리캡, 리캡 상세, 포토갤러리, 로딩화면) / 버그 수정(3D 캐러셀, 빌드 오류) / 다이어리 스타일 편집 옵션 추가 및 공유하기 UI 수정 ]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C41.-BE-%EB%B0%B1%EC%97%94%EB%93%9C-%EB%A7%88%EC%A7%80%EB%A7%89-code-refactoring.-FE-UIUX-%EB%A6%AC%ED%85%8C%EB%A7%88AI%EB%A6%AC%EC%BA%A1-%EB%A6%AC%EC%BA%A1-%EC%83%81%EC%84%B8-%ED%8F%AC%ED%86%A0%EA%B0%A4%EB%9F%AC%EB%A6%AC-%EB%A1%9C%EB%94%A9%ED%99%94%EB%A9%B4-%EB%B2%84%EA%B7%B8-%EC%88%98%EC%A0%953D-%EC%BA%90%EB%9F%AC%EC%85%80-%EB%B9%8C%EB%93%9C-%EC%98%A4%EB%A5%98-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%8A%A4%ED%83%80%EC%9D%BC-%ED%8E%B8%EC%A7%91-%EC%98%B5%EC%85%98-%EC%B6%94%EA%B0%80-%EB%B0%8F-%EA%B3%B5%EC%9C%A0%ED%95%98%EA%B8%B0-UI-%EC%88%98%EC%A0%95</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C41.-BE-%EB%B0%B1%EC%97%94%EB%93%9C-%EB%A7%88%EC%A7%80%EB%A7%89-code-refactoring.-FE-UIUX-%EB%A6%AC%ED%85%8C%EB%A7%88AI%EB%A6%AC%EC%BA%A1-%EB%A6%AC%EC%BA%A1-%EC%83%81%EC%84%B8-%ED%8F%AC%ED%86%A0%EA%B0%A4%EB%9F%AC%EB%A6%AC-%EB%A1%9C%EB%94%A9%ED%99%94%EB%A9%B4-%EB%B2%84%EA%B7%B8-%EC%88%98%EC%A0%953D-%EC%BA%90%EB%9F%AC%EC%85%80-%EB%B9%8C%EB%93%9C-%EC%98%A4%EB%A5%98-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%8A%A4%ED%83%80%EC%9D%BC-%ED%8E%B8%EC%A7%91-%EC%98%B5%EC%85%98-%EC%B6%94%EA%B0%80-%EB%B0%8F-%EA%B3%B5%EC%9C%A0%ED%95%98%EA%B8%B0-UI-%EC%88%98%EC%A0%95</guid>
            <pubDate>Sat, 17 Jan 2026 06:02:45 GMT</pubDate>
            <description><![CDATA[<h1 id="♥️-오늘의-개발-2026-01-07-♥️">[♥️ 오늘의 개발 2026. 01 .07 ♥️]</h1>
<h1 id="감성sensibility을-기술하다-레트로-노트-테마--3d-갤러리-최적화">감성(Sensibility)을 기술하다: 레트로 노트 테마 &amp; 3D 갤러리 최적화</h1>
<blockquote>
<p><strong>요약</strong>: 차가운 AI 분석 결과에 따뜻한 아날로그 감성을 입히기 위한 &#39;레트로 노트&#39; 테마 구현 과정과, 3D 포토 갤러리의 성능과 UX를 동시에 잡은 최적화 경험을 공유합니다.</p>
</blockquote>
<hr>
<h2 id="1-개요-기능-넘어-경험으로">1. 개요: 기능 넘어 &#39;경험&#39;으로</h2>
<p>기능이 &#39;작동&#39;하는 단계를 넘어 사용자가 서비스에 &#39;몰입&#39;하게 만들기 위해서는 섬세한 UX 설계가 필요합니다.
우리는 이번 업데이트에서 사용자의 추억을 담는 그릇인 다이어리와 리캡(Recap) UI를 전면 개편했습니다. 핵심 키워드는 <strong>&quot;레트로 노트(Retro Notebook)&quot;</strong>입니다.</p>
<p>단순히 이미지만 바꾸는 것이 아니라, 폰트 시스템부터 3D 인터랙션까지 전반적인 사용자 경험(UX)을 아날로그 감성에 맞춰 튜닝했습니다.</p>
<hr>
<h2 id="2-frontend-아날로그-감성의-기술적-구현">2. Frontend: 아날로그 감성의 기술적 구현</h2>
<h3 id="21-레트로-노트retro-notebook-테마-시스템">2.1. 레트로 노트(Retro Notebook) 테마 시스템</h3>
<p>사용자가 기록을 남길 때 &quot;진짜 노트에 쓰는 기분&quot;을 느끼게 하는 것이 목표였습니다. 이를 위해 컴포넌트 단위의 디자인 시스템을 구축했습니다.</p>
<ul>
<li><strong>Visual Elements</strong>: 스프링 제본(Spine), 모눈종이 배경(Grid), 마스킹 테이프 효과를 CSS로 구현하여 가벼우면서도 리얼한 질감을 표현했습니다.</li>
<li><strong>Dynamic Font System</strong>: 테마의 완성도는 폰트에서 옵니다. 사용자가 선택한 프리셋에 따라 &#39;Jua&#39;, &#39;Gaegu&#39;와 같은 손글씨 웹폰트를 동적으로 로드하고 적용하는 로직을 추가했습니다.</li>
<li><strong>Preview-Detail Sync</strong>: 다이어리 생성 단계(StyleStep)의 미리보기와 생성 완료 후 상세 페이지(Detail) 간의 괴리감을 없애기 위해, 뷰 컴포넌트를 공통화하여 <strong>WYSIWYG(What You See Is What You Get)</strong> 경험을 제공했습니다.</li>
</ul>
<pre><code class="language-typescript">// [Style Preset Logic Draft]
// 프리셋 선택 시 관련 폰트와 배경 스타일을 일괄 적용하여
// 사용자에게 즉각적인 시각적 피드백 제공
const applyPreset = (presetId) =&gt; {
  const theme = THEME_MAP[presetId];
  setFont(theme.fontFamily);
  setBackground(theme.paperTexture);
  // ...
};</code></pre>
<blockquote>
<h2 id="다이어리-스타일-편집">다이어리 스타일 편집</h2>
</blockquote>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/777b41b4-1a3f-4c2a-aefe-bda5dcd7414a/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/a3ff70c7-68e1-4f3a-9e97-28a642cd7b28/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/650bfc83-9574-4938-a1a2-b407432f9c5f/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/c7406e08-1bed-44db-9da8-fe430f1d8162/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/3c47a20e-fb55-4f10-be1b-fb3cb1181f29/image.png" alt=""></p>
<blockquote>
<h2 id="소셜-피드-공유-완료-시">소셜 피드 공유 완료 시</h2>
</blockquote>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/cff0d92b-73db-401e-b3d0-7cb40c2e8e87/image.png" alt=""></p>
<h3 id="22-3d-포토-갤러리-최적화-threejs">2.2. 3D 포토 갤러리 최적화 (Three.js)</h3>
<p>추억을 3D 공간에 띄워주는 &#39;포토 갤러리&#39; 기능은 시각적으로 화려하지만, 성능 이슈가 발생하기 쉬운 부분입니다.</p>
<ul>
<li><strong>리소스 제한 (Throttling)</strong>: 무제한으로 사진을 띄울 경우 브라우저 메모리 부족(OOM)이나 프레임 저하가 발생했습니다. 이를 방지하기 위해 <strong>&quot;최대 30장&quot;</strong>으로 렌더링 개수를 제한했습니다.</li>
<li><strong>자동 순환 로직</strong>: 제한된 개수로도 풍성한 경험을 주기 위해, <strong>20초 간격으로 새로운 랜덤 이미지 세트를 fetch</strong>하여 부드럽게 전환(Refresh)되도록 구현했습니다.</li>
<li><strong>UX 디테일</strong>: 사용자가 갤러리를 회전시키며 감상할 때 시선이 분산되지 않도록, 회전 조작 중에는 구체(Sphere)의 위치를 고정(Lock)하여 안정감을 주었습니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/eb10c33b-4cbc-4818-8040-a8ec82828c40/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/778a2092-d717-469c-affd-c3754680da0b/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/96a66fec-2294-4c17-adb9-b3e09c9b6e78/image.png" alt=""></p>
<blockquote>
<h2 id="리캡">리캡</h2>
</blockquote>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/6a0bad1c-dd39-4b6b-9309-62ec0338f8ec/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/2cc471df-3300-4a63-a286-703b8f1b6313/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/f143b743-ab57-4a1b-9b3c-952a14648886/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/2a6525b6-1741-493e-912c-b3b70b1d25d4/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/eef69cf6-6a98-414f-a838-cef8efad594b/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/159b186d-462e-40a3-a054-43df32fb877d/image.png" alt=""></p>
<hr>
<h2 id="3-backend-감성을-뒷받침하는-데이터-로직">3. Backend: 감성을 뒷받침하는 데이터 로직</h2>
<h3 id="31-랜덤-추억-추출-알고리즘">3.1. 랜덤 추억 추출 알고리즘</h3>
<p>&quot;과거의 오늘&quot;이나 &quot;이달의 베스트&quot;를 보여줄 때 항상 같은 사진만 나온다면 감동이 덜할 것입니다.
리캡 서비스에서 대표 이미지를 선정할 때, 단순 최신순이 아닌 <strong>&quot;무작위성(Randomness)&quot;</strong>을 부여하여 매번 새로운 추억을 발견하는 즐거움을 주었습니다.</p>
<pre><code class="language-java">// [Recap Image Extraction]
// 전체 이미지 풀에서 최대 20장을 무작위로 샘플링
// 단순 Shuffle보다는 DB 레벨(또는 인메모리)에서 효율적인 랜덤 추출 고려
public List&lt;Image&gt; getRandomImages(Long userId, int limit) {
    // ...
}</code></pre>
<hr>
<h2 id="4-마무리">4. 마무리</h2>
<p>이번 스프린트는 기술적인 난이도보다 <strong>&quot;디테일한 완성도&quot;</strong>에 집중한 시간이었습니다.
CORS 문제와 같은 하드웨어적인 이슈를 해결하는 것도 중요하지만, 결국 사용자가 마주하는 화면에서 &quot;예쁘다&quot;, &quot;편하다&quot;라고 느끼게 만드는 것이 프론트엔드 엔지니어링의 정수라고 생각합니다.</p>
<p>레트로한 감성으로 덧입혀진 이번 업데이트가 사용자들에게 더 따뜻한 기록 경험이 되기를 바랍니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발]40. [BE][REFACTOR] Record 서비스 가독성 개선 및 도메인 지식 문서화 표준 , [FE] 리캡 UI 개선, 캘린더 네비게이션 및 피드 공유 간소화, [fix] 포토갤러리 S3 이미지 CORS 문제 해결, 다이어리 및 리캡 UI 수정과 빌드/캐러셀 오류 수정]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C40.-BEREFACTOR-Record-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B0%80%EB%8F%85%EC%84%B1-%EA%B0%9C%EC%84%A0-%EB%B0%8F-%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%A7%80%EC%8B%9D-%EB%AC%B8%EC%84%9C%ED%99%94-%ED%91%9C%EC%A4%80-FE-%EB%A6%AC%EC%BA%A1-UI-%EA%B0%9C%EC%84%A0-%EC%BA%98%EB%A6%B0%EB%8D%94-%EB%84%A4%EB%B9%84%EA%B2%8C%EC%9D%B4%EC%85%98-%EB%B0%8F-%ED%94%BC%EB%93%9C-%EA%B3%B5%EC%9C%A0-%EA%B0%84%EC%86%8C%ED%99%94-fix-%ED%8F%AC%ED%86%A0%EA%B0%A4%EB%9F%AC%EB%A6%AC-S3-%EC%9D%B4%EB%AF%B8%EC%A7%80-CORS-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EB%B0%8F-%EB%A6%AC%EC%BA%A1-UI-%EC%88%98%EC%A0%95%EA%B3%BC-%EB%B9%8C%EB%93%9C%EC%BA%90%EB%9F%AC%EC%85%80-%EC%98%A4%EB%A5%98-%EC%88%98%EC%A0%95</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C40.-BEREFACTOR-Record-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B0%80%EB%8F%85%EC%84%B1-%EA%B0%9C%EC%84%A0-%EB%B0%8F-%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%A7%80%EC%8B%9D-%EB%AC%B8%EC%84%9C%ED%99%94-%ED%91%9C%EC%A4%80-FE-%EB%A6%AC%EC%BA%A1-UI-%EA%B0%9C%EC%84%A0-%EC%BA%98%EB%A6%B0%EB%8D%94-%EB%84%A4%EB%B9%84%EA%B2%8C%EC%9D%B4%EC%85%98-%EB%B0%8F-%ED%94%BC%EB%93%9C-%EA%B3%B5%EC%9C%A0-%EA%B0%84%EC%86%8C%ED%99%94-fix-%ED%8F%AC%ED%86%A0%EA%B0%A4%EB%9F%AC%EB%A6%AC-S3-%EC%9D%B4%EB%AF%B8%EC%A7%80-CORS-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EB%B0%8F-%EB%A6%AC%EC%BA%A1-UI-%EC%88%98%EC%A0%95%EA%B3%BC-%EB%B9%8C%EB%93%9C%EC%BA%90%EB%9F%AC%EC%85%80-%EC%98%A4%EB%A5%98-%EC%88%98%EC%A0%95</guid>
            <pubDate>Tue, 06 Jan 2026 15:21:11 GMT</pubDate>
            <description><![CDATA[<h1 id="🐈⬛-오늘의-개발-2026-01-06-🐈⬛">[🐈‍⬛ 오늘의 개발 2026. 01 .06 🐈‍⬛]</h1>
<h1 id="코드가-곧-문서가-되게-하라-record-서비스-리팩토링--트러블슈팅-로그">코드가 곧 문서가 되게 하라: Record 서비스 리팩토링 &amp; 트러블슈팅 로그</h1>
<blockquote>
<p><strong>요약</strong>: Record 서비스의 복잡한 비즈니스 로직(AI 분석, 위치 데이터 처리, 멀티 DB)을 코드 레벨에서 문서화하여 유지보수성을 극대화한 경험과, 프론트엔드 3D 렌더링 시 발생한 S3 CORS 문제 해결 과정을 공유합니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/b8bc8710-b9c8-4735-ac1c-a6d0c18720c7/image.png" alt=""></p>
<hr>
<h2 id="1-배경-background">1. 배경 (Background)</h2>
<p>프로젝트가 고도화되면서 <code>Record</code> 서비스(다이어리 및 리캡)는 단순한 CRUD를 넘어 <strong>AI 분석, GIS(공간 정보) 처리, 대용량 트래픽 제어, 3D 시각화</strong> 등 다양한 기술적 요구사항을 포함하게 되었습니다.</p>
<p>기능은 구현되었으나, 다음과 같은 문제들이 부채로 쌓이기 시작했습니다.</p>
<ol>
<li><strong>복잡도 증가</strong>: AI 프롬프트 엔지니어링 의도나 좌표 변환 알고리즘 같은 핵심 로직이 코드만으로는 한눈에 파악하기 힘들어짐.</li>
<li><strong>지식의 파편화</strong>: 특정 개발자만 아는 도메인 지식(Context)이 늘어남.</li>
<li><strong>외부 연동 이슈</strong>: 기상청 API, S3, Milvus 등 외부 시스템과의 연동 포인트에서 예기치 않은 트러블슈팅 소요 발생.</li>
</ol>
<p>우리는 이번 스프린트에서 <strong>&quot;코드가 곧 문서가 되는 환경(Documentation as Code)&quot;</strong>을 목표로 백엔드 전 계층을 리팩토링하고, 프론트엔드의 크리티컬한 이슈를 해결했습니다.</p>
<hr>
<h2 id="2-backend-refactoring-가독성-개선-및-지식의-내재화">2. Backend Refactoring: 가독성 개선 및 지식의 내재화</h2>
<h3 id="21-복잡한-오케스트레이션의-가시화-service-layer">2.1. 복잡한 오케스트레이션의 가시화 (Service Layer)</h3>
<p><code>DiaryServiceImpl</code>와 <code>RecapServiceImpl</code>는 단순한 비즈니스 로직 수행자가 아닌, 여러 컴포넌트를 지휘하는 <strong>지휘자(Orchestrator)</strong> 역할을 합니다.</p>
<p>우리는 AI 분석부터 저장, 이벤트 발행까지 이어지는 긴 호흡의 로직을 주석(Javadoc)으로 명확히 시각화했습니다.</p>
<blockquote>
<p><strong>Before</strong>: 코드가 길어지며 전체 흐름 파악이 어려움.
<strong>After</strong>: 단계별 주석을 통해 프로세스를 명확히 정의.</p>
</blockquote>
<pre><code class="language-java">// [Flow Overview]
// 1. AI 분석 수행 (프롬프트 전략: 페르소나 적용)
// 2. 좌표 기반 위치 정보 복원 (Kakao API)
// 3. Polyglot 저장 (RDB: 메타데이터 / Mongo: 상세 분석 / Milvus: 벡터)
// 4. 비동기 이벤트 발행 (Eventual Consistency)</code></pre>
<p>특히 <code>DiaryAiService</code> 내의 <strong>프롬프트 엔지니어링 의도</strong>를 코드에 상세히 기록했습니다. 단순한 문자열 조합이 아니라, <strong>&quot;왜 이 페르소나를 설정했는지&quot;, &quot;오감 묘사를 위해 어떤 파라미터를 튜닝했는지&quot;</strong>를 명시하여 후임자가 프롬프트를 수정할 때 맥락을 이해할 수 있도록 했습니다.</p>
<h3 id="22-공간-데이터gis와-기술적-난제-해결">2.2. 공간 데이터(GIS)와 기술적 난제 해결</h3>
<p>공간 데이터를 다룰 때 가장 큰 실수는 <strong>좌표계(CRS) 혼동</strong>과 <strong>매핑 순서(X, Y)</strong> 오류입니다.</p>
<ul>
<li><strong>PostGIS &amp; JTS</strong>: 표준인 SRID 4326(WGS84)을 준수하되, JTS 라이브러리 사용 시 <code>(Lng, Lat)</code> 순서(X, Y 순)를 지켜야 함을 명시했습니다.</li>
<li><strong>좌표 변환 알고리즘 (Blackbox 제거)</strong>: 기상청 API는 위경도가 아닌 격자 좌표(X, Y)를 요구합니다. 이를 변환하는 &#39;램버트 정각 원추 투영법&#39; 수식을 알기 쉽게 풀어서 주석화하여, 복잡한 수학 공식이 블랙박스로 남지 않도록 했습니다.</li>
</ul>
<h3 id="23-polyglot-persistence--data-modeling">2.3. Polyglot Persistence &amp; Data Modeling</h3>
<p>RDB(PostgreSQL)와 NoSQL(MongoDB)을 혼용하는 아키텍처에서 가장 중요한 것은 <strong>데이터 일관성</strong>과 <strong>참조 무결성</strong>입니다.</p>
<ul>
<li><strong>식별자 매핑</strong>: JPA 엔티티와 MongoDB 등큐먼트 간의 ID 매핑 전략을 명시했습니다.</li>
<li><strong>DTO 분리 전략</strong>: 네트워크 트래픽 최적화를 위해 목록 조회용 <code>SimpleDTO</code>와 상세 조회용 <code>DetailDTO</code>를 철저히 분리하고, 각 필드의 제외 이유(Cost 절감 등)를 기록했습니다.</li>
</ul>
<h3 id="24-스케줄링과-비동기-처리-전략">2.4. 스케줄링과 비동기 처리 전략</h3>
<ul>
<li><strong>RecapScheduler</strong>: 배치 작업의 주요 단계(조회 → 검증 → 변환)를 명시하고, 운영 환경에 적합한 Cron 표현식 정책을 가이드로 남겼습니다.</li>
<li><strong>Event Handling</strong>: 트랜잭션 종료 후 실행되어야 하는 로직(예: 알림 발송)을 위해 <code>@TransactionalEventListener</code>를 적용하고, 이를 통해 <strong>결과적 일관성(Eventual Consistency)</strong>을 어떻게 보장하는지 설명했습니다.</li>
</ul>
<hr>
<h2 id="3-frontend-troubleshooting-3d-갤러리-이미지-로드-이슈">3. Frontend Troubleshooting: 3D 갤러리 이미지 로드 이슈</h2>
<h3 id="31-문제-상황-problem">3.1. 문제 상황 (Problem)</h3>
<p><code>Three.js</code>의 <code>TextureLoader</code>를 사용하여 AWS S3에 저장된 이미지를 3D 객체의 텍스처로 입히는 과정에서 이미지가 로드되지 않는 현상이 발생했습니다.
브라우저 콘솔에는 악명 높은 <strong>CORS(Cross-Origin Resource Sharing)</strong> 에러가 출력되었습니다.</p>
<h3 id="32-원인-분석-root-cause">3.2. 원인 분석 (Root Cause)</h3>
<p>일반적인 <code>img</code> 태그와 달리, WebGL 컨텍스트(Three.js 등)에서 리소스를 로드할 때는 브라우저가 보안을 위해 <strong>Preflight Request (OPTIONS 메서드 호출)</strong>를 먼저 보냅니다.</p>
<p>확인 결과, AWS S3 버킷의 CORS 설정에 <code>GET</code> 메서드만 허용되어 있었고, <code>OPTIONS</code> 메서드에 대한 허용 설정이 누락되어 있어 Preflight 요청이 <code>403 Forbidden</code>으로 거부되고 있었습니다.</p>
<h3 id="33-해결-solution">3.3. 해결 (Solution)</h3>
<p>S3 버킷 권한 설정에서 CORS 구성을 다음과 같이 수정하여 <code>OPTIONS</code> 메서드를 명시적으로 허용했습니다.</p>
<pre><code class="language-json">[
    {
        &quot;AllowedHeaders&quot;: [&quot;*&quot;],
        &quot;AllowedMethods&quot;: [&quot;GET&quot;, &quot;HEAD&quot;, &quot;OPTIONS&quot;], // OPTIONS 추가
        &quot;AllowedOrigins&quot;: [&quot;*&quot;], // 운영 환경에서는 구체적 도메인으로 제한 권장
        &quot;ExposeHeaders&quot;: []
    }
]</code></pre>
<p>설정 변경 후 <code>TextureLoader</code>가 정상적으로 이미지를 받아와 3D 갤러리 렌더링에 성공했습니다.</p>
<hr>
<h2 id="4-마치며-conclusion">4. 마치며 (Conclusion)</h2>
<p>이번 작업은 눈에 보이는 기능을 추가하는 것만큼이나, <strong>&quot;보이지 않는 코드의 품질&quot;</strong>을 높이는 것이 얼마나 중요한지 깨닫는 과정이었습니다.</p>
<ul>
<li><strong>Backend</strong>: 도메인 지식과 기술적 의사결정을 코드에 내재화하여, 동료(혹은 미래의 나)가 코드를 읽는 것만으로도 전체 맥락을 이해할 수 있게 되었습니다.</li>
<li><strong>Frontend</strong>: 외부 리소스(S3)와의 연동에서 발생하는 CORS 메커니즘을 정확히 이해하고 해결하여 서비스 안정성을 확보했습니다.</li>
</ul>
<p>이러한 문서화와 안정화 작업은 향후 도입될 <strong>시맨틱 검색 고도화</strong>나 <strong>대규모 트래픽 처리</strong>를 위한 든든한 기반이 될 것입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발]39. 과거 날짜 일기 생성 시 위치 기록 복원 및 날씨 정보 연동 로직 고도화, 다이어리 서비스 비즈니스 로직 분리 및 오케스트레이터 구조 도입, AI 일기 생성 로직 고도화 및 멀티모달 프롬프트 최적화, 리캡 생성 알림 및 코인 적립 연동]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C39</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C39</guid>
            <pubDate>Mon, 05 Jan 2026 12:04:51 GMT</pubDate>
            <description><![CDATA[<h1 id="🤍-개발-기록-20260105-🤍">[🤍 개발 기록 2026.01.05 🤍]</h1>
<p>과거 날짜 위치 복원 + 과거 날씨(ASOS) 연동, 리캡/일기 알림·코인 보상까지: “기록 경험”을 완성하는 흐름 만들기</p>
<blockquote>
<p>오늘 작업은 기능을 몇 개 추가한 수준이 아니라,<br><strong>과거 기록을 “그날의 컨텍스트(위치/날씨)”로 복원</strong>하고,<br>생성 완료 후 <strong>알림과 보상(코인)</strong> 으로 사용자 경험을 닫는(Closing) 작업이었다.<br>(Front: 위치 전송 분기/타임아웃/알림 라우팅, Back: 위치 복원/ASOS/서비스 분리/알림/코인)</p>
</blockquote>
<hr>
<h2 id="1-문제-정의-과거-일기인데-현재-위치가-박히는-문제">1. 문제 정의: “과거 일기인데 현재 위치가 박히는 문제”</h2>
<p>AI 일기/미리보기는 사용자가 과거 날짜를 선택해도, 클라이언트가 현재 GPS를 그대로 전송하면서 <strong>과거 기록에 현재 위치가 덮이는</strong> 문제가 발생할 수 있다.<br>또한 그날의 날씨는 실시간 예보가 아니라 <strong>과거 기상 기록(ASOS)</strong> 을 써야 컨텍스트가 맞는다.</p>
<p>따라서 오늘 목표는 다음 두 가지였다.</p>
<ul>
<li>과거 날짜 일기 작성/생성 시 <strong>해당 날짜의 위치를 DB에서 복원</strong>하고 UI에 반영</li>
<li>날짜에 따라 <strong>실시간 예보 vs 과거 기록(ASOS)</strong> 를 자동 분기해 날씨를 정확히 적용</li>
</ul>
<hr>
<h2 id="2-front-과거-날짜는-gps를-보내지-않는다-위치-복원-허용">2. Front: 과거 날짜는 GPS를 보내지 않는다 (위치 복원 허용)</h2>
<h3 id="21-과거-날짜-gps-전송-로직-수정-diaryuploadpagetsx">2.1 과거 날짜 GPS 전송 로직 수정 (<code>DiaryUploadPage.tsx</code>)</h3>
<p>핵심은 “과거 날짜이면 클라이언트 GPS를 보내지 않고”,<br>백엔드가 DB 기준으로 위치를 찾아 반영할 수 있도록 <strong>요청 모델을 분기</strong>한 것이다.</p>
<ul>
<li>과거 날짜 선택 시 GPS 좌표 전송하지 않도록 변경</li>
<li><code>isPastDate</code> 플래그로 조건부 위치 전송</li>
<li>의도: <strong>클라이언트가 현재 위치로 과거 기록을 오염시키지 않기</strong></li>
</ul>
<hr>
<h3 id="22-백엔드-응답-위치-정보-우선-사용">2.2 백엔드 응답 위치 정보 우선 사용</h3>
<p>사용자가 선택한 날짜가 과거인지 여부에 따라, 프론트에서 최종 위치를 결정하는 로직을 추가했다.</p>
<ul>
<li>과거 날짜: 백엔드가 반환한 <code>latitude</code>, <code>longitude</code>를 우선 사용</li>
<li>오늘 날짜: GPS 위치를 우선 사용</li>
<li><code>finalLocationCoords</code>, <code>finalLocationName</code>로 UI 반영 기준을 단일화</li>
</ul>
<blockquote>
<p>결과적으로 UI는 “오늘은 GPS”, “과거는 DB 복원”이라는 정책이 명확해지고,
화면/지도/날씨 로직이 한 기준으로 움직이게 된다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/f08edc10-0398-4136-8f5e-cd0e1464d730/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/61d54106-b43e-488b-824d-a2f0dec7e065/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/3ee598ad-56bd-4c99-9d89-36ff24582494/image.png" alt=""></p>
<hr>
<h3 id="23-전역-타임아웃-증가-http-clientts">2.3 전역 타임아웃 증가 (<code>http-client.ts</code>)</h3>
<p>AI 일기 생성, 리캡 생성 등은 요청 시간이 길 수 있어 기본 10초 타임아웃에서 끊기는 문제가 있었다.</p>
<ul>
<li>전역 HTTP timeout: <strong>10초 → 60초</strong></li>
<li>목적: <code>ECONNABORTED</code> 같은 타임아웃 에러 방지</li>
</ul>
<hr>
<h3 id="24-디버깅-로그-추가">2.4 디버깅 로그 추가</h3>
<p>날짜 분기와 좌표 적용은 오류가 나면 체감이 큰 영역이라, 판단 근거를 로그로 남겼다.</p>
<ul>
<li>과거/오늘 날짜 구분 로그</li>
<li>GPS 전송 여부 로그</li>
<li>최종 위치 정보 로그</li>
</ul>
<hr>
<h2 id="3-back-과거-날짜면-db-위치를-최우선--과거-날씨asos-자동-연동">3. Back: “과거 날짜면 DB 위치를 최우선” + 과거 날씨(ASOS) 자동 연동</h2>
<h3 id="31-위치-복원-로직-고도화-diaryserviceimpl">3.1 위치 복원 로직 고도화 (<code>DiaryServiceImpl</code>)</h3>
<p>백엔드에서 “과거 날짜”일 경우 위치 우선순위를 명확히 정의했다.</p>
<ul>
<li>과거 날짜 우선순위: 오늘 이전이면 <strong>클라이언트 좌표보다 DB(walk_routes)</strong> 를 최우선</li>
<li>폴백: 해당 날짜 기록이 없으면 “오늘 대표 위치”를 조회해 기본값 제공<br>→ 완전히 빈 장소가 되지 않도록 UX를 방어</li>
<li>상태 동기화: 복원된 좌표를 <code>AiDiaryResponse</code>에 포함해 프론트 UI가 즉시 갱신 가능</li>
<li>주소 보정: DB에서 좌표를 복원한 경우 Kakao API로 실제 주소명을 자동 설정(행정동(H) 우선)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/7053d8d1-bdcc-4f1e-9330-fac58b46ff0d/image.png" alt=""></p>
<hr>
<h3 id="32-날씨-로직-통합-externalapiservice">3.2 날씨 로직 통합 (<code>ExternalApiService</code>)</h3>
<p>날씨는 “날짜”에 따라 호출 API가 달라야 한다.</p>
<ul>
<li><code>getWeatherInfo</code>에서 날짜 기준으로 자동 분기:<ul>
<li>오늘/미래: 실시간 예보(초단기예보)</li>
<li>과거: <strong>ASOS 일자료(과거 기상 기록)</strong></li>
</ul>
</li>
<li>좌표 기준 가장 가까운 관측소를 찾아 실제 기록을 가져오도록 구성</li>
</ul>
<blockquote>
<p>이 방식은 “사진 기반 추정”보다 신뢰성이 높고,
과거 기록을 ‘그날의 날씨’로 맞출 수 있다는 점에서 데이터 품질이 상승한다.</p>
</blockquote>
<hr>
<h3 id="33-데이터-영속성-강화-locationserviceimpl">3.3 데이터 영속성 강화 (<code>LocationServiceImpl</code>)</h3>
<p>과거 위치 복원이 “일회성”이면 재사용 가치가 낮다.<br>그래서 일기 저장 시 해당 날짜/장소를 <code>walk_routes</code>에 기록하도록 보강해,
같은 날짜에 추가 일기를 작성할 때 동일 위치를 공유할 수 있게 했다.</p>
<hr>
<h2 id="4-서비스-구조-개선-diaryserviceimpl을-오케스트레이터로-전환">4. 서비스 구조 개선: DiaryServiceImpl을 오케스트레이터로 전환</h2>
<p><code>DiaryServiceImpl</code>에 집중되어 있던 비대한 로직을 관심사별로 분리하고,<br>상위에서 흐름만 제어하는 오케스트레이터 패턴으로 구조를 정리했다.</p>
<ul>
<li><code>DiaryAiService</code>: AI 분석/프롬프트 실행</li>
<li><code>DiaryVectorService</code>: Milvus 동기화/비동기 저장</li>
<li><code>DiaryMediaService</code>: 이미지 업로드(Archive) + MongoDB 메타데이터</li>
<li><code>ExternalApiService</code>: Kakao 주소 변환 + 날씨 API 분기</li>
</ul>
<blockquote>
<p>효과: 변경이 자주 생기는 영역(외부 API, AI, 미디어)을 분리해서
추후 유지보수와 테스트 작성 난이도를 낮추는 방향으로 정리했다.</p>
</blockquote>
<hr>
<h2 id="5-알림-연동-리캡일기-생성-완료를-사용자에게-닫아주기">5. 알림 연동: 리캡/일기 생성 완료를 사용자에게 “닫아주기”</h2>
<h3 id="51-front-알림-api-연동-및-라우팅-개선-closes-115">5.1 Front: 알림 API 연동 및 라우팅 개선 (Closes #115)</h3>
<ul>
<li><code>createNotification</code> API 함수 추가 및 요청 DTO 정의</li>
<li>일기 저장 완료 시 <code>DIARY</code> 타입 알림 전송 (<code>DiaryStylePage</code>, <code>AiDiaryPage</code>)</li>
<li>알림 클릭 라우팅 개선<ul>
<li><code>DIARY</code> → <code>/diary/{targetId}</code></li>
<li><code>RECAP</code> → <code>/recap/{targetId}</code></li>
</ul>
</li>
<li>불필요한 소셜 피드 공유 알림 제거</li>
</ul>
<blockquote>
<p>핵심은 “알림이 와도 사용자가 어디로 가야 하는지”가 명확해야 한다는 점이었다.<br>캘린더 같은 우회 경로 대신 상세 페이지로 바로 이동하도록 정리했다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/0e047cc0-ee72-4489-965e-0a806e24d56f/image.png" alt=""></p>
<hr>
<h3 id="52-back-리캡-생성-알림-전송실패해도-본-트랜잭션-유지">5.2 Back: 리캡 생성 알림 전송(실패해도 본 트랜잭션 유지)</h3>
<ul>
<li><code>NotificationClient</code>(Feign)로 유저 서비스 알림 API 호출</li>
<li><code>RecapServiceImpl.createAiRecap()</code>에서 자동/수동 생성 모두 알림 지원</li>
<li>try-catch 처리: 알림 실패해도 리캡 생성은 정상 처리</li>
<li>디버깅을 위한 상세 로그 기록 + 중복 알림 제거</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/9152e09c-ad65-4f60-a333-25a48e7d5422/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/36bc602d-b61d-4c1b-886f-3711453c75d7/image.png" alt=""></p>
<hr>
<h2 id="6-코인-보상-리캡-생성-성공-시-30코인-적립">6. 코인 보상: 리캡 생성 성공 시 30코인 적립</h2>
<p>사용자 행동을 “완료 경험”으로 만드는 방법 중 하나가 보상 체계다.<br>리캡 생성은 사용자 입장에서 비용이 큰 행동(기억/정리/회고)이므로 일기보다 보상을 높게 설정했다.</p>
<ul>
<li>리캡 생성 성공 시 <strong>30코인 자동 적립</strong></li>
<li><code>UserClient</code>(Feign)로 유저 서비스 <code>earnCoin</code> 호출</li>
<li>자동 예약 리캡 / 수동 생성 리캡 모두 적용</li>
<li>보상 실패해도 리캡 생성은 정상 처리(try-catch)</li>
<li>알림에 코인 정보 포함(<code>coin: 30L</code>)하여 사용자에게 즉시 피드백 제공</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/28d0a942-db81-48d3-8d08-3f96a0f39267/image.png" alt=""></p>
<hr>
<h2 id="7-테스트-결과-요약">7. 테스트 결과 요약</h2>
<ul>
<li>과거 날짜 선택 → GPS 전송 안 함 확인</li>
<li>오늘 날짜 선택 → GPS 전송 확인</li>
<li>백엔드 응답 위치 정보 정상 반영 확인</li>
<li>과거 위치 존재/미존재 시나리오 UI 확인(없을 때는 AI 추정 장소로 표시 후 수정 유도)</li>
<li>타임아웃 에러 해결 확인(60초)</li>
<li>일기/리캡 생성 후 알림 전송 및 클릭 라우팅(<code>/diary/:id</code>, <code>/recap/:id</code>) 확인</li>
<li>리캡 생성 시 30 코인 적립 및 알림 문구 확인</li>
</ul>
<hr>
<h2 id="8-회고">8. 회고</h2>
<p>오늘 작업에서 가장 크게 느낀 점은 “과거 기록” 기능은 단순히 날짜만 바꾸는 게 아니라, 그날의 컨텍스트(위치/날씨)까지 같이 복원되어야 완성된 경험이 된다는 것이었다. 프론트에서 과거 날짜에 GPS를 보내지 않도록 정책을 분기한 것은 작은 변경처럼 보이지만, 데이터 오염을 막는 핵심 안전장치가 됐다. 백엔드에서 DB 위치를 최우선으로 사용하고 폴백을 두면서, “정확성”과 “사용자 경험” 사이의 균형을 실제로 설계해볼 수 있었다. 특히 날씨는 실시간 예보와 과거 기록(ASOS)을 분기해야 한다는 점에서, 시간 개념이 들어간 외부 API 설계가 생각보다 복잡하다는 걸 배웠다. 서비스 분리(오케스트레이터 패턴) 과정에서는 기능이 늘어날수록 한 클래스에 쌓이는 로직이 유지보수성을 빠르게 무너뜨린다는 걸 체감했고, 책임을 나누는 것 자체가 리팩토링의 핵심임을 다시 확인했다. 알림 연동은 “기능 완료 후 사용자에게 닫아주는 경험”을 만드는 작업이었고, 클릭 라우팅을 직접 상세로 연결하니 알림의 가치가 올라갔다. 코인 보상은 단순한 포인트 지급이 아니라, 사용자의 행동을 서비스 흐름에 정착시키는 장치라는 점을 배우게 됐다. 동시에 알림/코인처럼 외부 서비스에 의존하는 기능은 실패해도 본 트랜잭션을 망치지 않도록 설계해야 한다는 원칙을 다시 적용했다. 전역 타임아웃을 60초로 올리면서 “긴 작업”을 다루는 서비스는 네트워크 정책도 제품의 일부라는 걸 느꼈다. 전체적으로는 기능 하나를 붙이는 것보다, 데이터 정합성·예외 처리·UX 피드백 루프를 함께 닫는 게 더 중요한 작업이라는 걸 경험한 하루였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발][멘토링]5차 마지막 멘토링 + 38. 개발 ing~]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C%EB%A9%98%ED%86%A0%EB%A7%815%EC%B0%A8-%EB%A7%88%EC%A7%80%EB%A7%89-%EB%A9%98%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C%EB%A9%98%ED%86%A0%EB%A7%815%EC%B0%A8-%EB%A7%88%EC%A7%80%EB%A7%89-%EB%A9%98%ED%86%A0%EB%A7%81</guid>
            <pubDate>Fri, 02 Jan 2026 14:38:55 GMT</pubDate>
            <description><![CDATA[<h1 id="🎂-멘토링-기록-20260103-🎂">[🎂 멘토링 기록 2026.01.03 🎂]</h1>
<p>프로젝트 발표/기획 피드백 정리: PPT 구조, 비용·보안, 확장성, 그리고 “왜 Kafka/Bedrock인가”</p>
<hr>
<h2 id="0-멘토링-배경">0. 멘토링 배경</h2>
<p>오늘 멘토링은 <strong>프로젝트 발표 자료(PPT)</strong> 및 <strong>전반적인 기획/설계 방향</strong>을 중심으로 진행되었다.<br>특히 “사용자가 실제로 무엇을 쓰게 되는가”, “기술 선택의 근거가 충분한가(비용/보안/확장성)”, “홍보/사업 계획 관점이 들어가 있는가”가 핵심 피드백이었다.</p>
<hr>
<h2 id="1-ppt스토리텔링-구성-피드백">1. PPT/스토리텔링 구성 피드백</h2>
<h3 id="11-초반-문제의식-탭problem-집중도-개선">1.1 초반 문제의식 탭(Problem) 집중도 개선</h3>
<ul>
<li>현재 초반 탭이 난잡해 <strong>시각적 집중이 떨어짐</strong></li>
<li><strong>핵심 키워드/어텐션 요소(강조 문구, 숫자, 짧은 문장, 시각적 배치)</strong> 를 먼저 배치한 뒤<br>그 다음에 기사/펫로스 관련 언급을 연결해야 전달력이 높아짐</li>
</ul>
<p><strong>정리</strong></p>
<ul>
<li>“문제 → 근거(기사/데이터) → 해결 방향” 순서를 유지하되<br><em>문제 정의 첫 화면은 더 단단하고 단순하게</em> 만들기</li>
</ul>
<hr>
<h3 id="12-플랜을-단기중장기로-분리해서-제시">1.2 플랜을 단기/중장기로 분리해서 제시</h3>
<ul>
<li>발전 가능성(확장 계획)은 좋지만 <strong>한 화면에 섞이면 흐려짐</strong></li>
<li><strong>단기 플랜(1~2개월)</strong> 과 <strong>중장기 플랜(3~6개월/1년)</strong> 을 분리해 PPT에 뿌리는 구성이 더 설득력 있음</li>
</ul>
<p><strong>예시 구조</strong></p>
<ul>
<li>단기: MVP 안정화/핵심 기능 고도화/운영 이슈 해결  </li>
<li>중장기: 추천/검색 고도화, 자동화, 서비스 확장, B2B/파트너십 등</li>
</ul>
<hr>
<h2 id="2-기술-선택에-대한-피드백비용효율난이도">2. 기술 선택에 대한 피드백(비용/효율/난이도)</h2>
<h3 id="21-ai-호출-비용-비용-대비-효과-검증-필요">2.1 AI 호출 비용: 비용 대비 효과 검증 필요</h3>
<ul>
<li>AI 호출 비용이 실제 운영에서 부담이 될 수 있으므로<br><strong>“비용 대비 효과가 있는지” 검증</strong>이 필요함</li>
<li>이 포인트를 PPT에 포함하면 “기술적으로 멋있다”를 넘어<br><strong>현실적인 운영 감각</strong>을 보여줄 수 있고, 타 팀 대비 강점이 될 수 있음</li>
</ul>
<p><strong>포함하면 좋은 내용</strong></p>
<ul>
<li>호출 빈도/사용자 행동 기준으로 대략적인 비용 추정</li>
<li>최적화 계획(캐싱, 단계별 모델 분리, 임베딩 모델 분리 등)</li>
<li>기능별 비용/효과 비교(정말 필요한 곳에만 AI 사용)</li>
</ul>
<hr>
<h3 id="22-aws-bedrock-선택-근거-정리">2.2 AWS Bedrock 선택 근거 정리</h3>
<ul>
<li>Bedrock을 사용한 이유를 “기술 채택”이 아니라 “운영 전략” 관점으로 설명하면 설득력이 올라감</li>
<li>피드백 핵심:<br><strong>배포 포인트를 줄여 기술 난이도를 낮추고, 효율적으로 비용을 쓰기 위해</strong> 선택했다는 메시지가 필요함</li>
</ul>
<hr>
<h2 id="3-보안정보보호-관점-보강">3. 보안/정보보호 관점 보강</h2>
<h3 id="31-개인정보범죄-도용-가능성까지-고려하기">3.1 개인정보/범죄 도용 가능성까지 고려하기</h3>
<ul>
<li>단순히 “보안을 신경 썼다”가 아니라<br><strong>개인정보, 보안, 도용 가능성</strong>을 고려했는지를 명시적으로 드러내면 신뢰도가 올라감</li>
<li>특히 펫로스/추억 같은 민감한 정서 데이터를 다루는 서비스는<br>“사용자 신뢰”가 곧 제품 가치가 될 수 있음</li>
</ul>
<p><strong>PPT에 넣기 좋은 항목</strong></p>
<ul>
<li>저장되는 데이터 범위(무엇을 저장/비저장하는지)</li>
<li>접근 제어(권한 검증, IDOR 방지 등)</li>
<li>로그/마스킹/비식별화 정책</li>
<li>외부 공유(피드 공유, 이미지 공유) 시 노출 통제</li>
</ul>
<hr>
<h2 id="4-kafka-관련-피드백오버엔지니어링-논점-대응">4. Kafka 관련 피드백(오버엔지니어링 논점 대응)</h2>
<h3 id="41-kafka는-대기업-아니면-잘-안-쓴다-논점">4.1 “Kafka는 대기업 아니면 잘 안 쓴다” 논점</h3>
<ul>
<li>Kafka는 대용량 처리에 특화되어 있어<br>일반적으로는 RabbitMQ/SQS 같은 선택지를 쓰다가<br>데이터가 많아지면 Kafka로 마이그레이션하는 흐름이 많음</li>
</ul>
<h3 id="42-그럼에도-kafka를-쓴-이유를-명확히-해야-함">4.2 그럼에도 Kafka를 쓴 이유를 명확히 해야 함</h3>
<ul>
<li>“Kafka는 오버엔지니어링 아닌가?” 질문이 들어올 수 있음  </li>
<li>이에 대한 대응 방향:<br><strong>학술적/아키텍처적 가치가 있어서 Kafka를 채택했다</strong>는 메시지를 명확히 제시하기</li>
</ul>
<p><strong>정리 포인트(발표에서 말할 문장 예시)</strong></p>
<ul>
<li>“현 규모에서는 오버엔지니어링처럼 보일 수 있지만,<br>이벤트 기반 아키텍처의 정합성/확장성/장애 격리를 학습하고 구현하기 위해 Kafka를 선택했습니다.”</li>
<li>“향후 서비스 확장 시 마이그레이션 비용을 줄이기 위한 사전 설계 경험을 확보하는 목적도 있었습니다.”</li>
</ul>
<hr>
<h2 id="5-홍보마케팅사업-수행-계획-관점-추가">5. 홍보/마케팅/사업 수행 계획 관점 추가</h2>
<h3 id="51-좋은-기능만으로는-부족">5.1 “좋은 기능”만으로는 부족</h3>
<ul>
<li>만들어둔 기능이 좋아도 홍보/마케팅이 약하면 성과로 연결되기 어려움</li>
<li>따라서 “인사이트”를 만들어<br><strong>어떻게 사용자에게 닿게 할지</strong>를 고민해보라는 피드백이 있었다.</li>
</ul>
<p><strong>사업 수행 계획서처럼 넣을만한 내용</strong></p>
<ul>
<li>타깃 사용자 정의(펫로스를 겪는 사람, 기록을 남기는 사람, 반려동물 보호자 등)</li>
<li>유입 경로(커뮤니티/인스타/블로그/제휴/동물병원/보호소 등)</li>
<li>핵심 KPI(가입→작성→재방문→공유)</li>
<li>초기 성장 전략(콘텐츠/템플릿/공유 기능 활용)</li>
</ul>
<hr>
<h2 id="6-ux사용-흐름-관련-피드백오전오후-종합">6. UX/사용 흐름 관련 피드백(오전/오후 종합)</h2>
<h3 id="61-애자일-프로세스-관점으로-설명하기-오전">6.1 애자일 프로세스 관점으로 설명하기 (오전)</h3>
<ul>
<li>PPT와 프로젝트 설명에서<br>“한 번에 완성”이 아니라 <strong>애자일하게 개선해온 과정</strong>이 드러나면 설득력이 높아짐</li>
<li>요구사항 변화, 피드백 반영, 우선순위 조정 등의 흐름을 간단히라도 보여주기</li>
</ul>
<h3 id="62-사용자가-주로-쓰는-기능-중심으로-구성하기-오후">6.2 사용자가 ‘주로 쓰는 기능’ 중심으로 구성하기 (오후)</h3>
<ul>
<li>사용자가 실제로 가장 자주 쓰는 화면/기능이 무엇인지 기준을 세워야 함</li>
<li>“AI 스튜디오”가 네비바에 있어도 접근이 멀다면 이탈 가능성이 있음<br>→ <strong>한 번 더 노출</strong>해서 바로 이동할 수 있는 동선을 고려하기</li>
</ul>
<hr>
<h2 id="7-오늘-피드백을-반영한-개선-체크리스트">7. 오늘 피드백을 반영한 개선 체크리스트</h2>
<ul>
<li><input disabled="" type="checkbox"> 문제 정의 첫 화면: 키워드/한 문장/숫자 중심으로 정리  </li>
<li><input disabled="" type="checkbox"> 단기 플랜 vs 중장기 플랜 분리 슬라이드 구성  </li>
<li><input disabled="" type="checkbox"> AI 비용 추정 + 최적화 전략(비용 대비 효과) 추가  </li>
<li><input disabled="" type="checkbox"> 보안/정보보호(개인정보/도용 위험) 슬라이드 추가  </li>
<li><input disabled="" type="checkbox"> Kafka 선택 근거: “오버엔지니어링 논점”에 대한 답변 문장 준비  </li>
<li><input disabled="" type="checkbox"> Bedrock 선택 근거: 배포 포인트/운영 효율 관점으로 정리  </li>
<li><input disabled="" type="checkbox"> 홍보/마케팅 전략(유입/KPI/채널) 1~2장 추가  </li>
<li><input disabled="" type="checkbox"> AI 스튜디오 진입 동선 개선(네비 외 추가 CTA 노출)</li>
</ul>
<hr>
<h2 id="회고">회고</h2>
<p>오늘 멘토링에서 가장 크게 느낀 점은 “기술 구현”만으로는 발표가 설득력을 얻기 어렵다는 것이었다. 기능이 무엇을 해결하는지, 왜 그 기술을 선택했는지, 운영에서 어떤 리스크(비용/보안)를 어떻게 다룰지까지 같이 말해야 프로젝트가 ‘제품’처럼 보인다. 특히 Kafka나 AI 같은 선택은 멋있어 보이지만 질문을 받기 쉬운 영역이라, 반박 포인트를 미리 문장으로 정리해두는 게 중요하다고 느꼈다. 다음 작업에서는 PPT를 “사용자 흐름 + 문제 해결 + 운영 가능성” 중심으로 더 단단하게 정리하고, 단기/중장기 계획을 분리해 확장성도 설득력 있게 보여주는 방향으로 개선할 예정이다.</p>
<hr>
<h1 id="☂️-오늘의-개발-☂️">☂️ 오늘의 개발 ☂️</h1>
<h1 id="ai-리캡-기능-고도화-자동-예약waiting--상세-페이지--폴링-다중-이미지보안idor스케줄러-안정화">AI 리캡 기능 고도화: 자동 예약(WAITING) + 상세 페이지 + 폴링, 다중 이미지/보안(IDOR)/스케줄러 안정화</h1>
<blockquote>
<p>관련 이슈  </p>
<ul>
<li>Front: Closes #88 (리캡 예약/상세/폴링/배포 이슈 대응)  </li>
<li>Back: 리캡 생성·조회·예약 로직 전반 개선 (다중 이미지, 보안 검증, 스케줄러/배포 안정화)</li>
</ul>
</blockquote>
<hr>
<h2 id="개요">개요</h2>
<p>리캡(Recap)은 특정 기간의 다이어리를 기반으로 AI가 요약과 하이라이트를 생성해주는 기능이다.<br>기능이 확장되면서 다음 과제가 동시에 발생했다.</p>
<ul>
<li>“자동 생성”을 눌렀을 때 즉시 결과가 나오지 않는 경우가 많아 <strong>예약 상태/진행 상태 관리</strong>가 필요해짐</li>
<li>리캡 상세 화면이 커지면서 <strong>다중 이미지·하이라이트·기간 정보</strong>를 안정적으로 표현할 UI가 필요해짐</li>
<li>배포 환경에서 Lazy Loading/OSIV 설정 차이로 인해 컬렉션 조회가 깨지거나 500이 발생할 수 있음</li>
<li>리캡 조회 시 <code>recapId</code>만으로 접근 가능하면 <strong>IDOR(수평 권한 상승)</strong> 위험이 발생할 수 있음</li>
<li>자동 생성 스케줄러는 “기준 날짜 계산” 오류가 생기면 잘못된 기간의 일기를 요약할 수 있음</li>
</ul>
<p>이번 작업은 프론트/백엔드 모두에서 “예약 기반 생성 + 안정적인 조회/표현 + 보안/운영 안정성”을 목표로 개선했다.</p>
<hr>
<h2 id="1-front-리캡-자동-예약-및-상세폴링-플로우-구축-closes-88">1. Front: 리캡 자동 예약 및 상세/폴링 플로우 구축 (Closes #88)</h2>
<h3 id="11-자동-예약-기능-구현">1.1 자동 예약 기능 구현</h3>
<p>기존에는 “자동 생성” 클릭 시 즉시 생성 요청을 수행했다. 이를 <strong>예약 방식</strong>으로 변경했다.</p>
<ul>
<li>자동 생성 버튼 클릭 시 즉시 생성이 아닌 <code>WAITING</code> 상태로 예약</li>
<li><code>scheduleAutoRecapApi</code> 구현 → <code>/api/recaps/schedule/auto</code> 호출</li>
<li>“다음 달 리캡을 예약”하는 방식으로 동작</li>
</ul>
<blockquote>
<p>의도: 생성이 오래 걸리는 작업을 UX 관점에서 “즉시 결과”가 아닌 “예약 → 완료” 흐름으로 모델링한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/ab3e6226-112f-4779-9a6e-d3cb8a8fdf54/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/62f93a1e-2fd8-4a5e-a6d2-937e1db8ced5/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/fac1f5be-106d-41fc-ab49-822c648e102c/image.png" alt=""></p>
<hr>
<h3 id="12-리캡-상세-페이지-구현-recapdetailpage">1.2 리캡 상세 페이지 구현: <code>RecapDetailPage</code></h3>
<ul>
<li><code>recapId</code> 기반 상세 조회 및 표시</li>
<li>이미지 캐러셀 / 하이라이트 / 기간 정보 등 주요 데이터 렌더링</li>
<li>로그인 체크 로직 추가(미로그인 시 에러 메시지)</li>
<li>권한 체크를 위한 <code>userId</code> 쿼리 파라미터 포함(백엔드 소유권 검증과 연동)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/cc913aba-1f20-4826-a6d3-2cf07dbdbd2b/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/869eb301-f111-48bd-886c-b435d6ca2472/image.png" alt=""></p>
<hr>
<h3 id="13-자동-폴링-기능-추가-airecappage">1.3 자동 폴링 기능 추가: <code>AiRecapPage</code></h3>
<p>예약 기반 기능에서는 “완료 시점을 사용자가 직접 새로고침으로 확인”하게 되면 UX가 떨어진다.<br>따라서 <code>WAITING</code> 상태가 있는 동안만 폴링을 수행하도록 구현했다.</p>
<ul>
<li><code>WAITING</code> 리캡이 있으면 <strong>30초마다 자동 새로고침</strong></li>
<li>모든 리캡이 <code>GENERATED</code> 상태로 변경되면 <strong>폴링 자동 중지</strong></li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/b3cc8743-f662-4490-a167-b3d31823ffaf/image.png" alt=""></p>
<hr>
<h3 id="14-배포-환경-userid-오류-수정localstorage-파싱">1.4 배포 환경 userId 오류 수정(LocalStorage 파싱)</h3>
<p>배포 환경에서 <code>userId</code>가 0으로 전달되던 문제를 수정했다.</p>
<ul>
<li>기존: <code>localStorage.getItem(&#39;userId&#39;)</code></li>
<li>변경: <code>petlog_user</code> 객체에서 <code>id</code> 추출</li>
<li>수정 범위: <code>diary-api.ts</code>, <code>AiRecapPage.tsx</code>, <code>RecapDetailPage.tsx</code> 등 전역 적용</li>
</ul>
<hr>
<h3 id="15-리팩토링-및-안정화">1.5 리팩토링 및 안정화</h3>
<ul>
<li><code>UserDiaryPage</code>에서 deprecated <code>RecapModal</code> 제거</li>
<li>리캡 클릭 시 <code>navigate(/recap/:id)</code> 라우팅으로 변경</li>
<li>TypeScript 빌드 에러 해결, <code>pnpm run build</code> 통과 확인</li>
</ul>
<hr>
<h2 id="2-back-리캡-생성조회예약-로직-개선">2. Back: 리캡 생성/조회/예약 로직 개선</h2>
<h3 id="21-리캡-다중-이미지최대-8장-지원-및-엔티티-구조-변경">2.1 리캡 다중 이미지(최대 8장) 지원 및 엔티티 구조 변경</h3>
<p>기존 리캡은 단일 <code>mainImageUrl</code>로만 이미지를 표현했다. 이를 다중 이미지 리스트로 확장했다.</p>
<ul>
<li><code>mainImageUrl</code> 제거</li>
<li><code>List&lt;String&gt; imageUrls</code> 추가</li>
<li><code>@ElementCollection</code> + <code>@CollectionTable</code>로 리스트 저장 구조 구성</li>
</ul>
<h4 id="이미지-추출-알고리즘-개선">이미지 추출 알고리즘 개선</h4>
<ul>
<li><code>Diary</code>와 연관된 <code>List&lt;DiaryImage&gt;</code>를 <code>flatMap</code>으로 평탄화 수집</li>
<li>각 일기의 <code>mainImage == true</code>만 필터링하여 품질 확보</li>
<li><code>Collections.shuffle()</code>로 무작위성을 부여해 “항상 같은 사진만 나오는 문제”를 완화</li>
<li><code>limit(8)</code>로 최대 8장으로 제한해 성능 및 UI 일관성 유지</li>
</ul>
<hr>
<h3 id="22-리캡-상세-조회-보안-검증idor-방지">2.2 리캡 상세 조회 보안 검증(IDOR 방지)</h3>
<p><code>recapId</code>만 알면 다른 유저의 리캡을 조회할 수 있는 위험을 차단했다.</p>
<ul>
<li><code>getRecap(recapId, userId)</code> 시그니처 도입</li>
<li>서비스 계층에서 <code>recap.userId</code>와 요청 <code>userId</code> 일치 여부를 검증</li>
<li>불일치 시 예외 발생 및 경고 로그 기록</li>
</ul>
<blockquote>
<p>의도: 컨트롤러 단 검증이 아니라 핵심 비즈니스 규칙(소유권)을 서비스 계층에서 강제한다.</p>
</blockquote>
<hr>
<h3 id="23-스케줄러-고도화-및-버그-수정">2.3 스케줄러 고도화 및 버그 수정</h3>
<h4 id="1-예약-기간-참조-오류-수정">1) 예약 기간 참조 오류 수정</h4>
<p>기존 스케줄러가 “오늘 기준 지난달”로만 계산해 WAITING 리캡 기간과 불일치하는 문제가 있었다.<br>이를 <code>WAITING</code> 리캡이 가진 실제 <code>periodStart</code>, <code>periodEnd</code>를 참조하도록 수정했다.</p>
<ul>
<li>예: 2월 리캡이 12월 일기를 조회하던 문제 해결</li>
</ul>
<h4 id="2-프로세스-안정화">2) 프로세스 안정화</h4>
<ul>
<li><code>createWaitingRecap</code> 내부 중복 리턴/변수 미선언 컴파일 에러 수정</li>
<li><code>WAITING(예약)</code> ↔ <code>GENERATED(완료)</code> 상태 생명주기를 명확히 분리</li>
</ul>
<h4 id="3-디버깅-로깅-강화">3) 디버깅 로깅 강화</h4>
<p>운영 환경에서 추적 가능하도록 스케줄러 실행 로그를 강화했다.</p>
<ul>
<li>대상 펫 ID</li>
<li>조회 시작/종료 범위</li>
<li>발견된 일기 수</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/c5d33e08-74ea-436a-b7c9-5d1a9b44d6eb/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/e5c8f8f9-8c69-429e-85cd-5cba174ac2b5/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/0e2e01ae-6060-45f1-b6d1-60523cb986ac/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/53020138-f0f7-4f76-bf90-13d5de82e34d/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/222db457-eec9-44b9-984e-596a37089538/image.png" alt=""></p>
<hr>
<h3 id="24-배포-환경-최적화lazy-loading-대응">2.4 배포 환경 최적화(Lazy Loading 대응)</h3>
<p>배포 환경에서 OSIV 설정 유무에 따라 <code>imageUrls</code>, <code>highlights</code> 컬렉션이 DTO 변환 과정에서 초기화되지 않아 500이 발생할 수 있다.<br>이를 방지하기 위해 조회 트랜잭션 내에서 강제 초기화 로직을 추가했다.</p>
<ul>
<li>목적: 환경 차이에 따른 예외를 줄이고, DTO 변환을 안정화한다.</li>
</ul>
<hr>
<h2 id="3-테스트-결과-요약">3. 테스트 결과 요약</h2>
<h3 id="front">Front</h3>
<ul>
<li>자동 예약 → <code>WAITING</code> → <code>GENERATED</code> 플로우 동작 확인</li>
<li>리캡 상세 페이지 정상 렌더링 확인</li>
<li>자동 폴링 동작 및 종료 조건 확인</li>
<li>빌드 테스트 통과(<code>pnpm run build</code>)</li>
</ul>
<h3 id="back">Back</h3>
<ul>
<li>Postman 테스트: 다중 이미지(최대 8장) 랜덤 추출 및 JSON 리스트 반환 확인</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/d97e1530-e0ad-490c-b861-8ce23930f073/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/3afbc096-4d01-4f7a-8c96-32720d42116d/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/500a502a-954c-466e-8bae-a0b3ebfc22b4/image.png" alt=""></p>
<ul>
<li>보안 테스트: 소유자 불일치 시 접근 차단 확인</li>
<li>스케줄러 테스트: <code>WAITING</code> 리캡이 실제 기간 기준으로 분석 완료 후 <code>GENERATED</code>로 전환 확인</li>
<li>배포 환경 MIME 타입/자산 경로 이슈 점검</li>
</ul>
<hr>
<h2 id="4-리뷰-체크-포인트-표현-개선">4. 리뷰 체크 포인트 (표현 개선)</h2>
<h3 id="41-front">4.1 Front</h3>
<ul>
<li>폴링 주기(30초)가 UX/트래픽 관점에서 과도하지 않은지(사용자 수 증가 시 요청량 포함)</li>
<li>SPA 라우팅 환경에서 CloudFront 오류 페이지 설정이 <code>/recap/:id</code> 같은 딥링크를 안정적으로 처리하는지</li>
<li>백엔드 <code>RecapScheduler</code> cron이 운영 환경에서 원하는 시간/주기로 설정되어 있는지</li>
</ul>
<h3 id="42-back">4.2 Back</h3>
<ul>
<li><code>WAITING</code> 리캡 처리 시 기존 예약 데이터를 삭제하고 새 ID로 생성하는 방식 vs 동일 ID 유지(update) 방식 중 운영/추적 관점에서 더 적절한 방향</li>
<li>대표 이미지가 부족한 경우(8장 미만) 리캡 품질을 유지하기 위한 보완 전략(일반 이미지 포함 여부 등) 필요성</li>
<li>권한 예외를 <code>RuntimeException</code> 대신 커스텀 보안 예외로 분리하고, 전역 예외 처리에서 일관된 응답 스펙을 제공하는 구조가 적절한지</li>
</ul>
<hr>
<h2 id="5-회고">5. 회고</h2>
<p>이번 개선을 통해 리캡 기능이 단순히 “AI 호출해서 결과 받는 기능”이 아니라, 상태 모델(WAITING/GENERATED)과 화면 흐름, 그리고 운영 환경까지 포함한 하나의 제품 기능이라는 걸 더 명확히 이해하게 됐다. 프론트에서는 예약 기반 플로우를 도입하면서 사용자가 기다리는 시간을 UX로 풀어내야 했고, 그 결과로 폴링 조건(언제 시작/중지할지) 같은 기준을 직접 설계해볼 수 있었다. 배포 환경에서 <code>userId</code>가 0으로 들어가던 문제는 코드 자체보다 “저장된 사용자 정보 구조를 정확히 이해했는지”가 더 중요하다는 걸 보여줬고, 작은 파싱 실수가 권한 검증까지 흔들 수 있다는 점을 배웠다. 백엔드에서는 이미지 구조를 단일 URL에서 리스트로 바꾸는 작업이 생각보다 영향 범위가 넓었고, 엔티티 구조 변경이 DTO/조회/배포 안정성(OSIV/Lazy Loading)까지 연결된다는 걸 체감했다. IDOR 방지를 위해 서비스 계층에서 소유권 검증을 강제하면서 보안은 컨트롤러 옵션이 아니라 도메인 규칙이라는 감각을 얻었다. 스케줄러 기간 계산 버그를 고치며 “시간/기간 로직”은 가장 조용하게 큰 오류를 만드는 영역이라는 걸 느꼈고, 그래서 로깅과 테스트가 필수라는 점도 확인했다. 전체적으로는 기능이 동작하는 것에 더해, 상태 전이/보안/배포 환경 차이를 함께 고려해야 실제 서비스 품질이 올라간다는 걸 배운 작업이었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발]37.[FE][Refactor] 일기 생성 및 이미지 처리 개선, [BE] 다이어리 사진 비정형 메타데이터 저장을 위한 MongoDB 도입 및 연동,  Spring AI 기반 월간 리캡 자동 생성 기능 구현]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C38.FERefactor-%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B2%98%EB%A6%AC-%EA%B0%9C%EC%84%A0-BE-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%82%AC%EC%A7%84-%EB%B9%84%EC%A0%95%ED%98%95-%EB%A9%94%ED%83%80%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%80%EC%9E%A5%EC%9D%84-%EC%9C%84%ED%95%9C-MongoDB-%EB%8F%84%EC%9E%85-%EB%B0%8F-%EC%97%B0%EB%8F%99-Feat-Spring-AI-%EA%B8%B0%EB%B0%98-%EC%9B%94%EA%B0%84-%EB%A6%AC%EC%BA%A1-%EC%9E%90%EB%8F%99-%EC%83%9D%EC%84%B1-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C38.FERefactor-%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B2%98%EB%A6%AC-%EA%B0%9C%EC%84%A0-BE-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%82%AC%EC%A7%84-%EB%B9%84%EC%A0%95%ED%98%95-%EB%A9%94%ED%83%80%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%80%EC%9E%A5%EC%9D%84-%EC%9C%84%ED%95%9C-MongoDB-%EB%8F%84%EC%9E%85-%EB%B0%8F-%EC%97%B0%EB%8F%99-Feat-Spring-AI-%EA%B8%B0%EB%B0%98-%EC%9B%94%EA%B0%84-%EB%A6%AC%EC%BA%A1-%EC%9E%90%EB%8F%99-%EC%83%9D%EC%84%B1-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Fri, 02 Jan 2026 13:33:11 GMT</pubDate>
            <description><![CDATA[<h1 id="🌟-개발-기록-20260102-🌟">[🌟 개발 기록 2026.01.02 🌟]</h1>
<h1 id="petlog-다이어리-시스템-고도화-프론트-이미지-처리-안정화-mongodb-하이브리드-메타데이터-저장-월간-리캡-ai-생성">Petlog 다이어리 시스템 고도화: 프론트 이미지 처리 안정화, MongoDB 하이브리드 메타데이터 저장, 월간 리캡 AI 생성</h1>
<blockquote>
<p>관련 PR / 이슈  </p>
<ul>
<li>Closes #83 (Front: 이미지 업로드/메인 이미지/날씨 연동 UX 개선)  </li>
<li>Closes #47 (Back: MongoDB Atlas 기반 사진 메타데이터 Hybrid Storage)  </li>
<li>Closes #26 (Back: Spring AI 기반 월간 리캡 생성)</li>
</ul>
</blockquote>
<hr>
<h2 id="개요">개요</h2>
<p>Petlog의 다이어리 도메인은 “이미지 중심 기록”과 “AI 기반 요약/검색”을 동시에 다룬다. 기능이 확장되면서 다음과 같은 요구가 명확해졌다.</p>
<ul>
<li>이미지 업로드/삭제/재정렬 과정에서 <strong>메인 이미지와 이미지 소스(GALLERY/ARCHIVE) 정합성</strong>을 유지해야 한다.</li>
<li>이미지의 EXIF/기기 정보/AI 태그 같은 <strong>비정형 메타데이터</strong>를 저장·조회·삭제까지 안정적으로 처리해야 한다.</li>
<li>기간 기반으로 일기 데이터를 AI에게 전달해 <strong>월간 리캡(요약/하이라이트)</strong> 을 자동 생성하고, 결과를 구조화된 형태로 저장해야 한다.</li>
</ul>
<p>이번 작업은 위 요구를 기준으로 프론트/백엔드 모두를 개선했다.</p>
<hr>
<h2 id="1-front-closes-83-이미지-업로드-ux-및-데이터-정합성-강화">1. Front (Closes #83): 이미지 업로드 UX 및 데이터 정합성 강화</h2>
<h3 id="11-uiux-개선">1.1 UI/UX 개선</h3>
<ul>
<li>업로드 UI 변경: 다이어리 사진 업로드 인터페이스 개선</li>
<li>마이페이지 다이어리쓰기 버튼 연동: 마이페이지에서 작성 페이지로 자연스럽게 이동하도록 개선</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/2d639256-fa26-4a01-8062-05be0d86de29/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/f264e0d4-1f0c-44b6-850a-fac804762ee8/image.png" alt=""></p>
<hr>
<h3 id="12-이미지-처리-로직-개선">1.2 이미지 처리 로직 개선</h3>
<h4 id="1-업로드-장수-제한최대-6장">1) 업로드 장수 제한(최대 6장)</h4>
<ul>
<li>다이어리 생성 시 업로드 가능한 사진을 최대 6장으로 제한</li>
<li>목적: 과도한 업로드로 인한 렌더링/전송 비용 증가 및 UI 복잡도 상승 방지</li>
</ul>
<h4 id="2-메인-이미지-로직-수정">2) 메인 이미지 로직 수정</h4>
<p>이미지 삭제/변경 후에도 올바른 메인 이미지가 유지되도록 로직을 보강했다.</p>
<ul>
<li>이미지 삭제 후 메인 이미지가 올바르게 재지정되는지 보장</li>
<li>이미지 소스(GALLERY/ARCHIVE) 값이 꼬이던 오류 수정</li>
</ul>
<h4 id="3-렌더링-및-url-처리-안정화">3) 렌더링 및 URL 처리 안정화</h4>
<ul>
<li>이미지 렌더링 오류 수정(표시 깨짐/조건 분기 오류 등)</li>
<li>blob URL 오류 수정 및 <strong>S3 URL 우선 사용</strong> 로직 개선  <ul>
<li>목적: 새로고침/재진입 시에도 이미지가 유실되지 않도록 “영구 URL” 기준으로 일관성 확보</li>
</ul>
</li>
</ul>
<hr>
<h3 id="13-위치-변경-시-날씨-자동-업데이트">1.3 위치 변경 시 날씨 자동 업데이트</h3>
<ul>
<li>사용자가 위치를 변경하면 해당 위치의 날씨 정보가 자동으로 갱신되도록 개선</li>
<li>목적: 위치와 날씨 간 데이터 불일치를 줄이고 입력 편의성을 높임</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/56d89d39-cedd-4d31-8690-c5851d4f262d/image.png" alt=""></p>
<hr>
<h3 id="14-테스트">1.4 테스트</h3>
<ul>
<li>이미지 업로드 테스트 (1~6장)</li>
<li>메인 이미지 지정 테스트</li>
<li>위치 변경 시 날씨 업데이트 테스트</li>
<li>마이페이지 → 다이어리 작성 이동 테스트</li>
<li>이미지 렌더링 확인 (S3 URL, blob URL)</li>
</ul>
<hr>
<h2 id="2-back-closes-47-mongodb-atlas-기반-사진-메타데이터-hybrid-storage">2. Back (Closes #47): MongoDB Atlas 기반 사진 메타데이터 Hybrid Storage</h2>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/97f811f2-0d0b-401b-a510-3408f1e660e2/image.png" alt=""></p>
<h3 id="21-도입-배경">2.1 도입 배경</h3>
<p>이미지 메타데이터는 EXIF, 기기 정보, AI 태그 등 필드 형태가 유동적이고 확장 가능성이 높다.<br>RDB에 스키마를 고정해 저장하면 컬럼 확장 비용이 크기 때문에, 비정형 데이터 저장을 위해 MongoDB를 도입했다.</p>
<hr>
<h3 id="22-mongodb-atlas-연동-및-모델-구성">2.2 MongoDB Atlas 연동 및 모델 구성</h3>
<ul>
<li>MongoDB Atlas 기반 인프라 구성</li>
<li><code>PhotoMetadata</code> 도큐먼트 엔티티 및 <code>PhotoMetadataRepository</code> 구현</li>
<li><code>photo_metadata</code> 컬렉션 생성 및 저장 검증(mongosh/Compass)</li>
</ul>
<hr>
<h3 id="23-저장-로직-postgresql--mongodb-하이브리드-저장">2.3 저장 로직: PostgreSQL + MongoDB 하이브리드 저장</h3>
<p>다이어리 저장 시 다음 흐름으로 저장한다.</p>
<ol>
<li>PostgreSQL에 이미지 저장 → <code>imageId</code> 생성</li>
<li><code>imageId</code>를 참조하여 MongoDB에 메타데이터 저장</li>
</ol>
<p>이를 위해</p>
<ul>
<li><code>DiaryRequest.Image</code> DTO에 <code>metadata</code> 필드를 추가하여 클라이언트에서 메타데이터를 전달받을 수 있게 했다.</li>
</ul>
<blockquote>
<p>핵심: “이미지의 소유/정합성은 RDB 기준”, “확장 가능한 메타는 Mongo 기준”으로 역할을 분리했다.</p>
</blockquote>
<hr>
<h3 id="24-조회-및-병합-로직-최적화">2.4 조회 및 병합 로직 최적화</h3>
<p><code>getDiary</code> 시점에는 이미지가 여러 장일 수 있으므로, 메타데이터를 N번 조회하면 성능이 급격히 떨어질 수 있다.</p>
<ul>
<li>PostgreSQL에서 이미지 목록을 가져온 뒤</li>
<li>MongoDB에서 <code>IN</code> 쿼리로 메타데이터를 일괄 조회</li>
<li><code>Map(imageId → metadata)</code> 형태로 병합하여 효율적으로 매핑</li>
</ul>
<hr>
<h3 id="25-삭제-연동">2.5 삭제 연동</h3>
<p>다이어리 삭제 시 연관된 모든 사진의 MongoDB 메타데이터도 함께 삭제하도록 처리했다.</p>
<hr>
<h3 id="26-테스트">2.6 테스트</h3>
<ul>
<li>Atlas 내 <code>photo_metadata</code> 컬렉션 저장 확인</li>
<li>mongosh/Compass로 데이터 정합성 검증</li>
<li>Kafka 이벤트/Milvus 저장 로직과의 사이드 이펙트 없음 확인</li>
</ul>
<hr>
<h2 id="3-back-closes-26-spring-ai-기반-월간-리캡-생성structured-output">3. Back (Closes #26): Spring AI 기반 월간 리캡 생성(Structured Output)</h2>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/580e56b2-987a-497d-b8a4-26e5f14cd044/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/1b9e27b5-ee65-4f36-b58d-4bfeecb3af76/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/95e0382a-65e7-4d68-a54c-42afcdbf3386/image.png" alt=""></p>
<h3 id="31-기능-목표">3.1 기능 목표</h3>
<p>특정 기간의 일기 데이터를 조회하여 AI 분석을 수행하고, 한 달간의 기록을 요약한 “리캡”을 생성한다.</p>
<ul>
<li>결과: <code>title</code>, <code>summary</code>, <code>highlights(3개)</code> 구조화 데이터 생성 및 DB 저장</li>
</ul>
<hr>
<h3 id="32-데이터-기반-동적-리캡-생성">3.2 데이터 기반 동적 리캡 생성</h3>
<ul>
<li>요청의 <code>periodStart</code>를 기반으로 연/월을 추출</li>
<li>프롬프트에 시점을 주입하여 해당 기간에 맞는 리캡이 생성되도록 구성</li>
</ul>
<hr>
<h3 id="33-서비스-아키텍처-분리">3.3 서비스 아키텍처 분리</h3>
<p>역할을 분리해 책임을 명확히 했다.</p>
<ul>
<li><code>RecapServiceImpl</code><ul>
<li>기간별 일기 조회</li>
<li>AI 결과를 엔티티로 변환 및 저장</li>
</ul>
</li>
<li><code>RecapAiServiceImpl</code><ul>
<li>Spring AI <code>ChatModel</code> 호출</li>
<li>프롬프트 구성 및 JSON 응답(Structured Output) 파싱</li>
</ul>
</li>
</ul>
<p>데이터 조회를 위해</p>
<ul>
<li><code>DiaryRepository.findAllByPetIdAndDateBetween</code> 쿼리 메서드를 추가했다.</li>
</ul>
<hr>
<h3 id="34-하이라이트-저장">3.4 하이라이트 저장</h3>
<p>AI가 선정한 3개의 하이라이트를 <code>RecapHighlight</code> 엔티티와 연관관계로 저장하여, 리캡 조회 시 하이라이트를 독립적으로 다룰 수 있게 했다.</p>
<hr>
<h3 id="35-테스트">3.5 테스트</h3>
<ul>
<li>Postman 테스트: 특정 기간 일기 존재 시 리캡 ID 반환 확인</li>
<li>AI 응답 파싱 및 DB 저장 확인</li>
<li>일기 데이터 부재 시 예외 처리 확인</li>
</ul>
<hr>
<h2 id="4-체크-포인트">4. 체크 포인트</h2>
<h3 id="41-front83">4.1 Front(#83)</h3>
<ul>
<li>메인 이미지 재지정 로직이 삭제/순서 변경/소스 혼합(GALLERY/ARCHIVE) 상황에서도 일관되게 동작하는지</li>
<li>S3 URL 우선 처리 정책이 blob URL 유실(새로고침/재접속) 문제를 충분히 차단하는지</li>
<li>위치 변경 → 날씨 자동 갱신 흐름에서 불필요한 재요청/레이스 컨디션이 발생하지 않는지</li>
</ul>
<h3 id="42-back47">4.2 Back(#47)</h3>
<ul>
<li>MongoDB 저장이 PostgreSQL 트랜잭션과 분리되어 있는 상황에서, 실패 시 정합성을 어떻게 유지할지(재시도/보상/로그/배치 정리 등) 정책이 필요한지</li>
<li>조회 병합 로직에서 <code>Map</code> 기반 머지가 충분히 효율적인지(데이터량 증가 시 메모리/응답 시간 관점)</li>
</ul>
<h3 id="43-back26">4.3 Back(#26)</h3>
<ul>
<li>“반려동물 관점” 프롬프트가 실제 출력에서 일관되게 유지되는지(문체/대명사/톤)</li>
<li>최소 일기 개수(예: 1개)에서도 리캡 품질이 유의미한지, 또는 최소 생성 조건을 둘 필요가 있는지</li>
</ul>
<hr>
<h2 id="5-회고">5. 회고</h2>
<p>이번 작업을 하면서 다이어리 기능이 “이미지 업로드” 한 가지로 끝나는 게 아니라, 업로드/삭제/정렬/대표 이미지/URL 지속성까지 모두 맞물려 있다는 걸 체감했다. 특히 메인 이미지 로직은 UI에서 보이는 문제로 끝나지 않고, 저장 데이터의 기준점이 되기 때문에 예외 케이스(삭제 후, 소스 혼합 후, 재진입 후)를 기준으로 설계해야 했다. blob URL은 구현 단계에서는 편하지만, 새로고침이나 화면 이동 뒤에는 쉽게 유실되기 때문에 결국 서비스에서는 S3 같은 영구 URL 중심으로 사고해야 한다는 걸 배웠다. 백엔드에서는 메타데이터를 MongoDB로 분리하면서 “정형/비정형 데이터가 섞일 때 저장소를 어떻게 나눌지”를 실제로 고민해볼 수 있었다. 하이브리드 저장 구조는 확장성은 좋지만, RDB 트랜잭션 밖의 저장이 실패했을 때 정합성을 어떻게 회복할지(재시도/보상/정리)가 설계의 일부라는 것도 알게 됐다. 조회 병합에서는 여러 장의 이미지 메타데이터를 N번 조회하면 바로 병목이 생길 수 있어, IN 쿼리와 일괄 병합 방식이 왜 필요한지도 이해했다. 월간 리캡 기능을 구현하면서는 AI 결과를 그대로 문자열로 저장하는 게 아니라, Structured Output으로 받아 엔티티로 변환해 저장해야 서비스 기능으로 확장 가능하다는 점을 배웠다. 또한 프롬프트에 기간 정보를 주입하는 작은 변경이 결과의 일관성에 직접 영향을 주는 것도 확인했다. 전체적으로 프론트/백엔드 모두에서 “동작한다”를 넘어서 “데이터가 오래 유지되고, 예외가 생겨도 복구 가능한 구조”를 만드는 관점이 중요하다는 걸 경험했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발]36.[FE] 다이어리 UI 개선 및 이미지 캐러셀, 포토 갤러리, 다이어리 보관함 연동, AI 다이어리 대시보드 연동 및 카카오톡 공유 기능 구현]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C37.FE-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-UI-%EA%B0%9C%EC%84%A0-%EB%B0%8F-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%BA%90%EB%9F%AC%EC%85%80-%ED%8F%AC%ED%86%A0-%EA%B0%A4%EB%9F%AC%EB%A6%AC-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EB%B3%B4%EA%B4%80%ED%95%A8-%EC%97%B0%EB%8F%99-AI-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EB%8C%80%EC%8B%9C%EB%B3%B4%EB%93%9C-%EC%97%B0%EB%8F%99-%EB%B0%8F-%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%86%A1-%EA%B3%B5%EC%9C%A0-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C37.FE-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-UI-%EA%B0%9C%EC%84%A0-%EB%B0%8F-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%BA%90%EB%9F%AC%EC%85%80-%ED%8F%AC%ED%86%A0-%EA%B0%A4%EB%9F%AC%EB%A6%AC-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EB%B3%B4%EA%B4%80%ED%95%A8-%EC%97%B0%EB%8F%99-AI-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EB%8C%80%EC%8B%9C%EB%B3%B4%EB%93%9C-%EC%97%B0%EB%8F%99-%EB%B0%8F-%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%86%A1-%EA%B3%B5%EC%9C%A0-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Fri, 02 Jan 2026 13:32:14 GMT</pubDate>
            <description><![CDATA[<h1 id="🎼-개발-기록-20251231-🎼">[🎼 개발 기록 2025.12.31 🎼]</h1>
<h1 id="ai-다이어리-프론트엔드-ux-고도화-대시보드-3d-캐러셀-이미지-캐러셀-모달-개선-카카오톡-공유">AI 다이어리 프론트엔드 UX 고도화: 대시보드 3D 캐러셀, 이미지 캐러셀, 모달 개선, 카카오톡 공유</h1>
<blockquote>
<p>관련 이슈: Closes #58<br>범위: Dashboard / MyPage(AI 다이어리 보관함) / 다이어리 상세 모달 / 포트폴리오 3D 갤러리 / 공유 기능 / 리팩토링</p>
</blockquote>
<hr>
<h2 id="개요">개요</h2>
<p>AI 다이어리 기능이 확장되면서, 단순 목록/단일 이미지 중심 UI로는 사용 경험을 유지하기 어려워졌다. 특히 “다이어리 이미지가 여러 장일 때의 탐색”, “홈에서 최신 AI 다이어리를 빠르게 소비하는 경험”, “모달/상세 화면의 가독성과 반응형”, “공유 기능의 안정성”이 주요 개선 포인트였다.</p>
<p>이번 작업은 다음 목표를 중심으로 진행했다.</p>
<ul>
<li>대시보드에 <strong>최신 AI 다이어리 노출</strong> 및 3D 캐러셀 인터랙션 적용</li>
<li>마이페이지 보관함 UI를 <strong>그리드 중심으로 재정렬</strong>하여 탐색 효율 개선</li>
<li>다중 이미지 지원을 위해 <strong>이미지 캐러셀 UI를 표준화</strong>하여 여러 화면에서 재사용</li>
<li>모달의 크기/타이포/이미지 레이아웃을 조정해 <strong>가독성과 반응형 안정성</strong> 확보</li>
<li>다이어리 상세 페이지에서 <strong>카카오톡 공유(이미지 캡처 기반)</strong> 기능 제공</li>
<li>빌드/런타임 경고 및 데드코드 제거로 품질 개선</li>
</ul>
<hr>
<h2 id="1-대시보드-ai-다이어리-3d-캐러셀-연동-및-최적화">1. 대시보드: AI 다이어리 3D 캐러셀 연동 및 최적화</h2>
<h3 id="11-최신-ai-다이어리-연동">1.1 최신 AI 다이어리 연동</h3>
<ul>
<li><code>DashboardPage</code>에 3D 캐러셀 컴포넌트 <code>DiaryCarousel3D</code>를 추가</li>
<li><code>getAiDiariesApi</code> 연동을 통해 최신 AI 일기 데이터를 홈 화면에 노출</li>
<li>3D 캐러셀 인터랙션 및 애니메이션 적용으로 시각적 집중도를 높임</li>
</ul>
<h3 id="12-3d-캐러셀-최적화겹침-방지">1.2 3D 캐러셀 최적화(겹침 방지)</h3>
<p>3D 캐러셀 특성상 렌더링되는 카드 수가 많아질수록 겹침/가독성 문제가 발생했다. 이를 방지하기 위해:</p>
<ul>
<li>최대 표시 다이어리 수를 <strong>10개로 제한</strong></li>
<li>카드에 날짜(<code>createdAt</code>) 표시 기능 추가로 “최근성”을 명확히 전달</li>
</ul>
<blockquote>
<p>의도: 시각적 효과는 유지하되, 정보 밀도와 UI 안정성을 우선한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/6bc22c96-c589-40c6-bd1e-3fa15ed3c0c5/image.png" alt=""></p>
<hr>
<h2 id="2-마이페이지-ai-다이어리-보관함-레이아웃-개선">2. 마이페이지: AI 다이어리 보관함 레이아웃 개선</h2>
<h3 id="21-가로-스크롤-→-세로-그리드로-전환">2.1 가로 스크롤 → 세로 그리드로 전환</h3>
<p>기존 가로 스크롤 방식은 콘텐츠 밀도가 높아질수록 탐색 피로도가 증가하고, 사진 보관함 UI와도 일관성이 떨어졌다. 개선 사항은 다음과 같다.</p>
<ul>
<li>가로 스크롤 기반 레이아웃을 <strong>세로 그리드 레이아웃</strong>으로 변경</li>
<li>사진 보관함과 동일한 정보 구조로 맞춰 UI 일관성을 강화</li>
</ul>
<h3 id="22-표시-개수스크롤-정책-정리">2.2 표시 개수/스크롤 정책 정리</h3>
<ul>
<li><strong>16개까지는 스크롤 없이 노출</strong></li>
<li>17개부터 내부 스크롤이 생성되도록 <code>max-h-[900px]</code> 적용</li>
</ul>
<blockquote>
<p>의도: “첫 화면에서 충분히 보여주되, 무한 높이 확장으로 레이아웃이 무너지는 것”을 방지한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/f8d1b2d3-b9a1-4234-af4f-514e476609dc/image.png" alt=""></p>
<hr>
<h2 id="3-이미지-캐러셀-모달갤러리포트폴리오로-확장-적용">3. 이미지 캐러셀: 모달/갤러리/포트폴리오로 확장 적용</h2>
<p>다중 이미지 다이어리가 증가하면서 “이미지 탐색 UI”를 화면마다 개별 구현하면 UX가 일관되지 않고 유지보수 비용이 커진다. 이번 작업에서는 이미지 캐러셀 경험을 표준화했다.</p>
<h3 id="31-마이페이지-ai-다이어리-모달-캐러셀">3.1 마이페이지 AI 다이어리 모달 캐러셀</h3>
<ul>
<li>좌우 화살표 버튼 + 점 인디케이터로 다중 이미지 탐색 지원</li>
<li><strong>2장 이상일 때만 캐러셀 UI 표시</strong>(단일 이미지에서는 UI를 숨겨 복잡도를 낮춤)</li>
</ul>
<h3 id="32-포토-갤러리-모달-풀스크린-이미지-뷰어">3.2 포토 갤러리 모달: 풀스크린 이미지 뷰어</h3>
<ul>
<li>갤러리 모달에서 풀스크린 뷰어를 제공해 고해상도/세로형 이미지의 가독성을 강화</li>
</ul>
<h3 id="33-포트폴리오-페이지-3d-갤러리-연동">3.3 포트폴리오 페이지 3D 갤러리 연동</h3>
<ul>
<li>포트폴리오 3D 갤러리에서 다이어리 상세보기 시 이미지 캐러셀을 지원</li>
<li>동일한 조건(2장 이상일 때만 노출)을 적용해 UX 일관성을 유지</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/b68bd208-9d67-458c-b29a-eeb6e4dfedc2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/c48b169f-1a70-4345-96f6-d6f4bb3dbd03/image.png" alt=""></p>
<hr>
<h2 id="4-다이어리-모달-uiux-개선가독성반응형">4. 다이어리 모달 UI/UX 개선(가독성/반응형)</h2>
<h3 id="41-모달-크기타이포-개선">4.1 모달 크기/타이포 개선</h3>
<ul>
<li>모달 최대 너비: <code>max-w-2xl</code> → <code>max-w-4xl</code></li>
<li>폰트 크기 상향:<ul>
<li>제목 <code>text-4xl</code></li>
<li>본문 <code>text-lg</code></li>
<li>버튼 <code>text-base h-12</code></li>
</ul>
</li>
</ul>
<blockquote>
<p>결과: 콘텐츠(텍스트+이미지) 중심 화면에서 정보 밀도를 낮추고 읽기 흐름을 개선했다.</p>
</blockquote>
<h3 id="42-이미지-표시-최적화잘림-방지--반응형">4.2 이미지 표시 최적화(잘림 방지 + 반응형)</h3>
<ul>
<li><code>object-contain</code> 적용으로 이미지 잘림 방지</li>
<li><code>max-w-[85%]</code>로 과도한 확대를 제한하여 균형 유지</li>
<li>이미지 컨테이너 높이:<ul>
<li><code>min-h-[350px]</code></li>
<li><code>max-h-[55vh]</code>
→ 세로/가로 비율이 다른 이미지에서도 레이아웃이 안정적으로 유지되도록 조정</li>
</ul>
</li>
</ul>
<hr>
<h2 id="5-다이어리-상세-페이지-카카오톡-공유-기능-구현">5. 다이어리 상세 페이지: 카카오톡 공유 기능 구현</h2>
<h3 id="51-html2canvas-기반-캡처-공유">5.1 html2canvas 기반 캡처 공유</h3>
<ul>
<li>상세 페이지(<code>DiaryDetailPage</code>)에서 카카오톡 공유 기능 구현</li>
<li><code>html2canvas</code>를 사용하여 일기 내용을 이미지로 캡처한 뒤 공유하도록 구성</li>
</ul>
<blockquote>
<p>포인트: 텍스트/스타일이 적용된 “결과 화면” 자체를 공유할 수 있어, 사용자가 보는 경험과 공유 결과가 일치한다.</p>
</blockquote>
<h3 id="52-부가-개선">5.2 부가 개선</h3>
<ul>
<li>스타일 조회 API(<code>getMyStyleApi</code>) 호출 시 인자값 누락 오류 수정</li>
<li>사용하지 않는 <code>handleEdit</code> 및 데드 코드 제거</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/b006a39d-c4ab-42db-891a-9774f0acabf5/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/5d0022e5-9d71-4734-8b22-9453b0f2d5f2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/ce075ea5-0700-4922-bdac-b46bb49d5666/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/8f4e84fa-109e-4792-9a53-6017e24e262a/image.png" alt=""></p>
<hr>
<h2 id="6-기타-리팩토링-및-품질-개선">6. 기타 리팩토링 및 품질 개선</h2>
<ul>
<li><code>UserDiaryPage</code> 내 미사용 변수 <code>currentPhotoIndex</code> 정리</li>
<li><code>DashboardPage</code> 구문 오류(Syntax Error) 수정</li>
<li>빌드 시 발생하는 TypeScript 타입 오류 및 경고 해결</li>
<li><code>pnpm run build</code> 성공 확인 및 런타임 테스트 수행</li>
</ul>
<hr>
<h2 id="7-테스트-결과-요약">7. 테스트 결과 요약</h2>
<ul>
<li>다이어리 1장 이미지: 캐러셀 UI 미표시 확인</li>
<li>다이어리 2장 이상: 화살표 + 점 인디케이터 정상 작동 확인</li>
<li>마이페이지 AI 다이어리 그리드 레이아웃 및 내부 스크롤 정상 작동 확인</li>
<li>대시보드 3D 캐러셀 10개 제한 정상 작동 확인</li>
<li>포트폴리오 페이지 이미지 캐러셀 정상 작동 확인</li>
<li>다양한 이미지 비율(가로형/세로형) 정상 표시 확인</li>
<li>로컬(dev)에서 대시보드 렌더링 및 카카오 공유 동작 확인, 빌드 성공 확인</li>
</ul>
<hr>
<h2 id="8-리뷰-체크리스트검토-요청-사항">8. 리뷰 체크리스트(검토 요청 사항)</h2>
<h3 id="81-이미지-캐러셀-ux">8.1 이미지 캐러셀 UX</h3>
<ul>
<li>화살표 버튼 위치가 이미지 시야를 방해하지 않는지</li>
<li>점 인디케이터 크기/간격이 모바일/데스크톱 모두에서 적절한지</li>
<li>“2장 이상일 때만 노출” 정책이 사용자의 혼란을 유발하지 않는지</li>
</ul>
<h3 id="82-모달-반응형">8.2 모달 반응형</h3>
<ul>
<li>작은 화면(노트북/태블릿/모바일)에서 <code>max-w-4xl</code> 적용 시 오버플로우/스크롤 UX가 자연스러운지</li>
<li><code>max-h-[55vh]</code>가 이미지가 긴 케이스에서 과도하게 작아 보이지 않는지</li>
</ul>
<h3 id="83-성능">8.3 성능</h3>
<ul>
<li>이미지가 많은 다이어리 목록/모달에서 렌더링 지연이 없는지</li>
<li>캐러셀 전환 시 불필요한 리렌더링이 발생하지 않는지(특히 3D 캐러셀 + 모달 조합)</li>
</ul>
<hr>
<h2 id="회고">회고</h2>
<p>이번 PR은 기능 추가보다는 “사용자가 실제로 많이 쓰는 화면에서 불편이 생기는 지점”을 정리하고 하나씩 해결하는 작업에 가까웠다. 3D 캐러셀은 시각적으로는 강점이 있지만 콘텐츠가 늘어나면 겹침과 성능 문제가 바로 드러나서, 보여줄 개수를 제한하는 식으로 기준을 세우는 과정이 필요했다. 마이페이지 보관함 레이아웃을 그리드로 바꾸면서는 보기 좋은 화면만 만드는 게 아니라 스크롤 정책과 최대 높이 같은 운영 규칙도 함께 정해야 UI가 안정된다는 걸 느꼈다. 이미지 캐러셀을 여러 화면에 확장하면서 “다중 이미지일 때만 UI를 보여준다” 같은 조건을 통일하니 UX가 정돈되는 효과가 있었다. 모달 개선은 단순히 크기만 키우는 게 아니라 텍스트/버튼/이미지 비율을 함께 조정해야 결과가 자연스럽게 나왔고, <code>object-contain</code> 같은 작은 설정이 실제 만족도를 크게 올려줬다. 카카오 공유는 html2canvas로 해결했지만, 공유 결과가 화면과 동일하게 나오도록 캡처 범위와 렌더링 상태를 맞추는 부분이 생각보다 중요했다. 마지막으로 빌드 경고와 타입 오류를 정리하면서 “기능이 되냐”뿐 아니라 “배포 가능한 상태냐”까지 포함해서 마무리하는 습관이 필요하다는 점을 다시 확인했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발]35.[BE]Milvus 연동 및 벡터 DB 저장 로직 구현, Kafka 도입 및 다이어리 이벤트 발행(비동기) 로직 구현]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C36.BEMilvus-%EC%97%B0%EB%8F%99-%EB%B0%8F-%EB%B2%A1%ED%84%B0-DB-%EC%A0%80%EC%9E%A5-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84-Kafka-%EB%8F%84%EC%9E%85-%EB%B0%8F-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B0%9C%ED%96%89%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C36.BEMilvus-%EC%97%B0%EB%8F%99-%EB%B0%8F-%EB%B2%A1%ED%84%B0-DB-%EC%A0%80%EC%9E%A5-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84-Kafka-%EB%8F%84%EC%9E%85-%EB%B0%8F-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B0%9C%ED%96%89%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Fri, 02 Jan 2026 13:31:41 GMT</pubDate>
            <description><![CDATA[<h1 id="💌-개발-기록-20251229카프카-최종-구현-성공--1230-💌">[💌 개발 기록 2025.12.29(카프카 최종 구현 성공) ~ 12.30 💌]</h1>
<blockquote>
<h1 id="kafka와-milvus로-구축한-이벤트-기반-ai-다이어리-엔진-벡터-검색--실시간-동기화">Kafka와 Milvus로 구축한 이벤트 기반 AI 다이어리 엔진: 벡터 검색 + 실시간 동기화</h1>
</blockquote>
<p>관련 PR: Closes #35 (Milvus/인프라/외부 API), Closes #25 (Kafka 이벤트 발행)</p>
<hr>
<h2 id="개요">개요</h2>
<p>AI 다이어리 서비스는 단순 CRUD를 넘어 다음 두 가지 요구를 가진다.</p>
<ol>
<li><strong>지능형 검색</strong>: 텍스트 키워드 매칭이 아니라 “비 오는 날의 기록”, “지난 가을 우울했던 날”처럼 의미 기반 검색이 필요하다.  </li>
<li><strong>실시간 동기화</strong>: 다이어리 생성/수정/삭제 이벤트를 Healthcare 서비스 등 다른 서비스와 빠르게 동기화해야 한다.</li>
</ol>
<p>이를 위해 이번 작업에서는</p>
<ul>
<li><strong>Milvus 벡터 DB</strong>에 다이어리 임베딩을 저장하여 검색 기반을 만들고,</li>
<li><strong>Kafka</strong>로 다이어리 도메인 이벤트를 발행하는 이벤트 기반 아키텍처를 구축했으며,</li>
<li>개발 환경을 안정적으로 재현하기 위해 <strong>Docker Compose 인프라 스택</strong>을 함께 정리했다.</li>
</ul>
<hr>
<h2 id="1-milvus-벡터-db-연동-closes-35">1. Milvus 벡터 DB 연동 (Closes #35)</h2>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/22ebcb98-a68e-4c56-b3d1-470973d34986/image.png" alt=""></p>
<h3 id="11-저장-시-자동-임베딩-저장-savediarytovectordb">1.1 저장 시 자동 임베딩 저장: <code>saveDiaryToVectorDB</code></h3>
<p><code>DiaryServiceImpl</code>에 <code>saveDiaryToVectorDB</code> 메서드를 추가하여, 다이어리가 저장될 때 임베딩 데이터를 Milvus에 함께 저장하도록 구현했다.</p>
<ul>
<li><strong>목표</strong>: “DB 저장 + 벡터 저장”을 하나의 흐름으로 묶되, 검색 기능을 위해 별도 저장소(벡터 DB)를 병행 운영</li>
<li><strong>검증</strong>: Milvus GUI(Attu)에서 다이어리 텍스트가 임베딩 벡터로 저장되는 것을 확인</li>
</ul>
<hr>
<h3 id="12-메타데이터-최적화필터링-검색-대비">1.2 메타데이터 최적화(필터링 검색 대비)</h3>
<p>단순히 텍스트만 벡터화하면 “의미 기반 유사도 검색”은 가능하지만, 서비스에서 자주 필요한 조건 검색이 어렵다.<br>따라서 날짜/기분/위치 정보를 메타데이터로 함께 저장하여, 다음과 같은 필터링 검색을 가능하게 했다.</p>
<ul>
<li>날짜 범위: 특정 기간 다이어리 검색</li>
<li>기분: “우울”, “기쁨” 등 감정 기반 조건</li>
<li>위치: 주소/좌표 기반 조건</li>
</ul>
<blockquote>
<p>의도: “유사도 검색 + 조건 필터” 조합으로 검색 정밀도를 확보한다.</p>
</blockquote>
<hr>
<h3 id="13-예외-처리-전략트랜잭션-영향-최소화">1.3 예외 처리 전략(트랜잭션 영향 최소화)</h3>
<p><code>saveDiaryToVectorDB</code>는 예외 발생 시 <strong>로그만 남기고 메인 트랜잭션은 유지</strong>하도록 처리했다.</p>
<ul>
<li>장점: Milvus 장애가 다이어리 저장(핵심 기능)을 막지 않음</li>
<li>단점: 벡터 저장 실패 시 검색 데이터 누락이 발생할 수 있음(재처리 전략이 필요할 수 있음)</li>
</ul>
<blockquote>
<p>리뷰 포인트: 현재는 “동기 호출 + 예외 무시” 구조인데, <code>@Async</code>로 분리하거나 이벤트 기반 후처리로 바꾸는 것이 더 적절한지 검토 포인트로 남겼다.</p>
</blockquote>
<hr>
<h2 id="2-외부-api-연동-실제-날씨주소-보정-closes-35">2. 외부 API 연동: 실제 날씨/주소 보정 (Closes #35)</h2>
<h3 id="21-기상청-api-연동좌표-기반-날씨-보정">2.1 기상청 API 연동(좌표 기반 날씨 보정)</h3>
<p>다이어리에는 “당일 컨텍스트”가 중요하다. 이미지 기반 추정 대신 <strong>좌표 기반 실제 날씨</strong>를 적용했다.</p>
<ul>
<li>좌표(위/경도) 기반으로 실제 날씨 조회</li>
<li>저장 시 날씨 필드를 보정하여 최종 데이터 품질을 개선</li>
</ul>
<hr>
<h3 id="22-kakao-로컬-api-연동좌표-→-주소-변환">2.2 Kakao 로컬 API 연동(좌표 → 주소 변환)</h3>
<p>좌표만 저장하면 사용자에게 의미가 약하므로, 사람이 읽을 수 있는 주소 문자열로 보정하는 로직을 포함했다.</p>
<ul>
<li>위도/경도 → 주소 변환</li>
<li>위치 검색/표시/필터링에 활용 가능</li>
</ul>
<hr>
<h2 id="3-docker-기반-인프라-구축-closes-35">3. Docker 기반 인프라 구축 (Closes #35)</h2>
<p>개발 환경에서 “카프카/밀버스/DB”를 각각 따로 세팅하면 설정 차이로 디버깅 비용이 증가한다.<br>이번 작업에서는 Docker Compose로 스택을 통합하여 재현 가능한 환경을 만들었다.</p>
<h3 id="31-milvus-스택">3.1 Milvus 스택</h3>
<ul>
<li>etcd</li>
<li>MinIO</li>
<li>Milvus Standalone</li>
</ul>
<h3 id="32-kafka-스택">3.2 Kafka 스택</h3>
<ul>
<li>Zookeeper (또는 구성 방식에 따라 KRaft)</li>
<li>Kafka (외부/내부 리스너 분리)</li>
<li>Kafka-UI</li>
</ul>
<h3 id="33-database">3.3 Database</h3>
<ul>
<li>PostgreSQL 15 + PostGIS 포함 구성</li>
</ul>
<hr>
<h2 id="4-kafka-기반-도메인-이벤트-발행-closes-25">4. Kafka 기반 도메인 이벤트 발행 (Closes #25)</h2>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/cdaee5f2-3cde-487d-a440-45bba8bc762c/image.png" alt=""><img src="https://velog.velcdn.com/images/seolhxx_/post/7c9319b4-1a06-4858-8b57-51e14957c4b3/image.png" alt=""></p>
<h3 id="41-도입-배경-서비스-간-실시간-동기화">4.1 도입 배경: 서비스 간 실시간 동기화</h3>
<p>Healthcare Service 등과의 실시간 데이터 동기화가 필요했지만, 동기 호출 방식은 타 서비스 장애가 다이어리 저장 기능까지 영향을 줄 수 있다.<br>따라서 <strong>이벤트 기반 비동기 방식</strong>으로 변경했다.</p>
<ul>
<li>다이어리 생성/수정/삭제 → <code>diary-events</code> 토픽으로 이벤트 발행</li>
<li>소비자(다른 서비스)는 이벤트를 구독하여 동기화 수행</li>
</ul>
<hr>
<h3 id="42-kafka-인프라-및-프로듀서-설정">4.2 Kafka 인프라 및 프로듀서 설정</h3>
<ul>
<li>Docker Compose 기반 Kafka 환경 구성</li>
<li><code>KafkaProducerConfig</code><ul>
<li><code>acks=all</code>로 신뢰성 강화</li>
<li><code>snappy</code> 압축으로 성능/전송 효율 고려</li>
</ul>
</li>
<li><code>KafkaTopicConfig</code><ul>
<li><code>diary-events</code> 토픽 파티션 3개로 명시적 설정(처리량 확장 고려)</li>
</ul>
</li>
</ul>
<hr>
<h3 id="43-비동기-이벤트-발행--트랜잭션-정합성">4.3 비동기 이벤트 발행 + 트랜잭션 정합성</h3>
<p>이벤트 기반 아키텍처에서 가장 중요한 포인트는 “DB와 이벤트 발행의 정합성”이다.</p>
<ul>
<li><code>ApplicationEventPublisher</code>로 도메인 이벤트 발행</li>
<li><code>TransactionalEventListener</code>로 <strong>트랜잭션 커밋 성공 후에만</strong> Kafka 메시지 발행</li>
<li><code>@Async</code> 적용으로 브로커 지연/장애가 있어도 메인 비즈니스 로직이 블로킹되지 않도록 처리</li>
</ul>
<blockquote>
<p>결과: 브로커 중단 상태에서도 DB 저장이 완료되는 비차단(Non-blocking) 동작을 테스트로 확인했다.</p>
</blockquote>
<hr>
<h3 id="44-순서-보장-전략-메시지-키user-id">4.4 순서 보장 전략: 메시지 키(User ID)</h3>
<p>특정 사용자의 다이어리 이벤트는 순서가 중요할 수 있다.<br>따라서 사용자 ID를 메시지 키로 사용해, 동일 사용자 이벤트가 동일 파티션으로 라우팅되도록 구성했다.</p>
<ul>
<li>장점: 파티션 내 순서 보장</li>
<li>트레이드오프: 특정 사용자 트래픽이 집중되면 핫 파티션이 될 수 있음(향후 모니터링 대상)</li>
</ul>
<hr>
<h2 id="5-테스트-및-검증">5. 테스트 및 검증</h2>
<h3 id="51-인프라-기동-테스트">5.1 인프라 기동 테스트</h3>
<ul>
<li><code>docker-compose up</code>으로 Milvus/Kafka/PG 모두 기동</li>
<li>전체 컨테이너가 정상 헬스체크 통과</li>
</ul>
<h3 id="52-milvusattu-검증">5.2 Milvus(Attu) 검증</h3>
<ul>
<li>생성된 다이어리 내용이 임베딩 벡터로 저장된 것을 GUI에서 확인</li>
</ul>
<h3 id="53-kafka-ui-검증">5.3 Kafka UI 검증</h3>
<ul>
<li><code>diary-events</code> 토픽 파티션 3개 생성 확인</li>
<li>Offset 증가 및 데이터 분산 확인</li>
<li>브로커 중단 시에도 DB 저장이 정상 완료되는지 Non-blocking 테스트 완료</li>
</ul>
<hr>
<h2 id="6-리뷰-포인트-정리">6. 리뷰 포인트 정리</h2>
<h3 id="61-벡터-저장-실패-시-처리-방식동기비동기">6.1 벡터 저장 실패 시 처리 방식(동기/비동기)</h3>
<p>현재는 <code>saveDiaryToVectorDB</code>에서 예외 발생 시 로그만 남기고 메인 트랜잭션을 유지한다.</p>
<ul>
<li>대안 1) <code>@Async</code>로 비동기 처리하여 저장 API 지연 최소화</li>
<li>대안 2) Kafka 이벤트를 발행하고, 별도 컨슈머가 Milvus 저장을 담당(완전 분리)</li>
<li>대안 3) 재시도/보상 트랜잭션(Dead Letter/재처리) 추가로 누락 방지</li>
</ul>
<hr>
<h3 id="62-임베딩-비용-최적화모델-분리">6.2 임베딩 비용 최적화(모델 분리)</h3>
<p>현재는 <code>gpt-4o</code>로 분석을 수행 중이며, 비용을 줄이기 위해 임베딩 전용 모델을 분리하는 방향을 검토 포인트로 둔다.</p>
<ul>
<li>분석(요약/감정/키워드) 모델</li>
<li>임베딩(벡터화) 전용 모델</li>
</ul>
<blockquote>
<p>목적: 동일 품질의 검색 성능을 유지하면서 비용을 낮출 수 있는 구조로 분리 가능성 확인</p>
</blockquote>
<hr>
<h3 id="63-트랜잭션-전파-레벨-및-파티션-개수확장성">6.3 트랜잭션 전파 레벨 및 파티션 개수(확장성)</h3>
<ul>
<li><code>TransactionalEventListener</code> + 전파 레벨 조합이 의도대로 트랜잭션을 분리하는지 검토 필요</li>
<li>파티션 3개가 현재 단계에서 적절한지(확장성/운영 복잡도 관점) 의견 공유 필요</li>
</ul>
<hr>
<h2 id="회고">회고</h2>
<p>Milvus와 Kafka는 처음 도입하는 구성이라 인프라부터 애플리케이션 코드까지 연결이 제대로 될지 걱정이 컸다. 특히 Kafka는 12월 23일에 먼저 시도했을 때 이벤트 발행이 되지 않거나 연결이 자꾸 끊기고, 메시지가 토픽에 쌓이지 않는 문제가 반복되어 “여기서 멈추는 게 맞나”라는 생각까지 들었었다. 이번에는 리스너 설정(외부/내부), 브로커 기동 순서, 프로듀서 설정, 그리고 트랜잭션 커밋 이후 발행 구조를 차근히 정리하면서 원인을 하나씩 제거해 나갔다. 결과적으로 Kafka UI에서 파티션과 오프셋이 증가하는 것을 확인했을 때, 단순히 기능이 붙었다는 수준이 아니라 시스템이 실제로 “흐른다”는 감각을 얻었다. Milvus도 Attu에서 임베딩이 들어오는 것을 확인하면서, 다이어리가 단순 저장 데이터를 넘어 검색 가능한 데이터 자산으로 확장될 수 있다는 방향이 명확해졌다. 전체적으로는 새로운 기술을 붙이는 과정에서 설정/네트워크/트랜잭션 같은 기본 요소를 정확히 이해하지 못하면 쉽게 막히고, 반대로 하나씩 구조화해서 접근하면 결국 해결된다는 경험을 얻었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발]34.[FE] UI 수정, 스타일 설정 및 일기 생성 프로세스 분리 연동, [BE] [Refactor] 다이어리 스타일 개별 저장 및 위치 기반 날씨 조회 기능 수정, AI 일기 생성 프로세스 분리 (미리보기 및 최종 저장 로직 구현),]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C35.FE-UI-%EC%88%98%EC%A0%95-%EC%8A%A4%ED%83%80%EC%9D%BC-%EC%84%A4%EC%A0%95-%EB%B0%8F-%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EB%B6%84%EB%A6%AC-%EC%97%B0%EB%8F%99-BE-Refactor-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%8A%A4%ED%83%80%EC%9D%BC-%EA%B0%9C%EB%B3%84-%EC%A0%80%EC%9E%A5-%EB%B0%8F-%EC%9C%84%EC%B9%98-%EA%B8%B0%EB%B0%98-%EB%82%A0%EC%94%A8-%EC%A1%B0%ED%9A%8C-%EA%B8%B0%EB%8A%A5-%EC%88%98%EC%A0%95-AI-%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EB%B6%84%EB%A6%AC-%EB%AF%B8%EB%A6%AC%EB%B3%B4%EA%B8%B0-%EB%B0%8F-%EC%B5%9C%EC%A2%85-%EC%A0%80%EC%9E%A5-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84-%EC%B9%B4%ED%94%84%EC%B9%B4-%EC%B5%9C%EC%A2%85-%EA%B5%AC%ED%98%84-%EC%84%B1%EA%B3%B5</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C35.FE-UI-%EC%88%98%EC%A0%95-%EC%8A%A4%ED%83%80%EC%9D%BC-%EC%84%A4%EC%A0%95-%EB%B0%8F-%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EB%B6%84%EB%A6%AC-%EC%97%B0%EB%8F%99-BE-Refactor-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%8A%A4%ED%83%80%EC%9D%BC-%EA%B0%9C%EB%B3%84-%EC%A0%80%EC%9E%A5-%EB%B0%8F-%EC%9C%84%EC%B9%98-%EA%B8%B0%EB%B0%98-%EB%82%A0%EC%94%A8-%EC%A1%B0%ED%9A%8C-%EA%B8%B0%EB%8A%A5-%EC%88%98%EC%A0%95-AI-%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EB%B6%84%EB%A6%AC-%EB%AF%B8%EB%A6%AC%EB%B3%B4%EA%B8%B0-%EB%B0%8F-%EC%B5%9C%EC%A2%85-%EC%A0%80%EC%9E%A5-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84-%EC%B9%B4%ED%94%84%EC%B9%B4-%EC%B5%9C%EC%A2%85-%EA%B5%AC%ED%98%84-%EC%84%B1%EA%B3%B5</guid>
            <pubDate>Fri, 02 Jan 2026 13:20:39 GMT</pubDate>
            <description><![CDATA[<h1 id="🏓-개발-기록-20251229-🏓">[🏓 개발 기록 2025.12.29 🏓]</h1>
<blockquote>
<h1 id="ai-일기-생성-프로세스-고도화-previewsave-분리-일기별-스타일-저장-위치-기반-실제-날씨-반영">AI 일기 생성 프로세스 고도화: Preview/Save 분리, 일기별 스타일 저장, 위치 기반 실제 날씨 반영</h1>
</blockquote>
<h2 id="개요">개요</h2>
<p>AI 일기 기능은 사용자가 <strong>키워드 + 이미지</strong>를 입력하면 AI가 초안을 생성하고, 사용자가 내용을 확인·수정한 뒤 저장 및 공유(소셜 피드)까지 이어지는 흐름을 제공한다.<br>기존 구현은 “요청 즉시 DB 생성” 구조에 가까워 사용자 확인 이전에 Diary 데이터가 생성되거나, 공유 시점에 이미지/ID가 불안정하게 참조되는 문제가 있었다.</p>
<p>이번 작업에서는 다음을 중심으로 <strong>워크플로우 및 데이터 모델을 재정비</strong>했다.</p>
<ul>
<li>AI 일기 생성 프로세스 <strong>Preview → Save</strong> 단계 분리</li>
<li>미리보기 단계에서 생성된 데이터를 최종 저장 단계에서 재사용하도록 API/DTO 재구성</li>
<li>일기 스타일 저장을 <strong>User/Pet 단위 → Diary 단위</strong>로 확장</li>
<li>이미지 기반 날씨 추정 방식에서 <strong>위치 기반 실제 날씨</strong> 조회 방식으로 전환</li>
<li>프론트에서 생성 안정성 및 공유 안정성(타임아웃, diaryId 동기화, Blob URL 제거) 강화</li>
</ul>
<hr>
<h2 id="1-기존-구조의-한계와-요구사항">1. 기존 구조의 한계와 요구사항</h2>
<h3 id="1-즉시-생성-방식의-문제">1) 즉시 생성 방식의 문제</h3>
<ul>
<li>사용자가 미리보기만 확인하고 저장하지 않는 경우에도 Diary 레코드가 생성되어 <strong>불필요한 데이터(쓰레기 데이터)</strong>가 누적될 수 있음</li>
<li>저장 직후 공유 시점에 <code>diaryId</code>가 상태에 반영되기 전이면 피드 생성이 실패하는 등 <strong>비동기 타이밍 이슈</strong>가 발생할 수 있음</li>
<li>이미지 처리에서 임시 URL(Blob URL)을 사용하면 새로고침 시 리소스가 유실되어 <strong>이미지 Not Found</strong> 문제가 발생할 수 있음</li>
</ul>
<h3 id="2-목표">2) 목표</h3>
<ul>
<li>사용자 확인 이전에는 Diary 테이블에 확정 데이터를 만들지 않되, 미리보기 결과를 최종 저장에서 재활용할 수 있어야 함</li>
<li>Mixed 입력(보관함 + 신규 업로드)에서도 이미지와 보관함 ID가 정확히 매핑되어야 함</li>
<li>스타일 및 날씨와 같은 부가 데이터를 “일기 단위”로 안정적으로 저장할 수 있어야 함</li>
</ul>
<hr>
<h2 id="2-백엔드-ai-일기-생성-프로세스-previewsave-분리">2. 백엔드: AI 일기 생성 프로세스 Preview/Save 분리</h2>
<h3 id="21-미리보기-로직-추가-previewaidiary">2.1 미리보기 로직 추가: <code>previewAiDiary</code></h3>
<p>미리보기 단계는 사용자가 입력한 <strong>키워드 + 사진</strong>을 기반으로 AI 초안을 생성하고, 결과를 반환한다.</p>
<ul>
<li>신규 업로드 이미지는 <strong>즉시 S3 및 보관함(Archive)에 저장</strong></li>
<li>단, 미리보기 단계에서는 <strong>Diary 테이블에 레코드를 생성하지 않음</strong><br>→ 저장하지 않는 사용자의 시나리오에서 DB 쓰레기 데이터 방지</li>
<li>반환 데이터:<ul>
<li>AI 분석/생성 결과(초안 텍스트)</li>
<li>이미지 URL 목록</li>
<li>Archive ID 목록(또는 이미지별 archiveId)</li>
</ul>
</li>
</ul>
<p><strong>핵심 의도:</strong><br>Preview 단계에서 생성된 “확정 가능한 리소스(이미지)”는 선저장하고, “확정 불가능한 도메인 데이터(Diary)”는 Save 단계에서만 생성한다.</p>
<hr>
<h3 id="22-최종-저장-로직-구현-savediary">2.2 최종 저장 로직 구현: <code>saveDiary</code></h3>
<p>Save 단계는 Preview 단계에서 반환된 데이터 + 사용자가 수정한 최종 텍스트를 결합해 영구 저장한다.</p>
<ul>
<li>Preview 응답(이미지 URL/Archive ID/초안 등) + 사용자 수정 텍스트를 받아 Diary를 생성</li>
<li>최종 데이터 보정 로직 포함:<ul>
<li>날씨 정보 보정</li>
<li>위치 주소 변환 로직(좌표 → 주소 등)</li>
</ul>
</li>
</ul>
<hr>
<h3 id="23-api-구조-변경-요약">2.3 API 구조 변경 요약</h3>
<ul>
<li><strong>기존:</strong> 요청 즉시 생성(생성 = 저장)</li>
<li><strong>변경:</strong> 사용자 확인 후 저장(생성 ≠ 저장)<ul>
<li><code>/ai/preview</code> : 미리보기(초안 생성, 이미지 선저장, Diary 미생성)</li>
<li><code>/diaries</code> : 최종 저장(Preview 데이터 재사용 + 최종 텍스트 반영)</li>
</ul>
</li>
</ul>
<hr>
<h2 id="3-백엔드-다이어리-스타일-저장-구조-개선-diary-단위">3. 백엔드: 다이어리 스타일 저장 구조 개선 (Diary 단위)</h2>
<h3 id="31-요구-변경">3.1 요구 변경</h3>
<ul>
<li><strong>기존:</strong> 사용자(User) 또는 펫(Pet) 단위로 스타일 저장</li>
<li><strong>변경:</strong> 각 일기(Diary)마다 독립적인 스타일 저장</li>
</ul>
<h3 id="32-구현-변경">3.2 구현 변경</h3>
<ul>
<li><code>DiaryStyleRequest</code> 및 <code>DiaryStyle</code> Entity에 <code>diaryId</code> 필드 추가</li>
<li><code>DiaryStyleService.createOrUpdateStyle</code> 로직 수정<ul>
<li>요청에 <code>diaryId</code>가 포함되면 해당 다이어리에 종속된 스타일을 생성/수정</li>
</ul>
</li>
<li><code>diary_style</code> 테이블에 <code>diary_id</code> 컬럼 추가(마이그레이션 필요)</li>
</ul>
<p><strong>효과</strong></p>
<ul>
<li>동일 사용자/펫이라도 일기마다 다른 레이아웃/폰트/테마 적용 가능</li>
<li>“저장하고 공유하기” 흐름에서 일기 ID를 기준으로 스타일을 안정적으로 매핑 가능</li>
</ul>
<hr>
<h2 id="4-백엔드-위치-기반-실제-날씨-반영">4. 백엔드: 위치 기반 실제 날씨 반영</h2>
<h3 id="41-문제">4.1 문제</h3>
<p>AI가 이미지를 기반으로 날씨를 추정하는 방식은 정확도와 일관성이 낮을 수 있다.</p>
<h3 id="42-변경">4.2 변경</h3>
<ul>
<li><code>DiaryController.previewAiDiary</code> 파라미터에 <code>latitude</code>, <code>longitude</code>, <code>date</code> 추가</li>
<li>AI 분석 이후 <code>WeatherService</code>를 호출하여 외부 기상청 데이터(예: “맑음”, “구름많음”)를 조회</li>
<li>조회된 실제 날씨로 AI 추정 결과를 덮어씌우도록 처리</li>
</ul>
<p><strong>리뷰/운영 포인트</strong></p>
<ul>
<li>외부 API 의존성(키 설정, 호출 실패, 타임아웃)으로 인해 예외 처리/폴백 정책이 중요</li>
<li>로컬 테스트 시 API Key 등 환경 설정 확인 필요</li>
</ul>
<hr>
<h2 id="5-프론트엔드-api-연동-및-안정성-강화">5. 프론트엔드: API 연동 및 안정성 강화</h2>
<h3 id="51-스타일-저장-api-연동">5.1 스타일 저장 API 연동</h3>
<ul>
<li>“저장하고 공유하기” 클릭 시, 사용자가 편집한 스타일 설정을 서버로 전송하여 저장<ul>
<li><code>POST /api/v1/diary/styles</code></li>
</ul>
</li>
<li><code>diaryId</code>를 포함하여 <strong>일기 단위 스타일 매핑</strong>이 되도록 연동 로직 고도화</li>
</ul>
<hr>
<h3 id="52-위치-기반-실제-날씨-연동">5.2 위치 기반 실제 날씨 연동</h3>
<ul>
<li>프론트에서 미리보기 요청 시 실제 위치 정보(위도/경도/날짜)를 백엔드로 전달</li>
<li>백엔드가 반환한 “실제 날씨”를 최종 저장 데이터에 반영하도록 플로우 정리</li>
</ul>
<hr>
<h3 id="53-ai-생성-안정성-확보-타임아웃-조정">5.3 AI 생성 안정성 확보 (타임아웃 조정)</h3>
<ul>
<li><code>generateAiDiaryPreview</code> API 타임아웃을 <strong>10초 → 60초</strong>로 조정</li>
<li>생성 시간이 길어질 때 발생하던 <code>ECONNABORTED</code>를 완화</li>
</ul>
<hr>
<h3 id="54-소셜-피드-공유-안정화">5.4 소셜 피드 공유 안정화</h3>
<ul>
<li>일기 저장 직후 피드 공유 시 <code>diaryId</code>가 상태에 반영되지 않아 실패하던 문제 수정<br>→ 저장 결과(응답) 기반으로 공유 단계에서 diaryId를 안정적으로 사용하도록 흐름 개선</li>
<li>피드 이미지 URL을 Blob URL에서 <strong>S3 영구 URL</strong>로 전환<ul>
<li>새로고침 시 이미지가 깨지는 문제(Not Found) 해결</li>
</ul>
</li>
</ul>
<hr>
<h2 id="6-테스트-결과">6. 테스트 결과</h2>
<h3 id="백엔드">백엔드</h3>
<ul>
<li><code>/ai/preview</code> 및 <code>/diaries</code>(최종 저장) API 호출 및 데이터 저장 확인
<img src="https://velog.velcdn.com/images/seolhxx_/post/a00e09ec-0612-4f91-a005-89c85649fd45/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/085f508d-f89a-4deb-b2cf-bd24231227f1/image.png" alt=""></li>
<li>diaryId 기반 스타일 저장/조회 테스트 완료
<img src="https://velog.velcdn.com/images/seolhxx_/post/d578bbad-170a-4873-b5ee-eed517c9e3a0/image.png" alt=""></li>
<li>위도/경도 전달 시 실제 날씨 반환 확인</li>
</ul>
<h3 id="프론트">프론트</h3>
<ul>
<li>AI 다이어리 생성 및 스타일 저장 정상 동작 확인</li>
<li>위치 정보 전송 및 실제 날씨 반영 확인(예: “구름많음”)</li>
<li>소셜 피드 공유 후 새로고침 시 이미지 정상 출력 확인</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/8b0b5127-41ad-44f9-9f94-f576747b1585/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/69b2c00a-57df-4bcc-af2d-8c6319fab985/image.png" alt="">
<img src="https://velog.velcdn.com/images/seolhxx_/post/fab2dcc9-ab84-4e83-a576-c48b6956ae1c/image.png" alt=""></p>
<hr>
<h2 id="7-리뷰">7. 리뷰</h2>
<h3 id="71-preview-시점-이미지-선저장보관함-저장--save-시-id-연결-방식의-효율성">7.1 Preview 시점 이미지 선저장(보관함 저장) + Save 시 ID 연결 방식의 효율성</h3>
<ul>
<li>장점: Preview 단계에서 결과 재사용이 가능하고, Save 단계에서는 ID 연결 중심으로 단순화됨</li>
<li>고려사항:<ul>
<li>Preview만 하고 Save하지 않는 경우, 선저장된 이미지가 남을 수 있으므로 정리 정책(만료/삭제)이 필요할 수 있음</li>
<li>이미지 URL/Archive ID의 유효기간(서명 URL 등)과 재사용 가능 범위를 명확히 해야 함</li>
</ul>
</li>
</ul>
<h3 id="72-preview-↔-save-데이터-전달-과정의-필드-유실-가능성">7.2 Preview ↔ Save 데이터 전달 과정의 필드 유실 가능성</h3>
<ul>
<li>Preview 응답에서 Save 요청까지 반드시 유지되어야 하는 필드:<ul>
<li>이미지 URL / archiveId(리스트 또는 이미지별 매핑)</li>
<li>위치 정보(좌표/날짜) 및 날씨 보정에 필요한 값</li>
<li>AI 초안/분석 결과(최종 텍스트와 결합하는 기준)</li>
</ul>
</li>
<li>특히 Mixed 입력인 경우 “이미지별 매핑”이 정확히 유지되는지 점검 필요</li>
</ul>
<hr>
<h2 id="회고">회고</h2>
<p>이번 작업에서는 AI 일기 생성 플로우를 <strong>미리보기와 최종 저장으로 분리</strong>하면서, 사용자가 확정하기 전 단계의 데이터와 확정 이후 데이터의 성격이 다르다는 점을 명확히 정리할 수 있었다. 미리보기 단계에서 이미지를 선저장하고 ID로 연결하는 구조는 흐름을 안정화했지만, 중간 데이터가 어떻게 전달되고 유지되는지가 전체 품질을 좌우한다는 걸 느꼈다. 스타일 저장을 diaryId 단위로 확장한 과정에서는 단순 기능 추가처럼 보여도 DB/서비스 로직까지 함께 바뀌면서 영향 범위가 넓어질 수 있음을 경험했다. 위치 기반 실제 날씨를 붙이면서 외부 API 의존성이 생기면 예외 처리와 환경 설정이 기능만큼 중요하다는 것도 확인했다. 프론트에서는 타임아웃과 공유 시점 상태 동기화처럼 “사용자가 체감하는 안정성”이 작은 설정/흐름 차이에서 크게 달라진다는 점을 배웠다. 전체적으로는 기능 구현뿐 아니라 API 설계와 데이터 흐름을 함께 정리해야 개발과 운영 모두에서 안정적인 결과를 만들 수 있다는 걸 체감했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발]33. [FE] 사진선택 시 보관함 연동 및 UI 수정, [BE] AI 일기 생성 로직 고도화 및 다중 이미지 매핑 구조 도입]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C33.-FE-%EC%82%AC%EC%A7%84%EC%84%A0%ED%83%9D-%EC%8B%9C-%EB%B3%B4%EA%B4%80%ED%95%A8-%EC%97%B0%EB%8F%99-%EB%B0%8F-UI-%EC%88%98%EC%A0%95-BE-AI-%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%EB%A1%9C%EC%A7%81-%EA%B3%A0%EB%8F%84%ED%99%94-%EB%B0%8F-%EB%8B%A4%EC%A4%91-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A7%A4%ED%95%91-%EA%B5%AC%EC%A1%B0-%EB%8F%84%EC%9E%85</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C33.-FE-%EC%82%AC%EC%A7%84%EC%84%A0%ED%83%9D-%EC%8B%9C-%EB%B3%B4%EA%B4%80%ED%95%A8-%EC%97%B0%EB%8F%99-%EB%B0%8F-UI-%EC%88%98%EC%A0%95-BE-AI-%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%EB%A1%9C%EC%A7%81-%EA%B3%A0%EB%8F%84%ED%99%94-%EB%B0%8F-%EB%8B%A4%EC%A4%91-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A7%A4%ED%95%91-%EA%B5%AC%EC%A1%B0-%EB%8F%84%EC%9E%85</guid>
            <pubDate>Fri, 02 Jan 2026 13:19:31 GMT</pubDate>
            <description><![CDATA[<h1 id="🦖-개발-기록-20251227-🦖">[🦖 개발 기록 2025.12.27 🦖]</h1>
<blockquote>
<h1 id="ai-diary-생성-기능-고도화-프론트-모듈화부터-백엔드-nm-매핑멀티파트-처리까지">AI Diary 생성 기능 고도화: 프론트 모듈화부터 백엔드 N:M 매핑/멀티파트 처리까지</h1>
</blockquote>
<h2 id="개요">개요</h2>
<p>AI Diary 기능은 사용자가 이미지를 입력하면 AI 분석 결과를 기반으로 일기를 생성하고 저장하는 플로우를 제공한다.<br>이미지 입력은 <strong>보관함(Archive) 선택</strong>과 <strong>신규 파일 업로드(Gallery)</strong> 를 지원하며, 실사용 시에는 두 방식이 혼합되는 케이스가 빈번하다.</p>
<p>이번 작업은 다음 범위를 포함한다.</p>
<ul>
<li>프론트엔드: 페이지 구조 리팩토링 및 멀티파트 요청 구성 안정화</li>
<li>백엔드: Diary–Archive 관계 재설계(N:M), AI 분석 파이프라인 단순화, DTO/컨트롤러 인터페이스 확장</li>
</ul>
<hr>
<h2 id="1-시스템-요구사항-정리">1. 시스템 요구사항 정리</h2>
<h3 id="1-입력-시나리오">1) 입력 시나리오</h3>
<ul>
<li><strong>Archive only:</strong> 보관함 사진 단일/다중 선택</li>
<li><strong>Gallery only:</strong> 로컬 파일 업로드</li>
<li><strong>Mixed:</strong> 보관함 사진 + 로컬 파일 혼합</li>
</ul>
<h3 id="2-핵심-제약">2) 핵심 제약</h3>
<ul>
<li>MSA 구조상 diary-service는 archive-service의 엔티티를 직접 참조하지 않음</li>
<li>DB 제약 및 저장 일관성(매핑 테이블 포함)을 보장해야 함</li>
<li>멀티파트 요청에서 파일 파트가 없을 수 있음(Archive only)</li>
</ul>
<hr>
<h2 id="2-프론트엔드-변경-사항">2. 프론트엔드 변경 사항</h2>
<h3 id="21-ai-diary-페이지-리팩토링-및-모듈화">2.1 AI Diary 페이지 리팩토링 및 모듈화</h3>
<p>기존 <code>AiDiaryPage.tsx</code>에 UI/상태/네트워크 로직이 집중되어 있어 변경 영향도가 컸다.<br>이를 다음과 같이 분리하였다.</p>
<ul>
<li>UI 컴포넌트 분리: <code>KakaoMap</code>, <code>UploadStep</code>, <code>GeneratingStep</code> 등 → <code>src/features/diary/components/</code></li>
<li>API 로직 분리: <code>diary-api.ts</code></li>
<li>Custom Hook 분리: <code>useDiaryAuth.ts</code></li>
</ul>
<p><strong>효과</strong></p>
<ul>
<li>페이지 컴포넌트의 역할을 “플로우 오케스트레이션”으로 제한</li>
<li>단계별 UI 변경이 다른 로직에 전파되는 범위를 축소</li>
<li>API/인증 로직 수정 지점이 명확해져 회귀 버그 가능성 감소</li>
</ul>
<hr>
<h3 id="22-다이어리-생성-요청멀티파트-구조-개선-및-오류-수정">2.2 다이어리 생성 요청(멀티파트) 구조 개선 및 오류 수정</h3>
<p>혼합 입력을 지원하는 과정에서 멀티파트 구성 불일치로 다수 오류가 발생했다.</p>
<ul>
<li><p><strong>400 Bad Request</strong></p>
<ul>
<li>원인: Archive/Gallery 모드 분기 처리로 <code>imageFiles</code> 파트 구성이 충돌</li>
<li>조치: 입력을 “하나의 이미지 리스트”로 정규화하고, 동일 규칙으로 멀티파트를 구성하도록 개선</li>
</ul>
</li>
<li><p><strong>415 Unsupported Media Type</strong></p>
<ul>
<li>원인: multipart 요청의 Content-Type 처리 불명확</li>
<li>조치: API 호출 시 <code>Content-Type: multipart/form-data</code> 명시(라이브러리 boundary 처리 방식 고려)</li>
</ul>
</li>
<li><p><strong>500 Internal Server Error (DB Constraint)</strong></p>
<ul>
<li>원인: <code>Diaries.photo_archive_id</code> Not Null 제약과 업로드 케이스에서의 값 누락</li>
<li>조치: 백엔드 매핑 정책 변경에 맞추어, 프론트도 “이미지 단위 archiveId 포함” 방식으로 전환하여 저장 정합성 확보</li>
</ul>
</li>
</ul>
<hr>
<h3 id="23-혼합-콘텐츠-지원을-위한-페이로드-변경">2.3 혼합 콘텐츠 지원을 위한 페이로드 변경</h3>
<p>기존: 단일 <code>photoArchiveId</code> 전송<br>문제: Mixed 시나리오에서 “이미지별 매핑 정보”를 표현할 수 없음</p>
<p>개선: <code>images</code> 리스트의 각 항목에 <code>archiveId</code>를 포함하도록 변경<br>→ Mixed에서도 이미지 단위로 정확한 매핑을 서버에 전달 가능</p>
<hr>
<h2 id="3-백엔드-변경-사항">3. 백엔드 변경 사항</h2>
<h3 id="31-다중-이미지-매핑을-위한-diaryarchive-엔티티-도입">3.1 다중 이미지 매핑을 위한 DiaryArchive 엔티티 도입</h3>
<p>기존 Diary 엔티티의 단일 <code>photoArchiveId</code> 필드는 1:N 또는 N:M 요구를 충족하기 어렵고, 혼합 입력에서 모델 확장이 제한적이었다.</p>
<ul>
<li><code>Diary.photoArchiveId</code> 제거</li>
<li>Diary–Archive 관계를 위한 <strong>DiaryArchive 매핑 테이블 생성(N:M)</strong></li>
<li>MSA 고려: archive-service 엔티티를 직접 참조하지 않고 <code>archiveId(Long)</code>만 저장</li>
</ul>
<p><strong>의도</strong></p>
<ul>
<li>서비스 간 강결합을 피하고, diary-service가 독립적으로 저장 모델을 유지</li>
<li>이미지 단위로 다중 매핑(단일 일기 ↔ 여러 보관함 사진) 지원</li>
</ul>
<hr>
<h3 id="32-ai-일기-생성-서비스-로직diaryserviceimpl-고도화">3.2 AI 일기 생성 서비스 로직(DiaryServiceImpl) 고도화</h3>
<h4 id="1-이미지-혼합-처리">1) 이미지 혼합 처리</h4>
<p>보관함 사진과 업로드 사진을 각각 다른 흐름으로 처리하면 예외 케이스가 증가한다.<br>따라서 “입력 이미지 소스”를 하나의 리스트로 통합 관리하도록 변경했다.</p>
<h4 id="2-ai-분석-로직-단순화url-기반-통합">2) AI 분석 로직 단순화(URL 기반 통합)</h4>
<p>기존: 소스별(보관함/업로드) 분석 경로가 분기될 가능성이 있음<br>개선: 모든 이미지 소스를 URL 기반으로 통합하여 <code>generateContentWithAiFromUrls</code>로 처리</p>
<p><strong>효과</strong></p>
<ul>
<li>AI 분석 파이프라인이 단일 경로로 수렴</li>
<li>소스 타입 확장(추후 외부 링크 입력 등) 시 확장 지점이 명확</li>
</ul>
<h4 id="3-빈-파일-리스트-예외-방지file_001">3) 빈 파일 리스트 예외 방지(FILE_001)</h4>
<p>Archive only 요청처럼 파일이 없을 수 있는 상황에서 빈 파일 리스트가 전달되면 FILE_001이 발생했다.</p>
<ul>
<li><code>isActualFilePresent</code> 유효성 검사 로직 추가</li>
<li>“실제 파일이 있는 경우에만” 파일 처리 분기를 수행하도록 보강</li>
</ul>
<hr>
<h3 id="33-데이터-추적용-메타데이터-추가">3.3 데이터 추적용 메타데이터 추가</h3>
<p><code>DiaryArchive</code> 엔티티에 다음을 추가하여 생성/수정 시점을 추적 가능하게 했다.</p>
<ul>
<li><code>@CreationTimestamp</code></li>
<li><code>@UpdateTimestamp</code></li>
</ul>
<p>운영/분석 관점에서 “매핑 관계의 생성/변경 이력” 추적이 가능해진다.</p>
<hr>
<h3 id="34-dto-및-컨트롤러-인터페이스-수정">3.4 DTO 및 컨트롤러 인터페이스 수정</h3>
<ul>
<li><code>DiaryRequest.Create.Image</code> DTO에 <code>archiveId</code> 필드 추가<ul>
<li>클라이언트가 이미지 단위로 매핑 정보를 전달 가능</li>
</ul>
</li>
<li>컨트롤러에서 <code>imageFiles</code>를 optional(<code>required = false</code>)로 변경<ul>
<li>보관함 사진만으로도 일기 생성 가능(Archive only 지원)</li>
</ul>
</li>
</ul>
<hr>
<h2 id="4-통합-시나리오-및-테스트-결과">4. 통합 시나리오 및 테스트 결과</h2>
<h3 id="프론트">프론트</h3>
<ul>
<li>로그인, 펫 선택, 일기 생성 및 저장 플로우 정상 동작 확인</li>
<li>파일 업로드 / 보관함 선택 / 혼합 선택 시나리오 검증</li>
</ul>
<h3 id="백엔드">백엔드</h3>
<ul>
<li>보관함 사진 단일/다중 선택 시 일기 생성 확인</li>
<li>신규 갤러리 파일 업로드 시 유저 서비스 연동 및 보관함 자동 저장 확인</li>
<li>혼합 선택 시 통합 저장 및 매핑 테이블 반영 확인</li>
</ul>
<hr>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/2d0b26d1-e6f5-4e0c-9f3c-e1258c2842ac/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/8365a381-3b49-4314-8d20-819d0b920055/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/192a7150-8725-40f0-b59f-a0d096e9f304/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/f93ee560-ebc0-4154-8598-5d845595966a/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/05a52016-4aca-46d4-8e1a-583b63156d05/image.png" alt=""></p>
<h2 id="5-리뷰-포인트">5. 리뷰 포인트</h2>
<h3 id="1-msa에서-archiveidlong-참조-방식의-적절성">1) MSA에서 archiveId(Long) 참조 방식의 적절성</h3>
<ul>
<li>diary-service가 archive 엔티티를 직접 참조하지 않고, 식별자만 저장하는 방식은 서비스 결합도를 낮춘다.</li>
<li>다만 다음 운영 이슈를 고려해야 한다.<ul>
<li>archiveId의 정합성(존재 여부)은 런타임 검증 또는 이벤트 기반 동기화로 보완 필요</li>
<li>삭제/병합 등 archive 측 변경 이벤트 발생 시 매핑 테이블 정리 정책 필요</li>
</ul>
</li>
</ul>
<h3 id="2-url-기반-ai-분석-통합의-예외-처리">2) URL 기반 AI 분석 통합의 예외 처리</h3>
<p>URL 기반 분석 단일화는 로직을 단순화하지만, 예외 케이스를 명시적으로 다루는 것이 중요하다.</p>
<ul>
<li>URL 만료/권한 문제(서명 URL, 만료 시간)</li>
<li>네트워크 오류/타임아웃</li>
<li>이미지 포맷/용량 제한</li>
<li>부분 실패 시 정책(전체 실패 vs 가능한 범위로 생성)</li>
</ul>
<p>현재 구현이 어떤 정책을 채택했는지(예: 하나라도 실패하면 중단, 실패한 이미지만 제외 등)를 문서화해두면 운영 대응이 쉬워진다.</p>
<hr>
<h2 id="6-회고-약-6줄">6. 회고 (약 6줄)</h2>
<p>혼합 입력(Mixed)은 단순히 UI 옵션을 늘리는 수준이 아니라, 요청 구조와 DB 모델까지 함께 맞춰야 안정적으로 동작한다는 점을 체감했다. 그리고 단일 photoArchiveId 방식에서 N:M 매핑으로 전환하면서, “데이터가 어떻게 연결되는지”를 엔티티/테이블 기준으로 더 명확하게 이해하게 됐다. 또한 400/415/500 오류를 각각 따라가면서, 증상보다 “요청 형식과 인터페이스가 일치하는지”를 먼저 점검하는 접근이 중요하다는 걸 배웠다.</p>
<p>프론트에서는 입력 데이터를 하나의 리스트로 정규화하고, 백엔드에서는 이를 그대로 수용하는 구조로 맞추는 과정이 가장 핵심이었다. 그리고 MSA 환경에서는 다른 서비스의 엔티티를 직접 참조하지 않는 설계가 필요하고, 그 대신 식별자 기반으로 관계를 표현하는 방법을 익혔다. 이번 개발을 통해 전체적으로 기능 구현뿐 아니라 구조(모듈화, DTO/엔티티 설계, 데이터 매핑)를 함께 개선해야 유지보수가 쉬워진다는 걸 경험했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발][멘토링]4차 멘토링]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C%EB%A9%98%ED%86%A0%EB%A7%814%EC%B0%A8-%EB%A9%98%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C%EB%A9%98%ED%86%A0%EB%A7%814%EC%B0%A8-%EB%A9%98%ED%86%A0%EB%A7%81</guid>
            <pubDate>Fri, 02 Jan 2026 13:17:47 GMT</pubDate>
            <description><![CDATA[<h1 id="🥕-20251220-🥕">🥕 2025.12.20 🥕</h1>
<h4 id="개발-기록-beai-ai-일기생성-시-사진-한장만-분석---모든-사진-분석되도록-수정">[개발 기록] [BE/AI] AI 일기생성 시 사진 한장만 분석 -&gt; 모든 사진 분석되도록 수정</h4>
<hr>
<p>이번 4차 멘토링은 프로젝트의 마무리 단계에서 우리가 구축한 기술적 성과를 어떻게 &#39;언어&#39;와 &#39;시각&#39;으로 전달할 것인가에 대한 치열한 코칭이었습니다. 단순히 &quot;열심히 만들었다&quot;가 아니라, <strong>&quot;우리가 왜 이렇게 설계했고, 이 과정이 공학적으로 어떤 의미를 갖는지&quot;</strong>를 증명하는 법을 배웠습니다.</p>
<p>특히 이번에는 직접 참여하며 멘토님의 날카로운 피드백을 현장에서 수용할 수 있었는데, 지난 시간들에 참여하지 못했던 것이 더 큰 아쉬움으로 다가올 만큼 밀도 높은 시간이었습니다. 4차 멘토링의 핵심 내용과 제가 느낀 점을 정리해 봅니다.</p>
<h1 id="4차-멘토링과-최종-설계-고도화">4차 멘토링과 최종 설계 고도화</h1>
<h2 id="1-조직의-재구성-이름이-아닌-역할-중심의-스크럼">1. 조직의 재구성: 이름이 아닌 &#39;역할&#39; 중심의 스크럼</h2>
<p>프로젝트 팀 구성 섹션에서 단순히 팀원 이름을 나열하는 방식에서 벗어나, 역할 중심의 조직도로 재편했습니다.</p>
<ul>
<li><p>구조: 스크럼 마스터를 필두로 프론트엔드, 백엔드, 배포(Infra), AI 파트별 리더를 지정하여 각 도메인의 기술 표준과 책임을 명확히 했습니다.</p>
</li>
<li><p>전달 방식: 이론적인 애자일 설명보다는 실제 Jira 화면과 스토리 포인트를 어떻게 측정했는지, 번다운 차트를 통해 스프린트를 어떻게 개선해 나갔는지에 대한 &#39;데이터&#39;를 전면에 배치하기로 했습니다.</p>
</li>
</ul>
<h2 id="2-아키텍처의-가독성과-논리-실선과-점선의-차이">2. 아키텍처의 가독성과 논리: 실선과 점선의 차이</h2>
<p>아키텍처 설계에서도 &#39;표현의 정교함&#39;이 강조되었습니다.</p>
<ul>
<li><p>통신 규약의 시각화: 백엔드 아키텍처에서 서비스 간 동기(Sync) 호출은 실선으로, 비동기(Async) 호출은 점선으로 구분하여 데이터 흐름의 성격을 한눈에 파악할 수 있도록 했습니다.</p>
</li>
<li><p>AI &amp; RAG 아키텍처: 기록 서비스에서 외부 LLM을 어떻게 호출하는지, 벡터 DB를 어떻게 구축했는지 등 RAG(Retrieval-Augmented Generation) 관점의 경로를 구체적으로 그리는 데 집중했습니다. 불필요한 프론트 구조는 덜어내고 백엔드와 외부 API 간의 상호작용을 강조했습니다.</p>
</li>
</ul>
<h2 id="3-인프라와-배포-패킷의-경로를-증명하는-설계">3. 인프라와 배포: 패킷의 경로를 증명하는 설계</h2>
<p>인프라 섹션에서는 클러스터 내부의 복잡한 선들을 정리하고, 실질적인 트래픽의 In/Outbound 경로를 표시하는 데 주력했습니다.</p>
<ul>
<li><p>EKS 클러스터: 레플리카 간의 불필요한 연결 대신, 트래픽이 합쳐지는 경로를 단순화하여 가독성을 높였습니다.</p>
</li>
<li><p>기술적 선택: Kafka의 경우 Docker 이미지로 띄워 운영 효율성을 높이기로 결정했고, Redis 도입 여부 등 남은 기술적 의사결정을 ADR(Architecture Decision Record)에 반영하여 근거를 남기기로 했습니다.</p>
</li>
</ul>
<h2 id="4-발표-전략-육하원칙5w1h에-기반한-how의-증명">4. 발표 전략: 육하원칙(5W1H)에 기반한 How의 증명</h2>
<p>발표 장표는 <strong>&quot;What -&gt; Who -&gt; How&quot;</strong>의 순서로 구성하여 청중이 기술적 흐름을 놓치지 않게 설계하도록 하는 것입니다.</p>
<ul>
<li><p>HOW의 강조: 방법론(스크럼)부터 시작해 기술 스택, 아키텍처(BE-&gt;인프라-&gt;AI), 상세 설계(API, UI, DB), 코드 표준까지 이어지는 흐름을 이미지 중심으로 배치했습니다.</p>
</li>
<li><p>시각적 압도: 글씨를 키우고 텍스트를 줄이는 대신, 우리가 고민한 흔적이 담긴 아키텍처 그림과 UI 프레임워크 이미지를 적극 활용하여 효율적인 PPT를 지향했습니다.</p>
</li>
</ul>
<h3 id="💡-멘토링-회고-및-느낀-점">💡 멘토링 회고 및 느낀 점</h3>
<p>이번 멘토링에 직접 참여하며 느낀 것은, 엔지니어에게 &#39;구현 능력&#39;만큼 중요한 것이 <strong>&#39;자신의 설계를 논리적으로 표현하고 PR하는 능력&#39;</strong>이라는 사실이었습니다. 멘토님께서 강조하신 &quot;발표와 문서 작성을 잘해야 연봉이 올라간다&quot;는 말씀이 단순히 농담이 아니라, 기술적 가치를 타인에게 전달하는 역량의 중요성을 일깨워주셨습니다.</p>
<p>직접 참여해보니 멘토님의 피드백 하나하나가 프로젝트의 퀄리티를 수직 상승시키는 것을 경험할 수 있었고, 진작 참여하지 못했던 지난 시간들이 더욱 아쉽게 느껴졌습니다. 하지만 남은 최종 시연까지 멘토님의 가이드대로 육하원칙에 따라 우리의 과정을 투명하게 증명하여, 단순한 &#39;과제물&#39;이 아닌 &#39;진짜 서비스&#39;를 세상에 내보이겠다는 각오를 다지게 된 소중한 시간이었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발]32. [FE] AI 다이어리 소셜 피드 공유  기능 연동, 다중 이미지 전송 로직 구현 및 미리보기 메모리 관리(Blob) 오류 수정, [BE] 회원서비스의 s3 및 보관함 연동 일기 이미지 S3 업로드 자동화 및 다중 이미지 저장 로직 구현]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C32.-FE-AI-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%86%8C%EC%85%9C-%ED%94%BC%EB%93%9C-%EA%B3%B5%EC%9C%A0-%EA%B8%B0%EB%8A%A5-%EC%97%B0%EB%8F%99-%EA%B3%B5%EA%B0%9C-%EB%B2%94%EC%9C%84-%EC%84%A4%EC%A0%95-%EA%B5%AC%ED%98%84-%EC%86%8C%EC%85%9C-%ED%94%BC%EB%93%9C-%EC%97%85%EB%A1%9C%EB%93%9C-%EB%B0%A9%EC%8B%9D-%EB%B3%80%EA%B2%BD-Multipart-JSON-%EB%8B%A4%EC%A4%91-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%A0%84%EC%86%A1-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84-%EB%B0%8F-%EB%AF%B8%EB%A6%AC%EB%B3%B4%EA%B8%B0-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B4%80%EB%A6%ACBlob-%EC%98%A4%EB%A5%98-%EC%88%98%EC%A0%95-BE-%ED%9A%8C%EC%9B%90%EC%84%9C%EB%B9%84%EC%8A%A4%EC%9D%98-s3-%EB%B0%8F-%EB%B3%B4%EA%B4%80%ED%95%A8-%EC%97%B0%EB%8F%99-%EC%9D%BC%EA%B8%B0-%EC%9D%B4%EB%AF%B8%EC%A7%80-S3-%EC%97%85%EB%A1%9C%EB%93%9C-%EC%9E%90%EB%8F%99%ED%99%94-%EB%B0%8F-%EB%8B%A4%EC%A4%91-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%A0%80%EC%9E%A5-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C32.-FE-AI-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%86%8C%EC%85%9C-%ED%94%BC%EB%93%9C-%EA%B3%B5%EC%9C%A0-%EA%B8%B0%EB%8A%A5-%EC%97%B0%EB%8F%99-%EA%B3%B5%EA%B0%9C-%EB%B2%94%EC%9C%84-%EC%84%A4%EC%A0%95-%EA%B5%AC%ED%98%84-%EC%86%8C%EC%85%9C-%ED%94%BC%EB%93%9C-%EC%97%85%EB%A1%9C%EB%93%9C-%EB%B0%A9%EC%8B%9D-%EB%B3%80%EA%B2%BD-Multipart-JSON-%EB%8B%A4%EC%A4%91-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%A0%84%EC%86%A1-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84-%EB%B0%8F-%EB%AF%B8%EB%A6%AC%EB%B3%B4%EA%B8%B0-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B4%80%EB%A6%ACBlob-%EC%98%A4%EB%A5%98-%EC%88%98%EC%A0%95-BE-%ED%9A%8C%EC%9B%90%EC%84%9C%EB%B9%84%EC%8A%A4%EC%9D%98-s3-%EB%B0%8F-%EB%B3%B4%EA%B4%80%ED%95%A8-%EC%97%B0%EB%8F%99-%EC%9D%BC%EA%B8%B0-%EC%9D%B4%EB%AF%B8%EC%A7%80-S3-%EC%97%85%EB%A1%9C%EB%93%9C-%EC%9E%90%EB%8F%99%ED%99%94-%EB%B0%8F-%EB%8B%A4%EC%A4%91-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%A0%80%EC%9E%A5-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Fri, 02 Jan 2026 13:15:13 GMT</pubDate>
            <description><![CDATA[<h1 id="🍹-개발-기록-20251219-🍹">[🍹 개발 기록 2025.12.19 🍹]</h1>
<p>이번 업데이트에서는 사용자가 정성스럽게 작성한 AI 일기를 소셜 피드에 공유하고, 이 과정에서 발생하는 대용량 이미지 데이터를 안정적으로 처리하는 시스템을 구축했습니다. S3 자동화 업로드부터 마이크로서비스 간 통신(Feign Client), 그리고 브라우저 메모리 관리까지, 기술적으로 도전적이었던 부분들을 정리해 봅니다.</p>
<blockquote>
<h1 id="ai-일기에서-소셜-피드로-이미지-파이프라인-자동화와-공유-기능-구현기">AI 일기에서 소셜 피드로: 이미지 파이프라인 자동화와 공유 기능 구현기</h1>
<p>단순히 일기를 저장하는 것을 넘어, 다른 사용자들과 일상을 공유하고 보상을 받는 선순환 구조를 만들기 위해 소셜 피드 연동 작업을 진행했습니다.</p>
</blockquote>
<h2 id="1-backend-s3-업로드-자동화-및-서비스-간-통신">1. Backend: S3 업로드 자동화 및 서비스 간 통신</h2>
<p>가장 큰 과제는 사용자가 업로드한 다중 이미지를 안전하게 저장하고, 이를 여러 서비스에서 참조할 수 있게 만드는 것이었습니다.</p>
<h3 id="☁️-s3-이미지-업로드-자동화-feign-client-활용">☁️ S3 이미지 업로드 자동화 (Feign Client 활용)</h3>
<p>일기 서비스에서 직접 S3에 접근하는 대신, 이미지 관리를 전담하는 유저 서비스의 기능을 활용했습니다.</p>
<ul>
<li><p>Feign Client 도입: 일기 서비스에서 유저 서비스의 ImageController를 호출하기 위해 Feign 인터페이스를 구축했습니다. 이를 통해 서비스 간 결합도를 낮추면서도 효율적인 통신이 가능해졌습니다.</p>
</li>
<li><p>다중 이미지 처리: MultipartFile 리스트를 수신하여 S3 Public URL로 변환하고, 이를 다시 일기 DB에 매핑하는 전 과정을 자동화했습니다.</p>
</li>
<li><p>트랜잭션 관리: 이미지 업로드와 일기 저장 중 하나라도 실패할 경우 전체 프로세스를 롤백하여 데이터 불일치 문제를 사전에 방지했습니다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/b30cecf8-9631-4084-9f0b-4734fdf6b03d/image.png" alt=""></p>
<h2 id="2-frontend-끊김-없는-사용자-경험과-메모리-관리">2. Frontend: 끊김 없는 사용자 경험과 메모리 관리</h2>
<p>프론트엔드에서는 이미지 업로드 과정에서의 오류를 줄이고, 대용량 파일을 다룰 때 발생할 수 있는 브라우저 성능 문제를 해결하는 데 집중했습니다.</p>
<h3 id="🖼️-다중-이미지-전송-및-blob-메모리-누수-해결">🖼️ 다중 이미지 전송 및 Blob 메모리 누수 해결</h3>
<ul>
<li><p>Blob 파일명 보존: 이미지 파일을 Blob 객체로 처리할 때 파일명이 유실되어 백엔드에서 확장자를 추출하지 못하던 문제를 file.name 명시를 통해 해결했습니다.</p>
</li>
<li><p>메모리 관리 (URL.revokeObjectURL): 미리보기를 위해 생성한 임시 URL들이 메모리에 쌓여 발생하는 ERR_FILE_NOT_FOUND 에러를 해결하기 위해, 컴포넌트 언마운트나 이미지 삭제 시 메모리를 해제하는 클린업 함수를 적용했습니다.</p>
</li>
</ul>
<h3 id="전">(전)</h3>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/4aa16929-a709-4b19-802a-c1d7232da533/image.png" alt=""></p>
<h3 id="후">(후)</h3>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/e1058654-1454-41b5-895f-8ba3af02426e/image.png" alt=""></p>
<h3 id="🔄-spa-방식의-부드러운-페이지-전환">🔄 SPA 방식의 부드러운 페이지 전환</h3>
<ul>
<li><p>useNavigate 활용: 기존의 window.location.href 방식을 제거하고 React Router의 useNavigate를 도입하여 새로고침 없는 부드러운 페이지 이동을 구현했습니다.</p>
</li>
<li><p>3단계 완료 프로세스: 일기 저장 및 코인 적립 확인 → 공개 범위 설정(PUBLIC, FOLLOWER, PRIVATE) → 공유 성공 알림으로 이어지는 체계적인 UX 단계를 설계했습니다.
<img src="https://velog.velcdn.com/images/seolhxx_/post/d18648d3-32da-4152-be81-6dd452e41852/image.png" alt=""></p>
</li>
</ul>
<h2 id="3-주요-리팩토링-데이터-포맷의-표준화">3. 주요 리팩토링: 데이터 포맷의 표준화</h2>
<p>소셜 피드 생성 API의 데이터 전송 방식을 기존 multipart/form-data에서 application/json으로 변경했습니다.</p>
<p>이유: 이미지 파일 자체는 S3 업로드 단계를 통해 URL로 변환되었으므로, 피드 생성 단계에서는 굳이 무거운 멀티파트 포맷을 유지할 필요 없이 가벼운 JSON 배열로 이미지 URL들을 전달하는 것이 통신 효율 면에서 유리하다고 판단했습니다.</p>
<h2 id="4-회고">4. 회고</h2>
<h3 id="✅-성과와-배운-점">✅ 성과와 배운 점</h3>
<ul>
<li><p>MSA 구조에서의 협업: 서로 다른 포트(8080 유저 서비스, 8081 일기 서비스)에서 돌아가는 서버 간에 멀티파트 파일을 주고받으며 @RequestPart 설정 등 통신 규격을 맞추는 과정의 중요성을 실감했습니다.</p>
</li>
<li><p>브라우저 자원의 소중함: 단순히 기능을 구현하는 것뿐만 아니라, revokeObjectURL처럼 사용자의 브라우저 자원을 직접 관리하는 코드가 서비스의 안정성에 큰 영향을 미친다는 것을 배웠습니다.</p>
</li>
<li><p>사용자 보상 체계 연동: 기능 구현에 그치지 않고 &#39;공유하기&#39; 성공 시 코인이 적립되는 알림 UI를 추가함으로써, 사용자가 기능을 끝까지 완수하도록 유도하는 UX의 힘을 느꼈습니다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발]31. [BE REFACTOR]스웨거 테스트 시 이미지 삽입 오류 수정, 도커파일 수정(postgis 삽입),  [FE]마이페이지 펫 생성 시 가져오기, 프록시 요청 수정,  AI 일기 생성 페이지 위치 추적 통합 및 펫 목록 실시간 연동, AI 다이어리 작성 완료 시 펫 코인 적립 연동 구현]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C31.-BE-%EC%BD%94%EB%93%9C%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81%EC%8A%A4%EC%9B%A8%EA%B1%B0-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%8B%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%82%BD%EC%9E%85-%EC%98%A4%EB%A5%98-%EC%88%98%EC%A0%95-%EB%8F%84%EC%BB%A4%ED%8C%8C%EC%9D%BC-%EC%88%98%EC%A0%95postgis-%EC%82%BD%EC%9E%85-FE%EB%A7%88%EC%9D%B4%ED%8E%98%EC%9D%B4%EC%A7%80-%ED%8E%AB-%EC%83%9D%EC%84%B1-%EC%8B%9C-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0-FE-%ED%94%84%EB%A1%9D%EC%8B%9C-%EC%9A%94%EC%B2%AD-%EC%88%98%EC%A0%95-FE-AI-%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%9C%84%EC%B9%98-%EC%B6%94%EC%A0%81-%ED%86%B5%ED%95%A9-%EB%B0%8F-%ED%8E%AB-%EB%AA%A9%EB%A1%9D-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%97%B0%EB%8F%99</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C31.-BE-%EC%BD%94%EB%93%9C%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81%EC%8A%A4%EC%9B%A8%EA%B1%B0-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%8B%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%82%BD%EC%9E%85-%EC%98%A4%EB%A5%98-%EC%88%98%EC%A0%95-%EB%8F%84%EC%BB%A4%ED%8C%8C%EC%9D%BC-%EC%88%98%EC%A0%95postgis-%EC%82%BD%EC%9E%85-FE%EB%A7%88%EC%9D%B4%ED%8E%98%EC%9D%B4%EC%A7%80-%ED%8E%AB-%EC%83%9D%EC%84%B1-%EC%8B%9C-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0-FE-%ED%94%84%EB%A1%9D%EC%8B%9C-%EC%9A%94%EC%B2%AD-%EC%88%98%EC%A0%95-FE-AI-%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%9C%84%EC%B9%98-%EC%B6%94%EC%A0%81-%ED%86%B5%ED%95%A9-%EB%B0%8F-%ED%8E%AB-%EB%AA%A9%EB%A1%9D-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%97%B0%EB%8F%99</guid>
            <pubDate>Fri, 02 Jan 2026 13:14:10 GMT</pubDate>
            <description><![CDATA[<h1 id="🥁-개발-기록-20251218-🥁">[🥁 개발 기록 2025.12.18 🥁]</h1>
<blockquote>
<h1 id="데이터의-실시간성과-사용자-경험을-모두-잡는-ai-다이어리-고도화">데이터의 실시간성과 사용자 경험을 모두 잡는 AI 다이어리 고도화</h1>
<p>AI 다이어리 서비스가 단순히 일기를 쓰는 도구를 넘어, 사용자의 실시간 데이터를 반영하고 리워드를 제공하는 완성도 높은 서비스로 진화하고 있습니다. 이번 업데이트에서 다룬 주요 기술적 개선 사항과 고민의 흔적들 입니다.</p>
</blockquote>
<h2 id="1-인프라-및-백엔드-안정성과-확장성을-위한-리팩토링">1. 인프라 및 백엔드: 안정성과 확장성을 위한 리팩토링</h2>
<p>서비스가 커짐에 따라 코드의 품질뿐만 아니라 이를 뒷받침하는 인프라 설정의 중요성이 커졌습니다.</p>
<h3 id="🛠-환경별-설정-분리와-인프라-최적화">🛠 환경별 설정 분리와 인프라 최적화</h3>
<ul>
<li><p>Config 분리: application.yml을 dev와 prod 환경으로 분리하여 관리 효율성을 높였습니다. 로컬 테스트와 실제 배포 환경의 설정을 명확히 구분하여 실수를 방지합니다.</p>
</li>
<li><p>Docker &amp; PostGIS: 위치 기반 서비스를 위해 Docker 환경의 DB 이미지를 PostGIS 지원 버전으로 교체했습니다. 초기화 스크립트를 통해 개발 환경 구축 시 공간 데이터 처리를 위한 준비가 즉시 완료되도록 개선했습니다.</p>
</li>
<li><p>Swagger &amp; CORS: MultipartJackson2HttpMessageConverter 설정을 통해 Swagger 테스트 시 발생하던 오류를 해결하고, 개발 편의를 위한 CORS 설정을 최적화했습니다.</p>
</li>
</ul>
<h3 id="📍-데이터-응답-구조-개선">📍 데이터 응답 구조 개선</h3>
<ul>
<li>DiaryResponseDTO에 <strong>위도(latitude)</strong>와 경도(longitude) 필드를 추가했습니다. 이제 사용자는 일기를 조회할 때 자신이 어디에서 이 기록을 남겼는지 지도상에서 시각적으로 확인할 수 있습니다.</li>
</ul>
<h2 id="2-프론트엔드-실시간-데이터-동기화-및-위치-추적-통합">2. 프론트엔드: 실시간 데이터 동기화 및 위치 추적 통합</h2>
<p>사용자에게 &quot;지금 당장&quot;의 정보를 정확하게 보여주기 위해 프론트엔드 로직을 전면 개편했습니다.</p>
<h3 id="🐕-펫-목록-실시간-동기화">🐕 펫 목록 실시간 동기화</h3>
<p>기존에는 JWT 토큰 내부에 포함된 고정된 펫 데이터를 사용했으나, 이는 마이페이지에서 정보를 수정해도 즉시 반영되지 않는 단점이 있었습니다.</p>
<ul>
<li>개선: 페이지 진입 시 GET /api/pets/user/{userId}를 호출하여 DB의 최신 정보를 직접 조회합니다. 토큰 의존성을 제거함으로써 데이터 정합성을 확보하고, 로딩 상태 및 예외 처리 UI를 추가해 사용자 경험을 개선했습니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/46f30a3c-3528-44ec-885c-46c2f8a40179/image.png" alt=""></p>
<h3 id="🌍-스마트-위치-정보-처리-today-vs-past">🌍 스마트 위치 정보 처리 (Today vs Past)</h3>
<p>일기를 작성할 때 날짜에 따라 위치 정보를 가져오는 경로를 이원화했습니다.</p>
<ul>
<li><p>오늘 날짜: 브라우저의 navigator.geolocation API를 통해 현재 GPS 좌표를 실시간으로 획득합니다.</p>
</li>
<li><p>과거 날짜: 사용자가 직접 좌표를 입력할 필요 없이, 해당 날짜에 PostGIS에 기록된 이동 이력(GET /api/locations/history)을 자동으로 불러옵니다. 기록이 없을 경우 &#39;위치 정보 없음&#39;으로 처리하는 Fallback 로직을 구축했습니다.</p>
</li>
</ul>
<h2 id="3-사용자-경험-리워드-시스템pet-coin-도입">3. 사용자 경험: 리워드 시스템(Pet Coin) 도입</h2>
<p>사용자가 다이어리를 지속적으로 작성할 동기를 부여하기 위해 펫 코인 적립 시스템을 연동했습니다.</p>
<ul>
<li><p>적립 로직: 일기 작성을 완료하고 &#39;공유하기&#39;를 성공하면 자동으로 POST /users/{id}/coin/earn API를 호출합니다.</p>
</li>
<li><p>UI/UX: 적립 성공 시 완료 페이지에 코인 아이콘과 함께 +15 Coin 알림 배지를 노출하여 사용자에게 즉각적인 피드백을 제공합니다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/9373f703-25fb-4202-bbff-93e74523c53e/image.png" alt=""></p>
<h2 id="개발-회고-lesson-learned">개발 회고: Lesson Learned</h2>
<h3 id="✅-기술적-성과">✅ 기술적 성과</h3>
<ul>
<li><p>데이터의 실시간성 확보: 토큰에 의존하던 방식에서 API 기반 실시간 조회 방식으로 전환하며 클라이언트와 서버 간의 데이터 불일치 문제를 해결했습니다.</p>
</li>
<li><p>공간 데이터 활용: PostGIS를 통해 저장된 과거 위치 데이터를 AI 일기 생성 컨텍스트로 활용함으로써, 보다 풍부한 사용자 맞춤형 서비스가 가능해졌습니다.</p>
</li>
<li><p>인프라 안정화: Docker 환경과 Config 설정을 정교화하여 팀원들이 동일한 환경에서 오류 없이 개발에 집중할 수 있는 토대를 마련했습니다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발]30. 카카오맵 API : 지도 표시, [FE] 다이어리 생성 시 위치 및 날씨 데이터 호출 및 날짜, 펫선택 받아오도록 ui 수정, [BE] PostGIS : 과거 위치 데이터 저장, 기상청 API 사용 : 날씨 연동 ]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C30.-%EC%B9%B4%EC%B9%B4%EC%98%A4%EB%A7%B5-API-%EC%A7%80%EB%8F%84-%ED%91%9C%EC%8B%9C-FE-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%83%9D%EC%84%B1-%EC%8B%9C-%EC%9C%84%EC%B9%98-%EB%B0%8F-%EB%82%A0%EC%94%A8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%98%B8%EC%B6%9C-%EB%B0%8F-%EB%82%A0%EC%A7%9C-%ED%8E%AB%EC%84%A0%ED%83%9D-%EB%B0%9B%EC%95%84%EC%98%A4%EB%8F%84%EB%A1%9D-ui-%EC%88%98%EC%A0%95-BE-PostGIS-%EA%B3%BC%EA%B1%B0-%EC%9C%84%EC%B9%98-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%80%EC%9E%A5-%EA%B8%B0%EC%83%81%EC%B2%AD-API-%EC%82%AC%EC%9A%A9-%EB%82%A0%EC%94%A8-%EC%97%B0%EB%8F%99</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C30.-%EC%B9%B4%EC%B9%B4%EC%98%A4%EB%A7%B5-API-%EC%A7%80%EB%8F%84-%ED%91%9C%EC%8B%9C-FE-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%83%9D%EC%84%B1-%EC%8B%9C-%EC%9C%84%EC%B9%98-%EB%B0%8F-%EB%82%A0%EC%94%A8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%98%B8%EC%B6%9C-%EB%B0%8F-%EB%82%A0%EC%A7%9C-%ED%8E%AB%EC%84%A0%ED%83%9D-%EB%B0%9B%EC%95%84%EC%98%A4%EB%8F%84%EB%A1%9D-ui-%EC%88%98%EC%A0%95-BE-PostGIS-%EA%B3%BC%EA%B1%B0-%EC%9C%84%EC%B9%98-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%80%EC%9E%A5-%EA%B8%B0%EC%83%81%EC%B2%AD-API-%EC%82%AC%EC%9A%A9-%EB%82%A0%EC%94%A8-%EC%97%B0%EB%8F%99</guid>
            <pubDate>Fri, 02 Jan 2026 13:13:02 GMT</pubDate>
            <description><![CDATA[<h1 id="💫-개발-기록-20251217-💫-">[💫 개발 기록 2025.12.17 💫 ]</h1>
<blockquote>
<h1 id="postgis와-react를-활용한-위치-기반-ai-다이어리-구현기">PostGIS와 React를 활용한 위치 기반 AI 다이어리 구현기</h1>
<p>사용자의 이동 경로를 기록하고, 그 데이터를 바탕으로 AI가 일기를 생성해주는 기능을 구현하며 고민했던 기술적 도전과 해결 과정을 정리합니다.</p>
</blockquote>
<h2 id="1-backend-postgis를-이용한-공간-데이터-최적화">1. Backend: PostGIS를 이용한 공간 데이터 최적화</h2>
<p>단순한 위도, 경도 저장을 넘어 공간 쿼리를 효율적으로 수행하기 위해 PostGIS를 도입했습니다.</p>
<h3 id="📍-엔티티-설계-및-좌표계-설정">📍 엔티티 설계 및 좌표계 설정</h3>
<p>데이터베이스 레벨에서 공간 데이터를 다루기 위해 hibernate-spatial 라이브러리를 활용했습니다.</p>
<ul>
<li><p>좌표계 설정: 전 세계적으로 표준인 WGS84(SRID 4326) 좌표계를 사용했습니다.</p>
</li>
<li><p>공간 객체 생성: GeometryFactory와 PrecisionModel을 사용하여 클라이언트로부터 받은 데이터를 PostGIS의 Point 타입으로 변환하여 저장합니다.</p>
</li>
</ul>
<h3 id="🛠-위치-관리-api-구현">🛠 위치 관리 API 구현</h3>
<ul>
<li><p>실시간 위치 저장 (POST /api/locations): 클라이언트에서 주기적으로 보내는 위/경도 데이터를 LocationRequest DTO를 통해 검증 후 walk_routes 테이블에 실시간으로 INSERT 합니다.</p>
</li>
<li><p>이력 조회 (GET /api/locations/history): 특정 유저와 날짜를 기준으로 이동 기록 중 대표 위치를 조회합니다. 조회 결과가 없을 경우 404 Not Found를 반환하도록 예외 처리를 강화했습니다.</p>
</li>
</ul>
<h2 id="2-frontend-효율적인-위치-추적-및-데이터-연동">2. Frontend: 효율적인 위치 추적 및 데이터 연동</h2>
<p>프론트엔드에서는 배터리 효율과 사용자 경험을 동시에 고려한 위치 추적 로직을 설계했습니다.</p>
<h3 id="🛰-locationtracker-컴포넌트-구현">🛰 LocationTracker 컴포넌트 구현</h3>
<p>사용자의 위치를 백그라운드에서 실시간으로 수집하는 전용 컴포넌트를 개발했습니다.</p>
<ul>
<li><p>전송 주기 최적화: 배터리 소모를 고려하여 <strong>20분 주기(1,200,000ms)</strong>로 위치 정보를 서버에 전송합니다.</p>
</li>
<li><p>인증 로직 내재화: 외부 컨텍스트 의존성을 줄이기 위해 useAuth에서 localStorage의 JWT 토큰을 직접 파싱하여 userId를 추출하도록 개선했습니다.</p>
</li>
</ul>
<h3 id="✍️-ai-일기-생성-로직-고도화">✍️ AI 일기 생성 로직 고도화</h3>
<p>단순히 현재 위치만 사용하는 것이 아니라, 과거 기록을 활용할 수 있도록 로직을 확장했습니다.</p>
<ul>
<li><p>날짜별 컨텍스트 조회: 일기 작성 날짜가 &#39;오늘&#39;이 아닌 경우, GET /api/locations/history API를 호출하여 해당 날짜의 PostGIS 위치 기록을 우선적으로 가져옵니다.</p>
</li>
<li><p>Fallback 처리: 과거 기록이 존재하지 않을 경우 현재 위치를 사용하거나 에러 메시지를 표시하여 사용자 경험의 끊김을 방지했습니다.</p>
</li>
</ul>
<h2 id="3-주요-이슈-및-해결-과정-refactoring">3. 주요 이슈 및 해결 과정 (Refactoring)</h2>
<h3 id="🔓-cors-및-보안-정책">🔓 CORS 및 보안 정책</h3>
<p>로컬 개발 환경(<a href="http://localhost:5173)%EC%97%90%EC%84%9C%EC%9D%98">http://localhost:5173)에서의</a> 원활한 프론트엔드 통신을 위해 @CrossOrigin 설정을 임시 적용했습니다. 이는 추후 API Gateway 통신으로 전환하여 보안을 강화할 예정입니다.</p>
<h3 id="🧩-api-파라미터-확장">🧩 API 파라미터 확장</h3>
<p>AI 일기 생성 시 위치 명칭(locationName)을 포함할 수 있도록 DTO와 서비스 파라미터를 확장하여, AI가 보다 정확한 장소 정보를 기반으로 일기를 쓸 수 있는 기반을 마련했습니다.</p>
<h2 id="4-테스트-및-결과">4. 테스트 및 결과</h2>
<ul>
<li><p>API 테스트: POST 호출 시 walk_routes 테이블에 데이터가 정상 INSERT 됨을 확인했습니다.</p>
</li>
<li><p>응답 검증: 특정 날짜 조회 시 데이터가 JSON 형태로 정상 반환되며, 없는 데이터 요청 시 예외 처리가 작동함을 확인했습니다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seolhxx_/post/37c8da9a-eaf5-4c7e-ad2b-f7006d2e56a6/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발]29. [REFACTOR]일기 생성 시 petId 반환 오류 수정(유저서비스 dto와 다이어리 서비스 dto 맞추기), [FE] 일기 생성 전 펫 선택 ui 수정, AI 일기 생성 기능 구현 및 Service 로직 통합]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C29.-REFACTOR%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%EC%8B%9C-petId-%EB%B0%98%ED%99%98-%EC%98%A4%EB%A5%98-%EC%88%98%EC%A0%95%EC%9C%A0%EC%A0%80%EC%84%9C%EB%B9%84%EC%8A%A4-dto%EC%99%80-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%84%9C%EB%B9%84%EC%8A%A4-dto-%EB%A7%9E%EC%B6%94%EA%B8%B0-FE-%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%EC%A0%84-%ED%8E%AB-%EC%84%A0%ED%83%9D-ui-%EC%88%98%EC%A0%95-AI-%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84-%EB%B0%8F-Service-%EB%A1%9C%EC%A7%81-%ED%86%B5%ED%95%A9</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C29.-REFACTOR%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%EC%8B%9C-petId-%EB%B0%98%ED%99%98-%EC%98%A4%EB%A5%98-%EC%88%98%EC%A0%95%EC%9C%A0%EC%A0%80%EC%84%9C%EB%B9%84%EC%8A%A4-dto%EC%99%80-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%84%9C%EB%B9%84%EC%8A%A4-dto-%EB%A7%9E%EC%B6%94%EA%B8%B0-FE-%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%EC%A0%84-%ED%8E%AB-%EC%84%A0%ED%83%9D-ui-%EC%88%98%EC%A0%95-AI-%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84-%EB%B0%8F-Service-%EB%A1%9C%EC%A7%81-%ED%86%B5%ED%95%A9</guid>
            <pubDate>Fri, 02 Jan 2026 13:11:02 GMT</pubDate>
            <description><![CDATA[<h1 id="🌾-개발-기록-20251216-🌾">[🌾 개발 기록 2025.12.16 🌾]</h1>
<h1 id="데이터-동기화-전략-개선-및-시공간-컨텍스트를-활용한-ai-일기-고도화">데이터 동기화 전략 개선 및 시공간 컨텍스트를 활용한 AI 일기 고도화</h1>
<p>마이크로서비스 아키텍처(MSA) 환경에서 클라이언트가 최신 상태의 데이터를 유지하고, AI 모델에 정확한 환경 정보(위치, 시간)를 제공하는 것은 서비스의 신뢰도와 직결됩니다. 이번 스프린트에서는 정적 데이터 의존성을 제거하고 실시간 API 기반의 동기화 구조를 확립했습니다.</p>
<hr>
<h2 id="1-데이터-동기화-전략-jwt-의존성-제거-및-실시간-api-전환">1. 데이터 동기화 전략: JWT 의존성 제거 및 실시간 API 전환</h2>
<p>기존 구조에서는 사용자 편의를 위해 반려동물(Pet) 목록을 JWT 토큰 내부에 포함하여 관리했습니다. 그러나 이 방식은 마이페이지 등 타 서비스에서 데이터 변경이 발생했을 때 토큰이 갱신되기 전까지 클라이언트에 반영되지 않는 데이터 불일치 문제를 야기했습니다.</p>
<h4 id="개선된-조회-로직">개선된 조회 로직</h4>
<ul>
<li><p>Dynamic Fetching: 페이지 진입 시 GET /api/pets/user/{userId}를 호출하여 실시간으로 DB의 최신 정보를 조회하도록 변경했습니다.</p>
</li>
<li><p>State Management: 로딩 상태(isLoadingPets)를 도입하여 네트워크 지연 시의 UI 예외 처리를 강화하고, 데이터 부재 시의 사용자 가이드를 추가하여 런타임 안정성을 확보했습니다.</p>
</li>
</ul>
<h2 id="2-시공간-컨텍스트-확보-실시간-gps-및-위치-이력-통합">2. 시공간 컨텍스트 확보: 실시간 GPS 및 위치 이력 통합</h2>
<p>AI가 작성하는 일기의 현장감을 높이기 위해서는 해당 시점의 정확한 위치 정보가 필요합니다. 이를 위해 현재 시점과 과거 시점을 분리한 하이브리드 위치 추적 로직을 구현했습니다.</p>
<h4 id="위치-정보-획득-파이프라인">위치 정보 획득 파이프라인</h4>
<ul>
<li><p>현재 날짜 (Real-time): 브라우저의 navigator.geolocation API를 사용하여 실시간 위경도 좌표를 획득합니다.</p>
</li>
<li><p>과거 날짜 (History-based): 과거 날짜의 일기를 작성하거나 수정할 경우, 백엔드의 위치 이력 API(GET /api/locations/history)를 호출합니다. 이는 백엔드에서 PostGIS 등을 통해 관리되는 20분 단위의 위치 로그 중 해당 날짜의 대표 좌표를 추출하여 반환합니다.</p>
</li>
<li><p>Fallback 전략: GPS 수신 불가 또는 과거 기록 부재 시, 현재 위치를 기본값으로 사용하거나 명시적인 예외 처리를 통해 데이터 정합성을 유지합니다.</p>
</li>
</ul>
<h2 id="3-api-통신-구조-최적화-및-페이로드-경량화">3. API 통신 구조 최적화 및 페이로드 경량화</h2>
<h4 id="ai-모델-역할-중심의-데이터-설계">AI 모델 역할 중심의 데이터 설계</h4>
<p>기존에는 프론트엔드에서 임의의 감정(mood)이나 날씨(weather) 값을 전송했습니다. 하지만 이는 Spring AI와 GPT-4o 모델이 이미지와 텍스트를 분석하여 스스로 추론해야 하는 영역입니다.</p>
<ul>
<li>Refactoring: 프론트엔드의 임의 값 할당 로직을 제거하고, 모델이 컨텍스트를 기반으로 독립적으로 판단할 수 있도록 API 요청 페이로드를 간소화했습니다.</li>
</ul>
<h4 id="gateway-통합-및-인프라-설정">Gateway 통합 및 인프라 설정</h4>
<ul>
<li><p>Endpoint Unification: 서비스별로 분산되어 있던 BASE_URL을 API Gateway 포트(8000)로 통일하여 라우팅 관리의 복잡도를 낮추었습니다.</p>
</li>
<li><p>Security: 401 Unauthorized 에러 발생 시 세션 만료를 감지하고 즉시 로그인 페이지로 리다이렉트하는 공통 인터셉터 로직을 강화했습니다.</p>
</li>
</ul>
<h2 id="4-구현-결과-및-검증">4. 구현 결과 및 검증</h2>
<ul>
<li><p>동기화 확인: 마이페이지에서 펫 정보를 수정하거나 추가한 후, 별도의 재로그인 없이 일기 작성 페이지에서 즉시 최신 목록이 반영됨을 확인했습니다.</p>
</li>
<li><p>데이터 무결성: 과거 날짜 선택 시 해당 시점의 위치 정보가 정상적으로 바인딩되어 AI 일기 생성 요청에 포함되는 시나리오를 검증했습니다.</p>
</li>
<li><p>인터페이스 호환성: 페이로드에서 제거된 mood, weather 필드가 백엔드 AI 엔진에서 정상적으로 추론 및 저장되는지 확인하여 서버-클라이언트 간 규약 일관성을 확보했습니다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발]28. [FE] 회원가입/로그인 나의 로컬에 실행 테스트 및 다이어리 crud 프론트 연동]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C28.-FE-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EB%82%98%EC%9D%98-%EB%A1%9C%EC%BB%AC%EC%97%90-%EC%8B%A4%ED%96%89-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%B0%8F-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-crud-%ED%94%84%EB%A1%A0%ED%8A%B8-%EC%97%B0%EB%8F%99</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C28.-FE-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EB%82%98%EC%9D%98-%EB%A1%9C%EC%BB%AC%EC%97%90-%EC%8B%A4%ED%96%89-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%B0%8F-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-crud-%ED%94%84%EB%A1%A0%ED%8A%B8-%EC%97%B0%EB%8F%99</guid>
            <pubDate>Fri, 02 Jan 2026 13:03:57 GMT</pubDate>
            <description><![CDATA[<h1 id="🌷-개발-기록-20251215-🌷">[🌷 개발 기록 2025.12.15 🌷]</h1>
<h1 id="ai-다이어리-생성-기능의-프론트엔드-구현-및-백엔드-통합">AI 다이어리 생성 기능의 프론트엔드 구현 및 백엔드 통합</h1>
<p>이번 작업에서는 사용자가 업로드한 이미지와 반려동물의 메타데이터를 기반으로 AI 일기를 생성하고, 이를 검토 및 수정할 수 있는 <strong>멀티스텝 워크플로우(Multi-step Workflow)</strong>를 구현했습니다. 데이터 정합성을 위한 상태 관리와 비동기 API 통신 최적화에 중점을 두었습니다.</p>
<h2 id="1-ui-워크플로우-및-상태-설계">1. UI 워크플로우 및 상태 설계</h2>
<p>AI 일기 생성 프로세스는 단일 페이지 내에서 4단계의 상태 전환을 통해 이루어집니다. 사용자 경험의 일관성을 위해 각 단계의 상태를 정의하고, 단계별로 필요한 데이터의 유효성을 검증합니다.</p>
<h3 id="단계별-구성">단계별 구성</h3>
<ul>
<li><p>이미지 업로드 및 펫 선택: MultipartFile 객체 생성 및 백엔드(Member Service)로부터 조회한 펫 목록 중 하나를 선택합니다.</p>
</li>
<li><p>AI 생성 요청 및 로딩: 백엔드에 데이터를 전송하고, GPT-4o의 응답이 올 때까지 대기 상태(Loading Animation)를 유지합니다.</p>
</li>
<li><p>일기 편집: AI가 생성한 content와 mood를 textarea 및 입력 폼에 바인딩하여 사용자가 최종 수정할 수 있게 합니다.</p>
</li>
<li><p>저장 및 완료: 최종 수정본을 백엔드에 반영하고 결과를 확인합니다.</p>
</li>
</ul>
<h2 id="2-api-연동-및-데이터-처리">2. API 연동 및 데이터 처리</h2>
<h4 id="멀티모달-데이터-전송-formdata">멀티모달 데이터 전송 (FormData)</h4>
<p>이미지 파일과 JSON 데이터(userId, petId 등)를 동시에 전송하기 위해 multipart/form-data 형식을 사용했습니다. Blob 객체를 활용하여 JSON 데이터를 이미지와 함께 FormData에 담아 전송하는 구조를 설계했습니다.</p>
<ul>
<li><p>Endpoint: POST /api/diaries/ai</p>
</li>
<li><p>Payload: image (File), request (JSON Blob: petId, visibility 등)</p>
</li>
</ul>
<h4 id="api-호출-시퀀스">API 호출 시퀀스</h4>
<p>생성된 일기는 즉시 저장되지만, 사용자의 최종 확인을 거쳐야 완성됩니다. 이를 위해 다음의 시퀀스를 적용했습니다.</p>
<ul>
<li><p>POST /api/diaries/ai를 통해 초기 AI 일기 데이터 생성 및 DB 저장.</p>
</li>
<li><p>반환된 diaryId를 기반으로 GET /api/diaries/{diaryId} 호출(또는 생성 응답값 활용)하여 화면 렌더링.</p>
</li>
<li><p>사용자 수정 후 PATCH /api/diaries/{diaryId}를 통해 변경사항 최종 반영.</p>
</li>
</ul>
<h2 id="3-주요-컴포넌트-구현-상세">3. 주요 컴포넌트 구현 상세</h2>
<h4 id="펫-선택-및-유효성-검사">펫 선택 및 유효성 검사</h4>
<p>사용자가 등록한 펫이 여러 마리일 경우를 대비해 드롭다운 메뉴를 구현했습니다. petId는 일기 생성의 필수 값으로 설정되어 있으며, 펫이 선택되지 않은 상태에서는 생성 버튼이 비활성화되도록 클라이언트 측 유효성 검사(Validation)를 적용했습니다.</p>
<h4 id="이미지-처리-로직">이미지 처리 로직</h4>
<ul>
<li><p>미리보기: URL.createObjectURL을 사용하여 업로드된 이미지의 로컬 프리뷰를 제공합니다.</p>
</li>
<li><p>리소스 관리: 이미지 삭제 혹은 컴포넌트 언마운트 시 revokeObjectURL을 호출하여 메모리 누수를 방지합니다.</p>
</li>
</ul>
<h4 id="반응형-및-인터랙션">반응형 및 인터랙션</h4>
<ul>
<li><p>Progress Bar: 상단에 현재 단계를 시각화하여 사용자에게 프로세스 흐름을 인지시킵니다.</p>
</li>
<li><p>Responsive Design: CSS Media Queries를 사용하여 모바일 환경에서도 이미지 업로더와 편집 창이 최적화된 크기로 유지되도록 구성했습니다.</p>
</li>
</ul>
<h2 id="4-테스트-및-검증-결과">4. 테스트 및 검증 결과</h2>
<ul>
<li><p>데이터 연동: 선택된 petId가 백엔드의 PetServiceClient를 통해 정상적으로 유효성 검증을 통과하는지 확인했습니다.</p>
</li>
<li><p>상태 유지: 로딩 중 페이지 이탈 방지 및 단계 이동 시 기존에 입력한 데이터가 유지되는지 확인했습니다.</p>
</li>
<li><p>예외 처리: 서버 응답 지연이나 에러 발생 시 사용자에게 적절한 에러 메시지를 노출하는 예외 핸들링 로직을 검증했습니다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발]27. [BE]회원 서비스(User/Pet) 연동 및 다이어리 생성 시 유효성 검증 로직 구현, [BE Refactor]API 문서화 강화를 위한 DTO 리팩토링]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C27.-BE%ED%9A%8C%EC%9B%90-%EC%84%9C%EB%B9%84%EC%8A%A4UserPet-%EC%97%B0%EB%8F%99-%EB%B0%8F-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%83%9D%EC%84%B1-%EC%8B%9C-%EC%9C%A0%ED%9A%A8%EC%84%B1-%EA%B2%80%EC%A6%9D-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84-BE-RefactorAPI-%EB%AC%B8%EC%84%9C%ED%99%94-%EA%B0%95%ED%99%94%EB%A5%BC-%EC%9C%84%ED%95%9C-DTO-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C27.-BE%ED%9A%8C%EC%9B%90-%EC%84%9C%EB%B9%84%EC%8A%A4UserPet-%EC%97%B0%EB%8F%99-%EB%B0%8F-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%83%9D%EC%84%B1-%EC%8B%9C-%EC%9C%A0%ED%9A%A8%EC%84%B1-%EA%B2%80%EC%A6%9D-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84-BE-RefactorAPI-%EB%AC%B8%EC%84%9C%ED%99%94-%EA%B0%95%ED%99%94%EB%A5%BC-%EC%9C%84%ED%95%9C-DTO-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</guid>
            <pubDate>Sun, 14 Dec 2025 10:50:03 GMT</pubDate>
            <description><![CDATA[<h1 id="✈️-개발-기록-20251212-✈️">[✈️ 개발 기록 2025.12.12 ✈️]</h1>
<h1 id="record-마이크로서비스의-아키텍처-설계-및-서비스-간-데이터-정합성-최적화">Record 마이크로서비스의 아키텍처 설계 및 서비스 간 데이터 정합성 최적화</h1>
<p>시스템의 규모가 확장됨에 따라 단일 서비스 내에서의 데이터 관리 방식을 넘어, 분산된 도메인 간의 의존성을 관리하고 데이터의 무결성을 보장하는 설계가 핵심적인 과제로 부상했습니다. 본 아티클에서는 &#39;Petlog&#39; 프로젝트의 Record Service를 구축하며 적용한 기술적 의사결정과 구현 상세를 다룹니다.</p>
<h2 id="1-도메인-경계-확립-및-패키지-리팩토링">1. 도메인 경계 확립 및 패키지 리팩토링</h2>
<p>서비스의 독립성과 도메인 중심 설계(DDD)를 강화하기 위해 기존 com.example.petlog 체계를 com.petlog.record로 전면 개편했습니다. 이는 단순히 이름의 변경이 아니라, 마이크로서비스 간의 도메인 경계를 명확히 하고 향후 멀티 모듈 및 서비스 확장을 고려한 조치입니다.</p>
<p>동시에 다이어리 이미지의 생명주기(Lifecycle)를 비즈니스 요구사항에 맞춰 재정의했습니다.</p>
<ul>
<li><p>DiaryImage: 일기 본문과 결합된 일시적 요소로, 일기 삭제 시 연쇄 삭제(Cascade Delete) 수행.</p>
</li>
<li><p>PhotoArchive: 사용자의 영구 자산으로 분류되어 별도의 영속성 컨텍스트(Persistence Context)를 통해 관리.</p>
</li>
</ul>
<p>이러한 분리 모델은 데이터 삭제 정책의 유연성을 확보하고, 외부 스토리지로의 중복 전송을 방지하는 ImageSource 기반 분기 로직의 토대가 되었습니다.</p>
<h2 id="2-서비스-간-연동-및-데이터-정합성-검증-전략">2. 서비스 간 연동 및 데이터 정합성 검증 전략</h2>
<p>분산 환경에서는 참조 무결성 제약 조건(Foreign Key)을 DB 수준에서 강제할 수 없으므로, 애플리케이션 계층에서의 검증 로직이 필수적입니다.</p>
<h3 id="feign-client의-분리-및-유효성-확보">Feign Client의 분리 및 유효성 확보</h3>
<p>기존의 비대한 Client 인터페이스를 UserServiceClient와 PetServiceClient로 분리하여 <strong>단일 책임 원칙(SRP)</strong>을 준수하도록 설계했습니다.</p>
<p>다이어리 생성 시 발생할 수 있는 &#39;존재하지 않는 식별자&#39; 문제를 해결하기 위해 다음과 같은 검증 파이프라인을 구축했습니다.</p>
<ul>
<li><p>Validation Proxy: 생성 요청 진입 시 각 Client를 통해 해당 ID의 실존 여부를 질의.</p>
</li>
<li><p>Exception Handling: 회원 서비스에서 404 Not Found 반환 시 FeignException을 포착하여 EntityNotFoundException으로 전파, 트랜잭션 롤백 및 원자성 보장.</p>
</li>
</ul>
<h2 id="3-spring-ai를-이용한-비정형-데이터의-구조화">3. Spring AI를 이용한 비정형 데이터의 구조화</h2>
<p>AI 기능을 서비스 내부에 통합함에 있어 유지보수성과 확장성을 고려한 추상화 계층을 도입했습니다.</p>
<ul>
<li><p>Interface-based Integration: RestTemplate 대신 Spring AI의 ChatModel 인터페이스를 사용하여 특정 모델(OpenAI GPT-4o)에 대한 결합도를 낮추었습니다.</p>
</li>
<li><p>Structured Output: 비정형 텍스트 응답의 파싱 오류를 방지하기 위해 BeanOutputConverter를 적용했습니다. 이는 AI 모델의 응답을 사전에 정의된 자바 객체(POJO)로 즉시 매핑하여 런타임 안정성을 확보합니다.</p>
</li>
<li><p>Prompt Externalization: 시스템 프롬프트를 자바 코드와 분리하여 리소스 파일(.st)로 관리함으로써, 비즈니스 로직의 변경 없이 AI의 페르소나와 응답 형식을 조정할 수 있는 환경을 구축했습니다.</p>
</li>
</ul>
<h2 id="4-트랜잭션-전파-및-영속성-최적화">4. 트랜잭션 전파 및 영속성 최적화</h2>
<p>조회 시점의 데이터 상태에 따라 생성과 수정을 동시에 처리해야 하는 Upsert 로직에서 발생한 트랜잭션 경계 문제를 해결했습니다.</p>
<p>readOnly = true 트랜잭션 내에서 기본 스타일 생성을 시도할 때 발생하는 오류를 방지하기 위해, 쓰기 작업이 필요한 메서드를 별도의 서비스 계층으로 분리하고 Propagation.REQUIRES_NEW 또는 별도의 @Transactional 선언을 통해 프록시를 통한 트랜잭션 전파를 유도했습니다. 이를 통해 조회 성능과 데이터 생성의 안정성을 동시에 확보했습니다.</p>
<h2 id="5-인터페이스-규약의-엄격화-swagger-및-validation">5. 인터페이스 규약의 엄격화: Swagger 및 Validation</h2>
<p>마이크로서비스 간, 혹은 프론트엔드와의 협업에서 발생하는 커뮤니케이션 비용을 최소화하기 위해 API 명세를 강화했습니다.</p>
<ul>
<li><p>Schema Specification: @Schema 어노테이션을 활용해 모든 DTO 필드에 명확한 메타데이터와 예시 값을 명시했습니다. 이는 런타임 문서를 자동화할 뿐만 아니라, 클라이언트 측의 타입 안정성 확보를 돕습니다.</p>
</li>
<li><p>Granular Validation: Bean Validation 적용 시 단순한 제약 조건을 넘어 구체적인 에러 메시지를 정의했습니다. 이를 통해 예외 발생 시 클라이언트가 즉각적으로 원인을 파악하고 대응할 수 있는 Self-describing Error Response를 구현했습니다.</p>
</li>
</ul>
<h2 id="6-결론-및-향후-과제">6. 결론 및 향후 과제</h2>
<p>본 Record Service 구축 과정은 분산 시스템에서의 데이터 정합성 보장과 확장 가능한 아키텍처 설계에 초점을 맞추었습니다.</p>
<p>현재의 동기적 검증 방식은 서비스 간의 가용성 의존성을 높이는 한계가 있습니다. 향후 Circuit Breaker 패턴을 도입하여 외부 서비스 장애 시의 폴백(Fallback) 메커니즘을 강화하고, 메시지 브로커를 활용한 <strong>이벤트 기반 아키텍처(EDA)</strong>로 전환하여 데이터의 최종 일관성(Eventual Consistency)을 확보하는 방향으로 고도화할 예정입니다.</p>
<h4 id="기술-스택-요약"><strong>기술 스택 요약</strong></h4>
<ul>
<li><p>Framework: Spring Boot 3.3, Spring Cloud Gateway, Spring AI</p>
</li>
<li><p>Communication: OpenFeign</p>
</li>
<li><p>Persistence: PostgreSQL, Spring Data JPA</p>
</li>
<li><p>Documentation: Springdoc-openapi (Swagger)</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발]26. [BE]OpenAI(GPT-4o) 연동 다이어리 생성 로직 기본 구현, [FE]게이트웨이 통신설정, 일기 생성 후 서버 오류 수정 , 배포 오류 수정]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C26.-BEOpenAIGPT-4o-%EC%97%B0%EB%8F%99-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%83%9D%EC%84%B1-%EB%A1%9C%EC%A7%81-%EA%B8%B0%EB%B3%B8-%EA%B5%AC%ED%98%84-FE%EA%B2%8C%EC%9D%B4%ED%8A%B8%EC%9B%A8%EC%9D%B4-%ED%86%B5%EC%8B%A0%EC%84%A4%EC%A0%95-%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%ED%9B%84-%EC%84%9C%EB%B2%84-%EC%98%A4%EB%A5%98-%EC%88%98%EC%A0%95-%EB%B0%B0%ED%8F%AC-%EC%98%A4%EB%A5%98-%EC%88%98%EC%A0%95</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C26.-BEOpenAIGPT-4o-%EC%97%B0%EB%8F%99-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%83%9D%EC%84%B1-%EB%A1%9C%EC%A7%81-%EA%B8%B0%EB%B3%B8-%EA%B5%AC%ED%98%84-FE%EA%B2%8C%EC%9D%B4%ED%8A%B8%EC%9B%A8%EC%9D%B4-%ED%86%B5%EC%8B%A0%EC%84%A4%EC%A0%95-%EC%9D%BC%EA%B8%B0-%EC%83%9D%EC%84%B1-%ED%9B%84-%EC%84%9C%EB%B2%84-%EC%98%A4%EB%A5%98-%EC%88%98%EC%A0%95-%EB%B0%B0%ED%8F%AC-%EC%98%A4%EB%A5%98-%EC%88%98%EC%A0%95</guid>
            <pubDate>Sun, 14 Dec 2025 10:46:33 GMT</pubDate>
            <description><![CDATA[<h1 id="🍭-개발-기록-20251211-🍭">[🍭 개발 기록 2025.12.11 🍭]</h1>
<h1 id="spring-ai-gpt-4o로-구현하는-멀티모달-펫-다이어리-단순-연동을-넘어-설계하기">[Spring AI] GPT-4o로 구현하는 멀티모달 펫 다이어리: 단순 연동을 넘어 &#39;설계&#39;하기</h1>
<p>단순히 API를 호출해 결과를 받아오는 시대는 지났습니다. 이제는 AI 모델을 우리 시스템의 일부로 얼마나 유연하게 통합하느냐가 관건입니다.</p>
<p>&#39;Petlog&#39; 프로젝트의 핵심 기능인 AI 자동 일기 생성을 구현하며, Spring AI(v1.0.0-M4)를 활용해 &quot;Legacy한 방식&quot;에서 벗어나 &quot;현대적인 AI 오케스트레이션&quot;을 적용한 과정을 기록합니다.</p>
<h2 id="1-abstraction-resttemplate을-버리고-chatmodel을-택하다">1. Abstraction: RestTemplate을 버리고 ChatModel을 택하다</h2>
<p>가장 먼저 한 일은 RestTemplate이나 WebClient를 직접 사용하여 OpenAI API와 통신하던 기존 방식을 완전히 걷어내는 것이었습니다.</p>
<p>Spring AI의 ChatModel 인터페이스를 사용하면, 나중에 모델을 Anthropic의 Claude나 로컬의 Llama3로 교체하더라도 비즈니스 로직을 단 한 줄도 수정할 필요가 없습니다. 이것이 바로 인터페이스 추상화가 주는 강력한 유연성입니다.</p>
<h2 id="2-multimodal-사진에서-이야기를-읽어내는-기술">2. Multimodal: 사진에서 이야기를 읽어내는 기술</h2>
<p>이번 기능의 핵심은 사용자가 올린 반려동물 사진을 GPT-4o가 분석하는 것입니다. 이를 위해 이미지 파일을 ByteArrayResource로 변환하고, 텍스트 메시지와 함께 Media 객체로 묶어 전달하는 멀티모달 파이프라인을 구축했습니다.</p>
<p>Process: MultipartFile → ByteArrayResource → Media (Image/Jpeg) → UserMessage</p>
<p>Result: AI는 단순히 글을 쓰는 것이 아니라, 사진 속 강아지의 표정과 주변 환경을 인식해 더욱 생생한 일기를 생성합니다.</p>
<h2 id="3-engineering-프롬프트와-비즈니스-로직의-완전한-분리">3. Engineering: 프롬프트와 비즈니스 로직의 완전한 분리</h2>
<p>코드 내에 하드코딩된 거대한 프롬프트 문자열은 유지보수의 적입니다. 저는 이를 .st (StringTemplate) 파일로 추출하여 외부 리소스화했습니다.</p>
<h4 id="💡-도입한-패턴-prompttemplate">💡 도입한 패턴: PromptTemplate</h4>
<p>이 방식을 통해 개발자는 Java 코드를 건드리지 않고도 파일 수정만으로 AI의 어투나 페르소나를 언제든 조정할 수 있게 되었습니다. 또한 OpenAiChatOptions를 통해 <strong>Temperature(0.7)</strong>를 설정하여 AI가 매번 똑같은 말이 아닌, 적당히 창의적인 일기를 쓰도록 튜닝했습니다.</p>
<h2 id="4-automation-json-파싱-노가다에서-해방되다">4. Automation: JSON 파싱 노가다에서 해방되다</h2>
<p>가장 만족스러운 부분은 <strong>BeanOutputConverter</strong>의 활용입니다. AI의 응답은 기본적으로 문자열이지만, 우리에게 필요한 건 Diary 엔티티로 변환될 객체입니다.</p>
<p>과거에는 ObjectMapper를 사용해 복잡한 JSON 파싱 로직을 직접 짰지만, Spring AI에서는 구조화된 출력(Structured Output) 기능을 통해 AI 응답을 즉시 자바 POJO로 매핑할 수 있습니다.</p>
<p>&quot;AI가 응답한 텍스트가 바로 AiDiaryResponse 객체로 변환됩니다. 파싱 에러를 걱정할 필요가 없어졌습니다.&quot;</p>
<h2 id="5-persistence-ai와-데이터베이스의-만남">5. Persistence: AI와 데이터베이스의 만남</h2>
<p>생성된 다이어리는 단순 휘발성 데이터가 아닙니다.</p>
<p>isAiGen = true 플래그를 통해 사용자가 직접 쓴 일기와 구분합니다.</p>
<p>PostgreSQL에 저장 시 AI가 분석한 <strong>&#39;오늘의 감정(Mood)&#39;</strong>과 이미지 경로를 함께 매핑하여, 추후 AI 일기 보관함 기능을 위한 토대를 닦았습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Rookies 개발 4기][개발]25. 다이어리 - 회원서비스 DB 연결 오류 해결, 클라이언트(React SPA) 활용 - 이미지 출처 분기 검증 로직 확인, [FE] 다이어리 파일 구조 나누기 및 ui수정]]></title>
            <link>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C25.-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%ED%9A%8C%EC%9B%90%EC%84%9C%EB%B9%84%EC%8A%A4-DB-%EC%97%B0%EA%B2%B0-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8React-SPA-%ED%99%9C%EC%9A%A9-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B6%9C%EC%B2%98-%EB%B6%84%EA%B8%B0-%EA%B2%80%EC%A6%9D-%EB%A1%9C%EC%A7%81-%ED%99%95%EC%9D%B8-FE-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%ED%8C%8C%EC%9D%BC-%EA%B5%AC%EC%A1%B0-%EB%82%98%EB%88%84%EA%B8%B0-%EB%B0%8F-ui%EC%88%98%EC%A0%95</link>
            <guid>https://velog.io/@seolhxx_/Rookies-%EA%B0%9C%EB%B0%9C-4%EA%B8%B0%EA%B0%9C%EB%B0%9C25.-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%ED%9A%8C%EC%9B%90%EC%84%9C%EB%B9%84%EC%8A%A4-DB-%EC%97%B0%EA%B2%B0-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8React-SPA-%ED%99%9C%EC%9A%A9-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B6%9C%EC%B2%98-%EB%B6%84%EA%B8%B0-%EA%B2%80%EC%A6%9D-%EB%A1%9C%EC%A7%81-%ED%99%95%EC%9D%B8-FE-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%ED%8C%8C%EC%9D%BC-%EA%B5%AC%EC%A1%B0-%EB%82%98%EB%88%84%EA%B8%B0-%EB%B0%8F-ui%EC%88%98%EC%A0%95</guid>
            <pubDate>Sun, 14 Dec 2025 10:42:15 GMT</pubDate>
            <description><![CDATA[<h1 id="🌹-개발-기록-20251210-🌹">[🌹 개발 기록 2025.12.10 🌹]</h1>
<h1 id="spring-boot-일기는-지워져도-추억은-남아야-하므로-이미지-도메인-분리와-생명주기-설계">[Spring Boot] &quot;일기는 지워져도 추억은 남아야 하므로&quot;: 이미지 도메인 분리와 생명주기 설계</h1>
<p>서비스를 운영하다 보면 이런 기획적 고민에 부딪히게 됩니다.</p>
<p>&quot;사용자가 정성 들여 쓴 일기를 실수로 지웠을 때, 그 안에 첨부된 소중한 반려동물 사진까지 서버에서 영구 삭제되어야 할까?&quot;</p>
<p>대부분의 사용자는 일기라는 &#39;글&#39;은 지워도 &#39;사진&#39;이라는 추억은 남길 원합니다. 이를 위해 저는 이미지의 생명주기(Lifecycle)를 일기와 분리하는 리팩토링을 진행했습니다.</p>
<h2 id="1-도메인-분리-diaryimage-vs-photoarchive">1. 도메인 분리: DiaryImage vs PhotoArchive</h2>
<p>기존에는 일기가 삭제되면 그에 종속된 이미지도 함께 삭제되는 구조였습니다. 이를 해결하기 위해 엔티티의 역할을 명확히 두 개로 나누었습니다.</p>
<p>DiaryImage (일기 구성 요소): 특정 일기의 레이아웃이나 순서를 결정하는 요소입니다. 일기가 삭제되면 함께 제거됩니다.</p>
<p>PhotoArchive (사용자 자산): 사용자가 업로드한 원본 사진 데이터입니다. 일기 삭제 여부와 관계없이 사용자의 &#39;보관함&#39;에 영구적으로 보존됩니다.</p>
<p>이러한 분리를 통해 일기를 삭제하더라도 PhotoArchive에 저장된 사진 데이터를 통해 추억을 복원하거나 보관함에서 다시 확인할 수 있는 기반을 마련했습니다.</p>
<h2 id="2-효율적인-리소스-관리-imagesource에-따른-분기-처리">2. 효율적인 리소스 관리: ImageSource에 따른 분기 처리</h2>
<p>모든 이미지를 매번 외부 Storage(S3 등)에 업로드하는 것은 비용과 성능 측면에서 비효율적입니다. 이를 해결하기 위해 ImageSource라는 Enum을 도입하여 전송 로직을 최적화했습니다.</p>
<h3 id="🛠️-전략적-분기-처리">🛠️ 전략적 분기 처리</h3>
<p>GALLERY (새로 업로드하는 사진): 기기에서 직접 올리는 사진은 외부 Storage Service로 전송하고, 동시에 PhotoArchive에도 저장합니다.</p>
<p>ARCHIVE (이미 보관함에 있는 사진): 이미 서버에 존재하는 사진을 다시 일기에 쓸 때는 추가적인 외부 전송을 생략합니다.</p>
<pre><code>
// 이미지 출처에 따른 선별적 전송 로직 예시
public void processImages(List&lt;ImageRequest&gt; requests) {
    requests.forEach(req -&gt; {
        if (req.getSource() == ImageSource.GALLERY) {
            // 외부 스토리지 전송 및 아카이브 저장
            storageServiceClient.upload(req.getFile());
            photoArchiveService.save(req);
        } else if (req.getSource() == ImageSource.ARCHIVE) {
            // 전송 스킵, DB 매핑만 수행
            log.info(&quot;기존 아카이브 리소스를 사용합니다: {}&quot;, req.getUrl());
        }
    });
}</code></pre><p>이 설정을 통해 불필요한 네트워크 트래픽을 줄이고 중복 업로드를 방지하여 시스템의 전체적인 응답 속도를 개선했습니다.</p>
<h2 id="3-구조적-개선-서비스-간-의존성-정립">3. 구조적 개선: 서비스 간 의존성 정립</h2>
<p>도메인이 분리됨에 따라 서비스 레이어의 책임도 명확히 했습니다.</p>
<p>DiaryService는 일기 작성 시 PhotoArchiveService를 호출하여 사진을 보관함에 먼저 등록하도록 설계했습니다.</p>
<p>보관함 관련 기능만 전담하는 PhotoArchiveController를 신설하여, 나중에 사용자가 &quot;내 사진 모아보기&quot; 기능을 사용할 때 확장하기 좋게 만들었습니다.</p>
<h2 id="4-검증-일기는-삭제되어도-데이터는-남는다">4. 검증: 일기는 삭제되어도 데이터는 남는다</h2>
<p>구현 후 가장 중요하게 테스트한 시나리오는 <strong>&quot;삭제 후 잔존 여부&quot;</strong>였습니다.</p>
<p>테스트 시나리오: 일기를 생성(GALLERY 소스 활용) -&gt; 일기 삭제 API 호출 -&gt; 보관함 조회 API(GET /api/archives/user/{userId}) 호출</p>
<p>결과: 일기 테이블과 다이어리 이미지 테이블에서는 데이터가 사라졌지만, 보관함 테이블에는 사진 데이터가 안전하게 남아있는 것을 확인했습니다.</p>
<h3 id="🚀-마치며-데이터-중심의-설계">🚀 마치며: 데이터 중심의 설계</h3>
<p>이번 리팩토링은 단순한 기능 추가가 아니라, 사용자가 생성한 데이터의 가치를 어디까지 보존할 것인가에 대한 기술적 해답을 찾는 과정이었습니다.</p>
<p>단순히 Delete 쿼리를 날리는 것보다, 데이터의 성격에 따라 생명주기를 다르게 가져가는 설계가 서비스의 성숙도를 결정한다는 것을 다시 한번 느꼈습니다.</p>
]]></description>
        </item>
    </channel>
</rss>