<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev_will_d.log</title>
        <link>https://velog.io/</link>
        <description>질문의 질이 답의 질을 결정한다.</description>
        <lastBuildDate>Fri, 14 Jun 2024 05:56:11 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev_will_d.log</title>
            <url>https://velog.velcdn.com/images/will_d/profile/b30609a7-ee71-40a9-a795-050caf695e7c/image.JPG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev_will_d.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/will_d" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[성능 최적화와 Three-tier Architecture]]></title>
            <link>https://velog.io/@will_d/%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94%EC%99%80-Three-tier-Architecture</link>
            <guid>https://velog.io/@will_d/%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94%EC%99%80-Three-tier-Architecture</guid>
            <pubDate>Fri, 14 Jun 2024 05:56:11 GMT</pubDate>
            <description><![CDATA[<p>Three-tier Architecture는 Presentation Tier Application Tier, Data Tier 세 가지 주요 계층을 나눠서 구성한 아키텍처를 말한다. 회사의 비지니스에 따라 더 세부적으로 아키텍처가 구성될 수 있지만 보통 회사의 큰 아키텍처는 Three-tier Architecture로 설명할 수 있다. 가장 중요한 점은 이 Three-tier Architecture를 분석하면 어떻게 서비스의 성능을 높일 수 있을지 정확한 파악이 가능하며, 업무를 할 때 어떠한 목적을 가지고 각 개발직군의 개발자들과 소통하면 좋을지 파악할 수 있다.</p>
<h3 id="three-tier-architecture">Three-tier Architecture</h3>
<p><img src="https://velog.velcdn.com/images/will_d/post/b00ae4a3-28da-469a-9fa1-d74a8152edd0/image.png" alt=""></p>
<pre><code>1. Presentation Tier
   - 사용자에게 보이는 부분을 담당
   - React, Native App ...
   - Frontend, Mobile
2. Application Tier
   - 서비스와 관련된 기능과 정책 등등 비지니스 로직을 담당
   - Spring, Django
   - Backend
3. Data Tier
   - 데이터를 저장, 관리, 제공 역할
   - MySQL, Oracle, MongoDB
   - DBA</code></pre><p>Three-tier Architecture는 각 계층의 목표는 분명하다. </p>
<h3 id="presentation-tier">Presentation Tier</h3>
<p>Presentation Tier는 사용자와 상호작용하는 계층이다. 이 영역에 해당하는 개발자는 Frontend, Mobile 개발자가 될 수 있다. 이 영역에서의 가장 중요한 목표는 사용자와의 상호작용을 적절히 처리하고, UI / UX를 통한 고객경험을 높이는 것이다. </p>
<p>이 영역에서 주의할 점은 Application Tier에서 처리해야 할 비지니스 로직을 과도하게 위임하지 않는 것이 중요하다. 추가로 이 영역에서 발생하는 네트워크 I / O는 인터넷을 통해 발생하므로 불필요한 I / O는 발생시키지 않도록 하는 게 중요하다. (여러 번 인터넷을 통한 네트워크 I / O를 발생시키지 않고 한 번에 서버에서 처리할 수 있는 방안을 고민하는 것이 중요하다.)</p>
<h3 id="application-tier">Application Tier</h3>
<p>Application Tier는 서비스의 비지니스 로직을 처리하는 중요한 계층이다. 이 영역에 해당하는 개발자는 백엔드 개발자가 될 수 있다. 이 영역에서의 가장 중요한 목표는 서비스의 비지니스 로직을 처리하는 것이다. 그렇다면 비지니스 로직이란 무엇일까 비지니스 로직은 여러 서비스와의 소통, 데이터베이스와 소통을 복합적으로 수행하여 현실세계의 문제(도메인)를 해결하는 로직을 말한다. 즉, 백엔드 개발자는 이러한 사실을 이해하고 비지니스의 특정 기능에 맞게 여러 복합적 요소의 절차를 논리적으로 작성하는 것이 중요하다. 이러한 관점에서 백엔드 개발자는 비지니스를 잘 이해하고 비지니스에서 발생하는 데이터의 성격을 잘 이해하는 것이 매우 중요하다.</p>
<p>외부와 상호작용은 그냥 우리어지는 것이 아니라 여러 복합적인 환경 설정과 보조로직에 의해 이루어진다. 즉, 비지니스 로직은 보조로직의 집합체로 해석할 수 있다. 여기서 이야기하는 보조로직은 외부의 서비스와 통신, 명령하기 위한 코드, DB와 통신, 명령하기 위한 코드(Ex) SQL)를 이야기한다. Hexagonal Architecture로 해석하면 외부의 Out Adapter에 해당한다. 비지니스로직은 특정 비지니스의 기능이 올바르게 수행할 수 있는 논리를 책임지고, 보조로직은 실질적인 성능을 책임진다고 할 수 있다. 비지니스 로직의 논리가 옳다고 성능이 향상되는 것은 아니다. 성능을 향상시키기 위해서는 비지니스의 논리를 올바르게 보존하고 쿼리튜닝과 같은 보조로직의 튜닝에 집중해야 한다. 예를 들어 JPA N + 1문제가 생겨도 비지니스의 기능은 수행된다. 결론적으로 우리 백엔드 개발자는 비지니스 논리를 올바르게 작성하는 것과 성능을 향상시키는 것은 다르다는 것을 인지해야 한다. </p>
<p>위의 단락에서 이야기한 관점을 잘 이해하면 관심사 분리의 중요성을 알 수 있다. 관심사 분리가 잘 되어야 무엇이 문제이고 무엇에 집중하여 개선할지가 분명해지기 때문이다. 관심사 분리를 잘하기 위해서 원칙이 필요하고 이러란 원칙을 지키기 위한 토대가 아키텍쳐라 할 수 있다. 개인적으로 서비스의 로직과 성능의 관심사에 대해 분리를 잘한 Architecture는 Hexagonal Architecture라 생각한다. 기회가 된다면 Hexagonal Architecture를 참고해보면 좋을 거 같다.</p>
<p>추가로 이 영역에서 주의할 점은 I / O를 최대한 최소화 시킬 수 있는지 확인하고, I / O를 짧게 끊어줄 수 있으면 짧게 끊어주는 것이 매우 중요하다. 그리고 Presentation Tier은 한 명의 사용자에게 개인화된 특징이 있는 반면 서버는 개인화된 여러 다수 사용자가 동시에 수많은 요청을 할 수 있다는 점을 이해하고 대응하는 것이 중요하다. 이 부분은 CPU, RAM, Thread의 지표를 잘 확인하여 특정 문제가 되는 지표에 대해 분석하여 튜닝을 진행하고 추가로 Infra 영역에서 Scale-In, Scale-Out으로 해결을 기대할 수 있다. 더 나아가 Blocking I / O, NonBlocking I / O의 관점을 도입해서 해결을 기대할 수 있다.</p>
<h3 id="data-tier">Data Tier</h3>
<p>실질적으로 데이터를 저장, 관리, 제공하는 역할을 담당하는 부분이다. 이 영역이 어떠한 행위를 할 것인지에 대한 방향성을 Application Tier에 의해 결정되지만 실질적으로 수행하는 부분이기 때문에 이 부분에 대한 튜닝 (Ex) Indexing)을 잘하면 단기적인 성능 향상을 기대할 수 있다. Application Tier에서 보조로직을 튜닝을 했을 때 성능 향상을 기대할 수 있는 이유이기도 하다. Application Tier와 Data Tier의 관계는 Application Tier가 Data Tier에 방향성을 알려주고 이러한 수행은 Data Tier에서 이루어진다. Application Tier의 구조를 리팩토링 했을 때 당장 성능향상을 기대할 수 없지만 점진적인 성능향상을 기대할 수 있는 이유이기도 하다. 일관적이고 구조적인 Application Tier는 올바른 방향성을 Data Tier에 더 잘 알릴 수 있기 때문이다.</p>
<p>이 영역에서 우리 백엔드 개발자가 주의해야 할 점은 우리가 방향성을 알렸을 때 실질적으로 DBMS에서는 어떠한 동작이 이루어지는지 잘 확인해야 한다. 예를 들어 JPA를 사용한다고 해도 실제 어떠한 SQL이 DBMS에 전달되고 이 쿼리를 수행하는 Optimizer가 Full Scan인지 Index Scan 인지 어떠한 Index를 사용하는지 Join은 어떠한 Join을 걸고 복합적으로 확인하여 판단하는 것이 중요하다.</p>
<h3 id="소통">소통</h3>
<p>작은 스타트업에서 백엔드 개발자는 Application Tier, Data Tier, Infra와 관련하여 모두의 역할을 수행할 수 있어야 하겠지만, 만약 규모가 있는 회사에는 각 직군의 개발자가 있을 것이다. 각 직군의 개발자는 각각의 Tier에서 집중하고자 하는 역할에 맞게 임무를 수행하고 있을 것이다. 이러한 경우 우리 백엔드 개발자는 비지니스를 가장 잘 알고 중앙에서 소통해야 하는 역할이라는 점을 이해하고 의사결정을 할 수 있어야 한다.</p>
<p>예를 들어 특정 테이블에 여러 Index가 필요할것이다. 그리고 이러한 Index를 DBA 관계자분께서 Index에 대해 관리하고 있을 것이다. 그렇다면 백엔드 개발자는 어떤 비지니스 상황에서 Index가 필요하고 필요 없는지 정확히 알고 필요하다면 요청을 할 수 있어야 하고, 현재 삭제를 하면 안 되는 이유를 말할 수 있어야 한다. 또한 필요 없다면 필요 없는 이유를 정확히 전달하여 DBA 분께서 명확한 근거로 삭제할 수 있도록 도와야 한다.</p>
<p>인프라 관점에서는 만약 특정 시간에만 트래픽이 몰린다고 가정해보자. 현재 띄어져 있는 서버 Instance 수로 그 특정 시간에는 감당하기 힘들다는 지표를 확인하면 이러한 지표를 근거로 특정시간에 추가로 Instance를 띄울 수 있는지에 대한 설명을 Infra 담당자분께 하여 조정할 수 있어야 한다.</p>
<h3 id="마무리">마무리</h3>
<p>Application Tier에 속한 백엔드 개발자는 무엇보다도 비지니스를 잘 이해해야 한다. 세상은 변하고 비지니스도 변한다. 그러니 꾸준한 비지니스 (도메인)에 대한 이해를 하는 노력이 매우 중요한 것 같다. 또한 요즘은 AI를 이용해 개발을 많이 진행한다. AI의 효용성을 생각해보면 AI는 특정 기능을 작성하는데 있어서는 탁월하다고 생각하다 그러나 AI는 비지니스를 모른다. 비지니스는 개발을 하는 개발자가 더 잘 안다. 모든 맥락과 상황을 AI가 파악할 수 없기 때문이다. 즉, 융합은 사람이 더 잘한다. 이러한 관점을 봤을때도 백엔드 개발자는 비지니스의 논리, 데이터를 매우 잘 이해해야 한다는 사실은 분명한 사실인 거 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[모바일 개발자로 다시 생각한 백엔드 개발]]></title>
            <link>https://velog.io/@will_d/%EB%AA%A8%EB%B0%94%EC%9D%BC-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A1%9C-%EB%8B%A4%EC%8B%9C-%EC%83%9D%EA%B0%81%ED%95%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@will_d/%EB%AA%A8%EB%B0%94%EC%9D%BC-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A1%9C-%EB%8B%A4%EC%8B%9C-%EC%83%9D%EA%B0%81%ED%95%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Thu, 13 Jun 2024 07:33:43 GMT</pubDate>
            <description><![CDATA[<p>백엔드 개발자는 개발을 할 때 항상 클라이언트를 생각해야 한다. 여기서 클라이언트 개발자는 서버, 모바일, 웹이 될 수 있다. 오늘은 필자의 모바일 (iOS, Android) 개발 경험을 바탕으로 어떻게 모바일 클라이언트에게 API를 제공하는것이 좋을지에 대해 이야기하는 시간을 가져보도록 하겠다.</p>
<h3 id="1-restful-api-설계를-잘-지키서-잘-예측할-수-있도록-하자">1. RESTful API 설계를 잘 지키서 잘 예측할 수 있도록 하자.</h3>
<p><img src="https://velog.velcdn.com/images/will_d/post/5c64092e-a6e2-4e37-9214-cd78f2397a5d/image.png" alt="">
우리가 핸드폰을 사용할 때 핸드폰 안에 수많은 부품과 장치가 있는 것은 알지만 우리가 정확히 핸드폰 안의 어떠한 부품이 존재하지 모르는 것처럼 모바일 개발자도 백엔드 내부 구성을 정확히 알 수 없다. 나또한 모바일 개발을 할 때 서버의 구성 전체를 알 수 없었다. 내가 알 수 있는 사실은 API 스팩과 어떠한 응답을 받을 수 있는지가 전부였다. 이러한 사실을 토대로 모바일 개발을 할 때 API 스팩 (METHOD, URL, Parameter)을 보고 내부에서 어떠한 동작이 일어날지 어떠한 값을 받아올 수 있는지에 대해 잘 예측하는것이 중요했다.</p>
<p>서버 개발을 할 때 이러한 사실을 알고 있기에 반대로 클라이언트에게 API를 제공할 때 내부의 동작을 잘 예측할 수 있게 METHOD, URL을 구조적이고 일관되게 정의하고 직관적인 파라미터명을 사용하는 방향으로 개발하고 있다. 또한 응답을 할때 정의와 일맥상통하는 충분한 응답값을 내리는것에 집중하고 있다. 이러한 원칙을 잘 적용하는 방법에는 RESTful API가 있다. 이러한 사실을 인지하고 더더욱 RESTful API를 설계할 때 사용자가 잘 사용할 수 있을지 원칙이 잘 지켜지는지 응답이 적절하지 다시 한번 확인하며 개발하자.</p>
<h3 id="2-http-status-code-body를-잘-내려주자">2. Http Status Code, Body를 잘 내려주자</h3>
<p><img src="https://velog.velcdn.com/images/will_d/post/977c307b-08e0-48f7-9e6e-0ed2594aba31/image.png" alt=""></p>
<p>모바일 개발을 할 당시 Http 통신에 성공, 실패해도 Http Status Code를 2xx으로 내려받았던 적이 있다. 이때 성공과 실패를 Body에서 정의한 상태코드로 구분하며 로직을 작성하였는데, 이 당시 이러한 방식이 회사의 컨벤션이어서 이에 맞춰 개발했으나, 백엔드 개발을 하며 다시 이 부분을 회고했을 때 이러한 방식보다는 Http Status를 명확히 구분하고 Body에서 이에 따른 세부 정보를 내려주는 것이 클라이언트에서 더욱 명확하고 효율적인 대응이 가능하다고 판단된다.</p>
<p>모바일에서는 서버와 요청, 응답에 대응하는 코드는 공통으로 구현한다. 공통 부분에는 1차적으로 Http Status Code를 확인하고 이에 따라 Body를 파싱하는 로직이 작성되어 있다. 모바일에서는 이렇게 확인된 정보를 토대로 서버와의 통신이 성공적으로 이루어 졌을 때, 성공적으로 이루어지지 않을 때의 로직이 작성된다. 성공하면 Response에 대한 정보를 연관된 Component에 전달하고, 실패하면 모달을 띄우거나, 토스트를 띄우는 등 UI / UX 로직이 처리되며 추가로 연관된 Component에 실패 정보를 넘겨준다. 이렇게 넘겨받은 값을 토대로 각 연관된 Component에서는 적절한 처리를 한다.</p>
<p>우리 개발자들은 Http Status Code에 대해서 2xx, 3xx, 4xx, 5xx이 어떠한 의미를 가지는지 알고 있으며, Http Status Code로 여러 상황에 대한 풍부한 표현이 가능하다. 즉, 클라이언트는 Body에서 상태코드를 구분하지 않아도 1차적으로 Http Status Code만 보고 어떠한 대응을 할지 알 수 있으며 공통 부분에서는 이러한 사실을 근거로 Http Status Code에 대한 일반적인 대응이 가능하다. 결과적으로 모바일에서 공통으로 구현한 부분에서는 Http Status Code를 통해 1차 적으로 일반적인 대응을 하도록 하고, 추가로 특정 기능에서 우리가 정의한 상태코드를 통한 로직이 작성되어야 한다고 하면(Ex) USER_PASSWORD_NOT_MATCH ..) Component에서는 넘겨받은 실패 정보를 토대로 대응하는 것이 적합하다. 이렇게 하면 더욱 구조적인 프로그램이 가능하고, 불필요한 클라이언트의 역직렬화를 줄일 수 있어 효율적인 프로그램이 가능하다.</p>
<h3 id="3-어떻게-잘-내려줄까">3. 어떻게 잘 내려줄까</h3>
<p><img src="https://velog.velcdn.com/images/will_d/post/bd753cf9-9ff3-4eb1-82ed-3a2f1cf69391/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/381ed935-71c2-4050-98d1-722397b5e668/image.png" alt=""></p>
<p>클라이언트에게 응답값을 내려줄 때 기본적으로 성공에 대한 응답값, 실패에 대한 응답값을 구분하고 일관적, 구조적으로 내려주는 것이 핵심이다. 우리 서버에서 공통으로 응답값을 정의한 것을 토대로 클라이언트도 공통으로 어떠한 응답값을 받을지 정의한다. 이러한 관점을 기반으로 서버에서 클라이언트가 공통구조를 잘 설계할 수 있도록 먼저 방향성을 잘 잡아주자.</p>
<p>변수명은 스네이크 케이스보다는 카멜케이스로 내려주자. Java, Kotlin, Swift에서는 변수를 정의할 때 스네이크 케이스로 정의하지 않는다. 카멜케이스로 정의한다. 만약 스네이크 케이스로 내린다고 하면 엄격하게 이러한 컨벤션을 지켜야 하는 경우 파싱할 때 변수를 카멜케이스로 바꾸는 작업을 해야 한다. 실제로 나도 이러한 작업을 했을 때 여간 번거로운 작업이 아니었다. 그럼으로 서버에서는 이러한 사실을 잘 이해하고 카멜케이스로 내려줄수 있도록 하자.</p>
<p>클라이언트는 서버에서 내려주는 응답 DTO를 정의할 때 JSON Parsing Exception을 피하고자 서버에서 정의한 JSON Filed Name을 그대로 DTO 변수명으로 사용하는 경우는 흔한 일이다. 즉, 서버에서 변수명을 대충 정해주거나, 알맞지 않게 정해줘서 내려주면 클라이언트도 이러한 변수명을 그대로 사용할 여지가 충분하다. 이러한 사실을 이해하고 서버에서 클라이언트에 전달할 변수명을 신경 써서 잘 전달할 수 있도록 하자.</p>
<p>서버에서 배열의 값을 내릴 때는 null로 내리지 않고 빈배열로 내린다. 그러나 회사의 컨벤션에 따라 배열 이외의 변수 타입에 대해서는 null을 내리기도 한다.</p>
<p>요즘 클라이언트 언어는 null safety 한 언어가 많다. (iOS - Swift, Android - Kotlin) null safety 한 언어에는 nullable 변수가 존재하는데, 이 nullable 변수에 접근할 때는 null safety 접근 연산자를 사용해야 한다. (?.) nullable 한 변수와 null safety 연산자를 사용하면 NullpointException을 발생시키지 않아 Application의 안정성을 매우 높여주지만 사용을 하는 입장에서는 일반 연산자보다 null safety 연산자가 추가로 null 처리를 매번 해줘야 하므로 여간 번거로운 일이 아니다.</p>
<p>그런데 만약 배열의 값을 내릴 때 null로 내리지 않고 빈배열로 내리는것처럼 다른 타입도 값을 내릴 때 null로 내리지 않는다고, 사전에 정의하면 null safety 한 언어를 사용하는 클라이언트에서 서버와 통신을 통한 값을 받을 때 타입을 nullable로 정의하지 않아도 된다. 이는 개발의 생산성을 높일 수 있다는 장점이 있다. 그러나 nullable로 선언하지 않는 타입에 null 값이 들어오면 JsonParsingExcepion이 발생한다는 단점이 있다. 이 부분은 이러한 사실을 이해하고 안정성과 편의성 둘을 고려하여 각 회사의 룰에 맞는 전략을 잘 따른는 것이 중요하다고 판단된다.</p>
<h3 id="4-api를-요청하는-효율적인-시점-생각하기">4. API를 요청하는 효율적인 시점 생각하기</h3>
<p><img src="https://velog.velcdn.com/images/will_d/post/a5fab858-41bd-47b4-a93a-8220b37f410d/image.png" alt="">
모바일에는 여러 LifeCycle이 존재한다. Application의 LifeCycle도 존재하고, 각 화면에 따른 LifeCycle도 존재한다. 앱을 다시 켰을 때의 시점, 앱을 껐을 때의 시점, 화면의 뷰가 모두 로딩이 완료되었을 때 시점, 화면이 다시 보여졌을 때 시점 등.. 다양한 시점을 LifeCycle이 정의한다. 그리고 이러한 LifeCycle은 사용자의 특정 행위와 더불어 서버의 API를 호출하는 Trigger가 될 수 있다. 이러한 사실을 잘 이해하고 RESTful API를 설계할 때 언제 이 API를 사용하는가에 대한 시점을 생각하는 것은 매우 중요하다.</p>
<p>예를 들어보자. 사용자가 앱을 켜면 모바일에서는 버전 체크 API를 요청한다. 버전 체크가 완료되면 모바일에서는 앱을 사용하기 위해 초기에 반드시 필요한 사용자 정보, Static 한 데이터를 전달받기 위해 서버에 Init API를 요청한다. 모바일에서는 이렇게 요청한 API를 토대로 사용자의 인증정보를 확인하여 어떠한 화면으로 이동할지 결정한다. 이 모든 것은 사용자의 행위로 이루어 지는 것이 아니라 LifeCycle에 의해 이루어진다. 즉 서버 개발자는 각 시점을 잘 이해하고 각 시점에 필요한 적합한 API를 요청하도록 설계하는 것이 중요하다.</p>
<h3 id="5-여러번-클라이언트에-요청하지-않도록-특정-행위에-대한-응답값-잘-내려주기">5. 여러번 클라이언트에 요청하지 않도록 특정 행위에 대한 응답값 잘 내려주기</h3>
<p><img src="https://velog.velcdn.com/images/will_d/post/669fdcd0-fff9-4e96-940d-82457e4d53e0/image.png" alt=""></p>
<p>모바일은 Stack 기반으로 화면이 관리가 된다. 즉, 화면이 전환되면 각 화면이 FIFO으로 화면이 쌓이게 된다.</p>
<p>어떤 앱에서 디테일 페이지에 들어간다고 하면 이 전에 리스트를 먼저 표현했을 것이고 리스트중 특정 아이템의 ID를 통해 디테일 페이지에 진입했을 것이다. 그런데 만약 디테일 페이지에서 어떠한 행위로 이 아이템의 데이터가 서버에서 변경되었다고 하자. 그러면 이전의 리스트에서도 이 상태를 업데이트해야 할 것이다. 방법은 2가지가 있다. 디테일 페이지에서 아이템의 값을 변경하는 API를 요청하고 요청이 완료되었을 때 이전의 리스트를 받아오는 API를 요청할 수 있고, 처음에 API를 요청했을 때 받아온 응답값을 통해 리스트를 업데이트하는 방법이 있다. 효율성을 고려했을 때 두 번째 방식이 API 요청을 최소화하는 방식이기 트래픽의 관점으로 해석하면 두번째 방식이 효율적이겠다. 이러한 사실을 이해하고 서버에서는 클라이언트가 충분히 업데이트할 수 있도록 데이터를 잘 내려주는 것이 중요하겠다.</p>
<p>(단, 첫 번째 방식으로 구현하는것은 코드를 작성하는 데 있어 상대적으로 더 효율적이다. 또한 다시 한번 서버의 데이터를 받아오는 방식이므로 서버와 클라이언트의 정합성을 확실히 유지할 수 있다는 장점이 있다. 그리고 리스트를 업데이트하여 새로운 정보를 표현할 수 있다는 장점이 있으므로 이러한 방식은 기획과 각 비지니스 상황에 따라 적절하게 고려하는것도 중요하다. 또한 각 화면에서 표현된 데이터들의 너무 상이한 도메인에 대한 데이터들이라고 한다면 여러 API를 호출하는 것도 구조적으로 고려해야 한다.)</p>
<h3 id="6-모바일-인증-방식">6. 모바일 인증 방식</h3>
<p>모바일에서는 WebView를 사용하지 않는다면, 쿠키와 세션 기반으로 서버에 인증할 수 없다. 일반적인 Native 앱이라고 한다면 JWT 기반으로 서버에 인증을 하는 것이 일반적이다. 로그인한 사용자는 JWT를 내부 저장소에 저장하고 이 토큰이 존재한다고 하면 로그인을 사전에 실시한 것으로 간주하고 서버에 만료되지 않은 사용자인지 요청을 하여 인증을 진행하고, 만약 만료가되었다면 401 상태코드를 확인하여 로그아웃 (JWT를 내부 저장소에서 없애고, 로그인 화면으로 전환)을 진행한다. 유효하다면 이 유효한 토큰을 기반으로 서버와 여러 통신을 진행한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[배달의 민족 마이크로 서비스 여행기 영상을 보고 다시 생각한 Architecture 설계]]></title>
            <link>https://velog.io/@will_d/%EB%B0%B0%EB%8B%AC%EC%9D%98-%EB%AF%BC%EC%A1%B1-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%97%AC%ED%96%89%EA%B8%B0-%EC%98%81%EC%83%81%EC%9D%84-%EB%B3%B4%EA%B3%A0-%EB%8B%A4%EC%8B%9C-%EC%83%9D%EA%B0%81%ED%95%9C-Architecture-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@will_d/%EB%B0%B0%EB%8B%AC%EC%9D%98-%EB%AF%BC%EC%A1%B1-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%97%AC%ED%96%89%EA%B8%B0-%EC%98%81%EC%83%81%EC%9D%84-%EB%B3%B4%EA%B3%A0-%EB%8B%A4%EC%8B%9C-%EC%83%9D%EA%B0%81%ED%95%9C-Architecture-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Fri, 31 May 2024 13:09:06 GMT</pubDate>
            <description><![CDATA[<p>현재 필자는 본질적인 실력향상을 목표로 사이드 프로젝트를 진행하고 있다. 현재 우리팀은 <code>선생님 중심의 개발 강의 플랫폼</code> 개발을 진행중이고, 프로젝트의 전체 큰 아키텍처는 MSA로 구성하여 진행하고 있다. <img src="https://velog.velcdn.com/images/will_d/post/42c7bf5c-f3a2-440a-a353-ef6f84944f77/image.png" alt=""></p>
<p>프로젝트에서 필자의 역할은 <code>아키텍처 설계, Common System 개발, Product-service 개발</code>을 담당하고 있다.
오늘은 평소에 필자가 생각한 MSA 아키텍처 구성과 배달의 민족 마이크로 서비스 여행기 영상을 보고 연구 / 분석한 아키텍처 구성에 대해 이야기하는 시간을 가져보도록 하겠다.</p>
<p><strong>Team Lucycato Github</strong></p>
<ul>
<li><a href="https://github.com/lucycato-backend/lucycato-e-commerce">https://github.com/lucycato-backend/lucycato-e-commerce</a>  </li>
</ul>
<p><strong>배달의 민족 마이크로 서비스 여행기</strong></p>
<ul>
<li><a href="https://www.youtube.com/watch?v=BnS6343GTkY&amp;t=1635s">https://www.youtube.com/watch?v=BnS6343GTkY&amp;t=1635s</a></li>
</ul>
<h3 id="three-tier-architecture">Three Tier Architecture</h3>
<p>서비스의 가장 큰 아키텍처는 보통 Three-Tier Architecture로 구성이 되어 있다고 할 수 있다. 그리고 백엔드 개발자는 로직을 처리하는 Application Tier와 데이터의 저장 및 공급을 담당하는 Data Tier와 밀접하게 관련이 되어 있다. Application Tier와 Data Tier에 대해 좀더 세부적으로 확장해보자.
<img src="https://velog.velcdn.com/images/will_d/post/7160bc1f-21b9-4a37-9ba9-b3d74f513f26/image.png" alt=""></p>
<h3 id="하나의-서버-하나의-db">하나의 서버, 하나의 DB</h3>
<p>서버와 DB를 하나로 구성한 그림은 아래와 같다. 보통 작은 서비스, 초기 스타트업의 구조는 아래와 같은 구조로 되어 있을 확률이 높다. 그러나 이 구조에는 큰 문제가 있다. 서버 or DB가 죽으면 정상적인 서비스 운용을 할 수 없다. 이 문제를 해결하기 위해 좀더 확장해보자.
<img src="https://velog.velcdn.com/images/will_d/post/4d512f6e-21e1-45c3-984f-4128a8b1bc52/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/fb10c41e-7001-4843-a2cf-3009eae37602/image.png" alt=""></p>
<h3 id="msa-단일-db-서버-구성">MSA 단일 DB 서버 구성</h3>
<p>하나의 서버로 운용을 했을때 서버가 죽으면 서비스를 정상적으로 운용을 못해 아래와 같은 구조로 확장했다.
관심사 분리의 기준은 각 도메인을 기준으로 Admin 서버와, App 서버로 분리했다. 이렇게 했을때 장점은 권한에 대해 각각의 서버에서 관리할 수 있다는 장점이 있다. 그러나 Admin과 App 각각의 서버를 각각 개발하다 보면 Read, Write에 대한 로직 중복이 많이 발생하여 오버헤드를 발생시키는 단점이 있다. 이 문제를 해결하기 위해 CQRS를 도입하여 Query (Read), Write (Command)를 기준으로 서버를 분리했다. 이렇게 서버를 구성했을때 장점은 2가지 관점의 Applcation을 운용할 수 있어, scale-out에 좀더 유리해지는 것(성능 효율성 향상)과 Command 서버가 죽어도 조회는 가능하다는 장점이 있다. (완전히 서비스가 마비되지는 않는다.) 처음 구조보다 조금더 회복성을 높인 구조라고 할 수 있다.그러나 DB 서버가 죽으면 전체 서비스가 마비되는 문제는 여전이 남아 있다. 이 문제를 해결하기 위해 조금더 확장해보자.
<img src="https://velog.velcdn.com/images/will_d/post/aa697165-6ec7-4328-b5dd-0c0955c67bd1/image.png" alt=""> <img src="https://velog.velcdn.com/images/will_d/post/277b6114-132c-48b2-9af8-04bddd2f79d9/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/378e7af9-7e4a-42f8-93e6-58a760723af7/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/f36e81f9-3f4f-4297-9819-4a8a101dcec5/image.png" alt=""></p>
<h3 id="msa-복합-db-서버-구성">MSA 복합 DB 서버 구성</h3>
<p>하나의 DB로 운용을 했을때 DB 서버가 죽으면 서비스를 정상적으로 운용 하지 못하는 문제로 아래와 같은 구조로 확장했다. 이때 DB 서버는 각 서비스의 특징에 맞게 선택한다. 예를 들어 데이터를 입력하는 Command 서버와 통신하는 DB는 데이터의 무결성과 정합성이 중요하므로 엄격한 스키마에 의해 데이터가 관리되는 RDB를 선택한다. Qeury 서버와 통신하는 DB 서버는 정합성이 떨어지더라도 강한 조회 성능을 발휘할 수 있는 NoSQL(Redis, MongoDB, DynamoDB)를 선택하여 조회 성능을 높인다. 이렇게 했을때 장점은 각각의 특성에 맞게 서버를 구성하여 전략적인 (효율적인) scale-out이 가능하다는 장점을 지닌다. 결정적으로 여러 DB 서버를 운용함으로서 회복성이 많이 좋아진다. Command 서버, RDB 서버 및 Command 서버가 죽어도 서비스는 완전히 죽지 않는다. 그러나 이렇게 서버를 구성하면 데이터의 정합성에 대한 문제가 생긴다. 이러한 문제를 해결하기 위한 방법을 살펴보자.
<code>* 중요) 주문과 같은 중요한 사용자와 관련된 Command 서버는 죽는다면, 또는 사용자와 많이 직결된 쿼리 서버가 죽는다면 어떻게 해야 될까? 결론은 주문과 같이 사용자와 관련된 Command 서버와 쿼리 서버는 죽으면 안된다. 배달의 민족 영상에서도 모든 서버가 현실적으로 대용량 트래픽일 수 없다고 한다. 즉, 경중에 따라 서버를 전략적으로 운용하는게 다르다는 뜻이다. 어떤 서버는 자원을 풍부하게 할당하고 철저한 대비를 할 수 있지만 어떤 서버는 상대적으로 그렇지 않다고 할 수 있다. 결론적으로 이러한 구조에서도 Command 서버가 죽거나, 쿼리 서버가 죽으면 치명적이기 때문에 경중이 중한 서버는 상대적으로 철저히 운용할 필요가 있다.</code>
<img src="https://velog.velcdn.com/images/will_d/post/4d8a6fac-8abd-4c10-bb58-d044db4d4322/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/637ba619-5bd7-49dc-9a81-764cbbae2870/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/e9177eec-b47c-40bc-8600-333871c7f051/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/6d1a343d-f377-4df4-927f-7ad1c3fb26b6/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/55346b27-ca77-4fc8-b637-2ee6889fc755/image.png" alt=""></p>
<h3 id="msa-복합-db-데이터-정합성">MSA 복합 DB 데이터 정합성</h3>
<p>데이터의 정합성을 맞추는 과정은 Kafka와 같은 이벤트 스트리밍 플랫폼으로 해결할 수 있다. 첫번째 사진을 참고해보자. Write 작업을 Command 서버에서 처리를 하고 DB에 데이터를 저장한다. 데이터 저장이 완료 되면 Kafka의 특정 Topic을 기준으로 이벤트를 발행한다. 이 이벤트를 식별할 수 있는 키값만 전달한다. Query 서버에서는 이 키에 대한 정보를 Consume해서 Command 서버에 사전에 정합성을 위해 Fit한 데이터를 주는 API를 키값으로 호출한다 최종적으로 키값을 통해 응답받은 데이터를 자신이 담당하는 DB 서버에 저장한다.</p>
<p>중요하게 언급해야 될 부분중에 하나는 Application 서버의 모델이다. Query 서버는 빠른 검색과 응답이 중요하다 이러한 서버에 어울리는 모델은 Event Loop를 사용하며, Non Blocking I/O 모델을 기반으로 한 WebFlux라고 할 수 있다. MVC에 비해 좀 더 복잡한 프로그래밍 모델이며 개발 검증이 복잡하고 안정성이 떨어질 수 있지만 빠른 응답을 요구하는 Query 서버에 적합다고 할 수 있다. 또한 Command 서버는 기본적으로 MVC를 사용한다. 예를 들어 B2C 서비스라고 가정할때 플렛폼을 사용하는 사용자는 Command 요청 보다 쿼리 요청을 많이 발생시키고, 데이터를 넣어주는 작업 즉, Command는 어드민(Back Office)에서 일어날 확률이 높다. 이러한 상황을 고려했을때 Command 서버는 빠른 응답보다는 안정성이 더 중요하다. 어드민 관리자가 데이터를 넣을때 조금 느리더라도 안전하게 데이터를 넣는것이 서비스 전체적으로 안정적이기 때문이다. 좀더 설명을 하자면 어드민이 서비스 사용자의 100만명, 1000만명의 사람에게 영향을 줄 수 있다는 말이다..!! 이러한 근거로 상대적으로 안정적인 MVC가 더 적합하겠다.</p>
<p>이렇게 구성하면 전략적인 운용이 가능하나, Data Sink 문제가 발생한다 어떻게 해결하면 좋을까 좀더 살펴보자.
<code>* 추가설명) B2C 서비스라고 가정할때 Command가 어드민에서 일어날 확률이 높지만 플렛폼 사용자에 의해 Command가 많이 일어날 수 있는 도메인과 상황이 있다. 이럴때는 속도의 강점을 WebFlux를 고려해 볼 수 있다. 더 나아가 안정성과 속도를 챙겨야 되는 상황이라면 아래에 언급한 Fast Command, Stable Command 구성도 고려해 볼 수 있다.</code></p>
<ul>
<li>참고)
<img src="https://velog.velcdn.com/images/will_d/post/06918b52-c86d-43b7-93d7-1e729d36c442/image.png" alt=""></li>
</ul>
<p><img src="https://velog.velcdn.com/images/will_d/post/659d5af3-56e4-4077-8e96-6d7da88e8dca/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/bdaf68f4-461a-4f1e-a162-0356c34ee3c5/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/c33b5c00-5849-4fda-a6f7-a7de752d2b79/image.png" alt=""></p>
<h3 id="msa-복합-db-data-sink-문제-해결">MSA 복합 DB Data Sink 문제 해결</h3>
<p>Data Sink 문제가 발생한다고 했을때 대략적으로 1초 ~ 3초 정도의 걸린다고 한다. 그런데 만약 사용자가 요청을 보내고 1초 ~ 3초 사이에 쿼리 서버에 데이터를 요청하면 어떻게 될까? 이럴 경우 아직 데이터가 저장이 안된 상태여서 사용자에게 데이터를 로드 할 수 없을것이다. 그러나 정말 중요해서 보여야 된다면 어떻게 해야 할까 필자가 생각한 방법은 Redis를 사용하는 것이다. Command 서버에 값을 저장하면 해당 데이터에 대한 Key값을 Redis에 저장한다. 이렇게 하고 1초 ~ 3초 사이에 데이터를 쿼리 서버에 요청을 하면 쿼리 서버에서는 기존에 약속한 Protocol에 의해 Redis에 키값이 존재하는지 묻는다. 만약 존재한다면 Command 서버에 쿼리를 요청하여 데이터를 받고 만약 존재하지 않는다면 DB에서 데이터를 가지고 온다. 이렇게 함으로써 중요한 데이터에 대한 Data Sink 문제를 해결할 수 있다.</p>
<p><code>* 추가설명) Redis는 Application 관점에서 봤을때 전역성을 띈다. 즉 Command와 Query간의 변수를 공유하도록 할 수 있다.</code></p>
<p><code>* 추가설명) 모든 데이터에 대해서 이러한 과정을 적용해야 할까 모든 데이터에 이러한 방식을 적용하면 안정적이라고 할 수 있으나 상대적으로 Data Sink가 발생해도 서비스에 큰 문제가 없는 데이터의 경우 개발의 효율성을 떨어트릴수 있기 때문에 상황과 맥락에 따라 적용하면 좋을 방법인거 같다.</code></p>
<p><code>* 추가설명) 왜 Command 서버에 요청을 하는가? 이유는 원본 데이터를 가지고 있는 서버이기 때문이다.</code></p>
<p><code>* 추가설명) Redis에 해당하는 Key는 데이터를 쿼리 서버 DB에 저장을 하면 없애주는 방식으로 개발을 해도 좋고, TTL을 걸어서 자동 소멸 하게 하는 방식 2가지가 있을것이다.</code>
<img src="https://velog.velcdn.com/images/will_d/post/93747608-0ec9-41d2-970c-a530a62894cc/image.png" alt=""></p>
<ul>
<li>참고)
<img src="https://velog.velcdn.com/images/will_d/post/b93c6c51-8e29-43a1-b268-0559ce418ece/image.png" alt=""></li>
</ul>
<h3 id="서버-확장">서버 확장</h3>
<p>다시 Three-Tier Architecture를 살펴보자. Three-Tire Architecture에서는 어떻게 서버를 확장 할 수 있는지 방향성을 제시한다. 바로 Application의 확장과 Data Tier의 확장이다. 서버를 Infra Level에서 확장하면 최종적으로 서버 구성의 모습은 아래와 같다고 할 수 있다.
<img src="https://velog.velcdn.com/images/will_d/post/2ec18b73-9b35-41b4-aa53-68996216c99a/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/b867f74f-c0de-4b67-bcd3-71e72341bd08/image.png" alt=""></p>
<h3 id="마이크로-서비스-도입">마이크로 서비스 도입</h3>
<p><img src="https://velog.velcdn.com/images/will_d/post/6b4cdc6f-cb71-4785-886b-7339b90c679b/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/e8e9d2fd-4f41-48c5-b3d0-1debe7e5e61e/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/5f3930b2-0ec8-4fae-b72d-e9e6eb724ea4/image.png" alt="">
영상에서 마이크로 서비스의 도입은 개발, 시스템의 숙달과 무관하게 서비스 생존을 위해서 도입했다고 나오고 있다. 또한 영상 마지막에서도 마이크로 서비스를 개발하는 것은 기존의 하나의 서비스와 DB를 운용하는 것보다 많은 개발 비용이 든다고 언급한다. 즉, 마이크로 서비스를 도입할때는 각 상황과 맥락을 파악하고 정말 필요에 의한 도입인지를 많이 고민해야 된다는 말이다. 모호한 근거에 도입을 하는것은 옳지 않을것이다.</p>
<p>또한 마이크로 서비스를 구성한다고 했을때 정답은 없다 각 서비스와 시스템의 상황을 고려하여 우리 서비스에 가장 적합한 마이크로 서비스 아키텍처를 구성하는게 가장 중요하다. 그리고 개발자는 이러한 사실을 이해하고 적합한 마이크로 서비스를 개발하기 위한 노력과 생각을 할 수 있어야 한다. 이렇게 할 수 있는 개발자가 진정한 실력있는 개발자라고 생각한다.</p>
<h3 id="주요-주제였던-개발-안정성에-대해">주요 주제였던 개발 안정성에 대해</h3>
<p>예전에 읽었던 토스의 <code>유난한 도전</code>에서 어떤 개발자가 리팩토링 도입 문제에 대해 이야기한 글을 봤다. &#39;<code>언제 없어질지 모르는 제품인데, 리팩토링은 지옥에서나 하라</code>&#39; 지금 생각해도 직설적인 문장이다. 그러나 개발자로서 생각해볼 가치가 충분한 문장이였다. 필자는 이 문장을 읽고 비지니스와 개발의 안정성에 대해 고민한적이 있었는데, 이번에 더 나은 아키텍처 설계를 위해 참고한 <code>배달의 민족 마이크로 서비스 여행기</code>에서 이 주제에 대해 이야기를 하여 다시 한번 생각을 정리해보고자 한다. </p>
<p>비지니스가 있기에 개발이 존재한다. 또한 개발이 안정적이여야 비지니스가 존재한다. 즉, 비지니스와 개발 안정성은 상호보완적인 관계라고 할 수 있다. 상호보완적인 관계이지만 경중을 따지면 비지니스가 더 중요할 것이다. 비지니스가 없으면 서비스 개발도 없기 때문이다. </p>
<p>&#39;<code>기술만으로는 충분하지 않다. 우리 가슴을 뛰게 하는 것은 인문학과 결합된 기술이다</code>&#39; 같은 말처럼 개발자는 <code>비즈니스와 개발의 교차점</code>을 기억하며 비지니스를 생각하며 개발을 할 수 있어야 한다. 또한 <code>배달의 민족 마이크로 서비스 여행기</code>에서 핵심적으로 이야기하는 <code>점진적 개선의 관점</code>도 탑재해 비지니스의 각 상황과 맥락을 판단해 그에 대응하여 개발할 수 있는 개발자가 되어야 한다.</p>
<p>비지니스의 중요성을 먼저 이해했다면 개발의 안정성과 비지니스의 상호 보완적인 관계를 이해하여 개발의 안정성을 점검하고 검증할 수 있어야 한다. 더 나아가 현재 서비스와 시스템을 지속적으로 이해하여 <code>서비스의 도전적 과제</code>를 점검하고 미래를 대비할 수 있어야 한다. 마지막으로 이 모든것을 하는 행위는 비지니스와 서비스의 성장을 위한 일임을 명심해야 한다.</p>
<p>최종적으로 비지니스와 개발의 안정성을 챙기는 방법에 마법의 요술 램프는 존재하지 않는다는 것을 인정하고 평소 비지니스, 프로젝트 기술의 각 상황과 맥락을 잘 이해하고 이에 대응 할 수 있는 <code>실력과 태도</code>를 갖춰야 한다.</p>
<p>진정으로 실력있는 개발자가 되고 싶어 평소 여러 관점에 대해 많은 생각을 하고있다. 이번 포스트의 핵심 논지인 서비스 성능 및 장애대응과 관련하여 평소 중요하게 생각한 생각을 정리할 수 있어서 기쁘다.</p>
<p>긴 글 읽어주셔서 감사합니다😀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Troubleshooting - BFF에 대한 생각]]></title>
            <link>https://velog.io/@will_d/Troubleshooting-BFF%EC%97%90-%EB%8C%80%ED%95%9C-%EC%83%9D%EA%B0%81</link>
            <guid>https://velog.io/@will_d/Troubleshooting-BFF%EC%97%90-%EB%8C%80%ED%95%9C-%EC%83%9D%EA%B0%81</guid>
            <pubDate>Mon, 27 May 2024 10:45:49 GMT</pubDate>
            <description><![CDATA[<p>현업에서 아키텍처 구성은 보통 Three-tier Architecture로 구성된다. Three-tier Architecture의 구성과 각 레이어의 역할과 책임은 아래의 설명과 같다.</p>
<pre><code>Three-tier Architecture
1. Presentation Tier
   - 사용자에게 보여지는 부분을 담당
   - React, Native App ...
2. Logic Tier
   - 서비스와 관련된 기능과 정책 등등 비지니스 로직을 담당
   - Spring, Django
3. Data Tier
   - 데이터를 저장, 관리, 제공 역할
   - MySQL, Oracle, MongoDB</code></pre><h3 id="msa를-통한-분리분해로-인해-생긴-문제">MSA를 통한 분리/분해로 인해 생긴 문제</h3>
<p>Presentation Tier는 사용자에게 보여지는 부분을 담당한다. 즉 사용자와의 상호작용 및 표현을 담당하는 중요한 부분이다. Presentation Tier는 무엇에 집중해야 되냐 묻는다면 사용자와의 상호작용과 표현에 집중해야 한다고 말하는것이 올바를것이다. </p>
<p>개발의 자원과 시간은 유한하다 무한하지 않다. 이말은 Presentation Tier를 담당하는 개발자의 수와 개발을 하는 시간은 유한하다는 뜻이다. Presentation Tier를 담당하는 개발자가 Business Logic 처리에 과중한 집중을 하다 보면 서비스의 성패를 좌우하는 UI/UX 개발에 집중하지 못할 수 있다. Three-tier Architecture 관점에서 비지니스 로직은 Logic Tier(백엔드)에 집중시키고 Presentation Tier(프론트엔드, Native App)에서는 UI/UX 개발에 집중하여 역할과 책임을 분명히 하여 각 레이어에서 최대 장점을 발휘하는 개발과 그 목적성에 부합하는 개발을 하여 서비스 전체의 퀄리티를 올리는것이 적합하다.</p>
<p>그러나 서버 구성을 MSA로 구성하여 각 서버를 비지니스와 도메인에 따라 분리/분해 하면 발생하는 문제가 있다.
각 서버의 도메인은 특정 클라이언트가 아닌 모두를 만족하는 응답을 내린다. 즉, 도메인 속성을 내린다. 각각의 클라이언트 (모바일, 앱, 서버)는 이 응답값을 자체적으로 필요에 의한 로직으로 처리해야 한다. 즉, 서버에서 처리해야 할 비지니스 로직을 UI/UX에 집중해야 할 Presentation Tier의 클라이언트가 처리하는 문제가 발생 할 수 있음을 야기한다. </p>
<p>이 외에도 발생하는 문제가 있다. 비지니스 능력과 도메인을 기준으로 서버를 분리하다 보면 특정 페이지를 완성하기 위한 API를 Aggregate하여 완성하기 힘든 경우가 있다. 이럴때는 클라이언트가 서버에 요청을 물리적으로 여러번 해야 하는 경우가 생긴다. 클라이언트에서 여러번 물리적 통신을 여러번 하는것은 성능 문제를 야기할수 있다. (로컬망에서의 통신보다 네트워크를 통한 통신이 더욱 느리기 때문이다.)</p>
<h3 id="문제-해결-bff">문제 해결 BFF</h3>
<p>MSA에서 이 문제를 해결하는 방법 중 하나가 BFF (Backend For Frontend)이다.</p>
<p>BFF는 Presentation Tier를 위한 백엔드라고 할 수 있다. 즉, 각각의 Presentation Tier에 최적화 되어 있어 각각의 페이지와 기능에 맞게 응답값을 내려줄수 있다. 또한 서버의 분리 / 분해로 인해 클라이언트가 물리적으로 여러번 서버를 호출해야 하는 문제도 해결 할 수 있다.</p>
<p>그렇다면 BFF는 만능인가?</p>
<h3 id="bff-troubleshooting">BFF Troubleshooting</h3>
<ol>
<li>BFF에 너무 많은 책임을 위임하지 말자.
<img src="https://velog.velcdn.com/images/will_d/post/33ec7fb8-70df-4347-b823-d4b3746afc39/image.png" alt=""><pre><code>GET teachers/{teacherId}
RESPONSE: Teacher
</code></pre></li>
</ol>
<p>GET courses?teacherIds=
RESPONSE: List<Course></p>
<p>GET text-books?teacherIds=
RESPONSE: List<TextBook></p>
<pre><code>![](https://velog.velcdn.com/images/will_d/post/7fbab8d3-560b-4839-9859-67af3679af8c/image.png)</code></pre><p>GET teachers/{teachdrId}
RESPONSE: Teacher</p>
<p>GET teachers/{teacherId}/courses
RESPONSE: List<TeacherCourse></p>
<p>GET teacher/{teacherId}/text-books
RESPONSE: List<TeacherTextBook></p>
<pre><code>특정 선생님의 페이지를 완성한다고 가정하자. 선생님 페이지에는 선생님의 정보와, 선생님의 강의 정보, 선생님의 교재 정보가 필요하다.

필자는 처음에 BFF 서버에서 모두 Aggregate 하기 때문에 얽힘 없이 순수하게 필요한 데이터를 요청하여 Aggregate 하는것이 적합하다고 생각했다. 그러나 이렇게 얽힘 없이 순수하게 데이터를 가져와 비지니스 로직을 처리하면 비지니스 로직이 복잡해지고 BFF가 커진다는 사실을 알게되었다. 이는 너무 많은 책임을 부여하여 유지보수를 어렵게한다.

그래서 두번째와 같은 방식을 통해 도메인과 도메인이 특정 관계를 가지면 관계를 맺어주고 이렇게 맺어진 데이터를 기반으로 비지니스 로직을 처리하도록 구성을 바꿨다. 이렇게 하면 BFF에 로직 처리 부담을 줄이고 BFF의 크기를 줄일수 있다. 

각 도메인 서버에서는 각 상황과 맥락을 충분히 고려하여 도메인과 도메인의 관계를 명확히 하여 이와 관련한 응답을 주는 Endpoint을 제공 할 수 있도록 하자. 

[* 참고) REST API 설계](https://velog.io/@will_d/멘토님을-통해-다시-생각한-RESTful-API-그리고-객체지향)

``
참고) BFF는 각 페이지의 복잡한 요구사항을 충족하기 위해 여러 서버를 호출하여 Aggregate 하는 특징을 지닌다.
이러한 특징으로 인해 BFF 서버를 Spring Framework로 구성한다면 Non Blocking I/O를 모델로한 WebFlux를 사용한다.
``

--------------------------------------------------------------------------

2. 작은 서비스에서 BFF는 오히려 유지보수의 비용을 증가 시킨다.

  특정 페이지와 기능을 완성하기 위해 연관성이 낮은 도메인과 도메인이 많이 역여있는 큰 서비스에서 BFF는 적합 할 수 있다. 그러나 작은 서비스, 연관성이 낮은 도메인과 도메인이 적게 역여있는 서비스에서는 BFF는 오히려 관리 포인트를 증가 시킬 수 있다. 이러한 경우에는 BFF의 도입은 적합하지 않을 수 있다. 

  이럴 때는 각각의 도메인에서 클라이언트의 요구사항을 처리하는것이 적합할 수 있다. 서비스의 요구사항과 각 도메인의 관계를 종합적으로 판단하여 BFF 도입을 할 수 있도록 하자.


### Next.js
최근 Chrome 개발자 모드를 통해 API를 확인해보면 .../_next/data/...와 같은 API를 볼 수 있다. 이는 Next.js에서 백엔드를 구성하여 API를 만든것이다. Next.js에서 백엔드 서버는 BFF 서버로 활용될 수 있다. 그리고 각 회사의 상황에 따라 Next.js 서버는 프론트엔드 개발자가 개발할 수 있다.

이러한 상황까지 복합적으로 고려를 하여 프론트엔드 개발자가 UI/UX에 더욱 집중할 수 있도록 위에서 언급한 BFF에 너무 많은 책임을 위임하지 말자.
![](https://velog.velcdn.com/images/will_d/post/dd4ff901-560d-4463-9555-c978da4b8534/image.png)
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[멘토님을 통해 다시 생각한 RESTful API 그리고 객체지향]]></title>
            <link>https://velog.io/@will_d/%EB%A9%98%ED%86%A0%EB%8B%98%EC%9D%84-%ED%86%B5%ED%95%B4-%EB%8B%A4%EC%8B%9C-%EC%83%9D%EA%B0%81%ED%95%9C-RESTful-API-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5</link>
            <guid>https://velog.io/@will_d/%EB%A9%98%ED%86%A0%EB%8B%98%EC%9D%84-%ED%86%B5%ED%95%B4-%EB%8B%A4%EC%8B%9C-%EC%83%9D%EA%B0%81%ED%95%9C-RESTful-API-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5</guid>
            <pubDate>Fri, 24 May 2024 18:47:25 GMT</pubDate>
            <description><![CDATA[<p>나에게는 개발 멘토님이 계신다. 그리고 나는 이분을 개발 롤모델로 생각한다. 이 전에 멘토님과 소주를 먹으면서 멘토님의 개발에 대한 태도와 생각, 가치관에 대해 들은적이 있다. 그때 시스템, 그리고 코드를 자신의 딸 만큼 생각한다고 말씀하시면서 개발을 할때의 과정을 상세히 설명해 주셨을때 깊은 감명을 받은 것이 아직도 생각난다. 그 이후 나는 이분을 진심으로 존경하게 되었고, 닮고 싶었다. 이번에도 우연히 멘토님과 소주챗을 하였다. 여러 개발 주제로 토론을 했다. 이번에도 말씀 하나 하나 모두 인상 깊었지만, 그중 RESTful API와 객체지향에 대한 이야기에 깊은 감명을 받아 이와 관련해서 연구하고 분석해서 글을 쓰게 되었다.</p>
<h3 id="설계의-중요성">설계의 중요성</h3>
<p>멘토님과 만났을때 나도 멘토님처럼 생각하고 싶어서 어떻게 생각하시는지 관찰했다. 여러 대화를 해보면서 특징을 곰곰이 생각해봤다. 내가 발견한 특징은 아래와 같다. </p>
<p><code>여러 시간에 걸쳐 많은 코드를 작성하며 좋은 코드와 나쁜 코드를 경험하고, 많은 경험과 지식이 쌓이면 자신의 코드를 보며 반성할 수 있다. 반성을 할 수 있으면 머리속으로 중요한 원칙과 경험을 근거로 머릿속에서 각 경계의 코드를 작성하며 생각의 사냥을 할 수 있다. 생각의 사냥을 하며 끊임없이 머릿속에서 작성한 코드의 정당성과 타당성을 스스로 설명하고 결정한다. 이 과정을 빠르고 정확하게 할 수 있다면 코드의 품질은 높을 수 밖에 없고, 체계적인 코드를 작성할 수 있다.</code></p>
<p>나는 내가 관찰한 특징을 토대로 앞으로 이렇게 해야겠다고 생각했다. 그리고 추가로 질문했다.</p>
<p><code>👨🏻‍💻 그렇게 할 수 있다면 무엇에 집중해야 하나요?</code></p>
<p>멘토님은 설계라고 답변해주셨다. 그리고 위 과정은 설계하는 과정이라고 할 수 있다. 즉, 설계는 코드의 품질을 높이고 지속가능한 시스템의 초석이 되는 체계적인 코드 작성의 시작이라고 할 수 있다.</p>
<h3 id="rest-api-기본">REST API 기본</h3>
<p>백엔드 설계의 시작은 Endpoint(ex) Controller)에서 시작된다고 할 수 있다. 무엇을 개발할지(집중할지) 알아야 무엇을 개발할지 알 수 있기때문이다. 많은 API 설계 기법이 있지만 오늘은 대표적인 API 설계 기법중 하나인 REST API에 대해 설명해보자 한다.</p>
<ul>
<li>REST API
```</li>
</ul>
<ol>
<li>자원(URI), 행위(Method), 표현(Representation)을 통해 클라이언트와 서버 간의 통신 방식을 규정</li>
<li>자원, 행위, 표현을 통해 클라이언트와 서버가 어떻게 식별하고, 조작하며, 데이터를 주고 받을지를 정의
```</li>
</ol>
<ul>
<li>REST API 대표적인 Design Guide
```</li>
</ul>
<ol>
<li>URI는 정보의 자원을 표현</li>
<li>자원에 대한 행위는 HTTP Method 표현 (GET(발급), POST(생성), PATCH((일부)수정), DELETE(삭제))</li>
<li>(/)는 계층 관계를 나타내는 데 사용</li>
<li>Domain 표현은 복수 사용 (teachers, lectures)</li>
<li>하이픈 사용, 언더바 사용 X, 명사 사용, 소문자 사용</li>
<li>확장자는 URI 포함 x, 헤더에 표현</li>
<li>적절한 HTTP 응답 상태 코드 발급
```</li>
</ol>
<ul>
<li>REST API 특징
```</li>
</ul>
<ol>
<li>자원(URI)</li>
</ol>
<ul>
<li>자원은 고유한 URI를 통해 식별</li>
</ul>
<ol start="2">
<li>행위(Method)</li>
</ol>
<ul>
<li>자원에 대한 특정 행위 (GET, POST, DELETE, PUT) 정의</li>
</ul>
<ol start="3">
<li>표현(Representation)</li>
</ol>
<ul>
<li>자원의 상태나 데이터를 표현 (JSON, XML)</li>
</ul>
<ol start="4">
<li>상태 무결성 (Statelessness)</li>
</ol>
<ul>
<li>각 요청은 독립적, 서버는 클라이언트의 상태를 유지하지 않는다.</li>
<li>클라이언트는 필요한 모든 정보를 요청에 포함</li>
</ul>
<ol start="5">
<li>캐싱 (Caching)</li>
</ol>
<ul>
<li>응답이 캐시 가능하도록 설정 가능</li>
<li>HTTP 헤더를 통해 캐시 제어 가능</li>
</ul>
<ol start="6">
<li>계층화 시스템 (Layered System)</li>
</ol>
<ul>
<li><p>클라이언트는 중간 서버(프록시, 게이트웨이)를 통해 서버와 상호작용 가능</p>
<pre><code></code></pre></li>
<li><p>RESTful API</p>
<pre><code>REST API의 정의 및 특징과 REST API Design Guide를 잘 따른 API를 RESTful 하다고 표현한다.</code></pre><p>REST API 설계 표준은 없다. 즉, 정답은 없다. 그러나 REST API의 정의 및 특징, 잘 알려진 설계 기법들, 각 팀에서 정한 <strong>일관성 있는 설계</strong>를 따른다면 RESTful 하다고 할 수 있을것이다. </p>
</li>
</ul>
<h3 id="rest-api-설계-with-객체지향">REST API 설계 with 객체지향</h3>
<p>필자는 <strong>REST API 정의</strong>, 대표적인 REST API Guide와 함께 REST API를 객체지향에 집중해 설명해 보도록 하겠다.</p>
<p>필자는 멘토님의 말씀과 매달 플레이를 하는 유저의 수가 1억명이 넘는 Riot Games Open API를 기반으로 연구 / 분석을 실시했다.<img src="https://velog.velcdn.com/images/will_d/post/27233de2-2138-44c1-93fd-57767b049bfb/image.png" alt=""> <a href="https://developer.riotgames.com/apis">https://developer.riotgames.com/apis</a></p>
<h4 id="uri-의미-해석">URI 의미 해석</h4>
<ol>
<li>깊이: REST API에서 URI는 깊이를 나타낸다. 
```
예시)
GET teachers</li>
</ol>
<ul>
<li>설명: 모든 선생님
GET teachers/{teacherId}</li>
<li>설명: 특정 선생님<pre><code></code></pre></li>
</ul>
<ol start="2">
<li>도메인 관계: REST API에서 URI는 도메인 간의 관계를 나타낸다.</li>
</ol>
<ul>
<li>1:1
<img src="https://velog.velcdn.com/images/will_d/post/9f9127e6-393f-4c7b-8d0d-7b88455a140e/image.png" alt=""><pre><code>설명: 특정 도전(challenges/{challengeId})의 설정(config)
RESPONSE: 단수 (ChallengesConfigInfoDTO)</code></pre><pre><code>추가설명
Collection: 집합 데이터 ex) challenges
- REST API 에서 집합 데이터를 나타내는 도메인은 복수로 명시한다.
Document: 단건 문서 (데이터) ex) config
- REST API 에서 단건 문서를 나타내는 도메인은 단수로 명시한다.</code></pre></li>
<li>N:1
<img src="https://velog.velcdn.com/images/will_d/post/e6104c13-af2d-4d47-ab3f-b060dca58f72/image.png" alt=""><pre><code>설명: 모든 도전(challenges)의 설정(config)
RESTPONSE: 복수 (List&lt;ChallengesConfigInfoDTO&gt;)</code></pre><pre><code>GET teachers/events/{eventId}
설명: 모든 선생님(teachers)의 특정 이벤트 (events/{eventId})
RESTPONSE: 복수 (List&lt;TeacherEvent&gt;)
해석: 같은 이벤트를 가지는 여러 선생님들의 이벤트 리스트를 주세요.</code></pre></li>
<li>1:N
<img src="https://velog.velcdn.com/images/will_d/post/41f33225-2e2c-4a9c-841c-152ef8288ec5/image.png" alt=""><pre><code>설명: 특정 도전(challenges/{challengeId)의 모든 백분위수(percentiles)
RESTPONSE: 복수 (Map&lt;Level, Double&gt;)</code></pre><pre><code>GET /teachers/{teacherId}/courses
설명: 특정 선생님의 (teachers/{teacherId}) 모든 강의(courses)
RESPONSE: 복수 (List&lt;TeacherCourse&gt;)</code></pre></li>
<li>N:M
<img src="https://velog.velcdn.com/images/will_d/post/d76dcf38-7f87-4110-863d-f657bc145e69/image.png" alt=""><pre><code>설명: 모든 도전(challenges)의 모든 백분위수(percentiles)
RESTPONSE: 복수(Map[Long, Map[Integer, Map&lt;Level, Double&gt;]])</code></pre><pre><code>GET /teachers/courses
설명: 모든 선생님의 (teachers) 모든 강의 (courses)
RESPONSE: 복수 (List&lt;TeacherCourse&gt;)</code></pre></li>
</ul>
<ol start="3">
<li>책임: URI 역할, 책임을 나타낸다.
<img src="https://velog.velcdn.com/images/will_d/post/01570e3a-bbe7-4dc9-a9e5-9aa15565b42e/image.png" alt="">
<img src="https://velog.velcdn.com/images/will_d/post/ff01f77d-2aff-4b43-bfbd-efebabf40f02/image.png" alt=""><pre><code>GET users/{userId}/coupons
설명: 특정 유저에 대한 모든 쿠폰 
추가설명: 이는 유저와 관련한 요청과 응답에 대한 책임을 가지는 UserController에 속하는것이 적합한다.
RESTPONSE: List&lt;UserCoupon&gt;
class: UserController</code></pre><pre><code>GET coupon/{couponId}/users
설명: 특정 쿠폰에 대한 모든 유저들
추가설명: 이는 쿠폰에 관련한 요청과 응답에 대한 책임을 가지는 CouponeController에 속하는것이 적합하다.
RESTPONSE: List&lt;CouponUser&gt;
class: CouponController</code></pre><pre><code>GET courses?teacherId=?
설명: 모든 강의 그 중 특정 선생님
추가설명: 이는 강의에 관련한 요청과 응답에 대한 책임을 가지는 CourseController에 속하는것이 적합하다.
RESTPONSE: List&lt;Course&gt;
class: CourseController</code></pre><pre><code>GET teachers/{teacherId}/courses
설명: 특정 선생님의 강의들
 추가설명: 이는 선생님에 관련한 요청과 응답에 대한 책임을 가지는 TeacherController에 속하는것이 적합하다.
RESTPONSE: List&lt;TeacherCourse&gt;
class: TeacherController</code></pre>각 상황과 맥락에 따라 도메인들은 충분히 얽히고 섥힐수 있다. 또한 URI의 관계를 어떻게 표현하느냐에 따라 무엇에 집중해야 할지가 달라진다. 즉, 책임과 역할이 달라진다고 할 수 있다. 결론적으로 URI를 통해 역할과 책임을 파악 할 수 있다.</li>
</ol>
<h4 id="결론">결론</h4>
<p>기획 -&gt; 도메인 -&gt; 도메인 간의 관계 -&gt; URI 표현 -&gt; REST API를 객체지향 적으로 설계</p>
<pre><code>특정 서비스를 개발하기 위한 기획서가 있다. 개발자는 기획서를 읽고 도메인을 추산할 수 있다.
이렇게 추산된 도메인은 각각 관계를 가진다. 각 상황과 맥락에 따라 도메인의 관계는 다르게 표현이 된다.
즉, 책임과 역할이 달라진다. 이를 통해 우리는 객체지향적으로 REST API를 설계할 수 있다.</code></pre><p>아래는 REST API를 객체지향적으로 설계한 예시다.</p>
<h3 id="rest-api-객체지향-설계-예시">REST API 객체지향 설계 예시</h3>
<ul>
<li>POST: reviews<pre><code>설명: 리뷰 등록
Response: Review
Class: ReviewController</code></pre></li>
<li>GET: reviews<pre><code>설명: 리뷰 리스트 조회
Response: Review
Class: ReviewController</code></pre></li>
</ul>
<hr>
<ul>
<li>POST teachers/{teacherId}/reviews<pre><code>설명: 특정 선생님의 리뷰 등록
Response: TeacherReview
Class: TeacherController</code></pre></li>
<li>GET teachers/{teacherId}/reviews<pre><code>설명: 특정 선생님의 리뷰 정보 리스트
Response: List&lt;TeacherReview&gt;
Class: TeacherController</code></pre></li>
</ul>
<hr>
<ul>
<li>POST courses/{courseId}/reviews<pre><code>설명: 특정 강자의 리뷰 등록
Response: CourseReview
Class: CourseController</code></pre></li>
<li>GET courses/reiews<pre><code>설명: 모든 강의의 리뷰 리스트
Response: List&lt;CourseReview&gt;
Class: CourseController</code></pre></li>
<li>GET courses/{courseId}/reviews<pre><code>설명: 특정 강의의 리뷰 리스트
Response: List&lt;CourseReview&gt;
Class: CourseController</code></pre></li>
<li>GET courses/by-teacher/{teacherId}/reviews<pre><code>설명: 선생님의 아이디를 기반으로 한 특정 강의의 리뷰 리스트
Response: List&lt;CourseReview&gt;
Class: CourseController</code></pre></li>
</ul>
<h3 id="path-parameter-vs-query-parameter-optional">Path Parameter vs Query Parameter (Optional)</h3>
<p><img src="https://velog.velcdn.com/images/will_d/post/6585c95d-09b0-4a85-8ce5-ac82c39f4fdd/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/3bb46cd2-b499-411c-aa81-213d0a611bac/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/ba51a91a-9e95-4908-8874-c59c26fc1752/image.png" alt=""></p>
<pre><code>- Path Parameter: Require 
설명: 반드시 필요한 값들에 대해서는 Path Parameter를 통해 값을 받는다.
1. PK O
    ex) courses/{courseId}
    설명: PK는 by keyword를 path로 명시하지 않는다.
