<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sin_0.log</title>
        <link>https://velog.io/</link>
        <description>후회하지 않는 사람이 되자 🔥</description>
        <lastBuildDate>Tue, 04 Nov 2025 08:46:38 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sin_0.log</title>
            <url>https://velog.velcdn.com/images/sin_0/profile/4bc39c19-2d19-433f-af25-7135e1c6ddb8/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sin_0.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sin_0" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[솔루션 개발자 6개월 회고]]></title>
            <link>https://velog.io/@sin_0/%EC%86%94%EB%A3%A8%EC%85%98-%EA%B0%9C%EB%B0%9C%EC%9E%90-6%EA%B0%9C%EC%9B%94-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sin_0/%EC%86%94%EB%A3%A8%EC%85%98-%EA%B0%9C%EB%B0%9C%EC%9E%90-6%EA%B0%9C%EC%9B%94-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 04 Nov 2025 08:46:38 GMT</pubDate>
            <description><![CDATA[<h2 id="솔루션-기업에-입사한-이유">솔루션 기업에 입사한 이유?</h2>
<p>우선 간단하게 생각했다. SI보다 환경이 낫겠다 라는 생각과 &quot;솔루션&quot;을 개발하는 업무 자체가 흥미로워보였다. 이지만? 인생은 뜻대로 펼쳐지지 않는법..</p>
<h2 id="한국의-솔루션은-반-si다">한국의 솔루션은 반 SI다</h2>
<p>알고보니 내 업무가 솔루션을 개발하는게 아닌 <strong>솔루션을 활용하여 연동하는 업무</strong>였다는 것 😂 자사 검색 솔루션의 코어를 활용해서 고객사의 WAS 서버에 올려 검색을 연동하는 작업. REST API를 통해 검색 기능을 제공하는 거지만, 결국 내가 검색 엔진을 만드는 게 아니라 이미 만들어진 API를 고객 요구사항에 맞춰 커스터마이징하는 일이었다.</p>
<p>더 아쉬운 건, 한국의 솔루션은 사실상 반 SI 성격을 띄고있다. 왜냐하면 그러지않으면 <strong>안팔린다</strong>고 하더라구요.. 검색관련 솔루션이지만 구글도 한국의 솔루션 판매를 보고 당황했다고 카더라 😅 그만큼 엔지니어들이 어디까지 해주는지에 대한 범위가 한국은 넓은편인거같다. 일종의 판매전략이 아닌가 싶더라. 그만큼 업무가 많아지는건 우리들이겠죠?</p>
<p><strong>업무를 꼼꼼하게 읽어보고 면접시에도 꼭 물어보도록하자..!</strong> 그래도 입사한거 일단 다녀보긴 하자 라는 생각으로 다녔다.</p>
<h2 id="입사-초기에-한-일">입사 초기에 한 일</h2>
<p>아무튼 입사를 한 뒤로는 솔루션에 대해 학습하기에 바빴고, 두 달 간 학습을 하고있다보니 프로젝트가 배정됐고 첫 프로젝트는 원격이라 환경 자체는 어렵지 않았다. 하지만 요구사항이 만만치 않았다.</p>
<p>자사 검색솔루션을 활용해야 하는 요구사항들이었는데, 검색이라는 게 생각보다 복잡한 방식이다. JDBC 수집에서부터 난항이 시작됐고, 형태소 분석기를 커스텀하는 부분도 쉽지 않았다. 특히 AS-IS 기술보다 훨씬 더 복잡한 옵션들이 추가되어 API 수정 로직이 복잡해졌다.</p>
<p>가장 힘들었던 건 <strong>멘탈 관리</strong>였다. 몇 가지 방안으로 수정에 수정을 하다 보면 결국 도돌이표가 되거나, 롤백할 때마다 &quot;아 내가 뭐하고 있는 거지?&quot; 싶었다. 갈피를 잡고 방향이 아닌 거 같다 싶으면 과감하게 롤백해야 하는 경우가 엄청 잦았다. 그럴때마다 분명히 배운거 같은데 실무를 하니 내 능력이 의심되기도하고 아무튼 &quot;하기싫다&quot; 라고 생각이 들어도 길을 잃어선 안되는게 가장 중요한 역량인것같다.</p>
<p>또 배운 게 있다면, <strong>혼자 생각하기보다는 동료들과 이야기를 나누기만 해도 어느 정도 갈래가 정리된다</strong>는 것. 역시 사람은 머리를 맞대야 더 나은 아이디어가 나온다고 느꼈다.</p>
<h2 id="폐쇄망-경험">폐쇄망 경험</h2>
<p>그리고 현재는 어느덧 혼자 폐쇄망에 상주하여 솔루션 마이그레이션을 하고 있는 사원이 된 상태다. (사실 이게 맞나 싶긴 함)</p>
<h3 id="인터넷도-복지다">인터넷도 복지다</h3>
<p>폐쇄망에서의 업무와 이클립스, 망분리가 되어 있는 환경이라 적응하는 시간 자체도 오래 걸렸다. 레거시한 코드와 JSP들은 덤.. </p>
<p>근데 <strong>로컬 테스트 허용까지 2달이 걸렸다.</strong> 무슨 말이냐면, 로컬 자체에서 내 솔루션이 설치된 서버에 접근할 수 없고 중계서버를 통해 접근해야 하는데, 방화벽 요청 시간이 짧으면 3일, 길면 14일까지 걸리게 된다.. 서버자체도 늦게 열렸고 방화벽요청도 늦다. 그 시간 동안 할 수 있는 걸 찾다 보면 지루함이 엄청 느껴졌고 종종 현타가 왔다. </p>
<p>게다가 과거 프로젝트를 버전업해서 마이그레이션하는 작업인데, 업무 파악 시에 산출물이 부족하고 이곳 사람들도 잘 모르는 거라 굉장히 어려웠다. 또한 새로운 환경에서 가이드도 부족한 게 너무 어려웠다. <strong>폐쇄망 + 망분리 + 이클립스 + SVN</strong>이라 좀 많이 번거롭다는 게 느껴졌다. 사내에 인터넷이 된다는 것도 복지라는걸 느낌</p>
<h3 id="1층-로비-카페에서-업무하는-개발자">1층 로비 카페에서 업무하는 개발자</h3>
<p>연구소와 소통해야 하는 이슈가 있었는데, 파일을 반출하는 절차도 엄청 오래 걸렸다. 다행히도 카메라를 쓸 수 있는 환경이라(봐주는듯) 카메라를 통해서 빠르게 빠르게 소통했다. 근데 인터넷이 안 되니까 <strong>내 노트북을 들고 1층 로비의 카페로 가서 커맨트를 달고 소통하는 게</strong> 너무 번거로웠다..</p>
<p>이 과정에서 느껴진 건, <strong>난 비효율적인 업무 스타일을 적응하는 데 너무 스트레스를 받는 거 같다.</strong> 그래서 다시는 폐쇄망을 하고 싶지 않다는 다짐을 하게 됐다.</p>
<h3 id="신입으로서-느끼는-압박감">신입으로서 느끼는 압박감</h3>
<p>모든 상황에서 나 스스로 결정해야 한다는 게 큰 압박감으로 다가왔다. 간접적인 업무 협의는 본인 스스로 진행하기 때문에 회사가 나를 많이 믿나..? 라는 생각이 들긴 하지만 아무튼 해내고 있다.</p>
<p>그런데 너무 팀장님에게 물어보면 번거로워할 거 같아서 최대한 내선에서 해결하자는 마음이랑, 이건 물어봐야 알겠는데 라는 마음이 충돌해서 늘 업무가 좀 멈칫멈칫하게 된다. <strong>신입으로서 압박감을 이겨내면서 긴장하는 상황이 가장 어려운 듯하다.</strong></p>
<h2 id="6개월-동안-무엇을-배웠나">6개월 동안 무엇을 배웠나</h2>
<p>가장 크게 늘어난 건 <strong>회사가 돌아가는 구조와 프로젝트가 진행되는 다양한 환경에서의 적응력</strong>이다. 각 고객사가 요구하는 다양한 스펙에 맞춰서 협의하고 요구사항을 정의하는 게 사실 입사한 지 얼마 안 된 사원이 직접 하기엔 어렵지만, 해낼수록 성장하고 있다는 게 느껴진다.</p>
<p>특히 RNR에 대해서 협의하는 부분이 처음엔 너무 어려웠다. 내 경우는 허용하면 내가 하면 안 될 일도 해야 할지도 모른다는 입장이라 잘못된 일까지 할까 봐 스트레스를 받았다. 근데 이젠 하도 하다 보니 자연<del>스럽게 &quot;RNR에 대해서는 이런이런 것이라고 들었으나</del> 영업대표님에게 말씀드려보겠다~&quot; 라는 식으로 거슬리지 않도록 넘어가는 바이브가 벌써 만들어졌다 😅 살아남기위한 스킬이다 그죠?</p>
<h3 id="그런데-이게-코더로서의-성장인가">그런데 이게 코더로서의 성장인가?</h3>
<p>다만 이 성장이 <strong>코더로서의 성장이냐 하면 그건 잘 모르겠다.</strong> </p>
<p>내가 하고 있는 게 형태소 분석에 대한 커스텀, 다양한 분석기에 대한 이해는 코더로서의 역량은 아니라고 생각한다. 그나마 관련되는 건 JDBC를 다루니까 초중급 정도의 SQL 난이도, 검색 API에 대한 아키텍처 설계 정도를 제외하고는 코더로서 발전이 없다.</p>
<p>API를 만드는 일도 결국 솔루션에서 제공되는 API대로 이루어지기 때문에, 이 검색에 대해서 커스텀이 들어가는 건 아니고 검색 결과를 제어하려면 API에 대한 이해가 더 중요하다. 그래서 <strong>코더로서 성장이 부족한 거 같아 불안하다.</strong></p>
<p>다양한 환경에서 혼자 투입되어 경험을 해보고 있다는 건 큰 장점이지만, 따로 코더로서의 발전을 기대할 만큼 API의 엔지니어링 난이도가 높지 않은 게 너무 크다. 결국은 내가 모든 걸 스스로 구축하는 게 아닌 솔루션의 API를 활용하여 구축하는 것이기 때문에, 비즈니스 로직보다는 솔루션을 다루는 실력이 늘어났다.</p>
<p><strong>솔직히 말하면 여기는 내가 원하는 환경은 아닌 거 같다는 생각이다.</strong></p>
<h2 id="입사하고-느끼는-것">입사하고 느끼는 것</h2>
<h3 id="1-본인이-어떤-회사를-좋아하는지-알게됨">1. 본인이 어떤 회사를 좋아하는지 알게됨</h3>
<p>보통 말하길 일, 연봉, 사람 이 중에 두 개가 마음에 안 들면 이직을 고려한다고 한다. 내 경우엔 기준이 <strong>통근거리, 일, 성장</strong> 이 세 개가 내가 원하는 회사의 척도라고 생각이 들었다.</p>
<p>특히 통근거리.. <strong>광역버스가 진짜 압도적으로 편하다.</strong> 파견으로 서울역까지 지하철로 가니까 진짜 하.. 사람이 미워지고 성격이 나빠지는 느낌이 든다.</p>
<p>한여름에 낑기는 지하철에서 어깨를 좁힌 채 핸드폰을 하고 있었는데, 앞사람의 등땀으로 손이 축축해질 때 뭔가 머리에서 선이 끊어지는 기분이 들 정도로 분노했다. 통근이 무조건 1순위라고 다짐하게 된 일화임..</p>
<p>그 외에도 회사 동료들이랑 이야기하다 보면 얻게 되는 인사이트들도 추후 이직 시에 도움이 될 것들이다.</p>
<h3 id="2-왜-부트캠프에서-레거시를-가르치는지-알게됨">2. 왜 부트캠프에서 레거시를 가르치는지 알게됨</h3>
<p>부트캠프에서 ModelAndView를 가르치고 왜 스프링 레거시를 가르쳤는지 좀 이해가 된다 😅 </p>
<p>대부분 프로젝트가 폐쇄망이고 기술들이 진짜 옛날에 짜여진 코드다. 내가 그중에 수정해야 할 부분은 일부분이기 때문에 기술 자체를 변경하기 어렵다. 타사의 로직들과 얽혀있어서 어떤 사이드 이펙트가 발생할지는 진짜 모르기 때문이더라..</p>
<p>결국 나 또한 최소한의 변경점으로 작업하게 되고 영향을 덜 미치는 방안을 찾고 있더라. JSP는 신기한 거보다는 주석이 부족해서 모르는 문법이랑 하드코딩이 엄청 길어서 루즈한 방식이었다.</p>
<p><strong>이런 환경 때문에 개발이 재미없다는 생각이 요즘 들었다.</strong> 다음 회사는 절대로 폐쇄망 작업이 많은 곳으로 이직하고 싶지 않다는 생각이다.</p>
<h3 id="3-문서작업-잘하는-개발자가-진짜-고수다">3. 문서작업 잘하는 개발자가 진짜 고수다</h3>
<p>온전히 개발을 집중하는 시간은 생각보다 엄청 적다. 방화벽 요청, 업무보고서, 사내결재 등 뭐가 엄청 많다 😅 </p>
<p>또 말단이기 때문에 최소한의 예의와 사무적인 용어를 쓰는 게 익숙치 않기 때문에 의도치 않게 시간이 오래 걸리게 된다. 프로젝트가 마무리될 때 써야 할 양식이 5개가 넘던데 벌써 아찔하다..</p>
<p>특히나 여기는 무슨 아직도 대면결재를 하는 문서가 좀 있다. 이건 진짜 불편하다고 생각한다.. 뭔 90년도도 아니고 말이야.. 아무래도 난 문서 작업은 못하는 사람인 것 같다고 느껴진다 🤯 </p>
<p>그리고 업무용은 슬랙좀 써줘요 제발..!! (MZ마인드)</p>
<h3 id="4-it회사의-사정을-알게됨">4. IT회사의 사정을 알게됨</h3>
<p>6개월 전의 나와 지금의 나, 가장 크게 달라진 점을 꼽자면 <strong>IT회사의 사정을 알게 되니 그간 의문이었던 일들이 다 어쩔 수 없이 하던 거였음을 알게 됐다.</strong></p>
<ul>
<li>어째서 효율적으로 개발 안 하는지? → 업무 과중으로 너무 바빠서 그럴 여유가 없음</li>
<li>폐쇄망 개발자들은 코더가 아니다? → 파견 온 사람들이 짠 로직들이 겹쳐져서 내 RNR 상 고칠 수 없음</li>
</ul>
<p>등등.. 현실을 알게 되니 불만보다는 이해가 생기더라. 그래도 이런 환경에 안주하고 싶진 않지만.</p>
<h2 id="마치며">마치며</h2>
<p>6개월을 돌아보니 성장은 했지만, 내가 원하는 방향의 성장인지는 확신이 서지 않는다. 프로젝트 매니징 능력, 고객 커뮤니케이션, 다양한 환경 적응력은 분명 늘었다. 하지만 코더로서, 개발자로서의 기술적 성장은 기대에 못 미친다.</p>
<p>앞으로의 원하는 회사는 명확해졌다.</p>
<ul>
<li>폐쇄망 업무가 없는 환경</li>
<li>통근 1시간 이내 ★★★★★</li>
<li>비즈니스 로직을 직접 설계하고 개발할 수 있는 곳</li>
<li>효율적인 개발 문화가 있는 곳</li>
</ul>
<p>이번 경험이 나쁘다고만 할 순 없다. 적어도 <strong>내가 어떤 개발자가 되고 싶은지, 어떤 환경에서 일하고 싶은지는 확실히 알게 됐으니까.</strong> 남은 시간 동안은 주어진 업무에 최선을 다하되, 병행해서 개인 프로젝트나 스터디를 통해 코더로서의 감각을 잃지 않으려 한다.</p>
<p>그리고 다음엔... 제발 광역버스 타고 다닐 수 있는 회사로 셔틀이 있으면 더더욱 좋고~ 🚌</p>
<hr>
<p>오늘 한 프로젝트의 이슈를 완전히 다 끝내서 시간이 나길래 간만에 올려봤습니다 ㅎㅎ; 개발자들 화이팅입니다..!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[무명일기 사이트 제작기]]></title>
            <link>https://velog.io/@sin_0/%EB%AC%B4%EB%AA%85%EC%9D%BC%EA%B8%B0-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%A0%9C%EC%9E%91%EA%B8%B0</link>
            <guid>https://velog.io/@sin_0/%EB%AC%B4%EB%AA%85%EC%9D%BC%EA%B8%B0-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%A0%9C%EC%9E%91%EA%B8%B0</guid>
            <pubDate>Wed, 16 Jul 2025 01:15:27 GMT</pubDate>
            <description><![CDATA[<h1 id="왜-무명일기인가">왜 무명일기인가?</h1>
<p>나는 <strong>내 필요로 만드는 프로젝트</strong>에 가장 큰 가치를 둔다. 그만큼 애정이 가기도 하고, 자연스럽게 독창성을 발휘하기 좋은 환경이 되기 때문이다.</p>
<p>이따금 즐기는 취미 중 하나는, 내가 느낀 감정이나 생각을 글로 적는 것이다. 독후감의 형식으로 남기거나, 내 성격에 대한 고찰, 특정 문화나 사회 현상에 대한 에세이처럼 다양한 방식으로 기록해왔다. 주로 아이폰의 기본 일기 앱을 사용했는데, 이게 은근히 쓰게 된다.</p>
<p><img src="https://velog.velcdn.com/images/sin_0/post/8f20d39b-391b-47de-863e-667b3ce70232/image.png" alt=""></p>
<p>하지만 글을 쓰다 보면 문득 <strong>내 생각을 누군가에게 보여주고 싶어지는 순간</strong>이 온다. 동시에, 그 생각을 그대로 드러내는 게 민낯을 보여주는 듯한 민망함도 뒤따른다. 😅</p>
<p>그래서 자연스럽게 떠오른 물음은</p>
<blockquote>
<p><strong>&quot;익명으로 내 글을 공유하고, 누군가의 공감을 받을 수 있는 공간은 없을까?&quot;</strong></p>
</blockquote>
<p>그런 공간을 찾아봤지만, 마땅한 서비스가 보이지 않았다.
그래서 직접 만들기로 했다. 그것이 <strong>무명일기</strong>다!</p>
<p>👉 <a href="https://anonymousdiary.vercel.app/">무명일기 사이트</a></p>
<hr>
<hr>
<h1 id="프로젝트에서의-고민들">프로젝트에서의 고민들</h1>
<h3 id="백엔드특-crud-반복-싫어함">백엔드특) CRUD 반복 싫어함</h3>
<p>백엔드 개발자로서 한 가지 고민은 늘 존재한다.
<strong>&quot;이건 그냥 CRUD만 있는 프로젝트 아닌가?&quot;</strong>
수많은 백엔드 엔지니어가 프로젝트를 기획할 때 한 번쯤 해봤을 질문일 것이다.</p>
<p>물론 CRUD는 기본이다. 하지만 그것을 얼마나 <strong>효율적으로, 효과적으로 보여줄지</strong>, 얼마나 정교하게 쪼갤 수 있을지가 진짜 역량이라고 느낀다. 세분화된 설계가 늘 좋은 결과를 낳는 건 사실이다. 결국 결론은 하나다.</p>
<p><strong>&quot;백엔드는 고생할수록 퀄리티가 오른다 = 인력이 장땡이다.&quot;</strong>
현실적인 깨달음이 느껴진다. 😅</p>
<h3 id="글-다듬기-ai">글 다듬기 AI</h3>
<p>요즘 프로젝트들이 다 그렇다. CRUD싫어하고.. 좀 특색있고싶고 하면? 바로 AI를 섞으려한다 ㅋㅋ 아 물론 본인은 필요에 의해 AI를 넣긴했다.</p>
<p>나는 일기를 다 쓰고 나면, 종종 GPT에게 &quot;조금만 다듬어줘&quot;라고 부탁하는 편이다. 일기의 본질이 흐려지는 것 같다는 걱정도 들지만, <strong>인상적인 문장은 결국 더 오래 기억에 남는다는 사실</strong>도 알게 됐다.</p>
<p>사용자 입장에서도 웹에서 일기를 쓰고, GPT를 따로 켜서 돌리는 건 귀찮은 일이 될 수 있다.
그래서 아예 내 서비스 안에 AI 다듬기 기능을 넣었다. <a href="https://velog.io/@sin_0/Spring-Ai-Gemini-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0">Spring AI를 구현하는건 어렵지않기</a>때문에 생각보다 간단하게 구현할 수 있었고, 평균 응답 속도는 5초 미만으로 꽤 실용적이다.</p>
<p>결국 쓸지는 사용자 몫이겠지만… 저는 노력했습니다.. 써봐주십쇼 🙇‍♂️</p>
<h3 id="인증과-익명의-딜레마">인증과 익명의 딜레마</h3>
<p>사실 완전히 <strong>익명 기반의 구조</strong>를 구현하고 싶었다.
하지만 봇이나 매크로 공격을 막기 위해 현실적인 타협이 필요했다.</p>
<p>그래서 선택한 방식은 <strong>SMTP + 매직링크 기반 인증</strong>이다.
이 방식은 사용자에게 비밀번호를 요구하지 않고, <strong>민감정보 저장을 최소화</strong>할 수 있다. 일종의 OAuth 우회처럼 보일 수도 있지만, <strong>DB에 저장되는 정보가 제한적</strong>이기 때문에 보안상으로도 꽤 괜찮은 선택이다.</p>
<p>물론, <strong>&quot;이메일이 뚫리면 우리도 몰라요 ㅋㅋ;&quot;</strong> 이 되겠지만, 그건 각자의 책임이니까요… 😅</p>
<hr>
<h3 id="커서는-신이다">커서는 신이다</h3>
<p>백엔드는 꽤 잘 구성해오고 있다고 생각한다.
구조를 어떻게 짤지, 리팩토링 규칙을 어떻게 가져갈지, 내규를 세우고 스스로 잘 지켜왔다.</p>
<p>문제는… 역시 <strong>프론트엔드</strong>였다.</p>
<p>물론 예전보다는 구현 능력 자체가 많이 나아졌다고 생각한다. 특히 <strong>구조 설계, 상태 관리 전략, 렌더링 최적화</strong> 같은 이론적인 부분은 꾸준히 공부해왔다. 그러다 보니 <strong>바이브 코딩 능력</strong>이 많이 향상됐고, 덕분에 프론트도 꽤 빠르게 구현할 수 있게 되었다.</p>
<p>이번 프로젝트도 대부분을 커서로 <strong>바이브 코딩</strong>으로 진행했고, 백엔드는 약 1주 프론트는 약 3일만에 프로토타입이 나왔다.</p>
<p>현재 코드 분량은 백엔드 약 13,000줄, 프론트 약 15,000줄 정도다.</p>
<p>바이브 코딩은 미래고, <strong>커서는 백엔드 개발자의 구세주다..!</strong></p>
<blockquote>
<p>여담이지만, 요즘은 취업할 때 코딩 테스트 자체의 의미가 퇴색된 것 같다. 금융권이라던지 내부망만 존재하는 기업이 아니고서야 이게 필요한가 싶다. 모두가 대화형 ai를 적극활용하고 최근은 mcp가 각광받고있는 추세이다. 우수한 코더가 우수한 개발자가 맞을까? 아니, 내 손으로 잘 짤 필요가 있을까? 하는 생각이 지배적으로 든다. <strong>AI를 만들지 못한다면, 적어도 적극적으로 활용할 수 있는 능력</strong>은 반드시 필요하다고 본다. 트렌드를 민감하게 따라가는 것도 중요해보인다.</p>
</blockquote>
<hr>
<h3 id="기업은-정답을-알고-있다">기업은 정답을 알고 있다</h3>
<p>최근 들어 느끼는 점은, <strong>대기업은 늘 정답을 알고 있었다</strong>는 것이다.
내가 밥먹으러 갈때부터 잘때까지 수많은 고민 끝에 나온 최적의 구조나 전략은, 이미 그들의 기술 스택에 적용되어 있는 경우가 많았다.</p>
<p>그래서 요즘은 클론 코딩을 자주해보는게 이를 단축시키는 과정일 것 같기도하다. 비슷한 구조를 생각해냈을 때는 뿌듯하지만, 동시에 나도 역시 범부인가.. 하는 씁쓸함도 들고, 이 정도면 충분히 안도해도 되는 위치인가 싶기도 하고? 복잡한 기분이네요</p>
<hr>
<h3 id="마무리">마무리</h3>
<p>이번 프로젝트는 실제 서비스로 다듬어서 운영해보고 싶은 욕심이 있다.
회사 일을 병행하면서 여러 사이드 프로젝트를 하는 사람들 정말 리스펙트한다. 진심으로 어케하시죠?</p>
<p>지금은 Vercel을 통해 배포했지만, 추후엔 SEO 최적화도 고려해서 일기 사이트 라고 검색하면 당당하게 내 사이트가 보여지도록 하고싶다.</p>
<p>혹시 이 글을 읽고 있는 여러분도, 한 번쯤 일기를 써보는 건 어떨까요? 생각보다 내면을 정리하고, 스스로를 다듬는 데 꽤 도움이 되는 취미입니다 👍</p>
<p>그렇다면…</p>
<p>이왕이면 제 사이트에서 써주시면 감사하겠습니다. 🙏</p>
<p>👉 <a href="https://anonymousdiary.vercel.app/">무명일기 써보러 가기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring JPA] Slice를 활용해서 무한스크롤 기능 구현하기]]></title>
            <link>https://velog.io/@sin_0/Spring-JPA-Slice%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%B4%EC%84%9C-%EB%AC%B4%ED%95%9C%EC%8A%A4%ED%81%AC%EB%A1%A4-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sin_0/Spring-JPA-Slice%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%B4%EC%84%9C-%EB%AC%B4%ED%95%9C%EC%8A%A4%ED%81%AC%EB%A1%A4-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 02 Jul 2025 02:17:58 GMT</pubDate>
            <description><![CDATA[<p>최근 무한스크롤 기반의 일기 웹사이트 프로젝트를 진행하던 도중, 일기 전체, 내가 쓴 일기 목록, 열람 기록, 북마크 리스트 등 대부분의 리스트 API에 페이징이 필요했다.</p>
<p>매번 Page를 썼는데 Page에는 일기가 만약 수십만건이라하면 수십만건의 데이터 조회쿼리가 들어가기 때문에 비효율적이라고 생각해서 좀 더 무한 스크롤 기능에 어울리는 기능인 Slice를 써보았는데 효율적인 것 같아서 글을 적어보겠다.</p>
<hr>
<h2 id="page-vs-slice-차이">Page vs Slice 차이</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th><code>Page</code></th>
<th><code>Slice</code></th>
</tr>
</thead>
<tbody><tr>
<td><strong>총 개수</strong></td>
<td><code>totalElements</code>, <code>totalPages</code> 제공</td>
<td>제공하지 않음</td>
</tr>
<tr>
<td><strong>다음 페이지 여부</strong></td>
<td><code>hasNext()</code></td>
<td><code>hasNext()</code></td>
</tr>
<tr>
<td><strong>추가 정보</strong></td>
<td><code>totalElements</code>, <code>totalPages</code></td>
<td>없음</td>
</tr>
<tr>
<td><strong>쿼리 비용</strong></td>
<td><code>SELECT COUNT(*)</code> 필요</td>
<td>불필요</td>
</tr>
<tr>
<td><strong>용도</strong></td>
<td>페이지 기반 UI, 전체 건수 필요할 때</td>
<td>무한 스크롤</td>
</tr>
</tbody></table>
<hr>
<h2 id="실제-적용-사례">실제 적용 사례</h2>
<h3 id="기존-구조">기존 구조</h3>
<p>내 프로젝트의 <code>/api/diaries/public</code>, <code>/api/diaries/feed/recent</code>, <code>/api/diaries/me</code>, <code>/api/views/me</code> API는 모두 Page 기반으로 구성되어 있었다.</p>
<p>하지만 피드나 일기 목록은 사용자가 스크롤로 내려가며 <strong>끝없이 내려보는 형태</strong>가 이상적이며, 전체 개수를 표시할 필요도 없었다.</p>
<hr>
<h2 id="slice-적용-이유-정리">Slice 적용 이유 정리</h2>
<p><strong>무한 스크롤에 최적화</strong></p>
<ul>
<li>사용자는 “다음 페이지가 있는지”만 알면 되므로 Slice의 <code>hasNext</code>로 충분</li>
<li>“전체 몇 개 중 몇 번째”는 필요 없었음</li>
</ul>
<p><strong>성능 개선</strong></p>
<ul>
<li>Page는 <code>total count</code>를 구하기 위한 <code>SELECT COUNT(*)</code> 쿼리가 무조건 발생</li>
<li>Slice는 이를 생략해 쿼리 비용이 줄어듦</li>
<li>피드/목록의 데이터가 많아질수록 이 차이는 커진다</li>
<li>10만건 / 20Limit 이라 치면 단순 COUNT가 30ms로 가정할 때 총 5,000페이지를 요청해야 하니까 <code>30ms * 5000회 = 150,000ms</code> 이 발생하므로 2.5분의 순수한 오버헤드가 줄어든 셈이다.</li>
</ul>
<hr>
<h2 id="slice-작동-방식">Slice 작동 방식</h2>
<ol>
<li>프론트가 <code>/api/diaries/feed/recent?page=0&amp;size=10</code> 요청</li>
<li>서버는 <code>LIMIT 10 OFFSET 0</code> 으로 10개 데이터를 가져오고, 다음 페이지 존재 여부 판단 후 <code>hasNext = true/false</code> 반환</li>
<li>프론트는 <code>hasNext=true</code> 면 다음 페이지(<code>page=1</code>) 요청, 아니면 호출 중단</li>
<li>사용자는 무한 스크롤 UX로 자연스럽게 이용 가능</li>
</ol>
<hr>
<h2 id="slice를-적용하는-방법">Slice를 적용하는 방법</h2>
<p>Slice를 쓰려면 <strong>리포지토리에서 메서드 반환 타입만 Page → Slice로 바꿔주면 끝</strong>이다.</p>
<pre><code class="language-java">public interface DiaryRepository extends JpaRepository&lt;Diary, Long&gt; {
    Slice&lt;Diary&gt; findAllByVisibleTrueOrderByCreatedAtDesc(Pageable pageable);
}</code></pre>
<p>서비스/컨트롤러에서 기존 Page로 받던 부분을 Slice로 변경하면 된다.</p>
<pre><code class="language-java">@GetMapping(&quot;/api/diaries/feed/recent&quot;)
public ResponseEntity&lt;Slice&lt;VisibleDiarySummaryDto&gt;&gt; getRecentFeed(
        @PageableDefault(size = 10, sort = &quot;createdAt&quot;, direction = DESC) Pageable pageable
) {
    Slice&lt;VisibleDiarySummaryDto&gt; feed = diaryService.getRecentFeed(pageable);
    return ResponseEntity.ok(feed);
}</code></pre>
<p>이렇게 하면 무한 스크롤에 필요한 <code>hasNext</code>, <code>content</code>, <code>size</code>, <code>number</code> 등의 정보만 담긴 가벼운 응답으로 전환할 수 있다.</p>
<hr>
<h2 id="실제-예시--응답-예시">실제 예시 – 응답 예시</h2>
<pre><code class="language-json">{
  &quot;content&quot;: [
    {
      &quot;id&quot;: 8,
      &quot;title&quot;: &quot;월요일 싫어&quot;,
      &quot;content&quot;: &quot;내일 출근하기 싫다...&quot;,
      &quot;createdAt&quot;: &quot;2025-07-02T09:26:57.496&quot;,
      &quot;viewed&quot;: true,
      &quot;totalReactionCount&quot;: 3,
      &quot;commentCount&quot;: 2
    }
  ],
  &quot;pageable&quot;: { ... },
  &quot;last&quot;: false,
  &quot;first&quot;: true,
  &quot;hasNext&quot;: true,
  &quot;size&quot;: 10,
  &quot;number&quot;: 0
}</code></pre>
<p>프론트는 <code>hasNext</code>만 보고 다음 페이지를 호출하면 되므로 구현과 유지가 단순해진다.</p>
<hr>
<h2 id="그럼-언제-page를-쓰면-좋을까">그럼 언제 Page를 쓰면 좋을까?</h2>
<ul>
<li>전체 데이터 개수를 보여줘야 할 때</li>
<li>페이지네이션 UI (1, 2, 3, 4...) 를 제공해야 할 때</li>
<li>관리자 화면, 게시판 등의 특정 상황</li>
</ul>
<p>그 외의 대부분의 피드/리스트에서는 <strong>Slice 기반 무한 스크롤이 유리하다.</strong></p>
<hr>
<h2 id="정리">정리</h2>
<p>내 프로젝트 기준으로도 체감되는 효율 개선이 있었고, 불필요한 COUNT 쿼리를 없앰으로써 대량 데이터에서 성능 병목을 방지할 수 있다 !</p>
<p>구현하는 서비스가 무한 스크롤 기반이라면, Page 대신 Slice로 전환해 보길 추천한다 👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring AI + Gemini 사용해보기]]></title>
            <link>https://velog.io/@sin_0/Spring-Ai-Gemini-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@sin_0/Spring-Ai-Gemini-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Thu, 26 Jun 2025 15:41:13 GMT</pubDate>
            <description><![CDATA[<p>최근에 Spring Ai를 알았는데 이게 참 신세계다..</p>
<p>예전엔 ai연동하려면 RestTemplate써서 모델올려둔 서버에 api쏘고 그랬던거같은데 😅</p>
<p>근데 은근 이게 막히는게 많아서 글로 남겨보려고한다.</p>
<p>Google Cloud의 Vertex AI Gemini 모델을 연동하여 환경 구성부터 API 호출까지의 전체 과정을 알아보도록하자!
사용 환경은 IntelliJ 기준이다.</p>
<hr>
<h2 id="1-google-cloud-무료-체험-등록하기">1. Google Cloud 무료 체험 등록하기</h2>
<p>먼저 <strong><a href="https://console.cloud.google.com/">Google Cloud Console</a></strong> 에서 결제 정보를 등록하면 90일 동안 무료로 사용할 수 있다.</p>
<ul>
<li><strong>주의:</strong> 무료 체험은 최초 생성한 프로젝트에만 적용된다. 이후 생성한 프로젝트는 별도 요금이 부과되거나, 무료 혜택이 적용되지 않을 수 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sin_0/post/a25e5323-e99a-4a5e-9ab8-09569b67a435/image.png" alt="결제 정보 입력 예시"></p>
<p>정상적으로 했으면 이렇게 나온다</p>
<hr>
<h2 id="2-iam-및-서비스-계정-만들기">2. IAM 및 서비스 계정 만들기</h2>
<p>Vertex AI 사용을 위해 서비스 계정을 생성하고 적절한 권한을 부여해야 한다.</p>
<ol>
<li><strong>IAM 및 관리자 &gt; 서비스 계정 &gt; 계정 만들기</strong></li>
<li>이름과 설명은 자유롭게 입력</li>
<li>역할은 <code>Vertex AI 사용자</code>로 설정</li>
<li>나머지 항목은 생략하고 생성 완료</li>
</ol>
<p><img src="https://velog.velcdn.com/images/sin_0/post/3e608b4d-f14e-4627-90b0-99f57533ab43/image.png" alt="서비스 계정 생성"></p>
<h3 id="🔑-서비스-계정-키-발급하기">🔑 서비스 계정 키 발급하기</h3>
<ul>
<li><strong>서비스 계정 &gt; 키 관리 &gt; 키 추가 &gt; 새 키 만들기(JSON)</strong> 을 선택</li>
<li>생성된 키 파일은 프로젝트의 <code>resources</code> 폴더에 저장하고 <code>.gitignore</code>에 반드시 등록해두기</li>
</ul>
<hr>
<h2 id="3-vertex-ai-api-사용-설정하기">3. Vertex AI API 사용 설정하기</h2>
<p><strong>API 및 서비스 &gt; 라이브러리</strong> 메뉴에서 <code>Vertex AI API</code>를 검색한 후, <strong>사용</strong> 버튼을 클릭하여 활성화한다.</p>
<hr>
<h2 id="4-gradle-설정하기">4. Gradle 설정하기</h2>
<pre><code class="language-groovy">plugins {
    id &#39;java&#39;
    id &#39;org.springframework.boot&#39; version &#39;3.5.3&#39;
    id &#39;io.spring.dependency-management&#39; version &#39;1.1.7&#39;
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

dependencyManagement {
    imports {
        mavenBom &quot;org.springframework.ai:spring-ai-bom:1.0.0&quot;
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
    implementation &#39;org.springframework.ai:spring-ai-starter-model-vertex-ai-gemini&#39;
    testImplementation &#39;org.springframework.boot:spring-boot-starter-test&#39;
}</code></pre>
<hr>
<h2 id="5-applicationyml-설정하기">5. application.yml 설정하기</h2>
<pre><code class="language-yaml">spring:
  ai:
    vertex:
      ai:
        credentials-uri: classpath:genai-key.json
        gemini:
          project-id: YOUR_PROJECT_ID
          location: us-central1
      gemini:
        chat:
          options:
            model: gemini-2.0-flash</code></pre>
<h3 id="properties라면">properties라면</h3>
<pre><code>spring.ai.vertex.ai.credentials-uri=classpath:genai-key.json
spring.ai.vertex.ai.gemini.project-id=YOUR_PROJECT_ID
spring.ai.vertex.ai.gemini.location=us-central1
spring.ai.vertex.gemini.chat.options.model=gemini-2.0-flash
</code></pre><p>모델은 저런식으로 명칭을 쓰면되는데 지금은 2.5-pro 까지 나온상태인듯하다. 적당한거 쓰자.</p>
<hr>
<h2 id="6-컨트롤러-구현하기">6. 컨트롤러 구현하기</h2>
<p>Spring AI의 <code>ChatClient</code>를 활용하여, 일기를 자연스럽게 다듬어주는 API를 만들어보았다.</p>
<pre><code class="language-java">
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(&quot;/api/ai&quot;)
public class TestAiController {

    private final ChatClient chatClient;

    public TestAiController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    @PostMapping(&quot;/refine-diary&quot;)
    public String refineDiary(@RequestBody DiaryRefineRequest req) {
        String prompt = &quot;&quot;&quot;
            아래는 사용자가 작성한 일기야.
            문맥을 바꾸지 않으면서 어색한 표현을 자연스럽게 다듬어 주고
            존댓말을 유지하고, 문장은 최대한 매끄럽게 수정해줘.

            [사용자 일기 원문]
            %s
            &quot;&quot;&quot;.formatted(req.diary());

        return chatClient
                .prompt()
                .user(prompt)
                .call()
                .content();
    }

    public record DiaryRefineRequest(String diary) {}
}</code></pre>
<hr>
<h2 id="7-환경변수-등록하기">7. 환경변수 등록하기</h2>
<p>IntelliJ 기준으로 <strong>Run ▸ Edit Configurations &gt; Environment Variables</strong>에서 아래 값을 추가한다.</p>
<pre><code>GOOGLE_APPLICATION_CREDENTIALS=\절대\경로\genai-key.json</code></pre><blockquote>
<p>해당 값을 정상적으로 등록하지 못했으면 <code>Your default credentials were not found</code> 오류가 발생한다.</p>
</blockquote>
<hr>
<h2 id="8-postman으로-api-테스트하기">8. Postman으로 API 테스트하기</h2>
<p>API가 정상적으로 작동하는지 Postman을 통해 테스트해볼 수 있다.</p>
<ul>
<li>엔드포인트: <code>POST /api/ai/refine-diary</code></li>
<li>JSON Body 예시:</li>
</ul>
<pre><code class="language-json">{
  &quot;diary&quot;: &quot;오늘은 기분이 나빳다. 친구랑 싸워서 속상하다 근데 내가 참아야 되는건가? 매번 친구는 사과를 받는편이고 나는 사과를 하는편이다. 오늘은 왠지 평소처럼 무던하게 넘겨보낼 수 없는 하루였다. 무더운 더위때문이였을까ㅡ오늘은 장마 후 날씨라 불쾌지수가 조금 상승한 탓일까 기후에 기대서 평소와 다른 내 태도를 분석해본다.&quot;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/sin_0/post/99137285-8881-45a3-bd6e-b84d2c7aa968/image.png" alt=""></p>
<p>뭐 이런식으로?</p>
<hr>
<h2 id="✅-마무리-정리">✅ 마무리 정리</h2>
<ul>
<li>발급받은 서비스 계정 키는 <code>.gitignore</code>에 반드시 등록해야 한다. <strong>안그러면... 책임못진다</strong></li>
<li>사용 가능한 모델명(<code>gemini-2.0-flash</code>)은 향후 변경될 수 있으므로 공식 문서를 참고해보자</li>
</ul>
<hr>
<p>이젠 스프링도 AI시대인가~?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 초기개발 꿀팁 정리]]></title>
            <link>https://velog.io/@sin_0/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%B4%88%EA%B8%B0%EA%B0%9C%EB%B0%9C-%EA%BF%80%ED%8C%81</link>
            <guid>https://velog.io/@sin_0/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%B4%88%EA%B8%B0%EA%B0%9C%EB%B0%9C-%EA%BF%80%ED%8C%81</guid>
            <pubDate>Wed, 25 Jun 2025 02:11:43 GMT</pubDate>
            <description><![CDATA[<p>본인이 프로젝트 초기마다 유용하게 쓰는 팁들 몇가지가 있는데 기억날 때 마다 적어두겠습니다. 알쓸신잡이라서 한번씩 읽어보면 좋을지도?</p>
<hr>
<h2 id="1-controller-안에-dtorecord-넣고-리팩토리하기">1. Controller 안에 DTO(record) 넣고 리팩토리하기</h2>
<p>인텔리제이 윈도우 기준 F6 키를 누르거나 우클릭&gt;Refactor&gt;Move Inner Class&gt;to upper level 하면
<img src="https://velog.velcdn.com/images/sin_0/post/5c332385-841d-4599-bc92-a2c282c000ba/image.png" alt="">
편하게 DTO로 변경할 수 있다. 특히나 record를 사용할 때 DTO를 컨트롤러에 구현해두면 왔다갔다안해서 보기 편한데 리팩토링하는 경우 자주 씀.</p>
<h2 id="2-lombok으로-entityservice-코드-줄이기">2. Lombok으로 Entity/Service 코드 줄이기</h2>
<pre><code class="language-java">@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Diary { ... }</code></pre>
<p>뭐.... 상식이죠 사실..?</p>
<ul>
<li><code>@Builder</code>로 생성자 관리, 테스트/서비스 코드 작성 편리</li>
<li>초기에는 <code>@Data</code>로 빠르게 쓰고, 나중에 <code>@Getter</code>, <code>@ToString</code> 등으로 분리 리팩터링 가능</li>
<li><strong>단점</strong>: 리플렉션 기반으로 컴파일 오류 추적 어려움</li>
</ul>
<hr>
<h2 id="3-postman-안-써도-되는-spring-rest-client-기능">3. Postman 안 써도 되는 Spring REST Client 기능</h2>
<ul>
<li><code>IntelliJ</code> → HTTP Request (<code>.http</code> 파일)</li>
</ul>
<pre><code class="language-http">### Create Diary
POST http://localhost:8080/api/diaries
Content-Type: application/json

{
  &quot;content&quot;: &quot;기록 테스트&quot;,
  &quot;allowComment&quot;: true,
  &quot;visible&quot;: true
}</code></pre>
<ul>
<li>바로 IntelliJ에서 실행하고 응답 확인 가능</li>
<li><code>.http</code> 파일로 API 문서 겸 테스트 스크립트 관리 가능</li>
</ul>
<hr>
<h2 id="4-db-스키마-자동-생성-후-export하기-jpa-설정-팁">4. DB 스키마 자동 생성 후 export하기 (JPA 설정 팁)</h2>
<pre><code class="language-yaml">spring:
  jpa:
    hibernate:
      ddl-auto: create
    properties:
      jakarta:
        persistence:
          schema-generation.scripts.action: create
          schema-generation.scripts.create-target: schema.sql</code></pre>
<ul>
<li>초기 개발 시 스키마를 자동으로 export 가능 (루트 디렉터리에 <code>schema.sql</code>가 생성 됨)</li>
<li><code>create-drop</code>을 사용하면 테스트용으로도 유용</li>
</ul>
<hr>
<h2 id="5-테스트-자동-템플릿-생성-live-templates">5. 테스트 자동 템플릿 생성 (Live Templates)</h2>
<ul>
<li>IntelliJ <code>Settings &gt; Live Templates</code>에서 JUnit 템플릿 등록</li>
<li>예: <code>testpub</code> → <code>@Test public void $METHOD$() { }</code> 자동 생성</li>
<li>테스트 함수 이름도 Tab만으로 빠르게 작성 가능</li>
</ul>
<hr>
<h2 id="6-swaggerconfig-없이-빠르게-api-확인하기--springdoc-openapi">6. SwaggerConfig 없이 빠르게 API 확인하기 – springdoc-openapi</h2>
<ul>
<li><code>build.gradle</code>에 아래 추가</li>
</ul>
<pre><code class="language-groovy">implementation &#39;org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0&#39;</code></pre>
<ul>
<li>실행 후 <code>/swagger-ui.html</code> 로 접근</li>
<li>초기 API 빠르게 확인 및 테스트 가능</li>
<li>호환 버전은 <a href="https://springdoc.org/">공식문서</a>를 확인하자 </li>
</ul>
<hr>
<h2 id="7-application-설정파일-분리-팁">7. application 설정파일 분리 팁</h2>
<pre><code class="language-yml"># application.yml
spring:
  config:
    activate:
      on-profile: local

# application-local.yml
server:
  port: 8081</code></pre>
<pre><code class="language-yaml"># application.properties
spring:
  profiles:
    active: dev</code></pre>
<ul>
<li><code>application-dev.yml</code>, <code>application-prod.yml</code> 등 환경별 분리</li>
<li>실행할 Application에서 Edit Configurations &gt; Program arguments에 <code>--spring.profiles.active=local</code> 입력해서 실행 환경 쉽게 전환하면 편함</li>
</ul>
<hr>
<p>인텔리제이는 신이다..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[오류/해결]테스트 코드 작성 시 package xxx.xxx.config does not exist]]></title>
            <link>https://velog.io/@sin_0/%EC%98%A4%EB%A5%98%ED%95%B4%EA%B2%B0%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1-%EC%8B%9C-package-xxx.xxx.config-does-not-exist</link>
            <guid>https://velog.io/@sin_0/%EC%98%A4%EB%A5%98%ED%95%B4%EA%B2%B0%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1-%EC%8B%9C-package-xxx.xxx.config-does-not-exist</guid>
            <pubDate>Wed, 25 Jun 2025 00:37:09 GMT</pubDate>
            <description><![CDATA[<p>유닛테스트 도중 다음과 같은 오류가 발생</p>
<pre><code>error: package com.xxx.xxx.config does not exist</code></pre><h3 id="알고있는-것">알고있는 것</h3>
<ul>
<li><code>SecurityConfig.java</code> 파일은 분명 존재함</li>
<li>IntelliJ에서 import 시도 시 자동완성도 정상 작동 (<code>Alt + Enter</code>)</li>
<li>그러나 Gradle 빌드 시만 해당 패키지를 찾을 수 없다는 오류 발생</li>
</ul>
<h3 id="증상">증상</h3>
<ul>
<li><code>@Import(SecurityConfig.class)</code> 선언이 있는 테스트 클래스에서 <code>package does not exist</code> 컴파일 오류 발생</li>
<li><code>main()</code> 실행은 문제 없이 잘 작동</li>
<li>IntelliJ에서 import에 &quot;Unused import&quot; 경고는 있어도 오류는 아님</li>
</ul>
<h3 id="체크해본-것">체크해본 것</h3>
<ol>
<li><p>IDE설정 확인하기
main의 루트디렉터리 인식
<img src="https://velog.velcdn.com/images/sin_0/post/e8da1b35-4926-4b87-b544-93710a86576e/image.png" alt="">
test의 루트디렉터리 인식
<img src="https://velog.velcdn.com/images/sin_0/post/a3595d38-1502-4efb-9673-ad0804f3baad/image.png" alt=""></p>
</li>
<li><p>@Import(SecurityConfig.class)로 강제 인식시켜보기</p>
<pre><code class="language-java">@SpringBootTest
@AutoConfigureMockMvc(addFilters = true)
@ActiveProfiles(&quot;test&quot;)
@Import(SecurityConfig.class)
class UserControllerTest {

 @Autowired
 private MockMvc mockMvc;

 @Autowired
 private JwtTokenProvider jwtTokenProvider;</code></pre>
</li>
</ol>
<h2 id="해결">해결</h2>
<pre><code>./gradlew clean build --refresh-dependencies</code></pre><ul>
<li>clean: 기존 빌드 파일과 캐시 전부 제거</li>
<li>--refresh-dependencies: 의존성 전부 다시 다운로드</li>
</ul>
<hr>
<p>IntelliJ가 import는 되는데 Gradle이 컴파일을 못하면 대부분 캐시 문제다.</p>
<p>무작정 코드를 의심하기 전에 <code>clean build --refresh-dependencies</code>부터 돌려보자...</p>
<p>난 틀리지않았어!(합법)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[테스트코드.. 진짜 써야할까?]]></title>
            <link>https://velog.io/@sin_0/%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C..-%EC%A7%84%EC%A7%9C-%EC%8D%A8%EC%95%BC%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@sin_0/%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C..-%EC%A7%84%EC%A7%9C-%EC%8D%A8%EC%95%BC%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Mon, 23 Jun 2025 01:44:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>“등장인물은 작가의 지능을 넘지 못한다.”</p>
</blockquote>
<p>이 문장을 처음 들었을 때, 나는 테스트 코드가 떠오른다.</p>
<p><strong>테스트 코드라는 건 결국, 내가 만든 로직을 내가 만든 기준으로 검증하는 일이다.</strong>   그래서 종종 이런 생각이 들기도 한다.</p>
<blockquote>
<p>“아니, 그래서 테스트코드는 결국 본인이 만들어낸 쉐도우복싱이 아닌가?”</p>
</blockquote>
<hr>
<h2 id="현실-속-테스트-코드의-풍경">현실 속 테스트 코드의 풍경</h2>
<p>대부분의 개발자들은 테스트 코드의 중요성을 알고 있다.<br>TDD라는 개념도 알고, 단위 테스트를 작성해 본 적도 있다.<br>Junit, Mockito 같은 도구도 익숙하다. 하지만? ㅋㅋ</p>
<h4 id="딸깍">&quot;딸깍&quot;</h4>
<pre><code class="language-java">System.out.println(obj.toString());</code></pre>
<ul>
<li>로그 찍고</li>
<li>흐름 추적하고</li>
<li>직접 눈으로 확인하고</li>
<li>수정하고 끝</li>
</ul>
<p>특히 실무에선 이런 방식이 훨씬 빠르게 느껴진다.
테스트 코드는 작성도 귀찮고, 깨지면 고쳐야 하고, 런타임도 느리다.
게다가 기능이 바뀌면 테스트 코드도 함께 바꿔야 하니
결국 손이 더 간다고 느껴질 때도 많다.</p>
<hr>
<h2 id="테스트는-정말-비효율적일까">테스트는 정말 비효율적일까?</h2>
<p><strong>맞다. 테스트는 늘 효율적이지 않다.</strong></p>
<p>실무에서는 잦은 요구사항 변경으로 테스트가 자주 깨질 수 있고, 구조가 불안정할 땐 테스트를 짜는 게 오히려 발목을 잡기도 한다.</p>
<p>작은 기능엔 오히려 로그나 직접 확인이 더 빠르다.</p>
<p>게다가 앞서 말한 것처럼, 테스트는
&quot;<strong>내가 만든 시스템을 내가 만든 상상력으로 검증하는 것</strong>&quot;일 뿐이다.
완전히 새로운 사고를 할 수 있는 것도 아니고,
예상치 못한 시나리오는 대부분 테스트 밖에 있다.</p>
<hr>
<h2 id="그럼에도-테스트-코드를-쓰는-이유">그럼에도 테스트 코드를 쓰는 이유</h2>
<p>그런데도 나는 테스트 코드를 완전히 포기하진 않는다. (일단 노력은 해봄.. 마감기한이 다가오지 않는다면 😅)
왜냐하면 테스트 코드는 <strong>정답을 보장하는 수단</strong>은 아니지만,
<strong>위험을 줄여주는 안전망</strong>이기 때문이다.</p>
<hr>
<h3 id="내가-이해한-테스트-코드의-진짜-역할">내가 이해한 테스트 코드의 진짜 역할</h3>
<p>단순히 &quot;<strong>테스트 코드로 버그를 잡는다</strong>&quot;가 아니라, &quot;<strong>테스트 코드로 코드에 대한 신뢰를 하나 더 쌓는다</strong>&quot;는 쪽에 가깝다.</p>
<ul>
<li>혼자서 작업할 땐 로그로 빠르게 확인하면 된다.</li>
<li>하지만 여러 명이 함께 일할 땐, <strong>코드에 대한 신뢰 근거가 필요하다</strong>.</li>
<li>테스트는 그걸 만들어주는 <strong>명문화된 계약</strong>이다.</li>
</ul>
<hr>
<h3 id="그래서-나는-이렇게-합의하게-됐다">그래서 나는 이렇게 합의하게 됐다</h3>
<ul>
<li>모든 걸 테스트하진 않는다.</li>
<li>하지만 <strong>핵심 로직은 테스트로 방어선을 그어둔다</strong>.</li>
<li>테스트는 개발 속도를 늦추는 게 아니라,
<strong>미래에 발생할 리스크를 줄이는 보험</strong>이라고 생각한다.</li>
</ul>
<hr>
<h2 id="결론">결론</h2>
<p>테스트와 TDD는 <strong>절대적인 방패는 아니다</strong>. 하지만 협업과 유지보수에서 <strong>필요한 안전망</strong>이다.</p>
<p>결국 중요한 건 &#39;모든 걸 테스트할 수 없다&#39;는 걸 인정하면서도,
그 한계 안에서 어떻게 테스트를 의미 있게 활용할지를 고민하는 자세라고 생각한다.</p>
<p>또 이렇게 써두고 비즈니스 로직부터 짜는 바로 너.. 이번엔 반드시 꼬옥 TDD해보도록</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[실무에 도입해볼만한 Java Spring 설계 규칙들]]></title>
            <link>https://velog.io/@sin_0/%EC%8B%A4%EB%AC%B4%EC%97%90-%EB%8F%84%EC%9E%85%ED%95%B4%EB%B3%BC%EB%A7%8C%ED%95%9C-Java-Spring-%EC%84%A4%EA%B3%84-%EA%B7%9C%EC%B9%99%EB%93%A4</link>
            <guid>https://velog.io/@sin_0/%EC%8B%A4%EB%AC%B4%EC%97%90-%EB%8F%84%EC%9E%85%ED%95%B4%EB%B3%BC%EB%A7%8C%ED%95%9C-Java-Spring-%EC%84%A4%EA%B3%84-%EA%B7%9C%EC%B9%99%EB%93%A4</guid>
            <pubDate>Mon, 23 Jun 2025 01:04:30 GMT</pubDate>
            <description><![CDATA[<h3 id="왜-이런-규칙이-필요할까">왜 이런 규칙이 필요할까?</h3>
<p>웹 개발을 하다 보면 구조가 엉키고, 어느새 코드가 유지보수하기 어려워지는 경험을 하게 된다.<br>다양한 프로젝트를 겪다보니 그 상황에 맞는 <strong>깨끗한 설계</strong>의 필요성을 절감하면서 몇 가지 규칙들을 스스로 세우게 됐다.</p>
<p>이 글에서는 내가 경험적으로 적용해왔고, 실무에 바로 도입해볼 수 있는 Java Spring 기반 웹 개발 설계 규칙들을 정리해본다.</p>
<hr>
<h3 id="1-controller는-입출력만-담당한다">1. Controller는 입출력만 담당한다</h3>
<p>Controller는 말 그대로 HTTP 요청을 받아 처리하고, 응답을 내려주는 역할만 담당하는 게 좋다.<br>입력값을 DTO로 받고, 결과를 ResponseDTO로 변환해 내려주는 구조가 명확하다.</p>
<p>이렇게 하면 테스트도 수월하고, 변경에 유연한 구조를 만들 수 있다.</p>
<p>그런데 이렇게 되면 <strong>Service 계층이 너무 복잡해지는 건 아닌가?</strong> 라는 의문이 들 수도 있다.<br>실제로 Service가 무거워지는 경우가 많다. 그럴 땐 다음처럼 분리하는 걸 추천한다.</p>
<ul>
<li>유효성 검사: <code>Validator</code> 혹은 <code>Checker</code> 클래스에서 수행</li>
<li>포맷 변환, 계산 로직: <code>Util</code> 혹은 <code>Helper</code>로 분리</li>
<li>외부 연동 처리: <code>Client</code> 또는 <code>Adapter</code> 클래스 따로 분리</li>
</ul>
<p>핵심 비즈니스 로직만 Service에 남기고 나머지를 분리하면, 오히려 Service가 더 깔끔해진다.</p>
<hr>
<h3 id="2-service--serviceimpl-구분">2. Service / ServiceImpl 구분</h3>
<p>Service는 인터페이스와 구현 클래스를 나누는 걸 기본 구조로 가져간다.<br>테스트 코드 작성이나 확장성 측면에서 유리하다.<br>특히 프로젝트 규모가 커지거나, 여러 구현체가 필요한 상황에서 효과를 본다. </p>
<p>사실 좀 관례인 것 같기도하다 😅 구현체를 좀 자주 바꾸는 경우는 확실히 유용하다.</p>
<hr>
<h3 id="3-requestdto와-responsedto는-구분한다">3. RequestDTO와 ResponseDTO는 구분한다</h3>
<p>입력과 출력은 책임이 다르다.<br>사용자가 입력하는 필드와 클라이언트에게 보여줘야 할 필드는 완전히 다를 수 있다.</p>
<p>예를 들어, 사용자의 패스워드나 내부 상태 값은 응답에 포함되면 안 된다.<br>별도의 DTO로 구분하면 이런 실수를 줄일 수 있다. </p>
<p>구현이 어렵다면 초기엔 DTO를 통합하고 그 후 분리를 하는걸 추천한다.</p>
<hr>
<h3 id="4-entity와-dto는-철저히-분리">4. Entity와 DTO는 철저히 분리</h3>
<p>Entity 객체를 그대로 응답으로 내려주거나, 사용자 입력을 그대로 매핑하는 방식은 지양한다.<br>JPA의 lazy-loading, dirty checking 등에 의도치 않은 영향이 갈 수 있다.<br>DTO ↔ Entity 간 변환은 <code>Mapper</code> 혹은 <code>Converter</code> 클래스를 별도로 만들어 처리하는 것이 좋다.</p>
<hr>
<h3 id="5-lombok-data-대신-필요한-어노테이션만-사용">5. Lombok @Data 대신 필요한 어노테이션만 사용</h3>
<p><code>@Data</code>는 너무 많은 걸 한 번에 생성해버린다.<br><code>toString</code>, <code>equals</code>, <code>hashCode</code> 등이 자동 생성되기 때문에 불필요한 연산이나 순환 참조 문제가 발생할 수 있다.<br><code>@Getter</code>, <code>@Setter</code>, <code>@Builder</code> 등을 필요한 만큼 명시적으로 사용하는 게 좋다.</p>
<p>그 외에도 어노테이션은 개발에 편의성을 제공하지만 그게 어떤 기능인지 정확하게 알고있어야한다. 협업시에 어노테이션으로 로직을 숨긴다면 모두가 이걸 알고있어야함 (문서화 추천)</p>
<hr>
<h3 id="6-생성자-주입-사용">6. 생성자 주입 사용</h3>
<p>불변성을 유지할 수 있고, 테스트 코드 작성이 더 수월하다.<br><code>@RequiredArgsConstructor</code>와 <code>final</code> 키워드를 활용해 필드 주입을 없애는 걸 기본 원칙으로 삼는다.</p>
<hr>
<h3 id="7-예외-처리-통합">7. 예외 처리 통합</h3>
<p>Controller마다 예외를 처리하는 대신, 전역 예외 처리기를 만들어 일관성 있게 관리한다.<br><code>@RestControllerAdvice</code>를 사용해 예외 타입별로 명확하게 대응하고,<br>클라이언트에는 통일된 형태의 에러 응답을 내려주도록 구성한다.</p>
<hr>
<h3 id="8-패키지-구조는-도메인-중심으로-나눈다-규모가-크다면">8. 패키지 구조는 도메인 중심으로 나눈다 (규모가 크다면)</h3>
<p>패키지를 controller/service/repository로 나누는 전통적인 방식도 나쁘진 않다.<br>하지만 일정 규모 이상의 프로젝트에서는 도메인 단위로 나누는 것이 더 명확하고 응집도 높은 구조를 만든다.</p>
<pre><code>
user/
├── controller
├── service
├── domain
├── dto
└── repository
</code></pre><p>이런 구조는 관련된 코드가 한곳에 모여 있어 파악이 쉽고, 모듈화를 고려할 때 유리하다.</p>
<p>물론 단점도 있다.<br>공통 유틸이나 여러 도메인이 사용하는 기능의 위치가 애매해지는 경우가 있다.<br>이럴 땐 <code>common</code>, <code>shared</code> 같은 패키지를 별도로 두어 처리하는 방식으로 보완한다.</p>
<hr>
<h3 id="9-비즈니스-로직-외-기능은-service에서-분리">9. 비즈니스 로직 외 기능은 Service에서 분리</h3>
<p>전화번호 포맷팅, 이메일 유효성 검사처럼 도메인과 크게 관련 없는 로직은<br>Service에 넣지 말고 <code>Validator</code>, <code>Util</code> 등의 별도 클래스로 분리한다.</p>
<p>이렇게 하면 Service는 도메인 중심의 로직만을 담당하게 돼, 가독성과 테스트성이 올라간다.</p>
<hr>
<h3 id="10-rest-응답-포맷-통일">10. REST 응답 포맷 통일</h3>
<p>성공/실패 여부, 데이터, 에러 메시지를 일관된 포맷으로 응답하면 클라이언트와의 계약이 명확해진다.</p>
<pre><code class="language-json">{
  &quot;success&quot;: true,
  &quot;data&quot;: {...},
  &quot;error&quot;: null
}</code></pre>
<p>이 구조를 기본 응답 형태로 정의하고 <code>ResponseWrapper</code> 같은 클래스를 만들어 관리하면 좋다.</p>
<hr>
<p>위 정도만 대입해도 충분히 프로젝트가 한결 유지보수하기 쉬워진다 아래부터는 좀 더 깊게 고민해볼 것들이다.</p>
<h3 id="더-깊게-들어가볼-설계-요소들">더 깊게 들어가볼 설계 요소들</h3>
<h4 id="도메인-모델에서-비즈니스-로직-수행">도메인 모델에서 비즈니스 로직 수행</h4>
<p>서비스에서 모든 계산을 담당하기보다는, 도메인 객체가 스스로 책임지도록 설계하는 게 바람직하다.</p>
<pre><code class="language-java">public class Order {
    private List&lt;OrderItem&gt; items;

    public Money calculateTotalPrice() {
        return items.stream()
            .map(OrderItem::getPrice)
            .reduce(Money.ZERO, Money::add);
    }
}</code></pre>
<p>Order가 스스로 가격을 계산하는 구조가 더 자연스럽고 테스트도 쉬워진다.</p>
<hr>
<h4 id="enum과-value-object-활용">Enum과 Value Object 활용</h4>
<p>의미 있는 값은 단순 String이나 int로 표현하지 말고, Enum이나 VO로 감싸는 게 명확하고 안전하다.</p>
<pre><code class="language-java">public enum OrderStatus {
    PENDING, PAID, CANCELLED
}</code></pre>
<pre><code class="language-java">public class Email {
    private final String value;

    public Email(String value) {
        if (!value.matches(VALID_PATTERN)) throw new IllegalArgumentException();
        this.value = value;
    }
}</code></pre>
<blockquote>
<h4 id="이유-">이유 ?</h4>
<p>Enum 특성상 컴파일 시점에 잡혀서 잘못된 값이 들어갈 수 없기 때문 → 타입안정성</p>
</blockquote>
<hr>
<h4 id="설정-클래스는-역할별로-분리">설정 클래스는 역할별로 분리</h4>
<p>모든 설정을 <code>AppConfig</code>에 몰아넣지 말고, 역할별로 나눠서 관리하면 훨씬 명확하다.</p>
<pre><code class="language-java">@Configuration
public class WebConfig {}

@Configuration
public class SwaggerConfig {}

@Configuration
public class RedisConfig {}</code></pre>
<p>특히나 api를 추가하다보면 관리할게 많아지니까 따로 분리해야하고 또, 스프링 레거시 프로젝트라면 더더욱 구조화를 잘 시켜둬야한다.</p>
<hr>
<h4 id="테스트-구조도-계층별로-구분">테스트 구조도 계층별로 구분</h4>
<ul>
<li>단위 테스트는 <code>service</code>, <code>domain</code> 중심으로 작성</li>
<li>통합 테스트는 <code>@SpringBootTest</code>를 활용</li>
<li>Controller 테스트는 <code>@WebMvcTest</code>, 외부 API는 <code>@RestClientTest</code></li>
</ul>
<p>테스트의 목적이 명확하면 코드도 안정적이고, 리팩토링도 수월해진다.</p>
<hr>
<h3 id="마치며">마치며</h3>
<p>위에서 소개한 규칙들은 정답이라기보다는, 내가 프로젝트에서 겪은 경험을 토대로 정리한 기준들이다.</p>
<p>전반적으로 느껴지는 키워드는 <strong>책임에 대한 분리와 명확한 표현</strong> 이라고 생각한다. </p>
<p>처음부터 완벽한 구조를 만드는 건 어렵지만,
작은 습관부터 정리해나가다 보면 유지보수하기 좋은 구조를 자연스럽게 갖추게 된다.</p>
<p>협업을 위한 코드, 변화에 유연한 구조를 고민하고 있다면 이 글이 조금이나마 도움이 되었으면 한다 👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] 취준을 끝내며]]></title>
            <link>https://velog.io/@sin_0/%ED%9A%8C%EA%B3%A0-%EC%B7%A8%EC%A4%80%EC%9D%84-%EB%81%9D%EB%82%B4%EB%A9%B0</link>
            <guid>https://velog.io/@sin_0/%ED%9A%8C%EA%B3%A0-%EC%B7%A8%EC%A4%80%EC%9D%84-%EB%81%9D%EB%82%B4%EB%A9%B0</guid>
            <pubDate>Tue, 29 Apr 2025 09:32:19 GMT</pubDate>
            <description><![CDATA[<p>간만에 포스팅하네요 ㅎ.. 지금까지의 성장과 취업에 대한 과정에 대한 회고를 고봉밥으로 진행해보겠습니다 🔥</p>
<h2 id="1-공부와-성장">1. 공부와 성장 </h2>
<p>블로그에 첫 글을 올린 지 벌써 <strong>3년</strong>이 다 돼 간다.</p>
<img src="https://velog.velcdn.com/images/sin_0/post/53737a05-c5af-4f6a-9703-232462cc7698/image.png">

<p>예전 글을 내려보다 보면 “내가 이렇게 몰랐었나?” 싶은 대목이 셀 수 없이 많다.<br>당시 나는 <strong>Spring</strong>과 <strong>Spring Boot</strong>의 차이조차 어렴풋했고, 근거도 없이 <code>@Data</code>를 남발하거나 엉성한 엔티티를 설계하곤 했다. 배포는 꿈도 못 꾸었고, Git조차 버벅이던 파릇파릇한 초짜였다. 그럼에도 <strong>기록</strong>만큼은 놓치지 않았다.</p>
<p>지금 돌아보면 민망함보다는 “저렇게 무지했던 시절도 있었구나” 하는 감개무량함이 먼저 든다. 설계·구현의 작은 선택 하나가 성능과 유지보수성에 얼마나 큰 파급을 주는지 몸소 겪으며 <strong>‘알고 만들기’와 ‘모르고 만들기’</strong>의 차이를 절실히 배웠기 때문이다.</p>
<p>이제는 풀스택으로 배포까지 경험하며 전체 <strong>플로우</strong>를 깨달았고, 꾸준한 CS 공부 덕분에 ‘왜’와 ‘어떻게’를 먼저 생각하는 습관이 자리 잡았다. 덕분에 트러블슈팅 범위를 빠르게 좁힐 수 있게 됐고, <strong>이제야 한 사람 몫은 하는 개발자가 됐구나</strong> 하는 생각이 문득 든다.</p>
<hr>
<h2 id="2-취업-준비의-과정">2. 취업 준비의 과정 </h2>
<p><strong>자기소개서</strong>부터 손봤다. 흔히 나오는 질문을 전부 적어 놓고 “이 문장이 나를 제대로 설명하나?”를 끝없이 묻다 보니, 강점·약점은 물론 성향과 가치관까지 또렷해졌다. 이렇게 그려 낸 자화상은 <strong>포트폴리오</strong>를 구성할 때도 큰 힘이 됐다. 단순히 “이 프로젝트를 했다”가 아니라, <strong>왜 그렇게 설계했고 무엇을 얻었는지</strong> 근거를 갖춰 설명할 수 있었기 때문이다.</p>
<p>포트폴리오가 어느 정도 갖춰지자 과감하게 <strong>지원서</strong>를 던졌다. 핏이 맞을 듯한 회사라면 연봉이나 규모를 가리지 않고 총 <strong>서른 곳</strong>쯤 지원했었다. 목표는 합격보다 <strong>경험치</strong>였다. 서류가 통과될 때마다 회사 문화, 코딩테스트 유형, 면접 질문 같은 생생한 데이터를 모았고, 탈락 통보를 받으면 곧바로 다음 시도에 교훈을 적용했다. 그 결과 서류는 평균 <strong>열 곳</strong>에서 통과됐고, 최종 면접은 <strong>다섯 번</strong> 치렀다. </p>
<p>기억에 남는 건 합격의 기쁨보다 <strong>거절의 선택</strong>이었다. 두 번의 합격 제안은 연봉·복지가 기대에 못 미친다 판단해 정중히 고사했다. 이 과정에서 <strong>내 몸값</strong>을 규정하는 주체가 <strong>나</strong>라는 사실, 그리고 회사와 지원자가 <strong>상호 평가</strong>한다는 점을 뚜렷이 배웠다. 세 번째 합격 통보를 받은 지금의 회사는 기술 스택, 조직 문화, 성장 방향이 내가 꿈꾸던 미래와 맞아떨어졌고, 그제야 마음 편히 <strong>입사</strong>를 결정할 수 있었다. 판교의 솔루션 개발자.. 출퇴근길이 긴장된다 😵</p>
<img src="https://velog.velcdn.com/images/sin_0/post/3e44182c-0d97-486e-87a4-8f3f14e43680/image.png">

<p>돌아보면, 서류 작성부터 최종 선택까지 이어진 모든 과정은 결국 <strong>“나를 깊이 이해하고 시장을 체험한 여정”</strong>이었다. <strong>합격</strong>만큼이나 <strong>거절</strong>과 <strong>실패 분석</strong>이 소중한 자산으로 남았다.</p>
<hr>
<h2 id="3-취준생-개발자로서-느낀-점">3. 취준생 개발자로서 느낀 점 </h2>
<p>취업 문을 두드리며 가장 자주 마주친 감정은 <strong>부족함</strong>이었다. 코딩테스트마다 “아직도 이 정도밖에 안 되나?” 하는 자책이 들었고, 준비 없이 면접장에 들어가 기본 질문에도 버벅인 적이 있다. 게으름 섞인 다짐과 미뤄 둔 공부도 여전히 부끄러운 흔적이다.</p>
<p>그럼에도 버틸 수 있었던 건 <strong>기록</strong>이라는 단순한 습관 덕분이었다. 에러를 해결하면 원인과 과정을, 강의를 듣다 마음에 남는 문장은 즉시 메모 앱에 옮겨 적었다. 귀찮음을 덜기 위한 메모가 시간이 지나자 <strong>외장 메모리</strong>가 돼 주었다. (개발자적 문과표현) 기술 면접을 준비하다가 과거 메모에서 잊고 있던 개념을 찾아 활용한 순간이 한두 번이 아니다. 그때마다 <strong>의외로 꾸준했네?</strong> 하는 작은 확신을 얻었고, 다시 공부할 동력을 얻었다.</p>
<p>무엇보다 기록은 <strong>차별화</strong>를 만들었다. “이 프로젝트에서 배운 점을 설명해 달라”는 질문에 메모 속 맥락과 근거를 곁들여 구체적인 서사를 풀어낼 수 있었기 때문이다. 덕분에 <strong>일관성 있게 학습해 온 사람</strong>으로 보일 수 있었다.</p>
<p>결국 취준 기간이 남긴 가장 큰 교훈은 <strong>“사소한 깨달음을 붙잡아 두면 성장의 지렛대가 된다”</strong>는 사실이다. 화려한 프로젝트가 없어도, 메모 한 줄이 중요한 자산이 될 수 있다. 이 습관만 이어 간다면, 부족함을 느끼더라도 주저앉지 않고 앞으로 나아갈 수 있을 것이다.</p>
<p>한때 이 블로그에 매일 TIL을 올리려다 부담만 늘었던 경험도 있다. 배움은 로그 함수처럼 서서히 늘어난다는 걸 깨달은 뒤로는, 부담을 덜고 부트캠프 동료들과 가벼운 TIL을 공유하며 즐겁게 이어 가고 있다. </p>
<img src="https://velog.velcdn.com/images/sin_0/post/38139820-1e9c-4dad-904e-6029d0d1c631/image.png" width="15%">

<p>지금은 살짝 일기장으로 전락해버린 상황 😅</p>
<hr>
<h2 id="4-끝으로">4. 끝으로 </h2>
<p>생각해 보면 내 안에는 세 가지 고집이 있었다.</p>
<blockquote>
<ol>
<li>남에게 절대 폐를 끼치지 말자.</li>
<li>남들보다 한 발 앞서고 싶다.</li>
<li>스스로 문제를 해결하고싶다.</li>
</ol>
</blockquote>
<p>이 고집들은 한편으론 성장의 연료였지만, 다른 한편으론 <strong>열등감</strong>을 키우는 온상이었다. 허술해 보일까 두려워 자신을 과장하고, 모르는 것을 대수롭지 않은 듯 넘긴 것도 결국 이 조합 때문이었다.  </p>
<p>요즘 들어서는 그런 성향을 있는 그대로 인정하며, 과거의 과장 대신 <strong>투명함</strong>을 선택하려고 노력한다. 내 실력의 크기를 담담히 받아들이고, 부족함은 기록과 복기로 채우다 보니 문제를 추론해 근거를 잡는 과정에서도 <strong>여유</strong>가 생겼다.</p>
<p>물론 가끔은 <strong>자만</strong>의 그림자가 스친다. 그럴 때마다 떠올리는 것이 <strong>더닝–크루거 효과</strong>다.</p>
<img src="https://velog.velcdn.com/images/sin_0/post/8857e600-2bbd-42bc-bdec-84e49f2a6d72/image.png">

<blockquote>
<p><strong>“어설픈 지식이 과잉 확신을 낳을 수 있다.”</strong></p>
</blockquote>
<p>나는 여전히 배울 것이 산더미다. 이 사실만 잊지 않는다면 성장 가능성은 무한하다. 결국 <strong>기록하고, 돌아보고, 스스로를 객관화</strong>하려는 노력이 강박을 자신감으로 바꿔 준다.</p>
<p>언젠가 미래의 나도 “이 글을 쓸 때보다 한층 단단해졌구나” 하고 웃으며 회고할 수 있기를, 그리고 이 글을 읽는 누군가에게도 같은 확신이 스며들기를 진심으로 바란다.  </p>
<p>취준생 화이팅 !!</p>
<hr>
<h2 id="번외-취준하면서-깨달은-인사이트">번외) 취준하면서 깨달은 인사이트</h2>
<p>제가 진행하면서 느낀 방향성과 전략입니다 다 써놓으니 뻔한 말인데? 라는 생각이 들지만 제 전략이 도움이 될 수 있길 바랍니다..!</p>
<p>첫째, <strong>도메인 적합성</strong>이다. 사업 영역이 뚜렷한 회사라면 그 도메인을 겨냥한 프로젝트 경험이 결정적이었다. 반대로 고객사·솔루션이 다양하거나 SI 성격이 강한 곳에서는 “내가 맡은 프로젝트를 얼마나 깊이 이해했고, 어떤 쟁점을 어떻게 풀었는가”가 더 크게 작용했다.  </p>
<p>둘째, <strong>자격증은 선택이지만 정처기만큼은 체감상 기본 스펙</strong>에 가깝다. 필수는 아니어도 학습에 손해 볼 일은 없었다. 개인적으로는 <strong>ADsP</strong>와 <strong>SQLD</strong>까지 취득했는데, 쿼리를 직접‧간접으로 다루는 입장이라면 SQLD는 비용 대비 만족도가 높았다. ADsP는 빅분기까지 노려보자. 굉장히 흡사하다.</p>
<p>셋째, <strong>협업 역량은 ‘증거’로 보여 주면 좋다.</strong> 코드·커밋 컨벤션, Git 이슈 관리, 칸반 보드 같은 협업 흔적을 프로젝트에 적극적으로 남기면 말보다 설득력이 강했다. 블로그 조회수도 포함이다.</p>
<p>넷째, <strong>CS 지식은 기술 면접을 넘어서 모든 상황에서 통용되는 스탯</strong>이다. “자신 있게 설명할 수 있느냐”를 기준으로 꾸준히 학습했고, 다양한 주제의 CS로 토론 연습을 겸했다.</p>
<p>다섯째, <strong>코딩테스트는 결국 악으로 깡으로 해야한다.</strong> 처음 0문제에서 1문제까지 끌어올리는 구간이 가장 괴롭지만, 한 번 감을 잡고 나면 1 → 10문제는 생각보다 빨랐다. 턱걸이와 같은 원리다. (DP문제 제외...) 본인은 비슷한 유형을 잘 풀때까지 풀어냈고 주로 프로그래머스 웹에서 날코딩으로 준비를 했다. </p>
<img src="https://velog.velcdn.com/images/sin_0/post/d4897830-78d8-474a-849f-29836d3828dd/image.png">

<p>생각보다 많이 안풀었다. 아마 동일한 문제를 계속해서 푼 것 때문이 아닐까 싶기도함</p>
<p>여섯째, <strong>면접은 ‘말 잘하는 간절한 사람’이 이긴다.</strong> 기술이 비슷하면 결국 열의와 전달력에서 갈린다. 평소에 사람들 앞에 서는 걸 두려워하지 말고, 발표·스터디 리딩 등 꾸준히 진행하면 분명 도움이 된다. 또한, 면접관님들 대부분이 관대하시기 때문에 질문과 연관된 내용들이 있다면 추가답변을 요청해보자. 대답 기회가 균등하지않을 수 있기때문에 자기 차례때 간결하게 많은 인사이트를 보여주자. 말을 단순히 많이하란 이야기는 아니다 😅</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[네트워크] NAT은 부족한 IPv4의 해결책일까?]]></title>
            <link>https://velog.io/@sin_0/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-NAT%EC%9D%80-%EB%B6%80%EC%A1%B1%ED%95%9C-IPv4%EC%9D%98-%ED%95%B4%EA%B2%B0%EC%B1%85%EC%9D%BC%EA%B9%8C</link>
            <guid>https://velog.io/@sin_0/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-NAT%EC%9D%80-%EB%B6%80%EC%A1%B1%ED%95%9C-IPv4%EC%9D%98-%ED%95%B4%EA%B2%B0%EC%B1%85%EC%9D%BC%EA%B9%8C</guid>
            <pubDate>Mon, 10 Mar 2025 10:53:19 GMT</pubDate>
            <description><![CDATA[<h1 id="nat은-왜-ipv4-부족-해결책이-될까">NAT은 왜 IPv4 부족 해결책이 될까?</h1>
<p>인터넷이 대중화되면서 <strong>IPv4 주소가 부족해지는 문제</strong>가 발생했다.
초기 인터넷 설계 당시에는 전 세계적으로 사용할 IP 주소가 충분할 것이라 예상했지만, 스마트폰, IoT 기기, 데이터센터 등의 폭발적인 증가로 인해 IPv4 주소가 빠르게 소진되었다. </p>
<p>그렇다고 해서 모든 네트워크가 IPv6로 바로 전환할 수도 없으니, 현실적인 해결책이 필요했다. 그중 하나가 <strong>NAT(Network Address Translation)</strong>이다.</p>
<h3 id="nat란">NAT란?</h3>
<p>NAT은 하나의 공인 IP를 여러 기기가 공유할 수 있도록 만들어주는 방식으로, IPv4 주소 소진 문제를 완화하는 역할을 한다. 즉, <strong>IPv4 주소를 효율적으로 활용하는 기술</strong>이라고 볼 수 있다. 하지만 NAT도 근본적인 해결책은 아니기 때문에 장기적으로는 <strong>IPv6 전환</strong>이 필요하다.</p>
<p>즉, <strong>NAT은 IPv4를 더 오래 사용할 수 있도록 해주는 &quot;임시 대책&quot;</strong>이며, 궁극적인 해결책은 IPv6다. 그러나 NAT이 단순히 주소 부족을 해결하는 기술이 아니라, <strong>네트워크 보안 및 구조 최적화에도 기여할 수 있다는 점</strong>을 고려하면, IPv6 환경에서도 NAT과 유사한 개념이 계속 유지될 가능성이 있다.</p>
<hr>
<h2 id="현재-ipv4-주소는-얼마나-소진되었을까">현재 IPv4 주소는 얼마나 소진되었을까?</h2>
<p>IPv4 주소는 이미 대부분 소진된 상태다. </p>
<ul>
<li>IANA(인터넷 주소 할당 기관)는 2011년 2월에 <strong>마지막 IPv4 주소 블록을 할당</strong>했다.</li>
<li>RIR(대륙별 IP 관리 기관)들도 대부분 IPv4 주소를 거의 다 사용한 상태이며, 신규 IPv4 할당이 어려운 경우가 많다.</li>
<li>이를 해결하기 위해 <strong>CGNAT(Carrier-Grade NAT)</strong> 같은 방식이 사용되고 있지만, 이는 임시적인 해결책에 불과하다.</li>
</ul>
<p>IPv6 도입이 필수적이지만, 여전히 많은 시스템과 인프라가 IPv4에 의존하고 있어 완전한 전환이 지연되고 있다.</p>
<hr>
<h2 id="nat이란">NAT이란?</h2>
<p>네트워크 주소 변환(Network Address Translation, NAT)은 IP 패킷의 TCP/UDP 포트 숫자와 소스 및 목적지의 IP 주소 등을 재기록하면서 라우터를 통해 네트워크 트래픽을 주고받는 기술이다. 이는 단순한 주소 변환을 넘어, <strong>보안 강화, 네트워크 설계 최적화, 트래픽 관리</strong> 등 다양한 역할을 수행한다.</p>
<p>NAT의 주요 역할은 다음과 같다:</p>
<ol>
<li><strong>IPv4 주소 절약</strong> : 하나의 공인 IP를 여러 사설 IP와 공유할 수 있도록 한다.</li>
<li><strong>보안 강화</strong> : 외부에서 내부 네트워크 구조를 직접 알기 어렵게 만들어 보안성을 높인다.</li>
<li><strong>네트워크 유연성 증가</strong> : 동일한 사설 IP를 여러 네트워크에서 중복 사용 가능하게 하여 기업 환경에서도 효율적인 네트워크 운영을 가능하게 한다.</li>
</ol>
<hr>
<h2 id="nat은-원래-개별-ip를-하나로-묶는-걸까-아니면-처음부터-묶여서-생성되는-걸까">NAT은 원래 개별 IP를 하나로 묶는 걸까, 아니면 처음부터 묶여서 생성되는 걸까?</h2>
<p>원래 각각의 기기는 개별적인 <strong>사설 IP(Private IP)</strong>를 갖고 있고, NAT이 이를 <strong>공인 IP(Public IP)</strong>로 변환하는 역할을 한다.
즉, <strong>처음부터 묶여서 생성되는 것이 아니라, 개별적인 사설 IP들이 NAT을 통해 하나의 공인 IP로 변환</strong>되는 구조다.</p>
<h3 id="nat의-동작-방식">NAT의 동작 방식</h3>
<ol>
<li>각 기기(PC, 스마트폰 등)는 <strong>사설 IP</strong>를 할당받는다.</li>
<li>사설 IP를 가진 기기들은 인터넷과 직접 연결되지 않는다.</li>
<li>NAT(보통 공유기나 라우터에서 동작)가 여러 기기의 <strong>사설 IP를 하나의 공인 IP로 변환</strong>하여 인터넷과 연결해준다.</li>
<li>인터넷 서버는 <strong>공인 IP</strong>를 보고 응답을 보낸다.</li>
<li>NAT이 응답을 받아서 원래 요청을 보낸 <strong>사설 IP를 가진 기기로 변환하여 전달</strong>한다.</li>
</ol>
<hr>
<h2 id="nat의-한계와-ipv6의-필요성">NAT의 한계와 IPv6의 필요성</h2>
<p>NAT은 IPv4 주소 부족 문제를 해결하는 데 도움을 주었지만, 완벽한 해결책은 아니다.</p>
<ul>
<li><strong>연결 추적 부담</strong> : NAT을 사용하는 경우, 공유기나 라우터는 모든 세션 정보를 유지해야 한다.</li>
<li><strong>P2P 통신 어려움</strong> : NAT 환경에서는 직접적인 P2P 연결이 어렵고, 추가적인 설정(포트 포워딩 등)이 필요하다.</li>
<li><strong>IPv6 전환 필요성</strong> : NAT 없이도 충분한 주소 공간을 제공하는 IPv6 도입이 점점 중요해지고 있다.</li>
</ul>
<hr>
<h2 id="ipv6로-모두-전환되면-nat은-사라질까">IPv6로 모두 전환되면 NAT은 사라질까?</h2>
<p>이론적으로는 IPv6 환경에서는 모든 기기가 <strong>고유한 공인 IP</strong>를 가질 수 있으므로 NAT이 필요하지 않다. 하지만 현실적으로는 몇 가지 이유로 NAT과 유사한 기술이 계속 사용될 가능성이 크다.</p>
<ol>
<li><strong>프라이버시 보호</strong> : IPv6 주소는 고정될 가능성이 높아, 사용자 추적이 쉬워질 수 있다. 이를 방지하기 위해 <strong>IPv6 프라이버시 확장(Temporary Addresses)</strong> 같은 기능이 사용되지만, NAT처럼 IP를 숨기는 방식도 여전히 유용할 수 있다.</li>
<li><strong>보안 강화</strong> : NAT이 보안 장벽 역할을 해주었던 만큼, IPv6 환경에서도 이를 대체하는 솔루션이 필요할 것이다.</li>
<li><strong>이행 과정의 문제</strong> : IPv6 전환이 완전히 이루어지기까지는 NAT이 IPv4와 IPv6를 연결하는 브릿지 역할을 할 수도 있다.</li>
</ol>
<p>결국 IPv6 환경이 완전히 정착되더라도 NAT과 비슷한 기술이 일부 형태로 남아 있을 가능성이 높다. NAT은 단순한 주소 변환 기능을 넘어 <strong>네트워크 관리 및 보안 기능까지 포함하는 기술적 요소</strong>로 작용하기 때문이다. 향후 IPv6 환경에서도 유사한 메커니즘이 유지될지, 혹은 완전히 새로운 방식으로 대체될지에 대한 논의가 필요하다.</p>
<hr>
<p>30년 뒤의 IP의 모습이 궁금해지네요 🤔</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[네트워크] WebSocket vs SSE(Server-Sent Events)]]></title>
            <link>https://velog.io/@sin_0/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-WebSocket-vs-SSEServer-Sent-Events</link>
            <guid>https://velog.io/@sin_0/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-WebSocket-vs-SSEServer-Sent-Events</guid>
            <pubDate>Sun, 23 Feb 2025 01:56:14 GMT</pubDate>
            <description><![CDATA[<p>웹 개발에서 실시간 통신이 필요한 순간이 많다. 예전에는 짧은 주기로 폴링(polling) 요청을 보내야 했다면, 이제는 WebSocket이나 SSE(Server-Sent Events) 같은 기술로 좀 더 효율적으로 실시간 데이터를 주고받을 수 있다.  </p>
<p>그런데 WebSocket이랑 SSE가 뭐가 다를까 헷갈릴 수 있다. 인터넷 글을 찾아봐도 양방향 vs 단방향 이 정도만 설명되어있는 경우가 있어서 조금 더 알아보도록 하자</p>
<h4 id="한줄요약">한줄요약</h4>
<p>WebSocket은 <strong>양방향</strong> 통신용, SSE는 <strong>단방향(서버→클라이언트)</strong> 스트리밍용</p>
<hr>
<h2 id="websocket">WebSocket</h2>
<blockquote>
<p><strong>“서버와 클라이언트가 양방향으로 데이터를 주고받을 수 있는 실시간 통신 프로토콜”</strong>  </p>
</blockquote>
<ul>
<li><strong>통신 방식</strong> : <strong>Full Duplex</strong> (클라 ↔ 서버 동시에 주고받음)  </li>
<li><strong>프로토콜</strong> : <code>ws://</code> (또는 보안 연결 시 <code>wss://</code>)  </li>
<li><strong>예시</strong> : 실시간 채팅, 협업 툴(구글 독스처럼 동시에 문서 편집), 주식거래, 온라인 게임 등</li>
</ul>
<h3 id="장점">장점</h3>
<ol>
<li><strong>양방향</strong> : 클라이언트 → 서버, 서버 → 클라이언트 모두 자유롭게 메시지 전송 가능  </li>
<li><strong>빠른 데이터 전송</strong> : 연결 수립 후에는 오버헤드가 적은 프레임으로 데이터를 교환  </li>
<li><strong>이벤트 기반</strong> : 메시지 발생 시마다 즉시 전송</li>
</ol>
<h3 id="단점">단점</h3>
<ol>
<li><strong>서버 자원 부담</strong> : 연결마다 세션(혹은 소켓)을 지속적으로 관리해야 함  </li>
<li><strong>재연결 로직 필요</strong> : 연결이 끊겼을 때 클라이언트 측에서 다시 접속 시도 코드를 작성해줘야 함  </li>
<li><strong>오래된 브라우저 호환성</strong> : 대체로 최신 브라우저는 지원하지만, IE 구버전 같은 경우는 폴리필(polyfill) 필요</li>
</ol>
<h4 id="간단-코드-예시spring">간단 코드 예시(Spring)</h4>
<pre><code class="language-java">@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new ChatHandler(), &quot;/ws/chat&quot;)
                .setAllowedOrigins(&quot;*&quot;);
    }
}</code></pre>
<pre><code class="language-java">public class ChatHandler extends TextWebSocketHandler {
    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) {
        // 메시지를 받아서 브로드캐스팅 등 로직 처리
    }
}</code></pre>
<hr>
<h2 id="sse-server-sent-events">SSE (Server-Sent Events)</h2>
<blockquote>
<p><strong>“서버에서 클라이언트로 **단방향</strong> 스트리밍을 지원하는 기술”**  
즉, 클라이언트는 연결을 맺은 뒤, 서버가 이벤트를 <code>event-stream</code> 형식으로 계속 보내주면 그걸 받기만 한다.</p>
</blockquote>
<ul>
<li><strong>통신 방식</strong> : <strong>단방향</strong> (서버 → 클라이언트)  </li>
<li><strong>프로토콜</strong> : HTTP 기반 (<code>text/event-stream</code>)  </li>
<li><strong>예시</strong> : 알림(Notification), 대시보드 모니터링(서버 상태, 로그 스트리밍), 뉴스피드 등</li>
</ul>
<h3 id="장점-1">장점</h3>
<ol>
<li><strong>간단한 구현</strong> : 자바스크립트에서는 <code>EventSource</code> 객체만 생성해주면 끝  </li>
<li><strong>자동 재연결</strong> : 네트워크가 끊겨도 클라이언트에서 알아서 재연결 시도  </li>
<li><strong>문자열 데이터 전송에 최적</strong> : 일반 텍스트, JSON 등을 전송하기 간편</li>
</ol>
<h3 id="단점-1">단점</h3>
<ol>
<li><strong>단방향</strong> : 서버→클라만 가능. 클라에서 서버로 보내려면 AJAX 같은 걸 별도로 써야 함  </li>
<li><strong>바이너리 전송 제한</strong> : SSE는 텍스트 이벤트 스트림에 더 맞춰져 있음(Base64 등으로 우회 가능)  </li>
<li><strong>브라우저 호환성</strong> : IE는 지원 안 함(폴리필 또는 다른 방법 사용)</li>
</ol>
<blockquote>
<p>폴리필(Polyfill) : 브라우저에서 지원하지 않는 코드를 사용 가능한 코드 조각이나 플러그인으로 변환한 코드</p>
</blockquote>
<h4 id="간단-코드-예시spring-mvc">간단 코드 예시(Spring MVC)</h4>
<pre><code class="language-java">@RestController
@RequestMapping(&quot;/sse&quot;)
public class SseController {

