<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Mr.november11</title>
    <link>https://november11tech.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 9 Apr 2026 04:09:44 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>mr.november11</managingEditor>
    <image>
      <title>Mr.november11</title>
      <url>https://t1.daumcdn.net/cfile/tistory/99F8433E5B31BBAA01</url>
      <link>https://november11tech.tistory.com</link>
    </image>
    <item>
      <title>2026년 전국 벚꽃 개화 시기 및 명소 가이드   (미리 준비하는 봄 여행)</title>
      <link>https://november11tech.tistory.com/191</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;h2&amp;gt;안녕하세요! Mr.november11입니다.&amp;lt;/h2&amp;gt;&amp;lt;p&amp;gt;어느덧 날씨가 조금씩 풀리면서 봄 기운이 느껴지기 시작하네요. 봄 하면 가장 먼저 떠오르는 벚꽃! 오늘은 2026년 벚꽃 개화 시기와 꼭 가봐야 할 명소들을 미리 정리해 드립니다.&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;h3&amp;gt;  2026 벚꽃 개화 예상 시기&amp;lt;/h3&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;p&amp;gt;올해 벚꽃은 평년보다 조금 일찍 만날 수 있을 것으로 보입니다.&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;ul&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;li&amp;gt;제주: 3월 20일 ~ 22일&amp;lt;/li&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;li&amp;gt;남부 지방 (부산, 진해, 대구): 3월 24일 ~ 27일&amp;lt;/li&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;li&amp;gt;중부 지방 (대전, 청주): 3월 29일 ~ 4월 2일&amp;lt;/li&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;li&amp;gt;수도권 (서울, 인천): 4월 3일 ~ 7일&amp;lt;/li&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;/ul&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;h3&amp;gt;  놓치면 후회할 벚꽃 명소 BEST 3&amp;lt;/h3&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;ol&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;li&amp;gt;&amp;lt;b&amp;gt;진해 군항제&amp;lt;/b&amp;gt;: 말이 필요 없는 대한민국 최고의 벚꽃 축제! 여좌천 로망스다리와 경화역 철길은 인생샷 성지입니다.&amp;lt;/li&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;li&amp;gt;&amp;lt;b&amp;gt;경주 보문단지&amp;lt;/b&amp;gt;: 고즈넉한 한옥과 어우러진 벚꽃을 보고 싶다면 경주가 정답입니다. 보문호수를 따라 걷는 산책길이 정말 아름다워요.&amp;lt;/li&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;li&amp;gt;&amp;lt;b&amp;gt;서울 여의도 윤중로&amp;lt;/b&amp;gt;: 도심 속 벚꽃 터널을 즐길 수 있는 곳! 퇴근길이나 주말 나들이로 제격입니다.&amp;lt;/li&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;/ol&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;h3&amp;gt;  벚꽃 나들이 꿀팁&amp;lt;/h3&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;ul&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;li&amp;gt;개화 후 일주일 정도 지나면 만개하므로 시기를 잘 맞춰 가시는 것이 중요합니다.&amp;lt;/li&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;li&amp;gt;주말에는 인파가 몰리니 가급적 이른 아침이나 평일 방문을 추천드려요!&amp;lt;/li&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;/ul&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;p&amp;gt;여러분은 올해 어디로 벚꽃 구경을 가실 계획인가요? 댓글로 공유해 주세요!  &amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;p&amp;gt;#벚꽃개화시기 #2026벚꽃 #봄꽃축제 #벚꽃명소 #봄여행 #진해군항제 #여의도벚꽃 #경주여행 #꽃구경&amp;lt;/p&amp;gt;&lt;/p&gt;</description>
      <author>mr.november11</author>
      <guid isPermaLink="true">https://november11tech.tistory.com/191</guid>
      <comments>https://november11tech.tistory.com/191#entry191comment</comments>
      <pubDate>Sat, 7 Mar 2026 20:02:09 +0900</pubDate>
    </item>
    <item>
      <title>[GeekNews 요약] 단순함으로는 승진하지 못한다: 엔지니어링의 '보이지 않는 성과'를 증명하는 법</title>
      <link>https://november11tech.tistory.com/190</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nCyoc/dJMcaflrXPc/VNfR2Wx30QK2TJ5FXkRAP0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nCyoc/dJMcaflrXPc/VNfR2Wx30QK2TJ5FXkRAP0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nCyoc/dJMcaflrXPc/VNfR2Wx30QK2TJ5FXkRAP0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnCyoc%2FdJMcaflrXPc%2FVNfR2Wx30QK2TJ5FXkRAP0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;단순함으로는 승진하지 못한다: 엔지니어링의 '보이지 않는 성과'를 증명하는 법&lt;/h1&gt;