2. PK X
    ex) courses/by-teacher/{teacherId}
    설명: PK가 아닌 값은 by keyword를 path에 명시하여 작성한다.

- Query Parameter: Optional
설명: 선택적으로 필요한 값들에 대해서는 Query Parameter를 통해 값을 받는다.
    ex) teachers?page=&amp;perPage=&amp;isEncrypt=

이유: 1번 사진과, 2번 사진과 같이 검색을 하기 위해 여러 개의 값을 모두 받아야 검색이 가능한 경우가 있다.
     이럴때 Query Parameter 보다 Path Parameter를 통해 클라이언트에게 의도를 분명히 할 수 있다.
     또한 3번의 사진과 같이 여러개의 값을 받지 않더라도 필수 값에 대해 명확한 의도를 제시해야 하는 경우
     Path Parameter를 사용한다. 그 외의 선택적 조건을 통한 검색은 Query Parameter를 통해 값을 받는다.

     위의 정의를 다시 한번 살펴보고 아래의 예시를 다시한번 살펴보며 차이점을 이해해보자.
     ex 1) active-shards/by-game/{game}/by-puuid/{puuid}
     ex 2) active-shards?game=&amp;puuid=
     ex 3) teams/{teamId}
     ex 4) teams?teamId=</code></pre><h3 id="추가적으로-필자가-design한-rest-api">추가적으로 필자가 Design한 REST API</h3>
