<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>mini-boo.log</title>
        <link>https://velog.io/</link>
        <description>다부진 미래를 만들어가는 개발자</description>
        <lastBuildDate>Tue, 17 Mar 2026 01:33:30 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>mini-boo.log</title>
            <url>https://velog.velcdn.com/images/mini-boo/profile/ee120b24-a37d-4ce6-a41b-514b4c76f722/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. mini-boo.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/mini-boo" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Claude X Github Actions 에게 해줘. 하기]]></title>
            <link>https://velog.io/@mini-boo/Claude-X-Github-Actions-%EC%97%90%EA%B2%8C-%ED%95%B4%EC%A4%98.-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@mini-boo/Claude-X-Github-Actions-%EC%97%90%EA%B2%8C-%ED%95%B4%EC%A4%98.-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 17 Mar 2026 01:33:30 GMT</pubDate>
            <description><![CDATA[<p>개발자가 가장 싫어하는 작업은 무엇인가.</p>
<p>사람마다 다를 수 있겠지만 나(그리고 우리 팀) 같은 경우는 딱 한 마디로 축약할 수 있다.</p>
<h3 id="문서화를-비롯한-어쨌든-글로-작성해야-하는-여러가지">&quot;문서화를 비롯한 어쨌든 글로 작성해야 하는 여러가지&quot;</h3>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/30690457-4d7f-4c95-b4ba-09c0403d7069/image.png" alt=""></p>
<p>당연히 코드 컨벤션이 있고, 공통된 커밋 메시지가 있고, 정해진 PR 템플릿이 있어도 급하면 무너진다.</p>
<p>사람이니까 이거하고 저거하다가 커밋을 깜빡하고, 다른 커밋들과 뒤섞이고, PR도 애매해지면 결국 내 멘탈은 하룰라라로 가기 일쑤였다.</p>
<p>게다가 아무리 통일된 양식을 갖춰도 사람마다 성향이 다르다보니 결국 통일되지 못한 무언가가 나오곤 했다. 비단 회사 일뿐만 아니라 팀 단위 사이드 프로젝트 등을 진행할 때도 마찬가지였다.</p>
<p>그래서 이번엔 클로드와 Github Actions를 결합하여, 통일된 양식으로 &#39;해줘&#39;를 시전해 보기로 했다.</p>
<p>이미지로 정리해보면 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/75219515-81cf-4f60-a937-17b34b37c960/image.png" alt=""></p>
<h3 id="claude">Claude</h3>
<ul>
<li>git diff를 통해 작업 변경 내역을 추적하고 그걸 기반으로 기능별로 commit을 쪼갠다.</li>
<li>쌓인 커밋 메시지를 이용해 pr-template.md를 생성하고 자동 push 한다.</li>
</ul>
<h3 id="github-actions">Github Actions</h3>
<ul>
<li>pr-template.md를 추적하여 파일 내용을 기반으로 원격 리포지토리에 자동 Pull Request를 생성한다.</li>
<li>CI/CD 단계에서 생성된 Pull Request를 기반으로 정해진 템플릿에 맞춰 릴리즈 노트를 자동 생성한다.</li>
</ul>
<h2 id="결과는">결과는?</h2>
<ul>
<li>커밋메시지는 완전히 통일되진 않았지만 그래도 얼추 가능한 정도까지는 왔다. md 파일 내부에서 좀 더 세밀하게 조정하면 통일할 수도 있을 것 같다.</li>
<li>PR 템플릿의 경우 릴리즈노트도 자동화해야 하기 때문에 포맷이 훨씬 엄격하다. 그렇기에 충분히 통일된 템플릿을 얻을 수 있었다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/daff6046-983d-491f-927f-902ba905074f/image.png" alt=""></p>
<p>링크드인을 볼 때마다 내가 AI를 활용하는 수준은 거의 걸음마와 다를 게 없다고 생각하게 된다.</p>
<p>그래도 이번 시도로 인해 항상 나의 컨디션에 좌우되던 일에 통일성이 부여됐다는 것만으로도 매우 효과적인 방법이었다고 생각한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[RabbitMQ 실서비스 도입기]]></title>
            <link>https://velog.io/@mini-boo/%EC%8B%A4%EC%84%9C%EB%B9%84%EC%8A%A4%EC%9D%98-RabbitMQ-%EB%8F%84%EC%9E%85%EA%B8%B0</link>
            <guid>https://velog.io/@mini-boo/%EC%8B%A4%EC%84%9C%EB%B9%84%EC%8A%A4%EC%9D%98-RabbitMQ-%EB%8F%84%EC%9E%85%EA%B8%B0</guid>
            <pubDate>Mon, 16 Mar 2026 06:07:24 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/mini-boo/post/35edaeaf-463a-428c-9f60-47e169ee9d1a/image.png" alt=""></p>
<p>RabbitMQ는 작년부터 계속 공부해 왔다.(마지막 포스팅을 보니 작년 8월이더라...)
8월부터 약 7개월간 RabbitMQ를 회사 제품에 녹여내기 위해 열심이었는데, 드디어 2026년 3월. RabbitMQ를 포함한 알림 기능을 배포까지 완료했다.</p>
<p>따라서 회고 겸 RabbitMQ 도입기를 작성해 볼까 한다.</p>
<p>RabbitMQ에 관해 자세한 내용이 궁금하다면 여기를 참고하길: <a href="https://velog.io/@mini-boo/Rabbit-MQ">Rabbit MQ</a></p>
<h2 id="1단계-메시지-큐를-써보자">1단계: 메시지 큐를 써보자!</h2>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/4e890622-55d8-4df6-8742-d73b590b2037/image.png" alt=""></p>
<p>메시지 큐 도입을 고려한 이유는 여기저기 알림 기능이 끼어들며 복잡도가 기하급수적으로 늘어났기 때문이었다. 특히 MSA 아키텍쳐를 차용한 새로운 프로젝트에서는 모든 모듈마다 알림 모듈을 호출하기 위한 코드를 길게 길게 작성해야 했다.</p>
<p>그렇다면 알림이 그렇게 중요한 기능일까? 물론 중요하다. 고객사 입장에서 작업이 완료되었는지 체크하기에 메일, 웹, sms 알림만큼 효과적인 것은 없다. 하지만 주요 기능은 아니다. 고로 중요하지만 부수적인 기능에 불과한 것이다. 그런데 각 알림을 처리하기 위해 들어가는 공수가 생각보다 컸다. </p>
<p>기존 알림 로직이 동기 방식인 것도 문제였다. 예를 들어 A 로직 -&gt; A 메일 발송 -&gt; B 로직 -&gt; B 메일 발송으로 구성된 메서드가 있다고 하자. 메일 발송이 동기 방식이기 때문에 메일 발송에서 오류가 나면 B로직으로 넘어가지 않는 현상이 발생한다. </p>
<p>이 모든 문제점을 해결할 수 있는 방법을 계속 찾고 찾다가 발견한 것이 메시지 큐였다.
그때부터 메시지 큐를 알림에 도입할 것을 염두에 두고 공부하기 시작했다.</p>
<p><strong>1. 비동기 방식을 효율적으로 사용할 수 있고,
2. 알림 호출에서 발생하는 결합도를 낮출 수 있기 때문이었다.</strong></p>
<h2 id="2단계-메시지-큐를-공부하자">2단계: 메시지 큐를 공부하자!</h2>
<p>나 홀로 메시지 큐를 사용하기로 결심했다고 해서 무작정 도입할 수는 없었다. 팀에서 충분히 상의를 거치고 모두가 메시지 큐를 공부할 필요가 있었다. </p>
<p>그래서 두 달간 퇴근하면 메시지 큐를 중점적으로 깊게 공부하는 시간을 가졌다. 메시지 큐를 공부하고 공부한 내용을 팀에 공유했다.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/b151c641-2d30-4019-bafe-43ec38ff1847/image.png" alt=""></p>
<p>그렇게 해서 팀 모두가 메시지 큐를 이해하게 되었느냐 하면, ...사실 모르겠다. ㅎㅎ 나만 열심히 공부한 것 같은.. ... 후술하겠지만 결국 메시지 큐는 나 혼자 개발하게 됐다.</p>
<h2 id="3단계-rabbitmq를-도입하자">3단계: RabbitMQ를 도입하자!</h2>
<p>사실 메시지 큐 관련해서 2025년 하반기에 마감하는 것이 목표였으나, 여러 사건 사고로 인해 메시지 큐 도입이 차일피일 미뤄졌다. 결국 하반기에 메시지 큐를 포함하여 배포하는 건 물 건너 갔다.</p>
<p>대신 올해 초 목표에 반드시 제품에 RabbitMQ를 도입하겠다는 목표를 세웠다. 이 상태로 미루고 미루다가는 절대 제품에 편입하지 못할 것 같았다. On-prem 방식 위주인 우리 회사에서 새로운 툴을 도입하는 건 항상 부담으로 작용했다. 그러잖아도 부담스러운데 개발 일정까지 밀린다? 우선 순위에서 제거되기 너무 쉬웠다. 그때부터 추가 근무를 해서라도 Rabbit MQ에 매달리기 시작했다. 들어오는 일도 쳐내고, RabbitMQ도 열심히 했다.</p>
<p>개발하는 것 자체는 어렵지 않았다. 모든 API 호출을 비동기 방식으로 전환하는 것도 아니고, 알림 모듈 호출만 메시지 큐를 이용한 비동기 방식을 사용하는 것이니만큼 영향 범위가 그리 넓지도 않았다. 이후에는 로깅 처리까지 확대되었으나 ㅎㅎ..</p>
<p>그럼에도 아키텍처 부분에서 여러 시행착오를 겪게 되었다.</p>
<h3 id="문제-회사의-환경에-맞춰-rabbitmq를-적용하기">문제: 회사의 환경에 맞춰 RabbitMQ를 적용하기.</h3>
<p>앞서 말했듯 기능 구현에선 어려울 게 없었다. 문제는 회사의 환경에 맞춰 RabbitMQ를 적합하게 사용할 수 있게 구성하는 것이었다.</p>
<p>우리 회사는 일부 테스트 서버에서 로컬 개발과 알파 테스트를 진행한다. 테스트 서버에 설치된 자원을 공통으로 둔 상태에서 개발도 하고, 주기적으로 개발자들끼리 돌아가며 다른 사람의 기능도 테스트해 보곤 한다.</p>
<p>여기서 문제가 발생했다. RabbitMQ의 메시지는 라운드 로빈 방식으로 소비되는데, 로컬에서 개발하기 위해 메시지를 날리면 알파 테스트 환경의 큐에서 그 메시지를 날름 주워다 소비하는 상황이 반복된 것이다.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/ed36b8de-cc24-4344-a4f7-84bfa9b917a9/image.png" alt=""></p>
<p>이것 때문에 회사 환경 구성을 바꿔달라고 할 수는 없었다. 해결해야만 기능을 배포할 수 있었다.</p>
<h3 id="해결-1-fanout-exchange와-익명-큐-사용하기-실패">해결 1: Fanout Exchange와 익명 큐 사용하기 (실패)</h3>
<p>처음 GPT에게 물어봤을 때 답변으로 제안한 것은 바로 &#39;Fanout Exchange&#39;와 &#39;익명 큐&#39; 사용하기였다. RabbitMQ는 반드시 메시지를 소비할 큐를 지정해야만 한다. 하지만 모든 큐가 같은 메시지를 소비해야 하는 경우 Fanout Exchange를 사용하면 라우팅 키 구분 없이 모든 바인딩된 큐에 메시지를 복제 전달할 수 있따.</p>
<p>어느 환경이든 큐를 소비할 수 있어야 한다는 생각에 도입해 보았다. 하지만 테스트를 한 이후 적합하지 않다는 사실을 깨닫게 된다.</p>
<ol>
<li><p>모듈에서 불필요한 메시지 소비가 발생했다.
예를 들어 로그인하면 SMS 알림이 발송되고, 로그인에 n회 이상 실패하면 비밀번호 찾기 메일이 발송된다고 하자. Fanout Exchange와 익명 큐를 사용하자 로그인을 했을 때도 메일이 발송되려고 하고, 비밀번호 찾기 메일이 발송되어야 할 때도 SMS 알림이 발송된다. 모든 메시지를 소비하려고 하기 때문이었다. 기능 분리가 전혀 안 되는 상황이 발생한다.</p>
</li>
<li><p>라운드 로빈으로 하나의 환경이 소비하는 현상 자체는 유지되었다.
내 의도는 특정 환경에서 발송한 메시지는 해당 환경에서만 소비되게 하는 것이었다. 하지만 사용하는 큐가 같으니, 아무리 익명 큐를 사용해 메시지를 발송해도 결국 라운드 로빈으로 빠르게 낚아 챈 모듈만이 메시지를 소비했다.</p>
</li>
</ol>
<p>문제점은 고치지 못하고 그대로 문제 하나를 더 얹은 셈이었다.</p>
<h3 id="해결-2-큐-이름을-다르게-설정하기일부-성공">해결 2: 큐 이름을 다르게 설정하기(일부 성공)</h3>
<p>지금 현상의 가장 큰 문제점은 &#39;로컬 개발 환경&#39;의 큐와 &#39;테스트 서버 환경&#39;의 큐가 같기 때문에 발생한 일이다. 그렇다면 큐 이름을 다르게 설정하면 해결할 수 있지 않을까!</p>
<p>실제로 개발 환경과 테스트 서버 환경 큐를 구별하기 시작하자 메시지가 정상적으로 소비되기 시작했다. 예전에는 A라는 이름의 공통된 큐로 발송했다면 지금은 A-DEV 큐와 A-TEST 큐로 나누어 발송했다고 생각하면 이해하기 쉬울 것이다.</p>
<p>다만 테스트를 위한 조치였기 때문에 하드코딩 문제가 남아 있었다. 지금처럼 큐 이름을 하드코딩한 상태로 분기 처리하면 확장성이 떨어질 염려가 있었다.</p>
<h3 id="해결-3-applicationyml을-이용해-큐-이름을-다르게-설정하기성공">해결 3: Application.yml을 이용해 큐 이름을 다르게 설정하기(성공!)</h3>
<p>이미 배포 환경 별로 application.yml을 구별해서 사용하는 중이기에 이걸 활용해 보기로 했다. 해당 yml 파일을 이용해서 서버 별로 다음과 같이 설정해 두었다. (보안을 위해 코드 내용은 일부 변경되었다.)</p>
<pre><code>rabbit:
  consumer:
    notice:
      enabled: true
      email-queue-name: dev.notice.email.queue
      sms-queue-name: dev.notice.sms.queue</code></pre><p>이러자 각 환경에서 시행했을 때 각기 다른 큐를 정상적으로 소비하게 되었다.</p>
<h2 id="4단계-기능을-배포하자">4단계: 기능을 배포하자!</h2>
<p>기능 구현은 완료되었으니 남는 것은 배포였다. 이때만큼 긴장한 적이 없었다. 
다른 팀원들은 각자 본인이 해야 하는 업무가 있어서 RabbitMQ만큼은 나 혼자 붙들었기 때문이다. 즉, 문제가 발생한다면 그 책임은 나에게 있는 것과 마찬가지였다.</p>
<p>데모 버전 서버에 배포한 이후 테스트를 했을 때 제대로 작동하지 않자 눈앞이 깜깜해진 적도 있었다.(원래 다른 서버에서는 다 잘 되던 게 특정 서버에서만 작동하지 않을 때만큼 아찔한 상황이 또 없다.) 다행히 해당 서버 속도가 상당히 느렸기 때문에 출력도 느렸을 뿐 기능은 정상적으로 작동했다.</p>
<p>현재는 배포한 이후 로깅이나 필요한 알림 기능을 천천히 추가하며 RabbitMQ 사용을 확대해 나가고 있다.</p>
<h2 id="느낀-점">느낀 점</h2>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/b2f89f27-85a1-4ebe-b238-fcbafe91bfc8/image.png" alt=""></p>
<p>하나의 신기술을 붙잡고 도입한 경험은 입사 이래 처음이었다. 
다른 기술은 이미 개발된 부분을 내가 리팩토링하거나 구조를 수정하는 것 위주였다. 그렇기에 아예 새로운 것을 도입하기 위한 이번 경험은 나에게 새로운 짜릿함을 선물해 주었다. 심지어 내가 처음부터 끝까지 다듬은 것을 온전한 기능으로 배포까지 했으니!</p>
<p>그 과정에서 &#39;신기술을 도입할 때는 여러가지를 미리 고민해야 한다&#39;는 점도 배웠다. 특히 우리 회사는 On-prem 환경도 지원하기 때문에, 해당 환경에 RabbitMQ를 설치 및 도입하는 것도 충분히 고려해야 했다. 그렇기에 팀장님이 지금까지도 다 기억하지 못할 정도로 정말 많은 질문을 하셨는데 그때마다 &#39;이 기술이 정말 우리 팀/제품에 필요한가?&#39;를 충분히 반추할 기회가 되었다. 예전에는 그저 도전하고 싶어서, 라고 답했다면 이제는 정보를 찾아보고 근거를 기반으로 나의 논리를 펼치는 법도 배웠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025년 하반기 회고]]></title>
            <link>https://velog.io/@mini-boo/2025%EB%85%84-%ED%95%98%EB%B0%98%EA%B8%B0-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@mini-boo/2025%EB%85%84-%ED%95%98%EB%B0%98%EA%B8%B0-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Wed, 21 Jan 2026 03:49:10 GMT</pubDate>
            <description><![CDATA[<p>12월 말에는 반드시 올해 하반기 회고를 쓰겠다고 다짐했는데, 어느 덧 26년 1월이 저물어갑니다.
더는 미룰 수가 없는 지경까지 와서야 회고를 쓰기 시작해요.</p>
<p>그래도 2025년 한 해, 정말 수고 많았는데 회고 없이 지나갈 수는 없었습니다.</p>
<br>

<h1 id="2025년-하반기-총평-최악이야">2025년 하반기 총평: 최악이야.</h1>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/f8d4bcde-56c7-4fbe-8a98-14c29be6e927/image.png" alt=""></p>
<p>하반기를 요약하는 표현으로 &#39;최악&#39; 외에 달리 표현할 단어가 없을 것 같습니다.</p>
<p>더 나빠질 수 없겠다고 생각한 곳에는 한층 더 나쁜 것이 기다리고 있더라고요.</p>
<p>평소 사주는 재미로 한두 번 보는 정도에 그쳤는데, 알고보니 제가 올해 들삼재(삼재가 시작되는 해)라고 합니다.
역시 사주는 빅데이터인가 봐요.</p>
<p>개인적인 일로도, 회사 업무로도 정말 많은(-) 일이 벌어졌어요.</p>
<br>

<h2 id="78월-통합테스트에서-참패하다">7~8월: 통합테스트에서 참패하다.</h2>
<p>신규 프로젝트 개발에 착수한지 어언 1년이 되어가던 때입니다.
어느 정도 단위 테스트는 진행했고, 제품 인증 절차도 필요하니 통합테스트로 넘어가야 했어요.
통합테스트는 개발자가 아닌 사내 엔지니어들이 진행하게 되었습니다.</p>
<p>문서 작업이 덜 된 상태라 사용자 설명서는 고사하고 테스트 시나리오도 간소화해서 제공했는데요.
이로 인해 모든 것이 시작되었어요.</p>
<p>엔지니어들은 근 1년간 개발 중인 제품을 만져본 경험이 없었습니다.
그 와중에 문서도 제대로 주어지지 않으니, 말 그대로 제로베이스인 상태에서 이것저것 할 수 있는 모든 것을 테스트하기 시작했어요. (좋은 걸까...?)</p>
<p>첫 번째 통합테스트 성적은 100점 중 30점.
예상보다 더 초라한 성적에 우리 팀은 내부적으로 난리가 났습니다.</p>
<p>가장 큰 문제는 모듈 간 결합도였어요.
MSA 방식을 적용하기 위해 기능별로 세세하게 모듈을 쪼개고 개발자 한 명이 모듈 하나를 담당하는 구조로 개발이 진행되었거든요.
덕분에 단위테스트에서는 &#39;일단 내 것은 이상이 없으니까&#39;로 종결되기 일쑤였습니다.
물론 다른 사람의 API를 완전히 모르쇠한 건 아니었어요. 하지만 API 연동 이슈는 종종 개인의 우선순위에서 제외되고 말았습니다.
덕분에 통합테스트 때는 생각지도 못한 곳에서 한 번씩 500에러를 뱉어내게 된 것이죠.</p>
<p>이후로 우리 팀은 의도치 않은 1주 스프린트 개발을 진행하게 되었습니다.
테스트가 종료되면 결과를 기반으로 1주 동안 미친 듯이 고치고, 그걸 기반으로 다시 테스트가 진행됐죠.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/ff0b7aa6-26d5-4112-8c35-dfced65d2a07/image.png" alt=""></p>
<p>8월에 이르러서는 팀 분위기가 거의 이랬습니다.
고쳐도 어디선가 버그는 튀어나왔어요.
심지어 급한 부분을 고치니 엉뚱한 곳에서 에러가 발생하기도 했습니다.</p>
<p>사실 그게 개발이죠.
현관문을 고치면 변기 뚜껑이 부숴지는 게 개발의 묘미 아니겠어요?
하지만 마음이 급한 우리에게는 그 자체로 악몽이었습니다.</p>
<p>개인적으로는 이 시기에 이사를 해야 했는데요.
입주할 집에 문제가 발생해서 이사를 미루기도 했습니다.
그 덕에 퇴근하면 이사 문제 해결하느라 바빴던 기억이 있네요.</p>
<br>

<h2 id="9월-해외-출장-가도-되는-거-맞아요">9월: 해외 출장, 가도 되는 거 맞아요?</h2>
<p>그나마 통합 테스트가 마무리되던 때.
제품 인증 절차도 들어갔으니 본격적인 사전 영업이 시작되었어요.
자세하게 언급할 수는 없지만 이때부터 팀 분위기가 좋지 않게 돌아갔어요.</p>
<p>와중에 10월에는 제품 홍보를 위해 장기 해외 출장이 잡혀 있었는데요.
개발과 해외 출장 준비를 병행하며 심경이 다소 복잡했습니다.</p>
<p>출장 전에도 할 일이 많았고 출장 후에는 할 일이 더 많았거든요.
그 와중에 분위기도 좋지 않으니 눈치를 보게 되더라고요.</p>
<p>그래도 출장을 무를 수는 없으니, 떠나기 전에 최대한 해야 하는 일을 쳐내기로 했습니다.
덕분에 9월은 분위기가 뒤숭숭하긴 했어도 제 일에만 집중했던 것 같아요.</p>
<p>소문이 돌고 분위기가 가라앉을수록 거기서 멀리하는 법도 알아야 한다는 걸 배웠던 시기입니다.</p>
<br>

<h2 id="10월-해외-출장-그리고-퇴사요-지금요">10월: 해외 출장. 그리고 퇴사요? 지금요?</h2>
<h3 id="두바이로-해외-출장길에-오르며">두바이로 해외 출장길에 오르며</h3>
<p>긴 추석연휴를 보내자마자 바로 출장길에 올랐어요.
도착지는 두바이. 제품 홍보 겸 GITEX 참관을 위한 출장이었습니다.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/3b82344d-0deb-4269-8af5-3f6303d64017/image.png" alt=""></p>
<p>GITEX는 두바이에서 매년 주최되는 중동 및 북아프리카 지역 최대의 정보통신 기술 박람회입니다. 최신 기술 혁신과 디지털 전환 솔루션을 소개하는 글로벌 비즈니서 행사로, IT 및 통신 업계 리더, 스타트업, 기술 전문가들이 참여합니다.</p>
<p>회사는 GITEX와 함께 주최되는 North Star 행사 부스로 참여하게 되었어요. North Star은 스타트업 위주의 박람회인데요. 거기서 제품을 선보이고 소개하는 역할을 맡았습니다.</p>
<p>모든 게 영어로 진행되기 때문에 추석 연휴 내내 스크립트를 들고 다니며 달달 외웠어요. 그래도 막상 사람들 앞에 서니까 머릿속이 백지가 되더라고요. 
첫 날에는 더듬거리고 스크립트를 훔쳐 보며 소개했지만 마지막 날에는 외운 걸 기반으로 입에 나오는 대로 소개하기 시작했습니다. 
스크립트를 엄격하게 지키는 것보단 내용만 지키고 그때그때 대응하는 편이 훨씬 편하다는 걸 이때 알게 되었어요.</p>
<br>

<h3 id="진짜-정말-진심으로-지금-퇴사하신다고요">진짜, 정말, 진심으로, 지금 퇴사하신다고요?</h3>
<p>출장을 마치고 돌아오니 팀 분위기는 한층 더 딱딱해진 상태였습니다.
그리고 한 분이 결국 퇴사를 선언하셨어요.
그게 하반기를 악몽으로 만드는 주요한 계기가 되었습니다.</p>
<p>10월 중후반부터는 본격적으로 인증 시험과 더불어 고객사 시연이 예정되어 있었거든요.
그리고 그분은 1년간 제품의 중추를 담당한 분이셨습니다.</p>
<p>심지어 시연하러 간 곳에서 통합테스트에서도 미처 확인하지 못했던 매우 거대한 버그가 발견되는 바람에, 퇴사하면 타격이 매우 큰 상황이었어요.</p>
<p>여러 논의가 오갔지만 결국 그분은 퇴사하셨습니다.</p>
<p>인수인계를 받는 과정에서도 참 많은 일이 있었는데요.
결론적으로 그분이 담당하던 구역은 전면 재개발 결정이 났습니다.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/42f2dfdd-b5f1-4b4a-a4c7-a9525bb735d2/image.png" alt=""></p>
<p>짤은 이렇지만 사실 제가 부활할 수 있는? 퇴근 시간이? 딱히? 없었습니다.</p>
<p>이때부터 악몽이 본격적으로 시작되었거든요.
통합테스트 같은 위기는 약과였습니다.</p>
<p>참고로 8월에 한번 밀렸던 이사는 원래 10월로 예정되었으나 다시 밀렸습니다. 😃</p>
<br>

<h2 id="11월12월-수습하기-대작전">11월~12월: 수습하기 대작전</h2>
<p>모두 예상하셨겠지만 11월부터 12월은 벌어진 일을 수습하는 데 모든 공수가 들어갔습니다.
1년간 개발하던 것들을 한번에 뒤엎는 건 정말이지 악몽과도 같은 과정이었어요.</p>
<p>재설계하고 개발하고 다시 디버깅하고 단위테스트 하고,
와중에 시험 인증과 영업에서 미친듯한 재촉이 왔죠.</p>
<p>저번처럼 기한을 넉넉하게 받을 수도 없었습니다.
깃허브의 new project를 딸 때 얼마나 눈물이 앞을 가리던지...</p>
<p>다만 그렇다고 해서 급하게, 되는 대로 만들 생각은 전혀 없었습니다.
땜질하듯이 개발을 이어갔다가는 결국 이번과 똑같은 일이 벌어질 거라고 생각했거든요.
그렇게 되면 결국 수습은 저나 다른 동료의 일이 되겠죠.</p>
<p>촉박한 시간이었지만 설계에 정말 많은 시간을 쏟아부었습니다.
11월 한달은 회의와 설계의 반복이었어요.
정확히 이해가 안 되는 부분이 있으면 끝까지 물어봤습니다.
아주 조금이라도 더 견고한 구조를 세우려고 끝까지 노력했어요.</p>
<p>개발 스타트를 끊은 이후에도 설계는 절대 놓지 않았습니다.
지금과 다음 스텝, 그 다음 스텝까지 확장되리라는 걸 염두에 두고 최대한 확장성을 보장한 설계를 진행하려고 했습니다.</p>
<p>성공적이었다고 단언할 수는 없겠지만, 그래도 최선을 다했다고 생각해요.</p>
<p>11월~12월이 지금과 가장 가까운 시기인데도 불구하고 이외에 생각나는 게 많지는 않네요.</p>
<p>말그대로 정신 없었습니다.
팀원 모두가 울기 직전인 상태로 크런치 모드에 돌입했어요.</p>
<p>그 결과 1년 걸렸던 개발이 무려 2달만에 절반 정도 완료되는 기적을 맛볼 수 있었습니다.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/22afa029-994c-4f36-98c4-6ae4d1791034/image.png" alt=""></p>
<p>두 번이나 밀린 이사도 12월에는 클리어됐어요.</p>
<br>

<h1 id="26년에는">26년에는?</h1>
<p>이미 1달이 절반하고도 한참 넘어갔기 때문에, 26년 계획은 어느 정도 정리가 되었습니다.</p>
<p>일련의 사건도 이젠 과거가 되었고, 거기서 배운 점을 기반으로 새롭게 프로세스를 개선했어요.</p>
<h2 id="업무-일지">업무 일지</h2>
<p>원래 개인 업무일지는 팀의 협업 툴 내부 메뉴를 따로 생성해서 관리하는 중이었습니다.
다만 협업 툴 사용성이 매우 좋지 않았어요. 
한 페이지에서 일정을 관리할 수도 없고, 자동화 기능도 없고, 캘린더도 없어서 쓰다가도 불편해서 누락되기 시작했습니다.</p>
<p>그래서 개인 업무 일지는 노션으로 변경했어요.
<a href="https://www.youtube.com/shorts/ALE3bWbptZA">유튜버 유버디님의 노션 템플릿</a>을 활용했습니다.</p>
<p>이 템플릿의 장점은 일간 업무일지, 주간 업무일지가 자동 생성되어 관리하기 편하다는 점이에요. 
출근하자마자 업무일지를 작성하고, 퇴근할 때 업무 진행도를 평가합니다.
주간일지는 유버디님의 조언에 따라 노션 AI를 사용해서 작성하는데 그 효율이 매우 좋아요.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/89ff44cc-35ab-40b9-bfd8-050cf82af336/image.png" alt=""></p>
<p>저는 개인적으로 월간 회고 템플릿과 전체 업무 관리 페이지를 추가로 생성해서 업무를 관리하고 있습니다.
보통 단일에 끝나지 않고 일정 기간이 소요되는 업무가 많기 때문에 필요에 의해서 만들었습니다.</p>
<h2 id="개인-일정과-업무-일정-템플릿-분리">개인 일정과 업무 일정 템플릿 분리</h2>
<p>25년에는 하나의 템플릿에 개인 일정과 업무 일정을 동시에 관리했어요.
더불어서 협업 툴의 개인 업무일지까지 작성하다보니 관리가 중구난방으로 퍼져 쉽지 않았습니다.</p>
<p>그래서 올해부터는 개인과 업무 템플릿을 철저하게 분리했어요.</p>
<p>업무는 위에서 언급한 &#39;업무 일지&#39; 노션 템플릿으로 정리했고, 해당 페이지에 목표를 작성할 때도 개인적인 목표는 엄격하게 지웠습니다.
오로지 회사 업무에서 내가 목표로 하는 바에 집중해서 작성했어요.</p>
<p>개인적인 일은 <a href="https://blog.naver.com/yoonjungnomad/223724299486">노마드윤님의 노션 업무 템플릿</a>을 활용했습니다.</p>
<p>업무 템플릿이지만 저는 개인 일정, 사이드 프로젝트, 회사 업무를 배제한 개인 목표를 관리하기 위한 용도로 사용하고 있어요.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/d0ff5751-3420-4995-9e42-42c6a454bdb7/image.png" alt=""></p>
<p>회사 업무와 개인 일을 엄격하게 분리한 덕분에 시간을 효율적으로 관리할 수 있게 됐습니다.
회사 노트북 북마크 바에는 &#39;업무 일지&#39;만 추가하고, 집의 개인 데스크탑 북마크 바에는 &#39;개인 일지&#39;만 추가해두었는데요.
업무 시간에 무의식적으로 개인 업무를 마주하며 따로 생각하는 시간도 사라졌고, 반대로 퇴근 후에 회사 업무를 보며 생각이 딴길로 새어나가는 일도 없어졌어요.</p>
<p>제대로 된 피드백은 상반기가 지나야 나오겠지만 현재까지는 매우 만족하는 방법입니다.</p>
<h2 id="설계할-때-다이어그램-적극-활용하기">설계할 때 다이어그램 적극 활용하기</h2>
<p>이전까지는 종이에 직접 그려서 설계하는 게 다였어요. 그러다보니 설계 관리가 전혀 안 되더라고요.
중간에 설계한 종이를 잃어버리기라도 하면 나중에 수정하면서 기억이 나지 않아 곤란하기도 했고, 여러 번의 수정으로 지저분하게 덧댄 종이를 남에게 공유하며 민망한 상황이 벌어지기도 했어요.</p>
<p>그래서 draw.io에 지금까지 작업한 설계를 모두 다이어그램으로 정리했습니다.
또 이후에 추가 개발이 필요한 요소들은 1차 설계 이후 다이어그램으로 다시 정리하여 팀에 공유하고 있어요.</p>
<p>덕분에 협업하는 동료들도 제 프로세스를 좀 더 쉽게 이해할 수 있게 되었고, 수정도 훨씬 간편해졌습니다.</p>
<p>구체적인 내용을 공유할 수는 없지만, 아주아주 대략적으로 표현하자면 아래처럼 사용합니다.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/6f216280-2f75-4e01-a6c7-71ee8b717b52/image.png" alt=""></p>
<p>사실 이것보다 더 다양하고 복잡한 형태로 설계가 이루어지고 있어요.</p>
<h2 id="이력서-정리">이력서 정리</h2>
<p>개인 일지에서 보셨듯, 1월에 가장 먼저 한 것은 제 이력서를 정리하는 일이었습니다.</p>
<p>&#39;어? 이 사람 이직 준비하네?&#39;라고 생각하실 수도 있겠네요.</p>
<p>달마다, 혹은 분기마다 이력서를 점검하고 수정하는 건 제가 가진 총알의 개수를 확인하는 것과 같은 과정이라고 생각합니다.
내가 이 업계에서 얼마나 매력적인 인재인지 확인하고 부족한 부분을 체크하여 좀 더 보완할 수 있는 객관적인 기회이기도 하죠.</p>
<p>그래서 올해 가장 먼저 제 이력서를 점검하고 새로 작성했습니다.
아직 이력서가 완성된 건 아니지만, 완성되면 언젠가 블로그에도 공유하도록 할게요.</p>
<br>

<h1 id="그것이-삶이었던가-좋다-그러면-다시-한-번">그것이 삶이었던가? 좋다! 그러면 다시 한 번!</h1>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/e410d15b-f5ca-41cd-81c8-a81e8523a085/image.png" alt=""></p>
<p>민음사의 25년 마지막 일력 문구가 너무나 인상 깊었어요.
다사다난했던 25년을 한 문장으로 정리한 기분이었다고 해야 할까요.</p>
<p>26년에는 좀 더 즐거운 한 해가 되었으면 좋겠습니다.</p>
<p>앞으로는 블로그도 자주 방문할게요!</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/0bf34f51-2d82-453e-b167-6405b0c78fc1/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Rabbit MQ]]></title>
            <link>https://velog.io/@mini-boo/Rabbit-MQ</link>
            <guid>https://velog.io/@mini-boo/Rabbit-MQ</guid>
            <pubDate>Tue, 19 Aug 2025 08:44:41 GMT</pubDate>
            <description><![CDATA[<h2 id="1-메시지-큐messague-queue란">1. 메시지 큐(Messague Queue)란?</h2>
<p>메시지 큐는 메시지 지향 미들웨어(Message Oriented Middleware, MOM)의 한 형태로, 애플리케이션 간 비동기적으로 데이터를 주고받는 시스템입니다.
우체통처럼 메시지를 보관하고 전달하는 중간 저장소 역할을 수행합니다.
생산자(Producer)가 메시지를 발행하면 중간에 큐에 저장되고, 소비자(Consumer)가 필요할 때 꺼내 처리합니다.</p>
<h3 id="1-1-비동기">1-1) 비동기</h3>
<p>메시지 큐는 본질적으로 비동기입니다.</p>
<p><strong>장점</strong></p>
<ul>
<li>빠른 응답: Producer는 메시지 전송 후 즉시 다른 작업을 수행</li>
<li>시스템 안정성: Consumer 장애가 Producer에 영향을 끼치지 않음</li>
<li>확장성: Consumer 수를 자유롭게 조절 가능</li>
<li>부하 분산: 메시지를 여러 Consumer가 나눠서 처리</li>
</ul>
<p><strong>고려사항</strong></p>
<ul>
<li>즉시 응답 불가: 처리 결과를 바로 알 수 없음</li>
<li>복잡성 증가: 에러 처리, 모니터링이 복잡</li>
<li>데이터 일관성: Eventually Consistent 모델</li>
<li>순서 보장: 경우에 따라 메시지 순서가 중요할 수 있음</li>
</ul>
<h3 id="1-2-producer와-consumer의-차이">1-2) Producer와 Consumer의 차이</h3>
<p><strong>Producer</strong>    </p>
<ul>
<li>역할: 메시지를 생성하고 큐에 전송</li>
<li>특징: 메시지를 보낸 후 응답을 기다리지 않음</li>
<li>장점: 빠른 처리, 시스템 부하 분산</li>
</ul>
<p><strong>Consumer</strong></p>
<ul>
<li>역할: 큐에서 메시지를 수신하고 처리</li>
<li>특징: 자신의 처리 속도에 맞춰 메시지 소비</li>
<li>장점: 안정적 처리, 부하 조절 기능</li>
</ul>
<br>

<h2 id="2-rabbitmq란">2. RabbitMQ란?</h2>
<p>RabbitMQ는 AMQP(Advanced Message Queuing Protocol) 기반의 오픈소스 메시지 브로커입니다. Erlang으로 개발되었으며, 엔터프라이즈급 메시징 솔루션으로 널리 사용됩니다.
안정성, 유연성, 사용 편의성이 높다고 알려져 있습니다.</p>
<h3 id="2-1-rabbitmq의-장점과-단점">2-1) RabbitMQ의 장점과 단점</h3>
<p><strong>장점</strong>    </p>
<ul>
<li>신뢰성: 메시지를 디스크에 저장/복제하고 ACK 기반 삭제로 메시지 손실 방지</li>
<li>안정성: 메시지 지속성, 클러스터링 지원</li>
<li>유연한 라우팅: Exchange를 통한 라우팅 규칙</li>
<li>관리 편의성: 웹 기반 관리 인터페이스 제공</li>
<li>다양한 프로토콜: AMQP, STOMP, MQTT 지원</li>
<li>플러그인 시스템: 확장 가능한 아키텍처</li>
<li>강력한 커뮤니티: 풍부한 문서 지원    처리량 한계: 초당 수만 메시지 처리(Kafka 대비 낮음)</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>메모리 사용량: Erlang VM으로 인한 높은 메모리 사용</li>
<li>복잡성: Exchange, Binding 등 개념 학습 필요</li>
<li>단일 장애점: 클러스터 설정 없이는 SPOF 위험</li>
</ul>
<h3 id="2-2-rabbitmq의-핵심-특징">2-2) RabbitMQ의 핵심 특징</h3>
<p><strong>RabbitMQ 아키텍처 구성요소</strong></p>
<pre><code>     [Producer]
          |
          v
     +-----------+
     | Exchange  |
     +-----------+
     /     |     \
    v      v      v
 [Q1]   [Q2]    [Q3]
  |      |       |
  v      v       v
