<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>martin-han.log</title>
        <link>https://velog.io/</link>
        <description>산입니다.</description>
        <lastBuildDate>Thu, 17 Apr 2025 16:20:50 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>martin-han.log</title>
            <url>https://velog.velcdn.com/images/martin-han/profile/3c458508-7228-4eb9-94e6-ba8cac6804e2/image.JPG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. martin-han.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/martin-han" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[MCPO - OpenWebUI에서 MCP 사용하기]]></title>
            <link>https://velog.io/@martin-han/OpenWebUI-MCPO</link>
            <guid>https://velog.io/@martin-han/OpenWebUI-MCPO</guid>
            <pubDate>Thu, 17 Apr 2025 16:20:50 GMT</pubDate>
            <description><![CDATA[<p>요즘 <code>jetbrain MCP server</code> + <code>Claude</code> 으로 행복코딩을 하다가
claude가 간간히 락에 걸렸기 때문에 애플리케이션이 아닌 API로도 연결해보고 싶다는 니즈를 느낌</p>
<p>그래서 이전에 사용했던 OpenWebUI을 로컬에서 실행한 뒤 MCP 서버를 파이프라인으로 등록해서 써봐야겠다는 생각에 검색을 해봄</p>
<p>결과 굉장한 기능을 알게됨 이름하야 <strong><code>MCPO</code></strong></p>
<h2 id="mcp와-mcpo">MCP와 MCPO</h2>
<h4 id="mcpmodel-context-protocol">MCP(Model Context Protocol)</h4>
<p>기본적으로 알고있는 MCP의 개념은 <code>AI가 로컬에서 입출력 방식으로 애플리케이션을 작동시키는 것</code></p>
<h4 id="mcpomcp-openapi">MCPO(MCP OpenAPI)</h4>
<p>MCP를 RESTful OpenAPI를 통해 접근하게 만든 프록시도구로
HTTP 통신으로 도구를 쉽게 통합시키는 기법</p>
<h2 id="웹에서-로컬-도구-조작-가능">웹에서 로컬 도구 조작 가능</h2>
<p>브라우저 내에서 클라이언트 단에서 localhost로 요청을 보내 local로 호출
<img src="https://velog.velcdn.com/images/martin-han/post/1c1bcf58-1c04-4131-ac4c-935bd2f2a124/image.png" width=400></p>
<p>보안상의 이유등으로 계약된 혹은 폐쇄형 LLM을 사용하는 경우 유용하게 사용이 가능해 보임</p>
<blockquote>
<p>설정을 따로하지 않고 사용한다는 전재로 보면
전,후 처리가 타 프로덕트보다 확실히 떨어진다.</p>
</blockquote>
<h1 id="실행">실행</h1>
<p>예시는 <code>InteliJ MCP</code> + <code>GPT4.1</code> 조합으로 진행</p>
<h2 id="1openwebui-실행">1.<a href="https://velog.io/@martin-han/OpenWebUI-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0">OpenWebUI 실행</a></h2>
<blockquote>
<p>OpenWebUI v0.6.0 버전부터 지원</p>
</blockquote>
<p><a href="https://github.com/open-webui/open-webui/pkgs/container/open-webui#installation-with-default-configuration">OpenWebUI docs</a>
docker로 간단하게 실행이 가능하다.
<a href="https://platform.openai.com/api-keys">OPENAI_API_KEY</a>를 발급 받아서 첨부하기</p>
<pre><code class="language-bash">docker run -d -p 3000:8080 -e OPENAI_API_KEY=your_secret_key --name open-webui ghcr.io/open-webui/open-webui:main</code></pre>
<p>실행 후 접속해서 관리자 계정 생성</p>
<img src="https://velog.velcdn.com/images/martin-han/post/40d8d05d-e62e-42c1-b61c-e1072bfb7161/image.png" width=800>
잘 작동하는 것을 볼 수 있다.

<h2 id="2intelij-mcp-server-플러그인-설치">2.InteliJ MCP Server 플러그인 설치</h2>
<blockquote>
<p> IntelliJ IDEA 2023.2 버전부터 지원</p>
</blockquote>
<p>마켓에서 <a href="https://github.com/JetBrains/mcp-jetbrains/blob/main/README.md#usage-with-claude-desktop">MCP Server</a> 플러그인 설치
<img src="https://velog.velcdn.com/images/martin-han/post/9d846095-5df0-4add-8cc4-5395d510b213/image.png" alt=""></p>
<p>다음 MCP 설정을 뒤에 해줘야함
<code>/youRworkspace/config.json</code> 생성</p>
<pre><code>{
  &quot;mcpServers&quot;: {
    &quot;jetbrains&quot;: {
      &quot;command&quot;: &quot;npx&quot;,
      &quot;args&quot;: [&quot;-y&quot;, &quot;@jetbrains/mcp-proxy&quot;]
    }
  }
}</code></pre><h2 id="3mcpo-설치">3.MCPO 설치</h2>
<p><a href="https://github.com/open-webui/mcpo">MCPO Docs</a>
docker compose로 여러가지 MCP들을 한번에 관리하려고 했으나
InteliJ의 port가 로컬에서만 접근이 가능하기 때문에 docker로 실행하는 것을 포기함</p>
<h3 id="단건으로-관리">단건으로 관리</h3>
<pre><code>uvx mcpo --port 9000 -- npx @jetbrains/mcp-proxy -y

# 성공 응답
Starting MCP OpenAPI Proxy on 0.0.0.0:9000 with command: npx @jetbrains/mcp-proxy -y
INFO:     Started server process [21405]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:9000 (Press CTRL+C to quit)</code></pre><p><code>localhost:8000/openapi.json</code> 요청시 openWebUI에서 사용할 툴 정보를 확인 가능</p>
<h3 id="config로-한번에-관리">config로 한번에 관리</h3>
<pre><code>uvx mcpo --port 9000 --config /workspace/config.json

# 성공 응답
Starting MCP OpenAPI Proxy with config file: /workspace/config.json
INFO:     Started server process [21478]
INFO:     Waiting for application startup.
Perplexity Ask MCP Server running on stdio
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:9000 (Press CTRL+C to quit)</code></pre><p><code>localhost:9000/jetbrains/openapi.json</code> 요청시 openWebUI에서 사용할 툴 정보를 확인 가능</p>
<p>여러개의 mcp 사용시 다음 주소에서 연결된 mcp 정보 확인 가능</p>
<ul>
<li>{mcpo server}/{mcpName}/openapi.json</li>
<li>{mcpo server}/{mcpName}/docs</li>
</ul>
<h2 id="4openwebui에-mcpo-연결">4.OpenWebUI에 MCPO 연결</h2>
<h3 id="로그인-후-좌측-하단-프로필-클릭-후-설정-진입">로그인 후 좌측 하단 프로필 클릭 후 [설정] 진입</h3>
<img src="https://velog.velcdn.com/images/martin-han/post/5cce056c-8dd1-4a6e-9736-983072aca50b/image.png" width=300>

<h3 id="도구-탭에서-mcpo-등록">[도구] 탭에서 mcpo 등록</h3>
<img src="https://velog.velcdn.com/images/martin-han/post/ef3c7fa2-6121-476d-9b3f-c63946b90826/image.png" width=800>
URL 입력 후 새로고침 버튼으로 연결 확인

<h4 id="단건의-경우">단건의 경우</h4>
<p>URL - <a href="http://localhost:9000">http://localhost:9000</a></p>
<h4 id="config-사용의-경우">config 사용의 경우</h4>
<p>URL - <a href="http://localhost:9000/jetbrains">http://localhost:9000/jetbrains</a></p>
<h2 id="5-작동-확인">5. 작동 확인</h2>
<h4 id="새-채팅-하단에-tools가-활성화-됨">새 채팅 하단에 tools가 활성화 됨</h4>
<p><img src="https://velog.velcdn.com/images/martin-han/post/04170f67-2140-4de0-beea-c17a3af8d002/image.png" alt=""></p>
<h4 id="잘-읽어오는-것을-확인">잘 읽어오는 것을 확인</h4>
<p><img src="https://velog.velcdn.com/images/martin-han/post/b5ed14a3-be9e-49a9-81a5-277d80ee9a13/image.png" alt=""></p>
<h4 id="mcpo로-프록시된-mcp를-사용하는것을-확인">MCPO로 프록시된 MCP를 사용하는것을 확인</h4>
<p><img src="https://velog.velcdn.com/images/martin-han/post/02a53cc6-45bc-4466-9070-57a26e44e17c/image.png" alt=""></p>
<h2 id="6-tool-calling-사용">6. tool calling 사용</h2>
<p>chain of thought 처럼 연속되는 논리를 도와주는 옵션
이 옵션을 <code>기본값</code>으로 사용할 경우 tool을 <strong><code>한번만</code></strong> 사용함
<img src="https://velog.velcdn.com/images/martin-han/post/00181cdc-09d3-40ed-80b4-72bc7ffc82ce/image.png" alt=""></p>
<h1 id="마치며">마치며</h1>
<p>오픈소스 짱!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MSA 분산 트랜잭션 패턴]]></title>
            <link>https://velog.io/@martin-han/MSA-%EB%B6%84%EC%82%B0-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@martin-han/MSA-%EB%B6%84%EC%82%B0-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Mon, 10 Mar 2025 11:08:23 GMT</pubDate>
            <description><![CDATA[<h1 id="분산-트랜잭션">분산 트랜잭션</h1>
<p>모노리틱 서버에서 기타 사유로 서버가 분리되었을 때
분리되기 전 처럼 요청의 <code>원자성</code>을 보장하기 위해 분산된 <strong>트랜잭션</strong>을 관리해주어야한다.
이것을 흔히 <code>분산 트랜잭션</code>이라고 한다.</p>
<h3 id="메시지-전달-보장-수준message-delivery-semantics">메시지 전달 보장 수준(Message Delivery Semantics)</h3>
<p>메시지에 따라 해당 전달에 대한 보장 수준을 달리가져간다.
해당 수준은 따라 구현 또는 플로우에 지대한 영향을 끼치기 때문에
하나의 수준으로 모든 메시지를 처리하는 것이 아닌 상황에 맞는 수준을 채택하여 다양하게 사용한다.</p>
<h4 id="최대-한-번-전달at-most-once">최대 한 번 전달(At Most Once)</h4>
<p>메시지는 최대 한 번 전송되며, <strong>재전송하지 않는다.</strong>
해당 메시지는 유실될 가능성이 있다.
일상 생활로 치면 GPS라고 생각한다. 
주기적으로 전송하기 때문에 10번 중 한번의 데이터를 못받았다고 크게 문제가 발생하지 않는다.</p>
<h4 id="최소-한-번-전달at-least-once">최소 한 번 전달(At Least Once)</h4>
<p>메시지는 최소 한 번 이상 전송되며, 중복 전송이 발생할 수 있다.
중복에 대한 멱등성을 수신자쪽에서 보장해야한다.</p>
<h4 id="정확히-한-번-전달exactly-once">정확히 한 번 전달(Exactly Once)</h4>
<p>메시지는 정확히 한 번만 전송되며, 유실이나 중복이 없다.
구현이 복잡하고 비용이 높은 만큼 가장 이상적인 전달 보장 수준</p>
<h2 id="2pctwo-phase-commit">2PC(Two-Phase Commit)</h2>
<p>분산 트랜잭션의 강한 원자성을 보장하기 위해 두 단계(준비 및 커밋)로 트랜잭션을 처리함
그러나 중앙 코디네이터에 강하게 의존하기 때문에 장애시 전체 트랜잭션 실패의 위험이 있음</p>
<p>트랜잭션이 완료되면 트랜잭션이 준비 상태가 되고 트랜잭션 코디네이터에 의해 상태가 전파되어 커밋 혹은 롤백 됨</p>
<h4 id="장점">장점</h4>
<ul>
<li><strong>강한 원자성 보장</strong>
모든 트랜잭션이 준비되어야 커밋됨<h4 id="단점">단점</h4>
</li>
<li><strong>낮은 성능</strong>
DB 비관적 락을 사용하기 때문에 성능에 안좋은 영향을 줌</li>
<li><strong>DBMS 의존성</strong>
일부 DB에서 2PC를 지원하지 않으며 서버 간 서로 다른 DB를 사용하는 경우 호환성 문제가 있을 수 있음 (MSA에 사용시 DB종류에 제약이 생김)</li>
<li><strong>코디네이터 의존성</strong>
장애 발생시 전체 트랜잭션에 영향을 줌 </li>
</ul>
<h2 id="saga">SAGA</h2>
<p>SAGA는 <strong>여러 로컬 트랜잭션</strong>을 순차적으로 실행하며 중간에 문제 발생시 <strong>각 서비스별 보상 트랜잭션</strong>으로 <strong>이전 상태로 복구</strong>하는 방식
이를 통해 전체 시스템의 원자성을 보장</p>
<h3 id="종류">종류</h3>
<h4 id="choreographed-saga">Choreographed Saga</h4>
<p>각 마이크로서비스가 이벤트 및 보상 트랜잭션을 중앙제어 없이 자체적으로 관리</p>
<h4 id="orchestrated-saga">Orchestrated Saga</h4>
<p>중앙 Orchestrator가 모든 이벤트 및 보상 트랜잭션을 관리</p>
<h4 id="예시">예시</h4>
<blockquote>
<ul>
<li>복잡함을 최소화하기 위해 뒤에 ‘이벤트&#39;가 붙은 화살표는 중간에 이벤트 메시지 버스가 있다고 가정</li>
</ul>
</blockquote>
<ul>
<li>주문자에게 가는 응답은 이해를 돕기위해 추가함</li>
</ul>
<p><img src="https://velog.velcdn.com/images/martin-han/post/7177af71-4b35-4321-8c19-4e73fc1e8441/image.png" alt=""></p>
<h4 id="장점-1">장점</h4>
<ul>
<li><p><strong>최종적 일관성(Eventual Consistency)</strong>
데이터를 즉시 완벽하게 동기화하지 않고, 일정 시간이 지난 후 시스템 전체의 일관성을 보장하는 최종적 일관성 채택</p>
</li>
<li><p><strong>느슨한 결합(Loose coupling), 높은 확장성</strong>
각 마이크로서비스는 자체 트랜잭션과 보상 로직을 포함하므로 독립적으로 개발, 배포 및 확장에 용이</p>
</li>
<li><p><strong>장애에 견고함</strong>
부분 실패 시 보상 트랜잭션을 통한 복구가 가능하여 전체 시스템 장애를 방지할 수 있음</p>
</li>
</ul>
<h4 id="단점-1">단점</h4>
<ul>
<li><p><strong>복잡성 증가</strong>
각 로컬 트랜잭션 실패 시 대응하는 보상 로직을 설계하고 구현해야함</p>
</li>
<li><p><strong>보상 트랜잭션의 한계</strong>
보상 트랜잭션이 “완벽한 원자성”을 보장하지 않으므로, 도메인 로직에 따라 최종 일관성 상태에 도달하는 데 시간이 걸릴 수 있음</p>
</li>
<li><p><strong>메시지 전달 신뢰성</strong>
중복, 메세지 소실 등의 위험이 있음</p>
</li>
<li><p><strong>최종적 일관성</strong>
최종적인 일관성을 보장하기 때문에 데이터의 일시적 불일치가 생길 수 있음</p>
</li>
<li><p><strong>Orchestrator 의존성(Orchestrated Saga 시)</strong>
중앙 제어기의 장애시 전체 트랜잭션에 문제가 생김</p>
</li>
</ul>
<h2 id="outboxtransactional-outbox">Outbox(Transactional Outbox)</h2>
<ul>
<li>Outbox 패턴은 비즈니스 데이터 변경과 함께 메시지(이벤트)를 동일한 데이터베이스 트랜잭션 내에 별도 테이블(Outbox 테이블)에 저장</li>
<li>로컬 트랜잭션이 성공하면 메시지도 함께 기록되므로, 메시지 전파와 데이터 변경의 원자성 보장</li>
<li>별도의 메시지 전파기(이벤트 프로세서)가 Outbox 테이블을 모니터링하여 메시지를 메시지 브로커 등을 통해 다른 서비스에 전파</li>
</ul>
<h3 id="outbox-polling-publisher">Outbox Polling Publisher</h3>
<ul>
<li>주기적으로 아웃박스 테이블을 폴링하여 처리되지 않은 메시지를 읽어 메시지를 전파,처리</li>
<li>간단한 구현이 가능하지만, 폴링 간격에 따라 지연 발생</li>
</ul>
<h4 id="예시-1">예시</h4>
<p><img src="https://velog.velcdn.com/images/martin-han/post/e65f0905-fd33-4396-a84c-6adb7658e0b6/image.png" alt=""></p>
<h4 id="장점-2">장점</h4>
<ul>
<li><strong>구현과 운영 용이성</strong>
구현 및 운영이 비교적 단순하며 유지 보수가 편리</li>
<li><strong>원자성 보장 및 메시지 유실 최소화</strong>
로컬 트랜잭션 내 메시지 기록으로 메시지 유실 최소화, 원자성 보장</li>
<li><strong>장애에 견고함</strong>
장애 상황에서 재시도 및 보상이 용이 (폴링 주기대로 재시도 가능)<h4 id="단점-2">단점</h4>
</li>
<li><strong>지연 발생</strong>
폴링 간격에 의해 메시지 발행 실시간 성능 지연 발생 가능</li>
<li><strong>성능 이슈</strong>
Outbox 테이블 크기 커짐에 따라 폴링 성능 이슈 가능</li>
<li><strong>추가 관리 포인트</strong>
Outbox 데이터 관리(예: 이미 전송된 이벤트 정리)등의 관리가 추가됨</li>
</ul>
<h3 id="cdcchange-data-capture---transaction-log-tailing">CDC(Change Data Capture) - Transaction Log Tailing</h3>
<ul>
<li>Outbox 테이블 INSERT, UPDATE 등 변경 이벤트를 CDC 툴을 통해 감지하여 실시간으로 읽고 이벤트 브로커(Kafka, Pulsar 등)에 전송</li>
<li>CDC는 Debezium, Kafka Connect 같은 기술을 활용</li>
</ul>
<h4 id="예시-2">예시</h4>
<p><img src="https://velog.velcdn.com/images/martin-han/post/4cc1c7d6-42ab-42b8-a0fc-50802e78e266/image.png" alt=""></p>
<h4 id="장점-3">장점</h4>
<ul>
<li><strong>실시간성</strong>
거의 실시간에 가까운 빠른 이벤트 전달 보장</li>
<li><strong>낮은 지연시간</strong>
낮은 지연(latency) 및 메시지 처리 속도가 우수</li>
<li><strong>안정성 및 유지보수성</strong>
안정성 높고 복구 자동화, 모니터링 기능 제공 가능</li>
<li><strong>성능</strong>
Outbox 테이블 규모 증가 시 폴링 방식 대비 성능 유지 우수</li>
<li><strong>낮은 결합도</strong>
시스템 간의 결합도를 더욱 낮출 수 있음</li>
</ul>
<h4 id="단점-3">단점</h4>
<ul>
<li><strong>초기 비용및 운영 비용 증가</strong>
CDC 인프라(Debezium, Kafka Connect 등)를 추가로 도입해야 하므로 초기 설정과 운영 복잡성 증가 및 인프라 비용 및 학습 곡선</li>
<li><strong>운영관리 복잡성 증가</strong>
CDC 시스템 장애나 장애 복구, 데이터 스키마 변경 관리(버전 관리) 등의 추가 고려사항 필요</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[DDD 1부 - 2]]></title>
            <link>https://velog.io/@martin-han/DDD-1%EB%B6%80-2</link>
            <guid>https://velog.io/@martin-han/DDD-1%EB%B6%80-2</guid>
            <pubDate>Fri, 17 Jan 2025 15:53:51 GMT</pubDate>
            <description><![CDATA[<p>DDD 짜집기 1부 2장</p>
<h2 id="의사소통과-언어-사용">의사소통과 언어 사용</h2>
<p>모델은 프로젝트에 참여한 사람들의 머릿속에 <strong>축적된 개념을 모아 놓은 것</strong>으로서 도메인에 대한 통찰력을 반영하는 <code>용어</code>와 <code>관계</code>로 표현된다.
이를 표현하는 방법을 UML만으로 한정하지 않고
<strong>모든 의사소통 수단에 스며들게 해야한다.</strong></p>
<p>코드 자체 혹은 테스트을 기반으로 의사소통능력의 향상을 노려볼 수 있다.</p>
<h3 id="ubiquitous-language">UBIQUITOUS LANGUAGE</h3>
<blockquote>
<p><strong>개발자의 언어</strong>와 <strong>도메인 전문가의 언어</strong>를 통합하고 더 나아가 서로 다른 언어로 인한 오해나 외곡을 막기 위해 프로젝트에 참여하는 <strong>모든 구성원이 같은 언어를 사용해야한다.</strong></p>
</blockquote>
<p>소프트웨어 프로젝트에서는 <strong>도메인 전문가와 개발자가 각자의 전문 용어를 사용하면서 서로의 의미를 제대로 이해하지 못하는 경우가 많다</strong>.
도메인 전문가는 자신의 분야에 대한 용어는 잘 알지만 개발자의 전문 용어는 익숙하지 않고, 반대로 개발자들도 도메인 전문가의 용어 의미를 정확히 파악하지 못하는 경우가 발생한다.
이러한 상황에서는 <strong>추상화나 설계가 도메인 전문가에게 잘 전달되지 않아 프로젝트의 품질에 부정적인 영향을 줄 수 있다.</strong></p>
<p>공통 언어가 없는 팀에서는 <strong>개발자들이 도메인 전문가를 위해 자신의 용어를 번역</strong>해야 하고, <strong>도메인 전문가도 개발자들 간이나 다른 전문가들과의 의사소통을 위해 번역</strong>을 해야 한다.
심지어 개발자들 사이에서도 서로 다른 용어를 사용하기 때문에 추가적인 번역이 필요할 때가 있다.
이로 인해 <strong>팀 내 의사소통이 원활하지 않아 코드에도 혼란이 발생</strong>하고, 프로젝트 전반에 걸쳐 사용하는 언어가 분열되면서 심각한 문제로 이어질 수 있다.</p>
<p>예를 들어, <strong>도메인 전문가는 자신의 전문 용어를 사용하지만 기술적인 업무를 맡은 팀원들은 설계 측면에서 도메인에 관한 토론에 적합한 자신들만의 용어를 사용하게 된다.</strong>
이렇게 되면 아무리 많은 회의를 진행해도 <strong>본질적인 문제를 해결하지 못할 수 있다.</strong>
<code>일부 사람들만 사용하는 언어</code>는 모두의 필요를 충족하지 못해 공통 언어가 될 수 없으며, <strong>번역 과정에서 의사소통이 흐려지고 지식 탐구가 제한될 수 있습니다.</strong>
번역에 따른 추가 비용과 오해의 위험도 커져 프로젝트의 성공을 위협할 수 있다.</p>
<p>따라서, 프로젝트의 원활한 진행과 성공적인 결과를 위해서는 팀원 모두가 공통의 언어를 공유하고 사용하는 것이 매우 중요하다.
공통 언어를 통해 의사소통의 효율성을 높이고, 오해를 줄이며, 지식의 깊이를 더할 수 있고, 이는 궁극적으로 코드의 품질 향상과 프로젝트의 성공적인 완수에 큰 도움이 된다.</p>
<p>모델 기반 언어는 개발자 간에 시스템의 산출물뿐만 아니라 <strong>업무와 기능을 기술할 때에도 반드시 사용되어야 한다.</strong>
이러한 모델은 개발자와 도메인 전문가 간의 원활한 의사소통을 가능하게 할 뿐만 아니라, 도메인 전문가들 간의 요구사항, 개발 계획, 기능에 대한 논의에도 공통의 언어를 제공해야 한다.
모델 기반 언어가 팀 내에서 널리 사용될수록 이해도는 더욱 높아지고, 협업이 원활해진다.
초기 모델은 전문 용어의 의미적 풍부함이 부족하고, 개발자가 도메인의 개념을 충분히 반영하지 못할 수 있지만, 팀 전체가 지속적으로 헌신하여 지식을 탐구하고 모델을 개선함으로써 점차 유용한 언어로 발전할 수 있다.</p>
<p><strong><code>UBIQUITOUS LANGUAGE</code>를 꾸준히 사용하다 보면 언어의 취약점이 드러나고, 이는 도메인 모델의 변화로 이어진다.</strong>
이러한 변화는 클래스 다이어그램 수정, 코드 리팩토링, 메서드 및 클래스 이름 변경 등 다양한 형태로 반영되며, 개발자와 도메인 전문가가 함께 협력하여 보다 정확하고 일관된 모델을 구축하는 데 기여한다.</p>
<p>따라서, 팀 내 모든 의사소통과 코드 작성에서 <strong><code>모델 기반 언어</code>를 근간으로 삼아야 한다.</strong>
다이어그램과 문서화는 물론, 일상적인 대화에서도 동일한 언어를 사용함으로써 용어의 혼란을 최소화하고, <strong><code>UBIQUITOUS LANGUAGE</code>의 변화가 곧 모델의 변화를 의미함을 인식해야 한다.</strong></p>
<p><code>도메인 전문가</code>는 부정확하거나 부자연스러운 용어 사용에 대해 <strong>적극적으로 피드백을 제공</strong>하고, <code>개발자</code>는 설계 과정에서 발생하는 <strong>모호함과 불일치를 신속하게 해결해야 한다.</strong></p>
<p>결국, <strong>모델은 단순한 설계 산출물이 아니라 개발자와 도메인 전문가가 함께 협력</strong>하여 모든 프로젝트 단계를 원활하게 수행할 수 있도록 하는 필수적인 요소가 된다.
공통의 모델 기반 언어를 통해 팀은 더욱 효과적으로 소통하고, 고품질의 소프트웨어를 개발할 수 있게된다.</p>
<h3 id="크게-소리내어-모델링하기">크게 소리내어 모델링하기</h3>
<blockquote>
<p>시스템에 관해 이야기를 주고받을 때 모델을 사용하라.
모델의 요소와 상호작용을 이용하고 모델이 허용하는 범위에 개념을 조합하면서 시나리오를 큰 소리로 말해보라.
표현해야 할 것을 더 쉽게 말하는 방법을 찾아낸 다음 그러한 새로운 아이디어를 다이어그램과 코드에 적용하라 .</p>
</blockquote>
<p>인간은 자연스럽게 구어를 사용하지만, 의사소통 시 도메인 모델의 언어를 잘 사용하지 않는다.
요구사항이나 설계 논의에서 흔히 업무 관련 전문용어나 기술적용어가 주로 사용되며, <strong>도메인 모델의 관계나 상호작용을 표현하는 언어는 거의 들을 수 없다.</strong>
이는 의사소통의 효율성을 떨어뜨리고, 도메인 모델의 잠재력을 충분히 활용하지 못하게 된다.
이러한 문제를 인식하고 <strong>도메인 모델 언어 사용을 장려해야한다.</strong></p>
<p>모델을 정제하는 가장 좋은 방법은 가능한 모델 변형을 구성하는 <strong>다양한 요소를 큰 소리로</strong> 말 하면서 말하기를 통해 살펴보는 것이다. 다듬어지지 않은 표현은 쉽게 분간할 수 있다.</p>
<p>모델링할 때 다이어그램을 그려 시각적이고 공간적인 추론 능력을 활용하는 것이 중요한 것처럼, 언어 능력을 활용해 단어와 구절을 깊이 있게 분석하는 것도 필요하다.
이는 체계적인 분석과 설계 과정에서 우리의 분석 역량과 코드와 관련된 직관을 활용하는 것과 동일한 맥락이다.
이러한 사고방식은 서로 보완하며, 효과적인 모델과 설계를 찾는데 모두 필요하다. 
그럼에도 불구하고, <strong>언어를 활용한 실험은 종종 간과되는 경우가 많다.</strong></p>
<p>사업상 서로 다른 언어적 배경을 가진 사람들이 모일 때 공통 언어가 없으면 <strong>혼성어(<code>피진</code>)</strong>를 사용하게 된다.
<code>피진</code>은 화자의 모국어만큼 포괄적이지 않지만, <strong>당면한 문제를 해결하는데 적합하다.</strong>
대화 중 사람들이 <strong>단어 해석과 의미에서 발생하는 차이를 자연스럽게 발견하고 이를 원활하게 조정할 수 있다.</strong>
즉, <strong>언어적 불편함을 찾아내어 부드럽게 해결하는 과정이다.</strong>
이러한 <code>피진</code>의 사용은 다양한 언어적 배경을 가진 팀이 효율적으로 소통할 수 있게한다.</p>
<p>도메인 모델에서 사용하는 <code>UBIQUITOUS LANGUAGE</code>를<code>개발자</code>와 <code>도메인 전문가</code>가 시나리오와 요구사항을 충분히 논의하며 사용하면, <strong>해당 언어를 더욱 유창하게 구사할 수 있고 서로의 미묘한 언어 차이도 이해하게 된다.</strong>
이는 단순히 다이어그램이나 문서로는 얻기 힘든 <strong>구체적인 언어 공유</strong>를 가능하게한다.</p>
<p>하지만 <strong>소프트웨어 프로젝트에 <code>UBIQUITOUS LANGUAGE</code>를 적용하는 것은 쉽지 않은 일이다.</strong>
이를 성공적으로 구현하기 위해서는 <strong>우리의 언어적 재능을 최대한 발휘해야 한다.</strong></p>
<h3 id="한-팀-한-언어">한 팀, 한 언어</h3>
<blockquote>
<p>종종 기술 담당자는 업무 전문가에게 도메인 모델을 보여 줄 필요가 없다고 생각한다. 그들은 이렇게말한다.
<strong>&quot;업무 전문가들에게는 너무 추상적이라서요.&quot;
&quot;업무 전문가들은 객체를 이해하지 못해요.&quot;
&quot;업무 전문가들이 쓰는 용어로 된 요구사항을 만들어야 해요.&quot;</strong>
이는 팀에 두 가지 언어가 존재해야 하는 이유로 들은 이야기 중 일부에 불과하다. 이런 말은 귀담아 듣지 않아도 된다.
물론 설계에는 도메인 전문가와 직접 관련이 없는 기술적 요소들도 포함될 수 있지만, 모델의 핵심은 도메인 전문가의 관심을 끌어야 한다.
<code>&quot;너무 추상적이다&quot;</code>라고 느낀다면, 그 추상화가 제대로 이루어졌는지 어떻게 확인할 수 있는가?
도메인 전문가만큼 도메인을 깊이 이해하고 있는가?
특정 요구사항은 하위 사용자로부터 수집하고 도메인 전문가에게는 더 구체적인 용어가 필요할 수 있지만, 도메인 전문가는 해당 분야에 대해 깊이 있게 사고할 수 있는 능력을 갖추고 있다고 가정해야 한다.
만약 <strong>수준 높은 도메인 전문가조차 모델을 이해하지 못한다면, 그 모델이 뭔가 잘못된 것이다.</strong></p>
</blockquote>
<p>초기 단계에서는 논의에 사용할 모델이 존재하지 않아 <strong>도메인 전문가와 개발자가 함께 새로운 아이디어를 검토하면서 모델을 찾아가는 과정이 시작된다.</strong>
이 과정은 <strong>처음에는 부자연스럽고 완전하지 않을 수 있지만, 점차 정제되어간다.</strong>
새로운 언어가 발전함에 따라 도메인 전문가는 <strong>이를 채택하고 기존의 중요한 문서들을 개정하는 데 특별한 노력을 기울여야 한다.</strong></p>
<p>도메인 전문가가 개발자나 다른 도메인 전문가와 논의할 때 이 언어를 사용하면 <strong>모델에서 요구사항에 맞지 않거나 잘못된 부분을 빠르게 발견할 수 있다.</strong>
또한, 개발자의 도움을 받아 모델 기반 언어의 정밀함 덕분에 도메인 전문가 자신도 자신의 생각 중 모순되거나 모호한 부분을 깨닫는데 도움이 된다.
이를 통해 <strong>보다 정확하고 일관된 도메인 모델을 구축할 수 있다.</strong></p>
<p>개발자와 도메인 전문가는 <strong>시나리오를 토대로 모델 객체를 단계적으로 사용해 보면서 비공식적으로 모델을 시험해볼 수 있다.</strong>
거의 모든 논의에서 개발자와 도메인 전문가가 함께 모델을 검증할 기회가 마련되고 점차 서로의 개념 이해와 정제가 깊어진다.
도메인 전문가는 <strong>모델의 언어를 바탕으로 유스케이스를 작성하고, 인수 테스트를 구체화함으로써 모델을 훨씬 더 직접적으로 다룰 수 있다.</strong></p>
<p>도메인 모델은 통상 <strong>도메인 전문가의 전문용어에서 비롯되지만 더 명료하고 한정된 정의로 정리될 것이다.</strong>
물론 도메인 전문가는 이러한 정의가 해당 분야의 통념에서 벗어나면 이의를 제기해야 한다.
애자일 프로세스에서는 애플리케이션을 <strong>충분히 구체화할 지식을 초반에 갖출 수 있는 경우가 거의 없으므로</strong> 프로젝트가 진행되면서 요구사항 또한 발전한다고 본다.
<strong>정제된 <code>UBIQUITOUS LANGUAGE</code>로 요구사항을 재구성하는 일이 이러한 발전과정의 일부여야 한다.</strong></p>
<p>때로는 여러 언어가 필요할 때도 있지만 <strong>도메인 전문가와 개발자 사이에 언어적 분열이 일어나서는 안된다.</strong></p>
<p><img src="https://velog.velcdn.com/images/martin-han/post/43106757-a394-4167-86f3-08a01f10cb06/image.png" alt=""></p>
<p><code>UBIQUITOUS LANGUAGE</code>가 마련되면 개발자 간의 대화, 도메인 전문가 간의 논의, 코드 자체에 포함된 표현까지 이 모든것이 공유된 도메인 모델에서 비롯된 동일한 언어를 기반으로 한다.</p>
<h3 id="문서와-다이어그램">문서와 다이어그램</h3>
<p><strong>UML 다이어그램</strong>은 객체 간의 관계를 전달하는 데 특히 좋고 상호작용을 보이는 데도 알맞다. 그러나 <strong>해당 객체의 개념적 정의를 전해주지는 못한다.</strong>
회의에서 다이어그램의 밑그림을 그리면서 말로 그러한 의미를 보충하거나 다른 참석자와 대화를 나누는 과정에서 그러한 의미가 나타나곤 한다.</p>
<p>간결하고 형식에 얽매이지 않은 UML 다이어그램은 <strong>논의의 구심점 역할을 할 수 있다.</strong></p>
<p>사람들이 <strong>다양한 사고 실험을 시도해보면서 다이어그램은 변경될 수 있고, 밑그림은 논의의 핵심 영역에 해당하는 말로 표현되는 단어들의 유동성을 어느 정도 띠게 될 것이다.</strong> 어쨌든 UML은 통합 모델링 언어를 나타내는 말이 아니던가.</p>
<p>문제는 사람들이 <strong>UML을 통해서만 전체모델이나 설계를 전달해야 한다고 느낄 때 생긴다.</strong> 
많은 객체 모델 다이어그램은 <strong>지나치게 완전한 동시에 많은 부분이 생략돼 있다.</strong> 객체 모델이 지나 치게 완전해지는 까닭은 사람들이 <strong>앞으로 코딩할 것들을 모조리 모델링 툴에 집어넣어야 한다고 생각하기 때문이다.</strong>
이러한 모든 세부사항이 존재하는 상황에서는 어느 누구도 나무만 보고 숲은 보지 못한다.</p>
<p>객체의 행위(<strong>behavior</strong>)와 제약조건(<strong>constraint</strong>)은 그리기가 수월하지 않다. 객체 상호작용 다이어그램은 설계상 몇 가지 다루기 어려운 부분을 설명할 수도 있지만 <strong>상호작용의 상당수는 그러한 방식으로 표현할 수 없다.</strong>
다이어그램을 만들고 또 그것들을 해석하자면 해야 할 일이 너무 많다.
제약조건과 단언(<strong>assertion</strong>)까지 포함하려면 텍스트를 작은 괄호로 감싸서 다이어그램에 집어넣는 수밖에 없다.</p>
<p>즉, UML 다이어그램은 <strong>모델의 가장 중요한 두 가지 측면을 전달할 수 없는데</strong>, 그것은 바로 <strong>모델이 나타내는 개념의 의미</strong>와 <strong>모델 내 객체의 행위</strong>다.
그렇다고 이때문에 꼭 곤경에 빠진다는 의미는 아니다.</p>
<p>UML은 아주 만족스러운 프로그래밍 언어도 아니다.
네모와 선으로 구성된 다이어그램에는 <strong>맞지 않는다는 규칙 때문에 종종 모델의 가장 중요한 부분을 생략해야만 할 것이다.</strong>
UML을 구현 언어로 사용 한다면 <strong>정돈된 모델을 전달하는 다른 수단이 여전히 필요할 것</strong>이다.</p>
<p><strong>다이어그램은 의사소통과 설명의 수단이며 브레인스토밍을 촉진한다.</strong> 이러한 목적은 <strong>다이어그램이 최소화됐을 때 가장 잘 달성된다.</strong>
<strong>전체 객체 모델을 전부 포괄하는 다이어그램</strong>은 의사소통이나 설명이라는 목적을 달성하지 못한다.</p>
<ul>
<li><strong>읽는 이를 세부사항으로 압도하고 다이어그램의 의미 또한 누락돼 있다.</strong></li>
</ul>
<p>이러한 이유로 저자가 작성하는 다이어그램은 설계를 이해하는 데 필수적인, 객체 모델 중 개념적으로 중요한 부분만을 나타내는 단순화된 다이어그램을 작성한다고 한다.
이 다이어그램들은 간결하고 설명적이며 심지어 핵심을 명확하게 표현하기만 하면 표준에 없는 표기법도 일부 포함하고 있다.
이 다이어그램들은 설계상의 제약조건을 보여주지만 모든 세부사항에 걸친 설계 명세는 아니다. 그것들은 아이디어의 골자를 나타낼 뿐이다. </p>
<p>모델은 다이어그램이 아니라는 점을 항상 명심해야 한다.
다이어그램의 목적은 모델을 전달하고 설명하는 데 있다.
<strong>잘 작성된 자바코드는 UML만큼 표현력이 있다.</strong></p>
<h4 id="글로-쓴-설계-문서">글로 쓴 설계 문서</h4>
<blockquote>
<p>구두에 의한 의사소통은 코드의 정연함과 상세함을 의미적으로 보충하는 역할을 한다. 
그러나 모든 사람을 모델에 연결되게 하는 데 말하기가 결정적인 역할을 한다고 해도 <strong>어떠한 규모의 집단이든 어느 정도는 글로 쓴 문서로 안정과 공유를 꾀할 필요가 있다.</strong>
그러나 팀이 좋은 소프트웨어를 만들어내는데 <strong>실제로 도움 될 문서를 만드는 것은 쉽지 않은일이다.</strong></p>
</blockquote>
<p>문서가 일단 <strong>어떤 변하지 않는 형태를 취하게 되면 종종 프로젝트 흐름과의 연관성을 잃어버리곤 한다.</strong>
즉, 문서가 코드의 발전이나 프로젝트 언어의 발전에 뒤처지는 것이다.</p>
<ul>
<li>문서는 코드와 말을 보완하는 역할을 해야한다.</li>
</ul>
<p>익스트림 프로그래밍은 <strong>여분의 설계 문서를 전혀 사용하지 않고 코드 스스로 별도의 설명이 필요없는 상태를 유지해야 한다는 입장</strong>을 옹호한다.
<strong>어떤 다른 문서들은 거짓말을 하는 경우가 있을지도 모르지만 실행되는 코드는 그렇지 않다.</strong> 실행되는 코드의 행위는 명백한 것이다.
오직 프로그램의 실제 동작하는 영역과 실행 가능한 테스트에만 집중한다.</p>
<p>이처럼 코드가 의사소통 수단의 의미를 지닌 탓에 개발자는 <strong>코드를 깔끔하고 투명하게 유지할 필요성을 느끼게 된다.</strong></p>
<p>그러나 <strong>설계 문서로서의 코드에는 한계가 있다.</strong> 코드를 읽는 이는 코드의 세부사항에 압도될 수 있다.
<strong>코드의 행위에 모호함이 없다고 해서 코드를 이해하기가 쉽다는 것은 아니다.</strong> 그리고 행위 이면에 존재하는 의미는 전달하기 어렵다.
즉, 오직 코드를 통해서만 문서화하는 것은 포괄적인 UML 다이어그램을 사용할 때 일어나는 것과 기본적으로 동일한 문제를 일부 지니고 있다.
물론 팀 내에서 구두에 의한 의사소통이 활발하게 일어난다면 코드에 맥락과 지침을 제시할 수 있지만 그러한 의사소통은 <strong>일시적이고 국지적인 양상을 띤다.</strong> 그리고 개발자만 모델을 이해해야 하는 것은 아니다.</p>
<p><strong>문서는 코드가 이미 잘 하고 있는 것을 하려고 해서는 안 된다.</strong> 코드는 이미 세부사항을 충족한다. <strong>코드는 프로그램의 행위를 정확하게 규정한 명세에 해당한다.</strong></p>
<p>다른 문서들은 의미를 설명하고, 대규모 구조에 통찰력을 주며, 핵심 요소에 집중할 필요가 있다.
<strong>글로 쓴 문서는 코드와 논의를 보완해야 한다.</strong></p>
<ul>
<li><strong>문서는 유효한 상태를 유지하고 최신 내용을 담고 있어야 한다.</strong></li>
</ul>
<p>다이어그램을 <strong>손으로 그리면</strong> 일을 줄이는 것 말고도 딱딱하지 않고 임시적이라는 느낌을 준다는 이점도 있다. 이러한 특성은 <strong>일반적으로 우리가 모델에 관해 생각하는 바도 그러하기 때문에 의사소통에 도움이 된다.</strong>
<strong>팀의 철학에 따라 전체 설계 문서는 벽에 붙여둔 여러 장의 밑그림 만큼이나 간단할 수도 있고, 혹은 상당한 양이 될 수도 있다.</strong></p>
<ul>
<li><p><strong>문서는 프로젝트 활동과 관련을 맺고 있어야 한다.</strong>
이를 판단하는 가장 쉬운 방법은 문서가 <code>UBIQUITOUS LANGUAGE</code>와 상호작용하는지 살펴보는 것이다.
프로젝트에서 쓰는 언어로 작성돼 있는가? 문서가 코드에 포함된 언어로 쓰여 있는가?</p>
</li>
<li><p><strong><code>UBIQUITOUS LANGUAGE</code>와 그것이 어떻게 변화하는지에 주의를 기울여야 한다.</strong>
설계 문서에 설명된 용어가 대화와 코드에 나타나지 않는다면 문서가 본연의 목적을 수행하지 못하고 있는셈이다.
문서가 <code>UBIQUITOUS LANGUAGE</code>에 아무런 영향도 주지 못한다면 뭔가 잘못된 것이다.</p>
</li>
</ul>
<p>문서는 이력으로 안전하게 보관해 둘 수도 있지만 <strong>유효한 상태로 혼란을 일으키고 프로젝트에 악영향을 줄 수도 있다.</strong> 그리고 문서가 <strong>중요한 역할을 수행하지 않는다면</strong> 순수한 의지와 자제력를 발판삼아 문서를 <strong>최신 상태로 유지하는 것은 노력의 낭비 일뿐이다.</strong></p>
<p><code>UBIQUITOUS LANGUAGE</code>를 바탕으로 작성된 요구사항 명세와 같은 다른 문서는 좀더 간결하고 덜 모호해진다.
도메인 모델이 <strong>업무와 가장 직접적인 관련이 있는 지식을 반영하게 되면 애플리케이션의 요구사항은 해당 모델 내에서 시나리오가 되고</strong> <code>UBIQUITOUS LANGUAGE</code>는 MODEL-DRIVEN DESIGN과 직접적으로 연결돼 있다는 점에서 그러한 시나리오를 기술하는 데 사용될 수 있다.</p>
<p>결과적으로 명세는 더욱 간결하게 쓰여질 수 있는데, 이것은 모델 이면에 놓인 업무 지식을 명세에서 전달할 필요가 없기 때문이다.</p>
<p><strong>문서를 최소한으로 유지하고 코드와 대화를 보완하는 데 집중함으로써 문서는 프로젝트와 연관된 상태로 유지할 수 있다.</strong></p>
<h4 id="실행-가능한-기반">실행 가능한 기반</h4>
<p><strong>잘 작성된 코드는 의미 전달에 매우 충실할 수 있지만 코드가 전달하는 메시지가 정확하다는 보장은 없다.</strong>
메서드 이름이 모호하거나 잘못된 방향으로 유도하거나, 또는 메서드 내부와 비교해서 이전 것일 수 있다.</p>
<p>이 같은 불일치를 제거하는 것은 선언적 설계와 같은 접근법에서 주로 강조하는 특징에 해당한다.
이러한 접근법에서는 <strong>프로그램 요소의 목적을 서술하는 것이 곧 프로그램 내의 실제 행위를 결정한다.</strong></p>
<p>현재의 표준 기술을 활용해 코드의 행위, 의도, 메시지를 일치시키려면 <strong>훈련과 설계에 관한 어떤 특정한 사고방식이 필요하다.</strong>
의사소통을 효과적으로 하려면 코드는 요구사항을 작성하는 데 사용한 것과 동일한 언어이자 <strong>개발자가 다른 개발자와 이야기하거나 도메인 전문가와 이야기를 나눌 때 사용하는 것과 동일한 언어에 기반을 둬야한다.</strong></p>
<h3 id="설명을-위한-모델">설명을 위한 모델</h3>
<p>이 책의 요점은 <strong>하나의 모델이 구현, 설계, 의사소통의 기초가 돼야 한다는 것이다.</strong> 이러한 각 목적에 각기 다른 모델을 갖추는 것은 바람직하지 않다.</p>
<p>모델은 도메인을 가르치는 도구로도 아주 유용할 수 있다.
설계를 주도하는 모델은 도메인을 바라보는 하나의 관점에 해당하지만 <strong>도메인의 일반 지식을 전달하기 위해 교육적인 도구로만 사용되어 다른 관점을 견지하는 것을 익히는 데 도움을 줄 수 있다.</strong> 
이러한 목적으로 사람들은 소프트웨어 설계와는 무관한 다른 종류의 모델을 전달하는 그림이나 단어를 활용할 수 있다.</p>
<p>설명을 위한 모델에서는 특정 주제에 맞춰 훨씬 더 전달력이 높은 의사소통 방식을 마음껏 만들어낼 수 있다.
<strong>설명을 위한 모델이 꼭 객체 모델일 필요는 없으며, 오히려 그렇지 않을 때가 일반적으로 가장 좋다.</strong></p>
<p>모델에 대한 다이어그램 + 특정 도메인에 대한 시각적인 자료와 함께 설명을 하면 좋다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DDD 1부 - 1]]></title>
            <link>https://velog.io/@martin-han/DDD-1%EB%B6%80-1</link>
            <guid>https://velog.io/@martin-han/DDD-1%EB%B6%80-1</guid>
            <pubDate>Sun, 08 Dec 2024 08:02:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>에릭에반스 - DDD 책을 읽고 내생각을 첨가해서 짜집기한 글</p>
</blockquote>
<h1 id="동작하는-도메인-모델-만들기">동작하는 도메인 모델 만들기</h1>
<p>모델은 중요한 사실이나 사상의 일부 측면을 <strong>추상화하여 대상을 단순화한 것</strong>이다.
어떠한 사실을 해석한 것으로도 볼 수 있고, <strong>문제해결과  관련된 측면을 추상화</strong>하고 그 밖의 <strong>세부사항에는 주의를 기울이지 않는다.</strong>
(모델링을 할 때 아직 세부사항은 필요가 없다. 사람의 움직임을 그림으로 그린다고 생각해보자
뼈, 근육, 피부조직, 혈관, 혈액 등의 움직임이 필요한가?)</p>
<p>대개 소프트웨어 도메인은 몇 가지 예외적인 경우를 제외하면 컴퓨터와 거의 관련이 없다.
소프트웨어 개발 자체가 도메인이 되는 소스 코드 관리 시스템같이 말이다.</p>
<p>도메인 모델은 단지 도메인 전문가의 머릿속에만 존재하는 지식이 아니라 <strong>해당 지식을 엄격하게
구성하고 선택적으로 추상화한 것이다.</strong></p>
<p>책에서는 <strong>도메인 모델러</strong>를 자신의 경험을 토대로 특유의 방식으로 이야기하고 논지를 펼쳐 나가는 <strong>영화 제작자</strong>와 비유를 했다.
모델의 <strong>유용성에 따라 특정 모델을 선택</strong>하기 때문이다.</p>
<h3 id="도메인-주도-설계에서의-모델의-유용성">도메인 주도 설계에서의 모델의 유용성</h3>
<ol>
<li><code>모델과 핵심 설계는 서로 영향을 주며 구체화된다.</code><ul>
<li>모델을 이해한 바로 <strong>코드를 해석할 수 있다.</strong></li>
</ul>
</li>
<li><code>모델은 모든 팀 구성원이 사용하는 언어의 중추다.</code><ul>
<li>모두 같은 언어를 사용한다면 도메인 전문가와의 의사소통에서 <strong>별도의 번역이 필요하지 않다.</strong></li>
</ul>
</li>
<li><code>모델은 지식의 정수만을 뽑아낸 것이다.</code><ul>
<li>모델에는 우리가 <strong>용어를 선택하고, 개념을 분류하며, 분류한 지식을 서로 연관시키 때 도메인에 관한 우리의 사고방식이 담겨 있다.</strong></li>
</ul>
</li>
</ol>
<h3 id="소프트웨어의-본질">소프트웨어의 본질</h3>
<p>소프트웨어의 본질은 <strong>사용자를 위해 도메인에 관련된 문제를 해결하는 능력</strong>에 있다.
개발자는 그 <strong>문제를 해결하는 전문가</strong>다.</p>
<p>개발자는 업무 지식을 증진하기 위해 도메인 연구에 몰두해야 한다. 그뿐만 아니라 모델링 기법을 연마해서 도메인 설계에 통달해야한다.
기술적인 부분에만 너무 몰두한다면 정작 <strong>문제 해결보다 기술을 우선시하게 되어 본질과는 전혀 다른 결과를 만들 수 있기 때문</strong>이다.</p>
<p>익숙하지 않은 도메인과 복잡성을 두려워하지 않는다면 훨씬 더 가치 있는 개발자가 될 수 있는 성장의 밑거름이 될 것이다.</p>
<h2 id="지식탐구">지식탐구</h2>
<p>개발자는 도메인 전문가에게 <strong>도메인 지식을 배우고 그의 말 속 본질적인 문제영역을 탐색하여 모델링을 해야한다.</strong>
현재 나의 도메인 지식이 맞는지, 설계가 맞는지 <strong>도메인 전문가와 계속 확인 해야한다.</strong>
이때 <strong>개발자의 언어와 도메인 전문가의 언어를 통합</strong>한다면 더욱 원할한 소통이 가능하다.</p>
<p><code>개발자</code>와 <code>도메인 전문가</code>는 서로에게 배우는 자세로 임해야한다.</p>
<h3 id="효과적인-모델링의-요소">효과적인 모델링의 요소</h3>
<p>성공적인 모델링을 위한 활동</p>
<ol>
<li><code>모델과 구현의 연계</code><ul>
<li>초기 모델을 토대로 본질적인 연결 고리를 이어지는 <strong>반복주기 내내 유지</strong></li>
</ul>
</li>
<li><code>모델을 기반으로 하는 언어 정제</code><ul>
<li><strong>이해관계자간 언어의 규약을 설정</strong> (수신호, 봉화 같이)</li>
</ul>
</li>
<li><code>풍부한 지식이 담긴 모델 개발</code><ul>
<li><strong>객체는 행위를 지니고 규칙을 이행한다.</strong></li>
<li>모델은 데이터 스키마가 아니라 다양한 지식을 포함하여 복잡한 문제를 해결한다.</li>
</ul>
</li>
<li><code>모델의 정제</code><ul>
<li>본질에 집중하여 <strong>필요하지 않거나 중요하지 않은 개념은 제거한다.</strong></li>
<li><strong>변경을 두려워하지마라</strong></li>
</ul>
</li>
<li><code>브레인스토밍과 실험</code><ul>
<li>토의 중 나온 아이디어를 <strong>지속적으로 다양한 가능성을 연습,실험,평가</strong></li>
<li>풍부한 지식이 담긴 모델을 발견, 정제하기 위해선 <strong>지속적인 브레인스토밍과 수차례의 실험으로 얻은 창의성이 필요하다.</strong></li>
</ul>
</li>
</ol>
<h3 id="지식-탐구">지식 탐구</h3>
<p><strong>지식 탐구는 도메인 전문가와 개발자가 서로 협업하여 도메인을 도출하는 과정이다.</strong>
이 과정에서 <strong>수많은 모델을 시도,거부,변형하여 모든 세부사항에 들어 맞는 추상적 개념이 나타나면 비로소 성공에 이른다.</strong>
해당 모델의 원재료는 <strong>도메인 전문가의 머릿속</strong>이나 <strong>기존 시스템 사용자</strong>, <strong>비슷한 도메인의 기술팀</strong> 혹은 <strong>이전의 다른 프로젝트에서 얻은 경험</strong>에서 나온다.</p>
<h4 id="지식의-축적">지식의 축적</h4>
<p>과거의 폭포수 개발 방식의 경우
업무 전문가 -&gt; 분석가 -&gt; 프로그래머 에게 지식이 흐르는데
이러한 방법은 <strong>피드백이 잘 이루어질 수 없어 지식이 한 방향으로만 조금씩 흐르고 축적되지 않는다.</strong>
또한 업무 내용은 전달되는 과정에서 전달되어야하는 내용이 누락될 가능성도 크고 <strong>해결해야할 문제 영역을 잘못 이해할 수 있다.</strong></p>
<p>반면 애자일하게 일하지만 <strong>추상화를 하지 않아 지식의 축적이 실패</strong>하기도 한다.</p>
<h4 id="모델의-발전">모델의 발전</h4>
<p>프로그래머가 지속적으로 리팩터링하며 개발한다면 소프트웨어는 말끔한 상태로 유지되겠지만
도메인에 관심이 없다면 <strong>본질을 알지 못한 채 애플리케이션에서 수행해야 할 사항만 습득한다.</strong></p>
<p>모든 구성원이 함께 모델을 면밀히 만들어 나가 상호작용한다면
도메인 모델의 지속적인 정제를 토대로 <strong>개발자는 기능만 기계적으로 만드는 데 머무르지 않고</strong> 자신이 보조하고 있는 업무의 중요 원칙들을 배울 수 있다.</p>
<p>도메인 지식을 기반으로 추상화를 진행해라 그렇지않고 기술적인 측면에서만 접근한다면 <strong>개념은 초보적인 수준에 머무를 것이다.</strong></p>
<p>도메인 전문가의 지속적인 관여로 <strong>모델은 심층적인 업무 지식을 반영하고, 추상화된 개념은 참된 업무 원칙에 해당한다.</strong>
이렇게 만들어진 모델은 <strong>도메인을 이해하는 데 실용적이고 유용하며 쉽게 구현하고 이해하기에 충분할 만큼 엄밀해야 한다.</strong></p>
<h3 id="지속적인-학습">지속적인 학습</h3>
<p>소프트웨어를 작성하기 시작할 때 우리는 <strong>충분히 알지 못한 상태에서 시작</strong>한다.
이런 상태에서 스스로 얼마나 알지 못하는가를 깨닫지 못한다면 <strong>무지로 인해 잘못된 가정</strong>을 하게된다.</p>
<p>전형적인 설계 방법으로는 코드와 문서에서 유용한 형태로 표현되지 못한 지식이 어떠한 이유로든 <strong>구두로 전달하기 어려워지면 지식은 사라지게 된다.</strong></p>
<p>생산성이 매우 뛰어난 팀은 <strong><code>지속적인 학습</code></strong>을 바탕으로 의식적으로 지식을 함양한다.
핵심 팀원에게 축적된 지식으로 그들은 더욱 유능한 지식 탐구자가 된다.</p>
<p>이 다음 작업은 팀 구성원, 도메인 전문가 <strong>모두 똑같은 지식을 얻고 의사소통 체계를 공유</strong>하며, 구현을 거쳐 피드백 고리를 완성하는 일을 효과적으로 수행하는 작업을 <code>지식 탐구 프로세스</code>라는 작업으로 <strong>프로세스화 하는 것</strong>이다.</p>
<h3 id="풍부한-지식이-담긴-설계">풍부한 지식이 담긴 설계</h3>
<p>도메인의 <strong>업무 활동과 규칙</strong>도 도메인에 중요하다.
도메인마다 <strong>전문적인 용어</strong>가 있을탠데 이 용어의 <strong>뜻을 제대로 이해하는 통찰력을 모델에 반영</strong>해야한다.</p>
<p>업무 규칙을 차례로 확인하여 비거나 모순되는 사항을 명확하게 하고, 구체화하며, 조정하거나, 고려해야 할 범위 밖으로 배제하는 것은 소프트웨어 전문가와의 긴밀한 협업하에서 진행되는 지식 탐구를 통해 이뤄진다.
이러한 사항으로는 <strong>업계의 관행</strong> 같은게 있을 수 있다.
(도메인 전문가에게는 <strong>너무 당연</strong>해서 말하는걸 놓칠 수 있기 때문)</p>
<p><strong>정제 과정</strong>을 거쳐 <strong>중요한 것에 집중하고 나머지는 축소, 분리하여 더 명시적</strong>으로 만들어야 한다.</p>
<h3 id="심층-모델">심층 모델</h3>
<p>유용한 모델은 <strong>겉으로 드러나 있는 경우가 거의 없다.</strong>
우리는 요구사항을 더 잘 이해하게 되면서 대체로 <strong>처음에 중요하게 생각했던 모델 요소를 버리거나 관점을 바꾼다.</strong>
이로써 처음에는 나타나기 힘들지만 <strong>문제의 핵심을 관통하는 포착하기 힘든 추상화</strong>가 서서히 나타나기 시작한다.</p>
<p><strong>심층 모델</strong>을 유용하고 명확하게 만들려면 <strong>도메인은 물론 모델링 기법도 정교한 솜씨가 필요하다.</strong>
심층 모델이라 생각했지만 도메인 전문가가 만족스러워하지 않는다면 우리는 도메인 전문가가 <strong>업무를 바라보는 방식을 놓쳤기 때문이다.</strong></p>
<p>지식 탐구는 탐험과도 같아 <strong>어디서 끝날지 아무도 알지 못한다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[권한별 서버 & 리소스별 서버]]></title>
            <link>https://velog.io/@martin-han/%EA%B6%8C%ED%95%9C%EB%B3%84-%EC%84%9C%EB%B2%84-%EB%A6%AC%EC%86%8C%EC%8A%A4%EB%B3%84-%EC%84%9C%EB%B2%84</link>
            <guid>https://velog.io/@martin-han/%EA%B6%8C%ED%95%9C%EB%B3%84-%EC%84%9C%EB%B2%84-%EB%A6%AC%EC%86%8C%EC%8A%A4%EB%B3%84-%EC%84%9C%EB%B2%84</guid>
            <pubDate>Sat, 16 Nov 2024 08:29:42 GMT</pubDate>
            <description><![CDATA[<p>서버를 권한별로 만드는 것과 리소스별로 만드는 것 어느 방법이 좋을까?
각자만의 장단점이 있고 상황에 따라 다르겠지만
개인적으로 보통의 경우 리소스별로 분리하는게 좋지 않을까 싶다.</p>
<p>그래서 장단점을 대략적으로나마 정리해 보았다.</p>
<h1 id="권한별">권한별</h1>
<p>권한별로 서버를 만든다.
[USER, ADMIN] 이 있다고하면 UserServer, AdminServer가 나올 것이다.(API 서버)</p>
<h2 id="장점">장점</h2>
<ul>
<li>권한별로 행위가 확실하게 나뉜다.</li>
<li>유저의 트래픽이 늘어나면 유저서버만 스케일아웃 방식으로 서버를 증설 가능하다.</li>
<li>물리적인 격리로 보안에 용이하다.</li>
</ul>
<h2 id="단점">단점</h2>
<ul>
<li>의도치 않은 영향이 있다.
각 서버에서 공유 클래스가 있는 경우 한 쪽에서만 수정될 경우 문제가 발생할 수 있다.</li>
<li>지속적인 확인이 필요하다.
비지니스가 독립적이지 못하기 때문에 항상 모든 권한의 서버를 함께 검증 해야한다.</li>
<li>유저서버중 특정 비지니스만 트래픽이 폭발적으로 늘어난다해도 유저서버 자체를 늘려야한다.</li>
<li>비지니스 로직이 서버별로 파편화 된다.</li>
</ul>
<h1 id="리소스별">리소스별</h1>
<p>리소스 즉 비지니스 별로 서버를 만든다.
한 개의 리소스만이 대상이 아닌 상황에 따라 리소스의 묶음이 하나에 서버가 될 수 있다.
[Account, Notice, Vote, Gallery] 가 있다고하면 각각의 리소스 서버가 될 수도 있고, Account Server, [Notice, Vote, Gallery] Server 가 될 수도 있다.</p>
<h2 id="장점-1">장점</h2>
<ul>
<li>확장성이 좋다.
특정 리소스에 대한 트래픽이 증가할 경우 해당 리소스 서버를 스케일아웃한다.
(리소스 묶음의 경우 분리도 가능)</li>
<li>비지니스 로직의 응집도가 높아진다.
특정 리소스의 비지니스 로직이 해당 서버에만 존재할 것 이기에</li>
<li>유지보수성이 높아진다.
응집도가 높아지면 자연스럽게 유지보수성도 올라갈 것이다.</li>
<li>민감한 리소스의 경우 보안이 강화된 서버에 격리하여 관리가 가능하다.</li>
</ul>
<h2 id="단점-1">단점</h2>
<ul>
<li>비용 증가
구성하기에 따라서 권한별 서버보다 서버거 많아지기 때문에 비용이 증가한다.</li>
<li>복잡성 증가
서버간 통신이 많아질 수도 있기 때문에 그에 따른 관리가 필요</li>
</ul>
<h1 id="마치며">마치며</h1>
<p>권한별 서버만의 장점은 아직 잘 모르겠다.
리소스별 서버로 구성한 다음 게이트웨이 서버를 구축해서 해당 게이트웨이를 권한별로 관리하는 것도 좋아보이는 방법이기 때문에
권한별로 각 리소스를 가진 서버를 만드는건 리스크가 많아 보인다.</p>
<p>물론 비지니스의 수정이 절대 없다고 한다면 권한별 서버가 나을거라 생각한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[애자일 방법론 with 스크럼]]></title>
            <link>https://velog.io/@martin-han/%EC%95%A0%EC%9E%90%EC%9D%BC-%EB%B0%A9%EB%B2%95%EB%A1%A0-with-%EC%8A%A4%ED%81%AC%EB%9F%BC</link>
            <guid>https://velog.io/@martin-han/%EC%95%A0%EC%9E%90%EC%9D%BC-%EB%B0%A9%EB%B2%95%EB%A1%A0-with-%EC%8A%A4%ED%81%AC%EB%9F%BC</guid>
            <pubDate>Mon, 04 Nov 2024 16:08:41 GMT</pubDate>
            <description><![CDATA[<p>이번에 팀 동료가 <a href="https://product.kyobobook.co.kr/detail/S000001033102">클린애자일</a>을 추천해줘서 읽어보게 되었다.</p>
<p>나는 이전까지 애자일이라는게
그저 작업단위를 기능별로 쪼개서
<code>설계-개발-테스트-수정-설계-...</code> 와 같이 계속 순환되듯이
개발 생명주기를 짧게 가져가는 것이라 생각했다.</p>
<p>하지만 이 책을 읽고 (내가 생각하기에)애자일이 뭔지 조금은 알게 되었다.
특히 1,2장은 너무 웃겨서 재미있게 읽었다.</p>
<p>아직 읽어보지 않았다면 일단 1장이라도 읽어보기 바란다.</p>
<p>결과물은 거의 복붙 수준이지만 다 읽고 스크럼보드 정책을 구상해보았다.</p>
<blockquote>
<p>잘못된 부분이 있다면 댓글로 남겨주세요!</p>
</blockquote>
<h1 id="스크럼">스크럼</h1>
<p>애자일 책 한권만 읽고 구성해본 스크럼보드 정책
<img src="https://velog.velcdn.com/images/martin-han/post/d8419859-a971-40b6-ade4-5646c693e4db/image.png" width=400/><em>[이미지 출처]tvn 신서유기</em></p>
<blockquote>
<p>모든 결정사항은 상황에 따라 유동적으로 변화가 가능하다.</p>
</blockquote>
<h2 id="이슈">이슈</h2>
<h3 id="유형">유형</h3>
<p>이슈의 유형은 크게 세 종류가 있다.</p>
<ul>
<li><strong>에픽(스웜레인)</strong></li>
<li><strong>스토리</strong></li>
<li><strong>작업(태스크)</strong></li>
</ul>
<h4 id="에픽">에픽</h4>
<ul>
<li>큰 작업 단위로 <strong><code>스토리</code>의 묶음</strong></li>
</ul>
<h4 id="스토리">스토리</h4>
<ul>
<li>기능 요구사항, 이슈 등 작업의 기본 단위</li>
<li>스토리의 크기가 너무 크다면 <strong><code>에픽</code>으로 승격</strong>시킨 뒤 여러개의 스토리로 분할한다.</li>
</ul>
<h4 id="작업태스크">작업(태스크)</h4>
<ul>
<li>스토리의 구체적인 작업 단위</li>
</ul>
<h3 id="작성방법">작성방법</h3>
<ul>
<li><strong>누구나</strong> 필요시 에픽, 스토리 작성 가능</li>
<li>기본적으로 <strong>백로그에 작성</strong>(상황에 맞게)</li>
<li>전달되어야 하는 정보는 최대한 자세하면서 간결하게 작성</li>
<li>중요도, 기한(옵션) 입력</li>
</ul>
<h4 id="기한">기한</h4>
<ul>
<li>해당 에픽,스토리의 기한</li>
<li><strong>기한이 있는 경우에만 추가</strong></li>
</ul>
<h4 id="중요도-15">중요도 (1~5)</h4>
<p>에픽, 스토리의 <strong>우선순위</strong></p>
<h5 id="5-매우-높음">5. 매우 높음</h5>
<ul>
<li><strong>현재 스프린트에서</strong> 바로 작업 필요</li>
<li>즉시 현재 진행중인 스프린트 계획에 추가</li>
<li>미룰 수 있는 다른 이슈가 있다면 백로그로 이동</li>
</ul>
<h5 id="4-높음">4. 높음</h5>
<ul>
<li><strong>다음 스프린트에</strong> 작업 필요</li>
</ul>
<h5 id="3-보통">3. 보통</h5>
<ul>
<li><strong>특정 기간까지</strong> 작업이 필요</li>
<li>기간이 임박했다면 <strong>4단계+로 취급</strong></li>
</ul>
<h5 id="2-낮음">2. 낮음</h5>
<ul>
<li><strong>언젠간 해야하는</strong> 작업</li>
</ul>
<h5 id="1-매우-낮음">1. 매우 낮음</h5>
<ul>
<li><strong>하면 좋은</strong> 작업(회식, 1on1, 독서 등등)</li>
</ul>
<h2 id="스프린트">스프린트</h2>
<ul>
<li><strong>시작 스프린트</strong> - 2주 간격으로 진행</li>
<li><strong>중간 스프린트</strong> - 스프린트 중간에 진행
  현재 스프린트에 이상이 없는지 체크<ul>
<li>스프린트 절반 기준 이슈의 반 이상 끝낸 경우 - <code>스토리 추가</code></li>
<li>스프린트 절반 기준 이슈의 반도 못 끝낸 경우 - <code>스토리 차감</code></li>
</ul>
</li>
<li><strong>데일리</strong> - 매일
  간단한 이슈 공유</li>
</ul>
<h3 id="스프린트-진행">스프린트 진행</h3>
<ol>
<li>아침에 모여서 회의</li>
<li>이전 스프린트 카드 정리 및 <strong>스토리 포인트 통계</strong></li>
<li>이전 스프린트 <strong>간단 회고</strong></li>
<li><strong>백로그 정리</strong> 및 새로 추가된 이슈에 <strong>스토리포인트 부여</strong></li>
<li>현재 스프린트에 작업할 <strong>스토리 선별</strong></li>
<li>작업자의 스토리 선택(필요시 짝으로 진행)</li>
</ol>
<h4 id="스토리-포인트-16">스토리 포인트 (1~6)</h4>
<p>모두 함께 모여서 점수 부여</p>
<ol>
<li>먼저 <strong>4점 이상</strong>이라고 생각하는 사람이 거수
 이유 피력</li>
<li><strong>3점 이하</strong>라고 생각하는 사람 거수
 이유 피력</li>
<li>논의 후 적정점수 부여</li>
<li>점수를 매길 수 없는 경우</li>
</ol>
<ul>
<li><strong>작업이 너무 큰 경우</strong>
  에픽으로 격상 후 분리</li>
<li><strong>작업이 너무 작은 경우</strong>
  작은 작업들을 묶어서 관리</li>
</ul>
<h4 id="선별">선별</h4>
<ul>
<li><strong>팀의 평균 스토리 포인트 만큼</strong> 선택
  상황에 따라 초과될 수 있음
  (첫 스프린트에는 데이터가 없기 때문에 대략적으로 진행)</li>
<li>우선순위는 <strong>중요도를 기준</strong></li>
</ul>
<h4 id="작업-선택">작업 선택</h4>
<ul>
<li>작업자가 직접 선택
  다른 사람이 선택했다고 끝이 아닌 참여하고 싶은 스토리의 경우 짝을 요청하여
  페어를 맺고 작업 진행</li>
<li>다른 사람이 선택한 작업이 업무 효율상 자신이 하는게 효율적이란 판단이 들 경우 논의 후 자신에게 할당 혹은 짝을 맺어 진행</li>
</ul>
<h4 id="짝">짝</h4>
<ul>
<li>작업을 함께 진행할 페어</li>
<li>함께하거나 도움을 줄 수도 도움을 받을 수도 있는 관계</li>
<li>짝으로 직무의 경계를 느슨하게 하기 좋다고 생각함</li>
</ul>
<h3 id="작업-진행">작업 진행</h3>
<ul>
<li>이슈에 특이사항, 변경사항, 작업내용, 이슈 등을 기록</li>
<li>혹시나를 대비하여 기록</li>
</ul>
<h1 id="끝내며">끝내며</h1>
<p>애자일이 마음에 들어 책을 다 읽을 때 쯤 부터 조금씩 메모장에 끄적이고 있었다.
이 책을 추천해준 동료에게 너무 감사한 마음이다.</p>
<p>애자일이라고해서 월등히 좋거나 뭐든 해내는건 아니지만
책에 나오는 내용들을 실천한다면 지금보다 더 좋은 코드, 더 좋은 프로세스
나아가 더 좋은 개발자가 될 수 있을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LLM활용 방향성]]></title>
            <link>https://velog.io/@martin-han/LLM%ED%99%9C%EC%9A%A9-%EB%B0%A9%ED%96%A5%EC%84%B1</link>
            <guid>https://velog.io/@martin-han/LLM%ED%99%9C%EC%9A%A9-%EB%B0%A9%ED%96%A5%EC%84%B1</guid>
            <pubDate>Thu, 31 Oct 2024 17:20:02 GMT</pubDate>
            <description><![CDATA[<p>동료에게 LLM을 어떻게 활용하면 좋을지에 대한 질문을 받고
대략적인 생각의 방향을 제시할 만한 간단한 예제 두 가지를 생각해보았다.</p>
<blockquote>
<p>이해를 돕기위해 모두 한국어로 작성</p>
</blockquote>
<h1 id="오토크루즈컨트롤">오토크루즈컨트롤</h1>
<p>&quot;공감이 잘 갈만한 일상생활에서의 유용한 기능을 대신한다면 어떤게 있을까?&quot;를 생각함</p>
<p>만약 자동차의 오토크루즈컨트롤 기능을 LLM으로 따라해본다면?</p>
<h2 id="question">Question</h2>
<pre><code>너는 자동차의 오토크루즈컨트롤 시스템이야 
내가 &#39;현재 시속&#39;,&#39;상황&#39;을 주면 너는 상황에 맞게 다음 데이터를 알려줘 

브레이크 - 감속해야 하는 경우 어느 정도로 브레이크를 밟아야하는지 (0~10) 
엑셀 - 속도를 올려야 하는 경우 어느 정도로 엑셀을 밟아야하는지(0~10) 
적정속도 - 현재 상황에 맞는 적정 속도가 몇 km 인지 

현재시속: 120km 
상황: 전방 100m앞 차량 내 차선으로 차선변경 


응답 예시(JSON): 
{
 &quot;적정속도&quot; : 50, 
&quot;브레이크&quot; : 0,
 &quot;엑셀&quot;: 10 
}</code></pre><p>매번 메세지에 프롬프트를 넣어도 되겠지만 &#39;현재시속&#39;, &#39;상황&#39;을 제외한 내용을 시스템 프롬포트에 추가하면 모양이 더 예쁨</p>
<h2 id="answer">Answer</h2>
<pre><code>{
  &quot;적정속도&quot;: 80,
  &quot;브레이크&quot;: 4,
  &quot;엑셀&quot;: 0
}</code></pre><p><img src="https://velog.velcdn.com/images/martin-han/post/7030cc95-1095-4a79-bf18-247a741ad33a/image.png" alt=""></p>
<h1 id="ai에이전트의-동작">AI에이전트의 동작</h1>
<p>대략적으로 AI에이전트의 동작 원리는 다음과 같다고 생각한다.</p>
<h3 id="question-1">Question</h3>
<pre><code>다음 질문에 필요한 도구와 그의 행동 입력을 구하시오. 
&#39;관찰&#39;에 정보가 있다면 해당 내용을 참고하여 도구를 제외
다음 도구에 액세스할 수 있습니다:
[뉴스검색, 날씨검색, 지도검색, 주식검색]

행동 입력 - 행동에 대한 입력
관찰 - 도구를 사용한 결과

질문: 오늘 콜롬비아 날씨는 어때? 날씨에 따른 스타벅스 주식의 주가 변동도 알고싶어
관찰: []

응답 예시(JSON):
[
    {
        &quot;도구&quot;: &quot;도구이름&quot;,
        &quot;행동 입력&quot;: &quot;파라미터&quot;
    }
]</code></pre><h3 id="answer-1">Answer</h3>
<pre><code>[
    {
        &quot;도구&quot;: &quot;날씨검색&quot;,
        &quot;행동 입력&quot;: &quot;콜롬비아의 오늘 날씨&quot;
    },
    {
        &quot;도구&quot;: &quot;주식검색&quot;,
        &quot;행동 입력&quot;: &quot;스타벅스 주식의 현재 가격과 변동 상황&quot;
    }
]</code></pre><p>특정 질문과 도구목록을 LLM에게 제공하여
답변에 도움이 될 도구 목록을 추론한 뒤
해당 도구를 실행하여 관찰에 그 결과를 추가하여
최종질의 진행</p>
<h2 id="단계별로-진행한다면">단계별로 진행한다면</h2>
<h3 id="question-2">Question</h3>
<pre><code>다음 질문에 필요한 도구와 그의 행동 입력을 구하시오. 
&#39;관찰&#39;에 정보가 있다면 해당 내용을 참고하여 도구를 제외
다음 도구에 액세스할 수 있습니다:
[뉴스검색, 날씨검색, 지도검색, 주식검색]

행동 입력 - 행동에 대한 입력
관찰 - 도구를 사용한 결과

질문: 오늘 콜롬비아 날씨는 어때? 날씨에 따른 스타벅스 주식의 주가 변동도 알고싶어
관찰: [현재 콜롬비아의 날씨는 아주 화창합니다.]

응답 예시(JSON):
[
    {
        &quot;도구&quot;: &quot;도구이름&quot;,
        &quot;행동 입력&quot;: &quot;파라미터&quot;
    }
]</code></pre><h3 id="answer-2">Answer</h3>
<pre><code>[
    {
        &quot;도구&quot;: &quot;주식검색&quot;,
        &quot;행동 입력&quot;: &quot;스타벅스 주식의 오늘 주가&quot;
    }
]</code></pre><p>와 같이 단계별 진행도 가능할 것 같다.</p>
<h1 id="결론">결론</h1>
<p>사실 유튜브 영상 하나 보는게 더 이해하기 쉬울것이다.
그냥 질문을 받고 생각해 봤던 내용을 적어 본 것이고 아주아주 약식이다.</p>
<p>LLM을 활용하려면 일단 LLM이 잘하는게 무엇인지에 대한 파악과
자신이 원하는 결과에 도달하기 위한 과정을 생각하는게 중요한 것 같다.</p>
<p>어쨌든 LLM에 질문시 최종적으로는 답변하기 위한 보조 정보와 같이 프롬포트에 담아
결과를 도출한다는것만 알고 그 외는 응용의 영역이라고 생각한다.</p>
<p>만약 아직 잘 모르겠다면!
당장 Langchain 문서로 가보는걸 추천한다!
아주 재미있기 때문이다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[n8n + ollama + chatGPT 사용해보기]]></title>
            <link>https://velog.io/@martin-han/n8n-ollama-chatGPT-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@martin-han/n8n-ollama-chatGPT-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 01 Sep 2024 16:25:31 GMT</pubDate>
            <description><![CDATA[<p><a href="https://youtu.be/ZUwWpNEu8-k?si=4hL06VSYq-TaInsX">노코드캣님의 n8n 영상</a>을 보고 따라해보았다.</p>
<h1 id="n8n이란">n8n이란?</h1>
<p>n8n은 강력하고 유연한 워크플로우 자동화 도구다. </p>
<ul>
<li>노드 기반 워크플로우 자동화 도구</li>
<li>다양한 앱과 서비스를 API를 통해 연결하고 데이터를 조작가능</li>
<li>무료로도 사용 가능한 오픈 소스 솔루션</li>
</ul>
<h1 id="설치하기">설치하기</h1>
<blockquote>
<p>준비물 : <a href="https://www.docker.com/">Docker</a>, <a href="https://velog.io/@martin-han/Meta-Llama3.1-%EB%A1%9C%EC%BB%AC%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0">Ollama</a></p>
</blockquote>
<h2 id="git-clone">git clone</h2>
<p><a href="https://github.com/n8n-io/self-hosted-ai-starter-kit">n8n ai starter kit github</a>
에가서 자신의 환경에 맞는 방식으로 <code>clone</code>을 받는다.</p>
<pre><code>$ git clone https://github.com/n8n-io/self-hosted-ai-starter-kit.git</code></pre><h2 id="docker-composeyml-수정">docker-compose.yml 수정</h2>
<p>ollama까지 한번에 컨테이너에 올리는게 아닌 로컬의 ollama를 사용할 것이기 때문에 <code>docker-compse.yml</code>를 다음과 같이 수정한다.
(ollama도 한번에 하려면 그대로 해도 무관하다.)</p>
<pre><code>volumes:
  n8n_storage:
  postgres_storage:
  ollama_storage:
  qdrant_storage:

networks:
  demo:

x-n8n: &amp;service-n8n
  image: n8nio/n8n:latest
  networks: [&#39;demo&#39;]
  environment:
    - DB_TYPE=postgresdb
    - DB_POSTGRESDB_HOST=postgres
    - DB_POSTGRESDB_USER=${POSTGRES_USER}
    - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
    - N8N_DIAGNOSTICS_ENABLED=false
    - N8N_PERSONALIZATION_ENABLED=false
    - N8N_ENCRYPTION_KEY
    - N8N_USER_MANAGEMENT_JWT_SECRET
  links:
    - postgres

services:
  postgres:
    image: postgres:16-alpine
    networks: [&#39;demo&#39;]
    restart: unless-stopped
    environment:
      - POSTGRES_USER
      - POSTGRES_PASSWORD
      - POSTGRES_DB
    volumes:
      - postgres_storage:/var/lib/postgresql/data
    healthcheck:
      test: [&#39;CMD-SHELL&#39;, &#39;pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}&#39;]
      interval: 5s
      timeout: 5s
      retries: 10

  n8n-import:
    &lt;&lt;: *service-n8n
    container_name: n8n-import
    entrypoint: /bin/sh
    command:
      - &quot;-c&quot;
      - &quot;n8n import:credentials --separate --input=/backup/credentials &amp;&amp; n8n import:workflow --separate --input=/backup/workflows&quot;
    volumes:
      - ./n8n/backup:/backup
    depends_on:
      postgres:
        condition: service_healthy

  n8n:
    &lt;&lt;: *service-n8n
    container_name: n8n
    extra_hosts:
      - &quot;host.docker.internal:host-gateway&quot;
    restart: unless-stopped
    ports:
      - 5678:5678
    volumes:
      - n8n_storage:/home/node/.n8n
      - ./n8n/backup:/backup
      - ./shared:/data/shared
    depends_on:
      postgres:
        condition: service_healthy
      n8n-import:
        condition: service_completed_successfully

  qdrant:
    image: qdrant/qdrant
    container_name: qdrant
    networks: [&#39;demo&#39;]
    restart: unless-stopped
    ports:
      - 6333:6333
    volumes:
      - qdrant_storage:/qdrant/storage</code></pre><p>ollama 컨테이너에 대한 내용을 제거하고 컨테이너가 host에 접근할 수 있게 했다.</p>
<h1 id="실행하기">실행하기</h1>
<h2 id="docker-실행">docker 실행</h2>
<p>docker가 설치되어있지만 <code>docker compose</code> 명령어가 없다면 docker 버전을 확인해보자</p>
<pre><code>$ docker compose up -d</code></pre><h2 id="로컬-n8n-접속">로컬 n8n 접속</h2>
<p><a href="http://localhost:5678">http://localhost:5678</a></p>
<p>접속 후 대충 사용할 계정을 생성하고 로그인(귀찮게 비밀번호 검증도 있음)</p>
<h1 id="유튜브-따라한-결과물">유튜브 따라한 결과물</h1>
<p><img src="https://velog.velcdn.com/images/martin-han/post/fd4a09c9-7eca-4db2-b46c-87f16c733e35/image.png" alt=""></p>
<pre><code>$ curl http://localhost:5678/webhook-test/youtube\?url\=https://www.youtube.com/watch\?v\=AmwEIt0vhxA</code></pre><p>요청을 보내면 url의 영상을 읽어서 요약/관련 링크를 최대 3개 알려준다.</p>
<p>응답은 다음과 같다.</p>
<pre><code>{
  &quot;response&quot;: {
    &quot;text&quot;: &quot;&#39;팩토리 패턴&#39;(Factory Pattern)란 프로그래밍에서 특정 오브젝트를 생성하는 공장 클래스를 의미하며, 클라이언트가 직접 오브젝트의 복잡한 생성 과정을 관리하지 않고 팩토리가 이를 관리하는 방식입니다. 이 패턴은 고양이와 강아지와 같은 오브젝트를 클라이언트에게 제공할 때 유용합니다.&quot;
  },
  &quot;output&quot;: [
    {
      &quot;title&quot;: &quot;Factory Method Pattern – Design Patterns (ep 4)&quot;,
      &quot;link&quot;: &quot;https://www.youtube.com/watch?v=EcFVTgRHJLM&quot;
    },
    {
      &quot;title&quot;: &quot;Factory Method Design Pattern | C#&quot;,
      &quot;link&quot;: &quot;https://www.youtube.com/watch?v=5RrR6MaimT0&quot;
    },
    {
      &quot;title&quot;: &quot;Factory Design Pattern in Java Explained in 3 Minutes&quot;,
      &quot;link&quot;: &quot;https://www.youtube.com/watch?v=a7U4fj_kV1U&quot;
    }
  ]
}</code></pre><h2 id="로컬-ollama-주의사항">로컬 ollama 주의사항</h2>
<p>로컬의 ollama 모델을 사용하려면 docker-compose에 설정해준 <code>host.docker.internal</code>에 요청을 보내게 해야한다.
이렇게해야 <code>n8n 컨테이너</code>가 <strong>호스트에서 실행중인 ollama</strong>에 요청을 보낼 수 있다.
<img src="https://velog.velcdn.com/images/martin-han/post/188da373-89ec-4a39-beec-9b80536896e6/image.png" alt=""></p>
<h3 id="로컬-모델의-문제">로컬 모델의 문제</h3>
<p>테스트를 하다가 두 가지 문제가 발생했다.</p>
<ul>
<li><strong>하드웨어 성능 문제</strong> - 내 경우 M1 맥북 프로라서 로컬 모델의 처리 속도가 좀 느렸다.</li>
<li><strong>모델 성능 문제</strong> - <code>llama3.1 8B</code>모델을 사용했는대 대규모 모델에 비해 파라미터 수가 작다. 그래서 한번에 처리가능한 토큰수가 현저히 낮다.
즉, <strong>메모리 한계</strong>로 인한 <code>장기 의존성 문제(망각)</code>가 발생하여 너무 긴 컨텍스트의 경우 원하는 결과를 얻지 못했다.</li>
</ul>
<p>위 문제의 보완하기 위해 일부는 <strong>OpenAI API</strong>를 사용했다. </p>
<h2 id="webhook을-활용한-api">webhook을 활용한 api</h2>
<p>영상을 따라하고 이걸 API 처럼 사용하고 싶어서 webhook 노드를 적용해봤다.</p>
<h3 id="webhook-트리거">webhook 트리거</h3>
<p>url에 유튜브영상 주소를 보내면 후속 작업을 실행 후 응답을 준다.
<img width=300 src="https://velog.velcdn.com/images/martin-han/post/aff760ae-6dfd-43e0-87c7-911c4e6e32e2/image.png"></p>
<h3 id="webhook-response">webhook response</h3>
<p>Response Body를 다르게 작성하고 싶었지만 잘 안돼서 통짜로 응답을 보내게 했다.
<img width=600 src="https://velog.velcdn.com/images/martin-han/post/2838dc3c-3d52-4afd-9eeb-23b35e9c6a77/image.png"></p>
<p>이외는 영상의 내용과 동일하다.</p>
<h1 id="마치며">마치며</h1>
<p>지금까지 AI를 어떻게 활용하면 좋을지를 잘 몰랐는대
이번에 방향성을 알게된 것 같다.
AI 외에도 n8n이란 멋진 서비스를 알게돼서 좋았다.
온보딩 자동화, 프로세스의 시각화 등등</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OpenWebUI 사용해보기]]></title>
            <link>https://velog.io/@martin-han/OpenWebUI-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@martin-han/OpenWebUI-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Fri, 30 Aug 2024 16:24:04 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>주의 - OpenWebUI에 더 자세하고 친절하게 설명되어있음</p>
</blockquote>
<p>지금까지는 로컬에서 Python의 Gradio나 대충 만든 React AI 채팅으로 돌려보다가 OpenWebUI를 알게 됐다.
나온 지는 그래도 좀 됐지만 이제라도 알게 된 게 다행이다.</p>
<h1 id="openwebui">OpenWebUI</h1>
<p>Open WebUI는 Ollama를 위해 특별히 설계된 웹 기반 사용자 인터페이스이다.
원래 이름은 OllamaWebUI였던 것 같다.
그러나 오픈소스로 발전하면서 Ollama뿐만 아니라 여러 오픈소스 LLM도 사용 가능하게 되었다.
Ollama에 LLM을 넣어서 사용하는 것뿐만 아니라 OpenAI 호환 API를 사용하는 다른 LLM도 지원한다고 한다.</p>
<p><a href="https://docs.openwebui.com/">OpenWebUI</a>
<a href="https://github.com/open-webui/open-webui">OpenWebUI github</a></p>
<h2 id="실행시켜보기">실행시켜보기</h2>
<p>사실 위 링크에 설명이 너무나도 쉽게 되어있어서 따로 설명할게 없다.
나는 로컬에 Ollama가 설치되어있기 때문에 docker로 OpenWebUI만 실행해서 연결할 생각이다.</p>
<h3 id="docker로-실행">Docker로 실행</h3>
<pre><code>docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway --name open-webui ghcr.io/open-webui/open-webui:main</code></pre><p><code>--add-host=host.docker.internal:host-gateway</code></p>
<ul>
<li>컨테이너에서 호스트에서 실행중인 Ollama에 접근하기 위한 옵션
<code>-v open-webui:/app/backend/data</code></li>
<li>컨테이너의 데이터를 호스트에 저장/공유 하는 옵션</li>
<li>만약 현재 실행된 컨테이너의 데이터를 컨테이너가 제거되어도 사용하고 싶다면 사용권장<h2 id="사용해보기">사용해보기</h2>
실행시킨 포트로 접근하기<h3 id="로그인">로그인</h3>
</li>
</ul>
<img src="https://velog.velcdn.com/images/martin-han/post/fcbecb21-d69f-4f15-9756-783da1afa082/image.png" width=300>
기본 계정은 따로 제공하지는 않는것 같다.

<h3 id="어드민-계정-생성">어드민 계정 생성</h3>
<img src="https://velog.velcdn.com/images/martin-han/post/21d07171-45b1-496b-8ba5-ed70e3e00fed/image.png" width=300>
계정정보는 외부로 나가지 않는다고 한다.

<p>컨테이너가 실행되고 최초로 가입하는 계정이 Admin 계정이고 그 뒤로 가입하는 계정은 기본적으로 Admin계정의 허가가 필요하다.</p>
<p>우선 어드민 계정으로 사용할 계정을 생성하자</p>
<h3 id="모델-선택-및-채팅">모델 선택 및 채팅</h3>
<p><img src="https://velog.velcdn.com/images/martin-han/post/85634bb7-a965-4520-9dad-f4fb5eccc4cd/image.png" alt="">
굉장히 익숙한 UI이다.</p>
<p>왼쪽 상단에서 Ollama에 있는 모델을 선택해서 그냥 사용하면 된다.
모델을 두개 이상 선택도 가능하다.(리소스를 분산하는 만큼 모델 수 만큼 느려진다.)</p>
<h3 id="채팅-옵션">채팅 옵션</h3>
<img src="https://velog.velcdn.com/images/martin-han/post/d61e6519-6e23-4552-95ab-5f0a80872fcd/image.png" width=300>
우측 설정 아이콘을 열면 채팅의 미세옵션을 설정할 수 있다.

<h4 id="valves">Valves</h4>
<p>Tool이나 Function을 선택할 수 있다.(따로 추가해서 사용)</p>
<h4 id="ststem-prompt">Ststem Prompt</h4>
<p>시스템프롬프트를 설정할 수 있다.</p>
<h4 id="그외-미세설정이-가능하다">그외 미세설정이 가능하다.</h4>
<h3 id="워크스페이스">워크스페이스</h3>
<p><img src="https://velog.velcdn.com/images/martin-han/post/9cc9bc31-42ee-4bb0-a51c-ea8e56194501/image.png" alt=""></p>
<p>모델이나 도구 등을 import할 수 있다.</p>
<p><a href="https://openwebui.com/#open-webui-community">OpenWebUICommunity</a>에 가보면 다양한 정보가 많다.</p>
<h3 id="어드민-패널">어드민 패널</h3>
<p><img src="https://velog.velcdn.com/images/martin-han/post/60f102b2-3d82-429a-ac96-bc6105af9278/image.png" alt=""></p>
<p>여러 설정이 가능하다.
모델을 추가, 문서 설정, RAG 설정 등</p>
<h1 id="마치며">마치며</h1>
<p>이 글은 정리도 정보성 글도 아닌 그냥 재밌는걸 발견해서 써놓고 싶었다.
<a href="https://docs.openwebui.com/category/-tutorial">OpenWebUI튜토리얼</a> 진짜 친절한 문서다...
오픈소스 짱!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DDD - 도메인 주도 설계]]></title>
            <link>https://velog.io/@martin-han/DDD-%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%A3%BC%EB%8F%84-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@martin-han/DDD-%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%A3%BC%EB%8F%84-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Sun, 25 Aug 2024 07:50:15 GMT</pubDate>
            <description><![CDATA[<p>이 글은 이번에 DDD를 살짝 찍먹해 본 나의 생각을 정리한 글이다.</p>
<h1 id="domain-driven-design">Domain-Driven Design</h1>
<p>나는 이전까지 <code>DDD == 도메인주도개발</code>이라고 알고 있었다.
하지만 <code>DDD</code>는 <code>도메인주도개발</code>이 아닌 <strong><code>도메인주도설계</code></strong>이다.</p>
<p><strong>도메인 주도 설계</strong>란 <strong>소프트웨어로 해결하고자 하는 도메인(문제 영역)</strong>을 중심으로 설계를 진행하는 설계 기법이다.</p>
<p>DDD는 크게 <code>전략적 설계</code>와 <code>전술적 설계</code>로 구분할 수 있다.</p>
<ul>
<li><strong>전략적 설계 (Strategic Design)</strong></li>
<li><strong>전술적 설계 (Tactical Design)</strong><ul>
<li><code>도메인 주도 개발</code>이라는 말은 전술적 설계를 진행하는 방법론이라고 생각한다.</li>
</ul>
</li>
</ul>
<h1 id="전략적strategic설계">전략적(Strategic)설계</h1>
<p><strong>비즈니스</strong>에서 <strong>소프트웨어로 해결하고자 하는 영역</strong>을 효과적으로 <strong>이해하고 모델링</strong>하는 단계</p>
<h2 id="문제-영역problem-domain-식별">문제 영역(Problem domain) 식별</h2>
<p><strong>해결하고자 하는 도메인</strong>을 <code>문제 영역</code>이라고 한다.</p>
<blockquote>
<h4 id="도메인-예시">도메인 예시</h4>
<p>만약 모든걸 아날로그로 처리하던 영화관에서 <code>예매</code>를 소프트웨어로 처리하고 싶다는 요청이 들어왔다고 가정해보자
    - <code>예매</code>에는 <strong>상영 중인 영화 정보</strong>가 필요하다.
    - <strong>상영 중인 영화 정보</strong>로 <code>예매</code>를 진행한다.
위 내용을 정리하면 다음과 같이 <strong>두 가지</strong> <code>문제 도메인</code>으로 나눌 수 있다.
    - <strong>상영 중인 영화 정보</strong>를 보여주는 <code>영화 도메인</code>
    - <strong>영화의 정보로 예매</strong>를 하는 <code>예매 도메인</code></p>
</blockquote>
<h2 id="지식-탐구-knowledge-crunching">지식 탐구 (Knowledge Crunching)</h2>
<p>프로젝트 전반에 걸쳐 <strong>도메인 전문가와 개발자가 모여 지속적</strong>으로 <code>지식 탐구</code>를 진행하여 도메인에 대해 더 깊이있게 이해해야 한다.</p>
<blockquote>
<h4 id="지식-탐구란">지식 탐구란</h4>
<p>도메인 전문가와 개발자 같은 <strong>주요이해관계자가 모여 도메인에 대해 탐구하는 과정</strong>이다.
이 과정을 통해 <code>유비쿼터스 언어</code>를 <strong>정의</strong>하거나 <code>숨겨진 도메인의 규칙</code>을 찾아내는 등
<strong>도메인에 대해 더 깊이 이해하는 과정</strong>을 말한다.
이 과정은 <strong>지속적이고 반복적으로 이루어져야 한다.</strong></p>
</blockquote>
<h2 id="하위-도메인-식별">하위 도메인 식별</h2>
<p>문제 도메인을 추출했다면 이 도메인을 <code>하위 도메인</code>으로 식별할 수 있다.</p>
<p>위 도메인 예시로 보면 <code>예매 도메인</code>과 <code>영화 도메인</code>이 될 수 있다.</p>
<h2 id="하위-도메인-유형-분류">하위 도메인 유형 분류</h2>
<p>식별된 하위 도메인에 다음과 같은 유형으로 분류할 수 있다.</p>
<ul>
<li><strong>핵심 도메인</strong> - 비즈니스의 핵심 경쟁력을 제공하는 영역</li>
<li><strong>지원 도메인</strong> - 핵심 도메인을 지원하는 중요한 영역이지만 차별화 요소는 아님</li>
<li><strong>일반 도메인</strong> - 비즈니스에 필요하지만 특별한 맞춤화가 필요 없는 영역</li>
</ul>
<blockquote>
<p>유형을 나누는 이유는 조직의 리소스를 적재적소에 배분해야 하기 때문이다.</p>
</blockquote>
<h2 id="바운디드-컨텍스트-정의">바운디드 컨텍스트 정의</h2>
<p>각 하위 도메인에 대한 경계를 <code>바운디드 컨텍스트</code>로 정의한다.
지식 탐구에서 정의한 <strong><code>유비쿼터스 언어</code>는 <code>바운디드 컨텍스트</code> 단위로 고유</strong>해야 한다.</p>
<blockquote>
<h4 id="바운디드-컨텍스트란">바운디드 컨텍스트란</h4>
<p>복잡한 도메인을 관리 가능하게 정의한 단위</p>
</blockquote>
<h2 id="바운디드-컨텍스트-맵-작성">바운디드 컨텍스트 맵 작성</h2>
<p><strong>바운디드 컨텍스트 간의 관계를 시각화</strong>하여 이해관계자간의 <strong>커뮤니케이션을 원활하게 한다.</strong></p>
<p>이때 컨텍스트간의 관계를 <code>상류(Upstream)</code>와 <code>하류(Downstream)</code>라는 개념으로 <strong>의존성을 나타낸다.</strong></p>
<ul>
<li>상류(Upstream)<ul>
<li>데이터를 <strong>제공하는 영역</strong></li>
<li>변경이 데이터를 제공받는 쪽에 영향을 줌</li>
</ul>
</li>
<li>하류(Downstream)<ul>
<li>데이터를 <strong>제공받는 영역</strong></li>
<li>상류의 변경에 대응해야함</li>
</ul>
</li>
</ul>
<hr>
<h1 id="전술적tactical설계">전술적(Tactical)설계</h1>
<p>전략적 설계에서 정의된 바운디드 컨텍스트 내부의 <strong>세부적인 구현 방법</strong>을 다루는 단계</p>
<p>도메인 모델을 구체화하고, 실제 코드를 구현하여 비즈니스 로직을 명확하게 표현한다.</p>
<p>정의된 내용을 우선 <code>개념모델</code>로 만들고 <code>구현모델</code>로 변환하는 과정을 거친다.
<strong>처음부터 완벽한 모델을 만들기보다는</strong> 전체적인 윤곽을 개념모델로 만들고 구현과정에서 점진적으로 발전시키는게 좋을것 같다.</p>
<p><code>도메인 지식</code>이라는게 한번에 완성될 수도 있겠지만 보통은 프로젝트가 진행되면서 
<strong>도메인 지식이 쌓이고 그로인해 통찰력이 높아질거라 생각한다.</strong>
그렇기 때문에 처음에는 적합했던 모델이 현재의 도메인지식을 가지고 봤을 때는 적합하지 않을 수 있기 때문이다.</p>
<h2 id="도메인-모델-정의">도메인 모델 정의</h2>
<p>객체마다 <strong>고유한 식별자</strong>가 있는 도메인 모델을 정의한다.</p>
<p>특징으로는 </p>
<ul>
<li>무의미한 <code>public setter</code>를 사용하지 않는다.<ul>
<li>의도하지 않은 상태의 변경을 막는다.</li>
</ul>
</li>
<li><strong>비즈니스 기능을 모델 내부에 구현한다.</strong></li>
</ul>
<h2 id="벨류타입값객체-식별">벨류타입(값객체) 식별</h2>
<p>흔히 벨류타입이라고도 한다.</p>
<p><strong>여러 개의 속성이 하나의 개념</strong>으로 표현할 때 <code>벨류타입</code>이 유용하게 사용된다.
예) <code>나라</code>,<code>도시</code>,<code>지번</code> 같은 필드는 <code>주소</code> 라는 타입으로 묶을 수 있다. 이 때 <code>주소</code>가 <strong>벨류타입이다.</strong></p>
<p>특징으로는 </p>
<ul>
<li>값객체는 <strong>불변</strong>이다.</li>
<li>값객체의 속성이 변경된다면 <strong>객체를 새로 생성</strong>한다.</li>
</ul>
<h2 id="애그리거트">애그리거트</h2>
<p>관련된 <strong>도메인을 하나의 단위</strong>로 묶어 일관성을 유지한다.</p>
<blockquote>
<h4 id="바운디드-컨텍스트도-단위를-묶어주는게-아닌가">바운디드 컨텍스트도 단위를 묶어주는게 아닌가??</h4>
<p>바운디드 컨텍스트는 서로 다른 도메인간의 경계를 정의하고
애그리거트는 관련 객체들을 묶어 일관성을 유지하는게 목적이다.</p>
</blockquote>
<p>특징으로는</p>
<ul>
<li>애그리거트에 속한 객체는 <strong>유사하거나 동일한 라이프 사이클을 갖는다.</strong></li>
<li>애그리거트 자신을 관리하고 <strong>다른 애그리거트는 관리하지 않는다.</strong></li>
<li><strong>애그리거트 루트</strong>라는 애그리거트를 관리하는 객체를 지정해서 <strong>이곳에서만 상태의 관리를 한다.</strong></li>
</ul>
<h2 id="도메인-서비스-정의">도메인 서비스 정의</h2>
<p>특정 <strong>엔티티나 값 객체에 속하지 않는 도메인 로직</strong>을 처리한다.</p>
<p>주로 다음 상황에서 사용한다.</p>
<ul>
<li><strong>계산로직</strong> - <strong>여러 애그리거트가 필요한</strong> 계산 로직, 한 애그리거트에 넣기 다소 복잡한 로직</li>
<li><strong>외부 시스템 연동이 필요한 도메인 로직</strong> - 구현하기 위해 타 시스템을 사용해야 하는 도메인 로직</li>
</ul>
<p>특징으로는</p>
<ul>
<li><strong>도메인 로직 포함</strong> - 의사결정 관련 코드를 담당</li>
<li><strong>StateLeess</strong> - 서비스 자체는 상태를 가지지 않는다.</li>
<li><strong>여러 애그리거트 간 연산</strong> - 둘 이상의 애그리거트를 다루는 로직 처리</li>
</ul>
<h2 id="응용-서비스-정의">응용 서비스 정의</h2>
<p><strong>애그리거트와 도메인 서비스에 구현된 도메인 기능을 직접적으로 사용하는 서비스</strong>를 정의한다.</p>
<p>응용 서비스는 <strong>사용자가 요청한 기능을 실행한다.</strong></p>
<p>특징으로는</p>
<ul>
<li><strong>도메인 객체간의 흐름을 제어한다.</strong> (그렇기에 단순한 형태를 갖는다.)</li>
<li><strong>도메인 로직을 포함하지 않는다.</strong><ul>
<li>응집도가 떨어질 수 있기 때문에 도메인 로직은 애그리거트에 구현한다.</li>
</ul>
</li>
<li><strong>요청의 트랜잭션을 관리한다.</strong></li>
</ul>
<hr>
<h1 id="마치며">마치며</h1>
<blockquote>
<p>나는 지금까지 데이터 중심 모델(Anemic domain model)을 주로 사용하면서
DDD의 필요성을 느끼지 못했었다.
하지만 응집도가 떨어진 몇몇 코드들을 수정하면서 DDD에 관심을 갖게되었다.
이전까지는 굳이?? 라는 생각이 지배적이었다.</p>
</blockquote>
<p>현재 내가 이해한 바를 정리한 것이라 내용이 다소 빈약할 수 있고, 다르거나 잘못 알고 있는 내용이 있을 수 있다.
앞으로도 관심을 갖고 학습한 뒤에 다음에 다시 한번 더 정리해볼 생각이다.
이후에는 에릭 에반스의 DDD 책을 읽어볼 생각이다.</p>
<h1 id="참고-자료">참고 자료</h1>
<p>최범균 님의 &lt;<a href="https://www.google.co.kr/books/edition/%EB%8F%84%EB%A9%94%EC%9D%B8_%EC%A3%BC%EB%8F%84_%EA%B0%9C%EB%B0%9C_%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0/bi5qEAAAQBAJ?hl=ko&amp;gbpv=0">도메인주도개발 시작하기</a>&gt;
NHN Cloud 정명주 님의 &lt;<a href="https://www.youtube.com/watch?v=6w7SQ_1aJ0A&amp;t=613s">DDD 뭣이 중헌디?</a>&gt;</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Meta Llama3.1 Ollama로 로컬에서 사용해보기]]></title>
            <link>https://velog.io/@martin-han/Meta-Llama3.1-%EB%A1%9C%EC%BB%AC%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@martin-han/Meta-Llama3.1-%EB%A1%9C%EC%BB%AC%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sat, 27 Jul 2024 07:34:48 GMT</pubDate>
            <description><![CDATA[<h1 id="llama-ai-31">Llama AI 3.1</h1>
<p>Meta에서 공개한 오픈소스 AI로 비교적 최신 AI라서 그런지 우수한 성능을 보여준다고 한다.</p>
<p><a href="https://about.fb.com/news/2024/07/open-source-ai-is-the-path-forward/">저커버그 아티클</a> - 오픈소스 AI에 대한 생각
저커버그는 오픈소스 AI의 중요성을 얘기하면서 llama를 Unix/Linux와 같은 생태계를 채택했다고 한다.</p>
<h2 id="llama-31-모델">Llama 3.1 모델</h2>
<h3 id="특징">특징</h3>
<ul>
<li><strong>다국어 지원 강화(8개 언어 지원)</strong>
영어, 독일어, 프랑스어, 이탈리아어, 포르투갈어, 한국어, 스페인어, 태국어</li>
<li><strong>128K 토큰의 컨텍스트 길이 지원</strong>
아주 긴 문서나 대화도 한번에 처리 가능</li>
<li><strong>tool calling</strong>
ai가 다양한 툴 사용가능 (질문에 실시간으로 웹에서 학습한 데이터를 답변하는 등)</li>
<li><strong>오픈소스</strong>
라이센스를 자세히 확인해봐야겠지만 영리 활동도 가능한것으로 보임</li>
<li><strong>실시간 및 배치 추론</strong>
실시간 입력에 응답 가능하며 동시에 대량의 데이터 일괄 처리 가능</li>
<li><strong>Ollama를 통한 로컬환경 실행가능</strong></li>
</ul>
<h3 id="8b-8-billion-parameters">8B (8 billion parameters)</h3>
<ul>
<li>80억개의 파라미터</li>
<li>대략 4GB (다운로드 크기)</li>
</ul>
<h3 id="70b-70-billion-parameters">70B (70 billion parameters)</h3>
<ul>
<li>700억개의 파라미터</li>
<li>대략 40GB (다운로드 크기)</li>
</ul>
<h3 id="405b-405-billion-parameters">405B (405 billion parameters)</h3>
<ul>
<li>4050억개의 파라미터</li>
<li>대략 230GB (다운로드 크기)</li>
</ul>
<h1 id="ollama로-로컬에서-실행하기">Ollama로 로컬에서 실행하기</h1>
<p><a href="https://llama.meta.com/docs/llama-everywhere/running-meta-llama-on-mac/">meta llama docs - Mac용 다운 가이드</a>
위 링크를 타고가면 설명이 너무 쉽게 되어있어 설명은 생략한다.</p>
<p>Ollama를 설치 후 터미널에서 실행이 가능하다. (실수로 llama3로 실행했다... llama3.1로 실행하자)
<img src="https://velog.velcdn.com/images/martin-han/post/0c3e913a-6d55-493f-a9ec-ce0486bb9bb8/image.png" width=400></p>
<p><code>/?</code> 명령어를 실행하면 어떤 설정을 할 수 있는지 나온다.
<img src="https://velog.velcdn.com/images/martin-han/post/93008ddf-af44-46e4-bd90-653c1154f003/image.png"></p>
<h2 id="curl로-명령어-보내기">curl로 명령어 보내기</h2>
<h3 id="요청">요청</h3>
<pre><code>curl http://localhost:11434/api/chat -d &#39;{
  &quot;model&quot;: &quot;llama3&quot;,
  &quot;messages&quot;: [
    {
      &quot;role&quot;: &quot;user&quot;,
      &quot;content&quot;: &quot;오늘 서울의 날씨가 어떨지 한국어로 설명해줘&quot;
    }
  ],
  &quot;stream&quot;: false
}&#39;</code></pre><p>stream을 끄면 답변을 한번에 준다.(킨다면 단어마다 많게는 글자마다 잘려서 응답이 온다.)</p>
<h3 id="응답">응답</h3>
<pre><code>{
  &quot;model&quot;:&quot;llama3&quot;,
  &quot;created_at&quot;:&quot;2024-07-27T07:26:47.403705Z&quot;,
  &quot;message&quot;:{
    &quot;role&quot;:&quot;assistant&quot;,
    &quot;content&quot;:&quot;Hi! It&#39;s nice to meet you. Is there something I can help you
    with, or would you like to chat?&quot;
  },
  &quot;done_reason&quot;:&quot;stop&quot;,
  &quot;done&quot;:true,
  &quot;total_duration&quot;:2727816250,
  &quot;load_duration&quot;:31346083,
  &quot;prompt_eval_count&quot;:11,
  &quot;prompt_eval_duration&quot;:636490000,
  &quot;eval_count&quot;:26,
  &quot;eval_duration&quot;:2058647000
}</code></pre><p><a href="https://github.com/ollama/ollama/blob/main/docs/api.md">Ollama Docs</a> - 여기에 더 다양한 api가 소개되어있다.</p>
<h1 id="마치며">마치며</h1>
<p>8B 모델을 사용해서 그런지 아무 설정없이 생으로 쓰기에는 다국어가 다소 부족한 감이 있다.
하지만 더 공부해보고 사용한다면 재밌는것들을 만들 수 있어보인다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[도메인 모델 - ADM, RDM]]></title>
            <link>https://velog.io/@martin-han/Rich-Anemic-Domain-Models</link>
            <guid>https://velog.io/@martin-han/Rich-Anemic-Domain-Models</guid>
            <pubDate>Sun, 14 Jul 2024 00:36:22 GMT</pubDate>
            <description><![CDATA[<p>나의 주관적인 정리이다.</p>
<h1 id="anemic-domain-model">Anemic Domain Model</h1>
<h4 id="빈약한-도메인-객체-anemic-domain-object">빈약한 도메인 객체 (Anemic Domain Object)</h4>
<pre><code>데이터 중심
비지니스가 외부 구성에 의존</code></pre><h2 id="특징">특징</h2>
<ul>
<li><strong>데이터를 중심</strong>으로한 도메인</li>
<li>주로 <strong>데이터만 포함</strong>하고, 비지니스는 <strong>별도의 서비스 계층에 위임</strong></li>
<li>메서드는 주로 <strong><code>getter</code></strong>, <strong><code>setter</code></strong>만 포함
<img src="https://velog.velcdn.com/images/martin-han/post/3a7d4b28-18f7-42c3-91b5-21200e9fa50c/image.png" width=40%><img src="https://velog.velcdn.com/images/martin-han/post/687644fa-3327-493e-b3a6-62660a7f6af5/image.png" width=60%></li>
</ul>
<h2 id="장점">장점</h2>
<ul>
<li><h3 id="구현이-빠름">구현이 빠름</h3>
<ul>
<li>단순한 구조로 <strong>빠르게 구현이 가능</strong></li>
<li>간단한 프로젝트나 초기 단계에서 빠른 개발이 가능</li>
</ul>
</li>
<li><h3 id="테스트가-편함">테스트가 편함</h3>
<ul>
<li>테스트 데이터를 쉽게 만들 수 있음</li>
<li>도메인이 별도의 비지니스를 포함하고 있지않아 복잡한 테스트가 필요없음</li>
</ul>
</li>
<li><h3 id="관심사-분리">관심사 분리</h3>
<ul>
<li><code>데이터</code>와 <code>비지니스</code>를 각각 <strong>독립적으로 변경 가능</strong><h2 id="단점">단점</h2>
</li>
</ul>
</li>
<li><h3 id="oop-위반">OOP 위반</h3>
<ul>
<li>OOP의 <strong>이점을 활용하지 못함</strong></li>
<li>도메인의 <strong><code>표현력</code>이 떨어짐</strong></li>
<li>객체의 <strong>내부 상태가 외부로 모두 노출</strong>되므로 <strong><code>캡슐화 위반</code></strong></li>
</ul>
</li>
<li><h3 id="비지니스-로직-분산">비지니스 로직 분산</h3>
<ul>
<li><strong>중복코드 발생</strong><ul>
<li>특정 비지니스 로직이 여러 서비스에서 쓰인다면 중복된 로직이 생긴다.</li>
</ul>
</li>
<li><strong>일관성 있는 규칙 적용, 유지가 힘들다.</strong><ul>
<li>여러 서비스에 퍼져있는 동일한 비지니스 로직 중 하나를 수정하려면 해당 규칙이 있는 모든 곳을 찾아서 수정해야함</li>
</ul>
</li>
</ul>
</li>
<li><h3 id="유지보수성-저하">유지보수성 저하</h3>
<ul>
<li>비지니스 로직의 <strong>변경이나 확장이 어렵다.</strong><ul>
<li>하나의 비지니스 로직을 3곳의 서비스에서 사용한다 했을 때 이를 다 확인해야 한다.</li>
<li>다른곳을 빼먹었더라고 검증되기 전까지 이는 에러를 뱉지 않는다.(에러가 안나오고 비지니스만 잘못될 수도 있다.)</li>
</ul>
</li>
</ul>
</li>
<li><h3 id="테스트-복잡도-증가">테스트 복잡도 증가</h3>
<ul>
<li>모델에 대한 자체적인 <strong>유효성 테스트가 불가능하다.</strong></li>
<li><strong>비지니스를 포함하고 있는 계층의 테스트가 복잡해진다.</strong></li>
</ul>
</li>
</ul>
<h1 id="rich-domain-model">Rich Domain Model</h1>
<h4 id="풍부한-도메인-객체-rich-domain-object">풍부한 도메인 객체 (Rich Domain Object)</h4>
<pre><code>객체 지향적
비지니스가 포함되어 주체적</code></pre><h2 id="특징-1">특징</h2>
<ul>
<li><code>OOP 원칙</code>을 따르는 도메인</li>
<li>도메인이 <code>데이터</code>와 <code>행위</code>를 포함</li>
<li>비지니스 로직이 <strong>도메인 안에 존재</strong></li>
<li><code>도메인</code>이 <code>행위</code>를 직접 표현함으로써 <strong>표현력이 높음</strong><img src="https://velog.velcdn.com/images/martin-han/post/8b0097af-c6da-4252-ae3a-7c489fd3d17f/image.png" width=70%>
### RDM을 사용하면 DDD를 하는건가요?
`DDD`는 비즈니스 도메인을 중심으로 **소프트웨어를 모델링, 설계하는 `방법론`이다.**
`DDD`의 **핵심개념중 하나가 `RDM`인것은 맞지만** `RDM`이 `DDD`인것은 아니며 `DDD`가 `RDM`인것 또한 아니다.
## 장점</li>
<li><h4 id="oop-원칙-준수">OOP 원칙 준수</h4>
<ul>
<li><code>캡슐화</code> 가능</li>
<li><code>다형성</code>사용시 <strong>확장성 증가</strong></li>
</ul>
</li>
<li><h4 id="비지니스-응집도-증가">비지니스 응집도 증가</h4>
<ul>
<li><code>데이터</code>와 <code>행위</code>가 <strong>객체 안에 존재</strong></li>
<li>비지니스 로직 <code>중앙화</code>로 <strong>일관성을 유지하기 좋음</strong></li>
<li><strong>재사용성 증가</strong>로 인한 <strong>중복 코드 감소</strong></li>
</ul>
</li>
<li><h4 id="유지보수성-증가">유지보수성 증가</h4>
<ul>
<li><code>도메인</code>의 <strong>표현력이 높아짐</strong></li>
<li>비지니스 도메인의 <strong>이해 난이도가 낮아짐</strong></li>
<li>중앙화된 로직으로 <strong>유지보수성 증가</strong></li>
</ul>
</li>
<li><h4 id="단위-테스트-용이성">단위 테스트 용이성</h4>
<ul>
<li>비지니스 로직이 도메인에 존재하기 때문에 <strong>복잡한 의존성 없이 비지니스 테스트 가능</strong></li>
<li>모델에 대한 <strong>유효성 테스트 가능</strong><h2 id="단점-1">단점</h2>
</li>
</ul>
</li>
<li><h4 id="복잡성-증가">복잡성 증가</h4>
<ul>
<li>비지니스에 따라 도메인의 <strong>복잡도가 높아질 수 있음</strong></li>
<li>복잡도가 높은 도메인의 <strong>이해 및 유지보수가 어려워질 수 있음</strong></li>
</ul>
</li>
<li><h4 id="성능-이슈">성능 이슈</h4>
<ul>
<li>도메인에 비지니스 로직이 포함되면서 DB 조회시 <strong>불필요한 비용 발생 가능</strong></li>
</ul>
</li>
<li><h4 id="테스트-복잡성">테스트 복잡성</h4>
<ul>
<li>단위 테스트에서의 장점은 강해지나 <strong>통합테스트의 복잡성이 증가할 수 있음</strong></li>
<li>도메인의 복잡도에 따라 <strong>단위 테스트의 복잡도 또한 증가</strong></li>
</ul>
</li>
<li><h4 id="러닝-커브가-가파름">러닝 커브가 가파름</h4>
<ul>
<li>복잡한 도메인에 대해 <strong>많은 <code>시간</code>과 <code>노력</code> 그리고 <code>지속적인 관심</code>이 필요</strong></li>
</ul>
</li>
<li><h4 id="설계비용-증가">설계비용 증가</h4>
<ul>
<li>초기 설계에서 도메인을 정확히 이해하고 설계하는데 <strong>시간이 많이 듬</strong></li>
</ul>
</li>
</ul>
<h1 id="요약">요약</h1>
<ul>
<li>ADM은 상대적으로 간단하나 빈약하다.</li>
<li>RDM은 복잡하나 견고하다.</li>
<li>장점만 존재하는건 없다.</li>
<li>상황에 맞게 적절한 선택을 하자(RDM, ADM)</li>
<li>RDM을 사용한다면 많은 노력과 관심을 갖자</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[figma 컴포넌트 property]]></title>
            <link>https://velog.io/@martin-han/figma-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-property</link>
            <guid>https://velog.io/@martin-han/figma-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-property</guid>
            <pubDate>Sat, 13 Jul 2024 07:04:10 GMT</pubDate>
            <description><![CDATA[<p>컴포넌트의 Property를 사용해서 멋진 컴포넌트를 만들어보자</p>
<h1 id="목표">목표</h1>
<p>하나의 컴포넌트에서 Property를 사용하여 다양한 형태를 표현하기
<img src="https://velog.velcdn.com/images/martin-han/post/069f4548-0a42-4cc0-8f19-363234c822fe/image.png" width="50%"></p>
<h2 id="실습">실습</h2>
<h3 id="step1">step.1</h3>
<p>다음과 같이 대충 아이콘스러운걸 각각 컴포넌트로 만들어준다.
<img src="https://velog.velcdn.com/images/martin-han/post/53baf99d-92d2-402c-b601-91fcc27e0f1a/image.png" width="60%"></p>
<h3 id="step2">step.2</h3>
<p>마음에 드는 녀석의 인스턴스를 생성한다.
<img src="https://velog.velcdn.com/images/martin-han/post/550527c1-bf45-4993-b52b-ad090f6c0828/image.png" width="60%"></p>
<h3 id="step3">step.3</h3>
<ul>
<li><code>frame</code> 으로 감싸준다. (<code>Option</code> + <code>Command</code> + <code>G</code>)</li>
<li><strong>새로운 컴포넌트</strong>가 될 녀석이므로 컴포넌트의 이름을 지어준다.</li>
<li><code>컴포넌트</code>로 만들어준다.(<code>Option</code> + <code>Command</code> + <code>K</code>)</li>
<li><code>AutoLayout</code>을 걸어준다. (<code>Shift</code> + <code>A</code>)<img src="https://velog.velcdn.com/images/martin-han/post/7575ac53-3427-451a-a8f2-4e1aa4d1ec5c/image.png" width=60%>

</li>
</ul>
<h3 id="step4-variant">step.4 (Variant)</h3>
<ul>
<li>우측 상단에 <strong>more actions</strong>를 눌러서 <code>add variant</code>를 추가해준다. (<code>Add property</code> 의 플러스 버튼으로도 가능)<img src="https://velog.velcdn.com/images/martin-han/post/c49ac549-7ec7-4531-a530-9aa577a3f21b/image.png" width=60%></li>
<li>그럼 기존의 마스터가 보라 점선으로 둘러 싸져있다.</li>
<li>여기에도 Auto Layout을 걸어주자</li>
<li>점섬 안에 요소가 추가 되었는데 이를 <strong>복사 붙여넣기</strong>하거나 <code>more actions</code>에서 <code>add variant</code>로 새 상태를 추가해주자<img src="https://velog.velcdn.com/images/martin-han/post/2122bcfd-0088-409e-a2cc-49352ba1bdc6/image.png" width=80%>
### step.5</li>
<li>label이라는 text를 생성한다. (<code>T</code>누르고 대충 드래그)<img src="https://velog.velcdn.com/images/martin-han/post/d3a1821f-dd91-496f-b574-a95baf9745e3/image.png" width=60%></li>
<li><strong>Layers의 label 요소</strong>를 <code>드래그</code>해서 아무 <code>variant</code> 맨 뒤에 추가해준다.</li>
<li>이제 각 <code>variant tpye</code>에 이름을 정해준다. <strong>simple</strong>과 <strong>named</strong> 라고 하겠다. <ul>
<li>주의 - variant의 이름 형식은 <code>{propertyname} = {value}</code> 이다.<img src="https://velog.velcdn.com/images/martin-han/post/854c586d-2ad3-4cab-b374-9a7507c57169/image.png" width=80%>

</li>
</ul>
</li>
</ul>
<h3 id="step6-text">step.6 (Text)</h3>
<ul>
<li><p>마스터에 Text Property 추가하기</p>
<img src="https://velog.velcdn.com/images/martin-han/post/57ea9005-cb6b-4f6b-b53d-81bb9e61b6a3/image.png" width=50%></li>
<li><p>텍스트의 <code>Typography</code>에서 <strong>생성한 <code>property</code> 연결하기</strong></p>
<img src="https://velog.velcdn.com/images/martin-han/post/6bca1577-82fe-4ff2-ac2e-ffaccf6addb8/image.png" width=80%>
<img src="https://velog.velcdn.com/images/martin-han/post/a7c24cd9-28af-47ef-bf05-72b41ac3492d/image.png" width=50%>
### step.7</li>
<li><p><strong>named variant</strong>를 복붙한 뒤 다음과 같이 추가하기</p>
<img src="https://velog.velcdn.com/images/martin-han/post/145368b1-8c3d-4cc4-8aa2-aa0eb5e182ba/image.png" width=80%></li>
<li><p><strong>job</strong>이라는 <code>text property</code>추가 후 <strong>small-label에 적용</strong></p>
<h3 id="step8-instance-swap">step.8 (Instance Swap)</h3>
</li>
<li><p>마스터에서 <code>Instance Swap Property</code> 추가</p>
</li>
<li><p><code>value</code>에 <strong>기본으로 선택할 컴포넌트</strong> 선택</p>
</li>
<li><p><code>Preferred values</code>에 플러스 버튼으로 사용시 <strong>기본 보기항목 선택</strong></p>
<img src="https://velog.velcdn.com/images/martin-han/post/762c0af1-4403-4ffe-8f45-fbd21e978c71/image.png" width=80%></li>
<li><p><strong>Instance swap할 항목 선택</strong>(처음에 했어야했는데 순서가 꼬임..)</p>
<img src="https://velog.velcdn.com/images/martin-han/post/9bf691f2-7959-4737-b76d-997f6e5cf161/image.png" width=80%></li>
<li><p><code>more actions</code>에서 <code>Apply instance swap property</code> 클릭 후 <strong>방금 생성한 <code>property</code> 선택</strong></p>
<img src="https://velog.velcdn.com/images/martin-han/post/29130e89-3999-49fe-8776-0d254d047e27/image.png" width=50%>
### step.9 (Boolean)</li>
<li><p>마스터에 <code>Boolean Property</code> 추가</p>
<img src="https://velog.velcdn.com/images/martin-han/post/ff66cc88-ee4f-4683-bd2b-4402797b2b26/image.png" width=50%></li>
<li><p>선택적으로 안보이게할 요소 선택</p>
<img src="https://velog.velcdn.com/images/martin-han/post/54e62c54-d8f1-4358-83db-4bcb7a9427eb/image.png" width=80%>
</li>
<li><p><code>Appearance</code> 에서 <strong>인스턴스 모양 아이콘(?)</strong> 클릭 후 방금 생성한 <code>property</code> 선택</p>
<img src="https://velog.velcdn.com/images/martin-han/post/b2140152-69da-44ee-b3c8-799cfa6aa6d8/image.png" width=50%>
## 결과</li>
<li><p>마스터의 인스턴스 생성</p>
<img src="https://velog.velcdn.com/images/martin-han/post/ac25d75d-c22f-4959-8ac3-a6bed52216dd/image.png" width=80%></li>
<li><p>우측 인스턴스의 <code>Design</code>맨 위에 위에서 추가해준 <code>Property</code>가 보임 (Property 1 이름 깜빡하고 안바꿈...)</p>
<img src="https://velog.velcdn.com/images/martin-han/post/07192d1a-cc66-4f55-b008-47fa2910057f/image.png" width=50%></li>
<li><p>이제 하나하나 클릭해보면 위에서 어떤 것들을 추가 했었는지 확인가능!</p>
<h1 id="마치며">마치며</h1>
<p>이건 기본적인 사용방법만을 적어둔거고 이를 응용해서 더 복잡하고 멋진 컴포넌트 생성가능</p>
</li>
</ul>
<p>Prototype을 사용해서 인터렉션도 걸 수 있음 (무료는 기능이 제한적)</p>
<p>끝나고보니 내가 만든 컴포넌트가 멋진 외관은 아니다. 나는 거짓말쟁이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MySQL 이중화 - GTID mode in docker]]></title>
            <link>https://velog.io/@martin-han/MySQL-%EC%9D%B4%EC%A4%91%ED%99%94-GTID-mode-in-docker</link>
            <guid>https://velog.io/@martin-han/MySQL-%EC%9D%B4%EC%A4%91%ED%99%94-GTID-mode-in-docker</guid>
            <pubDate>Sat, 13 Jul 2024 06:02:09 GMT</pubDate>
            <description><![CDATA[<p>Docker 환경에서 MySQL 이중화 GTID 모드를 구성해보자</p>
<p>이전 기본방식과의 차이는 <code>자동화</code>되어있다는 거다.</p>
<ul>
<li>귀찮게 컨테이너에 들어갈 필요가 없다.</li>
</ul>
<h1 id="준비">준비</h1>
<h2 id="docker-composeyml">./docker-compose.yml</h2>
<pre><code>services:
  master:
    image: mysql:8.0.35
    environment:
      MYSQL_ROOT_PASSWORD: 1234
      MYSQL_DATABASE: myDb
    volumes:
      - ./master/init-master.sql:/docker-entrypoint-initdb.d/init-master.sql
      - ./master/config:/etc/mysql/conf.d
    networks:
      - mysql_network
    ports:
      - &quot;3306:3306&quot;

  slave:
    image: mysql:8.0.35
    environment:
      MYSQL_ROOT_PASSWORD: 1234
      MYSQL_DATABASE: myDb
    volumes:
      - ./slave/init-slave.sql:/docker-entrypoint-initdb.d/init-slave.sql
      - ./slave/config:/etc/mysql/conf.d
    networks:
      - mysql_network
    ports:
      - &quot;3307:3306&quot;

networks:
  mysql_network:</code></pre><h2 id="masterconfigmaster-configcnf">./master/config/master-config.cnf</h2>
<pre><code>[mysqld]
server-id = 1
log-bin = mysql-bin
binlog_format = ROW

gtid_mode = on
log_slave_updates = on
enforce-gtid-consistency=ON</code></pre><h2 id="masterinit-mastersql">./master/init-master.sql</h2>
<pre><code>CREATE USER &#39;repl&#39;@&#39;%&#39; IDENTIFIED BY &#39;repl_password&#39;;
GRANT REPLICATION SLAVE ON *.* TO &#39;repl&#39;@&#39;%&#39;;

FLUSH PRIVILEGES;</code></pre><h2 id="slaveconfigslave-configcnf">./slave/config/slave-config.cnf</h2>
<pre><code>[mysqld]
server-id = 2
read_only = 1
log-bin = mysql-bin

gtid_mode = on
log_slave_updates = on
enforce-gtid-consistency=ON</code></pre><h2 id="slaveinit-slavesql">./slave/init-slave.sql</h2>
<pre><code>CHANGE MASTER TO
  MASTER_HOST=&#39;master&#39;,
  MASTER_USER=&#39;repl&#39;,
  MASTER_PASSWORD=&#39;repl_password&#39;,
  MASTER_AUTO_POSITION=1;
START SLAVE;</code></pre><h1 id="시작하기">시작하기</h1>
<h2 id="docker-compose-실행">Docker-compose 실행</h2>
<pre><code>$ docker compose up -d</code></pre><h2 id="정상-실행-확인">정상 실행 확인</h2>
<h3 id="slave-컨테이너-접근">slave 컨테이너 접근</h3>
<pre><code>$ docker compose exec slave mysql -uroot -p1234</code></pre><h3 id="slave-상태-조회">slave 상태 조회</h3>
<pre><code>SHOW SLAVE STATUS\G</code></pre><h3 id="정상-실행시">정상 실행시</h3>
<p><code>Last_IO_Error</code>, <code>Last_SQL_Error</code>가 empty이다.
<img src="https://velog.velcdn.com/images/martin-han/post/d62e4dd7-1066-4026-aca1-e85e0daf988f/image.png" width=70%></p>
<h1 id="마치며">마치며</h1>
<p>완전 간단해져서 만족스럽다.
토폴로지 구성등은 응용해서 해보자~</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[figma 컴포넌트]]></title>
            <link>https://velog.io/@martin-han/figma-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</link>
            <guid>https://velog.io/@martin-han/figma-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</guid>
            <pubDate>Sat, 13 Jul 2024 04:59:08 GMT</pubDate>
            <description><![CDATA[<h1 id="figma">Figma</h1>
<p>디자인 협업 툴로 디자이너, 기획자, 개발자 등이 실시간으로 협업이 가능한 툴이다.</p>
<h2 id="컴포넌트">컴포넌트</h2>
<p>디자인 요소를 컴포넌트화 해서 사용가능하다.</p>
<ul>
<li>재사용성 증가</li>
<li>계층 구조</li>
<li>일관성 유지</li>
<li>변경 용이성</li>
</ul>
<h3 id="master-와-instance-컴포넌트">Master 와 Instance 컴포넌트</h3>
<p>컴포넌트에는 <code>두 가지</code> 타입이 존재한다.</p>
<h4 id="마스터-컴포넌트master-component">마스터 컴포넌트(Master Component)</h4>
<p><img src="https://velog.velcdn.com/images/martin-han/post/ab11cf72-ddcd-4af5-8f64-88bdfda78cb1/image.png" alt=""></p>
<ul>
<li>원본 컴포넌트로, 모든 인스턴스 컴포넌트의 기준</li>
<li><strong>마스터 컴포넌트 수정시 모든 인스턴스에 변경사항이 반영</strong></li>
</ul>
<h4 id="인스턴스-컴포넌트instance-component">인스턴스 컴포넌트(Instance Component)</h4>
<p><img src="https://velog.velcdn.com/images/martin-han/post/f5bf728c-1b14-4887-9093-7e7f2e893c8a/image.png" alt=""></p>
<ul>
<li><strong>마스터 컴포넌트</strong>의 <code>복사본</code></li>
<li>마스터 컴포넌트와 연결되어 있어 <strong>마스터의 변경사항이 자동으로 적용</strong></li>
<li>개별적으로 요소의 <code>값</code> 수정가능 (size, color, font 등등)<ul>
<li>단 <code>해당 값</code>은 <strong>마스터 수정시에 반영이 안될 수 있음</strong></li>
</ul>
</li>
<li>개별적으로 <strong>요소의 구조를 변경할 수 없다.</strong></li>
<li><strong>인스턴스도 요소</strong>이므로 다른 요소로 감싸서 새로운 마스터 컴포넌트 생성가능<ul>
<li>단 해당 마스터의 요소는 <strong>인스턴스 컴포넌트 이므로 마스터의 변경 내용이 적용</strong>된다. (<strong>일관성,변경 용이성</strong>)
<img src="https://velog.velcdn.com/images/martin-han/post/97f7d421-c60a-4d13-8ed9-45a030d07c1c/image.png" alt=""></li>
</ul>
</li>
</ul>
<h2 id="학습하기">학습하기</h2>
<blockquote>
<p>주의! - 본인은 유료 플랜이라 무료에서는 제약이 있을 수 있음</p>
</blockquote>
<p><a href="https://www.figma.com/community/file/1035203688168086460/material-3-design-kit">figma Material 3 Design Kit</a>으로 학습한 경험이 좋았다.
피그마에 추가해주고 열어보기
<img src="https://velog.velcdn.com/images/martin-han/post/66911ebe-bcf3-4867-b92c-2a55165df38d/image.png" width="50%">
둘 중 원하는 방법으로 컴포넌트 둘러보기!(<code>Shift</code> + <code>I</code>시 resource 검색가능)</p>
<ul>
<li>툴바를 Layers에 놓고 커멘드로 컴포넌트 찾는것을 추천</li>
</ul>
<h3 id="예시">예시</h3>
<p><code>Top app bar</code>를 예시로 사용
<img src="https://velog.velcdn.com/images/martin-han/post/e59878e9-ff6e-470b-927c-507be41ec531/image.png" width="50%"></p>
<p>하나씩 클릭해보면서 컴포넌트의 동작을 확인</p>
<ul>
<li>실제로 눌러보고 싶다면 <code>Shift</code> + <code>Space</code></li>
<li>여기서 되는건 내가 만들어 볼 수 있다는 뜻</li>
<li><code>prototype</code>에서는 <code>애니메이션 효과</code>, 사용자 상호작용(<code>hover</code>, <code>onClick</code>, <code>scroll</code> 등) 적용 가능</li>
</ul>
<h4 id="master-component-찾아가기">master component 찾아가기</h4>
<img src="https://velog.velcdn.com/images/martin-han/post/86f90b55-8187-43db-a1e5-db3de936e68d/image.png" width="50%">
마스터 마크 클릭시 마스터의 위치로 이동

<img src="https://velog.velcdn.com/images/martin-han/post/b7041e00-5f2a-437f-aa67-dae377030824/image.png" width="50%">
마스터가 어떻게 구성되어있는지 확인 가능
<img src="https://velog.velcdn.com/images/martin-han/post/45f97c9a-e5f7-499a-8960-cc53c8fa4757/image.png" width="60%">
이건 `Variants`를 사용한 것으로 컴포넌트의 `상태별` 형태이다.
]]></description>
        </item>
        <item>
            <title><![CDATA[MySQL 이중화 - Binary in docker]]></title>
            <link>https://velog.io/@martin-han/MySQL-%EC%9D%B4%EC%A4%91%ED%99%94-Binary-in-docker</link>
            <guid>https://velog.io/@martin-han/MySQL-%EC%9D%B4%EC%A4%91%ED%99%94-Binary-in-docker</guid>
            <pubDate>Thu, 11 Jul 2024 01:15:55 GMT</pubDate>
            <description><![CDATA[<p>Docker 환경에서 MySQL 이중화를 만들어보자</p>
<h1 id="준비">준비</h1>
<blockquote>
<p>docker 버전 <code>20.10.0</code> 부터 docker-compose 플러그인이 docker compose 같이 Docker CLI에 통합 되었다.</p>
</blockquote>
<h2 id="docker-composeyml">./docker-compose.yml</h2>
<blockquote>
<p>Docker Compose v2 부터는 <code>version</code> 필드가 의미가 없어졌다고 한다!</p>
</blockquote>
<pre><code>services:
  master:
    image: mysql:8.0.35
    environment:
      MYSQL_ROOT_PASSWORD: 1234
      MYSQL_DATABASE: my_db
      TZ: UTC
    volumes: 
      - ./master/config:/etc/mysql/conf.d
    networks:
      - mysql_network
    ports:
      - &quot;3306:3306&quot;
    command: --default-authentication-plugin=mysql_native_password

  slave:
    image: mysql:8.0.35
    environment:
      MYSQL_ROOT_PASSWORD: 1234
      MYSQL_DATABASE: my_db
      TZ: UTC
    volumes:
      - ./slave/config:/etc/mysql/conf.d
    networks:
      - mysql_network
    ports:
      - &quot;3307:3306&quot;
    command: --default-authentication-plugin=mysql_native_password

networks:
  mysql_network:</code></pre><h2 id="masterconfigmaster-configcnf">./master/config/master-config.cnf</h2>
<pre><code>[mysqld]
server-id = 1
log-bin = mysql-bin
binlog_format = ROW</code></pre><h2 id="slaveconfigslave-configcnf">./slave/config/slave-config.cnf</h2>
<pre><code>[mysqld]
server-id = 2
read_only = 1</code></pre><h1 id="시작하기">시작하기</h1>
<h2 id="docker-compose-실행">Docker-compose 실행</h2>
<pre><code>$ docker compose up -d</code></pre><h2 id="master-설정">master 설정</h2>
<p>master DataBase의 source를 Replication 할 계정 생성</p>
<h3 id="컨테이너-진입">컨테이너 진입</h3>
<pre><code>$ docker compose exec master mysql -uroot -p1234</code></pre><h3 id="계정-생성-및-status-확인">계정 생성 및 status 확인</h3>
<p>master status의 <code>File</code>과 <code>Position</code> 확인
해당 정보를 기반으로 <strong>replication이</strong> 이뤄짐</p>
<pre><code>CREATE USER &#39;repl&#39;@&#39;%&#39; IDENTIFIED BY &#39;repl_password&#39;;
GRANT REPLICATION SLAVE ON *.* TO &#39;repl&#39;@&#39;%&#39;;
FLUSH PRIVILEGES;
SHOW MASTER STATUS;</code></pre><h2 id="slave-설정">slave 설정</h2>
<h3 id="컨테이너-진입-1">컨테이너 진입</h3>
<pre><code>$ docker-compose exec slave mysql -uroot -p1234</code></pre><h3 id="master-정보-및-읽어올-binary-log-위치-설정">Master 정보 및 읽어올 binary log 위치 설정</h3>
<pre><code>CHANGE MASTER TO
  MASTER_HOST=&#39;master&#39;,
  MASTER_USER=&#39;repl&#39;,
  MASTER_PASSWORD=&#39;repl_password&#39;,
  MASTER_LOG_FILE=&#39;mysql-bin.000003, # master status의 file
  MASTER_LOG_POS=1345; # master status의 position</code></pre><h3 id="slave-실행-및-상태-조회">slave 실행 및 상태 조회</h3>
<pre><code>START SLAVE;
SHOW SLAVE STATUS\G; # 대문자로!</code></pre><h3 id="정상-실행시">정상 실행시</h3>
<p><code>Last_IO_Error</code>, <code>Last_SQL_Error</code>가 empty이다
<img src="https://velog.velcdn.com/images/martin-han/post/d62e4dd7-1066-4026-aca1-e85e0daf988f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Mysql Replication(이중화)]]></title>
            <link>https://velog.io/@martin-han/Mysql-Replication</link>
            <guid>https://velog.io/@martin-han/Mysql-Replication</guid>
            <pubDate>Tue, 09 Jul 2024 01:13:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>MySQL Replication</strong>은 데이터베이스의 <strong>트래픽 부하를 분산</strong>하여 <code>가용성을 향상</code>시켜주는 기술이다.</p>
</blockquote>
<h2 id="개념">개념</h2>
<p>하나의 <strong><code>Master</code>(source)</strong> 데이터베이스의 데이터를 <code>n</code>개의 <strong><code>Slave</code>(replica)</strong> 데이터베이스로 <strong><code>비동기 방식</code></strong>, <strong><code>반동기 방식</code></strong>으로 전체 또는 일부 데이터를 <strong>복제</strong>한다.</p>
<h2 id="master와-slave">Master와 Slave</h2>
<p><code>Master</code>와 <code>Slave</code>에는 <strong>Replication을 처리하기 위한 쓰레드</strong>가 존재한다.</p>
<h3 id="master">Master</h3>
<ul>
<li><strong>Binary Dump Thread</strong><ul>
<li>slave에서 요청이 들어오면 <code>생성</code></li>
<li>slave 요청의 <code>위치 정보</code>(<code>GTID</code> or <code>로그파일명, position</code>)를 가지고 <strong>바이너리 로그</strong>를 읽어서 <strong>변경사항</strong>을 <code>전달</code></li>
<li><strong>변경사항이 없다면</strong> 변경사항 발생할 때까지 Dump Thread는<code>대기</code> (heartbeat 로 연결 유지)</li>
</ul>
</li>
</ul>
<h3 id="slave">Slave</h3>
<ul>
<li><strong>I/O Thread</strong><ul>
<li><strong>Slave 시작시</strong> <code>생성</code></li>
<li><strong>Master 정보</strong>로 연결, <strong>현재 <code>복제 위치 정보</code>를 요청</strong></li>
<li><strong>Master로 부터 이벤트 수신시</strong> <code>Relay Log</code>에 <strong>기록</strong></li>
<li>이벤트 처리 완료 후 <strong>현재 <code>복제 위치 정보</code> 업데이트</strong></li>
</ul>
</li>
<li><strong>SQL Thread</strong> <ul>
<li><strong>Slave 시작시</strong> <code>생성</code></li>
<li><strong>현재 <code>이벤트 위치 정보</code></strong>를 읽음</li>
<li><code>I/O Thread</code>가 작성한 <code>Relay Log</code> 를 읽어 <strong>이벤트 유형(<code>DML</code>, <code>DDL</code> 등)을 식별</strong></li>
<li><strong>파싱된 이벤트</strong>를 Slave Storage에 적용(쿼리 실행)</li>
<li>트랜잭션 이벤트를 만나면 적절히 처리(필요시 <code>commit</code>, <code>rollback</code>)</li>
<li>이벤트 처리 완료시 <strong>현재 이벤트 위치 정보 업데이트</strong></li>
<li>지연 복제시 설정된 시간만큼 Lazy 후 처리</li>
</ul>
</li>
</ul>
<h2 id="프로세스-순서">프로세스 순서</h2>
<p><img src="https://velog.velcdn.com/images/martin-han/post/888bd79a-c3c0-4600-b4d0-fc60402d83d7/image.png" alt=""></p>
<ol>
<li>클라이언트가 <code>Master</code>에 <strong>Commit</strong> 요청</li>
<li>Storage에 <strong>Write</strong></li>
<li>Binary log에 <strong>변경 사항을 기록</strong></li>
<li>Storage <strong>Commit</strong> (이때 binary log도 <code>Flush</code>)
 여기 까지가 Client 요청</li>
<li><code>I/O Thread</code>가 <code>Binary Log Dump Thread</code>에 변경사항을 <strong>요청</strong></li>
<li>Relay log에 <strong>기록</strong></li>
<li><code>SQL Thread</code>가 Relay log의 변경사항을 <strong>조회</strong></li>
<li>Storage에 <strong>Write</strong></li>
</ol>
<h1 id="binary-방식">Binary 방식</h1>
<p><strong>바이너리 로그 파일 위치</strong> 기반 복제 방식
소스에서만 유효한 식별 방법</p>
<h2 id="위치정보-형식">위치정보 형식</h2>
<p><code>binary log file:position</code></p>
<ul>
<li><code>binary-log.000002:145</code></li>
</ul>
<h2 id="장단점">장단점</h2>
<h3 id="장점">장점</h3>
<ul>
<li><p><strong>버전 호환성</strong>
전통적인 방식인 만큼 거의 모든 MySQL 버전에서 사용 가능하여 호환성이 높다.</p>
</li>
<li><p><strong>설정이 단순하다</strong>
바이너리 로그 파일과 위치만 설정하면 끝이라 구성이 단순하다.</p>
</li>
<li><p><strong>유연성</strong>
특정 데이터베이스나 테이블에 대한 복제 필터링이 가능하다.
<code>replicate-do-db</code>, <code>replicate-ignore-db</code>, <code>replicate-do-table</code>, <code>replicate-ignore-table</code> 등등</p>
</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li><p><strong>수동 조작</strong>
<code>slave</code>를 추가하거나 복구시 <code>binary log</code> 파일과 <code>position</code>의 정확한 위치를 <strong>수동</strong>으로 설정해야한다.</p>
</li>
<li><p><strong>자동화</strong>
만약 master의 서버 장애로 slave를 master에 승격시켜야 하는 경우 네트워크,replica 처리속도 등 외부적인 요인으로 master와 slave간 position이 다를 수 가 있다.
이러한 상황에서 <code>binary log</code>와 <code>position</code>의 수동 작업이 필요하기 때문</p>
</li>
<li><p><strong>추적</strong>
저장 단위가 트랜잭션 단위가 아니기 때문에 트랜잭션에 비해 추적이 어렵다.
장애 복구시 정확한 위치를 찾아야 한다.</p>
</li>
<li><p><strong>일관성 보장</strong>
<code>binary log</code>의 <code>position</code>을 기반으로하기 때문에 일관성 보장이 어려울 수 있다.</p>
<ul>
<li>네트워크 등 외부요인으로 Master는 A,B,C 의 요청에 대한 내용을 갖고 있지만 slave1는 A,B slave2는 A,C 만 복제된다면 후에 해당 요청의 누락을 찾기가 다소 힘듬</li>
</ul>
</li>
<li><p><strong>topology(DB간 복제 구조) 변경 복잡성</strong>
<code>binary log</code>와 <code>position</code>을 수동으로 해야하기 때문</p>
</li>
</ul>
<h1 id="gtid-방식">GTID 방식</h1>
<blockquote>
<p>기존 <strong>binary log 위치기반 방식</strong>의 단점을 보완하기 위해 <code>5.6.5</code> 버전부터 <code>GTID</code>모드가 출시되었다.</p>
</blockquote>
<p><strong>글로벌 트랜잭션 아이디</strong> 기반 복제 방식</p>
<h2 id="차이점">차이점</h2>
<ul>
<li><strong>서버 고유 UUID 발급</strong>
서버에 고유한 UUID를 사용하여 식별</li>
<li><strong>트랜잭선 단위 로깅</strong>
요청에 고유한 트랜잭션ID를 부여해서 이를 사용한 복제</li>
<li><strong>자동 위치 설정</strong>
<code>MASTER_AUTO_POSITION=1;</code> 옵션을 사용하여 자동 위치 설정 가능</li>
</ul>
<h2 id="위치정보-형식-1">위치정보 형식</h2>
<p><code>server_UUID:Transaction_ID</code></p>
<ul>
<li><code>c6aec179-7370-4927-8e1a-e249ac278d14:11</code></li>
</ul>
<h2 id="장단점-1">장단점</h2>
<h3 id="장점-1">장점</h3>
<ul>
<li><p><strong>트랜잭션 단위 로깅</strong>
각 트랜잭션 단위로 고유 ID를 부여되어 자동추적이 가능
이로인해 <code>추적</code>이 쉬워짐</p>
</li>
<li><p><strong>일관성</strong>
트랜잭션 ID로 일관성을 보장하여 누락된 트랜잭션을 보다 쉽게 식별가능</p>
<ul>
<li>Master와 Slave 각각 트랜잭션 목록을 가지고 있기 때문</li>
</ul>
</li>
<li><p><strong>자동화</strong>
자동 추적 옵션을 사용하면 <code>binary log</code> 및 <code>position</code>을 수동으로 찾을 필요가 없음
이로인해 <code>자동화</code>, <code>포톨로지 변경</code> 등이 쉬워짐</p>
</li>
</ul>
<h3 id="단점-1">단점</h3>
<ul>
<li><p><strong>호환성</strong>
<code>GTID</code>모드는 <code>5.6.5</code>이상의 버전부터 사용가능</p>
</li>
<li><p><strong>유연성</strong>
트랜잭션 단위로 묶이는 만큼 <strong>유연성은 다소 떨어짐</strong>
<code>replicate-do-db</code>, <code>replicate-ignore-db</code> 등의 옵션을 <strong>직접 사용 불가</strong>
<code>8.0</code> 이상은 <strong><code>CHANGE REPLICATION FILTER</code></strong>로 유사한 기능 구현 가능</p>
</li>
<li><p><strong>성능</strong>
기본방식에 트랜잭션을 추가하는 만큼의 오버헤드가 있음</p>
</li>
</ul>
<h2 id="토폴로지topology">토폴로지(Topology)</h2>
<ul>
<li>단일 소스 복제 - 왼쪽처럼 <code>1:N</code>개로 각 레플리카가 <code>master</code>를 바라보게 구성</li>
<li>체인 복제 - 오른쪽처럼 <code>master</code>를 바라보는 <code>slave1</code>를 <code>slave2</code>가 바라보게 구성
물론 이 <strong>두 가지를 섞어서 사용</strong>도 가능하다.
당연하게도 해당 구조의 <code>slave</code>는 <strong><code>readOnly</code></strong>여야한다. (<strong>master로는 slave의 데이터가 가지않으니</strong>)
<img src="https://velog.velcdn.com/images/martin-han/post/97cb2daa-9560-4158-b5dd-764422932e56/image.png" alt=""></li>
</ul>
<h3 id="토폴로지-유형">토폴로지 유형</h3>
<ul>
<li><h4 id="단일-소스-복제single-source-replication">단일 소스 복제(Single-Source Replication)</h4>
</li>
<li><em>하나*</em>의 <code>Master</code>와 <strong>여러 개</strong>의<code>Slave</code>로 구성</li>
<li><h4 id="다중-소스-복제multi-source-replication">다중 소스 복제(Multi-Source Replication)</h4>
</li>
<li><em>하나*</em>의 <code>Slave</code>와 <strong>여러 개</strong>의<code>Master</code>로 구성
쓰기 트래픽 분산 혹은 각기 다른 환경의 데이터를 통합하여 관리, 분석하기 위해 사용<ul>
<li>다양한 애플리케이션 통합</li>
<li>테스트 환경 통합</li>
<li>글로벌의 경우 각 리전별 데이터 베이스 통합</li>
</ul>
</li>
<li><h4 id="체인-복제-chained-replication">체인 복제 (Chained Replication)</h4>
<code>Master</code> -&gt; <code>Slave1</code> -&gt; <code>Slave2</code> -&gt; <code>Slave3</code> 같이 구성</li>
<li><em><code>Master</code>의 부하*</em>를 분산하기 좋음(단계가 있는 만큼 복제에 시간차가 발생)</li>
<li><h4 id="순환-복제-circular-replication">순환 복제 (Circular Replication)</h4>
<code>A</code> -&gt; <code>B</code> -&gt; <code>C</code> -&gt; <code>A</code> 같이 구성</li>
<li><h4 id="마스터-마스터-복제-master-master-replication">마스터-마스터 복제 (Master-Master Replication)</h4>
<code>Master</code> &lt;-&gt; <code>Master</code> 같이 구성</li>
<li><h4 id="계층형-복제-hierarchical-replication">계층형 복제 (Hierarchical Replication)</h4>
</li>
</ul>
<ol>
<li><code>Master</code><ol>
<li><code>Slave1</code><ol>
<li><code>Slave1-1</code></li>
<li><code>Slave1-2</code></li>
</ol>
</li>
<li><code>Slave2</code><ol>
<li><code>Slave2-1</code></li>
</ol>
</li>
<li><code>Slave3</code></li>
</ol>
</li>
</ol>
<ul>
<li><h4 id="그룹-복제-group-replication">그룹 복제 (Group Replication)</h4>
여러 DB 인스턴스를 그룹화해서 사용(복잡하지만 강력한 기능)<h2 id="정리">정리</h2>
</li>
</ul>
<h3 id="구성">구성</h3>
<ul>
<li>여러 바이너리 로그 파일과 인덱스로 구성</li>
<li>각 파일은 각각의 <code>이름</code> 및 <code>숫자 확장자</code>가 존재<h3 id="바이너리-로그의-정의">바이너리 로그의 정의</h3>
</li>
<li>바이너리 로그는 데이터베이스의 변경 사항을 기록하는 일련의 파일
  <code>데이터 변경</code> 및 <code>테이블 구조</code> 변경도 포함 <h3 id="기록되는-내용">기록되는 내용</h3>
</li>
<li><code>CREATE</code>, <code>ALTER</code>, <code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code> 등 <strong>데이터에 영향을 주는 명령</strong>을 <strong><code>기록</code></strong></li>
<li><code>SELECT</code>, <code>SHOW</code> 와 같이 <strong>데이터에 영향을 주지않는 명령</strong>은 <strong><code>기록되지 않음</code></strong><h3 id="방식">방식</h3>
</li>
<li>트랜잭션의 각 명령이 성공하면 <code>바이너리 로그</code>에 기록</li>
<li><strong>동작순서</strong><ol>
<li>InnoDB <code>prepare</code> 로그 작성 및 <code>동기화</code></li>
<li>바이너리 로그 <code>기록</code> 및 <code>커밋마크 추가</code>, <code>fsync()</code>호출</li>
<li>InnoDB <code>커밋 로그 작성</code> 및 <code>동기화</code><h3 id="바이너리-로그-형식">바이너리 로그 형식</h3>
</li>
</ol>
</li>
<li><code>binlog_format</code> 환경 변수로 형식 제어
<code>Global</code> or <code>Session</code> 범위 설정 가능</li>
<li><code>세 가지</code> 바이너리 로그 형식 지원:<ol>
<li>행 기반(<code>Row-Based</code>) 로깅 (RBR):<ul>
<li>개별 테이블 행의 변경 사항을 기록</li>
<li><strong><code>--binlog-format=ROW</code></strong></li>
<li>MySQL 8.0 <code>기본 설정</code></li>
</ul>
</li>
<li>문장 기반(<code>Statement-Based</code>) 로깅 (SBR):<ul>
<li>SQL 문장 그대로 기록</li>
<li><strong><code>--binlog-format=STATEMENT</code></strong></li>
</ul>
</li>
<li>혼합(<code>Mixed</code>) 로깅:<ul>
<li>상황에 따라 문장 기반과 행 기반을 자동으로 전환하여 기록</li>
<li><strong><code>--binlog-format=MIXED</code></strong><h3 id="형식-선택-시-고려사항">형식 선택 시 고려사항</h3>
</li>
</ul>
</li>
</ol>
</li>
<li><code>Row-Based</code> - 안전하고 정확하지만, 로그 크기가 커짐<ul>
<li>레플리카는 <code>행 기반</code> <strong>바이너리 로그</strong> 항목을 <code>문장 기반 형식</code>으로 변환할 수 없음</li>
</ul>
</li>
<li><code>Statement-Based</code> - 로그 크기가 작지만, 비결정적 문장(실행시마다 다른 결과가 나오는 SQL문)에서 <strong>소스와 레플리카의 불일치 가능성 있음</strong></li>
</ul>
<h3 id="보안">보안</h3>
<ul>
<li><strong><code>민감한 정보</code></strong>를 포함할 수 있으므로 <strong>적절한 보호가 필요</strong></li>
<li>바이너리 로그 기반 복제는 <code>데이터 일관성 유지</code>, <code>백업</code>, <code>복구</code>에 중요한 역할</li>
</ul>
<h3 id="주의사항">주의사항</h3>
<ul>
<li><strong><code>디스크의 I/O 작업이 증가</code></strong>하여 <strong>서버 성능에 영향을 줄 수 있음</strong></li>
<li>동시 트랜잭션 커밋 시 <code>prepare_commit_mutex</code>로 인한 <strong>직렬화가 발생할 수 있음</strong></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[인텔리제이 단축키]]></title>
            <link>https://velog.io/@martin-han/%EC%9D%B8%ED%85%94%EB%A6%AC%EC%A0%9C%EC%9D%B4-%EB%8B%A8%EC%B6%95%ED%82%A4</link>
            <guid>https://velog.io/@martin-han/%EC%9D%B8%ED%85%94%EB%A6%AC%EC%A0%9C%EC%9D%B4-%EB%8B%A8%EC%B6%95%ED%82%A4</guid>
            <pubDate>Tue, 25 Jun 2024 07:07:20 GMT</pubDate>
            <description><![CDATA[<p>MacOS 기준!
자주쓰는 단축키 메모</p>
<h1 id="intellij-keymap">IntelliJ keymap</h1>
<p>인텔리제이에 어떤 단축키가 있는지, 특정 단축키를 커스텀하고 싶을때는 <code>Settings</code>에 있는 <code>keyMap</code>에서 가능하다.</p>
<h2 id="접근방법">접근방법</h2>
<h4 id="macos">macOS</h4>
<p><code>Command</code> + <code>,</code>
<code>Command</code> + <code>F</code> 후 or 그냥 keymap 검색
<img src="https://velog.velcdn.com/images/martin-han/post/38cbf01b-6f05-44a7-a56b-5f9b02803e65/image.png" alt=""></p>
<blockquote>
<p>터미널을 커스텀해두면 아주아주 편하다.</p>
</blockquote>
<h1 id="단축키">단축키</h1>
<h2 id="액션">액션</h2>
<p><code>Control 두번</code>
application 실행 클래스 대상으로
Run - <code>Enter</code> 
Debug - <code>Shift</code> + <code>Enter</code>
gradle, maven 컴파일,빌드 등 손쉽게 실행 가능</p>
<h2 id="에디터">에디터</h2>
<h3 id="대상-이름-변경">대상 이름 변경</h3>
<p><code>Shift</code> + <code>F6</code></p>
<h3 id="탐색기">탐색기</h3>
<p><code>Command</code> + <code>1</code></p>
<h3 id="탐색기에서-파일-열기">탐색기에서 파일 열기</h3>
<p><code>Enter</code> or <code>Command</code> + <code>방향키 아래</code></p>
<h3 id="현재-파일의-위치로-탐색기-이동">현재 파일의 위치로 탐색기 이동</h3>
<p><code>Option</code> + <code>F1</code> 후 <strong>Project View</strong></p>
<h3 id="네비게이션-바">네비게이션 바</h3>
<p><code>Command</code> + <code>방향키 위</code></p>
<h3 id="네비게이터-전후-위치로-이동">네비게이터 전,후 위치로 이동</h3>
<p>전 - <code>Command</code> + <code>[</code>
후 - <code>Command</code> + <code>]</code></p>
<h3 id="열린탭-좌우-이동">열린탭 좌,우 이동</h3>
<p>왼쪽 - <code>Shift</code> + <code>Commnad</code> + <code>[</code>
오른쪽 - <code>Shift</code> + <code>Commnad</code> + <code>]</code></p>
<h3 id="검색에-있는-단어-위-아래-이동">검색에 있는 단어 위 아래 이동</h3>
<p>아래 - <code>Command</code> + <code>G</code>
위 - <code>Shift</code> + <code>Command</code> + <code>G</code></p>
<h3 id="단어-한번에-선택하기">단어 한번에 선택하기</h3>
<p><code>Control</code> + <code>Commnad</code> + <code>G</code></p>
<h3 id="클래스메소드-축소해제">클래스,메소드 축소,해제</h3>
<h4 id="파일-전체">파일 전체</h4>
<p>축소 - <code>Shift</code> + <code>Command</code> + <code>-</code>
해제 - <code>Shift</code> + <code>Command</code> + <code>+</code>
둘다 - <code>Command</code> + <code>.</code></p>
<h4 id="선택된-영역">선택된 영역</h4>
<p>축소 - <code>Command</code> + <code>-</code>
해제 - <code>Command</code> + <code>+</code></p>
<h3 id="대상-내부-진입">대상 내부 진입</h3>
<p><code>Commnad</code> + <code>방향키 아래</code>
<code>Commnad</code> + <code>B</code></p>
<h3 id="전후-메소드">전,후 메소드</h3>
<p>전 - <code>Control</code> + <code>Shift</code> + <code>방향키 위</code>
후 - <code>Control</code> + <code>Shift</code> + <code>방향키 아래</code></p>
<h3 id="안쓰는-import-제거">안쓰는 import 제거</h3>
<p><code>Control</code> + <code>Option</code> + <code>O</code></p>
<h2 id="디버깅">디버깅</h2>
<h3 id="현재-위치에-브레이크-포인트-생성">현재 위치에 브레이크 포인트 생성</h3>
<p><code>Command</code> + <code>F8</code></p>
<h3 id="브레이크-포인트-목록">브레이크 포인트 목록</h3>
<p><code>Shift</code> + <code>Commnad</code> + <code>F8</code></p>
<h2 id="붙여넣기">붙여넣기</h2>
<h3 id="복사-이력에서-붙여넣기">복사 이력에서 붙여넣기</h3>
<p><code>Shift</code> + <code>Command</code> + <code>V</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[XSS(Cross Site Scripting)]]></title>
            <link>https://velog.io/@martin-han/XSSCross-Site-Scripting</link>
            <guid>https://velog.io/@martin-han/XSSCross-Site-Scripting</guid>
            <pubDate>Wed, 19 Jun 2024 15:27:34 GMT</pubDate>
            <description><![CDATA[<h1 id="xss">XSS</h1>
<p><strong>사이트 간 스크립팅</strong>으로 웹 사이트에서 관리자가 아닌 제3자가 악성 스크립트를 삽입하여 의도치않은 명령을 실행시키는 웹 취약점이다.</p>
<blockquote>
<p>제 3자가 마음대로 <code>javascript</code>를 이용하여 내가 웹에서 로그인할 때 누른 키보드 정보를 axios등을 이용해서 제3자의 server로 전송할 수 도있다.</p>
</blockquote>
<p>이로 인해 사용자는 다음과 같은 피해를 입을 수 있다.</p>
<ul>
<li>쿠키 및 세선졍보 탈취</li>
<li>악성 프로그램 다운 유도</li>
<li>악성 사이트로 접근 유도</li>
</ul>
<h2 id="공격-유형">공격 유형</h2>
<p>크게 3가지로 분류가 가능하다.</p>
<h3 id="stored-xss-저장형-크로스사이트스크립트">Stored XSS (저장형 크로스사이트스크립트)</h3>
<p>공격자가 웹 서버에 악성 스크립트를 저장시켜 이후 다른 사용자가 해당 데이터를 조회함으로써 악성 스크립트를 실행시키는 방식</p>
<h3 id="reflected-xss-반사형-크로스사이트스크립트">Reflected XSS (반사형 크로스사이트스크립트)</h3>
<p>공격자가 악성 스크립트를 서버에 저장시키지 않고 사용자에게 직접 절달하는 방식</p>
<p>다음과 같은 링크로 공격자가 보안이 취약한 사이트를 찾아 해당 사이트의 링크로
이메일, sns등을 통해 사용자의 접근을 유도할 수 있다.
<code>www.대충보안이취약한사이트.com?대충화면그려주는변수=&lt;script&gt;alert(&#39;공격&#39;)&lt;/script&gt;</code></p>
<h3 id="dom-based-xss-dom-기반-크로스사이트스크립트">DOM Based XSS (DOM 기반 크로스사이트스크립트)</h3>
<p>악성 스크립트가 서버가 아닌 브라우저에서 실행되는 방식</p>
<h1 id="spring-boot--stored-xss-대책">Spring boot &amp; Stored Xss 대책</h1>
<p>spring boot에서 <code>Stored Xss</code>에 대한 보안 대책으로 간단한 WhiteList 필터링을 구현해보자</p>
<h2 id="라이브러리">라이브러리</h2>
<pre><code>// spring boot web
implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
// OWASP ESAPI 
implementation &#39;com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1&#39;
// Apache Commons Text
implementation &#39;org.apache.commons:commons-text:1.12.0&#39;
// Lombok
compileOnly &#39;org.projectlombok:lombok&#39;
annotationProcessor &#39;org.projectlombok:lombok&#39;</code></pre><h2 id="xss-테스트-구현">XSS 테스트 구현</h2>
<h3 id="samplerestcontroller">SampleRestController</h3>
<p><code>RequestParam</code>과 <code>RequestBody</code>에 대한 WhiteList를 구현하기 위한 샘플</p>
<pre><code>@RestController
public class XssTestController {

    @GetMapping(&quot;/xss&quot;)
    public String getXss(@RequestParam(&quot;content&quot;) String content) {
        return content;
    }

    @Data
    public static class Xss {

        private String content;
    }

    @PostMapping(&quot;/xss&quot;)
    public String addXss(@RequestBody Xss xss) {
        return xss.content;
    }
}</code></pre><h3 id="resourcesstaticindexhtml">resources/static/index.html</h3>
<p>앱을 실행해서 root 접근 후 각 버튼을 눌러보면 alert이 실행된다.</p>
<p>이제 버튼 하나씩 alert이 실행되지 않게 처리를 해보자.</p>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;title&gt;Title&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div id=&quot;title&quot;&gt;hello world&lt;/div&gt;
&lt;button onclick=&quot;getTest()&quot;&gt;Get Test 버튼&lt;/button&gt;
&lt;button onclick=&quot;postTest()&quot;&gt;Post Test 버튼&lt;/button&gt;
&lt;script src=&quot;https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js&quot;&gt;&lt;/script&gt;
&lt;script&gt;

    const getTest = () =&gt; {
        axios.get(&quot;http://localhost:8080/xss?content=&quot;+&quot;&lt;img src=&#39;i&#39; onerror=\&quot;alert(&#39;test&#39;);\&quot;&gt;&quot;)
            .then(result =&gt; document.querySelector(&quot;#title&quot;).innerHTML = result.data);
    };

    const postTest = () =&gt; {
        axios.post(&quot;http://localhost:8080/xss&quot;, {
            &quot;content&quot;: &quot;&lt;img src=&#39;i&#39; onerror=\&quot;alert(&#39;test&#39;);\&quot;&gt;&quot;
        }).then((result) =&gt; document.querySelector(&quot;#title&quot;).innerHTML = result.data);
    };
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><h3 id="xsshelper">XssHelper</h3>
<p>악성 스크립트를 필터링해줄 헬퍼</p>
<pre><code>import org.apache.commons.text.StringEscapeUtils;
import org.owasp.html.PolicyFactory;
import org.owasp.html.Sanitizers;

public class XssHelper {

    private static final PolicyFactory POLICY = Sanitizers.FORMATTING
            .and(Sanitizers.STYLES)
            .and(Sanitizers.LINKS)
            .and(Sanitizers.IMAGES)
            .and(Sanitizers.TABLES)
            .and(Sanitizers.BLOCKS);

    public static String sanitizeXSS(String value) {
        if(value == null) {
            return null;
        }
        String passingValue = StringEscapeUtils.unescapeHtml4(value);
        return StringEscapeUtils.unescapeHtml4(POLICY.sanitize(passingValue));
    }
}</code></pre><h3 id="xssfilter">XssFilter</h3>
<p><code>RequestParam</code>으로 전달되는 악성 스크립트를 걸러주는 필터</p>
<p>적용 후 다시 root페이지의 <code>Get Test 버튼</code>을 눌러보면 작동하지 않는 것을 볼 수 있다.</p>
<pre><code>import io.martin.xsstest.helper.XssHelper;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class XssFilter implements Filter{

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        chain.doFilter(new XSSRequestWrapper((HttpServletRequest) request), response);
    }

    @Override
    public void destroy() {
    }

    private static class XSSRequestWrapper extends HttpServletRequestWrapper {

        public XSSRequestWrapper(HttpServletRequest servletRequest) {
            super(servletRequest);
        }

        @Override
        public String[] getParameterValues(String parameter) {
            String[] values = super.getParameterValues(parameter);
            if (values == null) {
                return null;
            }

            int count = values.length;
            String[] encodedValues = new String[count];
            for (int i = 0; i &lt; count; i++) {
                encodedValues[i] = XssHelper.sanitizeXSS(values[i]);
            }

            return encodedValues;
        }

        @Override
        public String getParameter(String parameter) {
            String value = super.getParameter(parameter);
            return XssHelper.sanitizeXSS(value);
        }

        @Override
        public String getHeader(String name) {
            String value = super.getHeader(name);
            return XssHelper.sanitizeXSS(value);
        }
    }
}</code></pre><h3 id="xssstringdeserializer">XssStringDeserializer</h3>
<p><code>RequestBody</code>로 전달되는 문자열타입의 악성 스크스크립트를 걸러줄 역직렬화기</p>
<pre><code>import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import io.martin.xsstest.helper.XssHelper;

import java.io.IOException;

public class XssStringDeserializer extends StdDeserializer&lt;String&gt; {

    public XssStringDeserializer() {
        super(String.class);
    }

    @Override
    public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        return XssHelper.sanitizeXSS(jsonParser.getValueAsString());
    }
}</code></pre><h3 id="appconfig">AppConfig</h3>
<p>역직렬화기를 ObjectMapper에 추가</p>
<pre><code>import io.martin.xsstest.databind.XssStringDeserializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer customJsonDeserializer() {
        return builder -&gt; builder.deserializerByType(String.class, new XssStringDeserializer());
    }
}</code></pre><p>모두 적용했다면 root 페이지의 버튼을 눌러도 alert이 안 뜨는 것을 볼 수 있다.</p>
<p>이상 아주 간단한 WhiteList 필터링을 구현해봤다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Mockito 유틸리티]]></title>
            <link>https://velog.io/@martin-han/Mockito-%EC%9C%A0%ED%8B%B8%EB%A6%AC%ED%8B%B0</link>
            <guid>https://velog.io/@martin-han/Mockito-%EC%9C%A0%ED%8B%B8%EB%A6%AC%ED%8B%B0</guid>
            <pubDate>Mon, 17 Jun 2024 16:13:27 GMT</pubDate>
            <description><![CDATA[<h1 id="argumentmatchers">ArgumentMatchers</h1>
<p>메소드 호출 시 사용된 인자를 매칭 및 검증하는 데 사용되는 유틸이다.</p>
<pre><code>    @Mock
    private BookRepository bookRepository;

    @InjectMocks
    private BookService bookService;

    @Test
    public void addTest_1() {
        // 인자 생성
        Book entity = Book.create(&quot;재밌는책&quot;, &quot;저자&quot;);

        // 엔티티 저장 메소드 호출
        bookService.add(entity);

        // save 메소드가 어떤 Book 객체든 전달된 채로 1번 호출되었는지 확인
        Mockito.verify(bookRepository, Mockito.times(1)).save(ArgumentMatchers.any(Book.class));
    }</code></pre><h2 id="주요-기능">주요 기능</h2>
<ul>
<li><code>any()</code> - 모든 값이든 허용</li>
<li><code>any(Class&lt;T&gt; type)</code> - 특정 클래스면 모두 허용</li>
<li><code>eq(T value)</code> - 정확히 일치하는 값</li>
<li><code>isNull()</code> - null 값</li>
<li><code>isNotNull()</code> - null이 아닌 값</li>
<li><code>argThat(Matcher&lt;T&gt; matcher)</code> - 커스텀 매처 사용</li>
</ul>
<h1 id="additionalmatchers">AdditionalMatchers</h1>
<p><code>ArgumentMatchers</code> 외에 추가적인 조건을 제공하는 유틸이다.
EasyMock과 호환성을 위해 유지된다고하며 <code>ArgumentMatchers</code>를 사용해 스텁 및 확인을 간단하게 유지하는 것이 좋다고 주석에 나와있다.</p>
<pre><code>    @Test
    public void getByIdTest_1() {
        // 10L Id의 Book 조회
        bookService.getById(10L);

        // 전달된 Id가 5보다 큰 값이였는지 확인
        Mockito.verify(bookRepository).findById(AdditionalMatchers.gt(5L));
    }

    @Test
    public void getByIdTest_2() {
        // 10L Id의 Book 조회
        bookService.getById(10L);

        // 전달된 Id가 10보다 작거나 같은 값이였는지 확인
        Mockito.verify(bookRepository).findById(AdditionalMatchers.leq(10L));
    }</code></pre><p><code>static import</code> 해봐도 가독성이 그닥 좋지않아보인다.
왜 추천하지 않는지 알겠다.</p>
<h2 id="주요-기능-1">주요 기능</h2>
<ul>
<li><code>gt(T value)</code> - 주어진 값보다 크면 허용</li>
<li><code>lt(T value)</code> - 주어진 값보다 작으면 허용</li>
<li><code>geq(T value)</code> - 주어진 값보다 크거나 같으면 허용</li>
<li><code>leq(T value)</code> - 주어진 값보다 작거나 같으면 허용</li>
<li><code>cmpEq(T value)</code> - 주어진 값과 compareTo로 비교한 동등 객체면 허용
등등</li>
</ul>
<h1 id="argumentcaptor">ArgumentCaptor</h1>
<p>Mockito에서 메소드 호출 시 전달된 인자를 캡처하여 나중에 검증할 수 있도록 도와주는 유틸이다.</p>
<blockquote>
<p> <strong>Warning</strong> (클래스 주석에 있는 경고)
ArgumentCaptor를 검증과 함께 사용하는 것이 좋지만 스텁과 함께 사용하는 것은 권장되지 않습니다.
스텁과 함께 ArgumentCaptor를 사용하면 캡터가 assert (일명 verify 또는 &#39;then&#39;) 블록 외부에서 생성되기 때문에 테스트 가독성이 떨어질 수 있습니다.
또한 스텁 된 메소드가 호출되지 않으면 인수가 캡처되지 않기 때문에 결함 지역화를 줄일 수 있습니다.</p>
</blockquote>
<pre><code>    @Test
    public void addTest_2() {
        // 인자 생성
        Book entity = Book.create(&quot;재밌는책&quot;, &quot;저자&quot;);

        // 엔티티 저장 메소드 호출
        bookService.add(entity);

        // 캡처 생성
        ArgumentCaptor&lt;Book&gt; entityCaptor = ArgumentCaptor.forClass(Book.class);

        // bookRepository.save() 메소드가 1번 호출되었는지 확인하고 인자 캡처
        Mockito.verify(bookRepository, Mockito.times(1)).save(entityCaptor.capture());

        // 캡처된 인자 값 검증
        Assertions.assertEquals(entity, entityCaptor.getValue());
    }</code></pre><h2 id="주요-기능-2">주요 기능</h2>
<ul>
<li><code>forClass(Class&lt;T&gt; clazz)</code> - 특정 클래스의 캡처를 생성</li>
<li><code>capture()</code> - 인수를 캡처 (verification안에서 사용, return: null or defaultValues)</li>
<li><code>getValue()</code> - 캡처된 단일 인자를 반환</li>
<li><code>getAllValues()</code> - 캡처된 여러 인자를 반환</li>
</ul>
<h1 id="additionalanswers">AdditionalAnswers</h1>
<p>Mockito 메소드 호출 시 메소드의 행위를 동적인 관리가 가능하게 도와주는 유틸이다.</p>
<pre><code>    @Test
    public void addTest_3() {
        // save() 호출 시 첫번째 인자를 그대로 반환
        Mockito.when(bookRepository.save(ArgumentMatchers.any())).then(AdditionalAnswers.returnsFirstArg());

        // 엔티티 저장 메소드 호출
        Book entity = Book.create(&quot;재밌는책&quot;, &quot;저자&quot;);
        Book savedEntity = bookService.add(entity);

        // 인자 객체와 반환 객체가 같은지 확인
        Assertions.assertEquals(entity, savedEntity);
    }</code></pre><h2 id="주요-기능-3">주요 기능</h2>
<ul>
<li><code>returnsFirstArg()</code> - 메소드 호출 시 첫번째 인자를 반환</li>
<li><code>returnsSecondArg()</code> - 메소드 호출 시 두번째 인자를 반환</li>
<li><code>answersWithDelay(Long delay, Answer answer)</code> - 메소드 호출 시 ms만큼 지연 후 동작</li>
<li><code>delegatesTo(Object delegate)</code> - 특정 객체로 메소드 호출을 위임 </li>
</ul>
]]></description>
        </item>
    </channel>
</rss>