<p>대표적으로 제시된 REST API Design Guide와 함께 개발을 하면서 필요하다고 생각을 한 부분에 대해 추가적으로 설계한 부분에 대해 정리하면 아래의 예시와 설명과 같다.</p>
<p>[1. open-api or api] <strong>/</strong>
[2. app or admin or 생략] <strong>/</strong>
[3. service_name] <strong>/</strong>
[4. API version] <strong>/</strong>
[5. 자원의 깊이, 관계, 책임] <strong>/</strong></p>
<ul>
<li><p>예시</p>
<pre><code>POST api/admin/product/v1/teachers
POST api/app/product/v1/course/{courseId}/reviews
GET open-api/product/v1/teachers/{teacherId}/reviews</code></pre></li>
<li><p>설명</p>
<ol>
<li>open-api or api: 공개 API인지 보호된 API인지 구분</li>
<li>app or admin or 생략: 보호된 API일때 클라이언트 권한 구분</li>
<li>service_name: 서비스 이름 명시 (user, product, notification)</li>
<li>API version: API 버전 명시 (v1, v2)</li>
<li>자원의 깊이, 관계, 책임 명시 (/teachers, /course/{courseId}/reviews)</li>
</ol>
<ul>
<li><p>단, POST, GET, DELETE, PATCH를 통해 행위를 설명하기 불가능하거나 애매한 경우 행위를 명시한다.</p>
<pre><code>    ex)
    POST lecture/{lectureId}/move
    설명: 강좌의 순서를 바꾸고 싶을때 사용
    Response: Lecture
    Class: LectureController

    이유: 행위를 명시하는 것은 특정 REST API 가이드에서는 위배되는 조건이다. 그러나 필자가 생각했을때
    REST API의 제일 중요한 원칙은 클라이언트와 서버 간의 통신이다. 즉, 직관적으로 API의 URI 및 메소드를
    보고 어떠한 행위가 일어나고, 어떠한 자원을 식별할 수 있다는것이 중요하다. 그래서 설명이 불가능하거나
    애매한 경우 행위를 명시하는 것으로 Design 했다.</code></pre></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[MSA 심장부를 설계하다 - Theme 6. MSA 통신 전략 (End)]]></title>
            <link>https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-Theme-6.-MSA-%ED%86%B5%EC%8B%A0-%EC%A0%84%EB%9E%B5-End</link>
            <guid>https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-Theme-6.-MSA-%ED%86%B5%EC%8B%A0-%EC%A0%84%EB%9E%B5-End</guid>
            <pubDate>Fri, 03 May 2024 16:07:56 GMT</pubDate>
            <description><![CDATA[<p>MSA 환경에서는 특정 서버가 분리 분해된 다른 우리의 서비스 또는 외부의 서비스와 어떻게 통신할 것인가에 대한 논의는 매우 중요한 논의이자 기본이다.</p>
<p>이번 시간에는 MSA 환경에서 어떻게 통신을 하면 좋을지에 대해 이야기 하는 시간을 가져보도록 하겠다.</p>
<p>외부와의 통신에 대해 크게 로직 처리 순서가 중요한 통신, 로직 처리 순서가 중요하지 않고 처리 효율성이 중요한 통신 2가지로 나눌수 있다. 각 통신의 효용성은 각 비즈니스의 상황에 따라 결정된다.</p>
<blockquote>
<ul>
<li>** 로직 처리 순서가 중요한 통신 **<ul>
<li>ex) MVC HttpClient, RestTemplate, WebFlux WebClient</li>
</ul>
</li>
</ul>
</blockquote>
<ul>
<li>** 로직 처리 순서가 중요하지 않고 처리 효율성이 중요한 통신 **<ul>
<li>ex) Message Queueing: Kafka, Rabbit MQ</li>
</ul>
</li>
</ul>
<h3 id="로직-처리-순서가-중요한-통신">로직 처리 순서가 중요한 통신</h3>
<blockquote>
<h4 id="httpclient-mvc">HttpClient (MVC)</h4>
</blockquote>
<ul>
<li>MVC 기반으로 개발 할때 우리가 개발하는 특정 도메인에서 순서가 중요한 비지니스 로직이 있을 수 있다. 이럴때는 순서를 보장해주는 HttpClient를 기반으로 통신을 하여 처리하는게 적합하다.</li>
<li>CommonHttpClient
MSA 환경에서 우리 서비스 및 서버와 통신하기 위한 보조로직을 HttpClient 기반으로 Mapping한 Class다.
MVC 기반으로 특정 서비스를 개발하고 있다면, 우리 서비스 및 서버와 통신할 때는 CommonHttpClient를 사용한다.<img src="https://velog.velcdn.com/images/will_d/post/13ebb5a7-ea15-4b1c-9ba0-6f3903800ff7/image.png" alt=""><blockquote>
<blockquote>
<p>** 주요 기능 **</p>
</blockquote>
</blockquote>
<ol>
<li>JWT Context 유지 (with Local Thread)<img src="https://velog.velcdn.com/images/will_d/post/0dacf7e9-a1c4-482f-bbc6-34333321c7d1/image.png" alt=""><ol start="2">
<li>Exception Handling </li>
</ol>
</li>
<li>값 변환<img src="https://velog.velcdn.com/images/will_d/post/53e74b1f-31cf-4da4-8867-f1e05cb7f04c/image.png" alt=""></li>
</ol>
</li>
</ul>
<blockquote>
<h4 id="webclient-webflux">WebClient (WebFlux)</h4>
</blockquote>
<ul>
<li>WebFlux 기반으로 개발을 할때 Async, NonBlocking 기반으로 외부와 통신해야 한다. WebFlux에서 병목 현상은 시스템에 치명적인 성능저하를 유발하기 때문이다. (참고: <a href="https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-Theme3.-MVC-WebFlux-%EA%B2%BD%EA%B3%84">MSA 심장부를 설계하다: Theme3. MVC, WebFlux 경계</a>) 그래서 WebFlux에서는 Async, NonBlocking Client인 WebClient를 지원하고, 이를 사용한다. WebClient가 Async, NonBlocking Client이긴 하나 이를 사용하는 효용성은 HttpClient를 사용하듯이 특정 비지니스의 순서가 중요한 로직에서 사용하는게 적합하다.</li>
<li>CommonWebClient
MSA 환경에서 우리 서비스 및 서버와 통신하기 위한 보조로직을 WebClient 기반으로 Mapping한 Class다.
WebFlux 기반으로 특정 서비스를 개발하고 있다면, 우리 서비스 및 서버와 통신할 때는 CommonWebClient를 사용한다.<img src="https://velog.velcdn.com/images/will_d/post/14292db8-bc99-417f-92e2-cf2d29dc2091/image.png" alt=""><blockquote>
<blockquote>
<p>** 주요 기능 **</p>
</blockquote>
</blockquote>
<ol>
<li>JWT Context 유지 (with Reactor Context)</li>
<li>Exception Handling</li>
<li>값 변환<img src="https://velog.velcdn.com/images/will_d/post/881d84aa-2d60-48c5-b5cc-5be98c4055dc/image.png" alt=""></li>
</ol>
</li>
</ul>
<h3 id="로직-처리-순서가-중요하지-않고-처리-효율성이-중요한-통신">로직 처리 순서가 중요하지 않고 처리 효율성이 중요한 통신</h3>
<blockquote>
<h4 id="message-queueing-with-kafka">Message Queueing (with Kafka)</h4>
<p>Message Queueing에 대한 설명은 Kafka를 기반으로 설명하도록 하겠다.
이커머스에서 특정 상품을 주문했을 때 재고에 대한 업데이트가 필요하며, 주문이 정상적으로 성공했을때 사용자에게 알림을 보내야 한다고 가정해보자. 이때 서버에서 로직처리를 할 때 재고가 모두 업데이트 되는 상황에 대해 응답을 받고 알림이 보내졌다는 응답을 모두 확인 한 후 사용자에게 최종 결과를 응답하는 것은 효율적일까? 이러한 방식으로 응답을 내리는것이 틀린 것은 아니지만 더 효율적인 방법이 있을거 같다. 정상적으로 사용자의 주문 요청이 완료 되면 바로 응답을 내리고 재고 처리와, 알림은 비동기적으로 처리하는것이다. 이러한 상황에서 우리는 비동기 통신을 활용할 수 있다. 이러한 절차를 기반으로 처리했을때 이점은 아래와 같다.</p>
<blockquote>
<ol>
<li>요청 - 응답 속도 향상 (사용자 경험 향상)</li>
<li>부하 / 분산을 통한 효율적 자원 활용 </li>
</ol>
</blockquote>
</blockquote>
<blockquote>
<p>결론적으로 우리의 서비스가 효율적인 자원활용과 빠른 처리가 필요할때 생각할 수 있는 방법이다.</p>
<blockquote>
<p>** * 참고) ** 부하 / 분산을 통한 효율적 자원 활용 및 성능 개선 이외에도 Kafka를 기반으로 통신하면 브로커 서버를 중심으로 각 서비스가 통신하여 서비스간의 결합도를 낮춰 더욱 확장성있는 프로그래밍을 가능하게 하는 장점도 있다. 이를 기반으로 한 아키텍처 EDA (Event Driven Architecture)가 있고 기회가 된다면 EDA에 대해 소개하는 포스팅을 남기도록 하겠다.</p>
</blockquote>
</blockquote>
<h3 id="kafka-운용">Kafka 운용</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/will_d/post/fbea10c4-0f0c-4a17-97e9-5c60609c4f8a/image.png" alt=""></p>
</blockquote>
<ol>
<li>각 도메인을 기준으로 Topic을 나눈다.</li>
<li>각 도메인의 특성과 처리량을 고려하여 Patition의 갯수를 정한다. (* Partition은 Kafka 운용에서 병렬처리에 대한 지표로서 중요한 역할을 한다.)</li>
<li>Producer를 통해 Kafka Broker 서버에 값을 발행할때는 Topic을 기준으로 아래와 같은 Json Data를 발행한다.
<img src="https://velog.velcdn.com/images/will_d/post/8bb0340e-7b4a-41c7-861a-e83f078e8093/image.png" alt=""></li>
<li>Partition의 갯수를 기반으로 Consumer Group에 Consumer를 배치하고, Topic을 기준으로 구독하여 Producer에서 보낸 값을 소비한다. (* Consumer Group에 배치된 Consumer들은 각각 특정 Partition을 할당받아 병렬 처리를 진행한다.)</li>
</ol>
<blockquote>
<p>** <em>부가 설명) *</em></p>
</blockquote>
<ul>
<li>Topic을 생성하는 기준은 각 도메인을 기반으로 생성한다. notification-service가 비동기 통신이 필요하면 이에 관한 Topic을 생성하고, product-sevice가 비동기 통신이 필요하면 이에 대한 Topic을 생성한다.</li>
<li>Consumer Group내의 Consumer들은 하나의 Topic을 공유할 수 있다. 이 말은 각 Consumer는 각각의 Partition을 할당 받아 각 Partition의 레코드를 처리한다. 이때 Consumer Group내의 Consumer들은 병렬처리를 진행한다. 즉, Partition은 병렬처리를 위한 중요한 성능 지표고 각 도메인의 특성을 고려하여 갯수를 잘 정하는것이 중요하다.</li>
<li>Producer는 우리가 정한 Protocol을 기반으로 값을 발행한다. Consumer 또한 이 Protocol을 이해하고 있다. 즉, Consumer는 Producer가 값을 발행하면 우리가 정한 Protocol을 기반으로 데이터를 해석하여 특정 비지니스 로직을 실행하는 Trigger 역할을 한다.</li>
</ul>
<h3 id="코드를-통해-자세히-이해해보자">코드를 통해 자세히 이해해보자</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/will_d/post/484357b4-9212-4370-9f14-04e36ecbafad/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/11d15a26-5859-488e-8e35-a5edcb423d29/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/431cbaa5-44e3-4cfe-80d4-d19265b19721/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/8e65016c-00f8-488e-850d-a130e994a70d/image.png" alt=""></p>
</blockquote>
<h3 id="통신을-안정적으로-운용하는-방법">통신을 안정적으로 운용하는 방법</h3>
<blockquote>
<p>동기적으로 통신을 하던, 비동기적으로 통신을 하던 통신에 대원칙이 있다. 한번 떠나간 요청은 다시 회수할 수 없다. 이미 떠나간 요청을 막는 방법은 없다. 이러한 특성을 고려하여 실패 상황에 대한 대응은 통신을 안정적으로 운용하는데 있어 매우 중요한다.</p>
</blockquote>
<ul>
<li>순서가 보장된 통신에서는 실패에 대한 응답을 확일 할 수 있다. 실패에 대한 응답을 기준으로 보상트랜잭션 (Rallback API)와 같은 처리를 통해 정합성을 맞출수 있다.</li>
<li>순서가 보장되지 않는 통신에서는 실패에 대한 응답을 확일 할 수 없다. 만약 Kafka를 통해 이벤트를 발행하여 특정 서버에서 이 처리를 하다 실패를 했을때 추후 대응을 위해 실패에 대한 요청을 관리해야 한다. 이 관리를 통해 데이터의 정합성을 맞출 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[MSA 심장부를 설계하다 - Theme 5. MSA Handle Exception]]></title>
            <link>https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-Theme-5.-MSA-Handle-Exception</link>
            <guid>https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-Theme-5.-MSA-Handle-Exception</guid>
            <pubDate>Fri, 26 Apr 2024 13:34:17 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/will_d/post/66752893-060b-4b7e-b7b3-e8ec60314a34/image.webp" alt=""></p>