    @GetMapping(value = &quot;/stream&quot;, produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux&lt;String&gt; stream() {
        return Flux.interval(Duration.ofSeconds(1))
                   .map(seq -&gt; &quot;data: Hello SSE! seq=&quot; + seq + &quot;\n\n&quot;);
    }
}</code></pre>
<pre><code class="language-javascript">// 클라이언트
const eventSource = new EventSource(&quot;/sse/stream&quot;);
eventSource.onmessage = (event) =&gt; {
  console.log(&quot;SSE 데이터:&quot;, event.data);
};</code></pre>
<hr>
<h2 id="간단-비교-표">간단 비교 표</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>WebSocket</th>
<th>SSE(Server-Sent Events)</th>
</tr>
</thead>
<tbody><tr>
<td><strong>통신 방식</strong></td>
<td>양방향 (Full-Duplex)</td>
<td>단방향 (Server → Client)</td>
</tr>
<tr>
<td><strong>프로토콜/스킴</strong></td>
<td><code>ws://</code>, <code>wss://</code></td>
<td>HTTP (일반적으로 <code>text/event-stream</code>)</td>
</tr>
<tr>
<td><strong>재연결 처리</strong></td>
<td>직접 구현 필요</td>
<td>자동 (브라우저가 알아서 재연결)</td>
</tr>
<tr>
<td><strong>사용 예</strong></td>
<td>채팅, 실시간 게임, 주식 거래, 협업툴</td>
<td>실시간 알림, 로그/뉴스피드, 대시보드 모니터링 등</td>
</tr>
<tr>
<td><strong>장점</strong></td>
<td>빠른 양방향 통신, 이벤트 기반</td>
<td>간단한 설정, 자동 재연결, 텍스트 기반 스트리밍</td>
</tr>
<tr>
<td><strong>단점</strong></td>
<td>서버 자원 부담, 재연결 로직 필요, 구버전 IE 문제</td>
<td>단방향, 바이너리 전송 제약, 구버전 IE 문제</td>
</tr>
<tr>
<td><strong>적합한 시나리오</strong></td>
<td>사용자 입력에 따라 즉시 서버가 응답해야 하는 경우(챗)</td>
<td>실시간 알림/모니터링 등 서버→클라가 주도되는 경우</td>
</tr>
</tbody></table>
<hr>
<h2 id="언제-뭘-써야-할까">언제 뭘 써야 할까?</h2>
<ol>
<li><p><strong>서버 ↔ 클라이언트 양방향</strong> 대화가 필요한 경우: <strong>WebSocket</strong>  </p>
<ul>
<li>채팅, 공동 편집, 주식호가 등 클라에서 업데이트하면 서버가 다른 클라에게도 정보를 푸시해야 하는 경우</li>
</ul>
</li>
<li><p><strong>주로 서버에서 클라이언트로</strong> 자주 데이터가 바뀌는 경우: <strong>SSE</strong>  </p>
<ul>
<li>단방향으로 계속해서 푸시하는 시나리오. 예) 실시간 알림, 서버 상태 모니터링 대시보드</li>
</ul>
</li>
<li><p><strong>구현 난이도</strong> &amp; <strong>운영 비용</strong> 측면 고려  </p>
<ul>
<li>WebSocket은 세션이 많으면 관리가 번거롭다. 반면 SSE는 HTTP 기반이라 방화벽/로드밸런서 세팅이 편할 수 있다.  </li>
<li>반면 SSE는 대규모(수많은 접속자) 환경에서 연결 관리가 까다로울 수도 있다(하지만 대부분 WebSocket이 더 복잡하긴 함).</li>
</ul>
</li>
</ol>
<hr>
<h2 id="마무리">마무리</h2>
<p><strong>실시간 통신</strong>이란 말만 들으면 무조건 WebSocket만 생각하기 쉽지만, SSE도 상당히 유용해보인다. 특히 이벤트성 알림, 대시보드, 로그 모니터링 등에는 SSE가 구현이 매우 간편하니, 내 서비스는 클라이언트에서 서버로 보내는 실시간 액션이 적다~ 싶으면 SSE도 적극 고려해볼 만한것같다.</p>
<p>물론 요즘은 HTTP/2(멀티플렉싱) 환경에서 이벤트 스트리밍이 더 효율적으로 처리되는 케이스도 많고, 완전히 대용량 트래픽을 다루려면 배포 구조, 로드밸런싱 같은 복잡도가 따라온다.<br>언제나처럼 상황과 요구사항을 잘 고려해야될듯</p>
<p>카프카를 좀 공부해봐야겠는데?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[네트워크] HTTP Keep-Alive란? Connection을 유지하면 뭐가 좋을까?]]></title>
            <link>https://velog.io/@sin_0/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-HTTP-Keep-Alive%EB%9E%80-Connection%EC%9D%84-%EC%9C%A0%EC%A7%80%ED%95%98%EB%A9%B4-%EB%AD%90%EA%B0%80-%EC%A2%8B%EC%9D%84%EA%B9%8C</link>
            <guid>https://velog.io/@sin_0/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-HTTP-Keep-Alive%EB%9E%80-Connection%EC%9D%84-%EC%9C%A0%EC%A7%80%ED%95%98%EB%A9%B4-%EB%AD%90%EA%B0%80-%EC%A2%8B%EC%9D%84%EA%B9%8C</guid>
            <pubDate>Sun, 23 Feb 2025 01:55:26 GMT</pubDate>
            <description><![CDATA[<p>어떤 웹 프로젝트를 하든, 프론트엔드와 서버 간의 연결은 늘 고민거리인 것 같다. 요청과 응답이 오가면서 연결을 유지해야 할지, 매번 끊어야 할지, 성능이 어떻게 될지… 간단해 보이지만 막상해보면 “어? 이거 어떻게 설정해야 하지?”라는 생각이 든다.  </p>
<p>이번 글에서는 <strong>HTTP Keep-Alive</strong>(일명 <strong>Persistent Connection</strong>)에 대해 살펴보자. 왜 존재하고, 어떤 이점이 있고, 또 개발자가 직접 설정할 때 놓칠 수 있는 부분이 있는지 알아보도록 하겠다.</p>
<hr>
<h2 id="http-keep-alive란">HTTP Keep-Alive란?</h2>
<ul>
<li><p><strong>개념</strong> : HTTP/1.1에서 기본으로 지원하는 <strong>지속 연결(Persistent Connection)</strong>.  
한 번 TCP 연결을 맺으면, <strong>그 연결을 계속 유지하면서 여러 요청/응답</strong>을 처리할 수 있도록 해준다.</p>
</li>
<li><p><strong>배경</strong> : HTTP/1.0 시절에는 <strong>요청-응답이 끝날 때마다</strong> TCP 연결을 끊었다. 리소스를 여러 개 요청하려면 그만큼 3-way handshake(연결 수립 과정)를 매번 수행해야 하니, <strong>오버헤드</strong>가 컸다.</p>
</li>
<li><p><strong>원리</strong> : 한 번 연결이 맺히면, <strong>Connection: keep-alive</strong> 헤더를 사용해 <strong>특정 시간 동안</strong> 연결을 닫지 않는다. 그동안은 추가 요청이 있으면 <strong>새로운 TCP 연결을 맺을 필요 없이</strong> 기존 연결을 재활용한다.</p>
</li>
</ul>
<hr>
<h2 id="왜-좋을까">왜 좋을까?</h2>
<ol>
<li><p><strong>TCP 연결 비용 절감</strong>  </p>
<ul>
<li>3-way handshake를 자주 할 필요가 없다.  </li>
<li>여러 개의 작은 리소스(이미지, CSS, JS 등)를 다운로드할 때, 한 번 연결로 연속해서 요청/응답을 주고받을 수 있다.</li>
</ul>
</li>
<li><p><strong>네트워크 성능 향상</strong>  </p>
<ul>
<li>매번 연결-해제 과정을 반복하지 않으니, 지연(latency)이 줄어든다.  </li>
<li>브라우저나 서버 측 리소스 소모도 최소화할 수 있다.</li>
</ul>
</li>
<li><p><strong>UX 개선</strong>  </p>
<ul>
<li>웹 페이지 로딩 속도가 좀 더 빨라져서 사용자 경험이 좋아진다.  </li>
<li>동시 접속자가 많은 웹서비스에서도 연결 비용 부담을 줄여서 좀 더 매끄러운 통신을 제공한다.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="실제로-설정하는-법">실제로 설정하는 법?</h2>
<p>사실 Spring Boot나 Tomcat, Nginx, Apache 같은 서버에서는 <strong>HTTP 1.1을 기본</strong> 사용하므로 별다른 설정 없이도 Keep-Alive가 기본 동작한다.<br>다만, <strong>타임아웃(Keep-Alive Timeout)</strong>을 조정해야 할 경우가 생긴다.</p>
<ul>
<li><strong>Tomcat 예시(tomcat/conf/server.xml)</strong></li>
</ul>
<pre><code class="language-xml">&lt;Connector
    protocol=&quot;HTTP/1.1&quot;
    ...
    connectionTimeout=&quot;20000&quot;  &lt;!-- 20초 --&gt;
    keepAliveTimeout=&quot;10000&quot;   &lt;!-- Keep-Alive Timeout 10초 --&gt;
    maxKeepAliveRequests=&quot;100&quot;
/&gt;</code></pre>
<ul>
<li><strong>Nginx 예시(nginx.conf)</strong></li>
</ul>
<pre><code class="language-conf">http {
    keepalive_timeout 65;  # 65초
    ...
}</code></pre>
<h4 id="주의할-점">주의할 점</h4>
<ul>
<li><strong>Timeout 너무 길면</strong> : 사용되지 않는 연결도 서버가 오래 유지해야 하므로 자원이 낭비될 수 있음  </li>
<li><strong>Timeout 너무 짧으면</strong> : 자주 연결이 끊겨서 Keep-Alive의 장점을 활용하지 못할 수 있음</li>
</ul>
<hr>
<h2 id="그래도-연결-유지가-무조건-좋은가">그래도 “연결 유지”가 무조건 좋은가?</h2>
<p><strong>동시 접속</strong>이 매우 많은 환경에서 Keep-Alive를 너무 오랫동안 유지하면, <strong>서버 소켓</strong>이 계속 점유되어 서버 부담이 커질 수 있다.<br><strong>Short-Lived</strong> API(한 번 요청 후 거의 다시 안 쓰는 API)나, <strong>대용량 파일 업로드</strong> 시에도 적절히 고려가 필요하다.<br><br>→ 그래서 대형 사이트들은 <strong>적절한 최적화 &amp; 로드 밸런싱</strong>으로 Keep-Alive와 서버 리소스를 함께 관리한다.</p>
<hr>
<h3 id="마무리">마무리</h3>
<p>분명 단순해 보이지만, 네트워크 세팅은 처음 보면 헷갈리는 요소가 많다. Keep-Alive 역시 예전부터 들어온 방식이지만, 아직도 성능 최적화에 핵심적인 역할을 한다.  </p>
<p>“어? 왜 갑자기 handshake가 자주 일어나지?” 같은 문제가 생기면, 브라우저나 서버의 Keep-Alive 설정이 꺼져있거나, Timeout 값이 너무 작은 경우일 수 있으니 한 번 체크해보자. </p>
<p>개발자는 클라이언트-서버-네트워크-인프라 전반에 관심을 가져야 한다지만 일이 늘어나버린 느낌.. 😅 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Velog Stat으로 블로그 통계 쉽게 확인하기 ]]></title>
            <link>https://velog.io/@sin_0/velog-%ED%86%B5%EA%B3%84-%EC%89%BD%EA%B2%8C-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sin_0/velog-%ED%86%B5%EA%B3%84-%EC%89%BD%EA%B2%8C-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 30 Dec 2024 15:09:04 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sin_0/post/8d928e52-6a24-4750-b813-f94824270d33/image.png" alt=""></p>