[Con1] [Con2]  [Con3]</code></pre><ul>
<li>Producer는 Exchange로 메시지를 보냄. </li>
<li>Exchange는 Binding 조건에 따라 메시지를 Q1, Q2, Q3 중 하나(또는 여러 개)에 보냄. </li>
<li>각 Queue는 연결된 Consumer에게 메시지를 전달.</li>
</ul>
<p><strong>1) Producer(메시지 생성자)</strong></p>
<ul>
<li>메시지를 발행하는 주체. </li>
<li>Exchange에 메시지를 전달.</li>
</ul>
<p><strong>2) Exchange(메시지 라우터/교환기)</strong></p>
<ul>
<li>Producer로부터 받은 메시지를 어떤 Queue로 보낼지 결정. </li>
<li>Exchange 타입에 따라서 라우팅 전략이 달라짐.</li>
</ul>
<p><strong>3) Binding(연결 설정)</strong></p>
<ul>
<li>Exchange와 Queue를 연결하는 규칙 또는 링크. </li>
<li>&quot;어떤 조건일 때 이 Queue로 보내라&quot; 설정.
예) Direct Exchange에서 &quot;error&quot;라는 Routing key에 대해서만 errorLogsQueue에 전달하라.</li>
</ul>
<p><strong>4) Queue(메시지 큐)</strong></p>
<ul>
<li>Exchange에서 라우팅된 메시지가 실제로 저장되는 공간. </li>
<li>여러 Consumer가 연결될 수 있으며 메시지는 FIFO 순서로 하나씩 처리됨. </li>
<li>큐는 메시지를 임시로 저장하며, Consumer가 ACK를 보낼 때까지 유지됨</li>
</ul>
<p><strong>5) Consumer(소비자)</strong></p>
<ul>
<li>큐로부터 메시지를 받아 처리하는 주체. </li>
<li>메시지를 수신하면 보통 ACK 응답을 보내 큐로부터 제거되도록 함. </li>
<li>비동기로 처리될 수 있으며, 다수의 Consumer가 하나의 Queue를 구독할 수 있음.</li>
</ul>
<p><strong>Exchange 타입별 특징</strong>
<img src="https://velog.velcdn.com/images/mini-boo/post/37948da8-479b-4d67-90ac-336726948aa2/image.png" alt=""></p>
<h3 id="2-3-타-메시지-큐">2-3) 타 메시지 큐</h3>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/3b25a296-55ff-4043-8d4d-0aacaf148861/image.png" alt=""></p>
<h3 id="2-4-rabbitmq-vs-kafka">2-4) RabbitMQ vs Kafka?</h3>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/7bc4e2ef-a56d-463a-b0cb-bb146b1ed558/image.png" alt=""></p>
<h3 id="2-5-메시지-손실-방지를-위한-세팅">2-5) 메시지 손실 방지를 위한 세팅</h3>
<ul>
<li>내구성 큐 생성(durable = true): 서버 재시작 후에도 큐 유지</li>
</ul>
<pre><code>@Bean
public Queue durableQueue() {
    return QueueBuilder.durable(&quot;my-queue&quot;).build();
}</code></pre><ul>
<li>Publisher Confirms 설정: 메시지가 브로커에 안전하게 도달했는지 확인</li>
</ul>
<p>application.yml</p>
<pre><code>spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    publisher-confirm-type: correlated  # 또는 simple
    publisher-returns: true</code></pre><pre><code>// Confirm Callback 등록
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
    RabbitTemplate template = new RabbitTemplate(connectionFactory);

    // 메시지 성공/실패 확인
    template.setConfirmCallback((correlationData, ack, cause) -&gt; {
        if (ack) {
            System.out.println(&quot;✅ 메시지 전송 성공&quot;);
        } else {
            System.err.println(&quot;❌ 메시지 전송 실패: &quot; + cause);
        }
    });

    // 메시지가 라우팅되지 못할 경우 처리
    template.setReturnsCallback(returned -&gt; {
        System.err.println(&quot;❌ 라우팅 실패 메시지: &quot; + new String(returned.getMessage().getBody()));
    });

    return template;
}
</code></pre><ul>
<li>수동 ACK 활용: 처리 중 실패 시 메시지를 다시 큐에 넣기</li>
</ul>
<p>application.yml</p>
<pre><code>spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: manual  # 수동 ack 설정</code></pre><pre><code>@RabbitListener(queues = &quot;my-queue&quot;)
public void receiveMessage(Message message, Channel channel) throws IOException {
    try {
        String msg = new String(message.getBody());
        System.out.println(&quot;수신된 메시지: &quot; + msg);

        // 비즈니스 로직 처리...

        // 성공 시 ack
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    } catch (Exception e) {
        // 실패 시 메시지를 다시 큐에 넣거나 폐기
        channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Spring AOP]]></title>
            <link>https://velog.io/@mini-boo/Spring-AOP</link>
            <guid>https://velog.io/@mini-boo/Spring-AOP</guid>
            <pubDate>Wed, 13 Aug 2025 07:04:24 GMT</pubDate>
            <description><![CDATA[<h1 id="💡-spring-aop란">💡 Spring AOP란?</h1>
<p>AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 한다. 쉽게 말해 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점을 나누어 보고 그 관점을 기준으로 각각 모듈화하겠다는 것이다.
(* 모듈화: 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것)</p>
<ul>
<li>핵심적인 관점: 비즈니스 로직</li>
<li>부가적인 관점: 데이터베이스 연결, 로깅, 파일 입출력 등</li>
</ul>
<p>소스 코드 상에서 다른 부분에 계속 반복해서 쓰는 코드들을 발견할 수 있는데, 이것을 흩어진 관심사(Crosscutting Concerns)라고 부른다.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/ec4ef8d4-8eb2-49c4-8a0a-9d40d2374979/image.png" alt=""></p>
<p>위와 같이 흩어진 관심사를 Aspect로 모듈화하고 핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 취지다.</p>
<h1 id="💡-aop-적용-방식">💡 AOP 적용 방식</h1>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/29cd1208-8f8d-4ba8-b0f7-f6bfbbfe59ea/image.png" alt=""></p>
<p>프록시 객체는 내부에 실제 대상 객체(target)를 참조하고 있다.
AOP에서는 프록시 객체를 통해 외부에서 호출된 메서드를 가로채고, PointCut 매칭 여부를 판단한 뒤 해당 메서드에 매핑된 Advice를 실행한다.</p>
<h1 id="💡aop-주요-개념">💡AOP 주요 개념</h1>
<ul>
<li>Aspect: 흩어진 관심사를 모듈화한 것. 주로 부가기능을 모듈화함</li>
<li>Target: Aspect를 적용하는 곳(클래스, 메서드 등)</li>
<li>Advice: 실질적인 부가 기능을 담은 구현체</li>
<li>JointPoint: Advice가 적용될 위치(메서드 진입 시점, 생성자 호출 시점, 필드에서 값을 꺼낼 때 등 다양한 시점에 적용 가능)</li>
<li>PointCut: JointPoint의 상세한 스펙을 정의한 것. 구체적으로 Advice가 실행될 지점을 정할 수 있음</li>
</ul>
<h1 id="💡스프링-aop-특징">💡스프링 AOP 특징</h1>
<ul>
<li>프록시 패턴 기반의 AOP 구현체: 접근 제어 및 부가기능을 추가하기 위함</li>
<li>스프링 Bean에만 AOP를 적용할 수 있음</li>
<li>모든 AOP 기능을 제공하는 것이 아닌 스프링 IoC와 연동하여 엔터프라이즈 애플리케이션에서 가장 흔한 문제(중복 코드, 프록시 클래스 작성 번거로움, 객체 간 복잡도 증가)에 대한 해결책을 지원하는 것이 목표</li>
</ul>
<h1 id="💡스프링-aop-단점-및-주의사항">💡스프링 AOP 단점 및 주의사항</h1>
<h3 id="1-public-이외의-메서드는-aop가-걸리지-않는다">1. public 이외의 메서드는 AOP가 걸리지 않는다.</h3>
<p>스프링에서는 일관된 AOP 적용을 위해 public을 제외한 접근 제한자는 트랜잭션이 걸리지 않게 처리한다. 즉, 프록시 설정에 따라 트랜잭션 적용 여부가 결정되는 변칙적인 결과를 막기 위해 public 이외의 메서드는 AOP가 작동하지 않는다.
스프링 AOP에서 프록시는 크게 JDK Dynamic proxy 또는 CGLIB으로 작동한다. 그리고 spring boot 1.4 버전 이후 부터는 default로 CGLIB을 사용한다. CGLIB은 동적으로 상속을 통해 프록시를 생성한다. 따라서 private 메소드는 상속이 불가능하기 때문에 프록시로 만들어지지 않는다.
마찬가지로 protected 또한 정상작동하지 않는다. JDK Dynamic proxy는 인터페이스를 기반으로 동작하기 때문이다.</p>
<h3 id="2-같은-클래스-내에서-트랜잭션이-걸린-메소드를-호출하면-트랜잭션이-작동하지-않는다">2. 같은 클래스 내에서 트랜잭션이 걸린 메소드를 호출하면 트랜잭션이 작동하지 않는다.</h3>
<p>Spring AOP의 프록시 동작 과정을 보면 프록시를 통해 들어오는 외부 메서드 호출을 인터셉트하여 작동한다. 이러한 성격으로 인해 self-invocation(자기 자신 호출) 관련 문제가 발생하게 된다.</p>
<pre><code>@Service
@Slf4j
public class RunService{

 public void go(){
    log.info(&quot;go!&quot;);
    run();
  }

  public void run(){
    log.info(&quot;run!);
  }         
}</code></pre><pre><code>@Aspect
@Slf4j
@Component
public class AspectService {

   @Pointcut(&quot;execution(* aop.test.service..*.*.(..))&quot;)
   public void before Execute(){}

   @Before(&quot;beforeExecute()&quot;)
   public void requestLogging(JoinPoint joinPoint){

     MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
     Method method = methodSignature.getMethod();

     log.info(method.getName() + &quot;() 메서드 실행 중&quot;);
   }                              
}</code></pre><p>RunService의 go 메서드는 내부에서 run을 호출한다. 해당 메서드 동작을 로깅하기 위한 AOP 소스 코드를 작성하여 go() 로직 실행을 테스트하면 다음과 같은 결과가 나온다.</p>
<p>예상 결과 로그:</p>
<pre><code>go() 메서드 실행 중
go!
run() 메서드 실행 중
run!</code></pre><p>실제 결과:</p>
<pre><code>go() 메서드 실행 중
go!
run!</code></pre><p>원인: Proxy 객체를 참조하지 않고 내부 target을 직접 참조하여 발생.</p>
<p>해결방안: AopContext를 통해 run 메서드 호출을 Proxy 객체를 통해 호출하도록 변경</p>
<pre><code>@Service
@Slf4j
public class RunService{

 public void go(){
    log.info(&quot;go!&quot;);
    ((RunService) AopContext.currentProxy()).run();
  }

  public void run(){
    log.info(&quot;run!);
  }         
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[1년차 백엔드 개발자 회고]]></title>
            <link>https://velog.io/@mini-boo/1%EB%85%84%EC%B0%A8-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@mini-boo/1%EB%85%84%EC%B0%A8-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Wed, 06 Aug 2025 08:17:38 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/mini-boo/post/2644715f-a016-4627-9910-d4b9ae82386d/image.jpeg" alt=""></p>
<p>꽤 오랜 기간 블로그를 방치해뒀던 것 같아, 가벼운 회고로 다시 시작해보려고 한다.</p>
<p>그동안 회사 안팎으로 정말 다양한 일들이 있었다.</p>
<h2 id="좋아하는-사람들과-헤어지기">좋아하는 사람들과 헤어지기</h2>
<p>회사란 영원한 공간이 아니다. 특히 IT 기업에서 장기근속하는 사람은 꽤 보기 드물다.(적어도 내 주위에서는 그렇다는 얘기다.)
우리 회사도 다르지 않았다. 해가 바뀌고 상반기가 되자 친했던 동료들이 하나둘 퇴사하기 시작했다. 이직한 사람도 있었고, 심신이 지친 사람들도 있었다.</p>
<p>올해 3월, 내가 정말 따르던 프론트엔드 개발자 한 분이 퇴사하게 되었을 때, 겉으로는 덤덤했지만 속으로는 매우 아쉽고 슬펐다. 회식을 좋아하지 않는 편인 내가 직접 송별회를 잡을 정도로 미련이 뚝뚝 흘러넘쳤다.</p>
<p>나의 사수도 이 시기 무렵, 이직으로 회사를 옮겼다. 둘 모두 다소 급하게 퇴사가 결정된 상황이라 팀 내부에 혼란이 다소 있었다. 내가 맡아야 하는 일들이 기하급수적으로 늘어나자 스트레스가 크게 몰려왔다. 이때는 사실 아침에 일어날 때마다 퇴사하고 싶다는 생각만 가득했다. 그래도 나름 열심히 버텨냈다.</p>
<h2 id="팀의-막내-관리자가-되다">팀의 막내, 관리자가 되다.</h2>
<p>팀의 주축을 이끌었던 두 사람이 퇴사하자, 큰 공백이 생겼다. 애초에 그 당시 우리 팀 인원이 그리 많지 않았기에, 즉 실무자는 나 하나뿐이라고 해도 무방했으므로 업무가 나에게로 와르르 쏟아졌다.</p>
<p>새로운 사람을 뽑기 위해 진행된 채용 절차도 매우 빠르게 진행됐다. 그렇게 내 직장 생활 첫 후배가 생겼다. 일을 덜 수 있다는 기대보다는, 아무래도 신입 교육 역시 내가 진행해야 하니 내 일이 더 늘었구나, 싶었다.</p>
<p>시간이 흐르자 더 많은 사람들이 입사했고, 지금은 올해 초보다 팀원이 약 두 배 정도 늘었다. 그러자 내 할일이 한도 끝도 없이 늘어났다.
현재 우리 팀이 개발 중인 프로젝트 히스토리를 명확하게 알고 있는 사람이 나뿐이라, 모든 사람들이 모르는 게 생길 때마다 나를 찾았다. 기존에 소수로 움직였기 때문에 각자 대화로 풀어가다보니 생긴 문제였다.</p>
<h2 id="문서화의-중요성">문서화의 중요성</h2>
<p>팀원이 늘어나면서 문서화도 늘어나고 있다. 예전에는 귀찮고 하기 싫다는 이유로 피했던 문서화였지만, 하지 않으면 이제는 더 큰 문제가 되어서 돌아오기에 꼼꼼하게 문서화하고 있다.</p>
<p>새로운 팀원들이 합류한지 현재 2개월쯤 되었는데, 이제는 본인 담당 업무도 얼추 지정된 상태이다. 아직 모르는 것들이 있으면 간혹 나에게 문의하러 오시지만, 그래도 이전보다는 빈도수가 훨씬 줄었다.</p>
<h2 id="앞으로는">앞으로는?</h2>
<p>앞으로 구상 중인 부분들을 다시 써보려고 한다. 여태 공부만 하고 블로그에 기록하진 않았던 기술들도 조금씩 정리해서 업데이트하려고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Security 302 Status 해결]]></title>
            <link>https://velog.io/@mini-boo/Spring-Security-302-Status-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@mini-boo/Spring-Security-302-Status-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Mon, 18 Nov 2024 05:10:47 GMT</pubDate>
            <description><![CDATA[<p>Spring Security를 로그인에 적용하던 중, 로그인을 성공하고 인증 객체가 제대로 생성이 되었음에도 302 상태 코드가 발생하여 다음 페이지로 넘어가지 않는 상황이 발생했다.</p>
<pre><code>o.s.web.servlet.DispatcherServlet        : Completed 302 FOUND</code></pre><h2 id="문제-원인">문제 원인</h2>
<p><strong>1. Spring SecurityContext 인증 정보 손실</strong></p>
<p>SecurityContext의 인증 정보는 기본적으로 HttpSession을 사용하여 저장되지만, 현재 컨트롤러에서 명시적으로 SecurityContext에 인증 정보를 설정했다. 그러나 이 설정이 리다이렉트 이후 HTTP 요청에 반영되지 않아 문제가 발생했다.</p>
<p><strong>2. 세션 인증과 Spring Security의 인증 상태 간 불일치</strong></p>
<p>HttpSession에 사용자의 정보를 저장했지만, Spring Security가 이를 인식하지 못한다. 따라서 다음 요청에서 인증된 사용자로 간주되지 않았다.</p>
<h2 id="해결-방법">해결 방법</h2>
<p><strong>SecurityContextPersistenceFilter 활성화 및 세션 관리 개선</strong></p>
<p>SecurityContextPersistenceFilter는 SecurityContext를 자동으로 관리해준다. 이를 위해 Spring Security 설정과 HttpSession을 적절히 연동해야 한다.</p>
<h3 id="config-security-filter-chain-메소드-수정">Config Security Filter Chain 메소드 수정</h3>
<pre><code>@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
            .securityContext(securityContext -&gt; securityContext
                    .securityContextRepository(new HttpSessionSecurityContextRepository())
            );

    return http.build();
}
</code></pre><h3 id="controller-로그인-메소드-수정">Controller 로그인 메소드 수정</h3>
<pre><code>SecurityContextHolder.getContext().setAuthentication(authentication);</code></pre><h2 id="결과">결과</h2>
<p>302 상태 코드는 발생하지만 저장된 세션을 활용할 수 있게 되면서 리디렉션을 통해 정상적으로 다음 페이지가 로딩되었다. 다음 페이지는 200 status로 떨어진다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 문자열 내 p와 y의 개수-Python]]></title>
            <link>https://velog.io/@mini-boo/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%82%B4-p%EC%99%80-y%EC%9D%98-%EA%B0%9C%EC%88%98-Python</link>
            <guid>https://velog.io/@mini-boo/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%82%B4-p%EC%99%80-y%EC%9D%98-%EA%B0%9C%EC%88%98-Python</guid>
            <pubDate>Mon, 28 Oct 2024 14:08:35 GMT</pubDate>
            <description><![CDATA[<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/12916?language=python3">프로그래머스 문제</a></p>
<h3 id="풀이">풀이</h3>
<pre><code>def solution(s):
    answer = True

    s = s.lower()

    p = s.count(&#39;p&#39;)
    y = s.count(&#39;y&#39;)

    if(p != y):
        answer = False

    return answer</code></pre><h3 id="what-i-learned">What I learned</h3>
<p>자바를 주 언어로 사용해 왔지만, 코딩 테스트에는 적합하지 않은 언어란 생각이 들었다. 지나치게 길어지는 코드와 메소드 때문에 골치가 아팠던 적이 한두 번이 아니라서 이번에는 파이썬으로 도전했다.</p>
<p>파이썬은 자바에 비해서 조금 더 빨리 배울 수 있는 언어라고 이야기를 많이 한다. 하지만 그렇다고 해서 손에 익은 언어만큼 빠르게 해결법을 떠올릴 수는 없었다.</p>
<p>이번 문제는 쉬웠기 때문에 간단하게 풀 수 있었다. 하지만 난이도를 올려가면서부터는 파이썬의 문법을 좀 더 철저하게 공부할 필요가 있다고 생각했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[25주차 주니어 개발자의 우당탕탕 회고]]></title>
            <link>https://velog.io/@mini-boo/25%EC%A3%BC%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@mini-boo/25%EC%A3%BC%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 04 Oct 2024 08:49:15 GMT</pubDate>
            <description><![CDATA[<h2 id="🎯-퐁당퐁당-업무-주간">🎯 퐁당퐁당 업무 주간</h2>
<p>이번 주는 독특하게 화, 목이 휴일이었다. 출근은 월, 수, 금만 하면 되었는데 휴일이 많은 만큼 여유 있으리라 생각했지만 오히려 배로 피곤했다. 하루 쉬고 출근하니 월수금이 월월월 같았기 때문이다.</p>
<p>게다가 쳐내야 하는 업무들이 있다 보니 집에 있어도 계속 업무 진행을 생각하느라 쉬어도 쉬는 것 같지 않았다. </p>
<p>휴일이 무턱대고 많은 것도 예상외로 좋지는 않은 것 같다.</p>
<h2 id="🎯-새로-합류한-동료에게-많은-것을-배웠다">🎯 새로 합류한 동료에게 많은 것을 배웠다.</h2>
<p>이번 주에 첫 출근하게 된 팀원 분을 맡아서 이것저것 설명해 드리느라 시간이 많이 소요되었다. 특히 보안 관련 제품은 이론적으로 어려운 부분이 많았다.</p>
<p>맡게될 업무를 설명하고 질의응답을 받으며 많은 걸 느꼈다.</p>
<p>첫 번째로 느낀 건, 경력직의 질문은 다르다는 점이다. 처음 입사했을 때 나는 모르는 게 너무 많다 못해 뭘 질문해야 할지도 모를 지경이었다. 그런데 이번에 합류한 팀원 분은 첫 출근부터 본인의 업무를 파악하며 모르는 부분이 생길 때마다 바로바로 질문하셨다. 덕분에 설명하는 나도 수월하게 짚고 넘어갈 수 있었다.</p>
<p>두 번째로 느낀 건, 일정에 따라 일을 조정할 필요가 있다는 점이었다. 내가 입사하기 전에 프론트엔드 개발자가 퇴사하며 한동안 프론트를 손 볼 분이 없었다. 나와 다른 팀원이 공부하며 메우긴 했지만 어디까지나 임시방편이었다. 그렇기에 우리 프로젝트의 프론트엔드는 상당히 너덜너덜한 상태였다.
그로 인해 새로 합류한 팀원 분에게 주어진 업무가 상당히 과중했고 마감일도 촉박했다. 우리 팀은 회의 끝에 최대한 화면이 깔끔하고 통일되어 보이도록 수정하고, 마감일이 지난 후 프론트엔드를 다시 손 보기로 했다.
처음부터 진행했다면 물론 좋았겠으나 일정에 따라 희생할 부분은 희생해야 한다는 걸 깨닫게 되었다. 급한 불을 끄고 나면 천천히 잡아도 될 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[24주차 주니어 개발자의 우당탕탕 회고]]></title>
            <link>https://velog.io/@mini-boo/24%EC%A3%BC%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@mini-boo/24%EC%A3%BC%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 27 Sep 2024 08:57:39 GMT</pubDate>
            <description><![CDATA[<p>어쩐지 요즘 블로그를 회고만 쓰는 것 같다. 일이 바쁘다보니 막히는 부분을 공부해도 정리할 시간이 도무지 나지 않는다. 이번 프로젝트 분량만 얼추 마무리되면 신경 썼던 부분들을 블로그에 다뤄볼 수 있지 않을까.</p>
<h2 id="🎯-기능-간-간섭은-어떻게-해결하면-좋을까">🎯 기능 간 간섭은 어떻게 해결하면 좋을까?</h2>
<p>개발 측면에서 골치 아픈 것은 중요한 기능을 개발할 때보다 부가적인 기능을 간섭 없이 완성하는 데에 있다고 생각한다. 이번 주는 크리티컬한 기능을 작업하진 않았다. 일반 사용자가 판단하기엔 사소하고 자잘한 기능이지만 그럼에도 꼭 있어야 하는 부분들이 주된 태스크였다.</p>
<p>처음엔 간단하게 작업할 수 있을 거라 여기며 착수했는데 막상 시작하니 만만한 일이 아니란 걸 깨닫게 되었다. 기존 기능을 위해 작업한 코드와 충돌하며 새로운 코드가 제대로 동작하지 않은 일들이 빈번하게 벌어진 것이다.</p>
<p>덕분에 전에 작업한 코드들을 계속 뜯어봐야 했는데 분명 내가 작성한 코드임에도 불구하고 시간이 지난 만큼 초면처럼 느껴졌다. 그러다 뜻밖의 수확도 얻었다. 전에는 구현에 급급해 넘어갔던 코드 중 복잡하고 어려운 것을 리팩토링할 기회가 주어진 것이다.</p>
<p>기존 코드와 새로운 코드가 모두 동작하기 위한 환경을 만들기 위해 적용 범위를 확인하고 테스트하면서, 결국 기능 간 간섭 문제는 더 세세한 설계와 구체적인 테스트가 필요하다는 것을 깨달았다.</p>
<p>현재는 테스트코드가 있다고, 로컬에서 구동해 봤다고 넘어가는 게 아니라 테스트해야 하는 부분을 리스트업하고 하나하나 체크하며 진행하고 있다. 덕분에 뒤늦게 문제를 발견하는 일이 많이 줄어들었다.</p>
<h2 id="🎯-어떤-방식을-선택해야-옳을까">🎯 어떤 방식을 선택해야 옳을까?</h2>
<p>개발을 하다보면 정답이 없다는 걸 느끼곤 한다. 하나를 개발해도 A부터 Z까지 다양한 방법이 있기 마련이다. 그리고 우리는 진행 중인 프로젝트에 가장 적합한 기술과 방법을 선택해야 한다.</p>
<p>임시저장 기능을 구현하기 위해 서치하며 많은 고민을 했다. 로컬 스토리지에 저장해서 불러와 쓰는 방법을 선택할까? 아니면 temp 테이블을 만들어서 db에 접근할까?</p>
<p>전자는 다른 pc환경에선 임시저장을 사용할 수 없다는 단점이 있었고, 후자는 설정한 주기 별로 db를 계속 업데이트해야 한다는 단점이 있었다.</p>
<p>처음 욕심난 것은 db에 접근하는 방법이었다. 큰 서비스에서 블로그 글쓰기나 메일 같은 기능을 위와 같은 구조로 되어 있기 때문이었다.</p>
<p>그러나 내 고민을 들은 동료가 &quot;우리 서비스에 가장 적합한 방법을 선택하는 게 좋겠다&quot;고 조언해 주었다. 고민 끝에 지금 내가 담당하는 솔루션은 이용자가 pc를 옮겨가며 사용하지 않을 가능성이 크다고 판단했다. 업무할 때 사용하는 솔루션이기에 대부분 회사에서 지급한 노트북이나 데스크탑을 이용할 것이다.</p>
<p>고로 나는 욕심을 접어두고 빠르게 구현할 수 있는 로컬스토리지를 선택했다. db에 접근하여 임시저장하는 방법은 1차 배포가 끝나고 여유가 생기면 작업할 수 있다. 지금은 더 중요한 기능에 집중하는 것이 나았다.</p>
<h2 id="🎯-팀원의-에러를-함께-해결하기">🎯 팀원의 에러를 함께 해결하기</h2>
<p>마감일에 쫓기며 작업하다 보면 팀원이 도움을 청했을 때 대응하기 곤란할 때가 있다. 내 일도 급한 나머지 다른 사람을 도울 시간을 내기 어렵다. 마감이 다가오며 우리 팀의 말수가 급격하게 줄어든 건 이러한 이유일 것이다.</p>
<p>나의 일을 빠르게 처리하고, 다른 사람도 바쁘니 도움은 최대한 청하지 않는다. 내 선에서 어떻게 해서든 해결해 본다. 이런 생각이 앞서니 더욱 말을 아끼게 되었다.</p>
<p>그러다 팀원 한분이 며칠 간 끙끙대던 에러를 함께 해결해달라고 요청하셨다. 처음에는 나와 둘이서 디버깅을 시작했는데 어느새 팀 전체가 그 에러를 고민하게 됐다. 팀이 모두 달라붙자 에러를 파악하고 고치는 데 3시간 정도 걸렸다.</p>
<p>신기한 건 그 에러를 고치고 다시 내 일을 시작한지 얼마되지 않아서 나에게도 같은 에러가 발생한 것이었다. 만약 팀원이 공유하지 않았다면 나도 며칠을 해결하느라 골머리 썩였을지도 모를 일이다.</p>
<p>이후 우리 팀은 하루 이상 고민한 문제는 즉각 보고하여 다 함께 들여다보기로 했다. 바쁜 만큼 아직 도움을 청하기 조심스럽기는 하지만 하나하나 해결하다 보니 팀워크가 무럭무럭 자라나는 게 느껴진다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[21-23주차 주니어 개발자의 우당탕탕 회고]]></title>
            <link>https://velog.io/@mini-boo/21-23%EC%A3%BC%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@mini-boo/21-23%EC%A3%BC%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 23 Sep 2024 08:26:27 GMT</pubDate>
            <description><![CDATA[<p>입사 후 계속 써내려가던 회사 일기가 이만큼 밀린 것도 처음이다. 그만큼 회사 내에서 그리고 개인적으로 일이 많았다는 뜻이기도 하다.</p>
<h1 id="💙-21주차-회고">💙 21주차 회고</h1>
<h2 id="🎯-1차-마감을-앞두고-full-burning-하기">🎯 1차 마감을 앞두고 Full-Burning 하기</h2>
<p>어느덧 프로젝트 1차 점검할 때가 다가왔다. 처음 프로젝트 설명을 듣고 나에게 어떤 업무가 떨어질까 전전긍긍하던 때를 지나 열심히 일한 증거물을 제출해야 했다. 덕분에 이번 일주일은 말그대로 일의 연속이었다. 일주일 내내 야근하며 업무를 쳐냈음에도 할 일이 산더미 같이 쌓였다.</p>
<p>9시 출근, 9시 반 퇴근. 그조차도 부족해서 출근하자마자 갖던 티타임도 포기하고 책상 앞에 앉아 열심히 키보드만 두드렸다. 다른 팀 부서원 분들도 우리 팀 분위기를 눈치챘는지 점심 시간에 &#39;요즘 연구소가 엄청 조용하다&#39;며 힘내라고 화이팅을 불어넣어 주셨다.</p>
<h2 id="🎯-수술을-했다">🎯 수술을 했다.</h2>
<p>21주차, 9월 1주차의 가장 큰 이벤트는 &#39;수술&#39;이 아닐까 싶다. 회사에 다니고 난 후 갑자기 생긴 병은 아니고, 예전부터 관리하고 있던 게 결국 터졌다. 수술 자체는 간단했지만 1박 2일 입원에 마취까지 해야 해서 금요일 반차를 내고 입원 및 수술 절차를 밟았다.</p>
<p>보통 우리 회사는 오후 반차를 내면 점심시간에 바로 퇴근하는 것이 일반적이다. 그러나 하필 그날에 프로젝트의 중간 보고가 있어서 머지하느라 아슬아슬한 시간대에 나갔다.</p>
<p>병원에 도착하니 일사천리로 진행됐다. 수술한 사람들의 경험담을 여럿 읽어보았을 때 마취를 풀린 순간부터 죽음과도 같은 고통이 시작된다고 했다. 그러나 매우 다행히도 고통이 크진 않았다. 다만 수술한 몸이니 무리하고 싶지는 않아서 무통주사를 달고 퇴원했다.</p>
<p>이후 주말 내내 누워서 지냈던 것 같다. 무통 주사 부작용 때문에 잠이 쏟아져서 쉬는 동안 기억은 그리 많지 않다.</p>
<h1 id="💙-22주차-회고">💙 22주차 회고</h1>
<h2 id="🎯-수술-후-출근-현명한-선택일까">🎯 수술 후 출근... 현명한 선택일까?</h2>
<p>주말만 쉬고 월요일부터 바로 출근했다. 다만 병원에서 한번 내원해 상태를 봐야 한다는 안내를 받아 월요일 오전엔 반차를 냈다. 수술이 잘 되었다는 보고를 받고 무통 주사를 떼어냈다. 출근해야 한다고 말씀 드리니 주사로 진통제를 놔주셨는데 이게 정말 큰 일을 해주었다.</p>
<p>일주일만 버티면 추석 연휴니까 괜찮다고, 괜찮을 거라고 생각했는데 오산이었다. 계속 앉아있자 통증보단 불편함이 지속되었다. 더불어 왕복 3시간 반의 출퇴근길도 큰 부담이 되었다.</p>
<p>우리 회사는 재택근무가 없나요... 를 외치며 꾸역꾸역 회사에 출근했다. 사실 1차 마감 이후 받은 피드백을 빨리 반영해서 이번주 금요일까지 다시 보여드려야 했는데 나는 수술을 받았으므로 좀 더 기한을 길게 받았다.</p>
<p>야근을 아끼고 정시 퇴근을 한 건 지금 돌이켜도 매우 훌륭한 선택이었던 것 같다.</p>
<p>출퇴근을 못할 수준은 아니었으나 그렇다고 오래 버티고 앉아 있을 상태도 아니었으니까.</p>
<h2 id="🎯-추석-이벤트">🎯 추석 이벤트!</h2>
<p>금요일은 추석 연휴를 앞두고 간단한 점심 회식과 빠른 퇴근이 이루어졌다. 오전 근무를 마치자 사업부에서 치킨, 피자, 맥주를 준비해 주셨다. 안타깝게도 수술을 했기에 맥주를 마실 수는 없었으나 연휴 이전에 회사 사람들과 즐거운 시간을 보냈다.</p>
<p>특별 이벤트로 회사 내 비치된 다트 게임이 진행됐는데, 개인전에서는 0점이라는 초라한 성적을 기록했으나 단체전에선 무려 잭팟!을 터뜨렸다. 아쉬운 점은 다른 팀이 막판에 역전을 했다는 점 정도일까....</p>
<h1 id="💙-23주차-회고">💙 23주차 회고</h1>
<h2 id="🎯-추석-연휴가-이렇게-빨리-간다고">🎯 추석 연휴가 이렇게 빨리 간다고?</h2>
<p>고대하고 고대하던 추석 연휴가 드디어 다다랐다. 신기하게 참 쏜살같이 지나가더라. 최근에는 일 때문에 바빴지만 여유가 있을 때 오후 시간이 참 안 지나간다 싶었는데 쉴 때는 순식간이었다.</p>
<p>목요일에 출근하자 연차를 낸 사람들로 인해 회사가 제법 한산했다. 연휴동안 푹 쉬어 한결 나아진 몸으로 힘내서 일하고 빠르게 칼퇴했다. 후후.</p>
<p>쉬고 왔기 때문인지 집중력이 좋아 피드백 받은 부분도 거의 마무리지을 수 있었다. 다음 주는 한결 편안하게 업무를 진행할 수 있을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[20주차 주니어 개발자의 우당탕탕 회고]]></title>
            <link>https://velog.io/@mini-boo/20%EC%A3%BC%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@mini-boo/20%EC%A3%BC%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 02 Sep 2024 01:53:28 GMT</pubDate>
            <description><![CDATA[<h2 id="🎯-일상이-바뀌었다">🎯 일상이 바뀌었다.</h2>
<p>월요일부터 평온하던 모든 흐름이 바뀌었다. 출근할 때까지만 해도 평소와 다름이 없을 거라 생각했는데 오후에 이르러 회사가 발칵 뒤집힐 일이 생겼다. 자세한 이야기는 다음에 다룰 수 있을 것 같지만 아무튼 그 소식을 듣는 순간 동요가 커서 일이 손에 잡히질 않았다.</p>
<p>일주일 간 회사 동료들 사이에서도 관련된 이야기가 끊이질 않았다. 이렇게 혼란스러운 모습은 또 처음이었다.</p>
<h2 id="🎯-일이-성큼-늘었다">🎯 일이 성큼 늘었다.</h2>
<p>첫 번째 사건과 관련하여 나의 일이 두 배 가까이 늘게 되었다. 업무 분담을 새로 진행하기 전에 일단 나에게 일이 편중된 것이다. 덕분에 일주일 내내 정신 없이 일을 하다가 퇴근했다. 잠시 서 있을 시간조차 없이 일하다 보니 금요일에는 도저히 책상에 앉을 수 없을 정도로 허리가 아팠다.</p>
<p>한 바퀴 천천히 산책한 후에 스트레칭하고 겨우 일을 마쳤다. 다음 주에는 일이 더 많은데 이를 어쩌면 좋지....</p>
<h2 id="🎯-회사-사람들과-야구를-보러-갔다">🎯 회사 사람들과 야구를 보러 갔다.</h2>
<p>저번 주에 야구 약속을 잡았다. 일하다가 문득 &quot;야구 보러 갈래요?&quot;라고 물어서 성사된 약속이었다. 이번 주에 들어 어딘가 놀러갈 기분은 아니게 되었으나 이미 정한 일정은 지켜야 했다.</p>
<p>놀랍게도 정말 재미 없는 야구를 했다. 10회 말까지 0대0을 유지하다가 11회 초에 홈런을 얻어맞고 졌다. 무승부가 팽팽하게 유지된 경기였느냐 하면 그것도 아니었다. 변화구에 모두 속아서 배트를 붕붕 돌리더라.... 보면서 속이 터졌다.</p>
<p>야구에 관해 잘 모르는 사람들과 함께 갔기에 좋은 경기를 보여줬으면 했건만 예상을 벗어나질 못했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[19주차 주니어 개발자의 우당탕탕 회고]]></title>
            <link>https://velog.io/@mini-boo/19%EC%A3%BC%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@mini-boo/19%EC%A3%BC%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 23 Aug 2024 07:48:22 GMT</pubDate>
            <description><![CDATA[<h2 id="🎯-본격적인-개발이-시작되었다">🎯 본격적인 개발이 시작되었다.</h2>
<p>이번 주부터 설계와 세팅을 끝내고 본격적인 개발에 접어들었다. 초반에 미리 작성해둔 코드도 있었는데, 휴가를 마치고 돌아와보니 너무 엉망이라 싹 다 뜯어고쳐서 다시 pr했다. 동료 분이 코드를 확인하고 저번 주와 너무 다르지 않냐고 놀라셨을 정도였다. 하지만 저번 주는 휴가 가기 전 급하게 pr하느라 테스트도 제대로 못 해본 코드였는걸....</p>
<h2 id="🎯-월요일-나-혼자-근무">🎯 월요일, 나 혼자 근무</h2>
<p>휴가를 마치고 돌아온 월요일, 우리 팀엔 나 혼자였다. 나를 뺀 모든 분들이 휴가와 출장을 떠나셨기 때문이다. 그래서 월요일엔 좀 더 여유있게 <del>월루를</del> 일을 하려고 했는데 하필 그날 개발의 신이 강림하시는 바람에(...) 시간 가는 줄 모르고 미친듯이 코드를 짰다.</p>
<p>물론 다음 날 확인하니 그 코드도 고쳐야 할 부분이 필요하다는 것을 인지하여 몇 번 리팩토링을 진행했지만, 신기할 정도로 진도가 잘 나가서 놀라웠다.</p>
<p>나는 혼자서 일하는 게 체질인 걸까? <del>물론 그렇지 않다는 걸 안다</del></p>
<h2 id="🎯-한층-더-친해지기">🎯 한층 더 친해지기</h2>
<p>입사하고 한 달 좀 넘었을 무렵부터 점심에 도시락을 싸오기 시작하면서 타 부서 팀원 분들과 조금씩 안면을 트고 친해지기 시작했다. 하지만 이번 주만큼 유독 친하게 대화한 적이 없었던 것 같다.</p>
<p>타 부서에 지원을 나가 곁에 앉아 대화하고 카페에도 가면서 얘기도 많이 했고 친해지기도 많이 친해졌다. 점심 시간에 이야기하면서 충분히 관계가 가까워졌다고 생각했는데 더 가까워질 게 있었다. 심지어 번개 약속도 잡았다.</p>
<p>우리 팀뿐만 아니라 타 부서 사람들과도 친해지니 회사 다니기 한층 편안해졌다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JS로 input 태그의 value속성을 동적으로 사용하기]]></title>
            <link>https://velog.io/@mini-boo/JS%EB%A1%9C-input-%ED%83%9C%EA%B7%B8%EC%9D%98-value%EC%86%8D%EC%84%B1%EC%9D%84-%EB%8F%99%EC%A0%81%EC%9C%BC%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@mini-boo/JS%EB%A1%9C-input-%ED%83%9C%EA%B7%B8%EC%9D%98-value%EC%86%8D%EC%84%B1%EC%9D%84-%EB%8F%99%EC%A0%81%EC%9C%BC%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 21 Aug 2024 01:40:28 GMT</pubDate>
            <description><![CDATA[<h2 id="이슈">이슈</h2>
<p>html input 태그에 value 속성이 할당되었음에도 화면에 value 값이 노출되지 않았다.</p>
</br>

<h2 id="해결과정">해결과정</h2>
<p>현재 value 속성은 자바스크립트를 활용해 동적으로 할당되고 있었다.</p>
<p>다만 할당하는 방식이 틀렸다.</p>
<h3 id="수정-전">수정 전</h3>
<p>attr로 value를 지정하여 할당하고 있다. 이 경우 id 속성을 수정하는 것이 value 속성을 부여하는 게 아니다.</p>
<pre><code>$(tr).children().children(&quot;#empno&quot;).attr(&quot;value&quot;, getValue(result));</code></pre><h3 id="수정-후">수정 후</h3>
<p>직접 value 속성을 할당한다.</p>
<pre><code>$(tr).children().children(&quot;#empno&quot;).val(getValue(result));</code></pre></br>

<h2 id="js를-사용해-동적으로-value-속성을-할당하는-방법">JS를 사용해 동적으로 value 속성을 할당하는 방법</h2>
<p>html:</p>
<pre><code>&lt;input type=&quot;text&quot; class=&quot;col-sm-2 form-control&quot; id=&quot;empno&quot; name=&quot;empno&quot; readonly&gt;</code></pre><p>JS:</p>
<pre><code>$.ajax({
        success: function(result) {

                    $(tr).children().children(&quot;#empno&quot;).val(getValue(result));


            });

function getValue(row) {
            let resultValue = &#39;&#39;;
            switch (row.resultValue) {
                case &#39;none&#39;:
                    resultValue = &quot;a&quot;;
                    break;
                case &#39;allowed&#39;:
                    resultValue = &quot;b&quot;;
                    break;
                case &#39;notallowed&#39;:
                    resultValue = &quot;c&quot;;
                    break;
                default:
                    resultValue = &quot;d&quot;;
            }
            return resultValue;
        }</code></pre></br>

<h2 id="소감">소감</h2>
<p>알고보면 쉬운 에러였는데, 기존 JS 코드가 너무 복잡해서 찾아내는 데만 2시간이 넘게 걸렸다... </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[18주차 주니어 개발자의 우당탕탕 회고]]></title>
            <link>https://velog.io/@mini-boo/18%EC%A3%BC%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@mini-boo/18%EC%A3%BC%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 19 Aug 2024 00:23:46 GMT</pubDate>
            <description><![CDATA[<h2 id="🎯-개발을-위한-기본-세팅이-모두-끝났다">🎯 개발을 위한 기본 세팅이 모두 끝났다.</h2>
<p>본격적인 API 개발에 앞서 필요한 세팅들이 어느 정도 정리되었다. 처음 개발을 시작할 당시만 해도 개발 세팅이라 하면 깃, 인텔리제이, 협업 툴 마련하기 수준이라고 생각했다.
개발 세팅은 말 그대로 개발하기 위한 세팅을 일컫는다. 즉, 본격적인 개발에 앞서 적용할 기술 중 미리 세팅해둬야 하는 것까지 포함해야 한다. 예를 들어, Swagger를 적용하기로 결정했다고 가정하자. 그렇다면 Swagger를 적용하기 위한 기본 토대가 마련되어야 한다. 개발을 하면서 Swagger를 구축할 수는 없지 않은가.
그렇게 기나긴 설계 회의를 거쳐 결정된 기술을 미리 세팅하는 데도 일주일이 꼬박 걸렸다. 개발 세팅이 오래 걸린다는 이유를 깨닫는 순간이었다.</p>
<h2 id="🎯-휴가를-떠난다">🎯 휴가를 떠난다.</h2>
<p>오늘의 일기가 짧은 이유는 다름 아닌 하계 휴가를 떠났기 때문이다. 신입인 나는 아직 연차가 몇 개 없는 상태라 하계 휴가는 일찌감치 포기한 상태였다. 그러다 광복절이 목요일인 것을 기회로 금요일 하루 연차를 내어 휴가 일정을 마련할 수 있었다.</p>
<p>덕분에 8월 셋째 주는 주 3일제 근무였는데, 얼마나 달콤했는지.... 돌아오면 할 일이 태산같을 걸 알고 있으면서도 막상 놀러갈 생각을 하니 뿌듯하다.</p>
<p>야근 시즌에 앞서 푹 리프레시하고 와야지!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Security 프로젝트에 Swagger 적용하기]]></title>
            <link>https://velog.io/@mini-boo/Spring-Security-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-Swagger-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@mini-boo/Spring-Security-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-Swagger-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 14 Aug 2024 05:00:29 GMT</pubDate>
            <description><![CDATA[<p>spring security 개발하며 swagger를 적용하다 문제가 생겼다. 에러를 보는 순간 발생 이유를 파악할 수 있었다.</p>
<p>나는 API 보호를 위해 spring security를 적용했다. 그리고 swagger는 API 명세서를 자동으로 작업해 주는 도구다. 그렇다. API 명세서를 작성하고 테스트 자동화를 하기 위해선 API에 접근해야 하는데, 내가 만든 spring security가 API 접근을 차단한 것이다.</p>
<h3 id="swaggerconfig-수정하기">SwaggerConfig 수정하기</h3>
<h4 id="openapidefinition">@OpenAPIDefinition</h4>
<p>api에 접근하기 위한 코드를 추가해 주자.</p>
<pre><code>security = @SecurityRequirement(name = &quot;swagger에서 쓸 토큰 이름&quot;)</code></pre><h4 id="securityscheme">@SecurityScheme</h4>
<pre><code>@SecurityScheme(
        name = &quot;swagger에서 쓸 토큰 이름&quot;,
        type = SecuritySchemeType.APIKEY,
        in = SecuritySchemeIn.HEADER,
        paramName = &quot;spring security를 위해 설정한 토큰 이름&quot;
)</code></pre><h3 id="springsecurityconfig-수정">SpringSecurityConfig 수정</h3>
<pre><code>http
      .authorizeRequests(authorizeRequests -&gt; authorizeRequests
                        .requestMatchers(&quot;/swagger-ui/**&quot;, &quot;/api-docs/**&quot;).permitAll() //swagger url 접근 설정 추가하기

        return http.build();</code></pre><br/>

<h3 id="결과">결과</h3>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/321c5983-296b-4ac5-ae0b-64d46e66fe2c/image.png" alt=""></p>
<p>Authorize 버튼을 통해 토큰을 검증하면 api 접근이 가능해진다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[오픈소스 프로젝트 번역에 기여하기]]></title>
            <link>https://velog.io/@mini-boo/%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B2%88%EC%97%AD%EC%97%90-%EA%B8%B0%EC%97%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@mini-boo/%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B2%88%EC%97%AD%EC%97%90-%EA%B8%B0%EC%97%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 12 Aug 2024 04:50:25 GMT</pubDate>
            <description><![CDATA[<p>개발자를 꿈꾸는 사람들이라면 &#39;오픈 소스&#39;라는 단어를 한번쯤은 들어봤을 것이다. 그리고 오픈 소스에 두려움을 느끼는 사람도 있을 것이다. 나 역시 그랬다. 오픈 소스란 대단한 사람들이 모여서 꾸리는 곳이고, 내가 기여할 부분은 없을 거라고 여겼다. 그러다 우연한 기회에 하나의 프로젝트를 보게 되었다.</p>
<p><a href="https://github.com/luciancah/nextjs-ko">Next.js 문서 한국어 번역 프로젝트</a></p>
<p><a href="https://nextjs-ko.org/docs/pages/building-your-application/upgrading/version-9">&gt;&gt; 내가 번역한 문서</a></p>
<p>Next.js는 서버 사이드 렌더링, 정적 웹 페이지 생성 등 리액트 기반 웹 애플리케이션 기능들을 가능케 하는 Node.js 위에서 빌드된 오픈 소스 웹 개발 프레임워크다.</p>
<p>물론 나는 자바 백엔드 위주로 공부했고, 현재도 자바를 주로 사용하는 위치이다 보니 Next.js와 가까워진 적이 없었다. 프론트엔드에 한층 뒤떨어져 살던 나였지만, &#39;번역&#39;에 기여하는 건 해볼 만하다고 생각해서 시작했다. 물론 그러다 큰코 다쳤다.</p>
<br/>

<h2 id="오픈소스-기여-신청하기">오픈소스 기여 신청하기</h2>
<p>오픈소스에 기여하기 위한 방법은 프로젝트마다 다르다. 그래서 리드미 문서를 꼼꼼하게 읽어 봐야 한다. 프로젝트 리더가 컨트리뷰터에게 친절하게 설명해 두었을 것이다.</p>
<p>다만 보통 기본적인 폼은 대체로 비슷하다.</p>
<p><strong>1. 기여할 부분을 찾으면 양식에 따라 issue를 작성한다.</strong></p>
<p>코드를 직접 보며 기여할 부분을 찾아야 하는 프로젝트도 있고, 기여가 필요한 이슈들을 이미 등록해 둔 프로젝트도 있다. 직접 코드를 들여다보기 어렵다면 후자처럼 이미 기여할 부분이 명확한 프로젝트를 찾아보는 걸 추천한다.</p>
<p>나는 이미 프로젝트 리더가 기여할 부분을 정리하여 올려줬기에 신청하여 프로젝트에 참여했다.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/8fccdfd5-2c47-47b2-9b28-370796e7f663/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/77041c25-0a07-4a1f-96be-c36402d6a4d1/image.png" alt=""></p>
<p><strong>2. 허가 받으면 프로젝트를 fork하여 작업을 진행한다.</strong></p>
<p>이때 중요한 건, 문서를 꼼꼼히 읽고 작업하는 것이다. 오픈 소스는 광범위한 팀플레이다. 내 마음대로 작업해서는 안 된다는 뜻이기도 하다. 문서를 잘 읽고 요구사항과 코드 스타일에 맞춰 작업해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/e0682d65-28a5-4b73-b765-e3c6b2896a67/image.png" alt=""></p>
<p><strong>3. 커밋 컨벤션에 맞게 pr한다.</strong></p>
<p>보통 커밋 컨벤션은 정형화되어 있다. (feat, fix, refactor 등...) 하지만 프로젝트에 따라 고유하게 사용하는 컨벤션이 있을 수 있으니 잘 체크하여 코드 컨벤션을 지키도록 하자. 2번에서도 말했듯이 오픈 소스는 팀플레이다.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/8632ebe2-02d9-4553-b805-96274a007195/image.png" alt=""></p>
<p>깃허브 pr 제목 양식도 문서를 참고해서 어긋나지 않게 작성해 줘야 한다.</p>
<p><strong>4. 코드 리뷰를 받은 후 문제가 없으면 merge된다.</strong></p>
<p>오픈 소스는 대부분 철저한 코드 리뷰 후에 merge가 이뤄진다. 상대가 수정을 요구하거나 다른 방향을 제시하는 경우, accept하여 수정하거나 debate를 통해 의견 차를 좁히자. </p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/5be23b26-7933-4a4f-a1a8-d65810ed6281/image.png" alt=""></p>
<p><strong>자잘한 팁?</strong></p>
<p>commit할 때 모든 변경 사항을 push해서는 절대 안 된다. 순수하게 내가 작업한 코드만 밀어넣어야 한다. 그렇지 않으면 fork하면서 생겨난 여러 잡다한 코드들까지 함께 push되어 프로젝트 구성원을 어지럽게 만들 것이다.</p>
<br/>

<h2 id="단순한-기여인-줄-알았는데-이렇게-힘들다고">단순한 기여인 줄 알았는데 이렇게 힘들다고?</h2>
<p>처음 프로젝트를 찾았을 때는 단순 번역 프로젝트이기 때문에 쉽게 끝날 줄 알았다. 하지만 잔실수가 여럿 등장하며 생각보다 시간이 오래 걸렸다. (아마 프로젝트 리더분이 이 인간은 역대급 빌런이라고 생각하셨을 게 분명하다....)</p>
<p>push할 때 습관처럼 git add .을 쓴 바람에 필요없는 변경사항까지 모두 push되어 머릿속이 새하얘지기도 하고, 더는 실수하지 않으리라 다짐하며 문서를 읽고 또 읽은 뒤에 작업해도 어김없이 코드 리뷰에서 수정 사항을 지적받곤 했다. 나조차도 내가 왜 이런지 모를 정도로 당황스러운 일의 연속이었다. 회사에서 코드 리뷰를 받을 때도 이 정도로 실수한 적이 없었기 때문이다.</p>
<p>내가 익숙하지 않은 프레임워크 관련 문서를 작업하려고 해서 그런가? 내가 이 프로젝트에 폐를 끼치는 게 아닐까? 다른 분들은 쉽게 accept됐는데 왜 나만 문제지? 프로젝트 리더는 날 보고 어떤 생각을 할까? 차라리 신청하지 말걸 그랬나? 등등. return을 받아 다시 작업해야 할 때마다 좋지 않은 생각이 꼬리에 꼬리를 물고 이어지는 바람에 오히려 쉬운 것도 어렵게 가고 말았다.</p>
<p>심지어는 하도 실수가 나오니 양해를 구하고 프로젝트에서 하차할 생각까지 했는데, 리더 분께서 (천사같은 마음으로) 이끌어 주셔서 무사히 merge에 다다를 수 있었다.</p>
<p>소중한 첫 기회를 마련해 주신 프로젝트 리더 분께 다시 한번 진심으로 감사를 전한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[17주차 주니어 개발자의 우당탕탕 회고]]></title>
            <link>https://velog.io/@mini-boo/17%EC%A3%BC%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@mini-boo/17%EC%A3%BC%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 09 Aug 2024 08:38:11 GMT</pubDate>
            <description><![CDATA[<h2 id="🎯-시간이-너무-빨리-간다">🎯 시간이 너무 빨리 간다.</h2>
<p>벌써 일주일이 지났다. 하루하루 바쁘게 살았기 때문에 시간이 빨리 간 것 같은데 돌이켜보면 생각보다 한 게 없는 것 같기도 하다.</p>
<p>본격적인 개발에 들어가면서 필요한 기본 세팅은 마쳤고, 도입해야 하는(사실은 도입하고 싶은) 기술에 따른 아키텍처도 그렸다. 다만 팀장님이 하계 휴가를 떠나셔서 아키텍처와 DB 최종 검토는 받지 못했다. 다음 주쯤 검토를 받을 예정이다.</p>
<p>현업의 제품을 개발하면서 확실히 내가 기존에 경험해 왔던 환경과는 다르다는 게 느껴졌다. </p>
<p>사이드 프로젝트에서는 내가 원하는 건 뭐든 적용해 봤고, 조금 걸리적거리는 게 있어도 크리티컬하지 않으면 무시하곤 했다. 하지만 현장에선 팀 단위로 굴러가기 때문에 아무리 사소한 것이라도 조건을 지켜 개발을 진행해야 한다. 그랬기에 기본 세팅에서 발생하는 자잘하지만 확실히 짚고 넘어가야 하는 부분들 때문에 시간을 제법 잡아먹혔다.</p>
<h2 id="🎯-기술부채가-끊임없이-쌓인다">🎯 기술부채가 끊임없이 쌓인다.</h2>
<p>제품을 개발하기 위한 기술들을 논의하며 도커, 카프카, 레디스, 스프링 시큐리티, JWT 등 다양한 기술들이 오갔다. 내가 아는 기술도 있었고 모르는 기술도 있었다.</p>
<p>특히 카프카는 대규모 서비스를 설계한 경험이 없으니 구인공고에서나 보던 기술이었는데 이번에 처음으로 기술 적용을 위해 공부하게 됐다.</p>
<p>개념에서부터 헷갈리는 부분들이 꽤 되었으나 당장 우리 서비스에 필요한 부분을 파고들자 조금은 명료해졌다. 하지만 기술을 완전히 이해하기 위해서는 필요한 부분만 조금 떼서 쓸 수는 없는 노릇이다.</p>
<p>개발 단계에 들어가며 헤아릴 수 없이 늘어나는 기술 부채를 하염없이 바라보고만 있다. 이걸 언제 다 해결할 수 있을까.</p>
<h2 id="🎯-날이-덥자-체력이-급하게-떨어진다">🎯 날이 덥자 체력이 급하게 떨어진다.</h2>
<p>이번 주는 극과 극의 날이라고 할 수 있겠다.</p>
<p>화요일부터 수요일까지는 새벽에 더워서 깼다. 덕분에 회사에서도 극한 피로를 느꼈다. 옆자리의 동료가 상태 괜찮은 것 맞냐고 물으실 정도였다.
덕분에 틈이 날 때마다 뻗어서 잠을 잤다. 심지어 주말에 최대한 수면 시간을 확보하고 평일에도 최소 7시간은 잤음에도 눈을 뜨고 있는 시간 내내 졸렸다.</p>
<p>그와 반대로 입추에 접어들며 내 상태는 또 급하게 바뀌었다. 출퇴근 시간에 기온이 낮아지자 수면 시간이 줄어든 것이다. 덕분에 6시 반이면 귀신같이 눈을 떴다.
체력도 저번보다는 많이 회복되었다. 자도자도 피곤한 증상이 확연히 줄어들어 다행이라고 생각한다.</p>
<h2 id="🎯-헌혈하러-가자">🎯 헌혈하러 가자!</h2>
<p>이번 주말에는 외출 없이 집에만 콕 박혀 있으려고 했는데, 아마 그러긴 힘들 것 같다. 헌혈의 집에서 연락이 왔기 때문이다.</p>
<p>나는 가능하면 꾸준히 헌혈에 참여하려고 한다. 다만 저번에는 한창 더운 때 가서 그런지 몰라도 철분 수치가 부족하여 거부당했다. 그때는 혈액량도 급하지 않아서 당분간 헌혈하지 않아도 괜찮겠다고 생각했다.</p>
<p>그런데 이번 주 근무 시간에 헌혈의 집에서 갑작스레 연락이 왔다. 헌혈 스케줄을 잡을 수 있겠느냐고 물으셨다. 전화를 할 정도로 혈액량이 부족한가 싶어 당장 스케줄을 잡았다.</p>
<p>덕분에 토요일에 외출 일정이 잡혔다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[APACHE KAFKA란?]]></title>
            <link>https://velog.io/@mini-boo/APACHE-KAFKA%EB%9E%80</link>
            <guid>https://velog.io/@mini-boo/APACHE-KAFKA%EB%9E%80</guid>
            <pubDate>Fri, 09 Aug 2024 02:28:47 GMT</pubDate>
            <description><![CDATA[<h2 id="apache-kafka란">Apache Kafka란?</h2>
<p>Apache kafka는 분산 메시지 스트리밍 플랫폼이로 대용량 데이터 스트림을 안정적으로 수집, 저장, 전송 및 처리하는 데 사용되는 소프트웨어이다. 특히 대용량 데이터 처리 및 실시간 데이터 스트리밍에 적합하다.</p>
<h3 id="apache-kafka의-주요-기능">Apache kafka의 주요 기능</h3>
<ul>
<li>분산 아키텍처: 여러 브로커로 구성된 클러스터를 형성하며 데이터 및 처리 부하를 분산하여 확장성을 제공한다.</li>
<li>내구성: 메시지는 디스크에 저장되어 장애 시에도 유실되지 않는다.</li>
<li>고신뢰도 메시징: 메시지 전달은 시멘틱을 통해 조절이 가능하다.</li>
<li>실시간 데이터 처리: Kafka 스트림 처리 API를 사용하여 데이터 스트림을 실시간으로 처리할 수 있으며 데이터 처리 엔진을 연결하는 데 이상적이다.</li>
</ul>
<br/>

<h2 id="링크드인과-apache-kafka">링크드인과 Apache Kafka</h2>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/1ba14e6e-25bc-4bee-9dec-886671ec9cee/image.png" alt=""></p>
<p>kafka는 2010년 링크드인에서 개발되었다. kafka가 도입되기 전엔 서비스가 내부적으로 통신하여 높은 결합도를 가지는 end-to-end 연결 방식으로 데이터를 주고받았다. 이후 서비스가 성장하며 해당 연결 방식으로 인해 복잡도가 증가하기 시작했다. 따라서 서비스와 통신 관리에 문제가 생겼으며 유지보수도 어려웠다. 또한 여러 개의 데이터 파이프라인은 데이터 처리 방법이 모두 다르기 때문에 확장성에 관한 고민이 생기게 된다.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/09e4acca-4e3a-4bba-96c8-2cc4570b7e29/image.png" alt=""></p>
<p>kafka는 위의 문제들을 해결하기 위해 도입되었다. kafka를 도입하면서, 모든 데이터와 데이터 흐름을 중앙에서 관리할 수 있게 되었고 서비스의 특정 부분에서 장애가 생기더라도 유연하게 대처할 수 있게 됐다.</p>
<br/>

<h2 id="apache-kafka-구성">Apache Kafka 구성</h2>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/4c2c4e30-cada-438f-bcb1-f14665b4c62c/image.png" alt=""></p>
<h3 id="topic-partitions-offsets">Topic, Partitions, Offsets</h3>
<h4 id="topic">Topic</h4>
<p>토픽은 메시지를 구분하는 통로다. 하나의 카프카 안에 여러 메시지가 뒤섞여서 오가는데, 그 메시지를 구분하는 기준이 토픽이다. 데이터베이스의 테이블 역할을 수행한다고 보면 된다. 토픽은 여러 개를 만들 수 있고, 이름으로 구분된다.</p>
<h4 id="partitions">Partitions</h4>
<p>파티션은 토픽 당 데이터를 분산 처리하는 단위다. 병렬 처리와 많은 양의 데이터 처리를 위해 파티션을 늘릴 수 있다. 다만 파티션은 증가만 가능하고, 삭제는 불가하다.
각각의 파티션은 인덱스를 가지며, 파티션 별로 증가하는 아이디(Offsets)을 가지게 된다.  Offset은 파티션에서만 의미가 있다. 예를 들어 파티션1, 파티션2가 있다면 Offset 0번은 파티션 별로 다른 데이터를 가지게 된다.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/be5cc49d-a6d9-46f7-82cf-1f34af353551/image.png" alt=""></p>
<p>파티션 안에서는 데이터의 순서가 보장되며, 파티션에 데이터가 한번 쓰이면 변경되지 않는다. 더불어 파티션에 데이터를 push하고자 할 때는 key값을 줘야 한다. key 값을 주지 않으면 데이터는 라운드로빈 방식으로 들어간다.</p>
<h4 id="조금-더-쉽게-설명해볼까">조금 더 쉽게 설명해볼까?</h4>
<p>Topic은 주제별로 모아둔 단톡방이라고 생각할 수 있다. 예를 들어 마블 정보를 공유하는 단톡방이 있다고 하자. 여기에는 참여한 사람들이 공유하는 다양한 정보가 담겨있을 것이다. 사람들은 이 정보를 차곡차곡 쌓아(partition) 필요한 경우, 데이터가 들어온 순서 별로 조회했다.</p>
<p>시간이 지나 단톡방에서 공유되는 정보가 바로 처리하기 힘들 정도로 늘어나기 시작한다. 따라서 사람들은 정보들을 분산처리하기로 한다. &#39;아이언 맨&#39;, &#39;캡틴 아메리카&#39;, &#39;스파이더맨&#39; 등 고유한 key값을 설정하여 정보들을 쌓아두고, 조회를 해야 할 때 key값에 따라 데이터를 순서 별로 조회한다.</p>
<p>다만 사람들이 설정한 고유한 key값에 따라 생성된 partition은 삭제할 수 없다. 만약 &#39;스파이더맨&#39;을 key값을 정보를 저장해야 하는데 깜빡하고 설정하지 않은 경우 어느 partitions에 들어가 있는지 찾을 수 없다. 즉, 데이터가 꼬일 수 있기 때문에 partition을 여러 개 설치하고자 하는 경우에는 관리에 유의해야 한다.</p>
<h3 id="broker">Broker</h3>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/65e5e6ff-dcca-46de-bc45-e33ed9fa0e94/image.png" alt=""></p>
<p>카프카를 클러스터로 구성했을 때, 각각의 서버를 Broker라고 부른다. Broker는 각각 토픽 파티션을 가지고, Read 및 Write를 관리한다. 어떤 Broker에 접속해도 전체 카프카 클러스터에 접속할 수 있다.</p>
<p>Broker는 ID(숫자)로 식별할 수 있다. 단, Broker ID와 Partition ID 간에는 아무런 관계가 없다. Partitions들은 여러 Broker 산에 분산되며, Topic이 생성될 때 Kafka는 자동으로 Topic을 구성하는 여러 Partitions들을 모든 Broker에게 할당 및 분배한다.</p>
<h4 id="조금-더-쉽게-설명해볼까-1">조금 더 쉽게 설명해볼까?</h4>
<p>Broker는 데이터를 분산 저장하여 장애가 발생하더라도 안전하게 사용할 수 있게 도와주는 애플리케이션이다. 데이터를 안전하게 보관하고 처리하기 위해 3개의 Broker 서버를 1개의 클러스터로 묶어서 운영한다. 즉, Broker는 데이터를 안전하게 저장하기 위해 분산 저장하고 복제하는 역할을 수행한다.</p>
<h3 id="zookeeper">Zookeeper</h3>
<p>Zookeeper는 Broker를 관리하고 Partitions의 Leader 선출을 돕는다. 또한 Kafka 변경에 관해 알림을 준다. Kafka는 Zookeeper 없이는 동작하지 못하며 Zookeeper의 Leader는 write, Follower는 read 작업을 수행한다.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/6b4c7ccc-e114-4b77-b794-ba2f13876746/image.png" alt=""></p>
<h3 id="producer">Producer</h3>
<p>Producer는 메시지를 생선하여 토픽으로 메시지를 보내는 애플리케이션이다. 메시지를 전송할 때 Key 옵션을 줄 수 있는데, 해당 Key 값으로 특정 파티션에 미시지를 보낼 수 있다. Key 옵션을 주지 않을 때는 파티션에 라운드로빈 방식으로 균등하게 메시지를 보낸다.</p>
<p>Producer는 전송 방식을 선택할 수 있따.</p>
<ul>
<li>메시지 손실 가능성이 높지만 빠른 전송이 필요한 경우: acks=0</li>
<li>메시지 손실 가능성이 적고 적당한 속도로 전송이 필요한 경우: asks=1</li>
<li>전송 속도는 느리지만 메시지 손실이 없어야 하는 경우: acks=all</li>
</ul>
<h3 id="consumer">Consumer</h3>
<p>토픽 이름으로 저장된 메시지를 가져가는 애플리케이션이나 서버 등을 Consumer라고 부른다. Consumer의 집합을 Consumer Group이라고 부르며, Kafka는 Consumer Group 단위로 데이터를 처리한다.
<img src="https://velog.velcdn.com/images/mini-boo/post/c6773369-506a-4584-a77c-7061b2a47005/image.png" alt=""></p>
<hr>
<h4 id="참고자료">참고자료</h4>
<ol>
<li><a href="https://velog.io/@hyun6ik/Apache-Kafka-Broker-Zookeeper">Apache Kafka - Broker, Zookeeper</a></li>
<li><a href="https://hyuuny.tistory.com/217">[Apache Kafka] 주키퍼(Zookeeper) &amp; 브로커(Broker)</a></li>
<li><a href="https://medium.com/@greg.shiny82/apache-kafka-%EA%B0%84%EB%9E%B5%ED%95%98%EA%B2%8C-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0-343ad84a959b">Apache Kafka 간략하게 살펴보기</a></li>
<li><a href="https://fastcampus.co.kr/pages/46896">Apache Kafka(아파치 카프카)로 구축하는 데이터 파이프라인과 대용량 데이터 처리까지</a></li>
<li><a href="https://www.redhat.com/ko/topics/integration/what-is-apache-kafka">Apache Kafka란 무엇일까요?</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Security로 API 권한 검증하기]]></title>
            <link>https://velog.io/@mini-boo/Spring-Security%EB%A1%9C-API-%EA%B6%8C%ED%95%9C-%EA%B2%80%EC%A6%9D%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@mini-boo/Spring-Security%EB%A1%9C-API-%EA%B6%8C%ED%95%9C-%EA%B2%80%EC%A6%9D%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 07 Aug 2024 05:39:42 GMT</pubDate>
            <description><![CDATA[<p>외부에서 URL만으로 API 콜을 할 수 없게 만들기 위한 방법이다.</p>
<p>Spring Security 기본 설정이 완료된 이후를 기점으로 작성되었다.
<br/></p>
<h2 id="apikeyauthfilter-작성하기">APIKeyAuthFilter 작성하기</h2>
<pre><code>import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;

import javax.servlet.http.HttpServletRequest;

public class APIKeyAuthFilter extends AbstractPreAuthenticatedProcessingFilter {

    private String principalRequestHeader;

    public APIKeyAuthFilter(String principalRequestHeader) {
        this.principalRequestHeader = principalRequestHeader;
    }

    @Override
    protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
        return request.getHeader(principalRequestHeader);
    }

    @Override
    protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
        return &quot;N/A&quot;;
    }
}</code></pre><br/>

<h2 id="security-configuration-작성하기">Security Configuration 작성하기</h2>
<p>@Value 어노테이션의 값은 application.yml에 설정된 값을 기반으로 작성하면 된다.</p>
<pre><code>@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Value(&quot;${appname.http.auth-token-header.name}&quot;)
    private String principalRequestHeader;

    @Value(&quot;${appname.http.auth-token}&quot;)
    private String principalRequestValue;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        APIKeyAuthFilter filter = new APIKeyAuthFilter(principalRequestHeader);
        filter.setAuthenticationManager(new AuthenticationManager() {
            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                String principal = (String) authentication.getPrincipal();
                if (!principalRequestValue.equals(principal)) {
                    throw new BadCredentialsException(&quot;The API key was not found or not the expected value.&quot;);
                }
                authentication.setAuthenticated(true);
                return authentication;
            }
        });

        http
            .cors(AbstractHttpConfigurer::disable)
            .csrf(AbstractHttpConfigurer::disable)
            .sessionManagement(sessionManagement -&gt; sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .addFilterBefore(filter, AbstractPreAuthenticatedProcessingFilter.class)
            .authorizeRequests(authorizeRequests -&gt; authorizeRequests.anyRequest().authenticated())
            .formLogin(AbstractHttpConfigurer::disable);

        return http.build();
    }
}</code></pre><br/>

<h2 id="applicationyml-파일-작성하기">Application.yml 파일 작성하기</h2>
<p>appname, 토근 이름, 토큰 정보는 custom해서 입력해주면 된다.</p>
<pre><code>appname:
  http:
    auth-token-header:
      name: Authorization
    auth-token: fdasrv34atdzbt4zeex7y
</code></pre><br/>

<h2 id="postman-테스트하기">Postman 테스트하기</h2>
<p>postman에서 API 테스트를 할 때, Header에 설정한 이름과 토큰 값을 세팅하고 날려야 한다. 그렇지 않으면 403 에러가 발생한다.</p>
<p><img src="https://velog.velcdn.com/images/mini-boo/post/1a5a460b-27e2-481d-8a13-19e7b481f40d/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>