<p>필자는 가끔 산책을 하며 견고하고 안전하게 잘 지어진 건축물을 바라보며 감탄 하는 경우가 종종 있다. 어떻게 저렇게 시간이 지나도 안전하게 사람들에게 가치를 제공할 수 있는지, 어떠한 설계 원칙과 생각을 가지고 건물을 짖는지 등 여러 생각과 함께 소프트웨어 개발에 대해 생각할 때가 있다.</p>
<p><strong>&#39;지속가능한 소프트웨어 구축&#39;</strong>은 개발자에게 가장 중요한 덕목이라고 해도 과언이 아니다. 사계절이 변하며 비가 오고 눈이 와도 사람들에게 안전한 공간과 가치를 건축물이 제공하듯 시스템 또한 세상이 변하고 비즈니스가 변해도 견고하고 안전하게 가치를 제공할 수 있어야 한다. 오늘은 지속가능한 시스템 구축에 있어 빼놓을 수 없는 예외처리에 대해 이야기 하는 시간을 갖도록 하겠다.</p>
<h2 id="msa-환경에서-예외를-처리하는-원리">MSA 환경에서 예외를 처리하는 원리</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/will_d/post/55f223bd-4097-47ae-83d3-ca526d27ea2a/image.png" alt="">
다수의 서버가 분리 / 분해된 MSA 환경에서 예외 처리 또한 이전 포스팅 (<a href="https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-Theme4.-MSA-JWT-%EC%A0%84%EB%8B%AC-%EC%A0%84%EB%9E%B5-like-Fractal">MSA 심장부를 설계하다: Theme4. MSA JWT 전달 전략 like Fractal</a>)에서 언급한 무한 프랙탈 구조와 닮아 있다. 특정 요청에 대한 예외를 발생시킨 서버에서 에러 메세지를 전달하고 전달받은 서버에서 이 에러메세지를 전달 받아 또 전달하는 구조이다. </p>
<blockquote>
<p>** 🚨Tip)** 필자는 계속해서  무한 프랙탈 구조에 대해 언급하고 있다. 개인적으로 필자가 MSA 설계를 하며 느끼는 감정은 무한히 반복되는 느낌이다. 즉, 무한 프랙탈 구조는 MSA에 대한 감각을 이해하는데 중요한 예시라고 할 수 있다. 이 글을 읽는 독자분들도 이 감각을 잘 알면 좋을거 같다.</p>
</blockquote>
</blockquote>
<h2 id="더-자세히-원리에-대해-알아보자">더 자세히 원리에 대해 알아보자</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/will_d/post/d3bc3878-3e9d-4840-a339-701ec3487be4/image.png" alt="">
클라이언트는 응답 서버에 요청을 보낸다. 응답 서버의 특정 로직에서 만약 Exception이 발생한다면 Common에 구축된 Exception Handler의 원리에 의해 클라이언트에 Exception 정보를 내려준다.<img src="https://velog.velcdn.com/images/will_d/post/e719b03a-7f10-4aea-afd7-519557b4ef29/image.png" alt="">
응답을 받는 서버는 Exception을 Catch하며 위와 같은 응답 구조를 알 수 있다. 응답을 받는 서버는 2가지 선택지가 있다. 응답을 주는 서버가 내린 Exception을 그대로 내릴지 특정 로직을 처리후 자신이 원하는 응답을 내릴지 선택해야 한다. 이러한 원리를 계속해서 반복함으로서 여러 서버가 분리 / 분산된 MSA 환경에서 Exception을 Handling 할 수 있다.</p>
</blockquote>
<h2 id="코드를-통해-더-자세히-이해해보자">코드를 통해 더 자세히 이해해보자</h2>
<blockquote>
<h4 id="mvc-원리">MVC 원리</h4>
<p><img src="https://velog.velcdn.com/images/will_d/post/eeb55ccd-a138-4a59-9f86-c486cbad0851/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/c12dc6f8-e984-46dd-8f96-ffbcd5b1e4e7/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/dd278d9a-7af3-467c-b7d1-793665bae55c/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/c4c99241-2c5c-4fef-9ca2-61054eb161f2/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/604c07f8-6ad2-4794-9c24-b5c4350e7dff/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/5831f247-7bd9-4b71-b79c-42d2d64a3a5e/image.png" alt="">
CommonHttpClient는 응답 값을 리턴 하기 전에 응답 서버에서 내린 status code를 통해 요청에 대해 성공인지 실패인지 판단한다. 실패라면 (응답을 내리는 서버에서 Exception이 발생) ApiExceptionImpl에 statusCode와 Result를 전달하여 ApiExceptionImpl Exception을 던진다. 이때 요청 서버에서는 그대로 Exception을 내릴지 아니면 특정 처리를 해서 내릴지 선택한다. 만약 그대로 내린다면 응답을 내리는 서버에서 Exception Handler를 통해 Exception을 내리듯이 똑같은 원리에 의해 Exception을 내릴수 있다.</p>
</blockquote>
<blockquote>
<h4 id="webflux-원리">WebFlux 원리</h4>
<p><img src="https://velog.velcdn.com/images/will_d/post/d0ab662b-e413-4f52-aecd-cd0fb6ca7e2f/image.png" alt=""> <img src="https://velog.velcdn.com/images/will_d/post/06f7c660-f44b-4d0d-bdf7-15c213e24de6/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/5bcc81fb-5e7a-429f-9c78-2c45d4153c35/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/abf86a7c-b67e-4269-8e84-50e580ba92a5/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/cdb62bc8-686f-4ce9-8c40-1564bb0b4e6e/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/f9a87588-4b00-4b7f-8962-ed268669490b/image.png" alt="">
WebClient는 응답을 내리는 서버에서 Exception이 발생하면 WebClientResponseException이 발생한다. 이 Exception을 Catch 하여 MVC와 똑같은 과정으로 Exception을 처리한다.</p>
</blockquote>
<p>이러한 과정이 각 서버와 서버 사이에서 계속 반복해서 이루어질 것이고 이를 통해 분리 / 분산된 MSA 환경에서 Exception Handling을 처리 할 수 있다.</p>
<h2 id="마무리">마무리</h2>
<p>재능인가 노력인가, 개발자로 살아가다 보면 주변에서 개발 재능이 너무 띄어나 절대 따라잡지 못한다는 말을 종종 듣는거 같다. 어떤 이들은 노력해도 따라 잡지 못한다고 한다. 정말 그럴까? 필자는 재능보다 더 중요한게 있다고 생각한다. 바로 &#39;태도&#39;다. &#39;지속가능한 소프트웨어를 구축하겠다.&#39;, &#39;더 탁월하고 훌륭한 소프트웨어를 구축하겠다. 과연 내가 작성한 코드는 탁월한가, 옳은가, 적합한가?!&#39;라는 태도를 가진 진중한 개발자라면 재능이 있는 개발자 보다 그 끝에는 더 잘할것이라고 믿는다. 즉, &#39;재능만으로 일류가 된 사례는 없다. 태도만이 유일한 일류가 되는 길이다.&#39;라는 말처럼 휼륭한 태도가 훌륭한 개발자를 만들고 탁월한 소프트웨어 및 비지니스를 만들것이다. 이 말은 지속가능한 소프트웨어의 구축에서 빼놓을 수 없는 Exception Handling에 대해 구축 할때 꼭 말하고 싶었다... 그러니 다들 할 수 있다!!!</p>
<p>긴글 읽어주셔서 감사합니다☺️</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MSA 심장부를 설계하다: Theme4. MSA JWT 전달 전략 like Fractal]]></title>
            <link>https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-Theme4.-MSA-JWT-%EC%A0%84%EB%8B%AC-%EC%A0%84%EB%9E%B5-like-Fractal</link>
            <guid>https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-Theme4.-MSA-JWT-%EC%A0%84%EB%8B%AC-%EC%A0%84%EB%9E%B5-like-Fractal</guid>
            <pubDate>Thu, 18 Apr 2024 11:34:43 GMT</pubDate>
            <description><![CDATA[<p>** 🚨 참고) ** 이글은 Hexagonal Architecture 기반으로 설명을 하고 있습니다. <a href="https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-Theme1.-Hexagonal-Architecture">MSA 심장부를 설계하다: Theme1. Hexagonal Architecture</a> 먼저 읽으시고 이글을 읽으시는 것을 추천드립니다😀
<img src="https://velog.velcdn.com/images/will_d/post/deb648b7-71da-4236-92c8-ec3e3f3a0d24/image.png" alt=""></p>
<p>위키 백과에서는 프랙탈 구조를 위와 같이 정의한다. 프랙탈 구조는 쉽게말해 무한히 반복되는 구조다. MSA 환경에서도 이러한 구조를 직면할 수 있는데 각 서버에 JWT 정보를 전달하는 전략을 구축할때 이러한 구조를 마주할 수 있다.</p>
<p>이번 MSA 심장부를 설계하다 4번째 시리즈에서는 분리 / 분산된 서버 환경에서 어떻게 JWT Context를 전달하는지에 대한 설명을 준비했다. 그리고 더 나아가 Passport 전략에 대해서도 간단히 언급하는 시간을 가지도록 하겠다.</p>
<h2 id="api-gateway">API Gateway</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/will_d/post/36514466-2e56-4421-9319-d6dfe1c2be61/image.png" alt="">
MSA 개발 환경에서는 분리 / 분산된 서버를 조직적으로 관리하기 위해 하나의 진입점을 둔다. 이러한 단일 진입점을 API Gateway 서버라 부른다. API Gateway 서버는 각 서비스에 접근하기 위한 모든 요청을 수용하고 트래픽을 분리 / 분산 시킨다. 각 서비스에 분리 / 분산 하기전 API Gateway 서버는 Auth 서버와의 통신을 통해 인증 / 인가를 거친 후 JWT 정보와 함께 트래픽을 각 서버에 할당한다.</p>
</blockquote>
<h2 id="요청-endpointcontroller-도달-흐름">요청 Endpoint(Controller) 도달 흐름</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/will_d/post/18095207-14d4-46dc-91f7-20aadd60272c/image.png" alt="">
API Gateway 서버로 부터 요청을 할당 받은 하나의 서버의 Endpoint 도달 과정을 살펴보면 위 그림의 오른쪽 사진과 같다. API Gateway 서버는 파싱한 JWT 정보를 Object 형태로 만든후 JSON 형태로 직렬화하여 Header에 주입해 정보를 전달한다. 이때 각 서버의 Filter에서는 Local Thread (MVC) or Reactor Context (WebFlux)에 이 값을 저장하여 유지하고, Resolver에서는 이 값을 다시 역직렬화 하여 Controller에 값을 주입 할 수 있는 상태를 구현한다. 더 자세한 내용은 아래를 살펴보자.</p>
<blockquote>
<p><strong>🚨 주의)</strong> WebFlux를 기반으로 Web Application Server를 구성한다면 특정 Context 정보를 Local Thread에 저장하면 안된다. 이유는 WebFlux는 하나의 요청에 대한 작업에 여러 Thread가 관여하여 Thread의 전환이 빈번하게 일어나기 때문이다. 자세한 내용은 이전에 작성한 <a href="https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-Theme3.-MVC-WebFlux-%EA%B2%BD%EA%B3%84">MSA 심장부를 설계하다: Theme3. MVC, WebFlux 경계</a>글을 참고 부탁드린다.</p>
</blockquote>
</blockquote>
<h2 id="local-thread-reactor-contex-jwt-주입-및-전달-원리">Local Thread, Reactor Contex JWT 주입 및 전달 원리</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/will_d/post/52606524-b525-4854-a042-9371649c9134/image.png" alt="">
위의 그림을 보면 Filter에 저장된 값이 다시 뽑아져 다른 서버와 통신하면서 JWT Context를 전달하는 흐름을 알 수 있다. 실제 구현 코드는 아래의 코드를 참고 부탁드린다.</p>
<blockquote>
<p>*<em>👨🏻‍💻 질문) 왜 이렇게 JWT 정보를 Local Thread, Reactor Context에 저장하고 뽑아내는 전략을 구축한것인가요? *</em>
우리가 API Gateway에서 본 그림을 보면 자원서버는 Private Network 환경에 있다는것을 알 수 있다. 즉 API Gateway를 통과하여 특정 자원서버로 도달했다는 말은 인증 / 인가에 대해 원활하게 수행했다고 해석할 수 있다. 그렇다면 내부의 자원 서버간의 통신을 할때 또 인증 / 인가를 거치는 것은 적합한가? 필자는 실제로 이 고민에 대해 깊게 했다. 필자의 결론은 이미 Gateway를 통과했다면 내부에서는 더이상 인증 / 인가를 거치지 않고 JWT 정보를 연속적으로 전달하는것이 더욱 효율적이고 Private Network 환경이니 안정하다 판단했다.(물론 이 방법도 정답은 아닐것이다. 오히려 보안 레벨을 높여야 되는 상황이면 내부에서 통신 할때도 인증 / 인가를 거치는것이 적합할 수 있다.) 즉, Filter에서 Header의 JWT JSON 값을 뽑아내 JWT JSON 정보를 Local Thread, Reactor Context에 저장하고 우리 서버와 통신할때는 같은 방식으로 Header에 값을 저장하여 전달한다. 위에서 서두에 프랙탈 구조에 대해 이야기 했는데 이렇게 하면 계속 해서 반복적인 구조로 Context가 유지되어 프랙탈 구조를 만들 수 있다. 필자도 처음에 이 구조를 다 구현해서 완성했을때 뭔가 프랙탈 구조를 구현한거 같아서 너무 기분이 좋았다 ㅎㅎㅎ...</p>
</blockquote>
</blockquote>
<blockquote>
<p><strong>* MVC (Local Thread 사용)</strong>
<img src="https://velog.velcdn.com/images/will_d/post/6f60c6ba-b8ce-4df1-98c6-2d4e739e7f9b/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/c8d64cb0-cdd3-49b4-9a1c-c6b673a3d1f9/image.png" alt="">
요청을 담당하는 CommonHttpClient에 Local Thread의 값을 뽑아내는 코드를 구현하여 JWT Context를 외부의 서버에 전달했다.</p>
</blockquote>
<blockquote>
<p><strong>* WebFlux (Reactor Context 사용)</strong>
<img src="https://velog.velcdn.com/images/will_d/post/3bb786f5-ecfe-4b08-8377-045a326ef1b6/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/e9aecd3c-3020-4d3c-bd04-8c0355cca795/image.png" alt="">
요청을 담당하는 CommonWebClient에서 Reactor Context의 값을 뽑아내는 코드를 구현했다. 이가 가능한 이유는 Filter CommonWebClient를 사용하는 시점에는 Filter에서 Context값을 저장하는 시점과 비교했을때 Up Stream이기 가능한 코드이다. 이러한 원리를 통해서 WebFlux에서도 JWT Context를 유지했다.</p>
</blockquote>
<h2 id="endpointcontroller-jwt-주입-및-전달-원리-with-resolver">Endpoint(Controller) JWT 주입 및 전달 원리 With Resolver</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/will_d/post/11253a8a-5ec9-48b3-a529-eaeec40dc1f9/image.png" alt="">
Argument Resolver에서도 Header에서 JWT JSON 값을 뽑아 역직렬화 하여 Controller에 주입하는 코드를 구현한다.
<strong>👨🏻‍💻 질문) 이미 위에서 JWT Context 정보를 저장하였는데 왜 Resolver 까지 사용하는 건가요?</strong> 
서버와 서버간의 JWT Context 유지는 Filter, Local Thread, Reactor Context를 통해서 실현하고, 비지니스 로직에 필요한 JWT 값을 전달하는 역할은 Resolver에서 주입한 값을 통해 실현하는것이 경계를 분명히 하여 클린 코드를 유지하는 방식이기에 이러한 구조를 채택하였다. 예를 들자면, Resolver를 구현하지 않아도 비지니스 로직을 구현할때 JWT 정보가 필요하다면 Context에서 값을 뽑아내 직렬화 하여 충분히 사용할 수 있다. 그러나 JWT의 어떤 특정 값만 사용하는 것인데 모든 정보를 뽑아내는것이 과연 타당한가? Controller에서 비니지스 레이어에서 필요한 특정 값을 넘겨주어 비지니스 레이어의 경계를 분명히 하는것이 더 타당하지 않을까? 그래서 Resolver를 구현했다. 추가적으로 Filter를 구현하지 않아도 충분히 서버와 서버간의 JWT Context 유지를 Resolver를 통해 가능하다 그러나 이 또한 비지니스 레이어에 불필요한 데이터를 넘겨주는 것과 같다. 위와 같은 이유로 경계를 분명히 하기 위해 Filter, Local Thread, Reactor Context 전략을 구축했다. 이렇게 함으로서 비지니스 레이어를 지키고 클린 코드를 작성할 수 있다. (이 부분은 필자도 정말 많이 고민하고 고민한 부분이다. 혹시 이해가 잘 안되는 분이 계신다면 과감하게 댓글 남겨주시면 감사하겠습니다😀) 실제 Resolver 주입 코드는 아래와 같다.</p>
</blockquote>
<blockquote>
<p>*** MVC Resolver **
<img src="https://velog.velcdn.com/images/will_d/post/9f675499-2213-4d25-981b-1a0875223d63/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>*** WebFlux Resolver **
<img src="https://velog.velcdn.com/images/will_d/post/70916b12-c123-4a1a-b18d-7471a4675b27/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>*** Endpoint **
<img src="https://velog.velcdn.com/images/will_d/post/d9893c81-b589-4827-a7b8-f939dc9328c0/image.png" alt=""></p>
</blockquote>
<h2 id="흐름-정리">흐름 정리</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/will_d/post/e0171f7f-507c-4f2c-8dee-30c07f9a1422/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/c32afeac-bb40-4006-816d-9de1028c627a/image.png" alt=""> 
흐름을 다시 한번 정리해보면, API Gateway에서 전달된 JWT 정보는 Auth를 거처 validate 되어 Object로 만들어 지며, 이 Object는 또 직렬화 되어 Header에 값이 저장될것이다. 이러한 과정을 거쳐 API Gateway 서버에서 특정 서버에 요청을 Route 할것이고 이 요청을 받은 서버는 위에서 설명한 원리로 계속해서 JWT Context를 유지해 나간다.</p>
</blockquote>
<h2 id="passport-전략">Passport 전략</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/will_d/post/8b2ac99e-3c37-4047-bd8a-898166886d15/image.png" alt="">
이 원리를 더욱 확장한다면 Passport 전략까지 구축을 할 수 있다. 즉 JWT 토큰 정보를 기반으로 사용자 정보를 User 서버에 요청하여 JWT 정보가 아닌 User 정보의 Context를 유지하는것이다. 그렇다면 왜 User Context를 유지하는 것인가 B2C 서비스는 사용자를 중심으로 비지니스가 수행된다. 즉 이말을 우리가 비지니스 로직을 작성할때 사용자 정보를 기반으로한 비지니스 로직을 매우 많이 작성한다는 말이다. 그리고 다시 이 말을 해석하면 그많큼 많은 서비스가 User 서비스에 수많은 요청을 한다는 것이다. 그렇다면 하나의 요청에 대해 매번 각 서비스에서 User 서버에 User의 정보를 요청하는 것이 아닌 한번만 요청하여 이 정보를 계속 유지할 수 있다면 더욱 효율적이고 User 서버의 트래픽을 낮출수 있지 않을까? 그래서 나온 방식이 Passport 방식이라고 할 수 있겠다. Passport 방식을 통한 서버 전체 최적화 글은 다른 포스트에서 작성하도록 하겠다.</p>
</blockquote>
<h2 id="마무리">마무리</h2>
<p>MSA 구조는 개발하다 보면 가끔 아름답다는 생각이 든다. 이번 MSA 심장부를 설계하다: Theme4. MSA JWT 전달 전략 like Fractal 글에서는 그 아름다움을 다시금 느끼는 계기가 되었다.
긴글 읽어주셔서 감사합니다😀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MSA 심장부를 설계하다: Theme3. MVC, WebFlux 경계]]></title>
            <link>https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-Theme3.-MVC-WebFlux-%EA%B2%BD%EA%B3%84</link>
            <guid>https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-Theme3.-MVC-WebFlux-%EA%B2%BD%EA%B3%84</guid>
            <pubDate>Wed, 03 Apr 2024 17:04:13 GMT</pubDate>
            <description><![CDATA[<p>Spring Framework 기반으로 Web Application Server를 개발한다고 하면 크게 두가지 방법이 있다. </p>
<ol>
<li>spring-web-mvc: Sync, Blocking 기반</li>
<li>spring-webflux: Async, NonBlocking 기반</li>
</ol>
<p>두 방식은 서로 다른 모델을 기반으로 Web Application Server를 개발 할 수 있도록 한다. 서로 다른 두 모델의 차이점을 이해하고 MSA 프로젝트에서 어떻게 경계를 나눠 사용하는지 알아보자.</p>
<h2 id="spring-web-mvc-동작-방식">Spring Web MVC 동작 방식</h2>
<blockquote>
<ul>
<li><strong>기본 설명</strong>
Spring Web MVC는 기본적으로 서블릿 컨테이너인 Tomcat을 기반으로 Sync, Blocking 방식으로 작업을 수행한다. Spring Web MVC에서는 효율적인 Thread 관리를 위해 Thread Pool을 운용하며, 하나의 요청에 대해 Thread Pool에서 관리중인 하나의 Thread를 할당하고 Thread Pool의 최소 Thread 갯수를 넘어 그 이상의 요청이 들어온다면 Thread를 생성하여 각각의 요청에 대해 Thread를 할당한다. (단, Tomcat은 Thread Pool에서 기본적으로 관리할 최소 Thread의 갯수를 정하고 동시에 다량의 요청이 들어왔을때 추가로 생성하여 대응할 최대 Thread의 갯수를 설정을 통해 관리한다. 즉, 이 최대 Thread의 갯수가 초과 되면 더이상 Thread를 생성하지 않고 나머지 요청을 대기상태로 진입시킨다. 또한 최대 Thread의 갯수를 초과할 만큼의 다수의 요청이 끝나고 일정 시간이 지나면 Thread를 회수하여 Thread Pool에 최소 Thread의 갯수를 유지하여 자원을 효율적으로 관리한다.) Spring Web MVC에서 단일 요청을 담당하는 Thread는 특정 요청의 작업이 모두 완료될때 까지 기본적으로 순차적으로 작업을 진행하고 작업을 진행하는 동안 이외의 작업을 수행할 수 없다. 즉, 요청을 처리하는 Thread는 Blocking 상태가 된다.</li>
</ul>
</blockquote>
<ul>
<li><strong>요약</strong> <ul>
<li>Spring Web MVC는 효율적인 Thread 관리를 위한 Thread Pool 운용</li>
<li>Spring Web MVC는 설정을 통해 Thread Pool에 기본적으로 관리할 최소 Thread 갯수 지정</li>
<li>Spring Web MVC는 설정을 통해 Thread Pool에서 관리할 최대 Thread 갯수 지정</li>
<li>최소 Thread의 갯수를 넘는 요청이 발생하면 추가로 Thread를 생성하여 대응하고 최대 Thread를 초과하는 다량의 요청이 들어오면 더이상 Thread를 생성하지 않고 나머지 요청은 대기 상태로 진입</li>
<li>최대 Thread 갯수를 초과하는 다량의 요청이 모두 종료 되고 일정시간이 지나면 Tomcat은 효율적인 자원관리를 위해 최소 Thread 갯수만 Thread Pool에 유지</li>
<li>하나의 요청에 대해 하나의 Thread를 할당하며 특정 요청에 할당된 Thread는 기본적으로 작업을 순차적으로 진행하며 작업이 모두 완료 될때 까지 Blocking 상태를 유지</li>
</ul>
</li>
</ul>
<h2 id="spring-webflux-동작-방식">Spring WebFlux 동작 방식</h2>
<blockquote>
<ul>
<li><strong>기본 설명</strong>
Spring Webflux는 기본적으로 Netty와 Reactor(Reactive Streams 구현체)를 기반으로 Async, NonBlocking 방식으로 작업을 수행한다. WebFlux는 요청이 들어 오면 Boss Event Loop Group(소수의 Thread로 구성이 되어 있다. 이해를 위해 하나라고 생각해도 좋을거 같다.)에서 이 요청을 수락하고 Chanel을 통해 Worker Event Loop Group(기본적으로 컴퓨터 코어수를 기반으로 Thread가 생성이 된다.)의 특정 Woker Thread에 이 요청을 할당한다. 특정 요청을 할당 받는 Worker Thread는 NonBlocking I/O 모델을 기반으로 Reactive Stream인 Mono, Flux의 구독을 진행하고 구독이 된 Reactive Stream(Mono, Flux)은 다양한 Scheduler를 통해 Async, NonBlocking 모델을 기반으로 실질적인 작업을 처리한다. WebFlux는 MVC와 다르게 각각의 요청에 대해서 Thread를 할당하지 않고 Event Loop를 기반으로 소수의 Thread로 다수의 요청들에 대해 비동기적으로 작업을 처리한다. 그 결과 WebFlux는 적은 자원(소소의 Thread)을 활용하여 고성능, 고효율의 성능을 발휘할수 있다.</li>
</ul>
</blockquote>
<ul>
<li><strong>요약</strong><ul>
<li>WebFlux는 효율적인 자원 활용을 위해 Event Loop 기반으로 동작</li>
<li>요청을 수락하는 Boss Event Loop Group은 소수의 Thread로 구성</li>
<li>NonBlocking I/O 모델을 기반으로 Worker Thread는 Reactive Stream(Mono, Flux)를 구독</li>
<li>Worker Thread는 기본적으로 CPU 코어수를 기반으로 생성</li>
<li>다수의 요청에 대해 각 Thread가 할당되는 방식이 아닌 Event Loop를 기반으로 저자원 고성능, 고효율 발휘</li>
</ul>
</li>
</ul>
<h2 id="spring-web-mvc-vs-spring-webflux">Spring Web MVC vs Spring WebFlux</h2>
<blockquote>
<p><strong>Spring Web MVC</strong></p>
</blockquote>
<ul>
<li>** 명령형 프로그래밍**<ul>
<li>행위를 중심으로 프로그램 개발을 진행하는 방식이다.</li>
<li>기본적으로 동기적으로 프로그래밍 개발한다.</li>
</ul>
</li>
<li><strong>메모리 효율성</strong><ul>
<li>하나의 Thread에는 독자적인 메모리 영역이 할당되는데 Thread Pool을 운용하는 Tomcat은 기본적으로 다수의 Thread를 생성해 놓으며, 필요에 의해 다수의 Thread를 추가로 생성한다. 즉, 생성된 Thread의 갯수 만큼 독자적인 메모리 영역이 할당될것이고 이는 메모리 효율성을 저하 시킬 여지가 있다.</li>
</ul>
</li>
<li><strong>CPU Bounds: 시스템의 성능이 CPU의 처리 능력에 의해 제한되는 경우</strong><ul>
<li>Spring Web MVC는 하나의 요청에 대해 하나의 Thread를 할당한다. 즉, 시스템의 CPU 코어수를 초과하는 다수의 Thread가 생설될 수 있음을 암시한다. 이러한 상황은 CPU에 큰 부하를 주는 다량의 Context Switching을 발생 시켜 CPU Bounds를 발생 시킬 여지가 있다.</li>
</ul>
</li>
<li><strong>I/O Bounds: 시스템의 성능이 I/O 작업에 의해 제한되는 경우</strong><ul>
<li>Spring Web MVC는 Blocking I/O 모델을 기반으로 작업을 수행한다. 즉, 하나의 요청에 대해 할당된 Thread는 현재 수행 중인 작업 이외의 작업을 수행 할 수 없다. 이렇게 특정 작업에 묶여 있는 다수의 Thread가 존재하는 것은 대기상태인 Thread가 다수 존재할 수 있음을 암시하고 이것은 I/O Bounds를 발생 시킬 여지가 있다.</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>Spring WebFlux</strong></p>
</blockquote>
<ul>
<li><strong>반응형 프로그래밍</strong><ul>
<li>데이터를 중심으로 프로그램 개발을 진행하는 방식이다.</li>
<li>기본적으로 Stream을 통해 비동기적으로 개발을 진행한다.</li>
<li>Reactive Stream의 형태는 선언형 프로그램의 형태를 띈다.</li>
</ul>
</li>
<li><strong>메모리 효율성</strong>
  소수의 Thread를 운용하는 철학을 가진 WebFlux는 상대적으로 많은 수의 Thread를 생성하지 않는다. 이는 상대적으로 Thread 자체적인 메모리 할당이 많지 않다는 뜻이므로 효율적으로 메모리를 사용한다고 할 수 있겠다.</li>
<li><strong>CPU Bounds: 시스템의 성능이 CPU의 처리 능력에 의해 제한되는 경우</strong>
  소수의 Thread를 운용하는 철학을 기반으로 다수의 Thread를 생성하지 않아 다수의 Context Switching이 발생할 여지가 많이 줄어 CPU Bounds가 상대적으로 덜하다고 할 수 있다.</li>
<li><strong>I/O Bounds: 시스템의 성능이 I/O 작업에 의해 제한되는 경우</strong>
  하나의 요청에 대해서 NonBlocing I/O 모델을 기반으로 작업을 수행한다. 하나의 요청에 대해 작업을 처리하는 Thread는 상황에 맞게 효율적으로 다수의 작업을 진행할 수 있음을 암시한다. 즉, 병목현상이 발생하지 않는다는 말이다. 이는 I/O Bounds가 상대적으로 덜하다고 할 수 있다.<blockquote>
<p><strong>주의점</strong>
위의 설명과 차이점을 비교해보면 WebFlux를 사용하는 것이 무조건 더 나은 성능을 발휘한다고 생각할 수 있겠다. 그렇다면 진짜 무조건 WebFlux가 MVC보다 무조건 더 나은 성능을 발휘할까? 결론은 그렇지 않다. 적은 수의 Thread를 운용하는 WebFlux의 가장 큰 약점은 병목현상이다. 개발자의 실수 또는 환경으로 인해 병목현상이 발생하면 특정 Thread는 그 작업에 묶이게 되고 적은 수의 Thread를 운용하는 WebFlux의 성능은 현저히 떨어진다. 실제 병목 현상이 발생하면 Spring WebFlux가 Spring Web MVC 보다 더 낮은 성능 퍼포먼스를 보인다. 그러니 WebFlux를 사용할때는 항상 병목현상을 주의하고 병목현상에 대한 테스트를 반드시 진행해야 한다.</p>
</blockquote>
</li>
</ul>
<h2 id="msa-프로젝트-mvc-module-webflux-module-경계-설정">MSA 프로젝트 MVC Module, WebFlux Module 경계 설정</h2>
<blockquote>
<p>우리는 위의 설명을 통해 Spring Web MVC와 Spring WebFlux의 차이를 이해하고 서로다른 두 모델을 이해했다. 그렇다면 실제로 코드를 작성하는 개발자 입장에서는 어떠한 차이점이 있을까? 결론은 Spring WebFlux는 Reactive Programming을 기반으로 Reactive Stream(Mono, Flux)을 구현해야 한다. 그 결과 아래의 4가지 기술을 구현하는 방식이 달라진다. 이를 근거로 하여 mvc module과 webflux module을 분리했다.</p>
</blockquote>
<ul>
<li>Exception Handler</li>
<li>Resolver</li>
<li>Filter</li>
<li>Client</li>
</ul>
<blockquote>
<p><strong>질문 👨🏻‍💻) 리액티브 스트림이 뭔가요</strong>
간단히 설명해서 리액티브 스트림은 데이터를 중심으로 행위의 흐름을 나타내는 집합이라고 할 수 있다. 이러한 여러 스트림의 상화작용을 통해 우리는 논리를 완성하고 기능을 구현한다. 그리고 이것을 복합적으로 Reactive Progamming이라고 할 수 있겠다.</p>
</blockquote>
<h2 id="마무리">마무리</h2>
<blockquote>
<p>필자는 원래 iOS, Android를 개발한 Mobile 개발자였다. 지금도 회사에서 백엔드를 메인으로 개발을 진행하고 있지만 iOS, Android 개발도 필요하면 진행하고 있다. (ex) WebView Mapping, fcm 기능, DynamicLink 기능) iOS, Android 개발을 할때 필자는 RxSwift, RxKotlin을 사용한다. 이 Rx 시리즈들은 Reactor과 같이 Reactive Streams의 구현체다. 필자는 모바일 개발을 할때 Reactive Progreamming을 기반으로 많은 프로그래밍을 했고 많은 학습을 진행했는데 WebFlux를 다시 봤을때 너무 반가웠다. 다시 고향으로 돌아온 느낌이였다. </p>
</blockquote>
<blockquote>
<p>그런데 갑자기 본질적인 질문이 머릿속에 떠올랏다. 왜 Spring 사단에서는 Reactive Programming이 대중화 되어 있지 않을까? (실제 Mobile 사단에서는 Reactive Programming은 기본이다.) 러닝 커브 때문인가? 아니면 Reactive Programming을 사용하는 것은 오버스팩이고 사용할 필요가 없어서 사용하지 않는것인가? 모바일도 그렇고 백엔드도 그렇고 다른 Framework이나 기술들을 살펴보면 확실히 비동기 프로그래밍의 중요성, 이를 기반으로 한 Publish, Subject 개념은 대두되고 있다. (이는 복잡한 세상의 비지니스를 해결하기 위한 노력과 하드웨어의 발전사(더 많은 CPU 코어수)와 관련이 있을것이다.) 즉, 이 두 개념을 기반으로 한 Reactive Progamming은 시대의 발전과 함께 가고 있다. 그렇다면 왜 적극적으로 이 시대의 흐름을 반영하지 않는것인가.</p>
</blockquote>
<blockquote>
<p>백엔드의 시스템은 방대하다. 하나의 시스템이 몇년 길게는 10년을 넘길 수 있다. 이렇게 긴 수명으로 유지된 시스템의 본질과 근간을 바꾸는 것은 쉽지 않을것이다. 효율성이 떨어진다고 해도 올바르게 동작하는 시스템을 개혁해서 위험 부담을 지는것은 옳지 않은 판단이다. 그래서 변하기 쉽지 않구나, 트랜드를 바로 반영하기 쉽지 않구나 생각했다. 또한 이러한 흐름을 기반으로 개발한 다수의 개발자들은 이 기조를 기반으로한 기술에 특화되어 있을것이다. 시스템을 개발하는 대상의 Pool또한 여기에 맞춰질 수 밖에 없다. 그러니 시대의 흐름이 아무리 변하고 더 좋은 기술이 나와도 신중해질수 밖에 없다.</p>
</blockquote>
<blockquote>
<p>하지만 필자는 미래에 모두 Reactive Programming으로 개발할 것이라고 생각한다. 개발자에게 여러 중요한 덕목이 있지만 그중 가장 중요한 덕목은 항상 자원을 낭비하지 말고 효율적으로 사용하자는 것이다. WebFlux도 그렇고 모든 Reactive Programming이 자원을 효율적으로 사용하자는 철학을 근거로 한다. 또한 기술의 발전이 이를 중심으로 발전하고 있다. 지금 현재는 복잡하고 어려워 오버스팩이라는 소리를 듣지만, 모두가 익숙해지는 날이 오고 위의 아름 다운 철학과 시대의 흐름이 잘 녹여진다면 기본이 되고 표준이 될것이라고 생각한다.</p>
</blockquote>
<p>긴글 읽어주셔서 감사합니디.☺️</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MSA 심장부를 설계하다: Theme1. Hexagonal Architecture]]></title>
            <link>https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-Theme1.-Hexagonal-Architecture</link>
            <guid>https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-Theme1.-Hexagonal-Architecture</guid>
            <pubDate>Thu, 28 Mar 2024 15:45:49 GMT</pubDate>
            <description><![CDATA[<h2 id="참고">참고</h2>
<p><a href="https://velog.io/@will_d/%ED%95%B5%EC%82%AC%EA%B3%A0%EB%82%A0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98">Hexagonal Architecture - 훌륭한 아키텍처는 코드를 보는 좋은 눈을 길러준다.</a></p>
<p>Architecture의 중요성에 대해 이전 Hexagonal Architecture글에서 많이 언급한거 같다. 이번 시간에는 MSA의 환경에서 Hexagonal Architecture의 효용성과 어떻게 코드를 작성하면 좋을지 이야기 하는 시간을 가져보겠다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/will_d/post/47ee8942-2cac-4f0a-a83e-7ed6b69ce481/image.png" alt="">
&#39;외부로 부터 비지니스를 지키고 집중하자&#39;는 핵심 철학을 가진 Hexagonal Architecture의 시각적 그림은 위와 같다. 그러나 위의 그림을 보고 Hexagonal Architecture를 이해 하는것은 무리가 있다. 조금더 실전적인 그림을 보자</p>
</blockquote>
<h2 id="hexagonal-architecture-시나리오">Hexagonal Architecture 시나리오</h2>
<blockquote>
<p>이 그림은 필자가 핵사고날 아키텍처를 직접 사용하면서 정리한 시나리오다. 이 시나리오를 만들면서 생각한 생각을 공유하고자 한다. 
<img src="https://velog.velcdn.com/images/will_d/post/2c6cd536-9539-4942-9dce-67762579b2a0/image.png" alt=""></p>
</blockquote>
<h3 id="1-in-adapter-동작">1. In Adapter 동작</h3>
<ul>
<li>외부 호출에 대한 상호작용을 담당하는 In Adapter의 대표적 사례로는 Controller, Kafka Consumer, Spring Batch가 있다. In Adapter에서는 결국 In Port(UseCase)를 통해 Business를 담당하는 Service를 호출 할 것이다. 이때 요청과 함께 받아온 데이터 AsyncTask, Request Body, Header를 Command로 변환하여 호출한다.</li>
</ul>
<h4 id="👨🏻💻질문-command로-변환해서-호출하는-이유가-뭔가요">👨🏻‍💻질문) Command로 변환해서 호출하는 이유가 뭔가요?</h4>
<ul>
<li>Command는 Service의 함수를 호출하는 실행 단위이다. Command를 만든 이유는 크게 2가지다.
첫째, 어떠한 값 요청이 들어오든 결국 Command로 변환해서 호출 해야 되니 Validation Check를 Command에서만 하면 된다. 즉, Validation Check의 시점을 하나로 모는 효과를 가진다.
둘째, 필자는 Header로 부터 전달 받은 값을 서비스 로직에서 사용하기 위해 SuvletHttpRequest를 Service에 전달하는 코드를 본적이 있다. 이 코드는 테스트 코드를 작성할때 문제가 된다. 테스트 코드는 외부의 의존성으로 부터 자유로워야 하고 순수해야 하는데 SuvletHttpRequest에 의해 외부 제약이 생긴다. 이러한 문제를 예방하고자 필자는 Command를 만들었다. Command를 통해 외부 의존성으로 부터 자유롭고 순수해질 수 있다. 그렇다면 flat하게 데이터를 Service에 전달하면 되지 않냐는 반문을 받을 수 있다. 그러나 이 방법은 첫번째 의도를 지킬 수 없다.</li>
</ul>
<h3 id="2-여러개의-in-port와-service">2. 여러개의 In Port와 Service</h3>
<ul>
<li>하나의 도메인에서 관심사에 따라 여러개의 서비스가 분리될 수 있다는 점을 강조하기 위해 여러개의 UseCase와 Service를 그렸다.</li>
</ul>
<h4 id="👨🏻💻질문-하나의-도메인에-존재하는-여러-서비스가-서로가-서로를-참조해도-되나요">👨🏻‍💻질문) 하나의 도메인에 존재하는 여러 서비스가 서로가 서로를 참조해도 되나요?</h4>
<ul>
<li>Hexagonal Archiecture에서 Service가 외부와 상호작용 한다면 반드시 Port를 통해 상호작용해야 한다는 중요한 원칙에 위배 되기에 필자는 이 부분에 있어서 참조하면 안된다고 생각한다. 원칙을 위배 하는 행위는 지속가능한 개발을 막는 요소 중 하나다. 우리가 원칙을 배워야 하는 이유이기도 하다. 그럼 무엇이 중요할까? Service를 나눈다면 경계를 분명히 하여 잘 나누자.</li>
</ul>
<h3 id="3-out-port-out-adapter">3. Out Port, Out Adapter</h3>
<ul>
<li>Out Port는 Service가 외부의 의존성(Out Adapter)을 호출하기 위한 접합부다. Out Port의 특징중 하나는 추상화 되어 있다는 점이다. 즉, Service 입장에서는 어떠한 외부(Out Adapter)와 통신하는지 모른다. Persistence일 수 있고, Service일 수 있다.</li>
</ul>
<h4 id="👨🏻💻질문-out-port를-호출할때-flat하게-값을-파라미터로-전달하는-이유는-뭔가요">👨🏻‍💻질문) Out Port를 호출할때 flat하게 값을 파라미터로 전달하는 이유는 뭔가요?</h4>
<ul>
<li>위에서 Command에 대한 얘기를 했다. Command는 분명히 Validation Check라는 효용성이 있다. 있었기에 Command를 만들었다. 그러나 Out Port를 호출할때 값 객체를 만들어 봤을때 큰 효용성이 없다. 없어서 만들지 않았다.</li>
</ul>
<h4 id="👨🏻💻질문-port와-상호작용한-결과를-response로-정의한-이유는-무엇인가요">👨🏻‍💻질문) Port와 상호작용한 결과를 Response로 정의한 이유는 무엇인가요?</h4>
<ul>
<li>Service는 어떠한 외부와 통신하는지 모른다. 그저 추상화된 Port를 통해 외부와 상호작용을 하는것이 전부다. Response를 만든 이유는 만약 특정 의존성의 종속적인 값을 리턴 받는다면 외부의 의존성이 변하는 순간(ex) Spring Data JPA -&gt; Spring Data MongoDB) Service의 비지니스 로직에 영향을 준다. Hexagonal Architecture에서는 Business를 잘 지키고 집중하는 것이 목표다. 즉, Open-Closed Principle을 지켜 비지니스를 지키기 위해 Response를 만들었다.</li>
</ul>
<h2 id="package-구조">Package 구조</h2>
<blockquote>
<p>그렇다면 실제 프로젝트에서는 어떻게 package가 구성되는지 살펴보자. Package 구조는 아래와 같다.
아래와 같은 package 구조를 통해서 위에서 설명한 효용성을 이룰 수 있다.
<img src="https://velog.velcdn.com/images/will_d/post/de443264-5e8b-4a70-92b0-850cc6c49c9e/image.png" alt="">
<img src="https://velog.velcdn.com/images/will_d/post/c04af166-f274-41d3-9fec-bfbd29d5b7bc/image.png" alt="">
<img src="https://velog.velcdn.com/images/will_d/post/bec62c2c-7d98-44a9-bc85-15674eeeee0b/image.png" alt=""></p>
</blockquote>
<h2 id="확장성">확장성</h2>
<blockquote>
<p>각자 도메인에 대해 위의 시나리오를 기반으로 개발하면 여러 육각형들이 생겨날 것이다. 각각의 육각형들은 각각의 Business를 담당하고 필요에 의해 서로 상호작용 할 것이다. 원칙을 준수한 시나리오를 기반으로 각각의 육각형들을 확장해 나갔다. 분명 원칙을 지켜나갔다면 유지보수를 넘어 지속 가능한 소프트웨어가 될것이라 기대한다.
<img src="https://velog.velcdn.com/images/will_d/post/979e009c-b5a0-43b7-a3d0-5ec14b613c0c/image.png" alt=""></p>
</blockquote>
<h2 id="마무리">마무리</h2>
<p>왜 갑자기 MSA 심장부를 설계하다 (MSA Common) 시리즈에서 Hexagonal Architecture를 설명했는지 의아해 하시는분이 있을것 같다. 일관성은 지속가능한 소프트웨어를 구축하는데 가장 중요한 덕목이다. 일관성을 지키기 위해서 우리는 원칙을 지켜야 한다. Architecture는 이러한 원칙을 지키게 한다. Common을 설계하는 이유가 중복된 코드를 줄여 효율성을 높이자는 것도 맞는 말이지만 더 중요한 사실은 팀의 원칙을 설계하는것이 아닐까? 이러한 관점에서 필자는 Architecture가 진정한 Common이라 생각했다. 그래서 이번 시리즈에 반드시 설명해야 하는 부분이라고 생각하여 글을 작성해 봤다.</p>
<p>긴글 읽어 주셔서 감사합니다😀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MSA 심장부를 설계하다: Theme2. API Result]]></title>
            <link>https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-Theme1.-API-Result</link>
            <guid>https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-Theme1.-API-Result</guid>
            <pubDate>Sat, 23 Mar 2024 04:17:24 GMT</pubDate>
            <description><![CDATA[<p>RESTful API를 기반으로 설계된 서버는 요청을 받으면 이 요청에 대한 적절한 응답값을 내려야 한다. 요청 - 응답의 원리는 클라이언트와 서버가 상호작용하고 소통을 하는 중요한 역할을 담당한다. 그러기에 API Result는 프로젝트를 초기에 설계 할때 반드시 짚고 넘어가야 될 중요한 지점이다.</p>
<blockquote>
<p>질문 🧑🏻‍💻) 공통으로 Api Result를 설계 하는것은 타당한가 MSA 환경은 쉽게 말해 서로다른 여러 서버가 존재 할 수 있다는 말이다. 즉 클라이언트 입장에서 서로 다른 여러 서버와 소통해야 한다. 소통을 해야 하는데 응답 값이 각 서버마다 다르다고 한다면 Client는 서로 다른 응답 값에 대해 관리하고 대응해야 한다. 이 말은 관리 포인트가 많아진다는 얘기다. 필자는 이러한 상황을 적합하지 않다고 판단했다. 통합된 응답 값이 관리 포인트를 낮추고 일관되게 만들어 준다. 그럼으로 공통으로 API Result를 설계하는 것은 적합한 행위다.</p>
</blockquote>
<blockquote>
<p>질문 🧑🏻‍💻) 더 나아가 MSA 환경에서 Common을 구현하는 것은 적합한가 독립적으로 개발하고 배포하는 것을 목표로 하는 전통 MSA를 지향하는 관점에서 사실 Common은 적합하지 않을 수 있다. 결합도를 높이기 때문이다. 그럼에도 불구하고 필자는 MSA 환경에서 Common을 만들고 이것을 MSA 심장부라 부른다. 비지니스 로직을 구현하기 위한 보조기능들에 대해 첨예하게 관리가 가능하고, 팀 규칙으로서 일관된 프로그래밍을 가능하게 하기 때문이다. 즉, 최종적으로 Common은 규율과 원칙의 기준이 되고 더 철저한 유지보수를 가능하게 한다. 결합도를 높이는 단점이 있지만 Common을 잘 이해하고 관리한다면 장점이 더 탁월하기에 Common을 구현하는 것은 적합하다 생각한다. MSA에도 정답은 없다. 각 팀의 상황, 각 비지니스 상황을 이해하고 그에 맞는 전략과 기준을 세워 프로젝트를 구축하는게 훌륭한 개발자의 덕목이지 않을까?</p>
</blockquote>
<h3 id="어떻게-내리고-싶은가">어떻게 내리고 싶은가</h3>
<blockquote>
<p>API Success</p>
</blockquote>
<ul>
<li>httpStatusCode: 2xx</li>
<li>httpBody: <pre><code>{
 result: {
     &quot;code&quot;: String,
     &quot;reason&quot;: String,
     &quot;Message&quot;: String
  }, 
  body: { 
      &lt;T&gt;
  }
}</code></pre></li>
</ul>
<blockquote>
<p>API Fail</p>
</blockquote>
<ul>
<li>httpStatusCod : 4xx, 5xx</li>
<li>httpBody: <pre><code>{
  result: {
    &quot;code&quot;: String,
    &quot;reason&quot;: String,
    &quot;frontMessage&quot;: String
  },
  body: { }
}</code></pre></li>
</ul>
<p>필자의 MSA 프로젝트에서 내리고 싶은 API Result는 결론적으로 위와 같다.
구현해보자.</p>
<h3 id="설명">설명</h3>
<blockquote>
<ol>
<li>Result
Api Result의 meta-data의 정보를 담는 class이다. 추가로 필자는 clientUseMessage라는 field를 추가했다. 서버에서 각 상황에 필요한 client use message를 내려주기 위함이다.</li>
<li>ErrorCode
서버 개발을 하다보면 각 도메인, 각 상황에 따른 예외 상황이 존재한다. 이 예외 상황에 대한 정보를 추상화한 interface이다. ErrorCode interface의 추상화 함수는 getter에 대한 함수이고, 실질적으로 이를 구현하는 enum의 field 역할을 한다. ErrorCode는 Result에 예외 상황에 대한 정보를 전달하는 역할을 담당한다. Result.java 파일의 public static ERROR() 함수를 참고하면 된다.</li>
<li>Api
Result의 정보와 Body 정보를 담는 역할을 한다. public static OK(), public static ERROR() 함수를 통해 성공에 대한 Api Result, 예외 상황에 대한 Api Result를 전달 할 수 있도록 설계했다.</li>
<li>ApiException
Exception을 발생할때 ExceptionHandler에게 전달할 정보를 가지는 interface이다. 이 interface의 추상화 함수도 실질적으로 구현체의 getter에 대한 함수이며, 구현체의 field 역할을 한다.</li>
</ol>
</blockquote>
<p>아래의 코드를 천천히 살펴보길 바란다.</p>
<pre><code>@Getter
@NoArgsConstructor
public class Result {
    private static final String SUCCESS_CODE = &quot;lucycato&quot;;
    private static final String SUCCESS_REASON = &quot;success&quot;;
    private String code;
    private String reason;
    private String clientUseMessage;