&lt;p&gt;GeekNews의 아티클 &lt;a href=&quot;https://news.hada.io/topic?id=27204&quot;&gt;&quot;단순함으로는 승진하지 못한다&quot;&lt;/a&gt;를 읽고, 엔지니어링 문화에서 간과되기 쉬운 '단순함'의 가치와 이를 성과로 증명하는 방법에 대해 정리해 보았습니다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3&gt;1. 승진 서사의 비대칭: 왜 복잡함이 똑똑해 보이는가?&lt;/h3&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;보이지 않는 성과&lt;/strong&gt;: 50줄의 코드로 며칠 만에 기능을 출시한 A와, 새로운 추상화 레이어와 프레임워크를 도입해 3주 만에 완성한 B가 있다면?&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;평가의 함정&lt;/strong&gt;: B의 작업은 &quot;확장 가능한 아키텍처 설계&quot;로 화려하게 서술되지만, A의 작업은 &quot;기능 구현&quot;이라는 세 단어로 축소되기 쉽습니다.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;교훈&lt;/strong&gt;: &quot;피한 복잡성&quot;으로는 아무도 승진하지 못합니다. 현재의 평가 체계가 복잡성을 보상하도록 설계되어 있기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;2. 면접과 디자인 리뷰에서 복잡성이 강화되는 구조&lt;/h3&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;압박의 메시지&lt;/strong&gt;: 면접관이 &quot;천만 명의 사용자가 오면?&quot;이라고 묻는 순간, 후보자는 &quot;단순한 답은 충분하지 않다&quot;는 교훈을 얻고 불필요한 레이어를 추가하게 됩니다.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;미래 대비(Future-proof)의 허상&lt;/strong&gt;: 문제가 요구해서가 아니라, 회의실의 기대를 충족하기 위해 추가하는 추상화는 오히려 유지보수를 어렵게 만듭니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;3. 엔지니어를 위한 실천 방안: 단순함을 가시화하라&lt;/h3&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;결정의 서사화&lt;/strong&gt;: &quot;기능 X 구현&quot; 대신 &quot;세 가지 접근법을 평가한 뒤, 단순 구현이 현재 요구사항을 충족한다고 판단하여 2일 만에 출시&quot;라고 기술하세요.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;무언가를 만들지 않기로 한 결정&lt;/strong&gt;도 중요한 결정이며, 이를 문서화해야 합니다.&lt;/li&gt;
    &lt;li&gt;매니저에게 자신의 결정이 반영된 성과 작성을 직접 요청하세요.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;4. 리더를 위한 실천 방안: 인센티브를 재설정하라&lt;/h3&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;입증 책임의 전환&lt;/strong&gt;: 디자인 리뷰에서 &quot;확장성은?&quot; 대신 &quot;출시 가능한 가장 단순한 버전은 무엇인가?&quot;라고 질문하세요.&lt;/li&gt;
    &lt;li&gt;복잡성에 입증 책임을 부여하고, 단순함을 기본값으로 설정해야 합니다.&lt;/li&gt;
    &lt;li&gt;코드를 삭제한 엔지니어, &quot;아직 필요 없다&quot;고 말하고 맞았던 엔지니어를 공식적으로 인정하고 축하하세요.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;진정한 숙련도는 더 많은 도구를 사용하는 것이 아니라, 언제 복잡성을 배제해야 하는지 아는 &lt;strong&gt;판단력과 자신감&lt;/strong&gt;에 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;* 본 포스팅은 GeekNews 아티클의 내용을 바탕으로 재구성되었습니다.&lt;/em&gt;&lt;/p&gt;</description>
      <category>Etc</category>
      <category>geeknews</category>
      <category>IT인사이트</category>
      <category>단순함</category>
      <category>승진</category>
      <category>엔지니어링</category>
      <author>mr.november11</author>
      <guid isPermaLink="true">https://november11tech.tistory.com/190</guid>
      <comments>https://november11tech.tistory.com/190#entry190comment</comments>
      <pubDate>Fri, 6 Mar 2026 11:45:09 +0900</pubDate>
    </item>
    <item>
      <title>2026년 AI 에이전트 기술 전망: 파트너로서의 진화</title>
      <link>https://november11tech.tistory.com/189</link>
      <description>&lt;h1&gt;2026년 AI 에이전트 기술 트렌드: 당신의 자율 파트너&lt;/h1&gt;

&lt;p&gt;2026년은 인공지능이 단순한 '명령 수행 도구'에서 벗어나 우리 삶의 '자율 파트너'로 확고히 자리 잡는 시기가 될 것입니다. 가트너와 마이크로소프트 등 글로벌 오피니언 리더들이 주목하는 주요 변화를 소개합니다.&lt;/p&gt;

&lt;h2&gt;1. 행동하는 AI: 에이전틱 리프(Agent Leap)&lt;/h2&gt;
&lt;p&gt;단순히 질문에 답하는 수준을 넘어, 복잡한 업무를 끝까지 완수하는 능력이 핵심입니다. 예약, 구매, 보고서 작성 등 수많은 단계를 스스로 판단하여 처리하는 '행동하는 지능'이 보편화됩니다.&lt;/p&gt;

&lt;h2&gt;2. 협업하는 지능: 멀티에이전트 네트워크&lt;/h2&gt;
&lt;p&gt;하나의 대형 언어 모델이 모든 것을 처리하기보다, 보안 담당, 기획 담당, 디자인 담당 등 전문성을 가진 소형 에이전트들이 서로 대화하고 협업하며 결과를 도출하는 시스템이 주류가 됩니다.&lt;/p&gt;

&lt;h2&gt;3. 물리적 세계로의 확장: 피지컬 AI&lt;/h2&gt;
&lt;p&gt;로보틱스와 결합된 AI는 우리 주변의 사물과 공간을 이해하고 조작하기 시작합니다. 이는 스마트 홈을 넘어 물류와 제조 현장에서 혁명적인 생산성 향상을 불러올 것입니다.&lt;/p&gt;

