<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>muok_1005.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 03 Nov 2024 09:18:19 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>muok_1005.log</title>
            <url>https://velog.velcdn.com/images/muok_1005/profile/80e5c318-9d6a-42d3-8e26-99f552394e10/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. muok_1005.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/muok_1005" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[창업 도전기] 창업에 도전해보겠습니다. 근데 왜 Why??]]></title>
            <link>https://velog.io/@muok_1005/%EC%B0%BD%EC%97%85-%EB%8F%84%EC%A0%84%EA%B8%B0-%EC%B0%BD%EC%97%85%EC%97%90-%EB%8F%84%EC%A0%84%ED%95%B4%EB%B3%B4%EA%B2%A0%EC%8A%B5%EB%8B%88%EB%8B%A4.-%EA%B7%BC%EB%8D%B0-%EC%9D%B4%EC%A0%9C-%EA%B8%B0%EB%A1%9D%EC%9D%84-%EA%B3%81%EB%93%A4%EC%9D%B8</link>
            <guid>https://velog.io/@muok_1005/%EC%B0%BD%EC%97%85-%EB%8F%84%EC%A0%84%EA%B8%B0-%EC%B0%BD%EC%97%85%EC%97%90-%EB%8F%84%EC%A0%84%ED%95%B4%EB%B3%B4%EA%B2%A0%EC%8A%B5%EB%8B%88%EB%8B%A4.-%EA%B7%BC%EB%8D%B0-%EC%9D%B4%EC%A0%9C-%EA%B8%B0%EB%A1%9D%EC%9D%84-%EA%B3%81%EB%93%A4%EC%9D%B8</guid>
            <pubDate>Sun, 03 Nov 2024 09:18:19 GMT</pubDate>
            <description><![CDATA[<h2 id="1-왜-기록을-하는가">1. 왜 기록을 하는가?</h2>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/d6c2dcff-c2b2-43c5-9c42-70935f482ea9/image.png" alt=""><em>(기록이 곧 자산이 된다.)</em></p>
<p>창업에 진심으로 올인한 지 한 달, 멘토링을 통해 귀중한 조언을 받았습니다. </p>
<blockquote>
<p>&quot;지금까지 했던 모든 것을 <strong>pending</strong>하라. 주머니에 넣어둔 것들이 언젠가는 새로운 영감이 되어 돌아올 것이다.&quot;</p>
</blockquote>
<p>이는 단순히 아이디어뿐만 아니라, 창업 과정에서 겪은 모든 순간들을 소중히 기록하고 간직해야겠다고 마음먹게 되었습니다. 기록을 함으로써 주머니에 넣은 자산들이 언젠가는 새로운 영감으로 돌아올 것이라 믿기 때문이죠.</p>
<h2 id="2-왜-창업을-하는가">2. 왜 창업을 하는가?</h2>
<p>저는 자기주도적인 성격이라 능동적인 삶을 희망했고, 과거 축제용 미팅 서비스를 운영해보면서 얻은 성취감, 짜릿함이 너무 좋았습니다. 때문에 나와 나의 무언가를 함께 성장시키며 성취감을 얻는 자기주도적인 삶을 살겠다고 다짐했습니다.</p>
<p>그래서 &#39;창업을 통해 나와 아이템을 함께 키워보는 것도 해보고 싶다&#39; 라는 생각만 갖고 실행에 옮기지는 않았습니다. </p>
<p>*<em>왜냐?? 불안했습니다. *</em></p>
<p>남들 하는 것처럼 취준하고, 취직해서 삶을 사는게 더 안정적이기 때문이죠.. 
그러던 중 군대에서 여러 책을 읽으면서 며칠이 지나도 잊혀지지 않는 그런 문구를 만났습니다.</p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/3ca1f4a8-b998-4903-9fa8-446b33c8e9ff/image.png" alt=""> <em>(군대에서 읽었던 책 목록)</em></p>
<blockquote>
<p>&#39;도전한 뒤 실패를 했을 때의 후회보다 죽을 때가 다 되어서 도전이라도 해볼 걸 하는 후회가 얼마나 더 크겠나?&#39;</p>
</blockquote>
<p>이 문구를 만나고 제 삶을 되돌아 봤습니다.</p>
<p>저는 <code>&#39;했는데 너무 후회가 돼!&#39;</code> 보다는 <code>&#39;아 그때 그냥 할 걸!!!!&#39;</code>이라고 후회했을 때 더 절망을 느끼는 사람이었습니다.</p>
<p>이제는 더 이상 똑같은 실수를 하면 안되죠. 이런 뇌리에 박힌 문구를 만나고도 변하지 않는다면 저는 그저 그런 사람이 되는 겁니다.</p>
<p>물론 창업은 매우x100 험난한 여정이며, 이런 여정을 지나도 성공할 확률이 매우x100 적은 어려운 것이라고 지극히 잘 알고 있습니다. </p>
<p>근데 어쩌겠습니까? 저 문구를 만난지 2년이 넘었는데도 아직 뇌리에 박혀있는데..</p>
<p>그렇습니다. 그냥 도전함으로써 노후에 <code>&#39;창업해볼 걸..&#39;</code> 하는 후회를 하지 말자는 결론을 내렸습니다.</p>
<p>차라리 시원하게 망하고 후회하는 게 더 정신 건강에 좋을 것 같네요.</p>
<h2 id="3-다음으로">3. 다음으로</h2>
<p>다음 포스트에서는 창업을 해보려고 노력했던 과정, 현재 창업팀이 결성된 과정을 적어보도록 하겠습니다.
마음이 꺾일 때마다 이 포스트를 보면서 초심을 찾아야겠네요.</p>
<p><strong>다시 한 번 외치면서 마무리 하겠습니다.</strong></p>
<blockquote>
<p>&#39;도전한 뒤 실패를 했을 때의 후회보다 죽을 때가 다 되어서 도전이라도 해볼 걸 하는 후회가 얼마나 더 크겠나?&#39;</p>
</blockquote>
<p><strong>그러니 그냥 도전하자! 화이팅!!!</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[창업 아티클 독후감] 유튜브 쇼츠에 AI를 곁들인 17살 사업가 (Feat. Crayo)]]></title>
            <link>https://velog.io/@muok_1005/%EC%B0%BD%EC%97%85-%EC%95%84%ED%8B%B0%ED%81%B4-%EB%8F%85%ED%9B%84%EA%B0%90-%EC%9C%A0%ED%8A%9C%EB%B8%8C-%EC%87%BC%EC%B8%A0%EC%97%90-AI%EB%A5%BC-%EA%B3%81%EB%93%A4%EC%9D%B8-17%EC%82%B4-%EC%82%AC%EC%97%85%EA%B0%80-Feat.-Crayo</link>
            <guid>https://velog.io/@muok_1005/%EC%B0%BD%EC%97%85-%EC%95%84%ED%8B%B0%ED%81%B4-%EB%8F%85%ED%9B%84%EA%B0%90-%EC%9C%A0%ED%8A%9C%EB%B8%8C-%EC%87%BC%EC%B8%A0%EC%97%90-AI%EB%A5%BC-%EA%B3%81%EB%93%A4%EC%9D%B8-17%EC%82%B4-%EC%82%AC%EC%97%85%EA%B0%80-Feat.-Crayo</guid>
            <pubDate>Thu, 17 Oct 2024 02:33:34 GMT</pubDate>
            <description><![CDATA[<p>오늘의 아티클: 이오플레닛의 <a href="https://eopla.net/magazines/22194"> 유튜브 쇼츠에 AI를 곁들인 17살 사업가 (Feat. Crayo)</a></p>
<h1 id="감명깊었던-문장">감명깊었던 문장</h1>
<h3 id="1-성공-전략과-마케팅-분석">1. 성공 전략과 마케팅 분석</h3>
<h4 id="11-틈새시장-공략">1.1 틈새시장 공략</h4>
<p><em>대규모 시장에서 경쟁하기보다는 특정 니치(틈새시장)에 집중하는 전략을 택했다. 경쟁이 적은 분야에서 전문성을 발휘하여 빠르게 시장을 선점할 수 있었다. 수요는 있지만 경쟁이 적은 분야를 찾기 위해 철저한 시장 조사를 진행했다.
&quot;작은 시장에서 최고의 자리에 오르는 것이 중요합니다. 그 후에 점차 확장해 나가는 것이 효율적이죠.&quot;</em></p>
<blockquote>
<p>수요가 있어야 하는 건 당연하고, 시장에 대한 분석도 중요하다고 느꼈다. 기존에는 시장에 상관없이 고객이 겪는 문제와 그 솔루션의 수요에 집중을 했지만 경쟁이 적은 시장인지는 신경조차 안썼다. 이제는 시장 분석도 함께 해야겠다..</p>
</blockquote>
<h4 id="12-단순함의-미학">1.2 단순함의 미학</h4>
<p><em>복잡한 기능보다는 사용자 친화적인 인터페이스와 핵심 기능에 집중하여 누구나 쉽게 사용할 수 있도록 설계되었다.</em></p>
<blockquote>
<p>현재 개발을 공부하는 학생 2명이서 아이템을 준비 중인데, 디자인에 대한 고민이 많았다. 역시 이쁜 UI보다는 정말 핵심 기능을 잘 녹일 수 있는 간단 명료한 UI로 가는게 맞는 것 같다.</p>
</blockquote>
<h4 id="13-유기적인-마케팅">1.3 유기적인 마케팅</h4>
<p><em>광고에 의존하지 않고, 컨텐츠와 커뮤니티를 통해 자연스럽게 제품을 홍보한다. 상세 내용은 다음과 같다.
콘텐츠 마케팅 : YouTube 채널과 블로그를 통해 Crayo 사용법, 콘텐츠 제작 팁, 업계 트렌드 등 가치 있는 정보를 제공했습니다.
소셜 미디어 활용 : 전략적 해시태그 사용과 사용자 제작 콘텐츠(UGC) 이벤트로 참여를 유도했습니다.
인플루언서 협업 : 마이크로 인플루언서와의 협업 및 베타 테스터 프로그램을 통해 진정성 있는 리뷰를 확보했습니다.
커뮤니티 구축 : Discord 서버 운영과 정기적인 온라인 워크숍으로 활발한 사용자 커뮤니티를 형성했습니다.
파트너십 프로그램 : 교육기관 협력과 스타트업 지원 프로그램으로 장기적인 고객 기반을 확보했습니다.</em></p>
<blockquote>
<p>마케팅이라고 하면 무조건 SNS 광고만 생각했는데, 다양한 마케팅 방법이 있다는 걸 알게 됐다. 특히 나의 고객층이 많이 분포되어 있는 커뮤니티에 광고를 하는 방법도 있지만, 직접 커뮤니티를 구축하는 새로운 방법도 알게 됐다. 근데 이거는 오래 걸릴 것 같아서 초기에는 사용하지 못할듯..?</p>
</blockquote>
<h3 id="2-비즈니스-모델과-서비스">2. 비즈니스 모델과 서비스</h3>
<h4 id="21-bm">2.1 BM</h4>
<p><em>1. 월 구독 모델: 다양한 요즘제를 제공하여 사용자의 필요에 따라 여러 요금제를 마련했다.
2. 부가 서비스와 크레딧 시스템: 추가 기능이나 콘텐츠를 이용하기 위해 크레딧 시스템을 활용했다.</em></p>
<blockquote>
<p>플랫폼, 프로그램은 구독 모델이 국룰인 것 같다. 아마 우리도 구독 모델을 채택할 예정이다.</p>
</blockquote>
<h4 id="22-부가적인-서비스">2.2 부가적인 서비스</h4>
<p><em>1. 제휴 프로그램: 다른 사람들을 소개하면 보상을 제공하는 제휴 프로그램을 운영했다. 사용자들이 보상을 얻기 위해 적극적으로 제품을 홍보하고 유입시킨다. 이로 인해 커뮤니티가 활성화된다고 한다.(이걸 커뮤니티 활성화로 보는게 맞는지..??)
2. 고객 지원 강화: 실시간 채팅(디스코드)이나 이메일을 통해 신속한 문제 해결을 지원한다. &quot;제품을 빠르게 개선하기 위해서는 고객한테 직접 문제점을 들어야 합니다. 그것만큼 나은 피드백은 없죠.&quot;</em></p>
<blockquote>
<p>유저를 초대하면 크레딧이나 쿠폰을 주는 서비스를 굉장히 많다. 항상 왜 이렇게 할까? 의문점을 가졌었는데 이 아티클을 통해 답변을 얻었다. 이러한 초대가 결국 입소문을 내고, 커뮤니티가 활성화되는 선순환을 만드는 중요한 마케팅 방법이라고 느꼈다.</p>
</blockquote>
<h3 id="3-교훈">3. 교훈</h3>
<h4 id="31-실행의-중요성">3.1. 실행의 중요성</h4>
<p><em>주저하지 않는 행동력: 아이디어가 떠오르면 곧바로 실행에 옮겨야 한다.
지속적인 개선: 실행 후 피드백을 수렴하고 지속적으로 빠르게 개선해야 한다.</em></p>
<h3 id="4-지속적인-학습과-적용">4. 지속적인 학습과 적용</h3>
<p><em>계속되는 자기 개발: 새로운 기술과 지식을 습득하기 위해 노력했다.
시장 트렌드 분석: 업계의 변화와 사용자 요구를 지속적으로 모니터링했다.</em></p>
<blockquote>
<p>컴퓨터공학과이면서 개발자가 되려고 늘 노력해왔기에 지속적인 학습과 성장은 정말정말 잘할 자신이 있다. 창업을 할 때도 이러한 장점을 최대한 활용해야겠다.</p>
</blockquote>
<h3 id="5-네트워킹과-협업">5. 네트워킹과 협업</h3>
<p><em>커뮤니티 참여: 업계 관련 이벤트나 온라인 커뮤니티에 적극 참여했습니다.
파트너십 구축: 다른 기업이나 개인과의 협업을 통해 시너지 효과를 창출했습니다.</em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[창업 아티클 독후감] 단 10일 만에 AI 제품을 만들어 성공한 비개발자 출신 1인 창업가]]></title>
            <link>https://velog.io/@muok_1005/%EC%B0%BD%EC%97%85-%EC%95%84%ED%8B%B0%ED%81%B4-%EB%8F%85%ED%9B%84%EA%B0%90-%EB%8B%A8-10%EC%9D%BC-%EB%A7%8C%EC%97%90-AI-%EC%A0%9C%ED%92%88%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4-%EC%84%B1%EA%B3%B5%ED%95%9C-%EB%B9%84%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%B6%9C%EC%8B%A0-1%EC%9D%B8-%EC%B0%BD%EC%97%85%EA%B0%80</link>
            <guid>https://velog.io/@muok_1005/%EC%B0%BD%EC%97%85-%EC%95%84%ED%8B%B0%ED%81%B4-%EB%8F%85%ED%9B%84%EA%B0%90-%EB%8B%A8-10%EC%9D%BC-%EB%A7%8C%EC%97%90-AI-%EC%A0%9C%ED%92%88%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4-%EC%84%B1%EA%B3%B5%ED%95%9C-%EB%B9%84%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%B6%9C%EC%8B%A0-1%EC%9D%B8-%EC%B0%BD%EC%97%85%EA%B0%80</guid>
            <pubDate>Tue, 08 Oct 2024 05:51:45 GMT</pubDate>
            <description><![CDATA[<p>오늘의 아티클: 이오플레닛의 <a href="https://eopla.net/magazines/21640&amp;utm_source=stibee_organic&amp;utm_medium=solonight_ai10&amp;utm_campaign=eopla_article#"> 단 10일 만에 AI 제품을 만들어 성공한 비개발자 출신 1인 창업가</a></p>
<h1 id="감명깊었던-문장">감명깊었던 문장</h1>
<ol>
<li><p><em>&#39;페르난도는 자신이 디자이너로 일하면서 느꼈던 불편함에서 사업 아이템을 찾았어요! 💡 바로 소셜미디어 캐러셀(옆으로 카드를 넘기며 보는 방식)제작에 관한 것이었죠.&#39;</em></p>
<blockquote>
<p>학생인지라 직장인들이 느끼는 불편함, 문제점을 찾는 건 쉽지 않을 것 같다. 직접 인터뷰를 해보며 찾는 것이 베스트일 것이다..!</p>
</blockquote>
</li>
<li><p><em>‘아이디어에 대한 사전 검증과 제대로 된 테스트 없이 제품을 출시한다는 것은 페르난도에게 엄청난 도전이었습니다. 그러나 다행히 출시는 비교적 성공적이었어요! 🚀 ’</em></p>
<blockquote>
<p>현재 우리 팀은 &#39;문제점 도출&#39;을 어느정도 마무리했고 &#39;가설 수립&#39;-&#39;가설 검증&#39; 싸이클을 돌리는 중이다. 강연에서 늘 듣는 말이 <strong>&#39;창업팀이 대부분 망하는 이유가 본인들이 하고 싶은 걸 했기에 망한다&#39;</strong> 이기 때문에 이 싸이클에 시간을 많이 쓰고 있다. 이 문장을 읽으니 어쩌면 싸이클을 돌리기보다는 무작정 출시를 해보고 부딪히면서 배우는 방식도 큰 경험을 할 것이라고 느꼈다. 일단 무작정 만들어 출시해볼까?!?!</p>
</blockquote>
</li>
<li><p><em>&#39;6개월 또는 1년 동안 무언가를 작업한 후에 사람들이 &quot;이봐요, 당신의 제품은 쓰레기에요.&quot;라고 말하는 것을 상상해 보세요. 하지만 몇 주 동안만 프로젝트를 진행한다면(10일은 좀 짧을 수 있음) 더 많은 시간을 투자할 가치가 있는지 시험해 볼 수 있습니다. 성공하지 못하더라도 다음 프로젝트에 적용할 수 있는 귀중한 교훈을 얻을 수 있습니다.&#39;</em></p>
<blockquote>
<p>이 방식이 가설 검증을 빠르게 하는 방식이라고 생각이 들었다. 전공이 컴공이라 가설 검증 아이템이 소프트웨어라면 빠른  시일 내에 만들도록 시도는 해보겠는데, 소프트웨어가 아닌 다른 분야라면 어떻게 해야할지 막막하다... 이 또한 부딪혀보며 배우는 게 최선일 것 같다..!</p>
</blockquote>
</li>
<li><p><em>‘저는 “나는 훌륭한 개발자가 아니야. 나는 그저 디자이너일 뿐이야.” 라고 생각했기 때문에 사기꾼이 된 기분이 들었습니다. 이 사고방식을 바꾸는 데 시간이 좀 걸렸어요. 여러분 중 많은 분이 자기 의심과 사기꾼 증후군에 공감하실 거라고 확신합니다. 저도 공감합니다. 하지만 저는 여러분이 &quot;적절한&quot; 개발자가 될 필요는 없다는 것을 증명합니다. 여러분에게 필요한 것은 사람들이 유용하다고 생각하는 아이디어와 그것을 끝까지 해내려는 결의뿐입니다.’</em></p>
<blockquote>
<p>3번의 느낀점에 대한 일침인 것 같다. 내가 해야하는 것은 여러 수단들(Ex: 프로그래밍, 마케팅 등등)을 통해 <strong>사람들이 유용하다고 생각하는 아이디어와 그것을 끝까지 해내려는 결의</strong>를 갖고 끊임없이 실패하고 도전해야 한다고 느꼈다. </p>
</blockquote>
</li>
<li><p><em>&#39;마케팅을 싫어하세요? 그렇다면 무료 도구를 만들어보세요!&#39;</em></p>
</li>
<li><p><em>&#39;그는 디자이너로서 자신이 가장 잘할 수 있는 것을 사업 아이템으로 선정하고 부족한 기술력은 OpenAI의 API 등을 이용하여 영리하게 보완했어요. 마케팅에 있어 그가 상대적으로 자신 없는 글쓰기보다 무료도구에 시간을 투자해 트래픽을 끌어낸 것도 같은 맥락이죠. 비 개발자 출신인 그가 10일 만에 제품을 만들어 성공할 수 있었던 큰 이유 중 하나는 이처럼 자신이 할 수 있는 것과 없는 것을 명확히 구별해내는 능력 덕분이 아닐까 합니다.&#39;</em></p>
<blockquote>
<p>자신이 할 수 있는 것과 없는 것을 명확히 구분하는 능력도 필요하다고 느꼈다.</p>
</blockquote>
</li>
</ol>
<h1 id="총평">총평</h1>
<h3 id="앞으로-나는">앞으로 나는!!</h3>
<ol>
<li>때로는 프로덕트를 만들어서 빠르게 출시를 해보며 직접 경험을 해보려고 한다!!</li>
<li>내가 할 수 있고 잘 할 수 있는 것, 할 수 없는 것을 명확하게 정리해보자!!</li>
<li>내가 하고 싶은 걸 빠르게 해보며 성공하면 개이득, 실패하면 교훈을 얻는 도전을 해보려고 한다!! 왜냐?? 말로만 듣는 것보단 직접 몸으로 경험하면 더 뼈저리게 느껴질 것 같기 때문이다!! <del>약간 회초리 먼저 맞기</del></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[창업 아티클 독후감] 리텐션 80%를 만든 A/B 테스트 팁]]></title>
            <link>https://velog.io/@muok_1005/%EC%B0%BD%EC%97%85-%EC%95%84%ED%8B%B0%ED%81%B4-%EB%8F%85%ED%9B%84%EA%B0%90-%EB%A6%AC%ED%85%90%EC%85%98-80%EB%A5%BC-%EB%A7%8C%EB%93%A0-AB-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%8C%81</link>
            <guid>https://velog.io/@muok_1005/%EC%B0%BD%EC%97%85-%EC%95%84%ED%8B%B0%ED%81%B4-%EB%8F%85%ED%9B%84%EA%B0%90-%EB%A6%AC%ED%85%90%EC%85%98-80%EB%A5%BC-%EB%A7%8C%EB%93%A0-AB-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%8C%81</guid>
            <pubDate>Mon, 07 Oct 2024 14:28:52 GMT</pubDate>
            <description><![CDATA[<p>오늘의 아티클: 이오플레닛의 <a href="https://disquiet.io/@williamjung/makerlog/%EC%88%98%EB%A9%B4-%EC%95%B1%EC%9D%84-%EB%A7%8C%EB%93%9C%EB%8A%94-%EB%AC%B4%EB%8B%88%EC%8A%A4%EA%B0%80-%EC%9E%A0%EC%9D%84-%EC%9E%90%EC%A7%80-%EC%95%8A%EB%8A%94-%EC%9D%B4%EC%9C%A0"> 리텐션 80%를 만든 A/B 테스트 팁</a></p>
<h1 id="감명깊었던-문장">감명깊었던 문장</h1>
<ol>
<li><p><em>&#39;직군을 나눠 일하는 것보다 모두가 각자의 장점을 살려 효율적으로 가설검증을 빠르게 하는 것이 중요해요&#39;</em></p>
<blockquote>
<p>직군에 얽매이게 되면 자신의 역량과 한계를 직군이라는 틀 안에서만 평가하게 된다. 안정적이고 체계적인 기업이라면 이게 옳다. 하지만, 빠르고 효율적으로 가설검증을 해야하는 스타트업에서는 직군이라는 틀에 갇히는 걸 주의해야 한다고 느꼈다. <strong>즉, 목표 달성을 위해 직군의 경계없이 효과적으로 일할 수 있어야 한다.</strong></p>
</blockquote>
</li>
<li><p><em>‘가설과 직관이 충돌할 때 고객의 니즈는 실험만이 알려줄 수 있다는 말씀이시네요. ’</em></p>
<blockquote>
<p>우리는 모든 것을 예상할 수 없다. 우리가 생각한 결과는 A or B인데, 실험을 해보니 생각치도 못한 C라는 결과가 나올 수도 있다는 것이다. 결론은 실험만이 고객의 니즈를 알 수 있으니, 실험의 싸이클을 빠르게 돌려야 한다는 것이다.</p>
</blockquote>
</li>
<li><p><em>‘HR 컨설턴트 분이 저희 회의를 보시고 ‘삼성에서는 3개월 동안 할 의사결정들이 여기서는 2시간 안에 일어나는게 대단하네요’라는 피드백을 주셨어요.</em></p>
<blockquote>
<p>활발환 소통으로 빠른 의사결정을 냄으로써 의사결정에 많은 리소스를 낭비하면 안되겠다고 느꼈다. 확실히 큰 기업은 &#39;절차&#39;가 존재하고 이 때문에 비효율적일 수도 있을 것 같다. 반면에 스타트업은 빠른 의사결정으로 빠른 변화를 가져오는 스타트업만의 장점을 살릴 수 있다고 느꼈다.</p>
</blockquote>
</li>
<li><p><em>‘그래서 지금은 1인 1PM 체제로 바꿨습니다. 매주 각자 진행할 실험과 진행한 실험에 대해 공유하고 소통하는 시간을 가지면서 개발자의 도움이 필요하면 요청하기도 하고, 실험에 대한 피드백도 주는 것이죠.’</em></p>
<blockquote>
<p>이런 식으로 빠르게 실험하고 실험에 대해 소통하는 업무를 팀원 모두가 한다면 유저의 니즈를 파악하고 그에 맞게 피보팅을 빠르게 할 수 있다고 느꼈다. </p>
</blockquote>
</li>
</ol>
<h1 id="총평">총평</h1>
<h3 id="앞으로-나는">앞으로 나는!!</h3>
<ol>
<li>각자의 장점을 살려 <strong>가설 수립-가설 검증 싸이클을 빠르게</strong> 돌려볼 것이다!!</li>
<li>싸이클을 돌린 결과들과 활발한 소통으로 <strong>빠른 의사결정을 통해 빠르게 행동</strong>하여 추진력을 얻을 것이다!!</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Github Actions, Jenkins로 CI/CD를 도전해보자! 4편 ( Private 레포지토리로 변경 및 웹훅 설정)]]></title>
            <link>https://velog.io/@muok_1005/Github-Actions-Jenkins%EB%A1%9C-CICD%EB%A5%BC-%EB%8F%84%EC%A0%84%ED%95%B4%EB%B3%B4%EC%9E%90-4%ED%8E%B8-Private-%EB%A0%88%ED%8F%AC%EC%A7%80%ED%86%A0%EB%A6%AC%EB%A1%9C-%EB%B3%80%EA%B2%BD%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@muok_1005/Github-Actions-Jenkins%EB%A1%9C-CICD%EB%A5%BC-%EB%8F%84%EC%A0%84%ED%95%B4%EB%B3%B4%EC%9E%90-4%ED%8E%B8-Private-%EB%A0%88%ED%8F%AC%EC%A7%80%ED%86%A0%EB%A6%AC%EB%A1%9C-%EB%B3%80%EA%B2%BD%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 06 Oct 2024 07:13:01 GMT</pubDate>
            <description><![CDATA[<h2 id="1-why">1. Why?</h2>
<p>진행 중인 프로젝트(<a href="https://www.withmorning.site/">윗모닝</a>)를 앱스토어에 출시하려 합니다. 이때 보안상의 이유로 레포지토리를 Private로 수정했더니 Jenkins에서 해당 레포지토리에 접근 권한이 없어 build를 못하는 상황이 발생했습니다. 이를 해결하고 기록하고자 작성합니다.</p>
<h2 id="2-personal-access-token-생성">2. Personal access token 생성</h2>
<blockquote>
<p>Personal access token은 GitHub API 또는 명령줄을 사용할 때 암호를 통해 GitHub에 인증하는 대신 사용할 수 있는 대안입니다. </p>
</blockquote>
<p>즉, 일반 암호보다는 더 보안적인 수단인거죠.</p>
<p>Developer settings &gt; Personal access tokens &gt; 생성 클릭
<img src="https://velog.velcdn.com/images/muok_1005/post/72c7cffc-1567-4314-844c-9bf329f86890/image.png" alt=""></p>
<p>repo, admin:org, admin:repo_hook 을 체크해줍니다.
<img src="https://velog.velcdn.com/images/muok_1005/post/18a02807-e0cb-4d60-bd43-ee40cdee418d/image.png" alt="">생성하고 나온 token 번호는 복사해서 저장해둡니다!</p>
<h2 id="3-organization의-access-token-정책-변경">3. Organization의 Access Token 정책 변경</h2>
<p>저는 개인 레포지토리가 아닌 Organization의 레포지토리에 접근해야하므로 아래 사진처럼 Organization의 Access Token 정책을 변경해줘야 합니다.</p>
<p>나의 Personal access token이 Fine-grained 인지 Tokens(classic)인지는 본인의 Developer Settings에서 볼 수 있습니다. 저의 경우는 Token(classic)입니다. (아래 사진 참고)
<img src="https://velog.velcdn.com/images/muok_1005/post/ab643045-9fd2-421e-81e4-ac9c1b38a335/image.png" alt=""> </p>
<p>때문에 Organization의 Access Token 정책을 아래 사진처럼 수정해줍니다. 이는 조직 구성원의 개인 액세스 토큰(클래식)을 사용하여 Git 액세스를 허용하는 것입니다.
<img src="https://velog.velcdn.com/images/muok_1005/post/17708dc1-7194-4209-81d9-702701eddfc5/image.png" alt=""></p>
<h2 id="3-jenkins-credential-생성">3. Jenkins Credential 생성</h2>
<p>github account에 필요한 Credential이 필요합니다.</p>
<p><strong>Kind: Username with password
Scope: Global
Username: &lt;깃헙 account name&gt;    -&gt; 실제 깃헙에서 사용하고 있는 이름입니다.
Password: &lt;아까 생성한 Personal access token 값&gt;   -&gt; token 을 생성했으니 pw 대신 token이 들어가야 합니다.</strong>
<img src="https://velog.velcdn.com/images/muok_1005/post/ecbb9762-5f5c-406c-9647-3e45f4651d3e/image.png" alt=""></p>
<h2 id="4-스크립트-작성">4. 스크립트 작성</h2>
<ul>
<li><p>기존 스크립트</p>
<pre><code class="language-bash">stage(&#39;Git Clone&#39;) {
          steps {
                git branch: &#39;develop&#39;, 
                url: &#39;https://github.com/2024-Saphy/BE.git&#39;
            }
        }</code></pre>
</li>
<li><p>수정된 스크립트</p>
<pre><code class="language-bash">stage(&#39;Git Clone&#39;) {
          steps {
              git(
                  credentialsId: &#39;github-signin&#39;,
                  branch: &#39;develop&#39;,
                  url: &#39;https://github.com/WithMorning/Backend.git&#39;
              )
          }
      }</code></pre>
</li>
</ul>
<h2 id="5-마무리">5. 마무리</h2>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/aa8f6914-21e9-427b-8b5e-015cbf0d6cf1/image.png" alt=""></p>
<p>큰 흐름을 정리해보면 다음과 같습니다.</p>
<ol>
<li>여러 권한(repo, admin:org, admin:repo_hook)을 가지는 개인용 Personal Access Token을 생성</li>
<li>Organization의 Access Token 정책을 변경</li>
<li>Jenkins Credential 생성하고 스크립트를 수정</li>
</ol>
<p>참고 사이트: <a href="https://jenakim47.tistory.com/73">Jenkins Pipeline Github Private Token 사용하여 연동</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[창업 아티클 독후감] 개발자가 100억을 버는 가장 빠른 방법]]></title>
            <link>https://velog.io/@muok_1005/%EC%B0%BD%EC%97%85-%EC%95%84%ED%8B%B0%ED%81%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-100%EC%96%B5%EC%9D%84-%EB%B2%84%EB%8A%94-%EA%B0%80%EC%9E%A5-%EB%B9%A0%EB%A5%B8-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@muok_1005/%EC%B0%BD%EC%97%85-%EC%95%84%ED%8B%B0%ED%81%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-100%EC%96%B5%EC%9D%84-%EB%B2%84%EB%8A%94-%EA%B0%80%EC%9E%A5-%EB%B9%A0%EB%A5%B8-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Sun, 06 Oct 2024 05:27:59 GMT</pubDate>
            <description><![CDATA[<p>오늘의 아티클: 이오플레닛의 <a href="https://eopla.net/magazines/12493#">개발자가 100억을 버는 가장 빠른 방법</a></p>
<h1 id="감명깊었던-문장">감명깊었던 문장</h1>
<ol>
<li><p>&#39;<em>대기업과 전략적 투자 관계를 맺기로 하셨다. 주요 임원진까지 나서서 믿음을 심어줬다. 그러나 *</em>결론은 배신*<em>이었다. 투자 결정은 철회되었고 기술은 그대로 도용당했다.</em>’</p>
<blockquote>
<p>교내 특허 강의를 수강할 때도 대기업의 횡포에 대해 알게 됐는데 개인 사업자 입장에서는 넘기 힘든 벽인 것 같다.</p>
</blockquote>
</li>
<li><p><em>‘본인의 사례를 직접 공유하는 대표님들이 꽤 계신다. 그중에서 가장 생생히 표현한 분을 꼽으라면 역시 인프랩 이형주 대표님이 아닐까’</em></p>
<blockquote>
<p>출처: <a href="https://www.hyungjoo.me/%EC%9D%B8%ED%94%84%EB%9E%A9-%EC%9E%AC%EB%AC%B4%EC%A0%81-log-1/">인프랩 스타트업 투자 재무적 Log</a> 를 읽어보자!!</p>
</blockquote>
</li>
<li><p><em>‘정부 지원금으로 대출 자금을 포함해서 지원받은 금액만 1억에 달한다. 하지만 페이퍼워크에 주객이 전도되고, <strong>고객이 아니라 사업계획서와 제품의 완성도에 몰두하는 내 모습이 너무 답답했다</strong>. 이러려고 사업을 시작한 건 아니잖나.’</em></p>
<blockquote>
<p>정부지원금에 몰두하여 본질을 잃어버리는 실수를 하지 않도록 늘 경계해야 겠다.</p>
</blockquote>
</li>
<li><p><em>‘정말 간절하다면 악착같이 버텨서 꿈을 이뤄내길 바란다. <strong>세상은 불공평하다. 시련은 몰아서 온다. 온갖 억지를 부리는 역경을 다 박살 내고 반드시 성공에 이르길 진심으로 응원</strong>한다.’</em></p>
<blockquote>
<p>아직 현실에 부딪혀보지는 않았지만 시련을 겪을 때마다 이 문구를 되새겨야겠다. </p>
</blockquote>
</li>
</ol>
<h1 id="총평">총평</h1>
<p><strong>여기서 강조하는 점은 100억을 버는 방법은 결국 스타트업을 해야 한다는 것이다. 그 과정에서 수많은 시련들이 있을 것이고, 이를 이겨내고 포기하지 않아야 100억을 벌 수 있다는 것이다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[창업 아티클 독후감] "달리세요. 움직이면 인생이 바뀝니다"]]></title>
            <link>https://velog.io/@muok_1005/%EC%B0%BD%EC%97%85-%EC%95%84%ED%8B%B0%ED%81%B4-%EB%8F%85%ED%9B%84%EA%B0%90-%EB%8B%AC%EB%A6%AC%EC%84%B8%EC%9A%94.-%EC%9B%80%EC%A7%81%EC%9D%B4%EB%A9%B4-%EC%9D%B8%EC%83%9D%EC%9D%B4-%EB%B0%94%EB%80%9D%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@muok_1005/%EC%B0%BD%EC%97%85-%EC%95%84%ED%8B%B0%ED%81%B4-%EB%8F%85%ED%9B%84%EA%B0%90-%EB%8B%AC%EB%A6%AC%EC%84%B8%EC%9A%94.-%EC%9B%80%EC%A7%81%EC%9D%B4%EB%A9%B4-%EC%9D%B8%EC%83%9D%EC%9D%B4-%EB%B0%94%EB%80%9D%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Sun, 06 Oct 2024 04:55:17 GMT</pubDate>
            <description><![CDATA[<p>오늘의 아티클: 이오플레닛의 <a href="https://eopla.net/magazines/21462&amp;utm_source=stibee_organic&amp;utm_medium=josh_solidcore&amp;utm_campaign=eopla_article#"> &quot;달리세요. 움직이면 인생이 바뀝니다&quot; 1140억 번 여성 창업가의 조언</a></p>
<h1 id="감명깊었던-문장">감명깊었던 문장</h1>
<ol>
<li><p><em>처음에 쉼터 운영자에게 이메일을 보냈지만, 그는 &#39;노숙자들에겐 더 큰 문제가 있어 달리기에 관심 없을 것&#39;이라고 거절했죠. 하지만 저는 이게 제 소명이라고 믿었어요. *</em>그래서 끈질기게 만남을 요청*<em>했습니다.</em>’</p>
<blockquote>
<p>끈질기게 물고 늘어져야지만 첫 단추를 끼울 수 있다고 느꼈다.</p>
</blockquote>
</li>
<li><p><em>‘솔직히 말하면, <strong>미디어는 이런 스토리를 좋아해요</strong>. &quot;금발 백인 여성이 노숙자 쉼터에 들어가 남자들과 달리기를 한다?&quot; 이런 이야기는 카메라를 끌어들이죠. ’</em></p>
<blockquote>
<p>사전인큐베이팅 교육에서도 강조했다시피 스토리가 정말 중요하다고 느꼈따. 우리도 스토리를 짜면 좋을 것 같다. (이건 아이템을 선정하게 된 과정을 좀 각색해서 만들면 좋을듯) 결국 다 기록해야 함.</p>
</blockquote>
</li>
<li><p><em>‘여러분이 정말 하고 싶은 일이 있다면, 그걸 위해 <strong>준비만 하지 말고 지금 당장 시작</strong>하세요. <strong>사업은 사업을 하면서 배우는 거</strong>예요. 때로는 빠르게 시작하고, <strong>실제 경험을 통해 배우는 게 더 효과적</strong>일 수 있습니다.’</em></p>
<blockquote>
<p>여태 책상에만 앉아서 브레인스토밍을 했던 나에 대해 반성하게 됐다… 그래서 이번에는 직접 수원역에 뛰쳐나가서 사람들을 관찰하고, 창업 아이템을 발굴할 것이다.</p>
</blockquote>
</li>
<li><p><em>‘<strong>데이터 기반 접근</strong>이 빠른 의사결정과 성장의 핵심이었습니다.’</em></p>
<blockquote>
<p>운영하면서 생기는 모든 데이터는 기록을 해서 데이터로써 활용해야 한다고 느꼈다.</p>
</blockquote>
</li>
</ol>
<h1 id="총평">총평</h1>
<p>솔직히 크게 와닿지는 않는다. 왜냐?? 지금 우리의 단계는 창업의 극극초반 시기이다. 그렇기에 창업 극초반의 내용을 다뤄주고 그 과정에서의 시련과 경험, 느낀 점, 교훈을 알려줬으면 더 좋았을듯. </p>
<p><strong>여기서 배울 점은 ‘노숙자들과 함께 달리기를 하며 그들의 삶을 변화시킨다’ 라는 명확한 비전을 갖고, 탄탄한 스토리와 데이터를 통해 투자자를 끌었다는 것이라고 생각함.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[에러 해결] BeanCreationException: Error creating bean with name 'handlerExceptionResolver' defined in class path resource]]></title>
            <link>https://velog.io/@muok_1005/%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0-BeanCreationException-Error-creating-bean-with-name-handlerExceptionResolver-defined-in-class-path-resourceorg.springframework.beans.factory.BeanCreationException-Error-creating-bean-with-name-handlerExceptionResolver-defined-in-class</link>
            <guid>https://velog.io/@muok_1005/%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0-BeanCreationException-Error-creating-bean-with-name-handlerExceptionResolver-defined-in-class-path-resourceorg.springframework.beans.factory.BeanCreationException-Error-creating-bean-with-name-handlerExceptionResolver-defined-in-class</guid>
            <pubDate>Sat, 21 Sep 2024 07:08:22 GMT</pubDate>
            <description><![CDATA[<h2 id="에러-발생-코드">에러 발생 코드</h2>
<pre><code class="language-java">@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(&quot;ErrorLogger&quot;);
    private static final String LOG_FORMAT_INFO = &quot;\n[🔵INFO] - ({} {})\n(id: {}, role: {})\n{}\n {}: {}&quot;;
    private static final String LOG_FORMAT_WARN = &quot;\n[🟠WARN] - ({} {})\n(id: {}, role: {})&quot;;
    private static final String LOG_FORMAT_ERROR = &quot;\n[🔴ERROR] - ({} {})\n(id: {}, role: {})&quot;;

    @ExceptionHandler(SaphyException.class)
    public ApiResponse&lt;Void&gt; handle(SaphyException exception, HttpServletRequest request) {
        logInfo(exception, request);

        return new ApiResponse&lt;&gt;(exception);
    }

    private void logInfo(SaphyException exception, HttpServletRequest request) {
        log.info(LOG_FORMAT_INFO, request.getMethod(), request.getRequestURI(), exception.getErrorCode(), exception.getClass().getName(), exception.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class) // MethodArgumentNotValidException 예외가 발생했을 때 아래 메소드를 실행
    public String processValidationError(MethodArgumentNotValidException exception) {
        BindingResult bindingResult = exception.getBindingResult();

        StringBuilder builder = new StringBuilder();
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            builder.append(&quot;[&quot;);
            builder.append(fieldError.getField());
            builder.append(&quot;]의 값이 잘못됐습니다. &quot;);
            builder.append(&quot;입력된 값: [&quot;);
            builder.append(fieldError.getRejectedValue());
            builder.append(&quot;]&quot;);
        }

        return builder.toString();
    }

}</code></pre>
<h2 id="발생한-에러">발생한 에러</h2>
<pre><code>org.springframework.beans.factory.BeanCreationException: Error creating bean with name &#39;handlerExceptionResolver&#39; defined in class path resource 
[org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Failed to instantiate 
[org.springframework.web.servlet.HandlerExceptionResolver]: Factory method &#39;handlerExceptionResolver&#39; threw exception with message: 
Ambiguous @ExceptionHandler method mapped for [class org.springframework.web.bind.MethodArgumentNotValidException]: 
{public java.lang.String saphy.saphy.global.handler.GlobalExceptionHandler.processValidationError(org.springframework.web.bind.MethodArgumentNotValidException), public final 
org.springframework.http.ResponseEntity org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler.handleException(java.lang.Exception,
org.springframework.web.context.request.WebRequest) throws java.lang.Exception}</code></pre><h2 id="에러-발생-원인">에러 발생 원인</h2>
<p><code>@ExceptionHandler(MethodArgumentNotValidException.class)</code> 메서드가 중복되어 발생하는 문제입니다. </p>
<p>구체적으로, 상속받은 <code>ResponseEntityExceptionHandler</code> 클래스는 이미 <code>MethodArgumentNotValidException</code>을 포함한 여러 일반적인 예외들에 대한 처리 메서드를 구현하고 있습니다. </p>
<p>따라서 <code>GlobalExceptionHandler</code>가<code>ResponseEntityExceptionHandler</code>를 상속받고 있는 상황에서, 동일한 예외에 대한 <code>@ExceptionHandler(MethodArgumentNotValidException.class)</code> 메서드를 추가로 정의하면 중복 처리 문제가 발생하는 것입니다.</p>
<h2 id="에러-해결하기">에러 해결하기</h2>
<p><code>MethodArgumentNotValidException</code>을 처리하는 메서드를 <code>ResponseEntityExceptionHandler</code>의 <code>handleMethodArgumentNotValid</code> 메서드를 오버라이드하는 것으로 변경하면 됩니다. 이렇게 하면 Spring의 기본 예외 처리와 충돌하지 않기 때문이죠.</p>
<pre><code class="language-java">@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(&quot;ErrorLogger&quot;);
    private static final String LOG_FORMAT_INFO = &quot;\n[🔵INFO] - ({} {})\n(id: {}, role: {})\n{}\n {}: {}&quot;;
    private static final String LOG_FORMAT_WARN = &quot;\n[🟠WARN] - ({} {})\n(id: {}, role: {})&quot;;
    private static final String LOG_FORMAT_ERROR = &quot;\n[🔴ERROR] - ({} {})\n(id: {}, role: {})&quot;;

    @ExceptionHandler(SaphyException.class)
    public ApiResponse&lt;Void&gt; handle(SaphyException exception, HttpServletRequest request) {
        logInfo(exception, request);

        return new ApiResponse&lt;&gt;(exception);
    }

    private void logInfo(SaphyException exception, HttpServletRequest request) {
        log.info(LOG_FORMAT_INFO, request.getMethod(), request.getRequestURI(), exception.getErrorCode(), exception.getClass().getName(), exception.getMessage());
    }

    // ------ 수정된 부분 --------
        @Override
    protected ResponseEntity&lt;Object&gt; handleMethodArgumentNotValid(
        MethodArgumentNotValidException ex,
        HttpHeaders headers,
        HttpStatusCode status,
        WebRequest request) {

        BindingResult bindingResult = ex.getBindingResult();
        StringBuilder builder = new StringBuilder();
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            builder.append(&quot;[&quot;);
            builder.append(fieldError.getField());
            builder.append(&quot;]의 값이 잘못됐습니다. &quot;);
            builder.append(&quot;입력된 값: [&quot;);
            builder.append(fieldError.getRejectedValue());
            builder.append(&quot;]&quot;);
        }

        ApiResponse&lt;String&gt; apiResponse = new ApiResponse&lt;&gt;(builder.toString());
        return new ResponseEntity&lt;&gt;(apiResponse, headers, status);
    }

    // ------ 수정된 부분 --------

}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[에러 해결] java.lang.IllegalArgumentException: Could not resolve placeholder 에러 해결하기]]></title>
            <link>https://velog.io/@muok_1005/%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0-java.lang.IllegalArgumentException-Could-not-resolve-placeholder-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@muok_1005/%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0-java.lang.IllegalArgumentException-Could-not-resolve-placeholder-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 13 Aug 2024 09:49:27 GMT</pubDate>
            <description><![CDATA[<h2 id="1-why">1. Why?</h2>
<p>이 에러 때문에 5시간을 소비했기에 너무너무 아까워서 기록하고자 합니다. <del>(너무 허무해서 잊을 수 없다...)</del>
또한 application.yml에 대해 너무나도 무지했다고 느껴 반성하고자 작성합니다....</p>
<h2 id="2-에러-발생">2. 에러 발생</h2>
<p><em><strong>org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name <del>~
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name ~</del>
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder ~~</strong></em></p>
<p>에러가 발생...
그래서 *<em>IllegalArgumentException: Could not resolve placeholder *</em> 라는 키워드로 구글링을 했습니다.</p>
<h2 id="3-에러-원인">3. 에러 원인</h2>
<p>이 에러는 Spring이 <code>application.yml</code> 파일에서 <code>${oauth2.provider.apple.client.id}</code>와 같은 플레이스홀더를 해석하지 못해서 발생합니다. 보통 이런 문제는 다음과 같은 이유로 발생할 수 있다고 하네요.</p>
<ol>
<li><p><strong><code>application.yml</code> 파일에 올바르게 정의되지 않았거나 경로가 잘못되는 경우</strong>
<code>application.yml</code> 파일에 해당 키가 없거나, 정확한 경로가 잘못되었음.</p>
</li>
<li><p><strong>플레이스홀더가 올바르게 설정되지 않은 경우</strong>
<code>@Value</code> 어노테이션에서 사용한 플레이스홀더가 <code>application.yml</code> 파일에서 정확히 매칭되지 않음.</p>
</li>
</ol>
<p>저의 경우는 1번 경로가 잘못된 경우입니다.....</p>
<h2 id="4-에러-해결">4. 에러 해결</h2>
<p>일단 @Value를 사용한 코드는 다음과 같습니다.</p>
<pre><code class="language-java">@Component
public class AppleOauthProvider implements OauthProvider {

    private final String clientId;
    private final String teamId;
    private final String keyId;
    private final String keyPath;

    private static final String APPLE_JWKS_URL = &quot;https://appleid.apple.com/auth/keys&quot;;

    public AppleOauthProvider(
        @Value(&quot;${oauth2.provider.apple.client.id}&quot;) final String clientId,
        @Value(&quot;${oauth2.provider.apple.team.id}&quot;) final String teamId,
        @Value(&quot;${oauth2.provider.apple.key.id}&quot;) final String keyId,
        @Value(&quot;${oauth2.provider.apple.key.path}&quot;) final String keyPath
    ) {
        this.clientId = clientId;
        this.teamId = teamId;
        this.keyId = keyId;
        this.keyPath = keyPath;
    }

    // 생략
}</code></pre>
<ul>
<li>기존 application.yml 코드<pre><code class="language-yaml">spring:
# .env import
config:
  import: optional:file:.env[.properties]
oauth2:
  provider:
    apple:
      auth:
        token-url: https://appleid.apple.com/auth/token
      redirect:
        url: ${REDIRECT_URL}
      client:
        id: ${CLIENT_ID}
      team:
        id: ${TEAM_ID}
      key:
        id: ${KEY_ID}
        path: ../../../../AuthKey_${KEY_ID}.p8
</code></pre>
</li>
</ul>
<pre><code>
- 수정한 application.yml 코드
```yaml
spring:
  # .env import
  config:
    import: optional:file:.env[.properties]
oauth2:
  provider:
    apple:
      auth:
        token-url: https://appleid.apple.com/auth/token
      redirect:
        url: ${REDIRECT_URL}
      client:
        id: ${CLIENT_ID}
      team:
        id: ${TEAM_ID}
      key:
        id: ${KEY_ID}
        path: ../../../../AuthKey_${KEY_ID}.p8</code></pre><p>수정한 application.yml을 사용하니 에러가 해결됐습니다. 아래 사진을 보시면</p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/05d9211e-c578-435a-a3e6-b1652cbd95b4/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/bd653502-3247-44d1-8e0a-130e30a38f5a/image.png" alt=""></p>
<blockquote>
<p><strong>차이점이 보이시나요??</strong> </p>
</blockquote>
<p>바로 들여쓰기 때문에 경로 설정이 잘못된 거였습니다!! </p>
<p>그렇기에 기존 application.yml 코드라면 <code>&quot;${oauth2.provider.apple.client.id}&quot;</code>
가 아닌<code>&quot;${spring.oauth2.provider.apple.client.id}&quot;</code>가 되어야 정상 작동이 되는 것이네요!!</p>
<p>참고 사이트:
<a href="https://iksflow.tistory.com/170?category=1023449">[오류해결] org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ~</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 소켓을 사용하여 채팅앱 만들기 (1) (Node.js, MongoDB)]]></title>
            <link>https://velog.io/@muok_1005/%EC%9B%B9-%EC%86%8C%EC%BC%93%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EC%B1%84%ED%8C%85%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0-1-Node.js-MongoDB</link>
            <guid>https://velog.io/@muok_1005/%EC%9B%B9-%EC%86%8C%EC%BC%93%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EC%B1%84%ED%8C%85%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0-1-Node.js-MongoDB</guid>
            <pubDate>Mon, 12 Aug 2024 06:07:13 GMT</pubDate>
            <description><![CDATA[<p>강의를 보면서 백엔드 코드와 주석을 자세하게 남겼습니다. 프론트 코드는 &#39;코딩알려주는 누나&#39;님의 코드를 clone 했습니다. 참고하실 분들은 참고해주세요.
백엔드: <a href="https://github.com/Muokok/chat-study">https://github.com/Muokok/chat-study</a>
프론트: <a href="https://github.com/legobitna/chatapp-client">https://github.com/legobitna/chatapp-client</a></p>
<blockquote>
<p>혹시 몰라서 제가 실습 때 작성한 프론트 코드도 함께 첨부하겠습니다!
<a href="https://github.com/Muokok/chat-study-client">https://github.com/Muokok/chat-study-client</a></p>
</blockquote>
<h2 id="1-why">1. Why?</h2>
<p>현재 진행 중인 프로젝트(UniveUS)에 채팅 기능을 추가할 예정이라 강의를 통해 학습한 내용을 정리하고자 작성한다.<a href="https://www.youtube.com/watch?v=uE9Ncr6qInQ">(참고 강의: 코딩알려주는 누나)</a></p>
<p>참고로 강의 진행 순서는 다음과 같다.</p>
<ol>
<li>백엔드 세팅: DB 세팅, 웹 소켓 세팅 &gt;&gt; (1)</li>
<li>프론트 세팅: 웹 소켓 세팅 &gt;&gt; (1)</li>
<li>백엔드, 프론트 연결 테스트 &gt;&gt; (1)</li>
<li>유저 로그인 &gt;&gt; (2)</li>
<li>메세지 주고 받기</li>
</ol>
<h2 id="2-http와-웹-소켓-프로토콜의-차이점">2. HTTP와 웹 소켓 프로토콜의 차이점</h2>
<h3 id="2-1-http-프로토콜">2-1. HTTP 프로토콜</h3>
<blockquote>
<p>HTTP는 단방향 통신이고 연결에 지속성이 없다.  </p>
</blockquote>
<p>http 프로토콜을 사용할 경우 클라이언트와 서버는 어떻게 소통을 하냐면 클라이언트가 요청할 때만 서버가 응답을 한다. 클라이언트만 대화를 시작할 수 있다. 그래서 단방향 통신이라는 것이다. 이때 클라이언트가 요청 후 서버가 응답을 했다면 둘의 연결은 끊긴다.</p>
<h3 id="2-2-웹소켓-프로토콜">2-2. 웹소켓 프로토콜</h3>
<blockquote>
<p>웹 소켓은 양방향 통신이고 연결에 지속성이 있다.</p>
</blockquote>
<p>클라이언트, 서버 모두 원할 때 요청을 할 수 있다. 실시간 채팅 기능을 구현하려면 실시간으로 정보를 주고 받아야 하므로 웹 소켓을 사용한다.   </p>
<h2 id="3-백엔드-세팅">3. 백엔드 세팅</h2>
<h3 id="3-1-라이브러리-설치">3-1. 라이브러리 설치</h3>
<pre><code>npm init -y
npm i express, mongoose, cors dotenv http
npm i socket.io 

npm i nodemon // 파일에 변화가 생기면 자동 리로딩을 해줌</code></pre><p><del>여기서 생긴 궁금증</del></p>
<p><del><strong><em>왜 http가 필요할까????????????</em></strong></del></p>
<h3 id="3-2-데이터베이스-세팅">3-2. 데이터베이스 세팅</h3>
<p>데이터베이스를 세팅한 후 파일 구조는 아래와 같다.
(더 상세한 코드는 <a href="https://github.com/Muokok/chat-study">깃허브</a>를 참고해주세요.)
<img src="https://velog.velcdn.com/images/muok_1005/post/0a3b2d52-5218-40d0-b88a-06da8b3d22d9/image.png" alt=""></p>
<ul>
<li>app.js
<img src="https://velog.velcdn.com/images/muok_1005/post/e887b594-7fa8-4f4f-885d-5b948db6d679/image.png" alt=""></li>
</ul>
<p>데이터베이스 세팅 후 <code>node app.js</code> 로 실행을 시키니 에러가 났다. MongoDB를 설치하지 않아서 생긴건가 싶어 설치 후 다시 해봤더니</p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/7619b642-3e42-4c94-a802-f4ba3c4a26ff/image.png" alt=""></p>
<p>여전히 에러가 발생한다.</p>
<p><code>MongooseServerSelectionError: connect ECONNREFUSED ::1:27017</code> 이라는 검색어로 구글링을 해봤더니 </p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/b2bb0ece-3931-45d8-8534-740b1f680267/image.png" alt="">
.env 파일의 localhost를 127.0.0.1로 바꿔주면 된다더라.</p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/ad794bc5-a60e-455c-bfd0-8c0313f64c2e/image.png" alt=""></p>
<p>정말 해결이 됐다...</p>
<p>왜 localhost가 아닌 127.0.0.1로 바꿔줘야 하는 걸까?</p>
<p>댓글을 읽어보니 </p>
<blockquote>
<p>::1:27017 부분을 보면
::1은 localhost를 IPv6 방식으로 표현한 것이라고 하네요. 아마 윈도우에서는 localhost가 IPv4 방식인 127.0.0.1이 되어야 하는대 IPv6 방식인 (0:0:0:0:0:0:0:1)가 되어서 오류가 났던거라고 추측해봅니다 !</p>
</blockquote>
<p>라고 한다. 뭔가 맞는 말 같다..!</p>
<h3 id="3-3-웹-소켓-세팅">3-3. 웹 소켓 세팅</h3>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/3b630322-c457-45d5-85db-91b08a62fec7/image.png" alt=""></p>
<p>위처럼 io.js와 index.js를 추가해줬다.</p>
<ul>
<li><p>io.js
<img src="https://velog.velcdn.com/images/muok_1005/post/ef8a87ef-0654-4f7e-b067-41c20617366e/image.png" alt=""></p>
</li>
<li><p>index.js
<img src="https://velog.velcdn.com/images/muok_1005/post/f03ba43f-cfe3-4aac-9b1f-c358a3cf8dad/image.png" alt="">
위처럼 세팅하면 웹 소켓 세팅은 끝이다. 이때 index.js에서 io.js의 io 모듈을 가져와야한다.</p>
</li>
</ul>
<h2 id="4-프론트-엔드-세팅">4. 프론트 엔드 세팅</h2>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/8c282c0a-82eb-4029-8d15-f73fc780fa9e/image.png" alt="">
파일 구조는 위와 같다. 코드는 깃허브에 올려놔주셔서 고대로 클론을 했다.</p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/6df2166d-1660-4afb-8c38-bdaa5efe700d/image.png" alt="">
추가로 해준 부분은  위 코드처럼 백엔드 서버의 주소를 연결해줄 프론트엔드 소켓을 만들어줬다.</p>
<h2 id="5-백엔드-프론트-연결-테스트">5. 백엔드, 프론트 연결 테스트</h2>
<ul>
<li><p>백엔드 코드만 실행 후 백엔드 콘솔창
<img src="https://velog.velcdn.com/images/muok_1005/post/64f22927-75e4-4e16-b644-33d77dcc7ccc/image.png" alt=""></p>
</li>
<li><p>프론트, 백엔드 코드 실행 후 백엔드 콘솔창
<img src="https://velog.velcdn.com/images/muok_1005/post/bec56e84-38e0-4688-961d-55ce95315657/image.png" alt=""></p>
</li>
</ul>
<p>웹 소켓 연결이 잘됐다! 
fg73~~ 저건 socket.io에서 연결마다 부여하는 id 값이다. 여기서 새로고침을 한다면 새로운 소켓 연결로 인식해 새로운 id 값을 부여한다. 
<img src="https://velog.velcdn.com/images/muok_1005/post/d6fd28b7-6bb1-4fc6-a56c-b15e4fb0921a/image.png" alt=""></p>
<h2 id="6-전체적인-구조-정리">6. 전체적인 구조 정리</h2>
<ol>
<li>app.js에서 mongoDB 연결</li>
<li>프론트에서 아래처럼 백엔드 서버의 주소로 연결할 수 있는 소켓을 만들어준다.
<img src="https://velog.velcdn.com/images/muok_1005/post/77521dc0-4473-4066-b4b4-7f437ef74102/image.png" alt=""></li>
<li>백엔드에서도 소켓을 연결해준다.
<img src="https://velog.velcdn.com/images/muok_1005/post/cfceb6e5-1155-4cbc-a8f6-64127c6fb43d/image.png" alt=""></li>
<li>연결 테스트를 한다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Github Actions, Jenkins로 CI/CD를 도전해보자! 3편 ( Jenkins CD 도입, 앱서버 EC2에 원격 배포, Webhook 추가하기)]]></title>
            <link>https://velog.io/@muok_1005/Github-Actions-Jenkins%EB%A1%9C-CICD%EB%A5%BC-%EB%8F%84%EC%A0%84%ED%95%B4%EB%B3%B4%EC%9E%90-3%ED%8E%B8-Jenkins-CD-%EB%8F%84%EC%9E%85-%EC%95%B1-EC2%EC%97%90-%EC%9B%90%EA%B2%A9-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@muok_1005/Github-Actions-Jenkins%EB%A1%9C-CICD%EB%A5%BC-%EB%8F%84%EC%A0%84%ED%95%B4%EB%B3%B4%EC%9E%90-3%ED%8E%B8-Jenkins-CD-%EB%8F%84%EC%9E%85-%EC%95%B1-EC2%EC%97%90-%EC%9B%90%EA%B2%A9-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Wed, 31 Jul 2024 06:07:43 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/muok_1005/post/e688434e-89a0-4f2e-a1f3-4f68eb9357ea/image.png" alt="">
이 포스트는 &#39;<a href="https://velog.io/@muok_1005/Jenkins%EB%A1%9C-CICD%EB%A5%BC-%EB%8F%84%EC%A0%84%ED%95%B4%EB%B3%B4%EC%9E%90">Github Actions, Jenkins로 CI/CD를 도전해보자! 1편</a>과 <a href="https://velog.io/@muok_1005/Github-Actions-Jenkins%EB%A1%9C-CICD%EB%A5%BC-%EB%8F%84%EC%A0%84%ED%95%B4%EB%B3%B4%EC%9E%90-2-hhi22180">Github Actions, Jenkins로 CI/CD를 도전해보자! 2편</a>&#39;의 후속편입니다. </p>
<p>CI/CD를 도입하게 된 계기, 전체적인 구조도, Github Actions를 통한 CI 테스트 내용을 원하신다면 1편을, Jenkins 및 docker 세팅, Jenkins CI 내용을 원하신다면 2편을 참고해주세요.</p>
<h2 id="1-jenkins-cd-도입하기">1. Jenkins CD 도입하기</h2>
<p>이번 프로젝트에서는 빌드, 테스트, 배포(환경 변수 설정, 권한 부여, 프로세스 종료 등) 과정을 서버용 EC2에 사람이 접속해서 설정하는 일 없도록 Jenkins로만 제어하고픈 목표가 있었습니다. 왜냐면 서버용 EC2에 접속해서 이것저것 수정하는 작업들(환경변수, 프로세스 종료 등)이 너무 귀찮았기 때문입니다.</p>
<h2 id="2-jenkins-ec2에서-앱서버-ec2로-원격-접속하기-위한-세팅">2. Jenkins EC2에서 앱서버 EC2로 원격 접속하기 위한 세팅</h2>
<p>SSH Plugin인 SSH Agent를 사용하여 Jenkins EC2 -&gt; 앱서버 EC2로 원격 접속을 할 예정입니다. 플러그인 설치 방법은 아래와 같습니다. 저는 이미 설치한 상태라 Installed plugins에 위치합니다. (아마도 이미 설치되어 있을 겁니다!)
<img src="https://velog.velcdn.com/images/muok_1005/post/1200fcd1-77ab-4234-a6f2-6115047b754e/image.png" alt=""></p>
<p>주로 PC에서 AWS EC2에 접속을 할 때는 <code>ssh -i {pem key 이름}.pem ubuntu@{ip주소}</code> 와 같은 명령어를 통해 접속하였습니다. (ppk를 이용해 Putty로 접속하는 방법도 있습니다.) </p>
<p>이러한 pem 키를 스크립트에 그대로 적는 것은 보안상 매우매우 위험하기 때문에 Jenkins에 등록하여 사용할 것입니다.
<img src="https://velog.velcdn.com/images/muok_1005/post/25061190-3259-4013-9e94-93324c531c4a/image.png" alt=""> ID는 스크립트에서 사용할 환경변수라고 생각하시면 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/267030fc-2aff-4064-99c1-7274b64e7d76/image.png" alt=""> Key 부분에 AWS EC2의 pem 키를 입력하면 됩니다. pem 키는 EC2 생성할 때 다운로드한 pem 파일에서 가져오시면 됩니다.</p>
<pre><code>-----BEGIN RSA PRIVATE KEY-----
{키 값들}
-----END RSA PRIVATE KEY-----</code></pre><p>pem 파일을 메모장으로 열면 위처럼 나올텐데, --- BEGIN --- 과 --- END --- 부분을 포함한 모든 내용을 key에 넣으시면 됩니다.</p>
<h2 id="3-cd-스크립트-작성">3. CD 스크립트 작성</h2>
<p><a href="https://velog.io/@muok_1005/Github-Actions-Jenkins%EB%A1%9C-CICD%EB%A5%BC-%EB%8F%84%EC%A0%84%ED%95%B4%EB%B3%B4%EC%9E%90-2-hhi22180">Github Actions, Jenkins로 CI/CD를 도전해보자! 2편</a>에서 작성한 CI 스크립트에 이어서 작성합니다.</p>
<pre><code>pipeline {
    agent any
    tools {
        gradle &#39;gradle&#39;
    }
    environment {
        DB_URL = credentials(&#39;db-url-credentials-id&#39;)
        DB_USER = credentials(&#39;db-user-credentials-id&#39;)
        DB_PASS = credentials(&#39;db-password-credentials-id&#39;)
    }
    stages {
        stage(&#39;Git Clone&#39;) {
            steps {
                git branch: &#39;develop&#39;, url: &#39;https://github.com/2024-Saphy/BE.git&#39;
            }
        }
        stage(&#39;BE-Build&#39;) {
            steps {
                sh &quot;chmod +x ./gradlew&quot;
                sh &quot;./gradlew clean build&quot;
            }
        }
        stage(&#39;Send Jar to remote Server&#39;) {
            steps {
                sshagent(credentials: [&#39;server-aws-key&#39;]) {
                    sh &#39;&#39;&#39;
                        ssh -o StrictHostKeyChecking=no ubuntu@x.xx.xxx uptime
                        scp /var/jenkins_home/workspace/Saphy/build/libs/saphy-0.0.1-SNAPSHOT.jar ubuntu@x.xx.xxx:/home/ubuntu/Saphy

                    &#39;&#39;&#39;
                }
            }
        }

        stage(&#39;Create .env File on remote Server&#39;) {
            steps {
                sshagent(credentials: [&#39;server-aws-key&#39;]) {
                    sh &#39;&#39;&#39;
                        ssh -o StrictHostKeyChecking=no ubuntu@x.xx.xxx &quot;
                        echo &#39;DB_URL=${DB_URL}&#39; &gt; /home/ubuntu/Saphy/.env
                        echo &#39;DB_USER=${DB_USER}&#39; &gt;&gt; /home/ubuntu/Saphy/.env
                        echo &#39;DB_PASS=${DB_PASS}&#39; &gt;&gt; /home/ubuntu/Saphy/.env
                        &quot;
                    &#39;&#39;&#39;
                }
            }
        }
        stage(&#39;Deploy&#39;) {
            steps {
                sshagent(credentials: [&#39;server-aws-key&#39;]) {
                    sh &#39;&#39;&#39;
                        ssh -o StrictHostKeyChecking=no ubuntu@x.xx.xxx &lt;&lt;&#39;EOF&#39;
                            pid=$(pgrep -f saphy)
                            echo $pid
                            if [ -n &quot;$pid&quot; ]
                            then
                                echo &quot;Stopping process $pid...&quot;
                                kill -15 $pid
                                echo &quot;kill process $pid&quot;
                                sleep 5 
                            else
                                echo &quot;no process&quot;
                            fi

                            chmod +x /home/ubuntu/Saphy/saphy-0.0.1-SNAPSHOT.jar
                            chmod +x /home/ubuntu/Saphy/.env
                            cd /home/ubuntu/Saphy
                            sudo nohup java -jar saphy-0.0.1-SNAPSHOT.jar &amp; 
        EOF
                    &#39;&#39;&#39;
                }
            }
        }
    }
}</code></pre><blockquote>
<p>2024/8/7 추가: 자동 배포를 하는 과정에서 kill 프로세스를 해줬음에도 불구하고 8080 포트 충돌이 일어나 서버가 죽는 현상이 자꾸 발생했습니다. 원인을 찾고자 Deploy 부분의 then ~ else 부분을 다음과 같이 변경하고 테스트 중입니다.</p>
</blockquote>
<pre><code>then
    echo &quot;Stopping process $pid...&quot;
    kill -15 $pid
    echo &quot;kill process $pid&quot;
    sleep 10
    if kill -0 $pid 2&gt;/dev/null; then
      echo &quot;Process still running. Sending SIGKILL...&quot;
      kill -9 $pid
      echo &quot;Sent SIGKILL to process $pid&quot; 
    fi
else</code></pre><p>2024/08/23 추가: 위처럼 수정해도 가끔씩 포트 충돌이 일어나는 현상이 발생했습니다. 그래서!! sleep 10 &gt;&gt; sleep 20으로 충분히 늘려주니 포트 충돌이 안 일어나더라구요. 이건 좀 테스트를 해보면서 적절한 sleep time을 정하는 게 좋아보입니다!</p>
<p>추가된 부분은 다음과 같습니다.</p>
<ol>
<li><p>&#39;Send Jar to remote Server&#39;
SSH agent 플러그인과 앞서 등록한 server-aws-key(pem) 키를 통해 앱 서버 EC2에 접속합니다. x.xx.xxx는 앱 서버 EC2의 ip 주소입니다.
이후 scp 명령어를 통해 Jenkins EC2에 존재하는 jar파일을 앱 서버 EC2에 복사합니다. </p>
</li>
<li><p>&#39;Create .env File on remote Server&#39; 
Jenkins credentioals에 등록한 환경 변수를 앱 서버 EC2에 .env 파일을 만들어 적어줍니다. </p>
</li>
</ol>
<ol start="3">
<li>&#39;Deploy&#39; 
앱 서버 EC2에 접속하여 jar파일을 백그라운드로 중단없이 실행합니다. 이때 앞서 실행 중인 서버 프로세스를 종료시키고 새롭게 실행해야 합니다. 그렇지 않다면 아래처럼 포트 충돌이 일어나기 때문에 새롭게 업데이트 된 jar 파일을 실행시킬 수 없고, 앞서 실행한 프로세스만 계속 실행됩니다.
<img src="https://velog.velcdn.com/images/muok_1005/post/50feb573-9135-4f42-b461-1d7baaf36143/image.png" alt=""> </li>
</ol>
<h2 id="4-환경변수-오류-해결">4. 환경변수 오류 해결</h2>
<p>CD 스크립트를 다 작성하고 빌드를 했습니다. 그런데 앱 서버 EC2에서 서버를 실행할 때 아래와 같은 DB 세팅 오류가 계속 발생... </p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/efb9ed1b-e380-45e5-9f30-dd117b5e7672/image.png" alt=""> .env파일이 생성되지 않아서 발생하는 에러인가 싶어서 확인을 해봤더니 잘 생성이 됨을 알 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/6a0b6720-f746-41fe-8203-b9160fe9b56a/image.png" alt=""></p>
<pre><code>// 디렉토리 구조
└──home
    ├── ubuntu
    │   ├── Saphy
    │       ├── saphy-0.0.1-SNAPSHOT.jar
    │       ├── .env</code></pre><p>이후 EC2에 접속해서 home/ubuntu/Saphy 디렉토리에서 <code>nohup java -jar /home/ubuntu/Saphy/saphy-0.0.1-SNAPSHOT.jar &amp;</code>(이하 jar를 실행) 로 실행시키면 분명 잘됐습니다. </p>
<p>근데 jenkins에서 스크립트로 jar를 실행시키면 DB_URL을 못찾는 에러가 발생. 원인을 찾고자 EC2에 접속해서 직접 실험을 해봤습니다.</p>
<ol>
<li><p>먼저 스크립트에 pwd를 추가하여 Jenkins가 jar를 실행시킬 때의 위치를 찾았더니 home/ubuntu였습니다. </p>
</li>
<li><p>서버용 EC2에 접속하여 home/ubuntu 디렉토리에서 jar를 실행했습니다. 이 디렉토리에서 jar를 실행시키니 바로 DB_URL 에러 발생</p>
</li>
<li><p>그래서 .env 파일이 위치한 home/ubuntu/Saphy 디렉토리로 이동하여 jar를 실행하니 정상적으로 실행</p>
</li>
</ol>
<p>그렇습니다.... jar 파일 실행 명령어를 입력하는 디렉토리 위치에 .env 파일이 존재해야 이 파일을 참고하여 환경변수를 설정해줄 수 있는 것입니다... <del>(삽질을 너무 많이 했다)</del></p>
<p>정리하자면, </p>
<ol>
<li><p>jar 파일을 실행할 때는 환경변수가 필요합니다.</p>
</li>
<li><p>저는 환경변수를 .env로 사용했고, jar 파일과 .env 파일의 디렉토리를 같은 장소(home/ubuntu/Saphy)에 위치시켰습니다. 둘의 디렉토리가 같으면 환경 변수를 참조한다고 생각했기 때문입니다.</p>
</li>
<li><p>그러나 jar 파일을 실행시켰더니 에러가 발생했습니다.</p>
</li>
<li><p>알고보니 jar 파일 실행 명령어를 입력하는 디렉토리에 .env파일이 있어야 환경변수를 제대로 먹일 수가 있는 것입니다. </p>
</li>
<li><p>home/ubuntu/Saphy 디렉토리에서 jar 파일 실행 명령어를 입력하니 정상작동되었습니다.</p>
</li>
</ol>
<blockquote>
<p>결론: jar를 실행시키는 명령어를 입력할 때의 디렉토리 위치에 .env가 존재해야 합니다. 혹시 몰라 다른 디렉토리에서 .env를 만들고 jar를 실행시켜봤더니 역시나 잘 실행됩니다. 
그렇기 때문에 &#39;Deploy&#39; step의 마지막 2번째 줄에서 .env파일이 위치한 home/ubuntu/Saphy 디렉토리로 이동하는 명령어를 추가한 것입니다.</p>
</blockquote>
<h2 id="5-github-webhook-추가하기">5. Github Webhook 추가하기</h2>
<p>여태 했던 Pipeline은 Jenkins 내에 &#39;지금 빌드&#39;를 클릭해야 가능했습니다. 이를 PR &gt;&gt; Merge가 됐을 때 자동적으로 수행하여 배포까지 하도록 Github Webhook을 걸어보겠습니다.</p>
<h3 id="5-1-jenkins에서-설정">5-1. Jenkins에서 설정</h3>
<p>이전에 생성한 Item의 Build Triggers 아래 사진처럼 설정해주면 됩니다.
<img src="https://velog.velcdn.com/images/muok_1005/post/6a96b74e-d84d-4739-a597-fd6e4f87aa6c/image.png" alt=""></p>
<h3 id="5-2-github에서-웹훅-추가">5-2. Github에서 웹훅 추가</h3>
<p>원하는 레포지토리의 Settings -&gt; 왼쪽 사이드바의 webhook -&gt; 오른쪽 위 add webhook 클릭
<img src="https://velog.velcdn.com/images/muok_1005/post/d8876d4c-c8ac-4283-a105-693608da3e23/image.png" alt="">위 사진처럼 젠킨스 접속 주소에 /github-webhook/을 추가하면 모든 과정이 끝납니다!!</p>
<p>이러면 PR -&gt; Merge가 됐을 경우 자동적으로 Jenkins에서 설정한 스크립트를 실행하게 됩니다. 완벽한 자동 빌드, 배포가 이루어 진거죠.</p>
<h2 id="6-성공">6. 성공</h2>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/ecbd7f2d-c44d-4b70-8317-b2a7efe5894d/image.png" alt=""> 무려 100번의 시도를 통해 성공할 수 있었습니다..!</p>
<h2 id="7-느낀점">7. 느낀점</h2>
<p>CI/CD를 도입하면서 다음 목표가 생겼습니다.</p>
<ol>
<li>앱 서버 또한 docker 컨테이너에 담아서 배포 도전해보기</li>
<li>무중단 배포 도전해보기</li>
</ol>
<p>추후 시리즈에 꼭 추가해보도록 하겠습니다!</p>
<p>동시에 </p>
<blockquote>
<p>퍼블릭 IP랑 포트번호 알면 Jenkins 홈페이지 들어와서 파이프라인 깽판칠 수도 있는 게 아닌가? 다행히 AWS Key라던가 깃허브 Key 등등 중요한 정보는 credential 설정으로 숨길 수 있어도 스크립트는 못 숨기기 때문에 위험할 수도 있지 않나??</p>
</blockquote>
<p>라는 궁금증이 생겼습니다.</p>
<p>생각해보니 Jenkins 계정으로 로그인해야하니 보안에는 문제가 없네요 ㅎㅎ...</p>
<p>참고 블로그:
<a href="https://seongwon.dev/DevOps/20220715-CICD%EA%B5%AC%EC%B6%95%EA%B8%B01/">[DevOps] Jenkins를 통한 CI/CD 구축기 1편 (Jenkins 설치)</a>
<a href="https://seongwon.dev/DevOps/20220717-CICD%EA%B5%AC%EC%B6%95%EA%B8%B02/">[DevOps] Jenkins를 통한 CI/CD 구축기 2편 (Backend CI/CD 구축)</a>
<a href="https://jjong2.tistory.com/70">ec2 스프링 빌드 시 멈춤 현상 해결법</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Github Actions, Jenkins로 CI/CD를 도전해보자! 2편 (Jenkins 세팅, Jenkins CI 도입)]]></title>
            <link>https://velog.io/@muok_1005/Github-Actions-Jenkins%EB%A1%9C-CICD%EB%A5%BC-%EB%8F%84%EC%A0%84%ED%95%B4%EB%B3%B4%EC%9E%90-2-hhi22180</link>
            <guid>https://velog.io/@muok_1005/Github-Actions-Jenkins%EB%A1%9C-CICD%EB%A5%BC-%EB%8F%84%EC%A0%84%ED%95%B4%EB%B3%B4%EC%9E%90-2-hhi22180</guid>
            <pubDate>Tue, 30 Jul 2024 11:20:01 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/muok_1005/post/03bd3546-f22a-4fc7-b5a8-7f227fb2c458/image.png" alt=""></p>
<p>이 포스트는 &#39;<a href="https://velog.io/@muok_1005/Jenkins%EB%A1%9C-CICD%EB%A5%BC-%EB%8F%84%EC%A0%84%ED%95%B4%EB%B3%B4%EC%9E%90">Github Actions, Jenkins로 CI/CD를 도전해보자! 1편 (Github Actions 도입하여 CI 테스트)</a>&#39;의 후속편입니다. CI/CD를 도입하게 된 계기, 전체적인 구조도, Github Actions를 통한 CI 테스트 내용을 원하신다면 1편을 참고해주세요.</p>
<h2 id="1-시작-전-세팅하기">1. 시작 전 세팅하기</h2>
<h3 id="1-1-swap-memory-설정">1-1. Swap Memory 설정</h3>
<p>앞선 포스트인 &#39;<a href="https://velog.io/@muok_1005/Jenkins%EB%A1%9C-CICD%EB%A5%BC-%EB%8F%84%EC%A0%84%ED%95%B4%EB%B3%B4%EC%9E%90">Github Actions, Jenkins로 CI/CD를 도전해보자! 1편</a>&#39; 을 보시면 아시겠지만, 프리티어 유형의 EC2에서 빌드를 진행하던 중에 EC2 멈춤 현상이 발생했습니다. 이는 메모리가 너무 적기 때문에 발생하는 현상입니다. 
그러므로!! Swap memory를 설정하여 RAM 크기를 늘려주니 해결됐습니다. 설정 방법은 다음과 같습니다.</p>
<ol>
<li>스왑 파일 생성</li>
</ol>
<pre><code>// aws에서는 count=32로 예제가 써있는데 절반인 16을 적은 이유는 권장 크기가 2GB이기 때문
$ sudo dd if=/dev/zero of=/swapfile bs=128M count=16</code></pre><ol start="2">
<li>스왑 파일 권한 설정</li>
</ol>
<pre><code>$ sudo chmod 600 /swapfile</code></pre><ol start="3">
<li>Linux 스왑 영역을 설정</li>
</ol>
<pre><code>$ sudo mkswap /swapfile</code></pre><ol start="4">
<li>스왑 공간에 스왑 파일을 추가하여 스왑 파일을 즉시 사용 가능하게 설정</li>
</ol>
<pre><code>$ sudo swapon /swapfile</code></pre><ol start="5">
<li>프로시저가 성공적인지 확인</li>
</ol>
<pre><code> $ sudo swapon -s</code></pre><ol start="6">
<li>/etc/fstab 파일을 편집하여 부팅 시 스왑 파일을 시작</li>
</ol>
<pre><code>$ sudo vi /etc/fstab</code></pre><ol start="7">
<li>파일 끝에 아래 문구 추가하고 저장</li>
</ol>
<pre><code>/swapfile swap swap defaults 0 0</code></pre><p><img src="https://velog.velcdn.com/images/muok_1005/post/61977ead-3f3d-45ed-8102-49924c7f997d/image.png" alt=""></p>
<pre><code>$ free -h</code></pre><p>위 명령어로 스왑 메모리 설정이 잘 됐는지 확인합니다.
<img src="https://velog.velcdn.com/images/muok_1005/post/12edcd1b-f83e-496d-81c5-1fbfd6a185ee/image.png" alt=""> Swap memory가 2.6GB가 된 걸 볼 수 있습니다. 성공!!!</p>
<h3 id="1-2-docker-설치">1-2. Docker 설치</h3>
<ol>
<li>apt package index 최신으로 업데이트 및 apt가 HTTPS를 통해 저장소를 사용할 수 있도록 패키지를 설치<pre><code>$ sudo apt-get update
$ sudo apt-get install \
ca-certificates \
curl \
gnupg \
lsb-release</code></pre></li>
<li>Docker의 GPG키 추가<pre><code>$ sudo mkdir -p /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg</code></pre></li>
<li>저장소 설정</li>
</ol>
<pre><code>$ echo \
 &quot;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
 $(lsb_release -cs) stable&quot; | sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null</code></pre><ol start="4">
<li>Docker Engine 설치</li>
</ol>
<pre><code>$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin</code></pre><ol start="5">
<li>Docker의 기본 이미지인 Hello-world 이미지를 통해 정상적으로 설치되었는지 테스트</li>
</ol>
<pre><code>$ sudo docker run hello-world</code></pre><p>위의 명령어로 테스트를 진행하면 됩니다!</p>
<h3 id="1-3-jenkins-컨테이너-설치">1-3. Jenkins 컨테이너 설치</h3>
<pre><code>$ sudo docker run -d --name jenkins -p 8080:8080 jenkins/jenkins:jdk17
// 프로젝트에선 jdk17을 사용했기에 jdk17입니다. </code></pre><ul>
<li>-d: 컨테이너를 데몬으로 띄운다.</li>
<li>--name: 컨테이너의 이름을 jenkins로 설정한다</li>
<li>-p 8080:8080: 컨테이너의 외부와 통신할 포트(앞의 값)를 내부적으로 사용할 포트(뒤의 값)를 포워딩해준다.</li>
</ul>
<h3 id="1-4-jenkins-설정">1-4. Jenkins 설정</h3>
<p>Jenkins 설정은 자료가 많으니 스킵하겠습니다..!</p>
<h2 id="2-jenkins로-ci-도입하기">2. Jenkins로 CI 도입하기</h2>
<p>이번 프로젝트에서는 빌드, 테스트, 배포(환경 변수 설정, 권한 부여, 프로세스 종료 등) 과정을 서버용 EC2에 사람이 접속해서 설정하는 일 없도록 Jenkins로만 제어하고픈 목표가 있었습니다. 왜냐면 서버용 EC2에 접속해서 이것저것 수정하는 작업들(환경변수, 프로세스 종료 등)이 너무 귀찮았기 때문입니다.</p>
<h3 id="2-1-jenkins-item-생성">2-1. Jenkins Item 생성</h3>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/e5666fbe-3bd7-4bbc-8564-4fa438a59dda/image.png" alt="">
이름 넣고 Pipeline을 선택하면 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/da6025bc-bc66-43f9-b203-466f2fbef28a/image.png" alt="">생성된 Item으로 들어가서 위 구성을 클릭합니다.</p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/9c5ef575-6ed2-45f0-b69f-59331c98af35/image.png" alt="">General, Advanced~ 부분은 아직은 건드리지 않고 위 사진의 Pipeline script에다가 스크립트를 작성하여 CI/CD를 커스텀할 예정입니다!</p>
<p>스크립트 작성 전에 Jenkins에 gradle을 구성해줘야 합니다. 아래 사진처럼 Item에서 설정을 하는 것이 아닌 전체 설정에서 추가해주면 됩니다.
<img src="https://velog.velcdn.com/images/muok_1005/post/0ffc1f04-e2d6-4d56-a4da-8ba57277991b/image.png" alt=""></p>
<h3 id="2-2-ci-스크립트-작성">2-2. CI 스크립트 작성</h3>
<pre><code>pipeline {
    agent any
    tools {
        gradle &#39;gradle&#39;
    }
    environment {
        DB_URL = credentials(&#39;db-url-credentials-id&#39;)
        DB_USER = credentials(&#39;db-user-credentials-id&#39;)
        DB_PASS = credentials(&#39;db-password-credentials-id&#39;)
    }
    stages {
        stage(&#39;Git Clone&#39;) {
            steps {
                git branch: &#39;develop&#39;, url: &#39;https://github.com/2024-Saphy/BE.git&#39;
            }
        }
       stage(&#39;BE-Build&#39;) {
            steps {
                sh &quot;chmod +x ./gradlew&quot;
                sh &quot;./gradlew clean build&quot;
            }
        }
    }
}</code></pre><p>스크립트를 보면 딱 느낌적인 느낌이 옵니다.</p>
<ol>
<li>build 툴은 gradle</li>
<li>environment로 환경 변수 설정해주기</li>
<li>&#39;Git Clone&#39; step에서는 원하는 저장소의 원하는 브랜치를 Clone 해오기</li>
<li>&#39;BE&#39;Build&#39; step에서는 ./gradlew에 권한 부여 및 빌드 시작</li>
</ol>
<p>이번 프로젝트에서는 application.yml의 DB 관련 정보를 숨기기 위해 아래 사진처럼 .env 파일을 이용했습니다. 
(.env를 쓰려면 꼭 yml에 .env import 부분을 추가해주셔야 합니다!!)</p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/7c165a3e-860e-4c6a-9ca2-7eb8a442a0ef/image.png" alt=""></p>
<p>Jenkins EC2에 .env를 직접 생성해줘도 되지만, .env 파일에 수정 사항이 생기면 EC2에 직접 접속해서 수정해줘야하는 점이 귀찮았습니다.</p>
<p>그렇기에 environment를 사용하여 .env에 수정 사항이 생겼을 때 Jenkins 페이지에서 credentials만 수정하는 방식이 훨씬 간편하다고 느껴 이 방식을 택했습니다. </p>
<p>credentials 생성은 아래 사진처럼 하시면 됩니다.
<img src="https://velog.velcdn.com/images/muok_1005/post/bc71e876-fb66-4a4b-a702-dc5f41577a9d/image.png" alt=""> Secret에 숨길 내용을 적으시면 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/201100f8-e1a0-426d-bc6d-613d02424bd8/image.png" alt="">이렇게 USER, URL, PASSWORD를 다 추가해주시고 스크립트에 ID를 넣으시면 됩니다. 참고로 저는 RDS를 사용했기 때문에 Jenkins EC2에 DB 세팅을 할 필요가 없었습니다. 만약 Jenkins EC2내에 로컬 DB를 쓰실 예정이라면 Jenkins EC2 내에서 DB 세팅을 해주셔야 합니다.</p>
<p>그 다음 아래 사진의 &#39;지금 빌드&#39; 를 클릭하면 스크립트대로 Pipeline이 실행됩니다. 
<img src="https://velog.velcdn.com/images/muok_1005/post/dbc533a2-36d4-4e8f-8bbf-30922efed881/image.png" alt="">그런데 젠킨스에 빌드중 아래 사진처럼 contextLoads() 메서드가 실패가 떴습니다. 분명 로컬에서는 잘 되는데 왜 젠킨스에서만 문제지?! </p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/d8cd4793-56c6-4501-a94a-74169a14ddea/image.png" alt=""> 해결 방법은 아래 사진처럼 코드를 추가해주면 됩니다. <del>(사실 정확한 원인이 뭔지 잘 모르겠습니다..)</del>
<img src="https://velog.velcdn.com/images/muok_1005/post/aea0a0f6-c333-4e84-9653-bd288de062d2/image.png" alt=""></p>
<p>여기까지는 단순히 깃허브 저장소의 develop 브랜치의 코드를 Clone 해오고 Build를 진행하는 과정입니다. </p>
<p>그런데 이 과정에서 궁금증이 생겼습니다.</p>
<blockquote>
<p>그러면 Clone한 깃허브 프로젝트 파일은 대체 어떤 디렉토리에 있는거야?!?!?! 
(정확히는 Jenkins 스크립트를 실행하는 디렉토리의 위치가 궁금한 것!!)</p>
</blockquote>
<p>일단 EC2에 접속해서 아래 명령어를 입력하여 jenkins 컨테이너를 실행시킵니다. 
(docker 컨테이너 안에 Jenkins 환경을 구축했습니다. 자세한 내용은 &#39;<a href="https://velog.io/@muok_1005/Jenkins%EB%A1%9C-CICD%EB%A5%BC-%EB%8F%84%EC%A0%84%ED%95%B4%EB%B3%B4%EC%9E%90">Github Actions, Jenkins로 CI/CD를 도전해보자! 1편</a>&#39; 을 참고해주세요!)</p>
<pre><code>$ docker exec -it jenkins bash</code></pre><p> <img src="https://velog.velcdn.com/images/muok_1005/post/9cea9104-9556-4768-a5eb-af92ffdebe7b/image.png" alt=""></p>
<p>위 사진처럼 jenkins라는 이름의 도커 컨테이너가 실행됩니다. 참고로 옆에 있는 84da94250d60은 컨테이너의 ID입니다.</p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/0c554068-7339-429d-bbee-d6063b43d7f2/image.png" alt=""> 구글링도 하고 직접 폴더에 들어가면서 찾아보니 Clone된 깃허브 프로젝트 폴더는 위 경로에 생기는 걸 알 수 있습니다! </p>
<blockquote>
<p>정리하자면 Jenkins Item의 이름과 동일한 &#39;Saphy&#39; 라는 폴더가 <code>/var/Jenkins_home/workspace</code> 에 생기고, &#39;Saphy&#39; 폴더 안에서 스크립트를 실행하게 되는 것입니다. 이전에 테스트용으로 &#39;Saphy-CI-CD&#39;라는 이름의 Item을 생성해서 빌드했기 때문에 위 사진처럼 &#39;Saphy-CI-CD&#39;라는 이름의 폴더가 존재하는 것입니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/b88bcc59-0ec7-4e25-bd58-5f7967251dd3/image.png" alt=""></p>
<p>&#39;Saphy&#39; 폴더 안에서 스크립트를 실행한 것을 위 사진에서 볼 수 있습니다.</p>
<h2 id="3-느낀점-및-다음으로">3. 느낀점 및 다음으로</h2>
<p>처음에 Jenkins를 사용할 때는 삐걱대는 경우가 많았는데, 포스트를 작성하면서 리뷰해보니 참 간단한 내용이었구나~ 라고 느껴집니다. 이처럼 포스트를 작성하며 공부했던, 프로젝트에 도입했던 내용을 작성하는 것이 배운 내용들이 정리되는 느낌이라 참 좋은 것 같습니다.</p>
<p>다음 포스트에서는 Jenkins로 CD를 하는 과정을 다루겠습니다.</p>
<p>참고 블로그:
<a href="https://seongwon.dev/DevOps/20220715-CICD%EA%B5%AC%EC%B6%95%EA%B8%B01/">[DevOps] Jenkins를 통한 CI/CD 구축기 1편 (Jenkins 설치)</a>
<a href="https://seongwon.dev/DevOps/20220717-CICD%EA%B5%AC%EC%B6%95%EA%B8%B02/">[DevOps] Jenkins를 통한 CI/CD 구축기 2편 (Backend CI/CD 구축)</a>
<a href="https://jjong2.tistory.com/70">ec2 스프링 빌드 시 멈춤 현상 해결법</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Batch란 무엇인가? (Spring Batch, Batch Processing)]]></title>
            <link>https://velog.io/@muok_1005/Batch%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-Spring-Batch-Batch-Processing</link>
            <guid>https://velog.io/@muok_1005/Batch%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-Spring-Batch-Batch-Processing</guid>
            <pubDate>Wed, 24 Jul 2024 08:12:08 GMT</pubDate>
            <description><![CDATA[<h2 id="1-why">1. Why?</h2>
<p>기상 알람 프로젝트를 진행하면서 Scheduler를 사용했습니다. 유저마다 기상 알람 시간이 천차만별이라고 예상되는데, 이를 위해 Scheduler를 매분마다 실행시켜 기상 알람을 보내는 것은 굉장한 리소스 낭비라고 생각했습니다. 
때문에 Batch 작업을 통해 하루에 보내야 할 알람들을 미리 한 번에 가져와 성능을 개선하고자 Spring Batch를 도입하기로 했습니다. 
개발에 앞서 Batch에 대한 학습 내용을 기록하고자 합니다.</p>
<h2 id="2-batch-processing이란-무엇인가">2. Batch Processing이란 무엇인가?</h2>
<p>컴퓨팅에서 여러 작업이나 데이터를 모아서 한꺼번에 처리하는 데이터 처리 방식입니다. 이는 실시간 처리와 대비되는 개념으로, 일정량의 데이터를 모아 한 번에 처리함으로써 대량의 데이터를 효율적으로 처리하고, 시스템 자원의 활용을 극대화하는 데 유용합니다. 배치 처리는 실시간 처리가 필요 없는 작업에 적합합니다.</p>
<h2 id="3-batch-processing을-왜-사용하는가">3. Batch Processing을 왜 사용하는가?</h2>
<ol>
<li><p>효율성: 대량의 데이터를 한 번에 처리함으로써 시스템 리소스를 효율적으로 사용할 수 있습니다.</p>
</li>
<li><p>비용 절감: 피크 시간(유저가 주로 사용하는 시간대)을 피해 작업을 수행함으로써 컴퓨팅 비용을 줄이고 성능 저하를 방지할 수 있습니다.</p>
</li>
<li><p>일관성: 대량의 데이터를 일괄적으로 처리, 반복적인 작업을 동일한 방식으로 수행하므로 결과가 일관됩니다.</p>
</li>
<li><p>신뢰성:  자동화된 프로세스는 휴먼 에러를 줄이고, 시스템 오류를 쉽게 감지하고 수정할 수 있습니다.</p>
</li>
<li><p>시스템 부하 관리: 실시간 처리로 인한 시스템 과부하를 방지할 수 있습니다.</p>
</li>
</ol>
<p>정리하자면, 대량의 데이터를 한 번에 처리함으로써 리소스를 효율적으로 사용할 수 있습니다. 또한 사람의 개입을 최소화하고 반복 작업을 보다 효율적으로 실행할 수 있기 때문에 일관성과 신뢰성을 얻을 수 있습니다.</p>
<h2 id="4-batch-processing이-어디에-쓰이는가">4. Batch Processing이 어디에 쓰이는가?</h2>
<ol>
<li><p>금융 서비스
신용카드 청구서 생성: 월말에 고객의 모든 거래를 집계하여 청구서를 생성합니다.</p>
</li>
<li><p>소매업
재고 관리: 매일 밤 판매 데이터를 분석하여 재고를 업데이트하고 발주 목록을 생성합니다.</p>
</li>
<li><p>인사 관리
급여 처리: 월말에 직원들의 근무 시간, 휴가, 보너스 등을 계산하여 급여를 일괄 처리합니다.</p>
</li>
<li><p>IT 운영
백업 및 아카이빙: 주기적으로 시스템 데이터를 백업하고 오래된 데이터를 아카이빙합니다.
로그 파일 정리: 시스템 로그 파일을 일정 주기마다 정리하여 디스크 공간을 확보하고, 로그 데이터를 분석합니다.</p>
</li>
</ol>
<h2 id="5-spring-batch란-무엇인가">5. Spring Batch란 무엇인가?</h2>
<p>배치 애플리케이션을 개발할 수 있도록 설계된 가볍고 포괄적인 배치 프레임워크입니다. 스프링 프레임워크 기반에서 작동합니다.</p>
<h2 id="6-그러면-batch랑-scheduler의-차이가-무엇인가">6. 그러면 Batch랑 Scheduler의 차이가 무엇인가?</h2>
<p>간단히 Batch는 작업이나 데이터를 모아서 한꺼번에 처리하는 데이터 처리 방식이고, Scheduler는 주어진 작업을 미리 정의된 시간에 실행될 수 있게 해주는 도구나 소프트웨어를 의미합니다.</p>
<h2 id="7-spring-batch의-처리-흐름-⭐⭐">7. Spring Batch의 처리 흐름 (⭐⭐)</h2>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/1fc1fc10-231d-459f-93b8-8e6e233361d2/image.png" alt="">먼저 Job이란 전체 Batch 프로세스를 나타내는 객체로 Step으로 구성되어 있습니다. </p>
<p>Step은 Job 내의 하나의 독립적인 작업 단위로, 실제 배치 처리 작업이 이루어집니다. 각 Step에서 데이터를 처리하는 방식은 Tasklet과 Chunksize가 있습니다. 
이때 Chunksize(이하 Chunk)는 ItemReader / ItemRrocessor / ItemWriter 로 구분되어 처리됩니다. </p>
<p>대량의 데이터를 처리하는 데 적합하고, 프레임워크가 효율적으로 메모리 관리를 해줄 수 있는 Chunk 방식을 프로젝트에 도입하였습니다. 때문에 Chunk 방식으로 기록하겠습니다.
(Tasklet vs Chunk 두 방식의 차이점은 따로 블로그에 작성하도록 하겠습니다.)</p>
<p>전체적인 흐름은 다음과 같습니다.
<img src="https://velog.velcdn.com/images/muok_1005/post/25ef4461-96ab-4ce2-9aa9-657974a7c8cb/image.png" alt=""></p>
<ol>
<li><p>Job 실행 요청
<code>JobLauncher</code>(Job을 실행하는 인터페이스)를 통해 Job 실행이 요청됩니다. 이때 <code>JobParameters</code>를 통해 실행에 필요한 파라미터를 전달할 수 있습니다.</p>
</li>
<li><p>Job 실행
<code>JobRepository</code> (Job의 실행 정보 <code>JobExecution</code>, Step의 실행 정보 <code>StepExecution</code>, 메타데이터 등을 저장하고 관리하는 메커니즘)에서 이전 실행 정보를 확인합니다.
Job이 실행될 때, <code>JobRepository</code>는 새로운 <code>JobExecution</code>과 <code>StepExecution</code>을 생성하고, 이를 통해 실행 상태를 추적합니다.
이후 Job의 구성요소인 Step들을 순차적으로 실행합니다.</p>
</li>
<li><p>Step 실행(Chunk방식 사용)
<img src="https://velog.velcdn.com/images/muok_1005/post/5707d504-9142-467c-b6e4-96f4035c74b4/image.png" alt=""><code>ItemReader</code>를 통한 데이터 읽기: DB, 파일 등 데이터 저장소로부터 데이터를 읽어옵니다.
<code>ItemProcessor</code>를 통한 데이터 처리: 읽어온 데이터에 대해 비즈니스 로직을 적용합니다. 그 예로 데이터 필터링, 변환, 검증 등이 있습니다.
<code>ItemWriter</code>를 통한 데이터 쓰기: 처리된 데이터를 DB, 파일 등 데이터 저장소에 저장합니다.</p>
</li>
<li><p>Chunk 처리 반복
3단계 과정을 반복하여 모든 데이터를 처리합니다. 
각 Chunk 처리가 완료될 때마다 트랜잭션이 커밋됩니다.</p>
</li>
<li><p>Step 완료
모든 Chunk 처리가 완료되면 Step이 종료됩니다.
Step의 실행 결과가 <code>JobRepository</code>에 저장됩니다.</p>
</li>
<li><p>다음 Step 실행
Job 내의 다음 Step으로 이동하여 위 3,4,5 과정을 반복합니다.</p>
</li>
<li><p>Job 완료
모든 Step이 완료되면 Job이 종료됩니다. 이때 Job의 최종 실행 결과가 JobRepository에 저장됩니다.</p>
</li>
<li><p>실행 결과 반환
JobLauncher는 Job 실행 결과를 반환합니다.</p>
</li>
</ol>
<h2 id="8-마치며">8. 마치며</h2>
<p>Spring Batch를 적용시키면 유의미한 성능 개선이 있으리라 생각듭니다. 정확히 얼마나 성능이 개선되는지는 측정을 해봐야할 것 같습니다. 다음 편에서는 프로젝트에 직접 적용시킨 내용과 어떻게 성능이 개선되었는지를 다루겠습니다.</p>
<p>참고 문서:
<a href="https://aws.amazon.com/ko/what-is/batch-processing/">AWS 공식 문서</a>
<a href="https://khj93.tistory.com/entry/Spring-Batch%EB%9E%80-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B3%A0-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0">Spring Batch란? 이해하고 사용하기(예제소스 포함)</a>
<a href="https://dkswnkk.tistory.com/707">Spring Batch란? 간단한 개념과 코드 살펴보기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Github Actions, Jenkins로 CI/CD를 도전해보자! 1편 (Github Actions 도입하여 CI 테스트)]]></title>
            <link>https://velog.io/@muok_1005/Jenkins%EB%A1%9C-CICD%EB%A5%BC-%EB%8F%84%EC%A0%84%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@muok_1005/Jenkins%EB%A1%9C-CICD%EB%A5%BC-%EB%8F%84%EC%A0%84%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sun, 21 Jul 2024 05:12:42 GMT</pubDate>
            <description><![CDATA[<h2 id="1-why">1. Why?</h2>
<p>과거 프로젝트에서 느꼈던 가장 큰 불편함이 배포 과정이었습니다. 이때의 배포 과정은 다음과 같습니다.</p>
<ol>
<li>개발이 완료된 코드가 모인 브랜치 develop에서 Build하여 Jar 파일 생성</li>
<li>Filezilla를 통해 EC2 인스턴스에 Jar 파일을 넘겨줌</li>
<li>EC2 인스턴스에 접속하여 기존 서버 프로세스를 kill</li>
<li><code>nohup java -jar [jar 이름] &amp;</code> 명령어로 프로세스를 백그라운드에서 띄워줌</li>
</ol>
<p>특히 API 통신 단계에서 개발 서버의 코드를 업데이트할 때마다 위 과정을 반복했습니다. <del>(너무너무 귀찮았다..)</del></p>
<p>때문에 이번 프로젝트에 CI/CD를 도입하게 됐고, 처음으로 CI/CD를 하는 것이기에 기록용 + 팀원들의 이해를 돕기 위해 블로그를 작성합니다.
구현 시작부터 끝까지 차근차근 모든 과정을 총 3편에 걸쳐서 적어보려고 합니다.</p>
<h2 id="2-구조도">2. 구조도</h2>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/e688434e-89a0-4f2e-a1f3-4f68eb9357ea/image.png" alt=""></p>
<h3 id="2-1-전체적인-과정">2-1. 전체적인 과정</h3>
<ol>
<li>PR을 날리면 디스코드로 알람이 간다.</li>
<li>Github Actions가 CI 테스트(빌드와 테스트 코드를 실행)를 하여 문제가 없는지 확인한다.</li>
<li>문제가 없는 코드라면 개발자가 직접 리뷰 후 Merge를 한다.</li>
<li>Merge가 된다면 Github Webhook이 발동되어 자동적으로 Jenkins를 담은 EC2에서 Clone, Build를 실행하게 된다.</li>
<li>Build한 Jar 파일을 앱 서버용 EC2에 보내고 자동적으로 서버를 가동한다.</li>
</ol>
<h3 id="2-2-의문점">2-2. 의문점</h3>
<p>그렇다면 위 과정을 보고 다음과 같은 의문점이 생길 수도 있습니다.<del>(아닐수도)</del></p>
<ol>
<li>왜 Github Actions, Jenkins만 쓰는 것이 아니라 둘 다 쓰는 것인가??</li>
</ol>
<ul>
<li><p><em>Why Github Actions???</em></p>
<blockquote>
<ul>
<li>CI의 목적은 자동 빌드 및 테스트를 진행하여 여러 개발자들이 공유하는 코드의 신뢰성을 높이는 개념이라고 생각합니다. 
이 개념을 프로젝트에 도입시킨다면 기능 개발을 마친 코드를 모두 합치는 develop 브랜치의 신뢰성을 높이는 것이라고 판단했습니다.</li>
<li>그러려면 develop 브랜치에 PR을 날렸을 때 CI 테스트를 하여 문제가 없는지부터 깃허브 홈페이지에서 확인하고, 코드 리뷰 후 Merge해야 한다고 느꼈습니다. 이 과정을 가장 쉽게 할 수 있는 CI 툴이 Github Actions라고 느껴 선택하였습니다.</li>
</ul>
</blockquote>
</li>
<li><p><em>Why Jenkins???</em></p>
<blockquote>
<ul>
<li>무엇보다 Jenkins는 높은 수준의 사용자 정의 가능성과 유연성을 제공하기 때문입니다. 이후 보여드릴 Jenkins 스크립트를 보시면 아시겠지만 저의 입맛에 맞게 커스텀을 했습니다. 이런 점이 Jenkins를 선택한 주된 이유라고 생각합니다.</li>
<li>Jenkins에는 크고 활발한 커뮤니티가 있으며 다양한 사용 사례에 사용할 수 있는 수많은 플러그인이 있습니다. </li>
<li>Jenkins는 대규모 프로젝트에 대해 확장성을 높일 수 있습니다.</li>
</ul>
</blockquote>
</li>
</ul>
<ol start="2">
<li><p>왜 도커에 Jenkins를 담아서 사용하는가?</p>
</li>
<li><p>왜 Jenkins EC2와 앱 서버 EC2를 분리하였는가?</p>
<blockquote>
<p>프리티어로 사용하기 위해 인스턴스의 유형을 t2.micro인 EC2에서 빌드를 진행했습니다. 그런데?! EC2 멈춤 현상이 발생..! 찾아보니 메모리가 1GB라서 메모리 부족으로 멈춤 현상이 발생할 수도 있다고 합니다. 
스왑 메모리를 통해 멈춤 현상은 없앴지만, 하나의 EC2에서 빌드를 진행하고 서버까지 함께 가동한다면 많은 부하가 발생할 것이기 때문에 Jenkins EC2와 앱 서버용 EC2를 분리하였습니다.</p>
</blockquote>
</li>
</ol>
<h2 id="3-github-actions-도입하여-ci-테스트">3. Github Actions 도입하여 CI 테스트</h2>
<h3 id="3-1-github-actions란">3-1. Github Actions란?</h3>
<p>Github Actions는 Github 에서 제공하는 CI/CD 툴입니다.</p>
<p>build, test, deploy 등 필요한 Workflow 를 등록해두면 Gihtub 의 특정 이벤트 (push, pull request) 가 발생했을 때 해당 Workflow를 수행합니다.</p>
<p>예를 들어 Pull Request를 올리면 자동으로 해당 코드의 테스트와 빌드를 수행하는 등 Workflow를 커스텀할 수 있습니다.</p>
<h3 id="3-2-프로젝트에-도입">3-2. 프로젝트에 도입</h3>
<p>저는 CI 테스트를 위해 Github Actions를 도입했습니다. 과정은 다음과 같습니다.</p>
<ol>
<li>하위 브랜치에서 develop 브랜치로 PR을 날린다.</li>
<li>Github Actions에서 하위 브랜치의 코드를 빌드, 테스트를 진행</li>
<li>하위 브랜치의 코드가 정상적으로 빌드, 테스트가 된다면 알려주고, 실패했다면 실패를 띄어줍니다.</li>
</ol>
<p>적용 방법은 매우 간단합니다. 아래 사진처럼 .github/workflows/파일명.yml 경로에 내용을 작성하기만 하면 됩니다.
<img src="https://velog.velcdn.com/images/muok_1005/post/1f983e1d-97fe-4b9f-874b-ace9687546df/image.png" alt=""></p>
<p>아래 코드는 프로젝트에 직접 도입한 dev_CI.yml 코드입니다.</p>
<pre><code>name: Java CI

on:
  # develop 브랜치로 pull_request를 보냈을 때
  pull_request:
    branches: [ develop ]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest

    steps:

      # 1) 워크플로우 실행 전 기본적으로 체크아웃 필요
      - uses: actions/checkout@v3

      # 2) JDK 11 버전 설치, 다른 JDK 버전을 사용한다면 수정 필요
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: &#39;17&#39;
          distribution: &#39;temurin&#39;

      # 3)권한 부여 &gt;&gt; 리눅스 명령어
      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew
        shell: bash

      # 4) .build시작 &gt;&gt; 리눅스 명령어
      - name: Build with Gradle
        run: ./gradlew clean build -x test
        shell: bash</code></pre><p>빌드 및 테스트를 진행했을 때 정상이라면 아래 사진처럼 뜹니다.
<img src="https://velog.velcdn.com/images/muok_1005/post/25b3f27b-c532-46a8-a216-a0a6d2e1e5a8/image.png" alt=""></p>
<h2 id="4-느낀점-및-다음으로">4. 느낀점 및 다음으로</h2>
<p>PR을 올렸을 때 Github Actions라는 툴이 자동으로 빌드, 테스트를 진행해주니 휴먼 에러를 줄일 수 있다고 느꼈습니다.</p>
<p>다음 포스트에서는 Jenkins 설정, CI에 관한 내용을 다루겠습니다.</p>
<p>참고 블로그:
<a href="https://bcp0109.tistory.com/362">Github Actions CI: 자동 빌드 및 테스트 하기</a>
<a href="https://kkh1902.tistory.com/194">Gitaction vs Jenkins 개념과 장단점</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그라피 9기 서버 사전과제 후기]]></title>
            <link>https://velog.io/@muok_1005/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9D%BC%ED%94%BC-9%EA%B8%B0-%EC%84%9C%EB%B2%84-%EC%82%AC%EC%A0%84%EA%B3%BC%EC%A0%9C-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@muok_1005/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9D%BC%ED%94%BC-9%EA%B8%B0-%EC%84%9C%EB%B2%84-%EC%82%AC%EC%A0%84%EA%B3%BC%EC%A0%9C-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Mon, 11 Mar 2024 09:44:26 GMT</pubDate>
            <description><![CDATA[<h2 id="1-why">1. Why?</h2>
<p>나는 다양한 사람들과 함께 공부나 프로젝트를 진행하는 것을 좋아한다. 왜냐? 혼자서 공부하면 늘어지기 마련이고, 팀을 꾸려서 프로젝트를 진행하면 책임감과 기간 마감이라는 압박감? 덕분에 나 자신을 더 채찍질하게 돼서 좋아한다. 그래서 심심할 때마다 IT 동아리를 찾아보곤 한다.  </p>
<p>때마침 프로그라피를 발견하였고, 고민 없이 당장 신청했다. 스프링 공부를 막 시작한 터라 사전과제하면서 많이 힘들었지만, 깨달은 점이 너무 많아 기록하려고 작성한다. 
<strong>(아쉽게도 사전과제에서 탈락했지만🥲 이를 발판 삼아 다른 동아리에도 도전해 보겠다)</strong>
<img src="https://velog.velcdn.com/images/muok_1005/post/f3ff7c18-bdf8-48e4-bd51-5c9c1dc7d02e/image.png" alt=""> 사전과제를 하면서 모르는 것들을 싹~~ 다 적어놓고 관련 블로그와 간단한 정리를 적어놨다.</p>
<h2 id="2-사전과제">2. 사전과제</h2>
<p>과제는 5~6일 동안 총 10개의 API들을 구현하는 것이다. 간단히 말하자면 조회, 생성 API와 로직이 은근 까다로운 API도 있었다.
*<em>(자세한 내용은 혹시 모르니 생략, 혹시라도 문제가 된다면 삭제하겠습니다..!) *</em></p>
<p>언어는 Java, 프레임워크는 Spring Boot, Spring JPA, 데이터베이스는 H2 Database를 사용해야 한다. 문서화, 가독성, 테스트 코드 등은 가산점이 부여되기도 한다.</p>
<h2 id="3-새롭게-알게된-점">3. 새롭게 알게된 점</h2>
<h3 id="3-1-헬스-체크">3-1. 헬스 체크</h3>
<p><a href="https://toss.tech/article/how-to-work-health-check-in-spring-boot-actuator">참고한 사이트(설명이 너무 좋다)</a>
나는 헬스 체크라는 걸 이번에 처음 알았다. 이걸 왜 해야 하는데?? 의문이 들었다. 그래서 Why?에 초점을 맞춰 찾아봤다.</p>
<p>여러 이유로 서버의 이중화를 하고, 앞에다가 어떤 서버로 요청을 보낼지 라우팅 역할을 하는 로드 밸런서를 둔다. <em><strong>요청을 보낼 때 서버가 서비스 가능한 상태인지 점검을 하는데, 이 점검이 바로 헬스 체크이다.</strong></em> 나는 늘 서버를 1대만 사용하여 인프라를 간단하게 구성했기 때문에 이런 고민을 해본 적이 없었다. </p>
<p>나는 그냥 GET 요청으로 간단한 API를 짰지만, Spring Boot Actuator를 이용할 수도 있다고 하더라. 대체 왜?? </p>
<p>그 이유는 서버의 다양한 상태 정보(ex: 디스크 공간, DB 연결 등등)를 얻을 수 있기 때문이다. 사실 Spring Boot Actuator를 써야할 필요성은 못느꼈었다. 단지 서버가 서비스 가능 상태인지만 점검하면 되는데 굳이 상태 정보를 알아야 하나?? 라는 생각이 지배적이었다. </p>
<p>추후 Blue/Green을 이용한 무중단 배포를 알아보면서(<a href="https://youtu.be/8sstaLbEBTY?si=DBuNFOHLMjfslZSU">참고 영상</a>) Spring Boot Actuator를 써봐도 좋겠다고 느꼈다. 나중에 무중단 배포를 공부하면서 자세히 알아봐야겠다.</p>
<h3 id="3-2-초기화-api--faker-api">3-2. 초기화 API (+ Faker API)</h3>
<p>DB의 모든 데이터를 초기화하고, API 안에서 또 다른 API를 호출하여 정보를 저장하는 API였다. 이 API(초기화 API) 안에서 호출한 API가 바로 Faker API라는 것이었다. </p>
<p><strong>가장 감명깊었던 건</strong></p>
<ol>
<li>API 안에서 외부 API를 호출할 수 있다는 것 &gt;&gt; 그냥 처음 알아서 신기했다..!</li>
<li>DB의 모든 데이터 초기화와 동시에 Faker API를 통해 더미데이터를 추가한다는 것  &gt;&gt; 개발할 때 너무너무 편할 것 같다. 난 늘 DB에 데이터를 직접 넣었다가 삭제했다가.. 이런 귀찮은 짓을 해왔는데 앞으로는 이 방식을 사용할 것이다. (신세계다..!)</li>
</ol>
<p>일단 <a href="https://fakerapi.it/en">Faker API</a>란? 더미데이터를 나의 입맛에 맞게 JSON 형식으로 가져올 수 있는 API이다. 무려 무료로 제공해주고 있다. 이를 사용한다면 개발 과정에서 더미데이터 생성을 매우 간편하게 할 수 있을 것이다..!
<img src="https://velog.velcdn.com/images/muok_1005/post/2df23bf2-ea20-4173-9ea5-ddc05f7a0f70/image.png" alt=""></p>
<p>위처럼 api 불러오니까</p>
<pre><code>{
    &quot;status&quot;: &quot;OK&quot;,
    &quot;code&quot;: 200,
    &quot;total&quot;: 10,
    &quot;data&quot;: [
        {
            &quot;id&quot;: 1,
            &quot;uuid&quot;: &quot;c81f4430-3ee4-3b6c-ba7a-29ac9cc9aefa&quot;,
            &quot;firstname&quot;: &quot;영일&quot;,
            &quot;lastname&quot;: &quot;제&quot;,
            &quot;username&quot;: &quot;mijung.bae&quot;,
            &quot;password&quot;: &quot;+]/!f(dU3i:C9djwa&gt;21&quot;,
            &quot;email&quot;: &quot;iheo@nam.net&quot;,
            &quot;ip&quot;: &quot;7.178.78.240&quot;,
            &quot;macAddress&quot;: &quot;2B:F3:74:8C:8B:67&quot;,
            &quot;website&quot;: &quot;http://kwak.kr&quot;,
            &quot;image&quot;: &quot;http://placeimg.com/640/480/people&quot;
        },
        {
            &quot;id&quot;: 2,
            &quot;uuid&quot;: &quot;3e603c7a-a005-3cd8-8041-1459a23dfe52&quot;,
            &quot;firstname&quot;: &quot;서영&quot;,
            &quot;lastname&quot;: &quot;변&quot;,
            &quot;username&quot;: &quot;hcheon&quot;,
            &quot;password&quot;: &quot;;OR:r6&#39;oj1h`i=0No2&quot;,
            &quot;email&quot;: &quot;yuri.ko@yahoo.com&quot;,
            &quot;ip&quot;: &quot;15.204.198.172&quot;,
            &quot;macAddress&quot;: &quot;BF:50:1D:DE:20:D4&quot;,
            &quot;website&quot;: &quot;http://oh.biz&quot;,
            &quot;image&quot;: &quot;http://placeimg.com/640/480/people&quot;
        },
                ...
        ]
}</code></pre><p>위처럼 더미데이터를 가져올 수 있다..!</p>
<p>Faker API에 대해서는 알겠는데... 그럼 API 안에서 다른 API를 대체 어떻게 호출을 하지??
가장 많이 나오는 방식이 &#39;RestTemplate&#39;과 &#39;WebClient&#39;이다. 솔직히 이 부분은 구글링,GPT,Bard에게 물어보고 구현만 급하게 하느라 깊은 이해는 못했다. 추후 새 프로젝트에 도입하면서 깊게 공부해보겠다...</p>
<h2 id="4-느낀점">4. 느낀점</h2>
<p>일단 망설임 없이 동아리에 신청해서 도전해 본 나를 칭찬해 주고 싶다. 특히 일주일 동안 밥 먹는 시간, 자는 시간 제외하고 사전과제에 몰입했던 경험 덕분에 많은 성장을 한 것 같다. 어떤 성장을 했는지 한번 읊어보자!</p>
<ol>
<li>스프링 부트로 API를 개발하는 감을 익히게 됨. (물론 내부적인 부분의 이해는 많이 부족하다)</li>
<li>서비스의 고가용성을 위해 로드 밸런서와 다수의 서버로 인프라를 구성할 수 있다는 걸 알게 됨. 이 과정에서 헬스 체크가 필요한 것</li>
<li>개발의 편의성을 위해 초기화 API로 DB 초기화와 함께 더미데이터를 넣어줄 수 있다는 걸 알게 됨.</li>
<li>짧은 시간 동안 몰입해서 코드를 짜는 능력을 기르게 됨.</li>
</ol>
<p><strong>아무튼 너무 값진 경험이었다. 비록 프로그라피는 탈락했지만, 다른 동아리에 붙을 때까지 전부 다 지원해볼 것이다!!!</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 소켓을 사용하여 채팅앱 만들기 (3) (Node.js, MongoDB)]]></title>
            <link>https://velog.io/@muok_1005/%EC%9B%B9-%EC%86%8C%EC%BC%93%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EC%B1%84%ED%8C%85%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0-3-Node.js-MongoDB</link>
            <guid>https://velog.io/@muok_1005/%EC%9B%B9-%EC%86%8C%EC%BC%93%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EC%B1%84%ED%8C%85%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0-3-Node.js-MongoDB</guid>
            <pubDate>Wed, 24 Jan 2024 08:11:56 GMT</pubDate>
            <description><![CDATA[<p>강의를 보면서 백엔드 코드와 주석을 자세하게 남겼습니다. 프론트 코드는 &#39;코딩알려주는 누나&#39;님의 코드를 clone 했습니다. 참고하실 분들은 참고해주세요.
백엔드: <a href="https://github.com/Muokok/chat-study">https://github.com/Muokok/chat-study</a>
프론트: <a href="https://github.com/legobitna/chatapp-client">https://github.com/legobitna/chatapp-client</a></p>
<blockquote>
<p>혹시 몰라서 제가 실습 때 작성한 프론트 코드도 함께 첨부하겠습니다!
<a href="https://github.com/Muokok/chat-study-client">https://github.com/Muokok/chat-study-client</a></p>
</blockquote>
<h2 id="1-what">1. What?</h2>
<p><del>백엔드 세팅: DB 세팅, 웹 소켓 세팅 &gt;&gt; (1)
프론트 세팅: 웹 소켓 세팅 &gt;&gt; (1)
백엔드, 프론트 연결 테스트 &gt;&gt; (1)
유저 로그인 &gt;&gt; (2)</del>
메세지 주고 받기 &gt;&gt; (3)</p>
<h2 id="2-메세지-넘겨받고-저장하기">2. 메세지 넘겨받고 저장하기</h2>
<p>아래 코드는 유저가 입력한 메세지를 프론트에서 백엔드로 넘겨준다.
<img src="https://velog.velcdn.com/images/muok_1005/post/b5779e89-4b8b-4143-9cc8-ae5230a2840d/image.png" alt=""></p>
<p>백엔드에서는 프론트로부터 넘겨받은 메세지 내용과 유저의 정보를 DB에 저장한 뒤 다시 메세지와 유저 정보를 뿌려준다. 이는 접속된 모든 유저들에게 메세지 내용과 보낸 유저가 누군지 알려주는 것이다.
<img src="https://velog.velcdn.com/images/muok_1005/post/b315554f-32ba-4c00-a0d0-279efb5d528e/image.png" alt=""></p>
<p>Controller의 세부적인 로직은 다음과 같다.
userController.js
<img src="https://velog.velcdn.com/images/muok_1005/post/2f85016c-5468-41a5-bc78-7425784b9810/image.png" alt=""></p>
<p>chatController.js
<img src="https://velog.velcdn.com/images/muok_1005/post/ac63f025-7c0a-470e-bba7-b9ad2b371873/image.png" alt=""></p>
<p>여기까지 하면 아래처럼 콘솔에 찍혀야 하는데 
<img src="https://velog.velcdn.com/images/muok_1005/post/1dbfd8f9-b05e-411a-a635-be65472cec38/image.png" alt=""></p>
<p>난 에러가 난다..
<img src="https://velog.velcdn.com/images/muok_1005/post/1c9b9201-139f-45cc-b88c-1a295dcc0e58/image.png" alt=""></p>
<h3 id="2-1-에러-해결">2-1. 에러 해결</h3>
<ol>
<li>MongoDB가 문제인가?
유저 정보는 매우 잘 들어오고 있으므로 문제 X</li>
</ol>
<ol start="2">
<li>코드 누락인가? &gt;&gt; 네 맞습니다.
<img src="https://velog.velcdn.com/images/muok_1005/post/6d6d4662-7c29-4275-bf8e-0b9bd9e21494/image.png" alt=""></li>
</ol>
<p>에러 로그를 보고 고민도 안하고 DB문제인가? 고민하는 실수를 저질렀다. 무조건 에러 로그부터 고민하자!! <del>정신차려</del></p>
<h2 id="3-메시지-리스트-출력하기">3. 메시지 리스트 출력하기</h2>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/0ededa8f-eaef-459b-b0ce-a3ea6f93770e/image.png" alt=""></p>
<p>이건 프론트에서 추가해주면 끝이어서 자세히 다루지 않겠다.</p>
<h2 id="4-접속한-유저-알려주기">4. 접속한 유저 알려주기</h2>
<p><img src="blob:https://velog.io/601b67f5-423f-4eda-8e72-f668679b57d4" alt="업로드중..">
<img src="blob:https://velog.io/27bc3c18-21bb-4c9f-b25c-d467b8476066" alt="업로드중..">
<strong>ㄴ&gt; 사진이 날라갔네요 ㅠㅡㅠ 찾아봤는데 결국 못찾았습니다...</strong></p>
<p>io.js에서 welcomeMessage에 접속했다는 메시지를 저장하고, io.emit으로 채팅방에 있는 모든 유저에게 메시지를 뿌려준다.</p>
<h2 id="5-정리하자면">5. 정리하자면</h2>
<ol>
<li>유저가 메시지를 입력하면 백엔드로 정보(메시지 내용, 유저의 socket id)를 넘겨준다.</li>
<li>백엔드에서 정보를 받아와 DB에 저장하고, 채팅방에 접속된 모든 유저들에게 보내주기 위해 다시 프론트로 데이터를 보내준다.</li>
<li>이 복잡한 로직을 userController와 chatController에서 구현한다.</li>
<li>프론트와 백엔드에 코드를 살짝 첨가하여 메시지 리스트와 접속한 유저 메시지를 뜨게 해준다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] RDS 백업과 복구 (백업은 필수...)]]></title>
            <link>https://velog.io/@muok_1005/AWS-RDS-%EB%B0%B1%EC%97%85%EA%B3%BC-%EB%B3%B5%EA%B5%AC-%EB%B0%B1%EC%97%85%EC%9D%80-%ED%95%84%EC%88%98</link>
            <guid>https://velog.io/@muok_1005/AWS-RDS-%EB%B0%B1%EC%97%85%EA%B3%BC-%EB%B3%B5%EA%B5%AC-%EB%B0%B1%EC%97%85%EC%9D%80-%ED%95%84%EC%88%98</guid>
            <pubDate>Fri, 12 Jan 2024 12:08:06 GMT</pubDate>
            <description><![CDATA[<h2 id="1-why">1. Why?</h2>
<p>토이 프로젝트를 진행하면서 잠깐 서비스를 운영하였는데 그동안 쌓인 데이터를 실수로 삭제해버렸다... 복구를 하려고 애썼으나 결국 실패 😭😭😭😭😭 
솔직히 삭제를 일부러 하는 거 아니면 할리가 있겠어?? 하고 백업을 해놓지 않았는데 내가 바보같이 drop table로 다 날려먹었다.. (데이터베이스 이름이 비슷해서 헷갈렸음..) 
때문에 누구나 똑같은 실수를 할 수도 있기에 백업과 복구 방법을 자세히 정리하고자 한다.</p>
<h2 id="2-rds를-이용한-데이터-백업">2. RDS를 이용한 데이터 백업</h2>
<p>mysql에서 mysqldump로 데이터를 백업하는 방법이 있지만 이 포스트에서는 RDS를 이용한 데이터 백업을 다루겠다.</p>
<h3 id="2-1-백업-설정-확인">2-1. 백업 설정 확인</h3>
<p>RDS -&gt; 데이터베이스 -&gt; [RDS 이름] -&gt; 유지 관리 및 백업 
으로 이동하면 아래 사진처럼 확인할 수 있다. 
<img src="https://velog.velcdn.com/images/muok_1005/post/2a4cae38-cd98-442f-912d-9c0f22946871/image.png" alt=""> 현재 나는 백업, 스냅샷을 생성해두지 않았다. 이제 이걸 복구가 가능하도록 백업을 해보자. </p>
<h3 id="2-2-백업과-스냅샷">2-2. 백업과 스냅샷</h3>
<p>백업이라는 것은 특정 시점의 스냅샷을 남기는 것이다. RDS 스냅샷이란 특정 시점의 Amazon RDS 데이터베이스 인스턴스의 백업이다. 데이터베이스의 데이터 및 구성 설정을 캡처하여 해당 스냅샷에서 데이터베이스를 복원하거나 새 데이터베이스를 만들 수 있다.</p>
<p>스냅샷의 세부적인 특징은 <a href="https://easyitwanner.tistory.com/402#3">여기</a>를 참고하자</p>
<blockquote>
<p>*<em>백업을 하는 방식은 수동 백업, 자동 백업 2가지가 있다.
*</em>
자동 백업된 스냅샷과 수동 백업한 스냅샷의 가장 큰 차이점은 기존 RDS 인스턴스를 삭제하면 자동 백업된 스냅샷들은 같이 삭제가 되고 수동 백업한 스냅샷은 유지가 된다. 그렇기 때문에 RDS 인스턴스를 삭제하기 전에 꼭 수동으로 스냅샷을 저장하고 삭제하자.</p>
</blockquote>
<h3 id="2-3-수동-백업">2-3. 수동 백업</h3>
<p>수동 백업은 RDS 생성 후 특정 시점에 데이터를 남기고 싶을 때 아래처럼 스냅샷을 생성하면 된다.
<img src="https://velog.velcdn.com/images/muok_1005/post/95d0dc5d-da27-4304-83e2-72a34e6c4ce8/image.png" alt=""></p>
<p>아래처럼 스냅샷이 생성되고 있다.
<img src="https://velog.velcdn.com/images/muok_1005/post/ca61727b-ddc8-443f-98dc-988cd4063c19/image.png" alt=""> 스냅샷이 생성되는 중에는 기존 DB가 다운될 수 있으니 서비스 중인 인스턴스가 있다면 주의하자! (데이터 양에 따라 다르지만 5분 내외로 걸린다고 한다.)</p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/8d32eeca-87c5-4f42-8343-a9a256e8d1c8/image.png" alt="">성공!</p>
<h3 id="2-4-자동-백업">2-4. 자동 백업</h3>
<p>RDS를 생성할 때 입맛에 맞게 설정을 해주면 된다.
<img src="https://velog.velcdn.com/images/muok_1005/post/c7606bb7-f0b4-4d0a-9981-9a42424f0500/image.png" alt=""></p>
<h2 id="3-rds를-이용한-데이터-복구">3. RDS를 이용한 데이터 복구</h2>
<h3 id="3-1-수동-백업한-데이터-복구">3-1. 수동 백업한 데이터 복구</h3>
<p>이건 나중에 해보고 정리하자.</p>
<h3 id="3-2-자동-백업한-데이터-복구">3-2. 자동 백업한 데이터 복구</h3>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/19d9e29c-e3a2-42b1-8cb0-1b032418390e/image.png" alt=""> 특정 시점으로 복원을 클릭한다.</p>
<p>복원 가능한 최근 시간을 선택한다면 제일 최근 시점으로 복원을 할 수 있고 사용자 지정 날짜 및 시간을 선택하면 복구 가능시간 사이에 정확한 시간 분 초까지 설정해서 복원 시점을 정할 수 있다.</p>
<p>그 외에 설정들은 기존 RDS 인스턴스와 똑같이 복제되므로 따로 수정할 필요 없다.</p>
<p>밑으로 내려가 삭제 방지를 활성화한 후 특정 시점으로 복원을 누르면 몇 분내에 새로운 RDS 인스턴스가 복제된다.</p>
<h2 id="4-느낀점">4. 느낀점</h2>
<p>너무너무 아찔하고 팀원들에게 미안하다.. 데이터를 삭제해버리다니... 앞으로 개발용 DB가 아닌 서비스용 DB는 무조건 백업을 해둬야겠다. 오늘도 하나 배워갑니다..!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[5th Ne(o)rdinary Hackathon 후기]]></title>
            <link>https://velog.io/@muok_1005/5th-Neordinary-Hackathon-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@muok_1005/5th-Neordinary-Hackathon-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Wed, 29 Nov 2023 15:49:07 GMT</pubDate>
            <description><![CDATA[<h2 id="참여한-이유">참여한 이유</h2>
<p>어느 정도 기간을 갖고 프로젝트를 진행해 본 경험은 있었지만 무박 2일 동안 빠르게 개발하는 건 얼마나 재밌을지 경험해 보고 싶었다. 개발에 자신있는 건 아니지만 도전하고자 신청했는데... 다행히 뽑혔다!! (아마도  선착순인 듯..?)</p>
<h2 id="주제">주제</h2>
<p>이번 해커톤의 주제는 <strong>뉴진스의 노래 제목</strong> 아무거나 택해서 아이템을 만드는 것이었다. 재밌는 주제였다 ㅋ.ㅋ</p>
<p>우리 팀은 뉴진스의 ETA(Estimated Time of Arrival) 로부터 감명받아 각종 팀플이나 프로젝트/공모전 회의 시간을 정할 수 있는 서비스를 주제로 삼았다. 
기획 1명, 디자인 1명, IOS 3명, 서버 4명 총 9명이 한 팀이었다.</p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/513be41e-b673-4a7d-9bb2-b33ba5826931/image.png" alt=""></p>
<h2 id="나의-역할">나의 역할</h2>
<p>서버 개발자 4명 중 나와 친구는 Javascript와 Express로만 서버 개발을 해봤고, 나머지 두 분은 Typescript와 NestJS로만 해봤다고 하셨다. Javascript를 안다면 Typescript를 금방 할 수 있을 것 같아서 의논 끝에 Typescript와 NestJS로 개발하기로 했다. </p>
<p>정말 큰 문제는 친구와 나는 ORM 또한 써본 적이 없었는데 ORM을 사용하여 개발하는 것이다... 그래서 인프라, DB, API 명세서에 더 집중하고 API 개발은 조금만 하였다.</p>
<h2 id="느낀점">느낀점</h2>
<h3 id="1-굉장히-뿌듯하지만-너무x100-아쉽다">1. 굉장히 뿌듯하지만 너무x100 아쉽다.</h3>
<p>단기간에 다 같이 기획~개발을 통해 결과물을 만들었다는 것이 너무 뿌듯하고 뭐든 하면 할 수 있겠다는 자신감을 얻었다..!
하지만 개발적인 면에서는 큰 기여를 못했다. 이게 너무너무 아쉬웠다. 개발하면서 의논도 하고 에러도 다 같이 해결하면서 협동하는 느낌을 받고 싶었지만 물어보고 구글링하는 데 시간을 다 소비했다. 다음 해커톤 때는 꼭꼭 개발에 큰 기여를 하고 싶다!</p>
<h3 id="2-orm을-쓰면-정말-간편해진다">2. ORM을 쓰면 정말 간편해진다.</h3>
<p>나는 늘 쿼리문을 직접 작성하여 개발했다. ORM을 쓰니까 테이블 생성이 간편하고 복잡한 쿼리문(특히 Join)을 짤 필요가 줄어든다고 느꼈다. 나중에 할 프로젝트는 꼭 ORM을 써봐야겠다.</p>
<h3 id="3-밤을-새우는-건-매우-할-만하다">3. 밤을 새우는 건 매우 할 만하다.</h3>
<p>밤을 새워본 적이 없어서 걱정을 했었는데, 매우 매우 멀쩡해서 신기했다.</p>
<h3 id="4-무조건-도전해보자">4. 무조건 도전해보자.</h3>
<p>솔직히 이번 해커톤 안 나가려고 했다. 자신이 너무 없었기 때문.. 근데 걍 &#39;에라 모르겠다~ 까짓것 해보자&#39;라는 느낌으로 나갔는데 최고의 선택이었다. 이제는 할까? 말까? 망설이지 말고 저지르고 보는 삶을 살아보자!</p>
<p>갑자기 생각난건데 자료 제출 1시간? 전에 팀원 한 분하고 DB의 컬럼을 삭제해버렸었다. 진짜 아무 생각없이 필요없네? 하고 삭제를 했는데 이것 때문에 에러가 나버린 것..... 진~~짜 다행히 조금 수정해서 해결했지만 아찔한 경험이었다...</p>
<blockquote>
<p><strong>교훈 &gt;&gt; DB를 건드는 건 100번 다시 생각해보고 고민해보자..</strong></p>
</blockquote>
<h2 id="결과">결과</h2>
<p>10팀 중에 3등을 하였다!! </p>
<p>멘토님께서는 바로 서비스를 운영해도 될 것 같다고 피드백을 주셨다!! 너무 기뻤고, 함께 한 팀원분들에게 너무 너무 감사했다. 그래서 종강 후에 서비스 배포까지 계획 중이다!! 화이팅!! <del>(TypeScript와 NestJS 열심히 배워보자...)</del></p>
<p><img src="https://velog.velcdn.com/images/muok_1005/post/73c8f1ae-355b-4ef6-9c1a-dfb5ee497b16/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 소켓을 사용하여 채팅앱 만들기 (2) (Node.js, MongoDB)]]></title>
            <link>https://velog.io/@muok_1005/%EC%9B%B9-%EC%86%8C%EC%BC%93%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EC%B1%84%ED%8C%85%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0-2-Node.js-MongoDB</link>
            <guid>https://velog.io/@muok_1005/%EC%9B%B9-%EC%86%8C%EC%BC%93%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EC%B1%84%ED%8C%85%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0-2-Node.js-MongoDB</guid>
            <pubDate>Wed, 22 Nov 2023 09:24:19 GMT</pubDate>
            <description><![CDATA[<p>강의를 보면서 백엔드 코드와 주석을 자세하게 남겼습니다. 프론트 코드는 &#39;코딩알려주는 누나&#39;님의 코드를 clone 했습니다. 참고하실 분들은 참고해주세요.
백엔드: <a href="https://github.com/Muokok/chat-study">https://github.com/Muokok/chat-study</a>
프론트: <a href="https://github.com/legobitna/chatapp-client">https://github.com/legobitna/chatapp-client</a></p>
<blockquote>
<p>혹시 몰라서 제가 실습 때 작성한 프론트 코드도 함께 첨부하겠습니다!
<a href="https://github.com/Muokok/chat-study-client">https://github.com/Muokok/chat-study-client</a></p>
</blockquote>
<h2 id="1-what">1. What?</h2>
<p><del>백엔드 세팅: DB 세팅, 웹 소켓 세팅 &gt;&gt; (1)
프론트 세팅: 웹 소켓 세팅 &gt;&gt; (1)
백엔드, 프론트 연결 테스트 &gt;&gt; (1)</del>
유저 로그인 &gt;&gt; (2)
메세지 주고 받기 &gt;&gt; (3)</p>
<h2 id="2-유저-로그인-및-정보-받기">2. 유저 로그인 및 정보 받기</h2>
<p>파일 구조는 다음과 같다.
<img src="https://velog.velcdn.com/images/muok_1005/post/7e74435a-0e17-4892-89df-5e1dec994365/image.png" alt="">
user관련 코드는 userController에 따로 빼줬다. io.js는 통신과 관련된 코드들만 모아두기 위해서다.</p>
<ul>
<li><p>io.js
<img src="https://velog.velcdn.com/images/muok_1005/post/1203b313-b1a4-41e9-926c-3bdcf1a894b8/image.png" alt=""></p>
</li>
<li><p>userController.js
<img src="https://velog.velcdn.com/images/muok_1005/post/08188b6d-4045-4c3b-a3b1-a85817064802/image.png" alt=""></p>
</li>
</ul>
<h2 id="3-전체적인-흐름">3. 전체적인 흐름</h2>
<p>전체적인 흐름은 다음과 같다.</p>
<ol>
<li><p>유저가 로그인(이 강의에서는 프롬프트 창에 userName을 입력하는 것을 로그인으로 한다.)하면 프론트에서 socket.emit 함수로 제목, 내용, 콜백함수를 백엔드로 넘겨준다.</p>
</li>
<li><p>백엔드에서 socket.on 함수로 제목, 내용, 콜백함수를 받아와 유저정보와 소켓 id 정보를 DB에 저장한다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[비즈니스 데이터 분석가의 현업 CASE 강의]]></title>
            <link>https://velog.io/@muok_1005/%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D%EA%B0%80%EC%9D%98-%ED%98%84%EC%97%85-CASE-%EA%B0%95%EC%9D%98</link>
            <guid>https://velog.io/@muok_1005/%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D%EA%B0%80%EC%9D%98-%ED%98%84%EC%97%85-CASE-%EA%B0%95%EC%9D%98</guid>
            <pubDate>Tue, 07 Nov 2023 08:20:32 GMT</pubDate>
            <description><![CDATA[<h3 id="1-why">1. Why?</h3>
<p>학교에서 진행한 4차 산업혁명 심화프로그램 &lt;비즈니스 데이터 분석가의 현업 CASE&gt; 강의를 듣고 유용했던 정보들을 정리 및 기록하고자 작성한다.</p>
<h3 id="2-데이터-분석이란">2. 데이터 분석이란?</h3>
<p>현실의 문제를 해결하고 이해하는 데 데이터를 사용하는 과정이다. 
<img src="https://velog.velcdn.com/images/muok_1005/post/fd7f8bbf-d08d-4192-a17f-382511b0272b/image.png" alt=""></p>
<h3 id="3-비즈니스-데이터-분석가ba란">3. 비즈니스 데이터 분석가(BA)란?</h3>
<p>데이터 분석가가 하는 일은 쉽게 말해 컴퓨터 코드로 데이터를 변환 하거나, 집계 및 통계분석, 머신러닝 모델을 훈련하는 것이다. 일반적으로는 비즈니스 분석가, 데이터 분석가, 데이터 엔지니어, 데이터 과학자, 데이터베이스 관리자 직군이 데이터분석가라고 한다. 하지만 Data Engineering, 머신러닝 엔지니어, Data Analytics, Data infra 를 하는 엔지니어 직군 만이 데이터 관련 직군은 아니다. 데이터 분석가의 일은 신뢰할 수 있는 데이터를 기반으로 비즈니스 의사 결정을 내릴 수 있도록 하는 것이다.</p>
<h3 id="4-기억에-남았던-내용들">4. 기억에 남았던 내용들</h3>
<ol>
<li>현업에서 많은 사람들이 아나운서 학원을 다닌다고 한다. 그만큼 청자의 관심을 끌고, 자신이 전달하려는 바를 확실하게 전하는 능력이 어디서나 중요하다. 나아가 자신의 지식을 정리하고 공유하는 것이 중요해졌다.</li>
<li>데이터 회사에서는 플랫폼 구축을 많이 한다. 특히 AWS, Azure을 많이 사용한다. 즉, AWS 관리자가 필요하다는 것.</li>
<li>요즘은 대기업에서 신입을 안 뽑는 추세이다. 대부분 경력자들을 뽑는다고 한다. 강사님께서는 학생 때는 해커톤, 부트 캠프, 인턴 등으로 여러 경험을 쌓고 추후에 중소기업,  스타트업에서 경험을 쌓아 더 좋은 곳으로 이직하는 게 좋다고 하셨다.</li>
<li>네트워크, 커뮤니케이션도 많이 경험해보라고 하셨다. 밋업 같은 네트워킹 사이트를 적극 활용해보라고 하셨다.</li>
<li>나를 증명하는 것은 깃허브, 오픈소스 컨트리뷰션, 블로그, 프로젝트이다. 자소서보다 중요한 건 포폴이라고 하셨다. 블로그로 내가 무엇을 했는지 남기고, 깃허브에 관련 문서 및 코드를 올려 증거를 남기는 것이다.</li>
<li>면접에서는 질문의 답변에 꼬리를 무는 질문을 하는 방식이 많다. 질문에 대한 꼬리를 무는 질문들을 고민하고 답변하고 해결하는 능력이 중요시되는 것 같다.</li>
<li>꼭 내 분야로의 진출이 아니라 여러 분야에서 어디로든 진출할 수 있을 열린 시야를 갖는 것이 좋다.</li>
<li>Chat GPT 를 잘 활용할 수 있는 프롬프트 엔지니어링 능력도 중요하다. 조지타운 대학교 Sam Potolicchio 교수님은 &#39;교육에 있어 가장 중요한 것은 질문하는 법을 배우는 것인데, 프롬프트 엔지니어링은 바로 그것이다&#39; 라고 할 정도이다.</li>
</ol>
]]></description>
        </item>
    </channel>
</rss>