    public static Result OK() {  
        Result result = new Result();  
        result.code = Result.SUCCESS_CODE;  
        result.reason = Result.SUCCESS_REASON;  
        result.clientUseMessage = &quot;&quot;;  
        return result;  
    }  

    public static Result ERROR(ErrorCode errorCode) {  
        Result result = new Result();  
        result.code = errorCode.getCode();  
        result.reason = errorCode.getReason();  
        result.clientUseMessage = errorCode.getClientUseMessage();  
        return result;  
    }  

    public static Result ERROR(ErrorCode errorCode, String reason) {  
        Result result = new Result();  
        result.code = errorCode.getCode();  
        result.reason = reason;  
        result.clientUseMessage = errorCode.getClientUseMessage();  
        return result;  
    }  
}</code></pre><pre><code>public interface ErrorCode {
    Integer getHttpCode();

    String getCode();  

    String getReason();  

    String getFrontMessage();  
}</code></pre><pre><code>@Getter
@NoArgsConstructor
public class Api {
    private Result result;
    private T body;

    public static &lt;T&gt; Api&lt;T&gt; OK(T body) {  
        Api&lt;T&gt; api = new Api&lt;&gt;();  
        api.result = Result.OK();  
        api.body = body;  
        return api;  
    }  

    public static Api&lt;Object&gt; ERROR(Result result) {  
        Api&lt;Object&gt; api = new Api&lt;&gt;();  
        api.result = result;  
        api.body = new Object();  
        return api;  
    }  

    public static Api&lt;Object&gt; ERROR(ErrorCode errorCode) {  
        Api&lt;Object&gt; api = new Api&lt;&gt;();  
        api.result = Result.ERROR(errorCode);  
        api.body = new Object();  
        return api;  
    }  