&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;2026년의 기술은 '인간의 대체'가 아닌 '인간의 증폭'을 목표로 합니다. AI 에이전트라는 새로운 동료와 함께하는 미래를 준비해야 할 때입니다.&lt;/p&gt;</description>
      <category>Etc</category>
      <category>2026</category>
      <category>AI</category>
      <category>ai에이전트</category>
      <category>기술트렌드</category>
      <author>mr.november11</author>
      <guid isPermaLink="true">https://november11tech.tistory.com/189</guid>
      <comments>https://november11tech.tistory.com/189#entry189comment</comments>
      <pubDate>Fri, 6 Mar 2026 11:14:37 +0900</pubDate>
    </item>
    <item>
      <title>켄트 벡의 Tidy First? 리뷰</title>
      <link>https://november11tech.tistory.com/187</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;book_cover.jpg&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;2048&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mjuVj/btsHcpqvXaJ/H6BK7bKAGk0StSKTRik250/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mjuVj/btsHcpqvXaJ/H6BK7bKAGk0StSKTRik250/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mjuVj/btsHcpqvXaJ/H6BK7bKAGk0StSKTRik250/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmjuVj%2FbtsHcpqvXaJ%2FH6BK7bKAGk0StSKTRik250%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;380&quot; height=&quot;507&quot; data-filename=&quot;book_cover.jpg&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;2048&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;ChatGPT, LLM, AI와 같은 유행성 서적들이 주를 이루는 IT도서 시장에서 오랜만에 소프트웨어 기본기를 다루는 서적이 출간되어 기쁘다. 특히 저자가 TDD로 유명한 '켄트 벡'이라는 것을 알게 되었을 때 이 책을 반드시 읽어야겠다고 생각했다.&lt;br /&gt;&lt;br /&gt;이&amp;nbsp;책은&amp;nbsp;두&amp;nbsp;권으로&amp;nbsp;구성되어&amp;nbsp;있다.&amp;nbsp;첫&amp;nbsp;번째&amp;nbsp;권은&amp;nbsp;켄트&amp;nbsp;벡이&amp;nbsp;집필한&amp;nbsp;'Tiny&amp;nbsp;First?'이며&amp;nbsp;두&amp;nbsp;번째&amp;nbsp;권은&amp;nbsp;역자가&amp;nbsp;추가로&amp;nbsp;기록한&amp;nbsp;'옮긴이&amp;nbsp;노트'이다.&amp;nbsp;'Tiny&amp;nbsp;First?'의&amp;nbsp;첫인상은&amp;nbsp;생각보다&amp;nbsp;책의&amp;nbsp;분량이&amp;nbsp;적다는&amp;nbsp;느낌이었다.&amp;nbsp;100페이지&amp;nbsp;조금&amp;nbsp;넘는&amp;nbsp;공간에서&amp;nbsp;복잡한&amp;nbsp;소프트웨어&amp;nbsp;설계에&amp;nbsp;대한&amp;nbsp;내용을&amp;nbsp;다&amp;nbsp;다룰&amp;nbsp;수&amp;nbsp;있을까라는&amp;nbsp;의문이&amp;nbsp;들었다.&amp;nbsp;켄트&amp;nbsp;벡에&amp;nbsp;따르면&amp;nbsp;이&amp;nbsp;책은&amp;nbsp;연작으로&amp;nbsp;기획되었으며&amp;nbsp;최소&amp;nbsp;3권의&amp;nbsp;시리즈로&amp;nbsp;구성될&amp;nbsp;예정이다.&amp;nbsp;이&amp;nbsp;책은&amp;nbsp;그&amp;nbsp;시리즈&amp;nbsp;중&amp;nbsp;첫&amp;nbsp;번째&amp;nbsp;책이다.&amp;nbsp;책&amp;nbsp;가격&amp;nbsp;대비&amp;nbsp;분량이&amp;nbsp;적어&amp;nbsp;조금&amp;nbsp;아쉽다는&amp;nbsp;생각이&amp;nbsp;들었다.&amp;nbsp;하지만&amp;nbsp;내용이&amp;nbsp;군더더기&amp;nbsp;없이&amp;nbsp;정제되어&amp;nbsp;있고,&amp;nbsp;출퇴근&amp;nbsp;시간에&amp;nbsp;대중교통에서&amp;nbsp;읽기에&amp;nbsp;적합한&amp;nbsp;크기라는&amp;nbsp;장점이&amp;nbsp;있다.&lt;br /&gt;&lt;br /&gt;켄트 벡의 'Tiny First?'는 단순한 코드 정리 방법부터 복잡한 설계 이론에 이르기까지 좋은 설계를 위한 다양한 내용을 다루고 있다. 초보 개발자들은 특히 1장 '코드 정리법'과 2장 '관리'에서 협업과 유지 보수를 위한 코드 정리 방법을 배울 수 있다(이 두 장의 내용에 흥미를 느꼈다면 해당 내용을 좀 더 자세히 다루고 관련 예시를 제공하는 클린 코드와 리팩터링 책을 읽어보길 권한다). 개인적으로 흥미로웠던 내용은 3장 '이론'에서 코드 정리의 가치를 경제 이론을 통해 다룬 부분이었다. 현업에서 개발 업무를 하면서 신규 기능 구현과 코드 정리의 중요성 사이에서 우선순위를 결정해야 하는 상황이 자주 발생한다. 개발자들은 자연스럽게 코드 정리에 더 큰 가치를 두는 경향이 있지만, 이를 사업부나 상위 관리자에게 설득하는 것이 쉽지가 않다. 또한 해당 시점에서 코드 정리가 실제로 더 중요한지에 대한 의문도 항상 남아 있었다. 켄트 벡은 이런 문제에 대해 경제적 직관을 활용하여 해결 방향을 제시한다.&lt;br /&gt;&lt;br /&gt;첫&amp;nbsp;번째&amp;nbsp;책인&amp;nbsp;'Tiny&amp;nbsp;First?'에서&amp;nbsp;소프트웨어&amp;nbsp;설계에&amp;nbsp;관련된&amp;nbsp;지식을&amp;nbsp;얻을&amp;nbsp;수&amp;nbsp;있었다면,&amp;nbsp;두&amp;nbsp;번째&amp;nbsp;책인&amp;nbsp;'옮긴이&amp;nbsp;노트'에서는&amp;nbsp;좋은&amp;nbsp;개발자로&amp;nbsp;성장하기&amp;nbsp;위한&amp;nbsp;동기&amp;nbsp;부여를&amp;nbsp;전달받을&amp;nbsp;수&amp;nbsp;있었다.&amp;nbsp;특히&amp;nbsp;역자가&amp;nbsp;켄트&amp;nbsp;벡과&amp;nbsp;주고받은&amp;nbsp;대화를&amp;nbsp;통해&amp;nbsp;개발자로서&amp;nbsp;가져야&amp;nbsp;할&amp;nbsp;사명감과&amp;nbsp;열정을&amp;nbsp;배울&amp;nbsp;수&amp;nbsp;있었다.&amp;nbsp;이들처럼&amp;nbsp;노력하고,&amp;nbsp;실천하며,&amp;nbsp;즐기는&amp;nbsp;태도로&amp;nbsp;꾸준함을&amp;nbsp;잃지&amp;nbsp;않으면&amp;nbsp;나도&amp;nbsp;언젠가&amp;nbsp;좋은&amp;nbsp;개발자가&amp;nbsp;될&amp;nbsp;수&amp;nbsp;있을&amp;nbsp;것이라는&amp;nbsp;희망을&amp;nbsp;가지게&amp;nbsp;되었다.&lt;br /&gt;&lt;br /&gt;&quot;한빛미디어의&amp;nbsp;도서&amp;nbsp;지원을&amp;nbsp;받아&amp;nbsp;작성한&amp;nbsp;리뷰입니다.&quot;&lt;/p&gt;</description>
      <category>리뷰</category>
      <author>mr.november11</author>
      <guid isPermaLink="true">https://november11tech.tistory.com/187</guid>
      <comments>https://november11tech.tistory.com/187#entry187comment</comments>
      <pubDate>Mon, 6 May 2024 14:39:43 +0900</pubDate>
    </item>
    <item>
      <title>NestJS에서 AWS S3로 파일 업로드하기</title>
      <link>https://november11tech.tistory.com/186</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. nest cli로 컨트롤러,서비스&amp;nbsp;생성하기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 업로드 서비스의 이름은 upload 로 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;llvm&quot;&gt;&lt;code&gt;nest g mo uploads
