<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>YoungSoo</title>
    <link>https://youngsoosoo.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 19 May 2026 01:55:54 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>YoungSooSoo</managingEditor>
    <item>
      <title>[오류 해결] not upgraded to websocket</title>
      <link>https://youngsoosoo.tistory.com/238</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;605&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dRPB4g/btsxq4v2lga/fMGMUXk2Ohx63rzbDJhLpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dRPB4g/btsxq4v2lga/fMGMUXk2Ohx63rzbDJhLpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dRPB4g/btsxq4v2lga/fMGMUXk2Ohx63rzbDJhLpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdRPB4g%2Fbtsxq4v2lga%2FfMGMUXk2Ohx63rzbDJhLpK%2Fimg.png&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;297&quot; height=&quot;394&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;605&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 발생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀원 중 하나가 Flutter로 개발 중인 Furry-Friend 프로젝트에서 백엔드 서버의 WebSocket Stomp와 연결하는 과정에서 오류를 겪었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 해결 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 Front-end를 Next.js로 개발한 부분에서 문제가 없어 보여 Flutter 코드를 의심했지만, 팀원이 많은 라이브러리와 방법을 시도해도 문제가 해결되지 않았습니다.&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;그러던 중 Flutter 및 Dart와 WebSocket 연결에 관한 문제 해결을 위한 글을 찾아보았습니다. 이 글을 통해 다른 개발자가 비슷한 문제를 겪었으며 NGINX를 프록시로 사용하고 있었다는 사실을 알게 되었습니다. 그리고 해당 글에서 NGINX 설정에 다음 코드를 추가해야 한다는 정보를 얻었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1696662475699&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection &quot;upgrade&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드를 추가한 후에야 WebSocket 연결이 성공하게 되었습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;늦은 시간까지 고생하신 팀원에게 다시 한 번 감사합니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccnFTD/btsxsRiGzsN/8GC5cmLtmW7i7qekrslIe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccnFTD/btsxsRiGzsN/8GC5cmLtmW7i7qekrslIe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccnFTD/btsxsRiGzsN/8GC5cmLtmW7i7qekrslIe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccnFTD%2FbtsxsRiGzsN%2F8GC5cmLtmW7i7qekrslIe1%2Fimg.png&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;278&quot; height=&quot;306&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;참고 링크&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/64125735/flutter-socket-io-error-on-websocketexception-connection-to-was-not-upgraded-to&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/64125735/flutter-socket-io-error-on-websocketexception-connection-to-was-not-upgraded-to&lt;/a&gt;&lt;/p&gt;</description>
      <category>프로젝트</category>
      <author>YoungSooSoo</author>
      <guid isPermaLink="true">https://youngsoosoo.tistory.com/238</guid>
      <comments>https://youngsoosoo.tistory.com/238#entry238comment</comments>
      <pubDate>Sat, 7 Oct 2023 16:12:51 +0900</pubDate>
    </item>
    <item>
      <title>Test Driven Development</title>
      <link>https://youngsoosoo.tistory.com/236</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Test&amp;nbsp;Driven&amp;nbsp;Developmen&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 방법론&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCeH3R/btsrCxQSrKP/oHOZE7OW6JFTWcNoNOuet0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCeH3R/btsrCxQSrKP/oHOZE7OW6JFTWcNoNOuet0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCeH3R/btsrCxQSrKP/oHOZE7OW6JFTWcNoNOuet0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCeH3R%2FbtsrCxQSrKP%2FoHOZE7OW6JFTWcNoNOuet0%2Fimg.png&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;541&quot; height=&quot;359&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;680&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Red - Green - Refactor 순으로 개발&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 1. Red - 실패하는 테스트 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 2. Green - 테스트를 통과하는 최소한의 코딩&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 3. Refactor - 구현 코드 개선과 테스트 통과 유지&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;이러한 사이클을 통해 TDD를 했을 땐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 1. 복잡도가 늦은, 테스트 가능한 코드로 구현할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 2. 쉽게 발견하기 어려운 엣지 케이스를 놓치지 않게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 3. 구현에 대한 빠른 피드백을 받을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 4. 과감한 리팩토링이 가능해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;반대로 선 기능 구현 후, 테스트를 작성했을 땐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 1. 테스트 자체의 누락 가능성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 2. 특정 테스트 케이스(해피 케이스)만 검증할 가능성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 3. 잘못된 구현을 다소 늦게 발견할 가능성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 단점들을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TDD - 관점의 변화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 1. 테스트는 구현부 검증을 위한 보조 수단 &amp;rarr; 테스트와 상호 작용하며 발전하는 구현부&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 2. 클라이언트 관점에서의 피드백을 주는 Test Driven&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;665&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8Cos4/btsrBmvBILh/Skv5vq77ecqgHyB2WdZG3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8Cos4/btsrBmvBILh/Skv5vq77ecqgHyB2WdZG3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8Cos4/btsrBmvBILh/Skv5vq77ecqgHyB2WdZG3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8Cos4%2FbtsrBmvBILh%2FSkv5vq77ecqgHyB2WdZG3k%2Fimg.png&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;1534&quot; height=&quot;665&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;665&quot;/&gt;&lt;/span&gt;&lt;/figure&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/practical-testing-%EC%8B%A4%EC%9A%A9%EC%A0%81%EC%9D%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%80%EC%9D%B4%EB%93%9C/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.inflearn.com/course/practical-testing-%EC%8B%A4%EC%9A%A9%EC%A0%81%EC%9D%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%80%EC%9D%B4%EB%93%9C/dashboard&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1692345771957&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Practical Testing: 실용적인 테스트 가이드 - 인프런 | 강의&quot; data-og-description=&quot;이 강의를 통해 실무에서 개발하는 방식 그대로, 깔끔하고 명료한 테스트 코드를 작성할 수 있게 됩니다. 테스트 코드가 왜 필요한지, 좋은 테스트 코드란 무엇인지 궁금하신 모든 분을 위한 강&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/practical-testing-%EC%8B%A4%EC%9A%A9%EC%A0%81%EC%9D%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%80%EC%9D%B4%EB%93%9C/dashboard&quot; data-og-url=&quot;https://www.inflearn.com/course/practical-testing-%EC%8B%A4%EC%9A%A9%EC%A0%81%EC%9D%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%80%EC%9D%B4%EB%93%9C&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hgVhf/hyTFgntplB/J6MIYWnSmP60veje9XcXMk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/dP4HSR/hyTFoy2F5N/H5MKgrYdzdw5USbETkR7f0/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/bYr9Xd/hyTFdqLg7P/5Uq7fOiq9a48YyakmyNSEk/img.png?width=2772&amp;amp;height=1558&amp;amp;face=0_0_2772_1558&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/practical-testing-%EC%8B%A4%EC%9A%A9%EC%A0%81%EC%9D%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%80%EC%9D%B4%EB%93%9C/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/practical-testing-%EC%8B%A4%EC%9A%A9%EC%A0%81%EC%9D%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%80%EC%9D%B4%EB%93%9C/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hgVhf/hyTFgntplB/J6MIYWnSmP60veje9XcXMk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/dP4HSR/hyTFoy2F5N/H5MKgrYdzdw5USbETkR7f0/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/bYr9Xd/hyTFdqLg7P/5Uq7fOiq9a48YyakmyNSEk/img.png?width=2772&amp;amp;height=1558&amp;amp;face=0_0_2772_1558');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Practical Testing: 실용적인 테스트 가이드 - 인프런 | 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 강의를 통해 실무에서 개발하는 방식 그대로, 깔끔하고 명료한 테스트 코드를 작성할 수 있게 됩니다. 테스트 코드가 왜 필요한지, 좋은 테스트 코드란 무엇인지 궁금하신 모든 분을 위한 강&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;박우빈 님의 강의인 실용적인 테스트 가이드 중 TDD 강의를 보고 학습을 위해 작성한 내용입니다.&lt;/p&gt;</description>
      <category>CS/Software Engineering</category>
      <category>TDD</category>
      <author>YoungSooSoo</author>
      <guid isPermaLink="true">https://youngsoosoo.tistory.com/236</guid>
      <comments>https://youngsoosoo.tistory.com/236#entry236comment</comments>
      <pubDate>Fri, 18 Aug 2023 17:12:22 +0900</pubDate>
    </item>
    <item>
      <title>[WebSocket] 명령과 조회를 분리하는 CQRS로 개발하기 - 4</title>
      <link>https://youngsoosoo.tistory.com/235</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 게시물에서는 WebSocket과 Stomp를 사용해서 pub/sub 방식으로 채팅 서비스를 개발했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 시간에는 이전 채팅 조회와 많은 고민이 있었던 읽음 처리에 대한 글을 작성하려고 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;이전 채팅 조회&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 채팅을 조회하는 기능에서 요구되는 사항은 아래와 같았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 채팅방&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 안 읽은 채팅의 개수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 마지막 채팅 내용&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;저는 SQLD 자격증을 보유하고 있어서 세 개를 조회하는 쿼리를 작성하는 것은 어렵다고 생각하진 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 조인과 서브쿼리를 사용해 요구하는 데이터를 조회하게 되었는데, 이 과정에서 N+1 문제와 고민이 생기게 되었습니다. N+1 문제는 하위 엔티티를 조회할 때 상위 엔티티가 같이 조회되는 상황을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;또한 이렇게 조인과 서브쿼리를 통해 기능을 개발했는데, 이것을 여러 개의 작은 쿼리로 나누어 요청하게 되면 어떤 것이 장점일까가 고민이었습니다. 작은 쿼리로 나누게 된다면 여러 번 트랜잭션이 발생하는 만큼 비용이 더 발생하게 됩니다. 하지만 그만큼 더 빨라질 것으로 예상이 됩니다.&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;이후에 CQRS를 적용할 예정이기 때문에 조회를 전부 변경해줄 것이지만 이 상황뿐만 아니라 언젠가 꼭 고민해야 할 문제인 거 같았습니다. 상황에 따라 &lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;데이터베이스 성능과 응답 시간을 모니터링하고, 비용과 성능 사이의 트레이드오프를 평가하여 최적의 방법을 찾아야 할 거 같습니다. 더 좋은 방법이 있으면 댓글로 알려주시면 정말 감사하겠습니다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;채팅 읽음 처리&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;처음에 채팅 읽음 처리라고 생각했을 때는 쉽게 구현할 것이라는 생각이 들었습니다. 하지만 막상 구현을 하는 도중에 많이 막혔습니다. 채팅 읽음 처리에 대한 기능은 아래와 같았습니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;1. 처음 채팅방을 들어왔을 때&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;2. &lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;상대방이 웹 소켓을 연결하고 채팅방을 확인하고 있을 때&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;위와 같이 두 개의 조건을 만족했을 때 읽음 처리가 되어야 한다고 생각했습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;(1:1 채팅방)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;이를 구현하기 위해 먼저 데이터베이스에 chatMessageRead라는 Column을 만들어 해당 메시지를 조회했는 지를 체크하는 방식을 생각해 데&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;이터 베이스를 모델링 했습니다. 읽음 처리가 되는 과정을 설명하겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;1. 사용자가 메시지를 전송하고 데이터베이스에 저장합니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;2. 웹 소켓에 연결과 화면을 보고 있고, 메시지를 받은 구독자와 메시지를 보낸 사람이 다르다면 읽음 요청을 백엔드 서버로 전송했습니다.(Front-End: focus와 blur 그리고 window의 beforeunload 이벤트 사용)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;3. 백엔드에서 읽음 처리를 하는 로직을 사용한 후 응답을 하면 프런트에서 반영하는 식으로 했습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&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;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;이와 같은 방법을 통해 읽음 처리를 해주었습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;메모리 릭 발생&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;&lt;b&gt;메모리 릭&lt;/b&gt;은 &lt;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: start;&quot;&gt;프&lt;span style=&quot;background-color: #ffffff;&quot;&gt;로그램이 더 이상 필요하지 않은 메모리를 해제하지 않고 계속 보유하게 되는 상태를 말합니다. 자바에서는 &lt;b&gt;GC(Garbage Collector)&lt;/b&gt;가 사&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;용하지 않는 메모리를 자동으로 수거하긴 하지만 &lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;모든 메모리 누수를 감지하고 해결하는 것은 보장되지 않습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&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;웹 소켓을 사용하며 쓰레드 관리를 해주지 않아 메모리 릭이 발생한 것으로 보이는 문제가 발생했습니다.ㅜㅜ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2023-07-28 163239.jpg&quot; data-origin-width=&quot;2002&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bi9V5X/btsph9TUDZh/Z42uoolFPeifDqJN4OZk01/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bi9V5X/btsph9TUDZh/Z42uoolFPeifDqJN4OZk01/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bi9V5X/btsph9TUDZh/Z42uoolFPeifDqJN4OZk01/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbi9V5X%2Fbtsph9TUDZh%2FZ42uoolFPeifDqJN4OZk01%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;2002&quot; height=&quot;245&quot; data-filename=&quot;화면 캡처 2023-07-28 163239.jpg&quot; data-origin-width=&quot;2002&quot; data-origin-height=&quot;245&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;다음 글은...&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 게시물에 이를 해결하고 알림 서비스를 개발하는 게시물을 작성해 볼까 합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 글을 읽어주셔서 감사하고 지적도 해주시면 감사하겠습니다!&lt;/p&gt;</description>
      <category>프로젝트</category>
      <author>YoungSooSoo</author>
      <guid isPermaLink="true">https://youngsoosoo.tistory.com/235</guid>
      <comments>https://youngsoosoo.tistory.com/235#entry235comment</comments>
      <pubDate>Sat, 29 Jul 2023 22:49:59 +0900</pubDate>
    </item>
    <item>
      <title>[WebSocket] 명령과 조회를 분리하는 CQRS로 개발하기 - 3</title>
      <link>https://youngsoosoo.tistory.com/234</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 게시물에서는 WebSocket과 Stomp를 사용해서 pub/sub 방식으로 간단한 채팅을 구현하는 실습을 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 게시물에서는 웹 소켓과 Rest API를 통해서 구현한 기본적인 채팅 서비스를 구현해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;사용자 입장의 채팅 서비스&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;채팅 서비스를 사용자 입장에서 처음 채팅을 시작할 때 게시글에 있는 채팅 보내기를 통해 채팅방을 구성하게 됩니다. 이때 채팅방과 채팅 참여자가 테이블에 등록이 됩니다. 채팅 목록을 조회하고 채팅방을 선택할 때 WebSocket 연결을 하고 Stomp를 통해 pub/sub 형식의 채팅이 이어지게 됩니다. 이때, 채팅방 고유 번호를 사용해 각 채팅방마다 발신자와 구독자에게만 메시지를 전달하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;필요 의존성 추가&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1689660220709&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	// Chat
	implementation 'org.springframework.boot:spring-boot-starter-websocket'
	// implementation 'org.springframework.kafka:spring-kafka'
	implementation 'org.webjars:stomp-websocket:2.3.3'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kafka는 아직 사용하지 않았지만 추후에 기능을 추가하기 위해 추가한 것을 참고해 주시면 감사하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;WebSocketConfig 추가&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1689660321164&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.setApplicationDestinationPrefixes(&quot;/pub&quot;);    // 메세지 받기
        config.enableSimpleBroker(&quot;/sub&quot;);  // 메세지 보내기
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry
            .addEndpoint(&quot;/chats/furry&quot;)
            .setAllowedOriginPatterns(&quot;*&quot;)
            .withSockJS();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stomp를 사용하기 위해 WebSocketMessageBrokerConfigurer를 구현할 WebSocketConfig를 만들어 메시지를 받고 다시 보내는 주소를 설정해 주었습니다. 또한 WebSocket 연결을 위한 Endpoint를 설정하고 CORS 설정을 해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Controller&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1689661129801&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @MessageMapping(&quot;/chats/{chat_room_id}&quot;)
    @SendTo(&quot;/sub/chats/{chat_room_id}&quot;)
    public ApiResponse&amp;lt;ChatMessageResponseDTO&amp;gt; chatting(ChatMessageRequestDTO chatMessageRequestDTO, @DestinationVariable(value = &quot;chat_room_id&quot;) Long chat_room_id, SimpMessageHeaderAccessor headerAccessor) {

        // Token 추출
        String accessToken = headerAccessor.getFirstNativeHeader(&quot;Authorization&quot;);
        try {

            ChatMessageResponseDTO chatMessageResponseDTO = chatMessageService.saveChatMessage(chat_room_id, chatMessageRequestDTO, accessToken);

            return ApiResponse.success(&quot;success&quot;, chatMessageResponseDTO);
        }catch (Exception e){

            log.error(&quot;메시지 전송 실패: &quot; + e.getMessage(), e);
            return ApiResponse.fail(400, &quot;메시지 전송 실패: &quot; + e.getMessage());
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller에서 메시지를 받고 Service에서 로직을 처리한 뒤에 메시지 정보가 리턴하게 됩니다. 이러한 과정을 끊김 없이 진행하기 위해 Http 메서드가 아닌 WebSocket을 통해 진행하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Service&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1689661289062&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // 메시지 저장 메서드
    public ChatMessageResponseDTO saveChatMessage(final Long chatRoomId, final ChatMessageRequestDTO chatMessageRequestDTO, String accessToken) throws Exception{

        try {
            
            JwtResponse jwtResponse = tokenService.getMember(accessToken);
            ChatRoom chatRoom = chatRoomService.findChatRoom(chatRoomId);
            
            // Entity로 변환 후 저장
            ChatMessage chatMessage = chatMessageRequestDTO.dtoToEntity(chatMessageRequestDTO, jwtResponse, chatRoom);
            chatMessageRepository.save(chatMessage);

            // DTO로 변환 후 응답
            return chatMessage.entityToDTO(chatMessage);
        } catch (Exception e){

            log.error(&quot;ChatMessageService 오류 발생: &quot; + e.getMessage(), e);
            throw new Exception(&quot;ChatMessageService 오류 발생: &quot; + e.getMessage());
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함께 보내온 메시지와 채팅방 고유 번호 그리고 토큰을 받아 로직을 진행해 줍니다. JWT 토큰의 유효성을 검증하고 메시지를 저장한 후 메시지를 리턴해주게 됩니다. 그렇게 되면 JPA의 Save 메서드를 통해 ChatMessage Entity를 저장해 주게 되고 이 결과를 리턴해주면 종료가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Tests&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이러한 과정을 진행하기 전에 Test를 통해 먼저 하게 되었습니다. Test를 하는 과정에서 ChatMessage에 해당하는 ChatRoom이 없었기에 ChatMessage를 같이 저장해 주기 위해 cascadeType을 All로 저장해 주어 실제 개발을 하는 과정에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;detached entity passed to persist&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;해당 오류가 발생했었습니다. 이 오류는 이미 저장되어 있는 ChatRoom 테이블의 값을 읽어와 다시 저장하려고 해 발생하는 오류였고, 오류를 해결하는 과정에서 cascade 설정이 원인인 것을 알게 되었습니다. 이것을 해결하기 위해 cascade를 지웠고, Test 또한 고쳐주었습니다. 하지만 잘못된 Test를 한 것이다라는&amp;nbsp;생각이 들었고, Test에 대한 공부를 해야겠다는 생각이 들었습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;채팅 구현&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;647&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xNoc8/btsn7J8mUI4/ZjlGMsNmUKF6vC4JvMcqRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xNoc8/btsn7J8mUI4/ZjlGMsNmUKF6vC4JvMcqRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xNoc8/btsn7J8mUI4/ZjlGMsNmUKF6vC4JvMcqRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxNoc8%2Fbtsn7J8mUI4%2FZjlGMsNmUKF6vC4JvMcqRK%2Fimg.png&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;487&quot; height=&quot;647&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;647&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 채팅 기능을 구현하게 되었습니다. 아직 추가해야할 기능들이 많지만 기본적인 채팅이 가능하게 되었습니다. ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에는 읽음 기능을 추가하고 알림 기능을 추가하며 점차 기능을 늘려 나가려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;글을 마치며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 조회를 CQRS가 아닌 기존의 방식대로 구현했지만 더 빠르게 구성하기 위해 NoSQL과 MessageQueue를 통해 CQRS를 구현해 더 빠르게 읽어오도록 할 예정입니다. 다음 게시물에서 CQRS 도입과 읽음 표시에 대한 기능을 추가하는 글을 작성하겠습니다. 읽어주셔서 감사합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트</category>
      <author>YoungSooSoo</author>
      <guid isPermaLink="true">https://youngsoosoo.tistory.com/234</guid>
      <comments>https://youngsoosoo.tistory.com/234#entry234comment</comments>
      <pubDate>Tue, 18 Jul 2023 15:43:19 +0900</pubDate>
    </item>
    <item>
      <title>[WebSocket] 명령과 조회를 분리하는 CQRS로 개발하기 - 2</title>
      <link>https://youngsoosoo.tistory.com/233</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;WebSocket에 대해 알아보자!&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H18UA/btsmBxavmAu/TTu8s3PvwXfkLk1S7yPRmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H18UA/btsmBxavmAu/TTu8s3PvwXfkLk1S7yPRmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H18UA/btsmBxavmAu/TTu8s3PvwXfkLk1S7yPRmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH18UA%2FbtsmBxavmAu%2FTTu8s3PvwXfkLk1S7yPRmk%2Fimg.png&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;552&quot; height=&quot;336&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;웹소켓이란, TCP 접속에 전이중 통신 채널(양방향 통신 채널)을 제공하는 컴퓨터 통신 프로토콜이다. 이를 사용하면 서버와 브라우저 간에 연결된 상태로 데이터를 교환할 수 있습니다. 데이터는 패킷(packet)의 형태로 전달이 되고, HTTP 요청을 하지 않고 양방향 통신이 이루어집니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;패킷이란?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;네트워크 통신에서 데이터를 전송하는 최소 단위&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;WebSocket 패킷은 &lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;헤더(Header)와 페이로드(Payload)로 구성됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;&lt;span style=&quot;color: #374151; text-align: start;&quot;&gt; 헤더에는 패킷의 메타데이터와 제어 정보, &lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: left;&quot;&gt;패킷의 길이, 압축 여부, 메시지 타입 등의 정보가 헤더에 포함될 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;&lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: left;&quot;&gt;페이로드는 실제 전송되는 메시지, 이벤트 데이터, 상태 정보 등이 페이로드에 포함됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;왜 WebSocket을 사용해?&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;WebSocket은 전이중 통신 채널을 제공해서 실시간성을 보장할 수가 있습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;WebSocket을 기반으로 실시간으로 채팅 서비스를 개발하려고 합니다. 하지만 WebSocket은 연결을 유지해야 하기에 서버의 자원 부하가 증가할 수 있습니다. 이러한 서버 부하를 해결해 주기 위해서는 메시지 큐를 도입해 비동기로 메시지 처리를 분산하고 빠르게 처리할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;HTTP를 통해서는 실시간성 통신이 불가능한가?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아닙니다. HTTP를 통해서도 실시간성을 보장하는 기술이 존재합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Polling, Long Polling, Streaming와 같은 기술이 있습니다. 하지만 HTTP는 기본적으로 요청/응답 방식으로 동작하기 때문에 실시간성으로 동작하는 애플리케이션엔 WebSocket이 적절하다고 생각을 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;WebSocket의 동작방식 - 핸드 쉐이킹&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;519&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d2SWoB/btsmBZxZb72/io67oHHF6TrVtYzMKNPi9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d2SWoB/btsmBZxZb72/io67oHHF6TrVtYzMKNPi9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d2SWoB/btsmBZxZb72/io67oHHF6TrVtYzMKNPi9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd2SWoB%2FbtsmBZxZb72%2Fio67oHHF6TrVtYzMKNPi9k%2Fimg.png&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;765&quot; height=&quot;445&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;519&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;STOMP(Simple Text Oriented Messaging Protocol)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 브로커을 활용하여 쉽게 메시지를 주고받을 수 있는 프로토콜&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pub - Sub(발행 - 구독) 방식으로 발신자가 메시지를 발행하면 수신자가 그것을 수신하는 메시징 패러다임&lt;/li&gt;
&lt;li&gt;메시지 중개인: 발신자의 메시지를 받아와서 수신자들에게 메시지를 전달하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebSocket 위에 함께 사용할 수 있는 하위(서브) 프로토콜&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;STOMP를 왜 같이 사용해?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebSocket만 사용한다면 키-값의 형태로 주고받아지지만 STOMP를 같이 사용하게 되면 커맨드, 헤더, 바디의 형태처럼 구조화된 메시지를 전송하고, 메시지 큐와 통합이 가능하게 됩니다. 또한 연결 주소마다 새로 핸들러를 구현하고 설정해 줄 필요가 없습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;동작흐름&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2176&quot; data-origin-height=&quot;989&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ChEek/btsmFg8D6kt/Nt7CfETsSQbDvkASMsEKw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ChEek/btsmFg8D6kt/Nt7CfETsSQbDvkASMsEKw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ChEek/btsmFg8D6kt/Nt7CfETsSQbDvkASMsEKw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FChEek%2FbtsmFg8D6kt%2FNt7CfETsSQbDvkASMsEKw1%2Fimg.png&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;2176&quot; height=&quot;989&quot; data-origin-width=&quot;2176&quot; data-origin-height=&quot;989&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발신자가 구독자에게 메시지를 보낼 때 메시지의 가공이나 처리가 필요하다면 MessageHandler를 통해 메시지를 처리하고 MessageBroker를 통해 다시 응답해 줄 수도 있고, 필요하지 않다면 바로 구독자에게 전송도 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;실습&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 링크를 통해 Spring Boot에서 WebSocket과 STOMP를 통해 실습을 진행해 주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://spring.io/guides/gs/messaging-stomp-websocket/&quot;&gt;https://spring.io/guides/gs/messaging-stomp-websocket/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1688626648189&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Getting Started | Using WebSocket to build an interactive web application&quot; data-og-description=&quot;In Spring&amp;rsquo;s approach to working with STOMP messaging, STOMP messages can be routed to @Controller classes. For example, the GreetingController (from src/main/java/com/example/messagingstompwebsocket/GreetingController.java) is mapped to handle messages t&quot; data-og-host=&quot;spring.io&quot; data-og-source-url=&quot;https://spring.io/guides/gs/messaging-stomp-websocket/&quot; data-og-url=&quot;https://spring.io/guides/gs/messaging-stomp-websocket/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cPoVtG/hyTd4BJNy8/9mZUQPKlAKKkkeGzkLISj1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bBiPy4/hyTfpEgExS/OZRyYo13KH4MQkdh9silm1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://spring.io/guides/gs/messaging-stomp-websocket/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://spring.io/guides/gs/messaging-stomp-websocket/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cPoVtG/hyTd4BJNy8/9mZUQPKlAKKkkeGzkLISj1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bBiPy4/hyTfpEgExS/OZRyYo13KH4MQkdh9silm1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Getting Started | Using WebSocket to build an interactive web application&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;In Spring&amp;rsquo;s approach to working with STOMP messaging, STOMP messages can be routed to @Controller classes. For example, the GreetingController (from src/main/java/com/example/messagingstompwebsocket/GreetingController.java) is mapped to handle messages t&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;의존성 추가&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1688626183944&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	implementation 'org.webjars:webjars-locator-core'
	implementation 'org.webjars:sockjs-client:1.0.2'
	implementation 'org.webjars:stomp-websocket:2.3.3'
	implementation 'org.webjars:bootstrap:3.3.7'
	implementation 'org.webjars:jquery:3.1.1-1'&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Message 클래스 생성&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1688626288824&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class HelloMessage {

  private String name;

  public HelloMessage() {
  }

  public HelloMessage(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}


public class Greeting {

  private String content;

  public Greeting() {
  }

  public Greeting(String content) {
    this.content = content;
  }

  public String getContent() {
    return content;
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;컨트롤러 생성&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1688626321777&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Controller
public class GreetingController {


  @MessageMapping(&quot;/hello&quot;)
  @SendTo(&quot;/topic/greetings&quot;)
  public Greeting greeting(HelloMessage message) throws Exception {
    Thread.sleep(1000); // simulated delay
    return new Greeting(&quot;Hello, &quot; + HtmlUtils.htmlEscape(message.getName()) + &quot;!&quot;);
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;STOMP 메시징을 위한 Config 구성&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1688626367553&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

  @Override
  public void configureMessageBroker(MessageBrokerRegistry config) {
    config.enableSimpleBroker(&quot;/topic&quot;);
    config.setApplicationDestinationPrefixes(&quot;/app&quot;);
  }

  @Override
  public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint(&quot;/gs-guide-websocket&quot;).withSockJS();
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;br /&gt;마무리&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;401&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mq54z/btsmHHjsNlw/JbW7kzT7m37xkqdWPPcEIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mq54z/btsmHHjsNlw/JbW7kzT7m37xkqdWPPcEIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mq54z/btsmHHjsNlw/JbW7kzT7m37xkqdWPPcEIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMq54z%2FbtsmHHjsNlw%2FJbW7kzT7m37xkqdWPPcEIk%2Fimg.png&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;401&quot; height=&quot;257&quot; data-origin-width=&quot;401&quot; data-origin-height=&quot;257&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이렇게 간단하게 입력된 메시지가 화면에 출력되는 것을 확인할 수 있었습니다. 처음에는 @MessageMapping를 사용한다면 &quot;Rest API와 비슷하게 동작하는 것이 아닐까?&quot;라는 생각이 들었습니다. 하지만 &lt;span style=&quot;background-color: #ffffff;&quot;&gt;@MessageMapping&lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;은 STOMP 기반의 메시징 엔드포인트를 처리하기 위한 어노테이션이고, &lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;WebSocket 연결을 통해 클라이언트로부터 수신된 메시지를 처리하는 핸들러 메서드에 적용됩니다. 그래서 이 어노테이션을 사용하면 실시간 메시징을 위해 사용되어 동작합니다. 다음 게시물은 실제 채팅 서비스를 구현하는 과정을 게시하려고 합니다!!&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>프로젝트</category>
      <author>YoungSooSoo</author>
      <guid isPermaLink="true">https://youngsoosoo.tistory.com/233</guid>
      <comments>https://youngsoosoo.tistory.com/233#entry233comment</comments>
      <pubDate>Wed, 5 Jul 2023 23:10:07 +0900</pubDate>
    </item>
    <item>
      <title>[CQRS] 명령과 조회를 분리하는 CQRS로 개발하기 - 1</title>
      <link>https://youngsoosoo.tistory.com/232</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;왜 갑자기 CQRS를 도입해요?&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;CQRS는 명령과 조회를 분리함으로써 성능 향상 및 결합도가 낮아지는 효과를 기대할 수 있습니다. 채팅 서비스를 구현하게 된다면 일반 게시글이나 댓글에 비해 더 많은 양의 데이터가 저장될 것이라고 생각했습니다. 이를 위해 명령과 조회를 분리하고 조회에 성능이 좋은 NoSQL을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;일관성과 정확성이 중요한 데이터 처리, 복잡한 관계 처리, 안정성을 보장하는 RDB를 조합하여 최적의 데이터베이스 환경을 구성하려고 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;CQRS 도입이 필요할까요?&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;CQRS 패턴은 모든 상황에 적합한 해결책은 아닙니다. 사용 여부는 프로젝트의 요구사항과 복잡성에 따라 결정되어야 합니다. 오히려 단순한 CRUD 작업이 주로 이루어지는 간단한 애플리케이션에는 비효율적입니다. 근데 사실 한 번 써보고 싶기도 하고 어떤 장점이 있을까 해서 채팅 서비스를 CQRS로 구현하려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;DB가 분리가 된다면 일관성은 어떻게 보장해 주나요?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;처음 CQRS에 대해 알게 되었을 때 &lt;b&gt;조회와 명령으로 DB가 분리된다면 대체 어떻게 데이터가 동기화되는 걸까?&lt;/b&gt;라는 의문이 제일 먼저 생겼다. 이때 강사님께 질문을 드렸는데 메시징 큐를 통해 비동기 처리를 해주면 된다는 것을 알게 되었습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;메시징 큐 선택&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;비동기 처리를 위해 어떤 메시징 큐를 사용할 것인지에 대한 고민이 생겼다. 메시징 큐는 두 가지가 대표적이라고 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;1. Kafka&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;2. RabbitMQ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;455&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Acuv7/btsmkcZU4vx/UPUZUgdYhctGz4U46h1Hv1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Acuv7/btsmkcZU4vx/UPUZUgdYhctGz4U46h1Hv1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Acuv7/btsmkcZU4vx/UPUZUgdYhctGz4U46h1Hv1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAcuv7%2FbtsmkcZU4vx%2FUPUZUgdYhctGz4U46h1Hv1%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;850&quot; height=&quot;455&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;455&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;공통점&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; background-color: #ffffff;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;1. &lt;span style=&quot;color: #374151; text-align: left;&quot;&gt;Kafka와 RabbitMQ 모두 메시지를 안전하게 전달합니다. 메시지를 생성하고 &lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;Producer가 Consumer&lt;/span&gt;에게 전달합니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;차이점&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;처리 방식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: left;&quot;&gt; Kafka는 메시지를 토픽으로 구성하여 여러 소비자 그룹이 해당 토픽을 구독하고 메시지를 처리. (pub/sub 방식)&lt;br /&gt;&amp;nbsp; &amp;nbsp; RabbitMQ는 메시지를 큐에 저장하고 소비자가 해당 큐에서 메시지를 가져와 처리. (메시지 브로커 방식)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: left;&quot;&gt;용도의 차이&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: left;&quot;&gt;2. &lt;span style=&quot;color: #374151; text-align: left;&quot;&gt; Kafka는 병렬 처리와 메시지를 지속적으로 저장하여 데이터를 보존, 대용량 데이터 처리와 장기적 데이터 보존에 적합.&lt;br /&gt;&amp;nbsp; &amp;nbsp; RabbitMQ는 메시지를 큐에서 소비되면 메모리에서 제거,실시간 메시지 처리에 적합.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: left;&quot;&gt;&lt;span style=&quot;color: #374151; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: left;&quot;&gt;&lt;span style=&quot;color: #374151; text-align: left;&quot;&gt;Kafka 선택&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: left;&quot;&gt;&lt;span style=&quot;color: #374151; text-align: left;&quot;&gt;저는 채팅 서비스에 CQRS를 구현하려고 합니다. 실시간 메시지 처리에 적합한 건 RabbitMQ가 아닌가? 라는 생각이 들었습니다. 하지만 Kafka를 선택한 이유는 많은 양의 데이터를 한 번에 처리하기 위해 CQRS를 도입한 것이기 때문입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: left;&quot;&gt;&lt;span style=&quot;color: #374151; text-align: left;&quot;&gt;결론&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: left;&quot;&gt;&lt;span style=&quot;color: #374151; text-align: left;&quot;&gt;CQRS와 &lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: left;&quot;&gt;메시징 큐&lt;/span&gt;를 도입하는 프로젝트에 대해 간단히 설명했습니다. 하지만 CQRS 및 메시징 큐는 이렇게 간단하게 설명되는 것이 아니라 조금 더 확실하게 알고 가면 좋을 거 같습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트</category>
      <author>YoungSooSoo</author>
      <guid isPermaLink="true">https://youngsoosoo.tistory.com/232</guid>
      <comments>https://youngsoosoo.tistory.com/232#entry232comment</comments>
      <pubDate>Tue, 4 Jul 2023 00:42:43 +0900</pubDate>
    </item>
    <item>
      <title>[Furry_Friend_Chat] 채팅 서비스 요구사항 분석 및 설계</title>
      <link>https://youngsoosoo.tistory.com/231</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;채팅 서비스를 개발하기 전에 소프트웨어 생명주기(Software Development Life Cycle - SDLC)에 맞춰 진행하려고 합니다.&lt;br /&gt;이 글은 생명주기의 요구사항 분석 및 설계를 작성해 보았습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;요구사항 분석&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;필요 기술 스택&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Java&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Spring Boot&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. MySQL&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. MongoDB&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. WebSocket&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. Kafka&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;우선 개발 기능&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 일반적인 채팅 서비스 (메시지 조회, 저장, 삭제, 읽음 표시)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 명령과 조회를 분리하기 위한 CQRS (Kafka)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;추가 개발 기능&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 알림 서비스 (Kafka를 통해 알림 생성)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;테이블 명세&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;873&quot; data-origin-height=&quot;391&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGhM7X/btsnLt5IuO7/5CPO0MK2oWYDysefkZzO2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGhM7X/btsnLt5IuO7/5CPO0MK2oWYDysefkZzO2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGhM7X/btsnLt5IuO7/5CPO0MK2oWYDysefkZzO2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGhM7X%2FbtsnLt5IuO7%2F5CPO0MK2oWYDysefkZzO2K%2Fimg.png&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;873&quot; height=&quot;391&quot; data-origin-width=&quot;873&quot; data-origin-height=&quot;391&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;b&gt;API 명세&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;273&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvTIGw/btsnEWBDvVM/E66ps4uPnCKVO11UzS9Dbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvTIGw/btsnEWBDvVM/E66ps4uPnCKVO11UzS9Dbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvTIGw/btsnEWBDvVM/E66ps4uPnCKVO11UzS9Dbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvTIGw%2FbtsnEWBDvVM%2FE66ps4uPnCKVO11UzS9Dbk%2Fimg.png&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;698&quot; height=&quot;273&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;273&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>프로젝트</category>
      <author>YoungSooSoo</author>
      <guid isPermaLink="true">https://youngsoosoo.tistory.com/231</guid>
      <comments>https://youngsoosoo.tistory.com/231#entry231comment</comments>
      <pubDate>Mon, 3 Jul 2023 22:38:45 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Cloud Gateway] CORS 문제 해결하기</title>
      <link>https://youngsoosoo.tistory.com/230</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 개발을 위해 프런트엔드 개발자 분과 협업을 하는 중에 이와 같이 중복으로 헤더가 생성되는 오류가 발생했습니다. 이를 해결하기 위해 CORS에 대해 간단하게 알고 가려고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;img1.png&quot; data-origin-width=&quot;771&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1IGzh/btskSoss9Fh/xKYOVKMnsxRq5hzdjuILhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1IGzh/btskSoss9Fh/xKYOVKMnsxRq5hzdjuILhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1IGzh/btskSoss9Fh/xKYOVKMnsxRq5hzdjuILhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1IGzh%2FbtskSoss9Fh%2FxKYOVKMnsxRq5hzdjuILhk%2Fimg.png&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;771&quot; height=&quot;72&quot; data-filename=&quot;img1.png&quot; data-origin-width=&quot;771&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CORS(Cross-Origin Resource Sharing) 란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CORS는 다른 출처의 자원을 공유하는 것을 허용하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동일 출처 정책&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #1b1b1b; text-align: start;&quot;&gt;(same-origin policy)에 의해 어떤&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; href=&quot;https://developer.mozilla.org/ko/docs/Glossary/Origin&quot;&gt;출처&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; color: #1b1b1b; text-align: start;&quot;&gt;에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한하는 중요한 보안 방식이 적용되어 CORS를 통해 허용해 주어야 가능합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1b1b1b; text-align: start;&quot;&gt;다른 출처는 아래의 세가지 경우 정도로 생각할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로토콜이 다른 경우(예시 : http/https)&lt;/li&gt;
&lt;li&gt;도메인이 다른 경우&lt;/li&gt;
&lt;li&gt;포트번호가 다른 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SOP(동일 출처 정책: Same-Origin Policy)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 출처에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한하는 중요한 보안 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Cloud Gateway에서의 CORS 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;application.yaml&lt;/p&gt;
&lt;pre id=&quot;code_1687267659621&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  cloud:
    gateway:
      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: 'http://localhost:3000'
            allow-credentials: true
            allowedHeaders: '*'
            allowedMethods:
              - PUT
              - GET
              - POST
              - DELETE
              - OPTIONS&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Cloud Gateway에서는&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트가 서버로 보내는 실제 요청 이전에 먼저 OPTIONS 메서드를 사용하여 preflight 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;그래서 allowedMethods에 OPTIONS를 설정해주어야 합니다.&lt;/li&gt;
&lt;li&gt;preflight 요청을 통해 실제 요청 전에 서버가 요청을 수락할 수 있는지 확인합니다.&lt;/li&gt;
&lt;li&gt;확인을 완료한 뒤 실제 요청을 처리합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 간단한 동작 원리를 알았으니 위에 오류에 대해 설명하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 얘기하면 위에 오류는 Access-Control-Allow-Origin 헤더가 중복으로 발생해서 생기는 오류였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 오류가 발생한 &lt;b&gt;원인은&amp;nbsp;&lt;/b&gt;라우팅을 통해 다른 서버에 다녀오면&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Access-Control-Allow-Origin 헤더가 또 발생하게 된 것입니다.&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;이 오류를 해결해주기 위해 해결 방법을 찾았습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;default filters를 설정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;DedupeResponseHeader GatewayFilter Factory를 통해 특정 헤더 중복 제거&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;먼저 default-filters를 사용해 모든 라우팅 요청에 filters를 거치도록 해줍니다. 다음으로 &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;DedupeResponseHeader를 통해 특정한 헤더의 중복 값이 있다면 제거해 주었습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1687267959715&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;routes:
        - id: member-service
          uri: http://localhost:8081
          predicates:
            - Path=/member/**
          filters:
            - RewritePath=/member/(?&amp;lt;path&amp;gt;.*), /member/$\{path}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 기존의 Java를 통해 구성한 라우팅을 yaml 파일로 변경해서 작업해 주었습니다.&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;이 글은 아래의 문서를 참고하여 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration&quot;&gt;https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1687267636967&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Cloud Gateway&quot; data-og-description=&quot;This project provides an API Gateway built on top of the Spring Ecosystem, including: Spring 6, Spring Boot 3 and Project Reactor. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them &quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration&quot; data-og-url=&quot;https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zutJF/hyS4t1uET5/7t3wZ5ecnlemdaLAyghQZK/img.png?width=443&amp;amp;height=595&amp;amp;face=0_0_443_595&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zutJF/hyS4t1uET5/7t3wZ5ecnlemdaLAyghQZK/img.png?width=443&amp;amp;height=595&amp;amp;face=0_0_443_595');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Cloud Gateway&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This project provides an API Gateway built on top of the Spring Ecosystem, including: Spring 6, Spring Boot 3 and Project Reactor. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.0.M1/#_default_filters&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.0.M1/#_default_filters&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1687268809722&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Cloud Gateway&quot; data-og-description=&quot;This project provides an API Gateway built on top of the Spring Ecosystem, including: Spring 5, Spring Boot 2 and Project Reactor. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them &quot; data-og-host=&quot;cloud.spring.io&quot; data-og-source-url=&quot;https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.0.M1/#_default_filters&quot; data-og-url=&quot;https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.0.M1/#_default_filters&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bNyeqQ/hyS4o0bFtj/l9IrDDAJluMdyUBdBs9AIK/img.png?width=443&amp;amp;height=595&amp;amp;face=0_0_443_595&quot;&gt;&lt;a href=&quot;https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.0.M1/#_default_filters&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.0.M1/#_default_filters&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bNyeqQ/hyS4o0bFtj/l9IrDDAJluMdyUBdBs9AIK/img.png?width=443&amp;amp;height=595&amp;amp;face=0_0_443_595');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Cloud Gateway&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This project provides an API Gateway built on top of the Spring Ecosystem, including: Spring 5, Spring Boot 2 and Project Reactor. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cloud.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cloud.spring.io/spring-cloud-gateway/2.1.x/multi/multi__gatewayfilter_factories.html#_deduperesponseheader_gatewayfilter_factory&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://cloud.spring.io/spring-cloud-gateway/2.1.x/multi/multi__gatewayfilter_factories.html#_deduperesponseheader_gatewayfilter_factory&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1687268815933&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;6.&amp;nbsp;GatewayFilter Factories&quot; data-og-description=&quot;Route filters allow the modification of the incoming HTTP request or outgoing HTTP response in some manner. Route filters are scoped to a particular route. Spring Cloud Gateway includes many built-in GatewayFilter Factories. NOTE For more detailed examples&quot; data-og-host=&quot;cloud.spring.io&quot; data-og-source-url=&quot;https://cloud.spring.io/spring-cloud-gateway/2.1.x/multi/multi__gatewayfilter_factories.html#_deduperesponseheader_gatewayfilter_factory&quot; data-og-url=&quot;https://cloud.spring.io/spring-cloud-gateway/2.1.x/multi/multi__gatewayfilter_factories.html#_deduperesponseheader_gatewayfilter_factory&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://cloud.spring.io/spring-cloud-gateway/2.1.x/multi/multi__gatewayfilter_factories.html#_deduperesponseheader_gatewayfilter_factory&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cloud.spring.io/spring-cloud-gateway/2.1.x/multi/multi__gatewayfilter_factories.html#_deduperesponseheader_gatewayfilter_factory&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;6.&amp;nbsp;GatewayFilter Factories&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Route filters allow the modification of the incoming HTTP request or outgoing HTTP response in some manner. Route filters are scoped to a particular route. Spring Cloud Gateway includes many built-in GatewayFilter Factories. NOTE For more detailed examples&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cloud.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트</category>
      <author>YoungSooSoo</author>
      <guid isPermaLink="true">https://youngsoosoo.tistory.com/230</guid>
      <comments>https://youngsoosoo.tistory.com/230#entry230comment</comments>
      <pubDate>Tue, 20 Jun 2023 22:47:23 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 - 과제 진행하기(Python 풀이)</title>
      <link>https://youngsoosoo.tistory.com/229</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/176962&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/176962&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1686239242118&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/176962&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/fwO6X/hySVBMSCwN/lPJ1k53nduf6E7YPitXoPK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/coeOT4/hySWp5TQKo/CdS3NnmYxwS9UvKqJyBzNK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/176962&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/176962&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/fwO6X/hySVBMSCwN/lPJ1k53nduf6E7YPitXoPK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/coeOT4/hySWp5TQKo/CdS3NnmYxwS9UvKqJyBzNK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 인식&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;과제를 해야합니다.&lt;/li&gt;
&lt;li&gt;하지만 진행중이어도 다음 과제가 시작한다면&lt;/li&gt;
&lt;li&gt;멈추고 새로운 과제를 시작해야합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;문제 해결 방법&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;먼저 시작 시간과 과제하는데 걸리는 시간을 모두 정수형으로 변환해줍니다.&lt;/li&gt;
&lt;li&gt;시작 시간을 기준으로 정렬해줍니다.&lt;/li&gt;
&lt;li&gt;plans의 길이만큼 반복문을 실행해줍니다.&lt;/li&gt;
&lt;li&gt;현재 반복한 횟수가 마지막인 경우 과제를 먼저 완료해줍니다.&lt;/li&gt;
&lt;li&gt;현재 과제와 다음 과제들에 대한 선언을 해줍니다.&lt;/li&gt;
&lt;li&gt;과제가 끝났을 때와 안 끝났을 때를 기준으로 과제가 끝났으면 answer에&amp;nbsp;안&amp;nbsp;끝났으면&amp;nbsp;result에&amp;nbsp;추가해줍니다.&lt;/li&gt;
&lt;li&gt;과제가 끝났을 때 cnt가 남아있다면 가장 최근에 중단한 과제를 마무리해줍니다.&lt;/li&gt;
&lt;li&gt;마지막으로 안 끝나 과제들을 최근 중단한 순으로 마무리 해줍니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1686239235100&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def solution(plans):
    answer = []
    
    # 1
    for i in range(len(plans)):
        time, minutes = map(int, plans[i][1].split(':'))
        total = time*60 + minutes
        plans[i][1] = total
        plans[i][2] = int(plans[i][2])
        
    # 2
    plans.sort(key=lambda x:x[1])
    
    result = []
    
    # 3
    for i in range(len(plans)):
        
        # 4
        if i == len(plans) - 1:
            plans[i][2] = 0
            answer.append(plans[i][0])
            break
        
        # 5
        name, time, minutes = plans[i][0], plans[i][1], plans[i][2]
        nname, ntime, nminutes = plans[i+1][0], plans[i+1][1], plans[i+1][2]
        
        cnt = ntime - time
        
        # 6
        # 과제가 끝났을 때
        if cnt &amp;gt;= minutes:
            cnt -= minutes
            plans[i][2] = 0
            answer.append(name)
            
            # 7
            while cnt &amp;gt; 0 and result:
                a, b, c = result.pop()
                if cnt &amp;gt;= c:
                    cnt -= c
                    answer.append(a)
                else:
                    result.append([a, b, c-cnt])
                    cnt = 0
        #과제가 안 끝났을 때
        else:
            plans[i][2] -= cnt
            cnt = 0
            result.append(plans[i])
    
    # 8
    while result:
        a, b, c = result.pop()
        answer.append(a)
    
    return answer&lt;/code&gt;&lt;/pre&gt;</description>
      <category>코딩테스트</category>
      <author>YoungSooSoo</author>
      <guid isPermaLink="true">https://youngsoosoo.tistory.com/229</guid>
      <comments>https://youngsoosoo.tistory.com/229#entry229comment</comments>
      <pubDate>Fri, 9 Jun 2023 00:49:06 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Cloud Gateway] MSA를 향해 가는 API Gateway 도입기 - 3</title>
      <link>https://youngsoosoo.tistory.com/228</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서는 Spring Cloud Gateway를 사용하여 API Gateway를 구축하고, Restful API를 통해 인증 과정을 완료했습니다. 이번 글에서는 Member Server를 분리하여 로그인, 회원가입, 토큰 발급 등의 기능을 제공하고, 토큰 인증은 API Gateway를 통해 처리하는 방식을 설명하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;아래의 그림으로 설명하면 다음과 같습니다:&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1261&quot; data-origin-height=&quot;679&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4RrXz/btsiU5IFBjv/0hg048CedjR7ylhmp7a5y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4RrXz/btsiU5IFBjv/0hg048CedjR7ylhmp7a5y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4RrXz/btsiU5IFBjv/0hg048CedjR7ylhmp7a5y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4RrXz%2FbtsiU5IFBjv%2F0hg048CedjR7ylhmp7a5y1%2Fimg.png&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;769&quot; height=&quot;414&quot; data-origin-width=&quot;1261&quot; data-origin-height=&quot;679&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Client는 Member Server로 API 호출을 하지만, API Gateway가 인증 과정 없이 로그인 화면을 출력합니다. 이는 API Gateway가 클라이언트의 요청을 가로채고 인증 관련 작업을 처리하기 위함입니다.&lt;/li&gt;
&lt;li&gt;클라이언트가 로그인 또는 회원가입 요청을 보내면, Member Server에서 해당 요청을 처리합니다. Member Server는 회원 정보를 관리하고, 토큰을 발급합니다.&lt;/li&gt;
&lt;li&gt;로그인 또는 회원가입이 성공하면, Member Server는 API Gateway에게 토큰을 전달합니다.&lt;/li&gt;
&lt;li&gt;API Gateway는 전달받은 토큰을 클라이언트에게 전송합니다. 이 때, 토큰은 쿠키를 통해 전달될 수 있습니다.&lt;/li&gt;
&lt;li&gt;이후 클라이언트의 모든 요청은 API Gateway를 통해 전달되며, API Gateway는 토큰 인증 과정을 거칩니다. 토큰의 유효성을 확인하고, 요청을 Member Server로 라우팅합니다.&lt;/li&gt;
&lt;li&gt;Member Server는 API Gateway로부터 전달받은 토큰을 검증하고, 해당 요청에 대한 처리를 진행합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 구조를 통해 Member Server와 API Gateway를 분리하여 개발할 수 있습니다. API Gateway는 인증과 관련된 작업을 처리하고, Member Server는 회원 관리와 관련된 기능을 수행합니다. 또한, 토큰은 쿠키를 통해 전달되어 사용자의 세션을 유지하면서 토큰 인증 과정을 거칠 수 있습니다.&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;이 방식을 통해 깔끔하게 마이크로 서비스 아키텍처를 구현할 수 있고, 각 서비스의 역할과 책임이 명확히 분리되어 개발 및 유지보수가 용이해집니다. 또한, 보안과 확장성도 고려된 구조입니다.&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;다음 글에서는 채팅 서비스를 개발하는 과정을 진행하려고 합니다.&lt;/p&gt;</description>
      <category>BE/Spring</category>
      <author>YoungSooSoo</author>
      <guid isPermaLink="true">https://youngsoosoo.tistory.com/228</guid>
      <comments>https://youngsoosoo.tistory.com/228#entry228comment</comments>
      <pubDate>Wed, 7 Jun 2023 01:02:43 +0900</pubDate>
    </item>
  </channel>
</rss>