    public static Api&lt;Object&gt; ERROR(ErrorCode errorCode, String reason) {  
        Api&lt;Object&gt; api = new Api&lt;&gt;();  
        api.result = Result.ERROR(errorCode, reason);  
        api.body = new Object();  
        return api;  
    }  
}</code></pre><pre><code>public interface ApiException {
    Integer getHttpCode();

    Result getResult();  
}</code></pre><h3 id="사용">사용<img src="https://velog.velcdn.com/images/will_d/post/cd4f3c87-ac16-4950-a893-a1225fc00bec/image.png" alt=""></h3>
<ul>
<li>Success
예외 상황 발생 없이 end-point의 함수가 정상적으로 호출이 되어 위의 코드와 같이 return을 하면, 우리가 말한 위와 같은 Api Result 규격으로 잘 내려갈 것이다.</li>
<li>Fail
위의 코드와 같이 예외를 던진다면 ExceptionHandler에 예외 정보를 전달할 것이다. 즉, 우리가 말한 위와 같은 Api Result 규격으로 사용자에게 잘 내려갈것이다.<ul>
<li>MSA Handle Execption에 대한 내용은 추가 포스팅에서 설명하도록 하겠다.</li>
</ul>
</li>
</ul>
<h3 id="마무리">마무리</h3>
<p>클라이언트와 잘 소통하는 것은 서버의 중요한 덕목이다. 이번 글에서는 우리의 소통 약속을 정하고 어떻게 하면 클라이언트와 잘 소통하는지에 대해 이야기 했다. 다음 시간에는 spring-web-mvc, spirng-webflux 두개의 큰 관심사에 대해 경계를 잘 나누어 어떻게 module로 분리하면 좋을지에 대해서 이야기 하는 시간 가지도록 하겠다.</p>
<h3 id="연구가-진행-되어될-부분">연구가 진행 되어될 부분</h3>
<blockquote>
<p>질문 🧑🏻‍💻) 예외 상황에서는 클라이언트에게 body 값을 내려주지 않는다. 이렇게 설계를 하는것은 올바른가 예외 상황에서 값을 내려준다고 하면 어떠한 상황이 예외상황에서 body를 내려주는것이 적합한 것인가</p>
</blockquote>
<ul>
<li>Api.java 파일의 ERROR 함수를 보면 body를 무조건 Object로 내리는 것을 확인할 수 있다. 즉, 지금 설계한 구조는 예외상황이 발생했을때는 body의 값을 받을 수 없다는 얘기이다. 이는 타당한가. 현재 필자는 이 부분에 대해 분석 / 연구를 진행하고 있다. 연구가 끝다면 다시 글을 쓰도록 하겠다.
감사합니다. 😀</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[MSA 심장부를 설계하다: preview]]></title>
            <link>https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-preview</link>
            <guid>https://velog.io/@will_d/MSA-%EC%8B%AC%EC%9E%A5%EB%B6%80%EB%A5%BC-%EC%84%A4%EA%B3%84%ED%95%98%EB%8B%A4-preview</guid>
            <pubDate>Thu, 21 Mar 2024 10:00:54 GMT</pubDate>
            <description><![CDATA[<p>거인과 목숨을 걸고 싸우는 진격의 거인이라는 만화에서 심장을 바치라는 대사가 있다. 그렇다. 심장은 인간의 생명을 나타내는 가장 상징적인 인체기관이다. Microservices Architecture (MSA) 프로젝트에서도 심장과 같은 역할을 하는 Module이 있다. 그것은 바로 Common이다. Common의 동작이 올바르지 않거나 문제가 발생하면 프로젝트 전체에 영향을 미쳐 프로젝트는 생명력을 잃을 것이다. Common을 설계하는 것은 심장을 바치는 일 만큼 중요한 일이다.
<img src="https://velog.velcdn.com/images/will_d/post/e6636a8b-4f1f-4422-a778-e672dad1d399/image.jpg" alt=""></p>
<h3 id="요구사항-정리">요구사항 정리</h3>
<blockquote>
<ol>
<li>MSA 프로젝트 전반에서 사용할 공통 기능을 구축해주세요.</li>
<li>sync, blocking I/O를 기반으로 한 spring-web-mvc를 기반으로 service를 개발할 수 있도록 해주세요.</li>
<li>async, non-blocking I/O를 기반으로 한 spring-webflux 기반으로 service를 개발할 수 있도록 해주세요.</li>
</ol>
</blockquote>
<p>필자에게 주어진 요구사항을 크게 3가지로 정리하면 위와 같다.</p>
<p>&#39;질문의 질이 답의 질을 결정한다.&#39; 올바른 답으로 나아가기 위해 올바른 질문을 해야 한다는 중요한 교훈이 담긴 말이다. 스스로 질문해보자. 무엇을 설계해야 하는가, 각 프로젝트에서 공통으로 무엇을 대응해야 하는가, 공통으로 비지니스 로직을 구현하기 위한 수단(보조 로직)은 어떤 것들이 있는가, spring-web-mvc, spring-webflux의 경계는 어떻게 나눌것인가</p>
<h3 id="설계">설계</h3>
<blockquote>
<ol>
<li>Hexagonal Architecture </li>
<li>API Result</li>
<li>mvc module, webflux module 경계<ul>
<li>Tomcat vs Netty</li>
<li>CPU Bounds</li>
<li>I/O Bounds<ul>
<li>Spring Web MVC vs Spring WebFlux</li>
</ul>
</li>
</ul>
</li>
<li>MSA JWT 전달 전략 like Fractal<ul>
<li>Local Thread, Reactor Context</li>
<li>MSA Resolver</li>
<li>MSA Filter</li>
</ul>
</li>
<li>MSA Handle Exception</li>
<li>MSA 통신 전략<ul>
<li>CommonHttpClient</li>
<li>CommonWebClient</li>
<li>Kafka</li>
</ul>
</li>
</ol>
</blockquote>
<p>위에서 한 질문을 기반으로 6가지 기준을 산정해서 설계했다. &#39;No Silver Bullet&#39;이라는 말이 있듯 개발에 정답은 없다. 이 설계도 정답은 아닐 것이고, 지속적으로 다듬고 발전시켜 나가야 한다.</p>
<h3 id="마무리">마무리</h3>
<p>개발에 정답은 없지만 각 시점에서 자신의 연구하고 정리한 궁극은 있다. 최근에 정말 많은 고민과 생각을 했다. 어떤 날은 코드를 작성하는 시간보다 생각을 하는 시간이 더 많은 날도 있었다. 각 경계를 머릿속으로 그리며 이렇게 내가 코드를 작성하고 설계 했을 때 과연 팀원들이 납득 하면서 사용할지 일관되게 사용할 수 있는지, 이러한 구조로 코드를 작성하는 것은 타당하고 올바른지 생각의 사냥을 멈추지 않았다. 머릿속의 코드가 수십 번 지워지고 다시 작성되었다. 그렇게 나의 궁극이 탄생 되었다. MSA 심장부를 설계하다 시리즈는 어떻게 MSA 프로젝트의 공통부를 설계해야 하는가에 대한 현재 나의 궁극이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[핵사고날 아키텍처]]></title>
            <link>https://velog.io/@will_d/%ED%95%B5%EC%82%AC%EA%B3%A0%EB%82%A0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98</link>
            <guid>https://velog.io/@will_d/%ED%95%B5%EC%82%AC%EA%B3%A0%EB%82%A0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98</guid>
            <pubDate>Mon, 18 Mar 2024 16:00:20 GMT</pubDate>
            <description><![CDATA[<h3 id="훌륭한-아키텍처는-코드를-보는-좋은-눈을-길러준다">&#39;훌륭한 아키텍처는 코드를 보는 좋은 눈을 길러준다.&#39;</h3>
<p>가끔 이러한 질문을 받을때가 있다. &#39;저는 회사에서 Layered Architecture로 코드를 짜는데 굳이 Hexagonal Architecture을 알아야 할까요?&#39; 나의 답변은 항상 일관되었다. 회사에서 Hexagonal Architecture을 쓰지 않더라도 Hexagonal Architecture의 철학과 효용성을 이해하고 다시 Layered Architecture를 본다면 코드를 보는 눈이 달라질 것입니다. </p>
<p>필자는 원래 iOS 개발자였다. 모바일 개발을 할때 다양한 아키텍처에 대해
학습을 하게 되었는데 그 중 많은 학습 시간을 들였던 MVVM 디자인 패턴을 기반으로한 Clean Architecture는 코드를 보는 눈을 한단계 향상시켜줬다.
규율과 원칙하에 코드를 작성해야 하는 이유들에 대해 배웠고 규율과 원칙하에 내가 작성한 코드가 잘못되었음을 알게되었다. 반성할 수 있게 되니 코드를 보며 생각이 더욱 깊어졌고 코드의 디테일을 보는 눈이 생겼다. 그렇게 하나씩 원칙을 지키고 수정하다보니 일관되어졌고 일관된 코드는 시간이 지난 내가, 다른 누군가 봤을때 큰 흐름을 이야기 해줬다. 그렇게 큰 문맥을 지닌 코드의 힘은 말로 설명할 수 없었다. 미래의 내가 편했고 미래의 누군가가 편했다. 그렇게 나는 아키텍처의 힘에 빠져들었다.</p>
<p>Spring 개발을 처음했을때 Layered Architecture를 직면했다. 의존성 주입을 통한 각 관심사에 따른 Layer의 단일 참조 철학은 MVVM을 기반으로 한 Clean Architecture와 비슷했다. MVVM 기반 Clean Architecture에서는 VM에서 비지니스 로직에 집중했고 Repository 영역은 Persistence 영역과 같이 Local Data, Server Data를 제공했다. Layered 아키텍쳐도 Service에 비지니스 로직을 집중했고 Persistance 영역에서는 데이터를 제공했다. 그러나 모바일의 환경과 서버 환경을 비교했을때 서버가 상대적으로 외부의 의존성에 대해서 더 많은 확장을 요구했다. 이러한 문제로 Service의 비지니스 로직이 점점 희석되어 간다는것을 깨닳았다. 즉, 보조로직이 비지니스 로직을 희석시키고 있었다.</p>
<blockquote>
<h4 id="비지니스-로직은-다양한-외부와의-복잡한-상호작용을-통해-만들어-진다-외부의-api를-요청하여-결과-값을-받고-persistence의-crud는-비지니스-로직을-위한-수단이지-그-자체가-비지니스-로직이-아니다-작은-도메인에서는-그것이-상응할-수-있겠지만-큰-도메인과-여러-시스템이-공존하는-큰-시스템에서는-한계에-부딛힌다">비지니스 로직은 다양한 외부와의 복잡한 상호작용을 통해 만들어 진다. 외부의 API를 요청하여 결과 값을 받고 Persistence의 CRUD는 비지니스 로직을 위한 수단이지 그 자체가 비지니스 로직이 아니다. 작은 도메인에서는 그것이 상응할 수 있겠지만 큰 도메인과 여러 시스템이 공존하는 큰 시스템에서는 한계에 부딛힌다.</h4>
</blockquote>
<p>나는 이러한 문제를 해결하고 싶었다. 나의 바램은 Service Layer를 봤을때 비지니스의 흐름과 원리를 분명하게 파악하고 싶었다. 그렇게 나는 이러한 문제를 해결할 수 있는 방법들에 대해 연구했고 그 중 Hexagonal Architecture는 이 문제를 해결하기 위한 강력한 수단이였다.</p>
<blockquote>
<h3 id="hexagonal-architecture-설명">Hexagonal Architecture 설명</h3>
</blockquote>
<ul>
<li>Domain / Business<ul>
<li>Hexagonal Architecture의 중심이다. Hexagonal Architecture의 원리와 원칙은 Domain / Business 영역을 중심으로 이뤄진다. 이 영역에서 작성되는 코드는 서비스의 비지니스와 관련된 원리와 흐름이 작성된다. 그 외 부가적인 것은 작성하지 않는다. Hexagonal Architecture에서 가장 지키고 싶고 소중히 여기는 부분이다. 우리도 소중히 여기자.</li>
</ul>
</li>
<li>Adapter<ul>
<li>외부와 상호작용을 하는 실질적 구현체이다. Endpoint Trigger, CRUD, 외부 API 호출 등... 비지니스 로직을 수행하기 위한 부가적인 로직은 모두 Adapter에서 이루어진다.</li>
</ul>
</li>
<li>Port<ul>
<li>비지니스 로직은 외부의 의존성에 의해 변질되어서 안된다. 그 변질을 막기 위해 Hexagonal Architecture는 Service와의 상화 작용은 반드시 Port를 통해서 상호작용 하도록 원칙을 제시하고 있다. 외부로 부터 유연한 변화와 대응을 가능하게 하는 중요한 연결 접합부다.</li>
</ul>
</li>
<li>In / Out<ul>
<li>Domain / Business 영역을 중심으로 외부를 호출하는 것을 Out이라한다. 또한 Domain / Business 영역을 중심으로 안으로 들어오는 것을 in이라고 한다. 이렇게 탄생한 개념으로 in-adapter, in-port, out-port, out-adapter가 있다. 이렇게 만들어진 in-out의 개념을 통해 더욱 분명한 경계를 만들 수 있다. 이렇게 만들어진 분명한 경계는 체계적인 확장 가능함을 야기하고 코드를 작성하는 분명한 원칙을 제시한다.</li>
</ul>
</li>
</ul>
<p>위의 설명에서 알다 싶이 Hexagonal Architecture는 엄격한 규율과 규칙이 있다. 이러한 규율과 원칙은 반성하게 하고 일관되게 할것이다. 종합적으로 Hexagonal Architecture는 아래와 같은 특징을 지닌다.</p>
<blockquote>
<h3 id="hexagonal-architecture-특징">Hexagonal Architecture 특징</h3>
</blockquote>
<ul>
<li>확장성<ul>
<li>Port를 통해 외부의 변화에 대해 Domain / Business가 변질되지 않는다 했다. 또한 In-Out의 개념을 통해 명확한 기준으로 체계적으로 확장할수 있다고 했다. Hexagonal Architecture는 확장성에 강하다.</li>
<li><ul>
<li>이해를 돕기 위해 예를 하나 들겠다. NotificationPort라는 out port가 있다. 이 out port는 Service의 Field이다. 추후 이 Port를 구현하는 구현체가 Spring Container에 의해 이 변수에 의존성 주입이 될것이다. 그리고 현재 이 Port를 구현하는 Out Adapter는 NotificationPersistenceAdapter이다. 즉 위에서 말한대로 이 PersistenceAdapter가 의존성 주입이 되어 비지니스 로직을 구성할것이다. 그런데 모종의 이유로 Notification이 서버로 분리됬다고 하자. 그리고 이 외부와의 상호작용을 하는 out Adapter를 NotificationServiceAdapter라 하자 이 친구는 Port를 구현한다. 아까와 똑같이 비지니스 로직은 NotificationServiceAdapter를 통해 로직이 구성이 된다. 그러나 Service 입장에서는 변하지 않는다... 즉 외부의 변화로 부터 안전하다. 왜 내가 이름을 NotificationPort라고 했는지 이해가 되는가? 이해가 되면 좋겠다 ㅎㅎ</li>
</ul>
</li>
</ul>
</li>
<li>유지보수성<ul>
<li>Hexagonal은 Domain / Business, Adapter, Port, In-Out이라는 복합적 개념을 통해 명확한 원칙을 제시한다고 했다. 명확한 원칙은 명확한 방향성을 야기한다. 또한 일관성을 야기한다. 명확한 흐름과 일관성은 시간이 흐르고 사람이 바뀌어도 시스템을 유지할 수 있는 유지보수성을 야기한다.</li>
</ul>
</li>
<li>테스트 용이성<ul>
<li>Domain / Business를 지키는것이 Hexagonal Architecture의 중요한 원칙이라 했다. 또한 Domain / Business는 외부의 의존성으로 부터 자유롭다. 그러기에 테스트가 용이해진다. 테스트 용이성 외적으로 한가지 더 말하고 싶은 부분은 철저한 원리와 원칙을 통해서 명확히 경계를 나눈것이 HexagonalArchitecture이다. 즉 이렇게 나뉘어진 명확한 경계는 철저한 테스트를 야기한다.</li>
</ul>
</li>
</ul>
<h3 id="마무리">마무리</h3>
<p>&#39;훌륭한 아키텍처는 코드를 보는 좋은 눈을 길러준다.&#39; Hexagonal Architecture는 필자의 경험과 더불어 이 말의 힘을 실어준다.
&#39;규율이 곧 자유다.&#39;라는 훌륭한 말이 있듯이 &#39;훌륭한 아키텍처를 지키는 것이 곧 자유다.&#39; </p>
<p>최근에 MSA 프로젝트를 진행하고 있는데 다양한 시스템이 확장되고 상호작용 하는 MSA 프로젝트에서 더욱 안성맞춤이다. 
다음 시간에는 실제로 Hexagonal Architecture를 통해 어떻게 코드를 작성하면 좋을지 같이 논의해보겠다.
감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kafka 서버를 운용하는 전략 1부]]></title>
            <link>https://velog.io/@will_d/Kafka-%EC%84%9C%EB%B2%84%EB%A5%BC-%EC%9A%B4%EC%9A%A9%ED%95%98%EB%8A%94-%EC%A0%84%EB%9E%B5-1%EB%B6%80</link>
            <guid>https://velog.io/@will_d/Kafka-%EC%84%9C%EB%B2%84%EB%A5%BC-%EC%9A%B4%EC%9A%A9%ED%95%98%EB%8A%94-%EC%A0%84%EB%9E%B5-1%EB%B6%80</guid>
            <pubDate>Thu, 14 Mar 2024 14:08:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>kafka는 대량의 데이터를 높은 처리량, 낮은 지연, 안정적 운용을 위해
탄생한 시스템입니다.</p>
</blockquote>
<blockquote>
<p>Kafka란 무엇일까요? 
우리는 가끔 대기업에 관련된 기사를 보면 하청업체에 일을 할당 했다는 기사를 볼 수 있습니다. 대기업은 하청업체에 일을 할당 함으로서 자신들이 집중할 수 있는 일에 더욱 집중 할 수 있게 되고 이로 인해 효율성을 확보 할 수 있습니다. 개발에도 이렇게 역할을 분리하여 일을 할당 시킴으로 효율성을 높이면 굉장히 좋지 않을까요? 이러한 일을 가능하게 만드는 시스템이 Kafka 입니다.
즉 Kafka를 한마디로 표현하면 &#39;하청업체에 일을 할당하는 중간 관리자&#39;라 할 수 있겠습니다.</p>
<blockquote>
<p>질문 🧑‍💻) WebFlux도 비동기를 통해 효율성을 향상시키고 Kafka도 비동기를 통해 효율성을 향상시킨다 하는데 무슨 차이가 있나요?</p>
</blockquote>
</blockquote>
<ul>
<li>같이 일하는 동료분이 이러한 질문을 한 적이 있습니다. Webflux는 하나의 회사에서 여러 직원들을 고용해서 효율성을 높이는 것이고 kafka는 하청업체에 일을 할당함으로서 효율성을 높이는 것입니다. 즉, 내부 인원 충원을 통해 효율성을 높이냐, 회사와 회사간의 협업으로 효율성을 높이냐의 차이가 있겠습니다.</li>
</ul>
<p>이번 글에서는 Kafka를 사용한다면 어떻게 확장하고 성능을 개선할지, 비동기 통신으로 인한 데이터 정합성 문제를 해결하는지에 대한 사전 준비글을 작성해 보겠습니다.</p>
<blockquote>
<ul>
<li>참고
1부: 사전 지식과 청사진 공유
2부: 동영상을 통한 실전 운용 전략 설명</li>
</ul>
</blockquote>
<h3 id="kafka-사전지식">kafka 사전지식</h3>
<ol>
<li>Topic</li>
</ol>
<ul>
<li>Kafka 서버는 여러 Topic을 관리한다. 이 토픽은 Kafka 서버에 값을 보내는 기준이 된다. 값을 보낼때는 Key, Value로 값을 보낸다.</li>
</ul>
<ol start="2">
<li>Producer</li>
</ol>
<ul>
<li>Topic을 기준으로 값을 보내는 주체이다. Producer가 Kafka Server에 값을 보낼때 어떠한 Topic에 어떠한 key, value값을 전달하지 값을 결정해서 보내면 된다. </li>
</ul>
<ol start="3">
<li>Consumer</li>
</ol>
<ul>
<li>Kafka Sever에서 관리하는 Topic을 구독하고 그 값을 전달 받는 주체이다. Consumer 또한 Producer와 동일하게 Topic을 기준으로 Key, Value 값을 받는다.</li>
</ul>
<ol start="4">
<li>Consumer Group</li>
</ol>
<ul>
<li>Kafka 서버의 Topic은 여러 Partition(데이터 파이프라인)이 관리가 된다. 여러 Consumer들은 하나의 Group으로 묶을 수 있는데 이렇게 묶인 Cosumer들은 하나의 Topic을 공유한다. 공유 한다는 말은 하나의 토픽의 여러 Partition들의 값을 Group에 묶인 Consumer들이 병렬적으로 값을 처리한다는 말이다. 이것은 kafka Server를 사용하여 성능을 높이는 중요한 척도이다.</li>
</ul>
<ol start="5">
<li>Kafka Cluster</li>
</ol>
<ul>
<li>여러 kafka Server의 집단을 kafka Cluster라 부른다. 이렇게 여러 kafka Server를 운용하면 Load Balancing을 통해 데이터 처리의 성능을 높일 수 있다. 이 또한 Kafka 서버를 운용하면서 성능과 효율성을 높이는 중요한 척도이다.</li>
</ul>
<ol start="6">
<li>kafka Reflication</li>
</ol>
<ul>
<li>Topic을 생성할때 reflication 설정을 통해 여러 복제본을 운용을 할 수 있다. 예를 들어 reflication 설정을 3으로 하면 Topic의 각 Partition들의 복제본이 3개가 생성 되고 이중 한개의 파티션은 Leader Partition이 된다. 나머지 2개의 파티션은 Flow Partition이라 한다. Flow Partition은 지속적으로 Leader 데이터를 복제하여 최신 상태를 유지한다. 만약 Leader에 장애가 발생하여 운용이 불가한 상태가 된다면 내부적으로 복제본 중 리더를 선정하여 장애에 대응한다. 이는 kafka의 안정적 운용을 위한 중요한 척도이다.<h3 id="kafka-효율성을-높이는-방법">kafka 효율성을 높이는 방법</h3>
</li>
</ul>
<ol>
<li><p>Topic의 Partition의 개수를 늘립니다. 그리고 하나의 Group을 공유하는 여러 Consumer들이 이 Topic을 구독합니다.</p>
</li>
<li><p>물리적으로 Kafka 브로커 서버 여러개를 구축하여 Load Balancing을 통해 효율성을 높입니다.</p>
</li>
</ol>
<h3 id="비동기-통신에서-정합성을-맞추는-운용-전략">비동기 통신에서 정합성을 맞추는 운용 전략</h3>
<ol>
<li><p>kafka 서버를 통한 비동기 통신 히스토리를 추적하는 하나의 서버를 준비합니다. (async-task-consumer-server)</p>
</li>
<li><p>Kafka Server에 Topic은 2개만 생성합니다.</p>
<ul>
<li>task.topic</li>
<li>task.result.topic</li>
</ul>
</li>
<li><p>비동기 통신이 필요한 각 서버는 AsyncTaskProducer를 통해 값을 전달
합니다. 값을 전달하는 규격은 아래와 같습니다.</p>
<pre><code>key: {
 &quot;transactionUUID&quot;: &quot;&quot;,
 &quot;aysncTaskCategory&quot;: Enum
},
body: {
 &lt;T: Object&gt;
}</code></pre></li>
<li><p>async-task-consumer-server에서는 task.topic을 구독하는 AsyncTaskConsumer를 통해 이 값을 전달 받습니다. 전달 받는 값은
async_task_history라는 DB에 업데이트 하여 history를 관리합니다.</p>
</li>
</ol>
<ul>
<li>async_task_history의 DB는 Create만 이루어 집니다.</li>
<li>이렇게 쌓인 데이터는 추후 transactionUUID를 통해서 정합성을 맞추는 중요한 데이터가 됩니다.</li>
</ul>
<ol start="5">
<li><p>history를 DB에 저장하면 AsyncTaskResultProducer를 통해
위와 같은 값 그대로 task.result.topic에 값을 전달합니다.</p>
</li>
<li><p>각 서버는 각자의 관심사에 맞게 Consumer를 구축하고 task.result.topic을 구독하여 값을 전달받습니다. 전달 받은 값을 통해 특정 비지니스 로직을 수행하여 비동기 통신을 완료합니다.</p>
</li>
<li><p>통신의 문제가 생겨 정합성 문제가 생긴다면 async-task-consumer-server에 저장된 고유값(UUID)을 통해 정합성을 맞추는 시도를 합니다.</p>
</li>
</ol>
<h3 id="마무리">마무리</h3>
<p>다음 시간에는 영상 촬영을 통해 실제 프로젝트를 구축해보면서 어떻게 성능을 높이고 정합성을 맞추는지에 대해 설명 하도록 하겠습니다. 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[더 나은 하이브리드앱 그리고 그 지점 [2부]]]></title>
            <link>https://velog.io/@will_d/%EB%8D%94-%EB%82%98%EC%9D%80-%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C%EC%95%B1-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EA%B7%B8-%EC%A7%80%EC%A0%90-2%EB%B6%80</link>
            <guid>https://velog.io/@will_d/%EB%8D%94-%EB%82%98%EC%9D%80-%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C%EC%95%B1-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EA%B7%B8-%EC%A7%80%EC%A0%90-2%EB%B6%80</guid>
            <pubDate>Thu, 27 Apr 2023 01:09:19 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@will_d/%EB%8D%94-%EB%82%98%EC%9D%80-%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C%EC%95%B1-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EA%B7%B8-%EC%A7%80%EC%A0%90-1%EB%B6%80">더 나은 하이브리드앱 그리고 그 지점 [1부]</a>에서는 더 나은 하이브리드 앱을 만들기 위한 중요한 지점들에 대해서 살펴보는 시간을 가졌다. 이번 글에서는 그렇다면 이러한 생각들을 어떻게 코드로 옮겼는지 이 과정에 대해서 설명하도록 하겠다.</p>
<h3 id="프로젝트에서-사용된-라이브러리">프로젝트에서 사용된 라이브러리</h3>
<ol>
<li>RxSwift</li>
<li>RxCocoa</li>
<li>Snapkit</li>
<li>Then</li>
<li>lottie-ios</li>
<li>Toast-Swfit</li>
<li>PanModal</li>
<li>RxGesture</li>
<li>UIColor_Hex_Swift</li>
</ol>
<h3 id="참고할-블로그">참고할 블로그</h3>
<ul>
<li><a href="https://velog.io/@will_d/UX%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4-%EA%B2%B0%EC%A0%95-%ED%95%98%EB%8A%94%EA%B0%80">UX는 무엇이 결정 하는가 [1부]</a></li>
<li><a href="https://velog.io/@will_d/UX%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4-%EA%B2%B0%EC%A0%95-%ED%95%98%EB%8A%94%EA%B0%80-2%EB%B6%80">UX는 무엇이 결정 하는가 [2부]</a></li>
</ul>
<h3 id="목차">목차</h3>
<ol>
<li>데이터 통신 및 소통</li>
<li>제공</li>
<li>URL 관리</li>
<li>설정</li>
</ol>
<h3 id="데이터-통신-및-소통">데이터 통신 및 소통</h3>
<blockquote>
<ol>
<li>Injection
<img src="https://velog.velcdn.com/images/will_d/post/f050a097-722e-4391-a526-3bfe8155a8b1/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/f265386a-1d48-4f2a-872c-f90067b844a7/image.png" alt=""></li>
</ol>
</blockquote>
<ul>
<li>설명<ul>
<li>WKUserScript를 통해서 script와 injectionTime을 정해준다 그리고 webViewConfigure에 등록을 해줬다. 주입이 된 javascript 코드는 웹에서 전역적으로 사용이 가능하다.</li>
</ul>
</li>
</ul>
<blockquote>
<ol start="2">
<li>Get 방식
<img src="https://velog.velcdn.com/images/will_d/post/02cd5dc2-ffdd-49c4-8e0c-1b7b809fd378/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/d9f24755-c46d-4031-a13d-42681ecfa66d/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/6fd1a53f-a49f-45f6-addc-fae8ed37f6e8/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/5b9cea54-31de-49bf-8906-b466843252e2/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/dbbf8494-765a-4121-81b0-9f58a1c8fd83/image.png" alt=""></li>
</ol>
</blockquote>
<ul>
<li>설명 <ul>
<li>Person 객체를 통해서 SwiftUrl 객체를 생성한다. 이 객체는 Person의 정보를 통해 resultUrl을 완성시킨다.
CommonWebViewController는 파라미터로 CommonUrlProtocol을 전달 받는데 SwiftUrl은 CommonUrlProtocol을 상속받은 struct 이므로 SwiftUrl을 전달한다.[*** 의존성 주입도 구현해 봤다.] 전달받은 객체를 통해 getUrl(resultUrl을 URL?로 리턴 시켜주는 함수) 함수를 호출하여 load한다.</li>
<li>** 여기서 중요한 점은 3번째 사진과 같이 Person 데이터를 통해서 Get방식으로 데이터를 WebView에 전달했다는 것이다. 이런식으로 Get방식으로 데이터 전달이 가능하다. [*** Web으로 구현된 디테일 페이지를 호출 할때 유용하다.]</li>
</ul>
</li>
</ul>
<blockquote>
<ol start="3">
<li>Bridge
<img src="https://velog.velcdn.com/images/will_d/post/8f8b4034-b6f3-4aed-a43c-255a2684cd10/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/eee39c17-db58-4a2d-beff-68f460d7c7d0/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/67b891ee-c1b5-4467-9afd-6106996bccab/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/a6777b71-e414-4e75-9d78-5dfe19c8a07b/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/6a39310a-d0e5-4f88-b968-61a5c3304ec8/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/6037e248-85c5-42b4-8be6-318e33b51c0f/image.jpg" alt=""><img src="https://velog.velcdn.com/images/will_d/post/03e9c63a-e307-4c8a-9637-68b04678395f/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/a1865c41-a545-41a2-93d4-94f594badac8/image.png" alt=""></li>
</ol>
</blockquote>
<ul>
<li>설명<ul>
<li>CommonWebView를 생성할때 bridgeName을 전달하여 웹뷰와 네이트브가 상호작용이 가능하도록 configuration.userContentController.add(_ scriptMessageHandler: WKScriptMessageHandler, name: String)이 함수에 연결을 해준다.
연결한 이름 하나를 통해서만 웹과 네이티브는 통신을 한다. 웹에서 네이티브에 브릿지 요청에 대한 시그널을 주면 func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) 함수가 호출이 되고 이 함수에서는 bridgeDelegate들에게 일괄적으로 functionName과 jsonData를 전달한다 전달받은 bridgeDelegate는 함수이름으로 분기를 하여 JsonData를 파싱하여 작업을 수행한다.</li>
<li>** 이러한 원리를 착안했을때 마지막 두번째 사진과 같이 요청을 받으면 네이티브의 데이터를 브릿지로 넘겨준다. 또한 마지막 사진과 같이 소통도 가능하게 구현한다.</li>
</ul>
</li>
</ul>
<h3 id="제공">제공</h3>
<blockquote>
<p>제공을 하는것을 설명하는것은 Native View를 제공하는 것을 중점으로 설명을 하겠다.</p>
</blockquote>
<blockquote>
<ul>
<li>Bridge를 통한 네이티브 View 제공 및 동작
<img src="https://velog.velcdn.com/images/will_d/post/ab0145a7-49cf-4861-b07b-e22edd8715d4/image.png" alt="">
)
<img src="https://velog.velcdn.com/images/will_d/post/efdac1ef-510b-4628-aefe-a8a9ebd1bb0b/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/f0819056-144f-449f-9d1d-a4089d0cc70b/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/0e4c8041-d2e3-435c-9d8b-2919c2eda0df/image.mov" alt=""></li>
</ul>
</blockquote>
<ul>
<li>설명<ul>
<li>나는 이전 글 <a href="https://velog.io/@will_d/UX%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4-%EA%B2%B0%EC%A0%95-%ED%95%98%EB%8A%94%EA%B0%80">UX는 무엇이 결정 하는가</a>에서 BottomModal, LoadingView에 대해서 정리를 했었다. 나는 이 부분에서 데이터만 잘 맞춰주면 충분히 웹에서도 사용이 가능하다는 발상을 했다. 그리고 이것은 하이브리드 앱에서 강력한 도구가 될것을 직감했다. 원리는 간단하다. 웹에서 bridge를 통해 BottomModal을 사용하기 위한 데이터를 넘겨준다. 자료구조는 첫번째 사진과 같다.(사진에서 보는것에서 알다시피 actions의 갯수에 따라서 선택할 리스트는 동적으로 적용된다.) 이러한 데이터를 넘겨받으면 네이티브에서는 데이터를 파싱하여 BottomModal을 생성 및 표현한다. 웹에서는 특정 기능 동작에 대한 actionId값을 넘겨주는데 네이티브에서는 특정 버튼에 대한 동작을 수행하면 다시 이 actionId를 웹에 전달한다.<pre><code>self.webView.evaluateJavaScript(&quot;doAction(&#39;\(action.actionId ?? &quot;&quot;)&#39;)&quot;)</code></pre>이 actionId를 전달받은 앱은 정의된 기능을 수행한다. 이런식으로 다른 Modal, Toast, LoadingView 등의 컨트롤도 수행이 가능하다. 웹에서 네이티브의 View를 사용하므로서 표현에 강한 강점을 가질 수 있고 작업 효율에서도 웹에서 이러한 Component를 만들 필요 없이 네이트브에서만 준비하면 되니 효율적이라 할 수 있다.(단, 웹이 단독으로 존재하지 않는다는 가정에서)</li>
</ul>
</li>
</ul>
<blockquote>
<ul>
<li>WebView에 진입할때 LoadingView
<img src="https://velog.velcdn.com/images/will_d/post/981c7302-8bd3-4179-8895-43ee2dc68617/image.mov" alt=""><img src="https://velog.velcdn.com/images/will_d/post/91a549fd-4dca-4326-b726-5fcb8ada21e2/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/49ccea23-f356-4a3e-9209-4d0b221630cd/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/35137d20-7675-4c19-9d12-82d1214d5a01/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/22302f82-cd00-44b7-b0fb-1a2ed24b9e9d/image.png" alt=""></li>
</ul>
</blockquote>
<ul>
<li>설명<ul>
<li>WebView를 사용한다고 진입을 할때 Web에서 로딩을 하는 시간을 고려해야 한다 그럼으로 관장자인 Native에서는 Loading View를 띄어줄 수 있어야 한다. WebViewController에서는 웹이 로딩되는 시점에 생성 및 show()를 하고 로딩이 완료되면 hide 및 제거를 수행한다. 이렇게 해서 Web이 로딩을 할때 빈화면을 처리해서 사용자 경험을 향상시킨다. 네이티브는 이러한 액션에서 제공자의 역할도 하는것은 타당하다.</li>
</ul>
</li>
</ul>
<h3 id="마무리">마무리</h3>
<p>좋은 앱을 만들고 싶다. 그리고 내가 지금 만드는 앱은 하이브리드 앱이다. 앱 전반에 걸쳐서 WebView의 역할은 중요한 역할을 한다. 그렇다면 좋은 앱을 만들기 위해서는 더 나은 하이브리드 앱을 만들기 위한 고민을 해야 했다. 웹 개발자 분과 더 좋은 하이브리드 앱은 무엇일까에 대해서 많은 이야기를 나눴다. 우리의 결론은 Bridge에 있다고 답을 내렸고 더 나아가 Native가 관장자로서 하는 역할의 중요성에 대해 이야기 했다. 그리고 생각을 정리하고 코드를 구현했다. 이번 더 나은 하이브리드 앱 그리고 그 지점은 본질적으로 좋은 앱을 만들기 위해서 무엇에 대해 고민해야 되는지에 대한 글이다. 중요하고 소중한 경험이다. 나는 앞으로도 좋은 앱을 만들것이다. 그러기에 노력을 게을리 할 이유가 없다.
<a href="https://github.com/learnGrowD/OrganizeWebView">전체코드</a></p>
<p>긴글 읽어 주셔서 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[더 나은 하이브리드앱 그리고 그 지점 [1부]]]></title>
            <link>https://velog.io/@will_d/%EB%8D%94-%EB%82%98%EC%9D%80-%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C%EC%95%B1-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EA%B7%B8-%EC%A7%80%EC%A0%90-1%EB%B6%80</link>
            <guid>https://velog.io/@will_d/%EB%8D%94-%EB%82%98%EC%9D%80-%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C%EC%95%B1-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EA%B7%B8-%EC%A7%80%EC%A0%90-1%EB%B6%80</guid>
            <pubDate>Wed, 26 Apr 2023 23:36:57 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>회사의 프로젝트는 WebView를 연결하는 하이브리드 앱이다.
좋은 하이브리드 앱을 만들기 위해서는 어떠한 덕목이 필요할까?
이번 글에서는 좋은 하이브리드 앱을 만들기 위한 나의 고민들에 대한 정리를 작성하겠다.</p>
</blockquote>
<h3 id="하이브리드-앱을-만든다고-하면-고민을-해야-되는-주요-지점">하이브리드 앱을 만든다고 하면 고민을 해야 되는 주요 지점</h3>
<ol>
<li>데이터 통신 및 소통</li>
<li>제공</li>
<li>URL 관리</li>
<li>설정</li>
</ol>
<blockquote>
<ul>
<li>하이브리드 앱을 만들기 위해서 첫번째로 생각해야 되는 지점은 데이터 통신 및 소통을 어떻게 할 것인지 고민해야 된다. 나는 WebView와 소통하는 방식에 대해 아래와 같이 3가지로 분류 하였다. 각 상황과 데이터의 성격에 따라서 알맞게 사용하는 것이 중요하다.</li>
</ul>
</blockquote>
<ol>
<li>Injection<ul>
<li>WebView에 전역적인 데이터를 넘겨주는데 용이한 방법 (전역적 성격)</li>
</ul>
</li>
<li>Get 방식<ul>
<li>특정 페이지에만 데이터를 넘겨주는 용이한 방법 (지역적 성격)</li>
</ul>
</li>
<li>Bridge<ul>
<li>Runtime 상황에서 데이터를 넘겨주는 유용한 방법</li>
<li>Runtime 상황에서  Native와 WebView 사이의 소통을 하게 해주는 근간<ul>
<li>ex) 뒤로가기</li>
</ul>
</li>
</ul>
</li>
</ol>
<blockquote>
<ul>
<li>두번째는 잘 제공하는것이다. 왜 제공해야 되는가? 그리고 무엇을 제공해야 하는가? 모바일 환경에서 웹이 단독으로 존재하는 상황과 Native와 소통을 할 수 있는 웹은 분명한 차이가 있다. 네이티브가 존재하는 웹은 네이티브를 통해 더욱 많은 일을 할 수 있다. 즉 네이티브가 관장자의 역할 로서 많은것을 제공해 주기 때문에 가능 한 일이다. 이러한 관점에서 봤을때 네이트브에서 웹뷰를 사용한다고 하면 많은 기능을 제공해야 되는것은 분명하다. 제공을 해야 되는 지점은 아래와 같다.</li>
</ul>
</blockquote>
<ol>
<li>Native 고유 기능 (ex) GPS, 걸음수, 블루투스 등)<ul>
<li>웹에서는 사용 할 수 없는 네이티브만이 구현 할 수 있는 고유의 기능들이 있다. 웹에서 할 수 있는것 없는것을 잘 구분해서 네이티브에서 고유의 기능을 잘 제공하는것은 중요한 지점이다. </li>
</ul>
</li>
<li>App Life Cycle에서 제공이 가능한 데이터<ul>
<li>앱 Life Cycle 전반에서 네이티브가 가지고 있는 값들이 있다. 예를 들어서 초기에 Splash 화면에서 받아서 App Life Cycle 전반에서 가지고 있어야 되는 정보가 있다고 하자 필요에 의해서 Native는 이 데이터도 웹에 제공 할 수 있어야 한다.</li>
</ul>
</li>
<li>Native View<ul>
<li>필요에 의해서 네이티브는의 View도 제공한다 표현에서 경험적으로 판단할때 Native의 View가 더욱 화면 표현에 있어서 우의에 있다. (그리고 작업 효율성 재사용성 측면에서도 이 방법이 우의에 있다.) 예를 들어서 Native의 LoadingView, Modal, BottomModal 등에 대해서 제공이 가능하다. 또한 Native 관장자의 역할이다. 즉 Web이 로드하는 중 빈 화면이 보일때 특정 작업을 처리(LoadingView)해야 되는 역할은 네이티브가 하는것이 적합하다.</li>
</ul>
</li>
</ol>
<blockquote>
<ul>
<li>세번째는 URL을 잘 관리해야 한다. 우리 앱에서는 여러 도메인이 존재하는데 이러한 여러 카테고리의 도메인을 잘 관리하는것, 그리고 이 도메들 path를 잘 관리 해야 했다. 그리고 다른 프로젝트도 이러한 URL을 관리하는 것은 중요한 지점이라 생각한다.</li>
</ul>
</blockquote>
<ul>
<li>ex)
<img src="https://velog.velcdn.com/images/will_d/post/db6523f3-fcda-4de0-ac55-5746a7e36613/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/46b38855-113a-4938-80a2-5208e9e23c4b/image.png" alt="">
<img src="https://velog.velcdn.com/images/will_d/post/375a9915-16fb-485f-b514-7076cb68fe3e/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/7575bfaa-0d33-4410-a3d9-64e0a3429b8b/image.png" alt=""></li>
</ul>
<blockquote>
<ul>
<li>네번째 설정, WKWebView를 사용하면 WkWebView에 다양한 설정을 할 수 있다는 사실을 안다. 이 설정을 통해 우리 앱에 최적화된 WebView를 잘 붙일 수 있어야 한다. 다양한 설정들이 존재한다 상황에 걸맞게 설정을 잘 찾아 최적화를 시켜주는게 핵심인것 같다. </li>
</ul>
</blockquote>
<ul>
<li>ex)
<img src="https://velog.velcdn.com/images/will_d/post/89504afc-107a-4d5c-a05e-6fb321ab63ff/image.png" alt=""></li>
</ul>
<h3 id="마무리">마무리</h3>
<p>이번 글에서는 더 나은 하이브리드 앱을 만들이 위한 주요 지점들에 대해서 살펴봤다. 다음 글에서는 데이터 통신 및 소통과 제공에 대해서 어떻게 코드로 구현했는지에 대한 설명을 이어가도록 하겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[UX는 무엇이 결정 하는가 [2부]]]></title>
            <link>https://velog.io/@will_d/UX%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4-%EA%B2%B0%EC%A0%95-%ED%95%98%EB%8A%94%EA%B0%80-2%EB%B6%80</link>
            <guid>https://velog.io/@will_d/UX%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4-%EA%B2%B0%EC%A0%95-%ED%95%98%EB%8A%94%EA%B0%80-2%EB%B6%80</guid>
            <pubDate>Wed, 26 Apr 2023 00:21:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://velog.io/@will_d/UX%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4-%EA%B2%B0%EC%A0%95-%ED%95%98%EB%8A%94%EA%B0%80">UX는 무엇이 결정 하는가 [1부]</a>에서는 왜 일관된 UX가 중요한지에 대해서 이야기를 했다. 이번 글에서는 실재 디자인적 일관성을 개발로 어떻게 구현했는지 설명하겠다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/will_d/post/207f40f0-47c8-4511-90bd-6c0dc7df7309/image.mov" alt=""></p>