<p><a href="https://velog-stat.streamlit.app/">배포 사이트</a>
<a href="https://github.com/Hello-LSY/velog-stat">깃허브</a></p>
<p>streamlit 기반의 배포로 velog의 통계를 쉽게 볼 수 있는 간단한 프로젝트를 만들었습니다. 로컬도 사용해볼 수 있으니 직접 수정해보셔도 됩니다~! </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[오류/해결] 포트번호를 찾아도 없을 때: Identify and stop the process that's listening on port 8080 or configure this application to listen on another port. ]]></title>
            <link>https://velog.io/@sin_0/%EC%98%A4%EB%A5%98%ED%95%B4%EA%B2%B0-%EC%9D%B8%ED%85%94%EB%A6%AC%EC%A0%9C%EC%9D%B4</link>
            <guid>https://velog.io/@sin_0/%EC%98%A4%EB%A5%98%ED%95%B4%EA%B2%B0-%EC%9D%B8%ED%85%94%EB%A6%AC%EC%A0%9C%EC%9D%B4</guid>
            <pubDate>Mon, 16 Dec 2024 05:35:30 GMT</pubDate>
            <description><![CDATA[<img src="https://velog.velcdn.com/images/sin_0/post/baa30bf1-83f2-4b0c-9677-75360333e8fb/image.png">

<p>어제 개발하다 화가나서 그냥 컴퓨터 꺼버리니 포트충돌이 발생</p>
<h2 id="해당-포트-프로세스-찾고-죽이기">해당 포트 프로세스 찾고 죽이기</h2>
<blockquote>
<p>netstat -ano </p>
</blockquote>
<p>명령어로 8080 찾고 죽이면 되겠지뭐~ 했지만?</p>
<img src="https://velog.velcdn.com/images/sin_0/post/203aef2c-80b8-4b96-8d38-7d02c20acb7e/image.png">

<p>? 내가 못찾나 싶어서 8080을 찾아봤지만 아무것도 보이지않음</p>
<h2 id="자바-프로세스-죽이기">자바 프로세스 죽이기</h2>
<img src="https://velog.velcdn.com/images/sin_0/post/8ac20b6d-6b74-43c1-ac3b-e7c59d85a064/image.png">

<blockquote>
<p>tasklist | findstr java</p>
</blockquote>
<p>자바를 죽여보기위해 찾아보기</p>
<img src="https://velog.velcdn.com/images/sin_0/post/f3064b1b-ce25-4cba-b3a0-bff782476a6d/image.png">

<blockquote>
<p>taskkill /PID &lt;프로세스ID&gt; /F</p>
</blockquote>
<p>죽이고 재실행 해도 여전히 안됨</p>
<h2 id="재부팅">재부팅</h2>
<img src="https://velog.velcdn.com/images/sin_0/post/3ad6ada5-bb25-4b12-b5a8-2ade149fdbca/image.png">

<p>도커문젠가 싶어서 가상머신 플랫폼을 체크박스 해제했다가 다시 키면서 자연스레 재부팅을 했더니</p>
<p>해결완료</p>
<hr>
<p>이해가 안되는건 좀비프로세스로도 남아있지않고 자바를 죽여도 해결되지않았다.. 재부팅하니 알아서 프로세스가 정리된거같은데 정확한 원인을 찾은건 아니라 찜찜하네.. 😅</p>
<p>해결법 아시는분은 댓글좀 부탁드립니다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] KB IT's Your Life를 끝내며]]></title>
            <link>https://velog.io/@sin_0/%ED%9A%8C%EA%B3%A0-KB-ITs-Your-Life%EB%A5%BC-%EB%81%9D%EB%82%B4%EB%A9%B0</link>
            <guid>https://velog.io/@sin_0/%ED%9A%8C%EA%B3%A0-KB-ITs-Your-Life%EB%A5%BC-%EB%81%9D%EB%82%B4%EB%A9%B0</guid>
            <pubDate>Thu, 14 Nov 2024 17:50:05 GMT</pubDate>
            <description><![CDATA[<p>이제 정말 끝이다!
소중했던 시간이었고, 모든 순간이 즐겁고 보람찼다. 올해는 눈 깜짝할 사이에 지나가 버린 것 같다. 😊</p>
<p>KB IT&#39;s Your Life 5기 수료생으로서, 지난 6개월을 돌아보며 회고를 남겨보겠습니다.</p>
<h1 id="무엇을-이뤘는가">무엇을 이뤘는가?</h1>
<h2 id="자격증">자격증</h2>
<ul>
<li>SQLD</li>
<li>정보처리기사</li>
<li>ADSP</li>
</ul>
<p>빅데이터분석기사만 아쉽게 불합격했다. 4시간 벼락치기로 붙을 수 있을 거라 생각한 내가 더 문제였다. 😅</p>
<p>반 분위기가 학구열이 높아 집중해서 공부할 수 있었고, ‘열심히 하면 터치하지 않는다’는 강사님의 스타일 덕분에 자격증 취득에 큰 도움이 됐다.</p>
<p>부트캠프 중간중간 시험이 있었지만, 프론트를 제외하면 대부분 익숙한 내용이라 무리 없이 좋은 점수를 받을 수 있었다.</p>
<h2 id="수상">수상</h2>
<ul>
<li>부트캠프 최종 프로젝트 <strong>최우수상</strong></li>
<li><strong>성적우수상</strong></li>
</ul>
<p>최우수상은 최종 프로젝트 반마다 한 팀만 수상하며, 성적우수상은 출결과 시험 성적으로 상위 10%에게 주어진다. 또 제주도 연수 혜택도 지원해준다. 성적우수상은 KB 서류 전형 면제 혜택을 준다. 1회 쓸수있음. (싸피랑 동일)</p>
<p>마지막 프로젝트에서 노력한 만큼 결과가 따라와 정말 기뻤다. PM을 담당했고 정말 많은 시간과 열정을 쏟았다고 생각한다. 정말로 다사다난했지만 결과가 좋으면 장땡이라고 봅니다 👏</p>
<p>프로젝트가 끝나고 제주도로 떠난 패키지 여행는 최고의 추억으로 남았다. 자유여행보다 훨씬 알차게 즐겼고, 비용도 전액 지원인데 인당 100만원정도 들어갔을 것 같더라구요 ㄷ 수학여행을 간 기분이었다. 👍👍</p>
<img src="https://velog.velcdn.com/images/sin_0/post/6515aedc-2054-40ae-8028-13ffc51bc936/image.jpg" width="40%">
<img src="https://velog.velcdn.com/images/sin_0/post/54699c15-eb23-4cef-bc1b-1a1d4d3fbaa2/image.jpg" width="40%">

<h2 id="경험">경험</h2>
<p>풀스택 과정답게 다양한 역량을 보강할 수 있었다. 특히 실력이 크게 느는 데 도움이 된 다섯 가지를 꼽자면 다음과 같다.</p>
<ul>
<li>Git</li>
<li>AWS</li>
<li>협업 능력</li>
<li>CS 지식</li>
<li>프런트엔드 지식</li>
</ul>
<p>스프링은 이미 다룰 줄 알아 큰 도움은 되지 않았지만, 발등에 불이 떨어지자 GPT를 효율적으로 활용해 빠르게 코드를 짜내는 능력이 크게 향상됐다. 최종 프로젝트에서는 Redux, React Native, React, Vue 등 프런트 기술과 Git 협업 플로우, 배포 경험까지 폭넓게 익혔다. 아니, 익혀야 살아남을수있었다 😅</p>
<p>특히 지금도 이어지고 있는 CS 스터디가 제일 가치있는 것 같다. 참여율과 열정이 높아 나 또한 덩달아 열심히 하게 됐다. 늘 감사합니다 🙏</p>
<h2 id="인간">인간</h2>
<p>사람을 배웠다, 고 말하고 싶다. 개발자들의 이해심, 배려, 열정을 가까이에서 보고 배우며 현업에서 마주칠 상황을 간접 경험할 수 있었다. 무엇과도 바꿀 수 없는 소중한 시간이었다. 개발자정신 무장완료..</p>
<hr>
<h2 id="절차">절차</h2>
<p>이번 기수부터 과정이 6개월로 늘고, 모집 인원도 크게 확대됐다.</p>
<p><strong>서류 → 코딩테스트 → 면접 → 최종 합격</strong></p>
<p>코테는 프로그래머스 Lv.2 정도. 단, 단계별 통과 방식이라 솔루션을 못 쓰면 다음 문제로 넘어가지 못하니 준비는 필요하다. </p>
<p>면접 때는 모두 정장을 입으니 그냥 정장으로 가면 됩니다 😅 면접관님마다 다르지만 그냥 열심히하겠다~ 싶으면 뽑은거같더라구요 기억나는 문항은 플젝경험이랑 경험중 어려웠던것, 지원동기 정도로 평이한 주제라 그냥 해온거 잘 정리해서 말하면됩니다.</p>
<h2 id="전반적인-과정">전반적인 과정</h2>
<h3 id="커리큘럼">커리큘럼</h3>
<blockquote>
<p>Vue.js → 스켈레톤 프로젝트(1주) → Java, MySQL, Spring(레거시) → <strong>최종 프로젝트</strong></p>
</blockquote>
<p>부가적으로</p>
<ul>
<li>알고리즘(프로그래머스 Lv.2~3)</li>
<li>MongoDB(거의 스킵)</li>
<li>Git 특강(도움 됨)</li>
<li>UI/UX 특강(비추)</li>
<li>자기소개서 특강(도움 됨)</li>
<li>현업자 특강(완전 비추, 최악)</li>
</ul>
<p>백엔드에 조금 더 치중된 구성이다. 제 기준이기 때문에 자세한 설명은 달지않겠습니다 </p>
<h3 id="이런-분께-추천합니다">이런 분께 추천합니다</h3>
<ul>
<li><strong>SSAFY, 우테코, 부스트캠프, 소마 등 네임드 부캠 시기와 안 겹친다?</strong>
출결이 널널하고 커리큘럼이 초심자 친화적이라 무리 없이 수료 가능.</li>
<li><strong>신입/재취업 준비 중이다?</strong>
규칙적인 생활 리듬과 비슷한 환경에 있는 동료가 큰 힘이 된다.</li>
<li><strong>갓 졸업한 취준생이다?</strong>
열심히 할 마음만 있다면 추천. 다만 아니다 싶으면 빨리 나오는 것이 낫다.</li>
</ul>
<p><strong>스스로 의지가 약하다면 추천하지 않는다.</strong> 강제성이 느슨한 만큼 최종 프로젝트에서 팀원에게 부담을 줄 수 있다. 그런 사람이라면 반드시 죄책감을 느끼길 바란다.....</p>
<h2 id="부캠-정말-좋을까">부캠, 정말 좋을까?</h2>
<p><strong>결론적으로</strong> 좋았다. 개발자 지망생이 한곳에 모이는 기회가 흔치 않다. 나 역시 좋은 사람들을 많이 만나 큰 만족을 느꼈다.</p>
<p>SSAFY와 비슷한 교육기관이라 그런지 커리큘럼도 체계적이라고 생각한다.</p>
<hr>
<h2 id="최종-평가">최종 평가</h2>
<h3 id="운영--★★☆☆☆">운영 : ★★☆☆☆</h3>
<p><strong>장점</strong></p>
<ul>
<li>간식·이벤트로 분위기 전환</li>
<li>다트 잘해짐</li>
<li>스터디 자금 지원</li>
<li>빠른 행정 처리와 소통</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>지원비를 서버 비용 등 실질적 개발비로 쓸 수 있었다면 더 좋았을 듯</li>
<li>시험 방식 등 세부 운영에서 아쉬움. 특히나 개발자라면 기초적으로 알아야할 상식에 대해 오픈북을 해버리니 시험이 변별력이 부족함. 아이러니하게 모듈평가는 논 오픈북으로 실무와 정반대의 느낌</li>
<li>최종 프로젝트 팀 매칭 운이 가장 큰 변수. 실력에 대한 편차가 극도로 심각하기 때문에 매칭만으로 결과가 결정날 수 있다. 팀마다 갈등이 엄청 많이 생길 수 있습니다. 다 취준생인거 이해하고 서로서로 잘 존중하길 바람</li>
</ul>
<h3 id="시설--★★★★☆">시설 : ★★★★☆</h3>
<p>멀티캠퍼스 세종대 캠퍼스 기준. 5층은 라운지, 다트, 카페 같은 공간이 있어 쾌적했다. 엘리베이터가 느린 것만 빼면 불만 없음. 다만 6층은 후기가 별로 좋지않더라구요 전 5층이였습니다 ㅎㅎ;</p>
<h3 id="커리큘럼--★★★★☆-완전-초보-기준">커리큘럼 : ★★★★☆ (완전 초보 기준)</h3>
<p>이미 스프링·풀스택 경험이 있다면 얻어갈 것이 많지 않을 수도. 그러나 협업 경험과 네트워크 확대가 목적이라면 충분히 가치 있다.</p>
<h3 id="분위기--반마다-상이">분위기 : 반마다 상이</h3>
<p>강사님에 따라 분위기가 달라진다. 정말 천차만별이다. 같은 곳이 맞나 싶을정도? <strong>ㅇㅎㅅ</strong> 강사님 반이라면 분명 뭔가 얻어갈 수 있을겁니다 👍</p>
<hr>
<h2 id="끝으로">끝으로</h2>
<p>정말 이를 악물고 달려온 6개월이었다. 익스트림 스포츠를 완주한 듯 뿌듯하다. 지금은 잠시 쉬며 작은 실무 프로젝트를 알바 개념으로 진행하는 중이다. 부캠이 아니었다면 놓쳤을 기회가 많았을 것이다.</p>
<p>아직도 배울 것이 산더미지만, 이 경험 덕분에 한층 성장했다. 앞으로도 꾸준히 전진하겠다. 💪</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[오류/해결] JsonMappingException: failed to lazily initialize a collection of role:]]></title>
            <link>https://velog.io/@sin_0/%EC%98%A4%EB%A5%98%ED%95%B4%EA%B2%B0-JsonMappingException-failed-to-lazily-initialize-a-collection-of-role</link>
            <guid>https://velog.io/@sin_0/%EC%98%A4%EB%A5%98%ED%95%B4%EA%B2%B0-JsonMappingException-failed-to-lazily-initialize-a-collection-of-role</guid>
            <pubDate>Thu, 14 Nov 2024 10:01:32 GMT</pubDate>
            <description><![CDATA[<p>프로젝트 중 DTO의 필드를 지연로딩으로 설정했지만 해당 필드가 초기화되지 않은 상태에서 Jackson이 이를 직렬화 시도할 때 발생한 문제다.</p>
<pre><code class="language-java">public class Post {

...

    @ElementCollection  // 이미지 파일 이름을 리스트로 저장
    private List&lt;String&gt; imageNames = new ArrayList&lt;&gt;();

...
</code></pre>
<h2 id="해결">해결</h2>
<pre><code class="language-java">    @Query(&quot;SELECT p FROM Post p LEFT JOIN FETCH p.imageNames WHERE p.id = :id&quot;)
    Post findByIdWithImages(@Param(&quot;id&quot;) Long id);

    // JPQL로 모든 게시글 조회 시 imageNames를 함께 로드하는 쿼리
    @Query(&quot;SELECT DISTINCT p FROM Post p LEFT JOIN FETCH p.imageNames ORDER BY p.createdAt DESC&quot;)
    List&lt;Post&gt; findAllWithImagesOrderByCreatedAtDesc();</code></pre>
<p>JOIN FETCH를 활용해서 Lazy 필드를 강제로 초기화했다. </p>
<p><strong>EAGER 쓰면안됨?</strong> 할 수 있는데 필드가 항상 로드가 되기때문에 분명히 성능문제가 발생할 수 있어서 다른 방법을 찾은게 저 JOIN FETCH이다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JAVA] ClassLoader]]></title>
            <link>https://velog.io/@sin_0/JAVA-ClassLoader</link>
            <guid>https://velog.io/@sin_0/JAVA-ClassLoader</guid>
            <pubDate>Wed, 06 Nov 2024 10:53:05 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@sin_0/Java-JVM%EC%9D%B4%EB%9E%80">이전에 다뤘던 JVM</a>에 이어서 클래스 로더를 좀 더 깊이있게 다뤄보고자 하는 마음으로 포스팅해본다.</p>
<h2 id="클래스-로더의-역할">클래스 로더의 역할</h2>
<p><img src="https://velog.velcdn.com/images/sin_0/post/f30b86c9-a8e1-496e-ae7b-0ae658a71e77/image.png" alt=""></p>
<p>자바 애플리케이션이 실행될 때 JVM은 필요할 때마다 클래스를 메모리에 로드하고 이를 활용한다. 이 런타임에 필요한 클래스를 로드하고 연결하는 과정에서, 클래스 로더는 해당 클래스가 어디에서 왔는지를 식별하고 메모리의 특정 위치에 올린다. 자바는 클래스가 처음 사용될 때 클래스 로딩이 이루어지므로, 자바의 클래스 로더는 <strong>동적 클래스 로더</strong>라고도 불린다. 이 특성 덕분에 자바는 메모리를 효율적으로 사용할 수 있다.</p>
<h3 id="그래서-이거-왜-씀">그래서 이거 왜 씀?</h3>
<p>클래스 로더는 단순히 클래스를 로드하는 것을 넘어, <strong>애플리케이션에서 플러그인이나 모듈 기반 아키텍처를 구현할 때 중요한 역할</strong>을 한다. 예를 들어, 특정 기능을 플러그인 형태로 분리하여 <strong>동적으로 로드</strong>하고 싶을 때, 클래스 로더를 사용하면 프로그램의 실행 중에 별도의 클래스를 불러와 애플리케이션을 <strong>확장</strong>할 수 있다. </p>
<p>또한, JVM에서는 <strong>커스텀 클래스 로더</strong>를 직접 구현할 수도 있어, <strong>보안을 강화</strong>하거나 <strong>메모리 로딩을 최적화</strong>하는 다양한 기법을 적용할 수 있다. 예를 들어, 특정 경로의 클래스만 로드하거나 네트워크나 파일 시스템의 특정 리소스에서만 클래스를 로드하도록 제한할 수 있다. </p>
<blockquote>
<p>즉, 클래스 로더를 통해 자바 애플리케이션은 필요할 때만 필요한 클래스를 불러와 모듈화를 통한 유연성을 갖출 수 있다. 객체지향 프로그래밍에서 지향하는 모듈화와 캡슐화를 JVM 차원에서 지원하는 역할을 클래스 로더가 수행하는 셈이다.</p>
</blockquote>
<p>자바는 런타임에 클래스 로딩과 링크하는 과정을 거치며, 이 과정을 통해 동적으로 클래스를 로드하는 기능을 제공한다. 이 기능을 담당하는 것이 바로 클래스 로더다.</p>
<p>클래스 로더는 3가지의 <strong>기본 클래스로더</strong>가 존재하고 이를 <strong>동작에 대한 원칙</strong>으로 구분한다. 지금부터 클래스로더의 종류와 원칙에 대해 알아보자 !</p>
<hr>
<h2 id="기본-클래스-로더java-8">기본 클래스 로더(JAVA 8)</h2>
<p><img src="https://velog.velcdn.com/images/sin_0/post/2d9809c3-18f7-4a82-be78-b01534a20ed2/image.png" alt=""></p>
<p>클래스 로더는 JVM이 자바 클래스를 어떻게 메모리에 로드할지를 결정하는 역할을 담당한다. 이 각각의 클래스 로더는 자바 런타임의 특정 부분에 있는 클래스를 로드하도록 설계되어 있다.</p>
<h3 id="부트스트랩-클래스-로더">부트스트랩 클래스 로더</h3>
<p>부트스트랩 클래스 로더는 최상위 클래스 로더로, JVM이 시작될 때 가장 먼저 실행된다. 이 로더는 <code>java.lang</code>, <code>java.util</code> 등의 기본적인 자바 표준 라이브러리를 로드하며, JVM 내부에 내장된 네이티브 코드로 구현되어 있다. 부트스트랩 클래스 로더는 최상위 레벨의 클래스 로더로서, 자바 애플리케이션이 로드되는 과정에서 최우선 순위를 가진다.</p>
<h3 id="확장-클래스-로더">확장 클래스 로더</h3>
<p>확장 클래스 로더는 부트스트랩 클래스 로더의 다음 계층에 위치하며, <code>java.ext.dirs</code> 디렉토리에 있는 클래스들을 로드한다. 자바 확장 라이브러리를 로드하는 역할을 담당하며, 자바 애플리케이션에서 추가적인 기능이나 확장 모듈을 로드할 때 이 확장 클래스 로더가 사용된다. 이 클래스 로더를 통해 시스템에서 필요한 추가 라이브러리를 JVM에 추가할 수 있다.</p>
<h3 id="시스템-클래스-로더">시스템 클래스 로더</h3>
<p>시스템 클래스 로더는 애플리케이션 클래스 로더라고도 불리며, 자바 애플리케이션이 실행될 때 클래스 경로에 있는 클래스들을 로드한다. 이 로더는 일반적으로 애플리케이션 코드나 개발자가 지정한 클래스들을 로드하는 역할을 담당하며, <code>classpath</code>에 포함된 모든 파일을 검색하여 필요한 클래스를 로드하게 된다.</p>
<hr>
<h2 id="클래스-로더의-동작-과정">클래스 로더의 동작 과정</h2>
<p><img src="https://velog.velcdn.com/images/sin_0/post/51574499-3426-41cc-9175-128ef2f78ccf/image.png" alt=""></p>
<ol>
<li><p><strong>로드</strong>: 클래스 파일이 필요한 경우, JVM은 클래스 로더를 통해 해당 클래스 파일을 로드한다. 이때 파일의 경로나 네트워크 위치를 탐색하여 클래스를 메모리에 올리는 작업을 수행한다.</p>
</li>
<li><p><strong>링크</strong>: 클래스 로딩이 완료되면, 그 클래스가 참조하는 다른 클래스들과의 관계를 설정하는 &quot;링크&quot; 과정을 거친다. 링크는 다시 <strong>검증(Verification)</strong>, <strong>준비(Preparation)</strong>, <strong>해결(Resolution)</strong> 세 단계로 이루어진다.</p>
</li>
<li><p><strong>초기화</strong>: 링크가 완료되면, 클래스의 정적 변수와 정적 블록이 실행되며 초기화가 이루어진다. 이때 클래스가 처음으로 메모리에 올라가며, 이후 해당 클래스를 사용할 준비가 된다.</p>
</li>
</ol>
<hr>
<h2 id="클래스-로더의-동작-원칙">클래스 로더의 동작 원칙</h2>
<h3 id="클래스-로더의-구조">클래스 로더의 구조</h3>
<p>클래스 로더는 일반적으로 <strong>부모-자식 관계</strong>로 구성되며, 자식 클래스 로더는 부모 클래스 로더에 클래스를 먼저 위임한 후 찾지 못한 경우에만 자신이 클래스를 로드한다.</p>
<h3 id="위임-원칙">위임 원칙</h3>
<p>클래스 로더가 클래스를 로드할 때 가장 상위 클래스 로더부터 하위로 내려가며 로딩을 시도한다는 원칙이다. 만약 상위 클래스 로더가 해당 클래스를 찾지 못하면, 하위 클래스 로더가 직접 클래스를 로드하게 된다. 이 원칙으로 JVM은 중복 로딩을 방지하고 메모리를 절약할 수 있다.</p>
<h3 id="가시-범위-원칙">가시 범위 원칙</h3>
<p>상위 클래스 로더에서 로드한 클래스는 하위 클래스 로더에서 볼 수 있지만, 하위 클래스 로더에서 로드한 클래스는 상위 클래스 로더에서 볼 수 없다는 원칙이다. 즉, 클래스 로더는 각 로더의 클래스 영역을 구분하고 보호할 수 있다.</p>
<h3 id="유일성-원칙">유일성 원칙</h3>
<p>클래스가 JVM 내부에서 유일하게 로드된다는 것을 보장해주는 원칙이다. 동일한 클래스를 여러 번 로드하면 메모리 낭비는 물론이고 알지못하는 버그를 유발할 수 있다. 이를 방지하기 위해, 클래스 로더는 이미 로드된 클래스를 재사용하여 유일성을 보장한다.</p>
<hr>
<h2 id="java-9-에서의-변경">JAVA 9 에서의 변경</h2>
<p>Java 9부터는 <strong>모듈</strong>이 추가되었다. 기존의 classpath 외에 module path가 추가되어서 클래스 로더의 기존 구조에 모듈 개념을 도입하여, 애플리케이션의 보안성과 모듈화 기능을 더 강화했다. </p>
<p>그 외 클래스 로더의 구조의 변경점이 있지만 너무 길어지므로.. 생략</p>
<hr>
<h2 id="reference">reference</h2>
<p><a href="https://engkimbs.tistory.com/606">https://engkimbs.tistory.com/606</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[form-data VS JSON]]></title>
            <link>https://velog.io/@sin_0/form-data-VS-JSON</link>
            <guid>https://velog.io/@sin_0/form-data-VS-JSON</guid>
            <pubDate>Mon, 19 Aug 2024 06:05:56 GMT</pubDate>
            <description><![CDATA[<p>어떤 스택을 사용하든지 간에 서로 연동하는 과정에서 데이터 타입의 일치에 대한 문제는 늘 발생한다. 프론트에서 서버에 요청을 보내면 서버는 알맞은 형식으로 데이터를 반환해주어야 한다. 이 과정에서 늘 문제가 발생하곤 한다.</p>
<p>스프링에서 RESTful한 웹을 만들 때, 간단하게 확인해보기위해서 포스트맨을 사용할 때 자주 발생할 만한 문제 중 하나가 바로 데이터 타입 문제다. 간단하게 <code>Form-Data</code>와 <code>JSON</code>의 차이점, 그리고 스프링에서 이를 어떻게 처리할 수 있는지 알아보자!</p>
<hr>
<h2 id="form-data와-json">form-data와 JSON?</h2>
<h3 id="form-data">form-data</h3>
<p>Ajax로 폼 전송을 가능하게 해주는 자바스크립트의 객체이며 주로 HTML 폼을 통해 전송되는 데이터 형식이다. </p>
<ul>
<li><strong>전송 방식</strong> : <code>application/x-www-form-urlencoded</code> 또는 <code>multipart/form-data</code> </li>
<li><strong>사용 사례</strong> : 사용자 로그인, 파일 업로드 등</li>
<li><strong>장점</strong> : 파일 업로드와 같은 바이너리 데이터 전송이 가능함. 폼 데이터를 쉽게 처리할 수 있음</li>
</ul>
<h3 id="json-javascript-object-notation">JSON (JavaScript Object Notation)</h3>
<p>사람이 읽고 쓰기 쉬우면서도 기계가 쉽게 분석하고 생성할 수 있는 경량의 데이터 교환 형식이다.</p>
<ul>
<li><strong>전송 방식</strong> : <code>application/json</code> </li>
<li><strong>사용 사례</strong>: 서버와 클라이언트 간의 데이터 교환에서 주로 사용되며, RESTful API에서 데이터를 주고받을 때 가장 널리 사용되는 형식이다.</li>
<li><strong>장점</strong>: 구조화된 데이터 표현에 적합함. 파싱이 비교적으로 쉬움. 특히, 객체 형태의 데이터를 주고받을 때 유용함.</li>
</ul>
<h3 id="스프링에서의-데이터-수신-방식">스프링에서의 데이터 수신 방식</h3>
<ul>
<li><code>@ModelAttribute</code> : <code>Form-Data</code>를 수신할 때 사용된다. HTML 폼에서 전송된 데이터를 객체에 매핑해준다.</li>
<li><code>@RequestBody</code> : JSON 데이터를 수신할 때 사용된다. 클라이언트에서 전송된 JSON 형식의 데이터를 객체에 매핑해준다.</li>
</ul>
<h3 id="포스트맨으로-api-테스트-시-발생할-수-있는-문제">포스트맨으로 API 테스트 시 발생할 수 있는 문제</h3>
<p>포스트맨을 사용하여 API를 테스트할 때, 클라이언트에서 서버로 데이터를 전송하는 형식이 맞지 않을 경우 흔히 <code>415 Unsupported Media Type</code> 오류나 <code>400 Bad Request</code> 오류가 발생할 수 있다.</p>
<ul>
<li><p><strong>JSON 형식의 데이터 전송</strong>: <code>Content-Type</code>을 <code>application/json</code>으로 설정하지 않으면 서버는 데이터를 파싱할 수 없어 오류가 발생한다.</p>
</li>
<li><p><strong>form-data 전송</strong>: 폼 데이터를 전송할 때 <code>@RequestBody</code> 대신 <code>@ModelAttribute</code>를 사용해야 한다. 그렇지 않으면 415 오류가 발생할 수 있다.</p>
</li>
</ul>
<h3 id="예시">예시</h3>
<p>예를 들어, 클라이언트가 <code>form-data</code>로 데이터를 전송했는데 서버에서 <code>@RequestBody</code>로 이를 수신하려 하면 서버는 415 오류를 반환한다.</p>
<ul>
<li><strong>Form-Data의 경우</strong>:<ul>
<li>클라이언트에서 <code>application/x-www-form-urlencoded</code> 형식으로 데이터를 전송할 경우, 서버는 이 데이터를 수신하기 위해 <code>@ModelAttribute</code>를 사용해야 한다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">@PostMapping(&quot;/member/insert&quot;)
public ResponseEntity&lt;HashMap&lt;String, Object&gt;&gt; member_insert(@ModelAttribute MemberDto memberDto) {
    // 회원가입 로직 처리
}</code></pre>
<ul>
<li>*<em>JSON의 경우 *</em><ul>
<li>클라이언트가 <code>application/json</code> 형식으로 데이터를 전송할 경우, 서버는 <code>@RequestBody</code>를 사용하여 데이터를 받아야 한다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">@PostMapping(&quot;/member/insert&quot;)
public ResponseEntity&lt;HashMap&lt;String, Object&gt;&gt; member_insert(@RequestBody MemberDto memberDto) {
    // 회원가입 로직 처리
}</code></pre>
<hr>
<p>한줄요약 : ModelAttribute는 폼데이터, RequestBody는 JSON으로 받자</p>
<p>분명 맞는데 어? 하면서 발생하기 쉬운 오류인 것 같다. 둘의 차이는 늘 가물가물하달까 😅</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[오류/해결] Could not acquire management access for administraion]]></title>
            <link>https://velog.io/@sin_0/%EC%98%A4%EB%A5%98%ED%95%B4%EA%B2%B0-Could-not-acquire-management-access-for-administraion</link>
            <guid>https://velog.io/@sin_0/%EC%98%A4%EB%A5%98%ED%95%B4%EA%B2%B0-Could-not-acquire-management-access-for-administraion</guid>
            <pubDate>Tue, 13 Aug 2024 02:09:26 GMT</pubDate>
            <description><![CDATA[<p>평소처럼 MySQL Workbench로 db를 들어가는데 </p>
<p><img src="https://velog.velcdn.com/images/sin_0/post/1e5a888a-d0c2-4087-bcff-a6332e5cbce4/image.png" alt=""></p>
<p>너 무슨일이니</p>
<h2 id="1-환경-변수-설정해보기">1. 환경 변수 설정해보기</h2>
<blockquote>
<p>시스템 환경 변수 편집 -&gt; 환경 변수 -&gt; 사용자변수 Path에 System32 경로 추가</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/sin_0/post/2b261535-bb68-4ca1-937b-290980771cf7/image.png" alt=""></p>
<p>일단 오류내용대로 System32를 환경변수로 설정하고 껐다켜봐도 딱히 변함은 없다</p>
<p>최근에 진행한거라고는 mysql server 업데이트 하나 뿐인데..</p>
<p>인터넷에서 해결법을 찾아옴</p>
<h2 id="2-시스템-로캘오타아님-변경">2. 시스템 로캘(오타아님) 변경</h2>
<blockquote>
<p>국가 또는 지역 변경 -&gt; 추가날짜, 시간 및 국가별 설정 -&gt; 국가 또는 지역 -&gt; 관리자 옵션 -&gt; 시스템 로캘 변경 -&gt; Beta: 세계 언어 지원을 위해 Inicode UTF-8 사용 켜기</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/sin_0/post/b34c79b7-b95b-4b00-a01e-46264544a691/image.png" alt=""></p>
<p>위 버튼을 찾아서 누르게되면 컴퓨터가 재부팅 된다.</p>
<p>다시 워크벤치를 켜보면?</p>
<p><img src="https://velog.velcdn.com/images/sin_0/post/73437d63-318a-4cdd-85ba-0056abc9b5c9/image.png" alt=""></p>
<p>잘 들어가진다 👍 </p>
<p>주로 경로에 한글이 섞여있으면 못찾는 문제를 발생하는데 Path 환경변수에 아마 UTF 설정이니까.. 한글문제아닐까?? </p>
<hr>
<p>또 너냐 한글?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[오류/해결] Address localhost:1099 is already in use]]></title>
            <link>https://velog.io/@sin_0/%EC%98%A4%EB%A5%98%ED%95%B4%EA%B2%B0-Address-localhost1099-is-already-in-use</link>
            <guid>https://velog.io/@sin_0/%EC%98%A4%EB%A5%98%ED%95%B4%EA%B2%B0-Address-localhost1099-is-already-in-use</guid>
            <pubDate>Tue, 13 Aug 2024 01:16:19 GMT</pubDate>
            <description><![CDATA[<img src="https://velog.velcdn.com/images/sin_0/post/897253de-53a6-4c01-be5b-75f389ab9c0c/image.png">



<p>IntelliJ로 tomcat을 돌려보는데 딱봐도 이미 1099포트가 사용중인 문제같다</p>
<blockquote>
<p>포트 번호는 동일한 IP 주소 내에서 하나의 프로세스만 사용할 수 있다.</p>
</blockquote>
<h2 id="1-포트-지워버리기">1. 포트 지워버리기</h2>
<p>1099 포트를 사용 중인 모든 프로세스를 찾아서 그 프로세스 ID(PID)를 포함한 정보를 표시하기위해 아래의 명령어를 입력</p>
<pre><code>netstat -ano | findstr :1099</code></pre><p><img src="https://velog.velcdn.com/images/sin_0/post/bbdf0ff4-4968-41ef-be23-2caa42892272/image.png" alt=""></p>
<p>PID 7068인 프로세스 발견</p>
<pre><code>taskkill /PID 7068 /F</code></pre><p>/F 옵션으로 강제 KILL</p>
<p><img src="https://velog.velcdn.com/images/sin_0/post/d7caa75e-c905-446e-9f5d-d7a6e296ad35/image.png" alt=""></p>
<p>Linux/Mac 기준</p>
<pre><code>sudo lsof -i :1099
sudo kill -9 &lt;PID번호&gt;
</code></pre><img src="https://velog.velcdn.com/images/sin_0/post/b6f3d13d-ca1f-4fa0-973a-961862266e92/image.png">


<p>작동완료~</p>
<h2 id="2-포트-번호-바꾸기">2. 포트 번호 바꾸기</h2>
<p><img src="https://velog.velcdn.com/images/sin_0/post/b89160ae-f5a6-4fc9-9843-c5a3b7b1eeb9/image.png" alt=""></p>
<img src="https://velog.velcdn.com/images/sin_0/post/277e28a5-994f-4971-9c5b-6dbf9af2f163/image.png">

<p>Edit configurations로 들어가서</p>
<img src="https://velog.velcdn.com/images/sin_0/post/878e997f-342c-4cfc-a71e-a5c9aaaa8327/image.png">

<p>1099로 되어있을텐데 다른 포트로 변경</p>
]]></description>
        </item>
    </channel>
</rss>