nest g co uploads
nest g s uploads&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 업로드 컨트롤러&amp;nbsp;구현&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참고 문서&amp;nbsp;: &lt;a href=&quot;https://docs.nestjs.com/techniques/file-upload&quot;&gt;https://docs.nestjs.com/techniques/file-upload&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nest는 파일 업로드 처리를 위해 Express의 multer 미들웨어를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;multer는 POST 메소드로 multipart/form-data 컨텐츠 타입을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업로드 컨트롤러를 구현하기 앞서 Multer 라이브러리를 설치한다.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;$ yarn add --dev @types/multer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 예제는 단일 파일 업로드를 처리한다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;@Controller('uploads')
export class UploadsController {&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;  @Post()
  @UseInterceptors(FileInterceptor('file'))
  uploadFile(@UploadedFile() file: Express.Multer.File) {
    console.log(file);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업로드 컨트롤러에서 단일 파일 입력을 받도록 설정한 후 포스트맨으로 API를 테스트한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL: &lt;a href=&quot;http://localhost:3000/uploads&quot; data-href=&quot;http://localhost:3000/uploads&quot;&gt;http://localhost:3000/uploads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;메소드: POST&lt;/li&gt;
&lt;li&gt;Body:&lt;/li&gt;
&lt;li&gt;form-data&lt;/li&gt;
&lt;li&gt;file 키 값에 파일을 지정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스트맨으로 테스트 한 결과 console.log(file) 에 다음과 같은 정보가 출력된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;{
  fieldname: 'file',
  originalname: 'test.png',
  encoding: '7bit',
  mimetype: 'image/png',
  buffer: &amp;lt;Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 01 46 00 00 01 3e 08 06 00 00 00 f1 a6 b9 5c 00 00 0c 6d 69 43 43 50 49 43 43 20 50 72 6f 66 69 ... 36041 more bytes&amp;gt;,
  size: 36091
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 컨트롤러의 file을 서비스로 전달하여 S3로 업로드하는 작업을 구현한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 업로드 서비스&amp;nbsp;구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 업로드 컨트롤러에서 업로드 서비스의 uploadFile 함수를 호출하도록 코드를 수정한다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Controller('uploads')
export class UploadsController {
  constructor(private readonly uploadService: UploadsService) {}
  @Post()
  @UseInterceptors(FileInterceptor('file'))
  uploadFile(@UploadedFile() file: Express.Multer.File) {
    return this.uploadService.uploadFile(file);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업로드 서비스에서는 AWS S3로 파일을 업로드하기 위해 aws-sdk 라이브러리를 사용한다.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;$ yarn add --dev aws-sdk&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업로드 서비스에서 aws-sdk를 사용하기 위해 클래스에 S3 멤버 변수를 추가한다.&lt;br /&gt;(실행 환경에 S3 업로드 권한을 가진 Access Key가 설정되어 있어야 한다.)&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;import { Injectable } from '@nestjs/common';
import * as AWS from 'aws-sdk';&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;@Injectable()
export class UploadsService {
  s3 = new AWS.S3();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 파일 업로드를 수행하는 uploadFile 함수를 구현한다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;async uploadFile(file: Express.Multer.File) {
    const AWS_S3_BUCKET = 'nestjs-upload-test-bucket';&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;    const params = {
      Bucket: AWS_S3_BUCKET,
      Key: String(file.originalname),
      Body: file.buffer,
    };&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;    try {
      const response = await this.s3.upload(params).promise();&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;      return response;
    } catch (e) {
      throw new Error('Failed to upload file.');
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업로드가 성공했다면 AWS.S3.ManagedUpload.SendData 타입의 결괏값이 반환된다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;export interface SendData {
        /**
         * URL of the uploaded object.
         */
        Location: string;
        /**
         * ETag of the uploaded object.
         */
        ETag: string;
        /**
         * Bucket to which the object was uploaded.
         */
        Bucket: string;
        /**
         * Key to which the object was uploaded.
         */
        Key: string;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답 객체의 Location은 업로드 된 객체의 URL을 포함한다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
    &quot;ETag&quot;: &quot;\&quot;fff730c0a3f344034854117aaf88d9ac\&quot;&quot;,
    &quot;Location&quot;: &quot;https://nestjs-upload-test-bucket.s3.ap-northeast-2.amazonaws.com/test.png&quot;,
    &quot;key&quot;: &quot;test.png&quot;,
    &quot;Key&quot;: &quot;test.png&quot;,
    &quot;Bucket&quot;: &quot;nestjs-upload-test-bucket&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업로드 시 별도의 ACL을 설정하지 않았기 때문에 해당 URL로 접속할 경우 AccessDenied 가 발생한다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;Error&amp;gt;
&amp;lt;Code&amp;gt;AccessDenied&amp;lt;/Code&amp;gt;
&amp;lt;Message&amp;gt;Access Denied&amp;lt;/Message&amp;gt;
&amp;lt;RequestId&amp;gt;BJNMX4V6S5NHCN9Q&amp;lt;/RequestId&amp;gt;
&amp;lt;HostId&amp;gt;
gW79WxeQfreLOY+bmPKSxrRBaOUJ/girPZsMev1icrU5foITkTwdF8njH6VnkKJVZRjibxm0P/I=
&amp;lt;/HostId&amp;gt;
&amp;lt;/Error&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부에서 접속 가능한 ACL로 파일을 업로드하려면 params 변수에 public-read로 ACL을 설정해야 한다.&lt;br /&gt;이 경우 해당 S3 버킷의 ACL도 퍼블릭 액세스가 허용된 상태여야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 버킷의 퍼블릭 액세스를 허용하는 방법은 다음 문서를 참고한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참고 문서&amp;nbsp;: &lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/WebsiteAccessPermissionsReqd.html&quot;&gt;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/WebsiteAccessPermissionsReqd.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;const params = {
      Bucket: AWS_S3_BUCKET,
      Key: String(file.originalname),
      Body: file.buffer,
      ACL: 'public-read',
    };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AWS</category>
      <author>mr.november11</author>
      <guid isPermaLink="true">https://november11tech.tistory.com/186</guid>
      <comments>https://november11tech.tistory.com/186#entry186comment</comments>
      <pubDate>Sun, 9 Jan 2022 08:55:57 +0900</pubDate>
    </item>
    <item>
      <title>[React] yarn build 후 배포한 페이지에서 tailwind.config.js 에 정의한 커스텀 컬러가 적용되지 않을 때 해결 방법</title>
      <link>https://november11tech.tistory.com/185</link>
      <description>&lt;ul&gt;
&lt;li&gt;&lt;p&gt;문제 현상 : yarn build 후 배포한 페이지에서 tailwind.config.js 에 정의한 커스텀 컬러가 적용되지 않음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;해결 방법&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;npx tailwindcss-cli@latest build -o tailwind.css&lt;/code&gt; 으로 tailwind 관련 css 파일 생성&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt; $ npx tailwindcss-cli@latest build -o tailwind.css

    tailwindcss 2.0.4

      Building from default CSS... (No input file provided)

    ✅ Finished in 3.04 s
      Size: 4.28MB
      Saved to tailwind.css&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;생성된 &lt;code&gt;tailwind.css&lt;/code&gt; 파일을 public 폴더에 복사 &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;public/index.html&lt;/code&gt; 파일에 head 내에서 tailwind.css 을 로드&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;link href=&amp;quot;/tailwind.css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>React</category>
      <author>mr.november11</author>
      <guid isPermaLink="true">https://november11tech.tistory.com/185</guid>
      <comments>https://november11tech.tistory.com/185#entry185comment</comments>
      <pubDate>Tue, 6 Apr 2021 22:54:21 +0900</pubDate>
    </item>
    <item>
      <title>[React Native] Expo 에서 iOS 시뮬레이터 실행 시 오류 발생 해결 방법</title>
      <link>https://november11tech.tistory.com/184</link>
      <description>&lt;p&gt;1. 현상&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;expo에서 iOS 시뮬레이터를 실행할 경우 터미널 상에서 아래와 같이 &lt;br /&gt;&lt;b&gt;'System Events에 Apple 이벤트를 보낼 권한이 없습니다.'&lt;/b&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;에러가 발생한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Expo 개발 웹 UI 상에서는 Error opening is simulator. Check Metro logs for details. 라는 에러 문구가 나온다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607356718852&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(node:47980) UnhandledPromiseRejectionWarning: Error: Command failed: osascript -e tell app &quot;System Events&quot; to count processes whose name is &quot;Simulator&quot;
28:69: execution error: System Events에 Apple 이벤트를 보낼 권한이 없습니다. (-1743)

    at ChildProcess.exithandler (child_process.js:295:12)
    at ChildProcess.emit (events.js:223:5)
    at maybeClose (internal/child_process.js:1021:16)
    at Socket.&amp;lt;anonymous&amp;gt; (internal/child_process.js:430:11)
    at Socket.emit (events.js:223:5)
    at Pipe.&amp;lt;anonymous&amp;gt; (net.js:664:12)
(node:47980) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 14)
(node:47980) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2. 해결 방법&lt;/p&gt;
&lt;p&gt;Mac OS의 환경 설정에서 Termial -&amp;gt; System Events 항목을 활성화한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2of5S/btqPiaOkSa2/7x1ZMijOUzOVKIwOkL93Qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2of5S/btqPiaOkSa2/7x1ZMijOUzOVKIwOkL93Qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2of5S/btqPiaOkSa2/7x1ZMijOUzOVKIwOkL93Qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2of5S%2FbtqPiaOkSa2%2F7x1ZMijOUzOVKIwOkL93Qk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>리뷰/기타</category>
      <author>mr.november11</author>
      <guid isPermaLink="true">https://november11tech.tistory.com/184</guid>
      <comments>https://november11tech.tistory.com/184#entry184comment</comments>
      <pubDate>Tue, 8 Dec 2020 01:03:57 +0900</pubDate>
    </item>
    <item>
      <title>[spring.io] 레디스로 메시지 통신하기(코틀린 ver.)</title>
      <link>https://november11tech.tistory.com/183</link>
      <description>&lt;h1&gt;# [spring.io] 레디스로 메시지 통신하기(코틀린 ver.)&lt;/h1&gt;
&lt;p&gt;#kotlin #spring&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;참고 사이트 : &lt;a href=&quot;https://spring.io/guides/gs/messaging-redis/&quot;&gt;Getting Started | Messaging with Redis&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;레디스 서버 실행하기&lt;/h2&gt;
&lt;p&gt;spring.io 예제에서는 brew 를 사용하여 reds 서버를 설치하고 실행합니다.&lt;br&gt;이번 예제에서는 docker 를 사용하여 redis 를 실행합니다.&lt;br&gt;참고 사이트 : &lt;a href=&quot;https://hub.docker.com/_/redis/&quot;&gt;https://hub.docker.com/_/redis/&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;실행 명령어 : &lt;code&gt;docker run —name some-redis -p 6379:6379 -d redis&lt;/code&gt; &lt;/p&gt;
&lt;h2&gt;Spring Initializr 로 시작하기&lt;/h2&gt;
&lt;p&gt;메이븐 기준으로 다음 의존성을 추가한 프로젝트를 생성합니다. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Spring Data Redis (Access+Driver)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependencies&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-starter-data-redis&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.jetbrains.kotlin&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;kotlin-reflect&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.jetbrains.kotlin&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;kotlin-stdlib-jdk8&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;

    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-starter-test&amp;lt;/artifactId&amp;gt;
        &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
        &amp;lt;exclusions&amp;gt;
            &amp;lt;exclusion&amp;gt;
                &amp;lt;groupId&amp;gt;org.junit.vintage&amp;lt;/groupId&amp;gt;
                &amp;lt;artifactId&amp;gt;junit-vintage-engine&amp;lt;/artifactId&amp;gt;
            &amp;lt;/exclusion&amp;gt;
        &amp;lt;/exclusions&amp;gt;
    &amp;lt;/dependency&amp;gt;
&amp;lt;/dependencies&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Receiver 클래스 생성하기&lt;/h2&gt;
&lt;p&gt;메시지를 받고 응답하는 Receiver 클래스를 생성합니다.&lt;br&gt;Receiver는 POJO로 메시지를 수신하는 메소드를 정의합니다.&lt;br&gt;예제에서는 메시지를 전달 받으면 &lt;code&gt;receiveMessage&lt;/code&gt; 함수를 호출할 예정입니다. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;class Receiver {
    val LOGGER = LoggerFactory.getLogger(Receiver::class.java)
    val counter = AtomicInteger()

    fun receiveMessage( message: String ) {
        LOGGER.info(“Received &amp;lt; $message &amp;gt;”);
        counter.incrementAndGet();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;리스너를 등록하고 메시지를 보내기&lt;/h2&gt;
&lt;p&gt;스프링 데이터 레디스는 레디스와 메시지를 주고받기 위한 모든 컴포넌트를 제공합니다.&lt;br&gt;    - 커넥션 팩토리&lt;br&gt;    - 메시지 리스너 컨테이너&lt;br&gt;    - 레디스 템플릿&lt;br&gt;예제에서는 레디스 템플릿을 사용하여 메시지를 보내고 Receiver  메시지 리스너로 등록합니다. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@SpringBootApplication
class RedisApplication{

    val LOGGER = LoggerFactory.getLogger(RedisApplication::class.java)

    @Bean
    fun container(
            connectionFactory: RedisConnectionFactory,
            listernerAdapter: MessageListenerAdapter) : RedisMessageListenerContainer {

        val container = RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory)
        container.addMessageListener(listernerAdapter, PatternTopic(“chat”))

        return container
    }

    @Bean
    fun listenerAdapter(receiver: Receiver) : MessageListenerAdapter {
        return MessageListenerAdapter(receiver, “receiveMessage”)
    }

    @Bean
    fun receiver() : Receiver {
        return Receiver()
    }

    @Bean
    fun template(connectionFactory: RedisConnectionFactory) : StringRedisTemplate {
        return StringRedisTemplate(connectionFactory)
    }

}

fun main(args: Array&amp;lt;String&amp;gt;) {

    val ctx = runApplication&amp;lt;RedisApplication&amp;gt;(*args)
    val template : StringRedisTemplate = ctx.getBean(StringRedisTemplate::class.java)

    val receiver : Receiver = ctx.getBean(Receiver::class.java)

    while(receiver.counter.toInt() == 0) {
        *println*(“Sending message…”);
        template.convertAndSend(“chat”, “Hello from Redis!”);
        Thread.sleep(500L);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;실행 결과&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  .   ____          _            __ _ _
 /\\ / ___&amp;#39;_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | &amp;#39;_ | &amp;#39;_| | &amp;#39;_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  &amp;#39;  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.4.RELEASE)

INFO 61756 --- [           main] com.example.redis.RedisApplicationKt     : Starting RedisApplicationKt on C02XHD9VJG5J with PID 61756 (/Users/user/study/redis/target/classes started by in /Users/user/study/redis)
INFO 61756 --- [           main] com.example.redis.RedisApplicationKt     : No active profile set, falling back to default profiles: default
INFO 61756 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
INFO 61756 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode.
INFO 61756 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 8ms. Found 0 Redis repository interfaces.
INFO 61756 --- [           main] com.example.redis.RedisApplicationKt     : Started RedisApplicationKt in 1.298 seconds (JVM running for 1.737)
Sending message...
2020-10-19 20:18:43.770  INFO 61756 --- [    container-2] com.example.redis.Receiver               : Received &amp;lt; Hello from Redis! &amp;gt;

Process finished with exit code 0&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Spring</category>
      <author>mr.november11</author>
      <guid isPermaLink="true">https://november11tech.tistory.com/183</guid>
      <comments>https://november11tech.tistory.com/183#entry183comment</comments>
      <pubDate>Mon, 19 Oct 2020 20:31:59 +0900</pubDate>
    </item>
    <item>
      <title>[spring.io] 스프링 JDBC를  활용하여 관계형 데이터베이스 연동하기(코틀린 ver.)</title>
      <link>https://november11tech.tistory.com/182</link>
      <description>&lt;h1&gt;[spring.io] 스프링 JDBC를  활용하여 관계형 데이터베이스 연동하기(코틀린 ver.)&lt;/h1&gt;
&lt;p&gt;#kotlin #spring&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;참고 사이트 : &lt;a href=&quot;https://spring.io/guides/gs/relational-data-access/&quot;&gt;https://spring.io/guides/gs/relational-data-access/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;스프링의 JdbcTemplate 을  사용하여 관계형 데이터베이스에 저장된 데이터에 접근하는 애플리케이션을 작성합니다. &lt;/p&gt;
&lt;h2&gt;Spring Initializr 로 시작하기&lt;/h2&gt;
&lt;p&gt;메이븐 기준으로 다음 의존성을 추가한 프로젝트를 생성합니다. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring DATA JDBC &lt;/li&gt;
&lt;li&gt;H2 Database&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependencies&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-starter-data-jdbc&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.jetbrains.kotlin&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;kotlin-reflect&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.jetbrains.kotlin&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;kotlin-stdlib-jdk8&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;

    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;com.h2database&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;h2&amp;lt;/artifactId&amp;gt;
        &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-starter-test&amp;lt;/artifactId&amp;gt;
        &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
        &amp;lt;exclusions&amp;gt;
            &amp;lt;exclusion&amp;gt;
                &amp;lt;groupId&amp;gt;org.junit.vintage&amp;lt;/groupId&amp;gt;
                &amp;lt;artifactId&amp;gt;junit-vintage-engine&amp;lt;/artifactId&amp;gt;
            &amp;lt;/exclusion&amp;gt;
        &amp;lt;/exclusions&amp;gt;
    &amp;lt;/dependency&amp;gt;
&amp;lt;/dependencies&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Customer 객체 생성하기&lt;/h2&gt;
&lt;p&gt;간단한 데이터 접근 로직 테스트를 위해서 서비스에서 사용할 Customer 클래스를 생성합니다. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;class Customer(
        val id : Long,
        val firstName : String,
        val lastName : String
) {
    override fun toString(): String {
        return “Customer id=$id, firstName=‘$firstName’, lastName=‘$lastName’]”
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;JDBC 로 H2 데이터 데이터 베이스에 SQL 쿼리 실행하기&lt;/h2&gt;
&lt;p&gt;생성자에서 JdbcTemplate 객체를 주입받아 사용합니다.&lt;br&gt;Customer 리스트를 생성한 후  jdbcTemplate.batchUpdate 로  데이터베이스에  적재합니다.&lt;br&gt;적재 이후에는 SELECT 쿼리를 사용하여 전체 Customer 목록을 조회한 후 log로 출력합니다. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@SpringBootApplication
class JdbcApplication(
        val jdbcTemplate: JdbcTemplate
): CommandLineRunner{

    override fun run(vararg args: String?) {
        val log = LoggerFactory.getLogger(this.javaClass)

        log.info(&amp;quot;Creating tables&amp;quot;);

        jdbcTemplate.execute(&amp;quot;DROP TABLE customers IF EXISTS&amp;quot;);
        jdbcTemplate.execute(&amp;quot;CREATE TABLE customers(&amp;quot; + &amp;quot;id SERIAL, first_name VARCHAR(255), last_name VARCHAR(255))&amp;quot;);

        val splitUpNames = listOf&amp;lt;String&amp;gt;(&amp;quot;John Woo&amp;quot;, &amp;quot;Jeff Dean&amp;quot;, &amp;quot;Josh Bloch&amp;quot;, &amp;quot;Josh Long&amp;quot;)
                .map{ it.split(&amp;quot; &amp;quot;).toTypedArray()}


        splitUpNames.forEach { log.info(&amp;quot;Inserting customer record for ${it[0]} / ${it[1]}&amp;quot;) }

        jdbcTemplate.batchUpdate(&amp;quot;INSERT INTO customers(first_name, last_name) VALUES (?,?)&amp;quot;, splitUpNames);

        log.info(&amp;quot;Querying for customer records where first_name = &amp;#39;Josh&amp;#39;:&amp;quot;);

        jdbcTemplate.query(&amp;quot;SELECT id, first_name, last_name FROM customers&amp;quot;) {
            rs, rowNum -&amp;gt; println(&amp;quot;${rs.getLong(&amp;quot;id&amp;quot;)}, ${rs.getString(&amp;quot;first_name&amp;quot;)}, ${rs.getString(&amp;quot;last_name&amp;quot;)} $rowNum&amp;quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;출력 결과&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;1, John, Woo 0
2, Jeff, Dean 1
3, Josh, Bloch 2
4, Josh, Long 3&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Spring</category>
      <category>Kotlin</category>
      <category>spring</category>
      <category>스프링</category>
      <category>코틀린</category>
      <author>mr.november11</author>
      <guid isPermaLink="true">https://november11tech.tistory.com/182</guid>
      <comments>https://november11tech.tistory.com/182#entry182comment</comments>
      <pubDate>Sun, 18 Oct 2020 01:39:48 +0900</pubDate>
    </item>
    <item>
      <title>[spring.io]RESTful 웹 서비스 호출하기(코틀린 ver.)</title>
      <link>https://november11tech.tistory.com/181</link>
      <description>&lt;h1&gt;# [spring.io]RESTful 웹 서비스 호출하기(코틀린 ver.)&lt;/h1&gt;
&lt;p&gt;#kotlin #spring&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;참고 사이트 : &lt;a href=&quot;https://spring.io/guides/gs/consuming-rest/&quot;&gt;https://spring.io/guides/gs/consuming-rest/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;RESTful 웹 서비스 호출하기&lt;/h1&gt;
&lt;p&gt;스프링의 &lt;code&gt;RestTemplate&lt;/code&gt;을 사용하여 RESTful 웹 서비스를 호출하는 예제입니다.&lt;br&gt;호출할 테스트 URL 은 &lt;a href=&quot;https://gturnquist-quoters.cfapps.io/api/random&quot;&gt;https://gturnquist-quoters.cfapps.io/api/random&lt;/a&gt; 입니다.&lt;/p&gt;
&lt;h2&gt;Spring Initializr 로 시작하기&lt;/h2&gt;
&lt;p&gt;메이븐 기준으로 다음 의존성을 추가한 프로젝트를 생성합니다. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Spring Web&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-&amp;lt;dependencies&amp;gt;&quot;&gt;    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;com.fasterxml.jackson.module&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;jackson-module-kotlin&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.jetbrains.kotlin&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;kotlin-reflect&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.jetbrains.kotlin&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;kotlin-stdlib-jdk8&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;

    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-starter-test&amp;lt;/artifactId&amp;gt;
        &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
        &amp;lt;exclusions&amp;gt;
            &amp;lt;exclusion&amp;gt;
                &amp;lt;groupId&amp;gt;org.junit.vintage&amp;lt;/groupId&amp;gt;
                &amp;lt;artifactId&amp;gt;junit-vintage-engine&amp;lt;/artifactId&amp;gt;
            &amp;lt;/exclusion&amp;gt;
        &amp;lt;/exclusions&amp;gt;
    &amp;lt;/dependency&amp;gt;
&amp;lt;/dependencies&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;REST 리소스 가져오기&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://gturnquist-quoters.cfapps.io/api/random&quot;&gt;https://gturnquist-quoters.cfapps.io/api/random&lt;/a&gt; URL을 호출하면 다음과 같은 JSON 응답을 받아옵니다. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
   type: &amp;quot;success&amp;quot;,
   value: {
      id: 10,
      quote: &amp;quot;Really loving Spring Boot, makes stand alone Spring apps easy.&amp;quot;
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;도메인 클래스 생성하기&lt;/h2&gt;
&lt;h3&gt;Quote 클래스 생성&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;class Quote (
        val type : String,
        val value : Value
){
    override fun toString(): String {
        return &amp;quot;Quote{&amp;quot; +
                &amp;quot;type=&amp;#39;&amp;quot; + type + &amp;#39;\&amp;#39;&amp;#39; +
                &amp;quot;, value=&amp;quot; + value +
                &amp;#39;}&amp;#39;;

    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Value 클래스 생성&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;class Value(
        val id: Long,
        val quote : String
) {
    override fun toString(): String {
        return “Value{“ +
                “id=“ + id +
                “, quote=‘” + quote + ‘\’’ +
                ‘}’;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;애플리케이션 생성&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;참고 사이트에서는 RestTemplate 의 getForObject 함수를 사용하지만,&lt;br&gt;코틀린에서는 getForObject 를 사용할 경우 코드가 조금 복잡해지기 떄문에 getForEntity를 사용합니다. &lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun main() {
    val quote: ResponseEntity&amp;lt;Quote&amp;gt; = RestTemplate().*getForEntity*&amp;lt;Quote&amp;gt;(
            “https://gturnquist-quoters.cfapps.io/api/random”)
    *println*(quote.toString())
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;실행 결과&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&amp;lt;200,Quote{type=&amp;#39;success&amp;#39;, value=Value{id=5, quote=&amp;#39;Spring Boot solves this problem. It gets rid of XML and wires up common components for me, so I don&amp;#39;t have to spend hours scratching my head just to figure out how it&amp;#39;s all pieced together.&amp;#39;}},[Content-Type:&amp;quot;application/json;charset=UTF-8&amp;quot;, Date:&amp;quot;Sat, 17 Oct 2020 14:36:41 GMT&amp;quot;, X-Vcap-Request-Id:&amp;quot;ad5f0439-f4ee-42b7-495c-0c44834274d5&amp;quot;, Content-Length:&amp;quot;235&amp;quot;, Connection:&amp;quot;keep-alive&amp;quot;]&amp;gt;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Spring</category>
      <category>Kotlin</category>
      <category>spring</category>
      <category>스프링</category>
      <category>코틀린</category>
      <author>mr.november11</author>
      <guid isPermaLink="true">https://november11tech.tistory.com/181</guid>
      <comments>https://november11tech.tistory.com/181#entry181comment</comments>
      <pubDate>Sat, 17 Oct 2020 23:40:13 +0900</pubDate>
    </item>
  </channel>
</rss>