<h3 id="이-프로젝트에-사용된-라이브러리">이 프로젝트에 사용된 라이브러리</h3>
<ul>
<li>RxSwift</li>
<li>RxCocoa</li>
<li>RxGesture</li>
<li>Kingfisher</li>
<li>Snapkit</li>
<li>Then</li>
<li>PanModal</li>
<li>Toast-Swift</li>
<li>Lottie-ios</li>
</ul>
<h3 id="글을-읽기전-참고-블로그">글을 읽기전 참고 블로그</h3>
<p><a href="https://velog.io/@will_d/iOS-%ED%99%94%EB%A9%B4-%ED%8C%90%EB%8B%A8">iOS 화면 판단</a></p>
<h3 id="modal">Modal</h3>
<ul>
<li><p>사용
<img src="https://velog.velcdn.com/images/will_d/post/a70f8ccf-fb85-4a0c-9d51-1a4d75f6aa6f/image.png" alt="">
<img src="https://velog.velcdn.com/images/will_d/post/3b4afcd2-2033-4fe0-a600-df5a293ea479/image.png" alt="">
<img src="https://velog.velcdn.com/images/will_d/post/74d602d2-d2c8-4fe3-aa0b-8bf4518ec998/image.png" alt=""></p>
</li>
<li><p>구현부</p>
<pre><code>class CommonModal : BaseViewController {
  let disposeBag = DisposeBag()

  let blurView = UIView().then {
      $0.backgroundColor = .black.withAlphaComponent(0.3)
  }

  let parentView = UIView().then {
      $0.backgroundColor = .clear
      $0.layer.cornerRadius = 16
  }

  let titleLabel = UILabel().then {
      $0.textAlignment = .center
      $0.numberOfLines = 1
  }

  let imageView = UIImageView()

  let messageLabel = UILabel().then {
      $0.textAlignment = .center
      $0.numberOfLines = 0
  }

  let underLineView = UIView().then {
      $0.heightAnchor.constraint(equalToConstant: 1).isActive = true
  }

  let nagativeButton = UILabel().then {
      $0.textAlignment = .center
  }

  var nagativeDelegate : (CommonModal) -&gt; Void = { _ in }

  let positiveButton = UILabel().then {
      $0.textAlignment = .center
  }

  var positiveDelegate : (CommonModal) -&gt; Void = { _ in }

</code></pre></li>
</ul>
<pre><code>lazy var buttonStackView = UIStackView(arrangedSubviews: [nagativeButton, positiveButton]).then {
    $0.heightAnchor.constraint(equalToConstant: 44).isActive = true
    $0.distribution = .fillEqually
    $0.axis = .horizontal
}

let pillarLineView = UIView().then {
    $0.widthAnchor.constraint(equalToConstant: 1).isActive = true
}

override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    layout()
    bind()
}

required init?(coder: NSCoder) {
    fatalError(&quot;init(coder:) has not been implemented&quot;)
}


/*
 화연에 표시
 */
func show() {
    /*
     현재 화면의 최상단에 보이는 ViewController가 자기 자신인지 판단.
     */
    if topMostViewController !== self {
        /*
         최상단 ViewController를 통해서 Modal 보여주기
         */
        self.view.backgroundColor = .clear
        self.modalTransitionStyle = .crossDissolve
        self.modalPresentationStyle = .overFullScreen
        topMostViewController?.present(self, animated: true)
    }
}

func bind() {
    nagativeButton.rx.tapGesture()
        .when(.recognized)
        .bind(onNext : { [weak self] _ in
            guard let self = self else { return }
            self.nagativeDelegate(self)
        })
        .disposed(by: disposeBag)

    positiveButton.rx.tapGesture()
        .when(.recognized)
        .bind(onNext : { [weak self] _ in
            guard let self = self else { return }
            self.positiveDelegate(self)
        })
        .disposed(by: disposeBag)
}

private func configure(
    title : String,
    message : String,

    image : UIImage?,
    imageUrl : String?,
    imgWidth : Int,
    imgHeight : Int,

    nagativeButtonStr : String,
    nagativeButtonDelegate : @escaping (CommonModal) -&gt; Void,
    positiveButtonStr : String,
    positiveButtonDelegate : @escaping (CommonModal) -&gt; Void,

    /*
     속성
     */
    titleColor : UIColor,
    messageColor : UIColor,
    nagativeButtonColor : UIColor,
    positiveButtonColor : UIColor,
    modalBackgroundColor : UIColor,
    lineColor : UIColor
) {
    self.titleLabel.text = title

    self.messageLabel.text = message

    /*
     image
     */

    imageView.snp.makeConstraints {
        $0.width.equalTo(imgWidth)
        $0.height.equalTo(imgHeight)
    }

    if let image = image {
        imageView.image = image
        imageView.snp.makeConstraints {
            $0.top.equalTo(titleLabel.snp.bottom).offset(16)
        }
    }

    if let imageUrl = imageUrl {
        imageView.kf.setImage(with: URL(string: imageUrl))
        imageView.snp.makeConstraints {
            $0.top.equalTo(titleLabel.snp.bottom).offset(16)
        }
    }

    /*
     setNavigationButton을 안한다면 숨겨서 positiveButton만 보이게 동작하자...
     */
    self.nagativeButton.isHidden = nagativeButtonStr.isEmpty
    self.pillarLineView.isHidden = nagativeButtonStr.isEmpty

    self.nagativeButton.text = nagativeButtonStr
    self.nagativeDelegate = nagativeButtonDelegate

    self.positiveButton.text = positiveButtonStr
    self.positiveDelegate = positiveButtonDelegate

    /*
     속성
    */
    self.titleLabel.textColor = titleColor
    self.messageLabel.textColor = messageColor
    self.nagativeButton.textColor = nagativeButtonColor
    self.positiveButton.textColor = positiveButtonColor
    self.parentView.backgroundColor = modalBackgroundColor
    self.underLineView.backgroundColor = lineColor
    self.pillarLineView.backgroundColor = lineColor
}

/*
 자신의 Design에 맞게 layout을 배치
 정확히는 설정을 했을때 그릇이 되는 View들에 대해서
 각 상황에 대응하는 layout을 배치하는 것이 핵심
 */
func layout() {
    [
        blurView,
        parentView,
        titleLabel,
        imageView,
        messageLabel,
        underLineView,
        buttonStackView,
        pillarLineView,
    ].forEach {
        view.addSubview($0)
    }

    blurView.snp.makeConstraints {
        $0.edges.equalToSuperview()
    }

    parentView.snp.makeConstraints {
        $0.width.equalTo(270)
        $0.center.equalToSuperview()
        $0.top.equalTo(titleLabel).offset(-16)
        $0.bottom.equalTo(buttonStackView)
    }


    titleLabel.snp.makeConstraints {
        $0.top.equalTo(parentView)
        $0.centerX.equalTo(parentView).inset(16)
    }

    imageView.snp.makeConstraints {
        $0.top.equalTo(titleLabel.snp.bottom)
        $0.centerX.equalTo(parentView)
    }

    messageLabel.snp.makeConstraints {
        $0.top.equalTo(imageView.snp.bottom).offset(16)
        $0.leading.trailing.equalTo(parentView).inset(16)
    }

    underLineView.snp.makeConstraints {
        $0.top.equalTo(messageLabel.snp.bottom).offset(16)
        $0.leading.trailing.equalTo(parentView)
    }

    buttonStackView.snp.makeConstraints {
        $0.top.equalTo(underLineView.snp.bottom)
        $0.leading.trailing.equalTo(parentView)
        $0.bottom.equalTo(parentView)
    }

    pillarLineView.snp.makeConstraints {
        $0.top.bottom.equalTo(buttonStackView)
        $0.centerX.equalTo(buttonStackView)
    }
}

class Builder {
    /*
     ...
     이후 필요한 속성에 대해서 지속적으로 추가하면 된다
     ex) Title, Message, button Font
     */

    /*
     Default 값 설정
     */
    private var title : String = &quot;&quot;
    private var message : String = &quot;&quot;

    private var image : UIImage? = nil
    private var imageUrl : String? = nil
    private var imgWidt : Int = 0
    private var imghight : Int = 0

    private var nagativeButtonStr : String = &quot;&quot;
    private var nagativeButtonDelegate : (CommonModal) -&gt; Void = { _ in }
    private var positiveButtonStr : String = &quot;&quot;
    private var positiveButtonDelegate : (CommonModal) -&gt; Void = { _ in }

    /*
     color 속성
     Default 값 설정
     */
    private var titleColor : UIColor = .black
    private var messageColor : UIColor = .black
    private var nagativeButtonColor : UIColor = .blue
    private var positiveButtonColor : UIColor = .red
    private var modalBackgroindColor : UIColor = .white
    private var lineColor : UIColor = .gray


    func setTitle(_ title : String) -&gt; Self {
        self.title = title
        return self
    }

    func setMessage(_ message : String) -&gt; Self {
        self.message = message
        return self
    }

    func setImage(_ image : UIImage?, width : Int, height : Int) -&gt; Self {
        self.image = image
        self.imgWidt = width
        self.imghight = height
        return self
    }

    func setImageUrl(_ url : String?, width : Int, height : Int) -&gt; Self {
        self.imageUrl = url
        self.imgWidt = width
        self.imghight = height
        return self
    }

    func setNagativeButton(
        _ label : String,
        _ delegate : @escaping (CommonModal) -&gt; Void
    ) -&gt; Self {
        self.nagativeButtonStr = label
        self.nagativeButtonDelegate = delegate
        return self
    }

    func setPositiveButton(
        _ label : String,
        _ delegate : @escaping (CommonModal) -&gt; Void
    ) -&gt; Self {
        self.positiveButtonStr = label
        self.positiveButtonDelegate = delegate
        return self
    }

    /*
     color 속성
     */

    func setTitleColor(_ color : UIColor) -&gt; Self {
        self.titleColor = color
        return self
    }

    func messageColor(_ color : UIColor) -&gt; Self {
        self.messageColor = color
        return self
    }

    func setNagativeButtonColor(_ color : UIColor) -&gt; Self {
        self.nagativeButtonColor = color
        return self
    }

    func setPositiveButtonColor(_ color : UIColor) -&gt; Self {
        self.positiveButtonColor = color
        return self
    }

    func setModalBackgroundColor(_ color : UIColor) -&gt; Self {
        self.modalBackgroindColor = color
        return self
    }

    func setLineColor(_ color : UIColor) -&gt; Self {
        self.lineColor = color
        return self
    }

    func build() -&gt; CommonModal {
        return CommonModal().then {
            $0.configure(
                title: title,
                message: message,
                image: image,
                imageUrl: imageUrl,
                imgWidth: imgWidt,
                imgHeight: imghight,
                nagativeButtonStr: nagativeButtonStr,
                nagativeButtonDelegate: nagativeButtonDelegate,
                positiveButtonStr: positiveButtonStr,
                positiveButtonDelegate: positiveButtonDelegate,
                titleColor: titleColor,
                messageColor: messageColor,
                nagativeButtonColor: nagativeButtonColor,
                positiveButtonColor: positiveButtonColor,
                modalBackgroundColor: modalBackgroindColor,
                lineColor: lineColor
            )
        }
    }
}</code></pre><p>}</p>
<pre><code>- 설명
&gt; Builder 클래스는 데이터 세팅 및 CommonModal의 최종 생산(build) 역할을 담당한다. 그리고 CommonModal는 이 데이터를 담는 그릇배치 및 표현(show)의 역할을 담당한다.
[자세한 설명은 주석 참고]

### BottomModal
- 사용
![](https://velog.velcdn.com/images/will_d/post/75faad97-bcdc-4de2-952c-5119c7794d82/image.png)
![](https://velog.velcdn.com/images/will_d/post/c6cd3cef-dba6-4de5-88b6-a8988cdc576a/image.png)
- 구현부</code></pre><p>struct CommoBottomModalAction {
    let title : String
    let titleColor : UIColor
    let action : (CommonBottomModal) -&gt; Void
}</p>
<p>class CommonBottomModal : BaseViewController {</p>
<pre><code>let disposeBag = DisposeBag()

let blurView = UIView().then {
    $0.backgroundColor = .clear
}

var actions : [CommoBottomModalAction] = []

let stackView = UIStackView().then {
    $0.spacing = 5
    $0.axis = .vertical
}

let cancleButton = UILabel().then {
    $0.heightAnchor.constraint(equalToConstant: 56).isActive = true
    $0.backgroundColor = .clear
    $0.clipsToBounds = true
    $0.layer.cornerRadius = 16
    $0.textAlignment = .center
}

func show() {

    /*
     최상단 ViewController가 자기 자신인지 판단
     */
    if topMostViewController !== self {
        topMostViewController?.presentPanModal(self)
    }
}

private func configure(
    _ actions : [CommoBottomModalAction],
    _ actionBackgroundColor : UIColor,
    _ cancelMessage : String,
    _ cancelBackgroundColor : UIColor
) {

    /*
     actionData Label로 변환
     실질 적인 UI/UX를 하는 실재로 변환하는 과정
     */
    let labels = actions
        .map { action in
            UILabel().then {
                $0.text = action.title
                $0.textColor = action.titleColor

                $0.heightAnchor.constraint(equalToConstant: 56).isActive = true
                $0.backgroundColor = actionBackgroundColor
                $0.clipsToBounds = true
                $0.layer.cornerRadius = 16
                $0.textAlignment = .center
            }
        }

    cancleButton.text = cancelMessage
    cancleButton.backgroundColor = cancelBackgroundColor

    /*
     stackView에 추가
     */
    labels.forEach {
        stackView.addArrangedSubview($0)
    }
    stackView.addArrangedSubview(cancleButton)


    [
        blurView,
        stackView
    ].forEach {
        view.addSubview($0)
    }

    blurView.snp.makeConstraints {
        $0.edges.equalToSuperview()
    }

    /*
     actions의 아이템 개수에 따라 stackView의 크기를 동적으로 결정
     */
    stackView.snp.makeConstraints {
        $0.leading.trailing.equalToSuperview().inset(16)
        $0.bottom.equalToSuperview().inset(38)
        /*
         주요 코드
         */
        $0.top.equalTo(labels[0])
    }


    /*
     bind
     */
    blurView.rx.tapGesture()
        .when(.recognized)
        .bind(onNext : { [weak self] _ in
            self?.dismiss(animated: true)
        })
        .disposed(by: disposeBag)

    cancleButton.rx.tapGesture()
        .when(.recognized)
        .bind(onNext : { [weak self] _ in
            self?.dismiss(animated: true)
        })
        .disposed(by: disposeBag)

    /*
     commonActionDelegate에서 정의한 함수 호출
     */
    labels
        .enumerated()
        .forEach { index, label in
            label.rx.tapGesture()
            .when(.recognized)
            .bind(onNext : { [weak self] _ in
                guard let self = self else { return }
                self.dismiss(animated: true) {
                    actions[index].action(self)
                }
            })
            .disposed(by: disposeBag)
    }
}


class Builder {
    /*
     필요한 Design에 맞게 속성 추가
     */
    private var actions : [CommoBottomModalAction] = []

    private var actionBackground : UIColor = .white

    private var cancelMessage : String = &quot;취소&quot;
    private var cancelBackgroundColor : UIColor = .gray


    func setActions(_ actions : [CommoBottomModalAction]) -&gt; Self {
        self.actions = actions
        return self
    }

    func setActionBackgroundColor(_ color : UIColor) -&gt; Self {
        self.actionBackground = color
        return self
    }

    func setCancelMessage(_ message : String) -&gt; Self {
        self.cancelMessage = message
        return self
    }

    func cancelBackgroundColor(_ color : UIColor) -&gt; Self {
        self.cancelBackgroundColor = color
        return self
    }


    /*
     설정을 모두 완료하고 이때 CommonBottomModal 생성
     */
    func build() -&gt; CommonBottomModal {
        return CommonBottomModal().then {
            $0.configure(
                actions,
                actionBackground,
                cancelMessage,
                cancelBackgroundColor
            )
        }
    }

}</code></pre><p>}</p>
<p>extension CommonBottomModal : PanModalPresentable {</p>
<pre><code>var showDragIndicator: Bool {
    return false
}

var panScrollable: UIScrollView? {
    return nil
}

var shortFormHeight: PanModalHeight {
    return .maxHeightWithTopInset(0)
}

var longFormHeight: PanModalHeight {
    return .maxHeightWithTopInset(0)
}
var anchorModalToLongForm: Bool {
    return false
}</code></pre><p>}</p>
<pre><code>
- 설명
&gt; 기본 원리는 CommonModal과 같다. 다른점이 있다면 BottomModal의 경우에 Action의 개수가 다양 할 수있다. 예제 에서는 2개의 액션만 보여지지만 5개가 될 수 도 있다. 이 액션에 대해서 각각의 동작을 정의해 줘야 했기에 CommoBottomModalAction를 구현했고 이를 통해서 각각의 액션을 정의해 Builder에게 전달하는 식으로 개발을 했다.
[자세한 설명은 주석 참고]

### CommonToast
* 사용
![](https://velog.velcdn.com/images/will_d/post/4ed6c38f-79aa-4f56-bb5d-5c94fad7cf40/image.png)
* 구현</code></pre><p>class CommonToast : UIView {</p>
<pre><code>private let disposeBag = DisposeBag()

private var onClickDelegate : (CommonToast) -&gt; Void = { _ in }

private let messageLabel = UILabel().then {
    $0.textAlignment = .center
    $0.numberOfLines = 0
}


override init(frame: CGRect) {
    super.init(frame: frame)
    layout()
    bind()
}

required init?(coder: NSCoder) {
    fatalError(&quot;init(coder:) has not been implemented&quot;)
}

func show() {
    let topMostViewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController?.topMostViewController
    topMostViewController?.view.showToast(self, duration: 1, position: .bottom)
}

private func bind() {
    /*
     ToastButton을 탭 했을때
     */
    self.rx.tapGesture()
        .when(.recognized)
        .bind(onNext : { [weak self] _ in
            guard let self = self else { return }
            self.onClickDelegate(self)
        })
        .disposed(by: disposeBag)
}

private func configure(
    message : String,
    messageColor : UIColor,
    backgroundColor : UIColor,
    onClickDelegate : @escaping (CommonToast) -&gt; Void
) {
    self.messageLabel.text = message
    self.messageLabel.textColor = messageColor

    self.backgroundColor = backgroundColor

    self.onClickDelegate = onClickDelegate
}


private func layout() {
    [
        messageLabel
    ].forEach {
        addSubview($0)
    }

    messageLabel.snp.makeConstraints {
        $0.center.equalToSuperview()
    }
}


/*
 필요에 의해서 추가 속성 등록 및 사용
 */

class Builder {
    private var message : String = &quot;&quot;
    private var messageColor : UIColor = .black
    private var backgroundColor : UIColor = .cyan
    private var heightSize : CGFloat = 56

    private(set) var onClickDelegate : (CommonToast) -&gt; Void = { _ in }

    func setMessage(_ message : String) -&gt; Self {
        self.message = message
        return self
    }

    func setMessageColor(_ color : UIColor) -&gt; Self {
        self.messageColor = color
        return self
    }

    func setBackgroundColor(_ color : UIColor) -&gt; Self {
        self.backgroundColor = color
        return self
    }

    func setHeightSize(_ height : CGFloat) -&gt; Self {
        self.heightSize = height
        return self
    }

    func setOnClickDelegate(_ delegate : @escaping (CommonToast) -&gt; Void) -&gt; Self {
        self.onClickDelegate = delegate
        return self
    }

    func build() -&gt; CommonToast {
        let rootViewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController
        return CommonToast(frame: .init(x: 0, y: 0, width: rootViewController?.view.frame.width ?? 0, height: heightSize)).then {
            $0.configure(
                message: message,
                messageColor: messageColor,
                backgroundColor: backgroundColor,
                onClickDelegate: onClickDelegate
            )
        }
    }
}   </code></pre><p>}</p>
<pre><code>* 설명
&gt; 원리는 CommoModal과 같다.

### CommonLoadingView
* 사용
![](https://velog.velcdn.com/images/will_d/post/6a29b584-f753-428b-9d8b-9dcda468b47d/image.png)
![](https://velog.velcdn.com/images/will_d/post/52ca8785-ef61-465e-821c-ae8045838ed3/image.png)
![](https://velog.velcdn.com/images/will_d/post/4af15773-4357-4406-b879-299711402290/image.png)

- 구현</code></pre><p>/*
 Lottie를 통한 LoadingView
 */</p>
<p>class CommonLoadingView {</p>
<pre><code>var loadingView : LottieAnimationView? = LottieAnimationView(name: &quot;lottie&quot;).then {

    $0.isHidden = true
    $0.loopMode = .loop
    $0.play()
}


var timer : Timer? = nil
var timeRemaining = 0.8



convenience init() {

    let superView = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController?.topMostViewController?.view
    self.init(superView: superView)
}


init(superView : UIView?) {
    superView?.addSubview(loadingView!)
    loadingView?.snp.makeConstraints {
        $0.center.equalToSuperview()
    }
}


func show() {
    /*
     touch 비활성
     */
    let window = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window
    window?.isUserInteractionEnabled = false

    /*
     타이머가 동작하기 전에는 보여줘서는 안되기 때문에
     loadingView를 숨긴다.
     */
    loadingView?.isHidden = true

    startTimer()
}


func dismiss() {
    hide()
    stopTimer()
    loadingView?.removeFromSuperview()
    loadingView = nil
}

private func hide() {
    /*
     touch 활성
     */
    let window = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window
    window?.isUserInteractionEnabled = true

    self.loadingView?.isHidden = true
}


private func startTimer() {
    timer = Timer.scheduledTimer(withTimeInterval: timeRemaining, repeats: true) { [weak self] _ in
        guard let self = self else { return }

        self.timeRemaining -= 0.8

        /*
         0.8초가 지나면 호출
         */
        if self.timeRemaining == 0 {
            /*
             loadingView zIndex 최고 레벨 부여
             */
            self.loadingView?.layer.zPosition = CGFloat(Float.greatestFiniteMagnitude)
            self.loadingView?.isHidden = false
        }

    }
}



private func stopTimer() {
    timer?.invalidate()
    timer = nil
}</code></pre><p>}</p>
<pre><code>- 설명
&gt; LoadingView는 Lottie-ios를 사용해서 표현했다. 내가 어디서 본지는 기억이 안나지만 사용자가 화면에서 집중력을 잃는 시간이 0.8초라고 들은적이 있다. 나는 이 근거로 화면에 LoadingView를 표시(show)할때 0.8초 전에는 LoadingView를 표시하지 않고 0.8초가 지나면 LoadingView를 표시하도록 로직을 작성해 봤다.
또한 어떤 작업이 진행될때 사용자가 추가 동작을 하면 못하도록 화면의 Touch또한 막아줬다.(이 화면터치는 0.8초와 상관없이 dismiss가 호출될때까지 이루어 진다.)
그리고 어떤 특정 작업(ex) Network)이 완료되면 LoadingView를 화면에서 지운도 그리고 터치도 다시 활성화 한다.(dismiss) 이번 예제에서는 Network 상황은 구현하지 않았고 대신에 timer를 통해 동작이 어떻게 이뤄지는지 구현했다.
[자세한 내용은 주석 참고]


### CommonRetry
* 사용
![](https://velog.velcdn.com/images/will_d/post/fa959bf3-5e6d-4af7-a28d-90a8810f72ed/image.png)
![업로드중..](blob:https://velog.io/ab8341d8-52ff-4ba0-b0f3-a55a063d41b6)
- 구현
#### BaseViewController
![](https://velog.velcdn.com/images/will_d/post/d98fb64a-052f-412b-8464-d8bd4f2924c6/image.png)
#### DetailViewController</code></pre><p>class DetailViewController : BaseViewController {
    let disposeBag = DisposeBag()</p>
<pre><code>let commonRetry = UILabel().then {
    $0.text = &quot;CommonRetry&quot;
}




var loadingView : CommonLoadingView? = nil
var timeRemaining = 2.0
var timer : Timer? = nil


override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    layout()
    bind()
}

required init?(coder: NSCoder) {
    fatalError(&quot;init(coder:) has not been implemented&quot;)
}</code></pre><p>...
    /*
    BaseViewController retry 함수 오버라이드
    */
    override func retry() {
        super.retry()
        print(&quot;HELLO RETRY&quot;)
    }
... 
}</p>
<pre><code>#### CommonRetryView</code></pre><p>class CommonRetryView : UIView {
    static let EXIST = 1</p>
<pre><code>let disposeBag = DisposeBag()

let titleLabel = UILabel().then {
    $0.numberOfLines = 0
}

let retryButton = UILabel().then {
    $0.textColor = .systemRed
    $0.numberOfLines = 0
}

override var intrinsicContentSize: CGSize {
    let titleSize = titleLabel.intrinsicContentSize
    let buttonSize = retryButton.intrinsicContentSize
    let width = min(titleSize.width, buttonSize.width)
    let height = titleSize.height + 16 + buttonSize.height
    return CGSize(width: width, height: height)
}


override init(frame: CGRect) {
    super.init(frame: frame)
    layout()
    bind()
}

required init?(coder: NSCoder) {
    fatalError(&quot;init(coder:) has not been implemented&quot;)
}

func show() {
    let topMostViewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController?.topMostViewController
    let alreadyAdded = topMostViewController?.view.subviews.contains(where: { $0.tag == CommonRetryView.EXIST }) ?? false
    guard !alreadyAdded else { return }

    topMostViewController?.view.addSubview(self)
    self.snp.makeConstraints {
        $0.center.equalToSuperview()
    }
}

func bind() {
    retryButton.rx.tapGesture()
        .when(.recognized)
        .bind(onNext : { [weak self] _ in
            let topMostViewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController?.depthViewController
            let baseViewController = topMostViewController as? BaseViewController

            /*
             BaseViewController에서 Override한 retry함수 호출
             */
            baseViewController?.retry()

            self?.removeFromSuperview()
        })
        .disposed(by: disposeBag)
}

private func configure(
    _ title : String,
    _ buttonText : String
) {
    titleLabel.text = title
    retryButton.text = buttonText


    /*
     tag 설정
     */
    self.tag = CommonRetryView.EXIST

    /*
     frame 설정
     */
    self.frame = CGRect(origin: .zero, size: intrinsicContentSize)
}


func layout() {
    [
        titleLabel,
        retryButton
    ].forEach {
        addSubview($0)
    }

    titleLabel.snp.makeConstraints {
        $0.top.equalToSuperview()
        $0.centerX.equalToSuperview()
    }

    retryButton.snp.makeConstraints {
        $0.top.equalTo(titleLabel.snp.bottom).offset(16)
        $0.centerX.equalTo(titleLabel)
    }

}

class Builder {
    private var title : String    = &quot;&quot;
    private var retryStr : String = &quot;&quot;

    func setTitle(_ title : String) -&gt; Self {
        self.title = title
        return self
    }

    func setRetryStr(_ retryStr : String) -&gt; Self {
        self.retryStr = retryStr
        return self
    }

    func build() -&gt; CommonRetryView {
        CommonRetryView().then {
            $0.configure(
                title,
                retryStr
            )
        }
    }
}   </code></pre><p>}</p>
<pre><code>* 설명
&gt; CommonRetryView에서 재시도 버튼을 누르는 코드를 보면 현재 화면에 보이는 가장 자녀 ViewController를 호출한다. 이 ViewController는 BaseViewController를 상속 받은 ViewController일 것이다. 왜냐 하면 내가 그렇게 설계를 했으니까! 그리고 이 ViewController의 retry를 실행한다. 즉 retry 함수를 override를 하여 각 화면에 맞는 retry 로직을 작성하면 된다. 이렇게 하면 재시도에 대한 원하는 동작을 구현 할 수 있다!!



### 마무리
위와 같이 개발했을때 디자인의 일관성을 구현했는가? 구현했다고 할 수 있다. 이유는 내가 만약 Loading을 표현해야 되고 Modal을 표현 한다고 하면 위의 Component들을 사용할것이기 때문이다. 그리고 이 Component들은 각자의 일관된 설계를 가지고 있기때문에 이렇게 해서 나는 일관된 UI/UX를 구현했다. 한단계 더 나아가 나는 Builder에 속성을 추가만 하면 새롭게 속성을 설정할 수 있도록 확장성 있는코드도 작성해 봤다. [전체코드](https://github.com/learnGrowD/CommonComponentCollection)

네이티브 개발자는 최전선에서 사용자와 상호작용 하는 개발자이다. 
개발자로서 여러 덕목도 있겠지만 좋은 UI/UX를 어떻게 사용자에게 전달해야 되는가 또한 매우 중요한 덕목이겠다. 그런 고민이 있기에 더 좋은 개발이 나올것이라고 믿는다. 앞으로도 나는 사용자에게 좋은 UI/UX를 제공하기 위해서는 어떻게 해야 되는가에대해서 고민하고 개발적으로 풀어나가는 노력을 열심히 할것이다.


</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[UX는 무엇이 결정 하는가 [1부]]]></title>
            <link>https://velog.io/@will_d/UX%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4-%EA%B2%B0%EC%A0%95-%ED%95%98%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@will_d/UX%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4-%EA%B2%B0%EC%A0%95-%ED%95%98%EB%8A%94%EA%B0%80</guid>
            <pubDate>Tue, 25 Apr 2023 23:20:12 GMT</pubDate>
            <description><![CDATA[<h3 id="상황">상황</h3>
<blockquote>
<p>나의 업무 중 하나는 주어진 디자인에 대해서 구현을 해야 되는 업무가 있다. 보통 나는 피그마의 작업물을 받아 업무를 하는데 피그마를 전반적으로 살펴봤을때 한가지 문제에 직면했다. 일관된 체계가 없었다. 나는 이 부분에 대해 디자이너분을 설득해야 됐다. 왜 일관된 체계가 필요한가?</p>
</blockquote>
<h3 id="고민">고민</h3>
<blockquote>
<p>왜 앱을 구성하는 요소들의 일관성 왜 중요한가 그리고 왜 이것이 체계화가 되어야 하는가 나는 이 고민을 집중적으로 했다. 이 생각 저생각 해보고 내가 내린 결론의 답은 UX에 있다.
사용자의 핸드폰에는 무수히 많은 앱들이 존재한다. 그리고 앱은 모두 다른 형태의 모습을 갖추고 있다. 사용자가 그 앱을 사용한다는것은 그 앱의 성격에 따라 달라질 것이다 즉 UX가 달라 질 수 있다는 말이다. 그렇다면 사용자에게 우리의 앱의 사용방식에 대해 직관적으로 전달을 해야 되는데 어떻게 잘 전달 할 수 있을까?</p>
</blockquote>
<h3 id="결론">결론</h3>
<blockquote>
<p>지속적이고 일관된 사용방식을 사용자에게 학습을 시키는것이 더 나은 UX와 사용방식을 결정한다. 즉 사용자가 충분히 학습을 할 수 있도록 일관된 UI/UX를 제공해야 된다. 그럼으로 우리는 디자인과 개발을 할때 일관성을 중요하게 생각해야 되고 설계를 해야 되는것이다. 나는 이 논리를 근거로 디자이너분과 대화를 했고 일관된 체계를 잡아갔다. 다음 글에서는 사용자와 상호작용을 하는 대표적인 Component를 기준으로 일관된 디자인을 개발적으로 어떻게 구현했는지에 대해서 작성하도록 하겠다.</p>
</blockquote>
<blockquote>
<p>사용자와 상호작용을 하는 대표적인 Component 소개</p>
</blockquote>
<ul>
<li>Modal</li>
<li>BottomModal</li>
<li>Toast</li>
<li>LoadingView</li>
<li>RetryView
<img src="https://velog.velcdn.com/images/will_d/post/ff34f714-bde4-4d0b-90ed-2b406f613b94/image.PNG" alt=""><img src="https://velog.velcdn.com/images/will_d/post/921be7ff-94a1-4246-ad67-b8b4cbd46826/image.PNG" alt=""><img src="https://velog.velcdn.com/images/will_d/post/1572f2cc-81c6-4043-bbf7-7c7e1209cc48/image.PNG" alt=""><img src="https://velog.velcdn.com/images/will_d/post/ba45139d-5957-4721-8fed-1f63ab64e827/image.PNG" alt=""><img src="https://velog.velcdn.com/images/will_d/post/0ca00eda-3708-45f7-8d42-68f72503872a/image.PNG" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[NavigationBar 정리]]></title>
            <link>https://velog.io/@will_d/NavigationBar-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@will_d/NavigationBar-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 24 Apr 2023 00:53:33 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/will_d/post/4cff9fdc-d749-4e03-8e61-213753334889/image.png" alt=""></p>
<p>오늘은 앱을 개발할때 자주 사용하는 NavigationBar에 대해서 정리하는 시간을 갖도록 하겠다.</p>
<h3 id="문제">문제</h3>
<ol>
<li>NavigationBar Title은 어떻게 처리 할 것인가? </li>
<li>NavigationBar의 속성과 Right, Left에 아이콘 배치는 어떻게 할 것 인가.<ul>
<li>Icon의 Right, Left 여백은 어떻게 할 것인가</li>
<li>Icon이 여러개라면 사이의 여백은 어떻게 할 것인가</li>
<li>Icon을 탭했을때 이벤트는 어떻게 처리 할 것인가</li>
</ul>
</li>
</ol>
<h3 id="해결">해결</h3>
<ol>
<li>NavigationBar Title 설정</li>
</ol>
<ul>
<li><p>BaseViewController
<img src="https://velog.velcdn.com/images/will_d/post/6daca836-3ee2-4a77-8e8c-14269267698e/image.png" alt=""></p>
</li>
<li><p>MainViewController
<img src="https://velog.velcdn.com/images/will_d/post/3e32dc7b-9181-4a97-9847-32af8ef70206/image.png" alt=""></p>
</li>
<li><p>DetailViewController
<img src="https://velog.velcdn.com/images/will_d/post/0a8ce7fb-9992-4dcd-8110-c9808cac297c/image.png" alt=""></p>
</li>
</ul>
<blockquote>
<p>BaseViewController는 생명주기 및 ViewController의 속성을 공통으로 관리하는 기반이 되는 ViewController 이다. 이 BaseViewController를 상속 받은 각각의 ViewController에서 자신이 가져야 할 Navigation Title에 대해서 override하여 navTitle을 설정한다.</p>
</blockquote>
<ol start="2">
<li>NavigationBar의 속성과 Right, Left에 아이콘 배치는 어떻게 할 것 인가.</li>
</ol>
<ul>
<li>NavigationBar 초기 설정<pre><code>import Foundation
import UIKit
import RxSwift
import RxCocoa


</code></pre></li>
</ul>
<p>class NavigationManager {
...</p>
<pre><code>static func initNavigation(
    _ backImgName : String      = &quot;back&quot;,
    _ backgroundColor : UIColor = .white,
    _ itemColor : UIColor       = .black,
    _ fontColor : UIColor       = .black,
    _ fontName : String         = &quot;Helvetica Neue&quot;,
    _ fontSize : CGFloat        = 16
) {

    /*
     iOS 애플리케이션 전체에서 UINavigationBar 객체의 속성을
     설정 할 수 있는 인스턴스
     */
    let appearance = UINavigationBar.appearance()

    /*
     UINavigationBar 하단에 불투명한 그림자 표시에 관한 속성
     */
    appearance.shadowImage = UIImage()

    /*
     뒤로가기 이미지에 대한 속성
     */
    appearance.backIndicatorImage = UIImage(named: &quot;back&quot;)
    appearance.backIndicatorTransitionMaskImage = UIImage(named: &quot;back&quot;)

    /*
     NavigationBar 투명도에 대한 속성
     */
    appearance.isTranslucent = true

    /*
     탭바의 배경화면을 설정하는 속성
     */
    appearance.barTintColor = backgroundColor

    /*
     NavigationBar 탭바의 아이템 색깔을 설정하는 속성
     */
    appearance.tintColor = itemColor

    /*
     title font를 지정
     */
    appearance.titleTextAttributes = [
        NSAttributedString.Key.foregroundColor : fontColor,
        NSAttributedString.Key.font : UIFont(name: fontName, size: fontSize)!
    ]
}
...</code></pre><p>}</p>
<pre><code>![](https://velog.velcdn.com/images/will_d/post/89516349-3997-416b-a93d-61b17df94a42/image.png)

&gt; NavigationManager라는 NavigationBar을 관리하는 class를 하나 만들었다 이 중 initNavigation 이라는 static 함수는 NavigationBar Appearance를 통해서 Navigation Bar의 Background Color나 BackButtonImage와 같은 초기 설정을 담당하는 함수이다. 코드는 위와 같다.</code></pre><p>func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -&gt; Bool</p>
<pre><code>이 함수는 앱 초기에 호출되는 application 함수에서 호출 한다.
이 후에 변경하고 싶으면 initNavigation 함수만 관리하면 된다.

- Icon 배치와 버튼 탭 처리</code></pre><p>class NavigationManager {
    var disposeBag = DisposeBag()</p>
<pre><code>...
![](https://velog.velcdn.com/images/will_d/post/01df1f3d-bda4-442a-83c6-4d0b9d279a7f/image.png)

func setItem(
    itemName : String,
    itemSize : Int = 24,
    padding : CGFloat = 0,
    _ tap : @escaping (UIView) -&gt; Void
) -&gt; [UIBarButtonItem] {
    let fixedSpace = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
    /*
     iOS에서 기본 Right, Left 여백 16
     */
    fixedSpace.width = padding

    let view = UIImageView().then {
        /*
         Icon 사이즈 및 Icon Img 설정
         */
        $0.image = UIImage(named: itemName)
        $0.snp.makeConstraints {
            $0.width.height.equalTo(itemSize)
        }
    }

    /*
     버튼 탭
     NavigationManager을 ViewController의 멤버로 생성한다.
     멤버로 생성하여 ButtonTap의 Observer를 ViewController와 생명주기와 일치 시킨다.
     */
    view.rx.tapGesture()
        .when(.recognized)
        .bind(onNext : { _ in
            tap(view)
        })
        .disposed(by: disposeBag)

    return [fixedSpace, UIBarButtonItem(customView: view)]
}

func setItems(
    itemNames : [String],
    itemSize : Int = 24,
    ltPadding : CGFloat = 0,
    betweenPadding : CGFloat = 0,
    _ tap : @escaping (Int) -&gt; Void
) -&gt; [UIBarButtonItem] {
    if itemNames.count &gt; 1 {
        let fixedSpace = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
        /*
         iOS에서 기본 Right, Left 여백 16
         iOS에서 기본 Icon 사이의 여백 8
         */
        fixedSpace.width = ltPadding
        var result = itemNames.enumerated().map { index, itemName in
            let view = UIImageView().then {
                /*
                 Icon 사이즈 및 Icon Img 설정
                 */
                $0.image = UIImage(named: itemName)
                $0.snp.makeConstraints {
                    $0.width.height.equalTo(itemSize)
                }
            }

            /*
             버튼 탭
             NavigationManager을 ViewController의 멤버로 생성한다.
             멤버로 생성하여 ButtonTap의 Observer를 ViewController와 생명주기와 일치 시킨다.
             */
            view.rx.tapGesture()
                .when(.recognized)
                .bind(onNext : { _ in
                    /*
                     index를 delegate에 전달한다
                     사용을 한다면 Index를 통해서 어떠한 icon을 선택했는지
                     판단이 가능하다.
                     */
                    tap(index)
                })
                .disposed(by: disposeBag)

            return UIBarButtonItem(customView: view)
        }


        /*
         Icon 사이의 여백을 결정하는 로직
         */
        for i in 0..&lt;result.count + (result.count - 1) {
            if i % 2 == 1 {
                let betweenSpace = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
                betweenSpace.width = betweenPadding - 8
                result.insert(betweenSpace, at: i)
            }
        }

        result.insert(fixedSpace, at: 0)
        return result
    } else {
        return []
    }
}</code></pre><p>}</p>
<pre><code>&gt; Icon 배치와 버튼 탭 처리는 setItem, setItems 함수를 통해서 구현했다. 이 setItem, setItems 함수는
IconSize, IconTap, Icon 오른쪽, 왼쪽 여백, Icon과 Icon 사이의 여백의 값을 조정하는 로직이 존재한다.
실제 사용을 할때는 NavigationItem의 오른쪽에 배치할 것인지 왼쪽에 배치할것인지만 결정해주면 된다. 실제 사용 코드는 아래와 같다. [setItem 사용 방법도 아래와 비슷하다.]![](https://velog.velcdn.com/images/will_d/post/0161d76b-34cd-48a8-b8d1-3ce8049dbf19/image.png)



### 결과
![](https://velog.velcdn.com/images/will_d/post/ebf1ccbc-2471-4639-bbbe-01140ad88c77/image.png)


![](https://velog.velcdn.com/images/will_d/post/070158ff-3985-4521-9952-665224f599a6/image.png)




오늘은 이렇게 iOS에서 자주 사용하는 NavigationBar에 대해서 정리를 해봤다.

위와 같은 방법으로 정리를 해 놓으면 나중에 유지 보수 할떄 좀더 수월 하게 할 수 있을것 같아 정리해봤다.





</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[iOS Naver Map CustomMarker [3부]]]></title>
            <link>https://velog.io/@will_d/iOS-Naver-Map-CustomMarker-3%EB%B6%80</link>
            <guid>https://velog.io/@will_d/iOS-Naver-Map-CustomMarker-3%EB%B6%80</guid>
            <pubDate>Fri, 21 Apr 2023 01:17:01 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@will_d/iOS-Naver-Map-CustomMarker-2%EB%B6%80">iOS Naver Map CustomMarker [2부]</a> 이전 글에서는 Naver Map에서 Custom Marke을 구현한 실제 코드를 살펴봤다. 이번글에서는 전글에서 직면한 문제에 대해서 어떻게 해결을 했는가에 대해서 글을 작성해 보겠다.</p>
<h3 id="문제상황">문제상황</h3>
<ol>
<li><p>View의 배치 및 상태를 결정하는 시점에서 나오는 Snapshot의 문제 (AutoLayout을 통해 배치한다고 하면 더더욱 중요하다.)</p>
</li>
<li><p>어떻게 CustomView의 크기를 동적으로 정할 것 인가.</p>
</li>
</ol>
<h3 id="해결">해결</h3>
<ol>
<li>첫번째 문제<blockquote>
<p>View의 Layout의 frame이 정해지지 않은 상태에서 View를 캡쳐해서 Marker의 imageIcon에 넣어주면 에러가 난다.
에러 코드는 아래와 같은데 정확한 이유는 알려주지 않는다.
여러 시도를 해보고 추론한 결과 View의 frame이 정해지지 않았으니 제대로 캡쳐를 못한것이다.
<img src="https://velog.velcdn.com/images/will_d/post/9263da15-7d61-4ea9-8205-d89fd248b45f/image.png" alt="">
나는 이러한 추론으로 frame을 설정해 봤다. 이렇게 하니까 
View가 정상적으로 캡쳐가 되어 Marker에 이미지로 잘 표현이 되었다.
<img src="https://velog.velcdn.com/images/will_d/post/e05abb0e-ecd2-49e3-a738-e00d9984ff49/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/5a2b2c33-f440-4d19-af70-5f24515febf9/image.png" alt="">
그러나 frame으로 정적으로 배치하는것이 아닌 AutoLayout을 통해서 동작으로 배치 할려고 하니까 제대로 캡쳐가 안되고 이상한 Image가 캡쳐 되었다. [첫번째는 비정상, 두번째 정상]
<img src="https://velog.velcdn.com/images/will_d/post/b413c322-de45-4a33-844f-252753ac35e2/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/3d9e6c7b-d88b-4a0a-8d89-285a20bfcafb/image.png" alt="">
왜 이러는 것일까? 이유는 간단하다 AutoLayout을 통해서 View의 배치과 완료되는 시점과 캡쳐를하는 시점이 맞지 않기 때문이다.
<img src="https://velog.velcdn.com/images/will_d/post/0768a8c4-82f7-443e-8202-aaad27b4aabc/image.png" alt="">
iOS의 UIView는 layoutIfNeeded라는 함수를 지원하는데 이 함수의 기능은 View와 그 자식 Vie의 레이아웃을 갱신한다.
나는 이 함수를 통해서 캡쳐를 하는 시점 전에 레이아웃을 갱신하게 했다. 그런다음에 캡쳐를 하니 정상적으로 Marker에 이미지를 표시 할 수 있었다. 해결한 코드는 아래와 같다.<img src="https://velog.velcdn.com/images/will_d/post/68db7eae-518c-4dc1-9528-d859b364017b/image.png" alt=""></p>
</blockquote>
</li>
</ol>
<ol start="2">
<li>두번째 문제<blockquote>
<p>매물 수나 정보에 관련한 마커의 글자수는 예측 할 수 없다 즉 표현한다고 하면 동적으로 표현하는게 적합한 표현이다. 마커로 표현한다고 하면 어떻게 표현하면 좋을까. 간단하다 label의 intrinsicContentSize을 통해 동적으로 View의 크기 동적으로 결정한 다음 AutoLayout을 업데이트 해서 캡쳐하면 된다.
해결한 코드는 아래와 같다.<img src="https://velog.velcdn.com/images/will_d/post/72710972-f78c-4519-9a37-e97ac7e54193/image.png" alt=""></p>
</blockquote>
</li>
</ol>
<h3 id="그리고-하나더">그리고 하나더</h3>
<p>마커의 데이터 객체는 MarkerProtocol을 상속 받는다. 그리고 만약 마커를 통해서 발생하는 이벤트(MarkerTap -&gt; Page 전환)는 id를 통해서 분기를 한다고 하면 굳이 아래의 3번째 사진처럼 할 필요 없이 2번째 사진처럼 다형성으로 처리한다. 더욱 깔끔한 코드를 작성해봤다.
<img src="https://velog.velcdn.com/images/will_d/post/a3752042-a853-4582-ab88-e47a1e62a799/image.png" alt="">
<img src="https://velog.velcdn.com/images/will_d/post/7eabaa3f-0a2c-4ceb-b106-61c9c9bb3297/image.png" alt="">
<img src="https://velog.velcdn.com/images/will_d/post/784c11d7-9eb1-4cff-aa1a-d88ad539c32b/image.png" alt=""></p>
<p>또한 CustomMarkerView는 그저 껍데기에 불과하다 매번 객체를 새로 생성하는것 보다 그냥 한번 생성해서 필요에 의해서 이 껍데기에 이미지를 넣어져서 캡쳐하는 방식으로 코드를 수정해 봤다. 코드는 아래와 같다. [첫번째 사진 : 새로 생성, 두번째, 세번째 사진 : 만들고 재사용]
<img src="https://velog.velcdn.com/images/will_d/post/29f5283e-c89a-4693-bd04-ae8f0e074f39/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/dd6113ac-82ab-4ee4-b8ee-2bbb851575c9/image.png" alt=""><img src="https://velog.velcdn.com/images/will_d/post/4e9f8eb1-2315-4b3b-af05-f9334631e120/image.png" alt=""></p>
<h3 id="최종-마무리">최종 마무리</h3>
<p>CusomMarkerView를 만들고 이미지를 shapshot하여 Naver Map에서 CustomMarker를 표현하는 방법에 대해서 알아봤다.
여러 시도가 있었고 여러 문제가 발생했다. 그리고 해결을 하는 과정에서 다시금 생명주기의 중요성에 대해서 깨닫는 시간을 갖게 되었다.</p>
<p>개발자는 항상 문제를 직면했을때 이 문제는 무엇인지에 대해 정의 할 수 있는 사람이여야 한다고 생각한다. 정의를 해야 어떻게 문제를 해결할지에대해서 방향성을 잡을 수 있기때문이다. iOS Naver Map CustomMaker 시리즈 글을 작성하면서 사고의 과정을 중심으로 글을 작성해 봤다. 중요한 지점이고 앞으로도 이러한 능력을 키우기 위해 부단히 노력할것이다. <a href="https://github.com/learnGrowD/NaverMap_Custom_Marker/tree/solve">해결 전체코드</a></p>
<p>긴글 읽어주셔서 감사합니다.</p>
]]></description>
        </item>
    </channel>
</rss>