<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://azurealstn.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://azurealstn.github.io/" rel="alternate" type="text/html" /><updated>2026-01-18T20:20:34+09:00</updated><id>https://azurealstn.github.io/feed.xml</id><title type="html">나의 IT Wiki 모음</title><subtitle>공부한 내용을 기록하고 참고하기 위한 나만의 블로그</subtitle><author><name>플라이팬</name></author><entry><title type="html">[강의 공부] 토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1</title><link href="https://azurealstn.github.io/%EA%B0%95%EC%9D%98%20%EA%B3%B5%EB%B6%80/2026/01/15/video-%ED%86%A0%EB%B9%84%EC%9D%98-%ED%81%B4%EB%A6%B0-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8F%84%EB%A9%94%EC%9D%B8.html" rel="alternate" type="text/html" title="[강의 공부] 토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1" /><published>2026-01-15T21:30:00+09:00</published><updated>2026-01-15T21:30:00+09:00</updated><id>https://azurealstn.github.io/%EA%B0%95%EC%9D%98%20%EA%B3%B5%EB%B6%80/2026/01/15/video-%ED%86%A0%EB%B9%84%EC%9D%98%20%ED%81%B4%EB%A6%B0%20%EC%8A%A4%ED%94%84%EB%A7%81%20-%20%EB%8F%84%EB%A9%94%EC%9D%B8</id><content type="html" xml:base="https://azurealstn.github.io/%EA%B0%95%EC%9D%98%20%EA%B3%B5%EB%B6%80/2026/01/15/video-%ED%86%A0%EB%B9%84%EC%9D%98-%ED%81%B4%EB%A6%B0-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8F%84%EB%A9%94%EC%9D%B8.html"><![CDATA[<h2 id="강의-소개">강의 소개</h2>

<p><img src="/assets/images/토비의%20클린%20스프링%20-%20도메인%20모델%20패턴과%20헥사고날%20아키텍처%20Part%201.png" alt="토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1" /></p>

<p>JDK 설정하는 여러 방법부터 도메인에 대한 설명 그리고 실제 코드레벨에서 적용까지 상세하게 설명해주셔서 좋았습니다. 클린코드를 적용하기 위해 테스트코드를 어떻게 작성할 수 있는지, 도메인 모델 아키텍처와 헥사고날의 사실과 오해에 대한 내용까지 깊이 있게 학습할 수 있습니다</p>

<h2 id="개발환경구성">개발환경구성</h2>

<h3 id="자바-버전-관리">자바 버전 관리</h3>

<ul>
  <li>IDE의 JDK 다운로드</li>
  <li>JDK 배포판 웹사이트에서 다운로드</li>
  <li>JDK 버전을 관리하는 도구 이용
    <ul>
      <li>대표적으로 SDKMAN 사용</li>
      <li>여러 Java / 개발 SDK 버전을 프로젝트별로 깔끔하게 관리해주는 도구</li>
      <li>프로젝트 루트내에 관리할 수 있어서 git에 올려서 같이 공유할 수 있음</li>
    </ul>
  </li>
</ul>

<h3 id="api-테스트">API 테스트</h3>

<ul>
  <li>Postman</li>
  <li>HTTPie
    <ul>
      <li>터미널을 사용한 간단하고 빠른 테스트 가능</li>
    </ul>
  </li>
</ul>

<h3 id="docker-compose">Docker Compose</h3>

<ul>
  <li>Spring Boot 3.1버전부터 Docker Compose support 사용 지원</li>
  <li>intellij에서 편리하게 Docker Compose를 설정하고 사용할 수 있다.</li>
  <li>실행시 반드시 도커 데몬 running 상태</li>
  <li>설정 변경시 도커 down 후에 다시 앱 실행</li>
</ul>

<h2 id="도메인">도메인</h2>

<ul>
  <li>사용자가 프로그램, 또는 소프트웨어 서비스를 적용하는 주제 영역을 도메인이라고 한다.</li>
  <li>모든 소프트웨어를 도메인이라고도 볼 수 있다.</li>
  <li>도메인은 현실 세계의 일부이고, 단순히 코드로 직접 옮길 수 없다. 따라서 도메인의 추상화인 <strong>도메인 모델</strong>을 만들어야 한다.</li>
  <li>도메인 모델은 소프트웨어가 해결하려는 특정 문제 영역(도메인)의 핵심지도이다.</li>
</ul>

<h3 id="도메인-주도-설계-ddd">도메인 주도 설계 (DDD)</h3>

<ul>
  <li>변경사항이 많은 도메인의 복잡성이 주는 문제를 해결하기 위해 나온 설계법이다.</li>
  <li>도메인 모델을 중심으로 개발한다.</li>
  <li>팀 안에서 도메인 모델에 기반한 단일 어휘체계를 만들고 이를 문서, 회의, 대화 그리고 코드까지 일관되게 사용한다. (보편 언어 - 유비쿼터스)</li>
</ul>

<h3 id="도메인-모델-만들기">도메인 모델 만들기</h3>

<ol>
  <li>도메인 전문가(실무자)에게 듣고 배우기</li>
  <li>‘중요한 것’들 찾기 (개념 식별)</li>
  <li>‘연결 고리’ 찾기 (관계 정의)</li>
  <li>‘것’들을 설명하기 (속성 및 기본 행위 명시)</li>
  <li>그려보기 (시각화) -&gt; 클래스 다이어그램, 개념 도식화(격벽을 사용한 개념도)</li>
  <li>토론하고 다듬기 (반복)</li>
</ol>

<h3 id="엔티티">엔티티</h3>

<ul>
  <li>도메인 안에 있는 대상이나 개념</li>
  <li>고유한 식별자(identity)를 가지고 이를 통해서 개별적으로 구분된다.</li>
  <li>생명주기를 가진다. 시간의 흐름에 따라 상태가 변경될 수 있다.</li>
</ul>

<h3 id="도메인-모델-패턴">도메인 모델 패턴</h3>

<ul>
  <li>도메인/비즈니스 로직을 구성하는 아키텍처 패턴의 한가지</li>
  <li>도메인 모델의 속성과 행위를 모두 포함하는 도메인의 오브젝트 모델이다.</li>
  <li>다른 방법인 트랜잭션 스크립트는 하나의 업무절차(TX)를 처리하기 위한 스크립트(메서드)를 만들고 비즈니스 로직을 순서대로 코드로 작성하는 방법이다.</li>
</ul>

<h3 id="도메인-서비스">도메인 서비스</h3>

<ul>
  <li>도메인 안에서 구현하기 애매한 기술적인 요소가 들어간 경우 도메인 서비스(Domain Service)를 사용한다.</li>
  <li>인터페이스를 만들어 메서드(행위)로만 관리하도록 한다.</li>
  <li>스프링의 <code class="language-plaintext highlighter-rouge">@Service</code>를 사용한 영역과는 다른 영역이다.</li>
  <li>외부에서 도메인 서비스를 주입하는 경우 생성자를 사용하는 것보다는 <code class="language-plaintext highlighter-rouge">static</code>을 사용한 정적 팩토리 메서드가 더 유용하다.</li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="nf">Member</span><span class="o">()</span> <span class="o">{</span>

<span class="o">}</span>

<span class="kd">public</span> <span class="kd">static</span> <span class="nc">Member</span> <span class="nf">create</span><span class="o">(</span><span class="nc">MemberCreateRequest</span> <span class="n">createRequest</span><span class="o">,</span> <span class="nc">PasswordEncoder</span> <span class="n">passwordEncoder</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">Member</span> <span class="n">member</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Member</span><span class="o">();</span>

    <span class="n">member</span><span class="o">.</span><span class="na">email</span> <span class="o">=</span> <span class="nc">Objects</span><span class="o">.</span><span class="na">requireNonNull</span><span class="o">(</span><span class="n">createRequest</span><span class="o">.</span><span class="na">email</span><span class="o">());</span>
    <span class="n">member</span><span class="o">.</span><span class="na">nickname</span> <span class="o">=</span> <span class="nc">Objects</span><span class="o">.</span><span class="na">requireNonNull</span><span class="o">(</span><span class="n">createRequest</span><span class="o">.</span><span class="na">nickname</span><span class="o">());</span>
    <span class="n">member</span><span class="o">.</span><span class="na">passwordHash</span> <span class="o">=</span> <span class="nc">Objects</span><span class="o">.</span><span class="na">requireNonNull</span><span class="o">(</span><span class="n">passwordEncoder</span><span class="o">.</span><span class="na">encode</span><span class="o">(</span><span class="n">createRequest</span><span class="o">.</span><span class="na">password</span><span class="o">()));</span>

    <span class="n">member</span><span class="o">.</span><span class="na">status</span> <span class="o">=</span> <span class="nc">MemberStatus</span><span class="o">.</span><span class="na">PENDING</span><span class="o">;</span>

    <span class="k">return</span> <span class="n">member</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="값-객체value-object">값 객체(Value Object)</h3>

<ul>
  <li>도메인 모델에서 식별자가 필요하지 않고 속성/값으로만 구별되는 오브젝트</li>
  <li>엔티티가 너무 많은 책임을 가지는 것을 방지</li>
  <li>불변 객체이므로, 생성 이후에 상태가 변하지 않고 변경이 필요하면 새로운 객체로 교체한다.</li>
  <li>값 객체에서 동등성 비교를 하기 위해서 반드시 <code class="language-plaintext highlighter-rouge">equals</code>와 <code class="language-plaintext highlighter-rouge">hashCode</code>를 재구현해야 한다.</li>
  <li>record를 사용한다면 record 클래스가 대신 이를 구현해준다.</li>
</ul>

<h2 id="헥사고날-아키텍처">헥사고날 아키텍처</h2>

<ul>
  <li>계층형 아키텍처의 단방형 비대칭 구조가 아닌 <strong>대칭형(symmetric)</strong> 아키텍처</li>
  <li>위 아래, 좌 우가 아닌 애플리케이션의 내부와 외부 세계라는 대칭 구조를 가진다.</li>
  <li>그리기 쉬운 대표적인 도형인 <strong>육각형(hexagonal)</strong>으로 설명</li>
</ul>

<h3 id="4-계층-아키텍처">4 계층 아키텍처</h3>

<p><img src="/assets/images/4계층%20아키텍처.png" alt="4 계층 아키텍처" /></p>

<ul>
  <li>도메인 주도 개발(DDD)</li>
  <li>결국 도메인 주도 개발 패턴을 지킨 아키텍처가 헥사고날 아키텍처가 된다.</li>
</ul>

<h3 id="특징과-장점">특징과 장점</h3>

<ul>
  <li>테스팅 - 운영 시스템에 연결되지 않고 애플리케이션 테스트</li>
  <li>애플리케이션과 상호작용하는 액터(Actor)가 바뀌더라도 다시 빌드하지 않고 테스트</li>
  <li>기술정보가 도메인 로직 안으로 노출되지 않도록 보호한다.</li>
  <li>컴포넌트를 각각 개발하고 연결하는 방식으로 큰 시스템을 분리할 수 있다. (MSA)</li>
  <li>외부 연결을 다른 것으로 변경할 수 있다.</li>
  <li>의존된 기술 요소를 제거하여 도메인 설계에 집중한다.</li>
</ul>

<h3 id="포트와-어댑터-아키텍처">포트와 어댑터 아키텍처</h3>

<ul>
  <li>도메인(핵심 로직)을 중심에 두고, 외부 시스템(DB, UI, API 등)은 전부 교체 가능한 어댑터로 만든다</li>
  <li>Domain: 순수 비즈니스 로직</li>
  <li>Port: 도메인이 외부와 소통하는 인터페이스
    <ul>
      <li>포트는 도메인 안에 존재</li>
      <li>기술(DB, HTTP, Kafka 등)을 전혀 모름</li>
    </ul>
  </li>
  <li>Adapter: Port를 구현해서 실제 기술과 연결
    <ul>
      <li>어댑터는 도메인 바깥에 있음</li>
      <li>기술 의존 코드 포함 가능</li>
    </ul>
  </li>
</ul>

<h3 id="정리">정리</h3>

<ul>
  <li>외부에서 내부로 향하는 일종의 계층 구조</li>
  <li>코드의 의존 방향은 내부로만 향한다
    <ul>
      <li>어댑터 -&gt; 애플리케이션 -&gt; 도메인</li>
      <li>반대는 절대로 안된다</li>
    </ul>
  </li>
  <li>단, 사용의 흐름은 비대칭적이다.</li>
  <li>헥사고날 아키텍처의 핵심은 테스트코드를 작성하는 것</li>
  <li>CQS(Command Query Seperate) 패턴 적용
    <ul>
      <li><code class="language-plaintext highlighter-rouge">MemberFinder</code>, <code class="language-plaintext highlighter-rouge">MemberRegister</code> 인터페이스 분리</li>
    </ul>
  </li>
</ul>

<h2 id="jpa-모델과-도메인-모델">JPA 모델과 도메인 모델</h2>

<ul>
  <li>도메인 모델은 DB와 매핑되는 데이터 모델과 다르며 이를 분리해야 한다.
    <ul>
      <li>Ex1) <code class="language-plaintext highlighter-rouge">Member</code>, <code class="language-plaintext highlighter-rouge">MemberEntity</code> 분리하여 관리</li>
      <li>Ex2) <code class="language-plaintext highlighter-rouge">MemberRepository</code>, <code class="language-plaintext highlighter-rouge">MemberRepositoryJpaAdapter</code></li>
    </ul>
  </li>
  <li>레거시 DB가 너무 오래된 경우 데이터 모델과 도메인 모델이 너무 다르기 때문에 분리하는 것이 좋다.
    <ul>
      <li>도메인 모델 설계가 안정적이면 그 이후에 데이터 마이그레이션 진행</li>
    </ul>
  </li>
  <li>JPA가 아닌 NoSQL 등 데이터 저장 기술이 바뀌는 경우에 유연하다.
    <ul>
      <li>다만, 데이터 자장 기술이 바뀌는 경우가 거의(?) 없을 것이다</li>
    </ul>
  </li>
</ul>

<h3 id="분리하지-않아도-되는-경우">분리하지 않아도 되는 경우</h3>

<ul>
  <li>도메인 모델과 데이터 모델이 거의(?) 동일한 경우</li>
  <li>도메인 코드에 JPA 애노테이션은 기술 의존적이므로 분리해야 한다.
    <ul>
      <li>JPA 애노테이션이 붙었다고 기술에 의존적일까?</li>
      <li>도메인 모델과 데이터 모델이 동일한 상태에서 JPA가 아닌 다른 기술을 쓴다해도 충분히 대체 가능</li>
      <li>애노테이션은 단순 주석일뿐.</li>
      <li>클래스의 증가는 복잡성을 증가시킨다</li>
    </ul>
  </li>
  <li>JPA 기술의 정체성
    <ul>
      <li>JPA의 엔티티는 경량 영속 도메인 오브젝트</li>
    </ul>
  </li>
</ul>

<blockquote>
  <p>중요한 것은 팀의 컨벤션을 따르는 것이다. 정답은 없다. 새로 들어온 내가 “도메인 모델과 데이터 모델을 분리해야 한다고 배웠으니까 이렇게 사용해” 라고 강요하는건 바보짓이다. 팀이 이미 안정적으로 사용하고 있는 컨벤션이 있다면 따르는 것이고, 서비스 안정화 하는 것이 먼저다. 다만, 추후 안정된 후에 의견을 제시할 수는 있을 것이다.</p>
</blockquote>

<ul>
  <li>그럼에도 JPA 관련 애노테이션이 덕지덕지 붙어있어서 순수 도메인 코드를 보기가 힘들 수도 있다. (그래서 분리하려는 경향이 있다)</li>
  <li><code class="language-plaintext highlighter-rouge">orm.xml</code> 파일을 만들어서 관리하는 방법으로 해결할 수 있다.
    <ul>
      <li>그러면 기존 JPA 엔티티의 애노테이션들을 모두 제거할 수 있다.</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">@Id</code>가 붙은 컬럼을 슈퍼클래스로 만드는 경우 반드시 <code class="language-plaintext highlighter-rouge">equals</code>와 <code class="language-plaintext highlighter-rouge">hashCode</code>를 재구현해야 한다.
    <ul>
      <li>클래스에 <code class="language-plaintext highlighter-rouge">@MappedSuperclass</code> 추가 필요</li>
    </ul>
  </li>
</ul>

<h2 id="애그리거트">애그리거트</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Member</code> : <code class="language-plaintext highlighter-rouge">MemberDetail</code> -&gt; 1:1 관계</li>
  <li>각각 엔티티에 대해 각각의 Repository로 분리하여 관리하므로 복잡성이 증가한다</li>
  <li><code class="language-plaintext highlighter-rouge">Member</code>와 <code class="language-plaintext highlighter-rouge">MemberDetail</code>는 매우 밀접한 관계이므로 하나의 그룹으로 만든 것을 Member Aggregate, 애그리거트라고 한다.
    <ul>
      <li><code class="language-plaintext highlighter-rouge">Member</code>: Aggregate Root</li>
      <li><code class="language-plaintext highlighter-rouge">MemberDetail</code>: Aggregate Member</li>
    </ul>
  </li>
  <li>Member Aggregate를 가지고 하나의 Repository로 관리한다</li>
  <li>정의: 데이터 변경의 목적을 위해 <strong>하나의 단위로 취급</strong>되는 연관된 객체들의 클러스터</li>
</ul>

<h3 id="애그리거트-적용-방법">애그리거트 적용 방법</h3>

<ul>
  <li>JPA의 cascading을 적절하게 활용</li>
  <li>Repository는 애그리거트 단위
    <ul>
      <li><code class="language-plaintext highlighter-rouge">Repository&lt;T, ID&gt; : T = Aggregate Root</code></li>
    </ul>
  </li>
  <li>다른 애그리거트의 참조는 Aggregate Root를 통해서만 한다</li>
  <li>성능에 부담을 주기 때문에 <code class="language-plaintext highlighter-rouge">lazy loading</code> 활용 필요</li>
  <li><strong>정답은 없다</strong></li>
</ul>

<h2 id="entity-vs-dto">Entity vs DTO</h2>

<p><img src="/assets/images/애플리케이션%20계층.png" alt="애플리케이션 계층" /></p>

<h3 id="애플리케이션-포트의-리턴-타입은">애플리케이션 포트의 리턴 타입은?</h3>

<ul>
  <li>DTO를 리턴하는 방식의 가장 큰 문제는 <strong>프레젠테이션 로직이 애플리케이션 레이어로 침투</strong>하는 것이다</li>
  <li>뷰(view) 로직에 따라 엔티티에서 복제되는 DTO의 구성이 달라진다</li>
  <li>DTO를 통해 애플리케이션 계층이 어댑터 계층의 로직에 의존한다</li>
  <li>이는 완화된 아키텍처를 깨는 행위이다</li>
  <li>다만, 복잡한 리포트성 조회 결과는 DTO를 리턴한다</li>
</ul>

<p>따라서 애플리케이션(서비스) 계층의 리턴타입은 가능하다면 엔티티로 한다 - 애그리거트 루트 활용</p>]]></content><author><name>플라이팬</name></author><category term="강의 공부" /><category term="Backend" /><category term="도메인" /><summary type="html"><![CDATA[도메인 모델 패턴과 헥사고날 아키텍처를 활용해서 도메인 중심의 초기 개발 단계에서 필요한 기술과 개발전략을 익힐 수 있습니다.]]></summary></entry><entry><title type="html">[강의 공부] 제미니의 개발실무 - 커머스 백엔드 기본편</title><link href="https://azurealstn.github.io/%EA%B0%95%EC%9D%98%20%EA%B3%B5%EB%B6%80/2026/01/04/video-%EC%BB%A4%EB%A8%B8%EC%8A%A4-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B8%B0%EB%B3%B8%ED%8E%B8.html" rel="alternate" type="text/html" title="[강의 공부] 제미니의 개발실무 - 커머스 백엔드 기본편" /><published>2026-01-04T20:30:00+09:00</published><updated>2026-01-04T20:30:00+09:00</updated><id>https://azurealstn.github.io/%EA%B0%95%EC%9D%98%20%EA%B3%B5%EB%B6%80/2026/01/04/video-%EC%BB%A4%EB%A8%B8%EC%8A%A4%20%EB%B0%B1%EC%97%94%EB%93%9C%20%EA%B8%B0%EB%B3%B8%ED%8E%B8</id><content type="html" xml:base="https://azurealstn.github.io/%EA%B0%95%EC%9D%98%20%EA%B3%B5%EB%B6%80/2026/01/04/video-%EC%BB%A4%EB%A8%B8%EC%8A%A4-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B8%B0%EB%B3%B8%ED%8E%B8.html"><![CDATA[<h2 id="강의-소개">강의 소개</h2>

<p><img src="/assets/images/제미니의%20개발실무%20-%20커머스%20백엔드%20기본편.png" alt="제미니의 개발실무 - 커머스 백엔드 기본편" /></p>

<p>현재 상황이 사용자가 10000명, 상품이 10000개 이상이고, 꾸준히 성장하고 있는 상황에서 개발자가 단 3명밖에 없다면 어떤 고민을 해야하고, 어떻게 개발을 진행해야 할까? 클린코드, 헥사고날 아키텍처도 좋지만 결국에는 일이 잘 되게 하는 것이 목적이다. 이 강의는 그러한 트레이드 오프속에 고민하고 문제해결을 위주로 생각하도록 도와주는 강의로써 좋은 강의입니다!</p>

<h2 id="상품목록">상품목록</h2>

<p>ProductService 라는 클래스 안에 굳이 ProductFinder 클래스를 따로 만들어 둔 이유</p>

<ul>
  <li>조회(Query) 책임을 서비스에서 분리시키기 위해.</li>
</ul>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">ProductService</span> <span class="p">{</span>
    <span class="k">fun</span> <span class="nf">findProduct</span><span class="p">(</span><span class="o">..</span><span class="p">.)</span> <span class="p">{</span> <span class="o">..</span><span class="p">.</span> <span class="p">}</span>
    <span class="k">fun</span> <span class="nf">findProductsByCategory</span><span class="p">(</span><span class="o">..</span><span class="p">.)</span> <span class="p">{</span> <span class="o">..</span><span class="p">.</span> <span class="p">}</span>
    <span class="k">fun</span> <span class="nf">createProduct</span><span class="p">(</span><span class="o">..</span><span class="p">.)</span> <span class="p">{</span> <span class="o">..</span><span class="p">.</span> <span class="p">}</span>
    <span class="k">fun</span> <span class="nf">updateProduct</span><span class="p">(</span><span class="o">..</span><span class="p">.)</span> <span class="p">{</span> <span class="o">..</span><span class="p">.</span> <span class="p">}</span>
    <span class="k">fun</span> <span class="nf">deleteProduct</span><span class="p">(</span><span class="o">..</span><span class="p">.)</span> <span class="p">{</span> <span class="o">..</span><span class="p">.</span> <span class="p">}</span>
    <span class="k">fun</span> <span class="nf">validateProduct</span><span class="p">(</span><span class="o">..</span><span class="p">.)</span> <span class="p">{</span> <span class="o">..</span><span class="p">.</span> <span class="p">}</span>
    <span class="k">fun</span> <span class="nf">calculatePrice</span><span class="p">(</span><span class="o">..</span><span class="p">.)</span> <span class="p">{</span> <span class="o">..</span><span class="p">.</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<ul>
  <li>조회 + 생성/수정 + 비즈니스 규칙 + 검증 모든 것이 한 클래스에 몰리면
    <ul>
      <li>읽기가 어렵고</li>
      <li>테스트가 어려워지고</li>
      <li>한 클래스에 책임이 많아진다</li>
    </ul>
  </li>
  <li>그래서 이러한 읽기(find) 계열은 따로 분리하여 은닉화시켜두고, ProductService 클래스에서는 ProductFinder의 메서드를 호출하도록 변경한다.</li>
</ul>

<blockquote>
  <p>여기서 핵심은 한 클래스에 책임이 많다면 분리시키는 것이다. CQRS와 연관이 있다.</p>
</blockquote>

<ul>
  <li>상품과 카테고리의 관계
    <ul>
      <li>상품 -&gt; 카테고리에 의존하면 안된다.
        <ul>
          <li>카테고리의 정책이 변경되면 상품의 정책이 흔들리게 된다. (중요!)</li>
        </ul>
      </li>
      <li>매핑 테이블(ProductCategory)을 만들어 Category를 의존하게 하면 된다.</li>
      <li>Product는 ProductCategory을 통해 접근한다. 즉, Product는 Category를 모른다.</li>
    </ul>
  </li>
  <li>뭔가 다양한 분류를 생각해야될 것 같다면 Enum 클래스를 적극적으로 활용하자. 기능을 확장하는데에 도움이 된다.</li>
</ul>

<blockquote>
  <p>도메인을 설계할 때는 DB 테이블을 먼저 생각하면 안된다. 개념적으로 접근하여 이 클래스가 어떤 역할을 수행하고, 어떤 메시지를 가지고 있는지를 고민한다. 그러면 저절로 어떤 클래스가 또 분리되어 필요한지 고민하게 된다.</p>
</blockquote>

<h2 id="리뷰">리뷰</h2>

<p>리뷰(Review)에도 정책이 다양하다. 여기서의 핵심도 책임을 분리하는 것이다. 여기서 마찬가지로 <code class="language-plaintext highlighter-rouge">ReviewFinder</code> 클랙스가 있을 것이고, 리뷰를 작성하면 포인트를 지급해주는 정책이 있다면 이러한 정책을 관리하는 클래스를 하나 또 만드는 것이다. (<code class="language-plaintext highlighter-rouge">ReviewPolicyValidator</code>) 만약 정책이 바뀐다면 <code class="language-plaintext highlighter-rouge">ReviewPolicyValidator</code> 클래스만 변경하면 되는 것이다. 하나의 <code class="language-plaintext highlighter-rouge">ReviewService</code> 이러한 모든게 담겨있게 된다면 변경해야 할 곳이 이곳저곳 정신없을 것이다. (테스트도 어려워질 것이다.)</p>

<p>만약 정책이 “하나의 주문에 하나의 리뷰을 달 수 있고, 포인트를 지급한다” 라고 되어 있다면</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Review</code> -&gt; <code class="language-plaintext highlighter-rouge">Order</code> 의존</li>
  <li><code class="language-plaintext highlighter-rouge">Review</code> -&gt; <code class="language-plaintext highlighter-rouge">Point</code> 의존</li>
  <li>여전히 <code class="language-plaintext highlighter-rouge">Product</code>는 <code class="language-plaintext highlighter-rouge">Review</code>를 모른다.</li>
</ul>

<p>그 밖에 QnA, 포인트, 쿠폰, 장바구니, 결제 등등 다양한 도메인을 설계하기 전에 먼저 개념적으로 접근하는 것이 좋다. 실제 어떤 제약이 있고, 어떤 정책이 있다는 전제하에 어디까지 접근할 수 있는지 그러한 고민들을 해나가는 것이다. 이러한 고민들을 기획자, 프론트엔드 등 팀원들과 얘기하면서 문제를 해결해나가는 것이 중요하다. 아키텍처, 기술적인 얘기도 중요하지만, 개념적으로 어떻게 접근해서 문제들이 발생할 수 있는지, 그러면 그 문제들을 어떻게 해결해나갈지 깊이 있게 생각해보는 것이다.</p>

<h2 id="cart-장바구니">Cart (장바구니)</h2>

<p>도메인 설계하기 전에 DB테이블을 기준으로 설계하지 말고 개념적으로 설계하라는 이유가 있다. 만약 DB테이블을 기준으로 한다면 Cart 라는 테이블을 만들 것이다. 하지만 개념적으로 보면 Cart라는 것은 하나이고, Cart 안에 item들을 담을 수 있다. 그래서 CartItemEntity를 만들 수 있고, Cart라는 DB테이블과 관계없는 논리 클래스를 만들 수 있다. Cart라는 클래스는 CartItem들과 Product들을 담는 논리적인 클래스인 것이다. (사용자는 마트를 가서 상품들을 담을 때 장바구니 2~3씩 들고 다니지 않다. 한 사람당 하나씩 들고다닌다는 가정하에 설정)</p>

<p>그러면 CartEntity를 만들지 않은 이유는 결국 장바구니의 관점을 봤을 때 CartItem에 대한 정보들이 더 중요하다. Item의 수량이나 날짜 등의 중요한 정보가 포함되어 있기 때문에 CartItemEntity로 만드는 것이다. (CartItem으로 핸들링 하기에 더 적합하다.)</p>

<h2 id="cancel-취소">Cancel (취소)</h2>

<p>결제(Payment)에서 취소 도메인이 있다. PaymentEntity에 취소 상태 컬럼을 추가해도 되지만, 이러면 취소 행위가 발생할 때마다 PaymentEntity에 Update를 발생시킨다. 그러면 PaymentEntity가 헤비해지고, PaymentEntity 자체에 부담이 가진다. 그리고 PaymentEntity에 의존성이 많아져 응집도가 떨어지게 된다. 그래서 CancelEntity를 만들어서 취소에 대한 정보를 자체적으로 관리하도록 만든다. 이러면 정산이나 데이터 분석시 좀 더 도움이 되고, 단순화된다. (그렇지만 규모에 따라 트레이드 오프를 고려해볼 필요가 있다.)</p>

<h2 id="settle-정산">Settle (정산)</h2>

<p>가맹점들에게 정산해주거나 돈 관련 비즈니스를 할 때 필요한 도메인이다. 돈 관련 도메인이나 보니 복잡하지만 그만큼 백엔드 개발자들이 도전하고 싶어하는 도메인이다. 정산은 보통 배치를 돌려서 관리한다. 정산에 필요한 도메인은 Payment, OrderItem, Cancel 데이터가 필요하다. 규모가 크지 않다면 대부분의 시간 베이스로 정산하는 것이 일반적이다. 다만 단점이 있는데,</p>

<ul>
  <li>환불/취소가 늦게 발생하면 보정 필요</li>
  <li>실시간 수익 확인 어려움</li>
</ul>

<p>규모가 크다면 이벤트 기반이나 상태 기반으로 개선할 수도 있다.</p>

<p>설계시 Settlement 클래스만 만들어도 되겠지만 Settlement의 의존성을 덜어주기 위해 SettlementTarget 클래스를 별도로 만든다. 그러면 Settlement -&gt; SettlementTarget만 보면 된다. 결과적으로 어떠한 도메인의 데이터가 추가되어도 SettlementTarget이 처리해주고, Settlement는 아무런 영향을 받지 않는다.</p>

<h3 id="정리">정리</h3>

<p>지금까지의 도메인들을 개념적으로 격벽을 사용하여 그림을 그려보면 어떤 도메인이 우리 서비스에서 중요하게 보고 있는지를 알 수 있다. 그래서 혹시 중요한 도메인이 다른 서브 도메인에 의존적인 관계가 있지 않은지, 개선할 수 없는지를 판별할 수 있을 것이다.</p>]]></content><author><name>플라이팬</name></author><category term="강의 공부" /><category term="Backend" /><summary type="html"><![CDATA[개발실무에서 진짜 필요한 힘을 키우는데 집중합니다.]]></summary></entry><entry><title type="html">[책 공부] AWS 잘하는 개발자 되기</title><link href="https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/21/book-AWS-%EC%9E%98%ED%95%98%EB%8A%94-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%90%98%EA%B8%B0.html" rel="alternate" type="text/html" title="[책 공부] AWS 잘하는 개발자 되기" /><published>2025-12-21T17:30:00+09:00</published><updated>2025-12-21T17:30:00+09:00</updated><id>https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/21/book-AWS%20%EC%9E%98%ED%95%98%EB%8A%94%20%EA%B0%9C%EB%B0%9C%EC%9E%90%20%EB%90%98%EA%B8%B0</id><content type="html" xml:base="https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/21/book-AWS-%EC%9E%98%ED%95%98%EB%8A%94-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%90%98%EA%B8%B0.html"><![CDATA[<h2 id="책-소개">책 소개</h2>

<p><img src="/assets/images/AWS_잘하는_개발자_되기.png" alt="AWS 잘하는 개발자 되기" /></p>

<p>AWS에서 제공하는 서비스들이 어떤 것들이 있는지, 왜 생기게 되었는지, 어떻게 설정하면 좋은지에 대한 내용을 배울 수 있습니다. 만약 AWS에 익숙하다면 굳이 이 책을 보지 않아도 되지만 사용해보지 않은 경우에는 한번쯤 볼만한 책입니다. (하지만 요새는 AI가..)</p>

<h2 id="iam">IAM</h2>

<p>AWS에서 권한 관리는 주로 AWS IAM을 활용하여 이루어지낟. 예외로 데이터 관리와 보안에 아마존 S3와 백업과 복원 등을 지원하는 AWS 백업의 백업 볼트(Backup Vault)와 같은 서비스를 이용해 자체적인 엑세스 정책을 설정하여 관리할 수도 있다.</p>

<p>IAM(Identity and Access Management)은 AWS 계정 내에서 각 사용자, 그룹 또는 리소스에 대한 권한을 중앙 집중적으로 관리한다. 이를 통해 AWS에 대한 엑세스를 제어하고 각 사용자에게 필요한 작업만 수행할 수 있는 권한을 부여한다. 예를들면, EC2 인스턴스에 접근할 수 있는 권한, RDS에 접근할 수 있는 권한 같은 것들이 있다.</p>

<ul>
  <li>IAM 사용자</li>
  <li>IAM 그룹</li>
  <li>IAM 정책</li>
  <li>IAM 역할</li>
</ul>

<h2 id="ec2">EC2</h2>

<p>아마존 EC2(AWS Elastic Compute Cloud)는 AWS에서 제공하는 가상 클라우드 서버를 의미한다. 아마존 EC2를 이용하면 단 몇분으로 서버를 구축하며, 물리서버 구축과 환경 구축의 어려움을 해소할 수 있다.</p>

<ul>
  <li>AMI(Amazon Machine Image): EC2 인스턴스를 시작하는 템플릿</li>
  <li>인스턴스 유형: CPU, 메모리와 같은 하드웨어의 조합</li>
  <li>스토리지 옵션: 인스턴스 스토어 볼륨, EBS 볼륨으로 구분되며, EC2 인스턴스의 저장 공간</li>
  <li>보안그룹: EC2 인스턴스에 대한 네트워크 트래픽을 제어하는 가상 방화벽</li>
  <li>키 페어: EC2 인스턴스 접속에 사용되는 키</li>
</ul>

<blockquote>
  <p>퍼블릭 서브넷에 생성된 EC2 인스턴스는 외부에서 접근할 수 있어야 하므로 퍼블릭 IP주소와 프라이빗 IP주소 모두 할당된다. 그러나 퍼블릭 IP주소는 EC2 인스턴스를 재부팅하거나 재시작할 때마다 주소가 변경될 수 있는데 이는 AWS에서 자원을 효율적으로 관리하기 위한 동적 IP 할당 방식이다. 이 동적 IP주소를 변하지 않고 고정시킬 수 있는데, 이를 탄력적 IP주소라 한다.</p>
</blockquote>

<h2 id="rds">RDS</h2>

<p>아마존 RDS(Amazon Relational Database Service)는 AWS에서 관계형 데이터베이스를 제공하는 서비스를 말한다. 데이터베이스의 백업, 소프트웨어 패치, 장애 감지 및 복구를 자동으로 관리해주며, 사용자는 애플리케이션만 집중할 수 있도록 도와준다.</p>

<ul>
  <li>엔진 유형: MySQL, MariaDB, PostgreSQL …</li>
  <li>인스턴스 클래스:
    <ul>
      <li>스탠다드: 범용 DB</li>
      <li>메모리 최적화: 메모리 소비가 높은 애플리케이션에 최적화된 DB</li>
      <li>버스터블: 최대 CPU 사용량까지 버스트 가능한 DB</li>
    </ul>
  </li>
  <li>스토리지 유형:
    <ul>
      <li>범용 SSD: 비용, 성능면에서 밸런스가 잡혀 있으며, 폭 넓게 사용 가능</li>
      <li>프로비저닝된 IOPS(io1): 낮은 대기 시간과 높은 I/O 성능이 요구되는 DB에 적합</li>
      <li>마그네틱: 데이터의 액세스 빈도가 낮은 상황에서 사용되지만 구세대 유형이기 때문에 권장X</li>
      <li>DB 그룹:
        <ul>
          <li>서브넷 그룹: 서브넷의 집합</li>
          <li>파라미터 그룹: 시간(time_zone), 문자집합(characterset) 등을 관리</li>
          <li>옵션 그룹: 스냅샷 관리</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<h2 id="s3">S3</h2>

<p>S3는 객체 스토리지 서비스이다. 객체 스토리지는 객체라고 하는 일정한 형태나 형식, 틀이 정해지지 않은 비정형 형식의 데이터를 저장하고 관리하는 스토리지를 의미한다. 빚정형 데이터를 저장하기 때문에 어떠한 파일 형식이든 상관없이 저장할 수 있다.</p>

<p>아마존 S3는 버킷(Bucket)이라는 스토리에 객체를 저장한다. 버킷은 전 리전에 걸쳐 고유한 이름을 가지며, 다른 리전에서 같은 이름의 버킷을 사용할 수 없다. 객체 또한 버킷 내부에서 고유한 키를 가지며, 버킷 내부에서 같은 이름의 키 객체를 저장하면 덮어쓰기가 일어난다.</p>

<h2 id="라우트53">라우트53</h2>

<p>AWS에서는 도메인 관리를 위한 서비스로 아마존 라우트53을 제공한다. 이 서비스를 사용해 도메인을 구매하고, AWS에서 구축한 웹 서버와 연결해 효율적으로 트래픽을 전달할 수 있다. 또한 라우트53는 회사내부에서 사용할 수 있는 비공개 도메인과 인터넷에서 사용할 수 있는 공용 도메인을 지정할 수 있다.</p>

<h2 id="cdn">CDN</h2>

<p>아마존 클라우드프론트는 사용자에게 동영상, 이미지와 같은 정적 컨텐츠를 사용자에게 빠르고 안전하게 배포하는 CDN(Content Delivery Network) 서비스이다. 사용자는 에지 로케이션에서 컨텐츠를 빠른 속도로 안전하게 전송받을 수 있으며, 이 아마존 클라우드프론트는 AWS에서 제공하는 다양한 서비스와 연동할 수 있다.</p>

<h2 id="프론트엔드-서비스">프론트엔드 서비스</h2>

<h3 id="amplify">Amplify</h3>

<p>AWS에서는 앰플리파이(Amplify)라는 웹 애플리케이션 배포용 프론트 서비스를 제공하고 있으며, 이 서비스를 사용하면 AWS 클라우드에 쉽고 빠르게 웹 애플리케이션을 배포할 수 있다. 개발자는 프론트엔드 개발을 가속화할 수 있으며, 서버리스 아키텍처로 인한 비용 절감과 웹 애플리케이션 개발을 단순화할 수 있다. 또한 다른 클라우드 기능과의 간편한 연동할 수 있다.</p>

<h3 id="코그니토">코그니토</h3>

<p>아마존 코그니토는 AWS에서 사용자 관리와 인증 기능을 제공하는 서비스이다. 코그니토를 사용하면 별도로 사용자를 관리할 프로그램을 구성할 필요없이 AWS에서 사용자를 관리할 수 있다. 또한 사용자는 웹 애플리케이션에 로그인할 시, 아마존 코그니토를 사용해 사용자의 인증 정보를 검증할 수 있으므로, 개발자는 보안 및 사용자 관리에 대한 부담을 줄이고 효율적으로 웹 애플리케이션을 구축할 수 있다.</p>

<h2 id="백엔드-서비스">백엔드 서비스</h2>

<h3 id="부하-분산-서비스-탄력적-로드-밸런서">부하 분산 서비스, 탄력적 로드 밸런서</h3>

<p>AWS에서는 부하 분산 서비스인 탄력적 로드 밸런서를 제공하고 있다. 탄력적 로드 밸런서를 사용한다면, 사용자가 급증하더라도 적절하게 트래픽을 부하 분산 할 수 있으며, 안정적으로 서버를 운영할 수 있다. 탄력적 로드 밸런서는 웹 서버 앞에 배치되며, 사용자는 이 탄력적 로드 밸런서를 경유해 웹사이트로 접근하게 된다.</p>

<p>사용자가 인터넷을 통해 웹사이트에 접근할 수 있도록 탄력적 로드 밸런서는 퍼블릭 서브넷에 배치된다. 탄력적 로드 밸런서에서는 이 서비스에 접근할 수 있는 DNS 이름을 별도로 제공하고 있으며, 아마존 라우트53와 연동해 별도록 도메인을 할당할 수 있다.</p>

<h3 id="아마존-ec2-오토스케일링">아마존 EC2 오토스케일링</h3>

<p>오토스케일링은 스케일업과 스케일아웃 그리고 스케일인을 구현하는데 사용된다. 이를 활용해 클라우드 서버를 최적화할 수 있다. EC2 오토스케일링은 오토스케일링 그룹을 생성해 EC2 인스턴스에 대한 스케일업, 스케일아웃, 스케일인을 구현할 수 있으며, 오토스케일링 그룹을 생성하려면 기본적인 EC2 인스턴스의 구성을 설정할 필요가 있다. 이 EC2 구성을 설정하려면 사용하는 것이 시작 템플릿이다.</p>

<h3 id="ecs">ECS</h3>

<p>아마존 ECS(Elastic Container Service)는 AWS에서 도커 컨테이너를 배포하고 운영, 관리하는 완전 관리형 컨테이너 서비스이다. ECS는 정적인 이미지를 보관하는 아마존 ECR(Elastic Container Registry) 서비스를 제공하고 있다. ECR에 푸시한 정적인 이미지를 동적인 이미지로 변환해 하나의 컨테이너로 사용하기 위해 ECS를 사용한다.</p>

<h3 id="aws-람다">AWS 람다</h3>

<p>AWS 람다는 AWS에서 제공하는 서버리스 서비스로 서버를 구축하지 않고도 작성한 코드를 실행할 수 있는 이벤트 구동형 프로그램 실행 환경이다. AWS 람다는 이벤트 기반 아키텍처를 기반으로 하며, 특정 이벤트가 발생하면 해당 이벤트를 처리하는 별도의 함수를 생성해 실행하는 서버리스 컴퓨팅 서비스이다.</p>

<h3 id="클라우드워치">클라우드워치</h3>

<p>AWS에서 아마존 EC2, RDS와 같은 리소스를 실시간으로 감시하는 아맞존 클라우드워치 서비스를 제공한다. 클라우드워치는 실시간으로 모니터링을 수행하며, 로그, 메트릭, 이벤트를 통해 모니터링 및 운영 데이터를 수집한다. 이런 데이터는 대시보드를 통해 정보를 받아볼 수 있으며, 수집된 데이터를 바탕으로 향후 서비스 운영 방침을 결정하는데 도움을 준다.</p>

<h3 id="waf">WAF</h3>

<p>AWS에서는 허용하지 않는 사용자의 접근을 차단하기 위해 보안 그룹을 제공하고 있다. 이런 보안 그룹은 EC2, RDS에서 설정할 수 있으며, 네트워크 계층과 전송 계층에서 효율적인 보호 기능을 제공한다. 반면에 사용자가 개발한 애플리케이션을 외부 공격으로부터 보호하려면 보안 그룹만으로는 부족하다. 이런 상황에 대비해 AWS는 AWS WAF를 제공하고 있다. 아마존 클라우드프론트, 애플리케이션 로드밸런서와 같은 서비스에 연결할 수 있는 서비스로 공격을 방어하는데 효과적인 서비스이다.</p>]]></content><author><name>플라이팬</name></author><category term="책 공부" /><category term="AWS" /><category term="클라우드" /><summary type="html"><![CDATA[AWS ALL Certifications 엔지니어가 엄선한 최신 테크 트리로 탄탄히 배우기]]></summary></entry><entry><title type="html">[책 공부] 주니어 백엔드 개발자가 반드시 알아야 할 실무 지식</title><link href="https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/21/book-%EC%A3%BC%EB%8B%88%EC%96%B4-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EB%B0%98%EB%93%9C%EC%8B%9C-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-%EC%8B%A4%EB%AC%B4-%EC%A7%80%EC%8B%9D.html" rel="alternate" type="text/html" title="[책 공부] 주니어 백엔드 개발자가 반드시 알아야 할 실무 지식" /><published>2025-12-21T17:30:00+09:00</published><updated>2025-12-21T17:30:00+09:00</updated><id>https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/21/book-%EC%A3%BC%EB%8B%88%EC%96%B4%20%EB%B0%B1%EC%97%94%EB%93%9C%20%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80%20%EB%B0%98%EB%93%9C%EC%8B%9C%20%EC%95%8C%EC%95%84%EC%95%BC%20%ED%95%A0%20%EC%8B%A4%EB%AC%B4%20%EC%A7%80%EC%8B%9D</id><content type="html" xml:base="https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/21/book-%EC%A3%BC%EB%8B%88%EC%96%B4-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EB%B0%98%EB%93%9C%EC%8B%9C-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-%EC%8B%A4%EB%AC%B4-%EC%A7%80%EC%8B%9D.html"><![CDATA[<h2 id="책-소개">책 소개</h2>

<p><img src="/assets/images/주니어_백엔드_개발자가_반드시_알아야할_실무지식.png" alt="주니어 백엔드 개발자가 반드시 알아야 할 실무 지식" /></p>

<p>공부한 책 내용을 그대로 적는 것이 아닌 용어와 간단한 개념 설명 정도로만 정리하였습니다. 필요할 때 모르는게 있다면 그 때 검색하기 위함입니다. 주니어 개발자로써 다방면으로 시니어 개발자가 조언해주는 느낌입니다. 보면 생각의 전환으로 좋습니다.</p>

<blockquote>
  <p>뭐든 성능을 높이겠다고 처음부터 어렵게 설계해놓으면 오버 엔지니어링이 될 수 있다. 일단 사용자를 불러들이는 것이 가장 중요하고, 그 후에 대처하는 것이다. 대처하기 위해서는 당연히 공부를 해야 한다. 초반에 어느정도는 대처할 수 있을 것이다.</p>
</blockquote>

<h2 id="느려진-서비스">느려진 서비스</h2>

<h3 id="처리량과-응답시간">처리량과 응답시간</h3>

<p>구글에 따르면 검색 지연 시간이 길어질수록 사용자당 검색 횟수가 줄어드는 경향이 있다.</p>

<ul>
  <li>100ms 지연 시: 검색 횟수 0.2% 감소</li>
  <li>400ms 지연 시: 검색 횟수 0.6% 감소</li>
</ul>

<p>서버가 처리할 수 있는 요청수보다 많으면 응답시간은 길어진다. 이를 방지하기 위한 2가지 방식</p>

<ul>
  <li>서버가 동시에 처리할 수 있는 요청 수를 늘려 대기 시간 줄이기</li>
  <li>처리 시간 자체를 줄여 대기 시간 줄이기</li>
</ul>

<blockquote>
  <p>응답시간과 비슷한 타임아웃을 5초 정도(예시)로 짧게 잡는 것이 좋다. 30초 무한정 기다리는 것보다는 빠르게 에러 화면을 보여주는 것이다.</p>
</blockquote>

<h3 id="커넥션-풀">커넥션 풀</h3>

<p>커넥션 풀 크기를 늘리면 처리량을 높일 수 있다. 그러나 무턱대고 늘리다보면 DB 서버의 CPU 사용률이 80%에 육박하는 상황에서 DB에 가해지는 부하가 더 커져 쿼리 실행 시간이 급격히 증가한다. 또한 대기시간을 설정할 수 있는데 기본값인 30초로 설정하는 것보다 짧은 시간으로 설정하여 빠르게 에러를 반환하는 것이 더 낫다.</p>

<h3 id="서버-캐시">서버 캐시</h3>

<p>DB 서버를 확장하지 않고도 응답 시간과 처리량을 개선하고 싶다면 캐시(Cache) 사용을 고려할 수 있다.</p>

<p>서버가 사용하는 캐시에는 두 종류가 있다.</p>

<ul>
  <li>로컬 캐시: 서버 프로세스와 동일한 메모리를 캐시 저장소로 사용
    <ul>
      <li>Caffeine(자바)</li>
    </ul>
  </li>
  <li>리모트 캐시: 별도 프로세스를 캐시 젖장소로 사용
    <ul>
      <li>레디스(Redis)</li>
    </ul>
  </li>
</ul>

<blockquote>
  <p>캐시에 보관할 데이터 규모가 작고 변경 빈도가 매우 낮다면 로컬 캐시, 데이터 규모가 크다면 리모트 캐시</p>
</blockquote>

<p>캐시 적중률이 낮아지고, 단시간에 150만명이 동시에 접속시 트래픽이 감당 안될 수 있다. 이를 방지하기 위해 사전에 캐시에 데이터를 넣어두는 것이다. 그러면 사용자가 접속시 캐시 적중률이 99%에 가깝게 유지되어 트래픽이 안정될 수 있다.</p>

<p>하지만 가격 정보, 게시글 내용처럼 민감한 데이터는 변경되는 즉시 캐시를 무효화해야 한다. 변경에 민감한 데이터는 로컬 캐시가 아닌 리모트 캐시에 보관해야 한다. 로컬 캐시는 자신의 데이터만 변경하지 다른 서버의 로컬 캐시는 변경하지 않기 때문이다.</p>

<ul>
  <li>최근 인기 목록 같은 경우 캐시의 유효시간을 설정하여 주기적으로 갱신하는 방식을 사용해도 ok</li>
</ul>

<h3 id="메모리">메모리</h3>

<p>대량의 데이터를 한번에 메모리에 올리면 안된다. 서버가 뻗을 수 있다. 파일 다운로드와 같은 기능을 구현할 때는 스트림을 활용해야 한다.</p>

<h3 id="대기처리">대기처리</h3>

<p>사용자가 순간적으로 폭증할 때가 있다.</p>

<ul>
  <li>예시) 콘서트 예매</li>
</ul>

<p>여기서 생각할 수 있는 트래픽 대처는 먼저 사전에 서버를 증설하는 것이다. 다만 순간적인 짧은 시간을 버티기 위해 서버를 증설하는 것은 부담이 될 수 밖에 없다. 따라서 수용할 수 있는 수준의 트래픽만 받아들이고 나머지는 대기처리 하는 것이다.</p>

<ul>
  <li>예시) 은행창구</li>
</ul>

<blockquote>
  <p>대규모 트래픽을 반드시 처리해야할 필요는 없다. 트래픽과 비용을 같이 생각해야 한다. 적절한 제약은 좋은 것이다.</p>
</blockquote>

<h2 id="db-설계와-쿼리">DB 설계와 쿼리</h2>

<p>DB 성능 문제 원인 - 풀스캔</p>

<h3 id="인덱스-설계">인덱스 설계</h3>

<ul>
  <li>단일 인덱스: userId만 인덱스로 사용</li>
  <li>복합 인덱스: (userId, activityDate)를 인덱스로 사용</li>
</ul>

<p>단일 인덱스를 사용해도 되지만 회원들의 활동성이 좋다면 (userId, activityDate) 컬럼을 ㅈ조합한 복합 인덱스 사용을 고려해야 한다.</p>

<p>인덱스를 생성할 때는 선택도가 높은 컬럼을 골라야 한다. 다만 너무 많이 만들면서 CUD 작업시 성능이 악화되 필요한 만큼만 만들어야한다.</p>

<h3 id="전체-개수-세지-않기">전체 개수 세지 않기</h3>

<p>목록을 표시하는 기능은 전체 개수를 함께 표시하는 경우가 많다. 데이터가 적을 때는 count 쿼리를 사용해도 무방하지만, 문제는 데이터가 급격히 증가하기 시작할 때이다. 데이터가 많아질수록 count 실행 시간도 증가하는데, 그 이유는 조건에 해당하는 모든 데이터를 탐색해야 하기 때문이다. 커버링 인덱스를 사용하더라도 전체 인덱스를 스캔해야 한다.</p>

<h3 id="오래된-데이터-삭제-및-분리-보관하기">오래된 데이터 삭제 및 분리 보관하기</h3>

<p>데이터 개수가 늘어나면 늘어날수록 쿼리 실행시간은 증가한다. 따라서 로그인 시도 내역 같은 데이터는 장기간 보관할 필요가 없기 때문에 삭제한다. 혹은 분리하여 데이터를 보관한다.</p>

<h3 id="트랜잭션-고려">트랜잭션 고려</h3>

<p>모든 코드가 항상 정상적으로 동작하는 것은 아니기 때문에 비정상 상황에서의 트랜잭션 처리를 반드시 고민해야 한다. 트랜잭션을 고민하지 않고 코드를 작성하면 데이터 일관성에 문제가 생길 수 있다. 자주 발생하는 실수 중 하나가 트랜잭션 없이 여러 데이터를 수정하는 것이다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Transactional</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">join</span><span class="o">(</span><span class="nc">JoinRequest</span> <span class="n">join</span><span class="o">)</span> <span class="o">{</span>
  <span class="o">...</span>
  <span class="n">memberDao</span><span class="o">.</span><span class="na">insert</span><span class="o">(</span><span class="n">member</span><span class="o">);</span> <span class="c1">// DB에 데이터 추가</span>
  <span class="n">mailClient</span><span class="o">.</span><span class="na">sendMail</span><span class="o">(...);</span> <span class="c1">// 메일 발송</span>
<span class="o">}</span>
</code></pre></div></div>

<p>메일 서버에 일시적인 문제가 있어 <code class="language-plaintext highlighter-rouge">sendMail()</code>에서 런타임 예외가 발생했다. 스프링의 <code class="language-plaintext highlighter-rouge">@Transactional</code>은 예외가 발생하면 해당 메서드에서 전체 롤백한다. 따라서 DB에 회원 데이터를 정상적으로 추가했더라도 메일 발송 중 예외가 발생하면 회원가입 전체가 실패하게 된다. 하지만 운영에서 이런걸 원하지는 않을 것이다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Transactional</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">join</span><span class="o">(</span><span class="nc">JoinRequest</span> <span class="n">join</span><span class="o">)</span> <span class="o">{</span>
  <span class="o">...</span>
  <span class="n">memberDao</span><span class="o">.</span><span class="na">insert</span><span class="o">(</span><span class="n">member</span><span class="o">);</span> <span class="c1">// DB에 데이터 추가</span>
  <span class="k">try</span> <span class="o">{</span>
    <span class="n">mailClient</span><span class="o">.</span><span class="na">sendMail</span><span class="o">(...);</span> <span class="c1">// 메일 발송</span>
  <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
    <span class="c1">// 메일 발송 오류 무시</span>
    <span class="c1">// 로그로 기록해 모니터링</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>위와 같이 따로 에러처리를 수행할 수 있다. 외부 API 연동과 DB 작업이 섞이면 트랜잭션 처리가 복잡해진다. 자, 다시 정리해보자.</p>

<h3 id="재시도">재시도</h3>

<p>재시도를 통해 연동 실패를 줄일 수 있지만, 항상 재시도를 할 수 있는 것은 아니다. 연동 API를 다시 호출해도 되는 조건인지 확인해야 한다.</p>

<ul>
  <li>단순 조회 기능</li>
  <li>연결 타임아웃</li>
  <li>멱등성을 가진 변경 기능</li>
</ul>

<p>또한 재시도를 무한정 할 수는 없기 때문에 1~2번 정도의 재시도가 적당하다. 단점도 존재하는데 연동 서비스에는 더 큰 부하를 줄 수 있다. 연동 서비스의 성능이 느려져서 타임아웃이 발생한다면 같은 요청을 두배로 받게되기 때문에 상당히 느려질 것이다.</p>

<h2 id="동기-비동기">동기 비동기</h2>

<p>일반적으로 로그인에 성공하면 포인트를 지급하는 기능을 개발해야 하는 경우 동기적으로 순서대로 코드를 작성하여 작동하게 만들 수 있다. 하지만 포인트를 지급하는 것과 같은 외부 서비스의 응답시간이 길어질수록 전체 응답시간이 느려진다. 심한 경우 외부 연동 서비스로 인해 전체 서비스가 먹통이 되기도 한다. 이 경우에는 비동기 방식으로 연동하는 것을 고려해볼 필요가 있다. 생각보다 많은 연동에서 비동기 방식을 사용해도 된다.</p>

<ul>
  <li>쇼핑몰에서 주문이 들어오면 판매자에게 푸시 보내기 (푸시 서비스 연동)</li>
  <li>학습을 완료하면 학생에게 포인트 지급 (포인트 서비스 연동)</li>
  <li>컨텐츠를 등록할 때 검색 서비스에도 등록 (검색 서비스 연동)</li>
  <li>인증 번호를 요청하면 SMS로 메시지 발송 (SMS 발송 서비스 연동)</li>
</ul>

<p>이 예시들은 몇가지 공통점이 있다.</p>

<ul>
  <li>연동에 약간의 시차가 생겨도 문제가 되지 않는다.
    <ul>
      <li>쇼핑몰에서 주문이 완료된 후 1분 뒤에 판매자에게 푸시가 나가도 판매에 지장이 없다.</li>
    </ul>
  </li>
  <li>일부 기능은 실패했을 때 재시도가 가능하다.
    <ul>
      <li>푸시 발송에 실패했을 경우 재시도를 통해 푸시가 발송될 수 있다.</li>
    </ul>
  </li>
  <li>연동에 실패했을 때 나중에 수동으로 처리할 수 있는 기능도 있다.</li>
  <li>연동에 실패했을 때 무시해도 되는 기능도 있다.
    <ul>
      <li>주문이 들어왔을 때 판매자에게 푸시가 발송되지 않더라도 판매에는 문제가 생기지 않는다.</li>
    </ul>
  </li>
</ul>

<p>비동기 연동 방식 5가지</p>

<ol>
  <li>별도 스레드로 실행</li>
  <li>메시지 시스템 이용</li>
  <li>트랜잭션 아웃박스 패턴 사용</li>
  <li>배치로 연동</li>
  <li>CDC 이용</li>
</ol>

<h2 id="동시성">동시성</h2>

<h3 id="경쟁상태">경쟁상태</h3>

<p>여러 쓰레드가 동시에 공유자원에 접근할 때, 접근 순서에 따라 결과가 달라지는 상황을 경쟁상태(race condition)라 한다. 경쟁상태가 발생하면 예상하지 못한 결과가 발생할 수 있다. 쓰레드 말고도 static 변수에 변화를 주면 동시성 이슈, 즉 경쟁상태가 발생한다. 그래서 멀티쓰레드 환경에서는 항상 동시성 문제를 생각해야 한다.</p>

<h3 id="동시성-문제-해결">동시성 문제 해결</h3>

<ol>
  <li>잠금(lock)을 이용한 접근 제어</li>
</ol>

<ul>
  <li>락을 획득함</li>
  <li>공유 자원에 접근 (임계영역)</li>
  <li>락 해제</li>
</ul>

<blockquote>
  <p>임계영역은 동시에 둘 이상의 스레드나 프로세스가 접근하면 안되는 공유자원에 접근하는 코드 영역을 말한다.</p>
</blockquote>

<h4 id="뮤텍스와-세마포어">뮤텍스와 세마포어</h4>

<p>뮤텍스는 mutual exclusion의 약자로 뮤텍스를 다른 말로 잠금(lock)이라고도 한다. 자바에서는 Lock 타입을 사용하고, Go에서는 Mutex인 타입을 사용한다.</p>

<p>세마포어(Semaphore)는 동시에 실행할 수 있는 쓰레드 수를 제한한다. 자원에 대한 접근을 일정 수준으로 제한하고 싶을 때 세마포어를 사용할 수 있다. 예를들면, 외부 서비스에 대한 동시 요청을 최대 5개로 제한하고 싶을 때 세마포어를 사용한다.</p>

<h4 id="동시성-지원-컬렉션">동시성 지원 컬렉션</h4>

<p>스레드에 안전하지 않은 컬렉션을 여러 쓰레드가 공유하면 동시성 문제가 발생할 수 있다. 자바에서 제공하는 HashMap, HashSet 같은 컬렉션을 여러 쓰레드가 동시에 변경하면 데이터가 깨진다. 이를 해결하기 위해 나온 것이 동기화된 컬렉션을 사용하는 것이다. 즉, 데이터를 변경하는 모든 연산에 락을 적용해서 한번에 한 스레드만 접근할 수 있도록 제한하는 것이다.</p>

<blockquote>
  <p>동시성 문제를 피하기 위한 또 다른 방법은 불변값을 사용하는 것이다. 값이 바뀌지 않기 때문에 동시에 여러 스레드가 접근해도 문제가 발생하지 않는다.</p>
</blockquote>]]></content><author><name>플라이팬</name></author><category term="책 공부" /><category term="AWS" /><category term="클라우드" /><summary type="html"><![CDATA[시행착오를 줄여주는 실무 밀착 백엔드 개발 가이드]]></summary></entry><entry><title type="html">[책 공부] 개발자를 위한 쉬운 도커</title><link href="https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/20/book-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%89%AC%EC%9A%B4-%EB%8F%84%EC%BB%A4.html" rel="alternate" type="text/html" title="[책 공부] 개발자를 위한 쉬운 도커" /><published>2025-12-20T20:15:00+09:00</published><updated>2025-12-20T20:15:00+09:00</updated><id>https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/20/book-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC%20%EC%9C%84%ED%95%9C%20%EC%89%AC%EC%9A%B4%20%EB%8F%84%EC%BB%A4</id><content type="html" xml:base="https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/20/book-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%89%AC%EC%9A%B4-%EB%8F%84%EC%BB%A4.html"><![CDATA[<h2 id="책-소개">책 소개</h2>

<p><img src="/assets/images/개발자를_위한_쉬운_도커.png" alt="개발자를 위한 쉬운 도커" /></p>

<p>그동안 도커에 대해서 듣기만 했지만 제대로 공부를 한적이 없었습니다. 이번 기회를 통해 공부를 하게 됐고, 이 책은 도커의 원리, 구성, 왜 탄생하게 되었는지, 실무에서는 어떻게 쓰이는지에 대해 자세하게 설명하고 있습니다. 그래서 도커를 공부하고 싶다면 이 책을 꼭 추천합니다!</p>

<h2 id="서버-운영-방식">서버 운영 방식</h2>

<p>도커는 컨테이너를 관리하는 소프트웨어이다. 컨테이너는 서버를 효율적으로 사용하기 위한 가상화 기술이다.</p>

<p>엔터프라이즈 환경에서 기업이 서비스를 운영할 때 하나의 서비스는 다양한 종류와 역할을 가진 여러 서버로 구성된다. 이때 기업에서 여러 서버를 운영하는 방법은 크게 3가지로 나뉜다.</p>

<ol>
  <li>베어메탈(bare metal): PC 한 대에 운영체제를 설치하고, 그 OS에서 여러 소프트웨어를 운영하는 방식</li>
  <li>하이퍼바이저, 컨테이너: 서버 운영에 가상화 기술을 활용한다. 서버의 수나 실행되는 소프트웨어는 동일하지만 중간에 VM이나 컨테이너가 추가된 형태이다.</li>
</ol>

<blockquote>
  <p>가상화 기술은 물리적인 컴퓨팅 환경 내에서 여러 개의 논리적인 컴퓨팅 환경을 만들 수 있는 기술로 정의할 수 있다.</p>
</blockquote>

<p>하이퍼바이저를 실행하는 OS를 호스트OS라 부르고, 호스트OS는 물리적인 하드웨어 서버를 관리한다. 하이퍼바이저는 이 호스트OS의 자원을 이용해 새로운 가상의 OS인 게스트OS를 실행한다. 즉, 호스트OS는 물리서버를 직접 관리하고 게스트OS는 호스트OS의 리소스를 나눠 사용하는 논리적인 공간이다. 이렇게 논리적으로 분리된 게스트OS를 가상머신이라고 부른다. 가상머신에서는 웹 서버, WAS, DB 서버 등의 프로그램을 프로세스로 실행한다. 여기서 프로세스란 실행중인 프로그램을 의미한다. 하이퍼바이저는 다양한 커널 간의 요청을 관리하고, 가상머신에 필요한 리소스를 할당한다. 이를 통해 다양한 종류의 게스트OS를 격리된 공간에서 사용 가능한 것이다. 하이퍼바이저 역할을 하는 소프트웨어는 제조사별로 다양하며, VirtualBox가 그중 하나이다.</p>

<h2 id="컨테이너-가상화">컨테이너 가상화</h2>

<p>컨테이너 가상화는 하이퍼바이저 가상화보다 가볍고 빠르며, 이는 현대 애플리케이션 운영에서 중요한 요소이다. 소비자의 요구사항이 빠르게 변화함에 따라 애플리케이션도 신속하게 대응할 수 있어야하기 때문이다. 컨테이너의 의미가 격리된 공간을 말한다. 즉, 컨테이너 가상화는 하이퍼바이저 없이도 커널의 기능을 사용하는 것이 가상화 기술이다. 호스트OS의 커널을 공유하는 것은 컨테이너 가상화의 중요한 특징이다.</p>

<h2 id="도커">도커</h2>

<p>커널의 가상화 기술은 일반 사용자가 직접 다루기에 복잡한데, 이러한 어려움을 해결하기 위해 2013년에 도커(Docker)가 등장했다. 도커는 컨테이너 가상화 기술을 쉽게 사용할 수 있게 해주는 오픈소스 도구이다. 그리고 도커와 같은 도구를 컨테이너 플랫폼이라고 한다.</p>

<p>도커는 클라이언트와 컨테이너를 관리하는 도커 데몬(docker daemon) 서버로 구성되는데, 도커 데몬이 클라이언트가 커널의 기능을 이용할 수 있도록 API를 제공한다. 즉, 컨테이너를 생성하거나 삭제하려면 도커 데몬에 API 요청을 보내면 된다.</p>

<h3 id="실행과정">실행과정</h3>

<ol>
  <li>현재 실습 PC에 도커를 설치해 호스트OS로 사용중</li>
  <li><code class="language-plaintext highlighter-rouge">docker run nginx</code> CLI가 명령어를 컨테이너 실행 API에 맞게 변환해 도커 데몬으로 전달</li>
  <li>도커 데몬은 요청을 분석한 뒤 컨테이너 런타임을 통해 컨테이너 생성</li>
  <li>nginx 컨테이너의 프로세스가 요청을 받아 웹페이지 제공</li>
  <li>이러한 컨테이너는 디스크, 네트워크, 메모리, CPU 등의 자원이 완전히 격리된 공간으로 실행되며 이를 통해 여러 서버를 안전하게 운영할 수 있다.</li>
</ol>

<h2 id="이미지">이미지</h2>

<p>컨테이너 가상화는 호스트OS의 공간을 격리해서 컨테이너를 생성하고, 그 내부에서 프로그램을 실행하는 방식이다. 컨테이너를 실행하려면 이미지가 필요하다. 이미지는 컨테이너에서 프로그램을 실행하는데 필요한 모든 것이 포함된 압축 파일이다. 이미지는 파일 시스템의 특정 시점을 저장해 놓은 압축 파일이다. 이 이미지에는 프로그램, OS, 구성요소들이 모두 포함되어 있다. 이렇게 준비된 이미질로 컨테이너를 실행하면 프로그램을 빠르게 실행할 수 있다.</p>

<blockquote>
  <p>앞서 docker run nginx를 실행했었는데, 여기서 nginx가 이미지인 것이다. 이 이미지를 기반으로 생성한 격리된 실행 환경이 컨테이너인 것이다.</p>
</blockquote>

<p>이미지의 장점은 크기가 작아서 인터넷에 저장하고 공유하기에 편리하다. 사용자는 다른 사람이 만든 이미지를 내려받아 사용할 수 있으며, 직접 이미지를 제작해서 사용할 수도 있다.</p>

<p>컨테이너를 실행하려면 이미지가 필요합니다. 실행 가능한 프로그램과 실행에 필요한 모든 환경이 준비된 파일 시스템으로, 이 이미지를 기반으로 컨테이너가 실행된다.</p>

<h3 id="이미지-1">이미지</h3>

<ul>
  <li>실행 가능한 소프트웨어 + 실행에 필요한 환경</li>
  <li>디스크 공간 차지</li>
</ul>

<h3 id="컨테이너">컨테이너</h3>

<ul>
  <li>실행 상태의 이미지</li>
  <li>이미지 1 : N 컨테이너</li>
  <li>컨테이너 실행시 CPU, 메모리 사용</li>
  <li>컨테이너 간 리소스 격리</li>
  <li>실행시 격리된 가상 공간 생성</li>
  <li>컨테이너 실행시 프로세스도 함께 실행</li>
</ul>

<p>이미지의 최대 장점은 프로그램뿐 아니라 실행환경까지 포함하므로 도커만 설치돼 있으면 모든 서버에서 동일한 환경을 구성할 수가 있다. 따라서 도커를 사용하면 소프트웨어 배포와 환경 구성을 일관성 있게 유지할 수 있다.</p>

<h2 id="이미지-레지스트리">이미지 레지스트리</h2>

<p>이미지 레지스트리는 이미지를 저장하는 저장소이다. Github의 레포지토리와 비슷하다. Github는 소스코드를 저장하지만 이미지 레지스트리는 이미지를 저장한다. 이미지를 저장하는 공간은 3가지로 나뉜다.</p>

<ul>
  <li>로컬 스토리지: 도커가 설치된 호스트OS의 스토리지로, 사용자 PC의 특정 폴더에 이미지를 저장한다.</li>
  <li>프라이빗 레지스트리: 주로 기업 환경에서 사용된다.</li>
  <li>퍼블릭 레지스트리: 누구나 접근할 수 있는 온라인 저장소이다.</li>
</ul>

<p>컨테이너를 실행할 때 <code class="language-plaintext highlighter-rouge">docker run</code> 명령과 이미지 이름을 입력하면 도커는 가장 먼저 로컬 스토리지에서 해당 이미지를 찾는다. 이미지가 있다면 다운로드 없이 바로 컨테이너를 실행한다.</p>

<p>이미지가 없으면 도커는 온라인 레지스트리에서 이미지를 다운로드한 뒤 로컬 스토리지에 저장하고, 이후 컨테이너를 실행한다. 한 번 로컬 스토리지에 다운로드한 이미지는 계속 사용할 수 있다.</p>

<h3 id="프라이빗-레지스트리">프라이빗 레지스트리</h3>

<p>프라이빗 레지스트리는 두 가지 방식이 있는데,</p>

<ol>
  <li>서버에 직접 레지스트리 소프트웨어를 설치하는 방식: 하버(Harbor)나 도커 프라이빗 레지스트리 같은 소프트웨어를 사용</li>
  <li>클라우드 서비스를 이용하는 방식: AWS의 ECR(Elastic Container Registry), Azure의 ACR(Azure Container Registry) 등을 사용</li>
</ol>

<blockquote>
  <p>퍼블릭 레지스트리는 가입만 누구나 이미지를 자유롭게 업로드하거나 다운로드 할 수 있다. 도커 허브 같은 서비스를 제공한다.</p>
</blockquote>

<h2 id="이미지-빌드">이미지 빌드</h2>

<p>이미지 빌드 들어가기 전에 IaC(Infrastructure as Code)라는 개념을 이해하는 것이 중요하다. 기존 서버 관리자는 대시보드를 클릭하거나 명령어를 실행해 사내 인프라를 관리했었다. 하지만 사람이 직접 작업하면 실수하기 쉽고, 인프라 상태의 변경 기록을 체계적으로 관리하기 어렵다. IaC는 이러한 작업을 코드로 관리하는 방식으로, 프로그램이 코드를 읽어 인프라를 관리하므로 사람이 직접 관리하는 것보다 더 빠르고 안전하다.</p>

<p>도커는 IaC 방법론을 활용해 도커파일(Dockerfile)이라는 파일로 파일시스템의 상태를 정의하고, 이를 기반으로 이미지를 만든다. 빌드 방식에서는 도커파일을 분석해 도커가 자동으로 컨테이너를 생성하고 커밋한다.</p>

<h3 id="멀티-스테이지-빌드">멀티 스테이지 빌드</h3>

<p>일반적인 빌드 방식에서는 이미지에 소스코드와 외부 라이브러리 파일이 포함되어 많은 저장 공간을 차지한다. 반면, 멀티 스테이지 빌드는 첫 번째 빌드 스테이지에서 maven 이미지를 사용하고, 두 번째 실행 스테이지에서는 openjdk 이미지를 사용한다. 빌드 스테이지에서는 소스코드로 애플리케이션을 빌드하고, 생성된 JAR 파일을 실행 스테이지로 복사한다. 최종적으로 생성된 이미지에는 자바 런타임과 JAR 파일만 포함돼 있어 이미지 크기를 효과적으로 줄일 수 있다.</p>

<h2 id="도커-네트워크">도커 네트워크</h2>

<p>물리 서버는 공인망이나 사설망을 통해 공인IP 또는 사설IP를 할당받는다. 이 상태에서 PC에 여러 컨테이너를 실행하면 각 컨테이너는 어떻게 IP를 할당받게 될까? 컨테이너 간 통신은 어떻게 이뤄질까? 이러한 문제를 도커의 가상 네트워크 기술로 해결할 수 있다. 가상 네트워크란 물리적인 인터넷 케이블이나 공유기 없이 서버 내에서 논리적으로 정의한 네트워크이다.</p>

<h3 id="브리지-네트워크">브리지 네트워크</h3>

<p>도커를 설치하고 실행하면 브리지 네트워크(bridge network)라는 가상 네트워크가 자동으로 생성되며, docker0 브리지가 생성되어 가상 공유기 역할을 수행한다. 브리지 네트워크는 보통 172.17.0.1 대역의 IP주소를 사용한다. 브리지 네트워크는 컨테이너 간 통신을 위해 각 컨테이너에 IP주소를 할당한다. 이처럼 소프트웨어로 논리적인 네트워크 환경을 구성하는 기술을 SDN(Software Defined Network)이라고 한다.</p>

<blockquote>
  <p>또한 도커는 컨테이너가 사용할 수 있는 내부 DNS 서버를 기본적으로 제공한다.</p>
</blockquote>

<h2 id="볼륨">볼륨</h2>

<p>도커의 볼륨은 컨테이너에 상태를 부여하는 핵심 기능이다. 여기서 상태는 컨테이너가 실행되는 동안 생성되거나 변경된 데이터를 말한다. 예를 들어, 데이터베이스에 저장된 정보, 사용자가 업로드한 파일, 로그 기록 등이 상태에 해당한다. 컨테이너는 기본적으로 스테이트리스(stateless) 특징을 가지고 있기 때문에 컨테이너가 종료되면 컨테이너 내에서 발생한 모든 변경사항이 사라진다. 그래서 이러한 컨테이너 영속성을 부여하기 위한 기능이 도커 볼륨이다.</p>

<ul>
  <li>컨테이너가 삭제되거나 재시작돼도 데이터를 안전하게 유지</li>
  <li>여러 컨테이너가 같은 데이터를 공유</li>
  <li>서비스의 안정성과 데이터의 일관성 유지</li>
</ul>

<p>도커 볼륨은 컨테이너 외부에 독립적인 저장 공간을 제공한다. 그리고, 여러 컨테이너가 같은 볼륨을 마운트하고 공유할 수 있다.</p>

<blockquote>
  <p>마운트란 컴퓨터 시스템에서 외부 저장 장치나 파일 시스템을 특정 티렉터리에 연결하는 것을 의미한다.</p>
</blockquote>

<ol>
  <li>단일 컨테이너에서의 다중 볼륨 사용</li>
  <li>다중 컨테이너에서의 하나의 볼륨 공유</li>
</ol>

<h2 id="이미지-관리">이미지 관리</h2>

<p>이미지의 레이어를 효과적으로 관리하고 이미지 크기를 줄이는 방법</p>

<p>도커 파일로 이미지를 빌드할 때, 보통 지시어 하나당 새로운 레이어가 하나 추가된다. 레이어가 많아지면 빌드 속도가 느려지며, 이미지 관리가 복잡해지는 등의 문제가 발생한다.</p>

<p>이미지를 빌드할 때는 레이어 수를 최소화하는 것이 중요한 최적화 전략이다. 또한 이미지 크기를 줄이면 네트워크 사용량이 줄어 배포 속도가 빨라지고 전반적인 시스템 효율성이 높아진다.</p>

<ul>
  <li>이미지에 많은 명령을 실행해야할 때 &amp;&amp; 연삱자와 RUN 지시어로 통합하면 하나의 레이어만 추가되는 효과가 있다.</li>
  <li>scratch 이미지를 사용한 이미지 경량화</li>
</ul>

<h2 id="도커-컴포즈">도커 컴포즈</h2>

<p>도커 컴포즈(Docker Compose)는 여러 컨테이너를 실행하는 환경을 효율적으로 관리하는 도구이다. 많은 컨테이너를 동시에 관리하는 기능을 수행하며, 헌번의 명령어로 모든 컨테이너, 네트워크, 볼륨을 동시에 생성할 수 있으며, 전체 환경을 한꺼번에 종료할 수 있다.</p>

<p>도커 컴포즈를 사용하면 개발 환경을 운영 환경과 동일하게 PC에 빠르게 구성할 수 있다. 예를들어, <code class="language-plaintext highlighter-rouge">docker run</code> 명령을 여러번 입력해 각 컨테이너를 실행해야 했다면 도커 컴포즈를 사용하면 <code class="language-plaintext highlighter-rouge">docker compose up</code> 명령어 하나로 모든 환경을 한번에 구성할 수 있다. 이는 개발 프로세스를 크게 간소화하고 효율성을 높이는 장점중 하나이다.</p>

<blockquote>
  <p>도커 컴포즈는 <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> 파일로 동작한다.</p>
</blockquote>]]></content><author><name>플라이팬</name></author><category term="책 공부" /><category term="도커" /><category term="컨테이너" /><summary type="html"><![CDATA[컨테이너 기초부터 개발자에게 필요한 네트워크, 볼륨 개념과 다양한 실무 사례까지]]></summary></entry><entry><title type="html">[책 공부] 리눅스 입문 with 우분투</title><link href="https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/15/book-%EB%A6%AC%EB%88%85%EC%8A%A4%EC%9E%85%EB%AC%B8.html" rel="alternate" type="text/html" title="[책 공부] 리눅스 입문 with 우분투" /><published>2025-12-15T20:15:00+09:00</published><updated>2025-12-15T20:15:00+09:00</updated><id>https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/15/book-%EB%A6%AC%EB%88%85%EC%8A%A4%EC%9E%85%EB%AC%B8</id><content type="html" xml:base="https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/15/book-%EB%A6%AC%EB%88%85%EC%8A%A4%EC%9E%85%EB%AC%B8.html"><![CDATA[<h2 id="책-소개">책 소개</h2>

<p><img src="/assets/images/리눅스_입문.png" alt="리눅스 입문 with 우분투" /></p>

<p>처음에 리눅스 공부하기에 좋은 책입니다. 리눅스 뿐만 아니라 리눅스에서 사용되는 CS지식들도 설명해주고 있어서 한권만 봐도 잘 배울 수 있는 것이 좋습니다.</p>

<h2 id="리눅스란">리눅스란</h2>

<p>리눅스는 무료로 사용할 수 있는 오픈소스 운영체제이다. 운영체제란 컴퓨터의 하드웨어를 관리하고 프로그램들을 실행시켜주는 기본 소프트웨어이다. 리눅스 운영체제의 핵심 부분인 리눅스 커널(Kernel)이 있다.</p>

<p>리눅스는 다양한 분야에 걸쳐 광범위하게 사용하는 운영체제이다. 현대의 거의 모든 프로그래밍 언어는 리눅스에서 개발 및 배포할 수 있다. 서버 분야나 컴퓨팅 분야 등 리눅스를 기반으로 하는 분야가 상당히 넓기 때문에 리눅스를 배우는 것이 필수이다.</p>

<h3 id="리눅스-설치">리눅스 설치</h3>

<ul>
  <li>PC에 리눅스 설치</li>
  <li>가상 머신에 리눅스 설치</li>
  <li>클라우드 서비스로 리눅스 인스턴스 생성</li>
</ul>

<p>집에서 실습하려면 일반적으로 가상 머신을 설치하여 진행한다. 대표적으로 Oracle의 VirtualBox와 VMWare의 VMWare Workstation Player가 있다.</p>

<h2 id="터미널과-셸">터미널과 셸</h2>

<p>터미널(terminal)은 컴퓨터와 사용자 간에 상호작용할 수 있게 연결하는 장치이다. 사용자가 컴퓨터에 명령을 전달하고 컴퓨터는 명령을 수행한 결과를 사용자에게 전달하는 것이다.</p>

<p>셸(Shell)은 CLI 프로그램이다. 사용자는 셸을 통해 운영체제에 명령을 내리고 결과를 확인한다. 터미널이 컴퓨터와 사용자 간 상호작용할 수 있는 하드웨어라면, 셸을 운영체제와 사용자 간 상호작용을 지원하는 소프트웨어이다. 여러 셸이 있는데 그중에 Bash는 리누스 토르발즈가 리눅스를 개발할 때 리눅스로 처음 포팅한 프로그램 중 하나이다.</p>

<p>과거에는 텔넷(Telnet)과 같은 네트워크 프로토콜을 이용해 원격 운영체제에 터미널을 연결했다. 그러나 텔넷은 모든 데이터가 평문으로 전송돼 보안에 매우 취약했다. 근래에는 모든 데이터가 암호화돼 전송되는 SSH(Secure SHell) 프로토콜을 이용해 터미널을 안전하게 원격 운영체제에 연결할 수 있다.</p>

<h2 id="셸-스크립트">셸 스크립트</h2>

<p>셸에서 동작 가능한 명령을 모아놓은 파일을 셸 스크립트(Shell Script)라고 한다. 셸 스크립트를 실행하면 셸 스크립트 파일의 내용이 순차적으로 실행된다. 셸 스크립트는 여러 명령을 한 파일에 모아 실행하는 방식이여서 자동화할 수 있다. 또한 주기적으로 실행할 작업을 등록해 사용하는 cron(스케쥴 서비스)이라는 프로그램이 있다. 셸 스크립트를 잘 작성하면 이미 검증되었기 때문에 안전하게 모든 사람이 같이 공유하여 사용할 수 있기 때문에 휴먼 에러가 발생할 확률을 낮출 수 있다.</p>

<h3 id="대표적인-명령어">대표적인 명령어</h3>

<ul>
  <li>ls: 파일 목록</li>
  <li>cd: change directory로, 현재 작업 디렉터리(CWD, Current Working Directory)의 위치를 이동하는 명령어</li>
  <li>pwd: print working directory로, 현재 작업 디렉터리를 절대 경로로 조회하는 명령어</li>
  <li>cat: concatenate 또는 catenate에서 유래한 cat으로 파일의 내용을 연결하거나 조회</li>
  <li>exit: 현재 실행중인 셸 종료</li>
  <li>nano: 텍스트 편집기 (vim이나 emacs보다 쉽게 사용 가능)</li>
</ul>

<p>그 밖에 명령어가 옵션 포함해서 많지만 이것은 외우면 안된다. 그때그때 필요할 때마다 검색하여 찾아보자.</p>

<h2 id="소프트-링크와-하드-링크">소프트 링크와 하드 링크</h2>

<p>파일 시스템은 각 파일에 대한 정보를 아이노드(inode, index node)라는 자료구조에 저장한다. 파일에 대한 메타데이터를 저장한다고 보면 된다. 그리고 파일의 실제 내용을 저장하고 있는 데이터 블록(data block)을 아이노드에 연결한다. 아이노드에는 식별자인 아이노드 번호가 있어서 아이노드를 구분할 수 있다.</p>

<p>파일의 경로 정보는 덴트리(dentry, directory entry)라는 자료구조로 표현한다. 덴트리는 파일의 계층 구조를 유지하는데 사용한다. 각 덴트리는 파일 경로를 나타내며, 파일의 메타데이터를 저장하는 하나의 아이노드와 연결한다. 즉, 덴트리는 파일의 경로와 파일의 실체(아이노드로 표현되는 파일의 메타데이터)를 연결하는 매개체로 볼 수 있다.</p>

<h3 id="소프트-링크">소프트 링크</h3>

<p>윈도우에서 바로가기를 사용해봤을 것이다. 리눅스에서 소프트 링크가 윈도우의 바로가기와 같은 개념이다. 소프트 링크(soft link)는 연결할 대상 파일의 경로를 저장한다. 즉, 소프트링크를 조회하거나 실행하면 실제로는 연결된 대상 파일을 조회하거나 실행하게 된다. 이 소프트링크를 <strong>심볼릭 링크(symbolic link) 또는 심링크(symlink)</strong> 라고도 한다. 실무에서는 이 심볼릭 링크를 자주 사용한다. 경로가 복잡한 곳을 소프트링크를 걸어주면 쉽게 연결 대상 파일에 접근할 수 있는 것이다.</p>

<h3 id="하드링크">하드링크</h3>

<p>하드링크는 소프트링크처럼 파일을 연결한다기보다는 대상 파일의 복제본(clone)을 만든다고 보면 된다. 복사본과 복제본은 소프트웨어 관점에서 다른데, 복사본은 말그대로 같은 내용의 파일을 다른 파일명으로 복사하는 것이고, 복제본은 원본과 동일하게 만든 것으로 사실상 어떤 파일이 원본이고 어떤 파일이 복제본인지 구분할 수 없다. 그래서 하드링크를 대상 파일에 대한 별치(alias)을 부여하는 것으로 볼 수도 있다. 즉, 원본 대상 파일을 수정하고 하드링크를 조회하면 수정한 내용으로 보여지게 된다.</p>

<blockquote>
  <p>만약 원본 파일을 삭제하게 되면 아이노드와 데이터 블록은 삭제되지 않기 때문에 연결된 하드링크 파일로 조회가 가능하다. 말그대로 복제본을 만든 하드링크이다.</p>
</blockquote>

<h2 id="사용자">사용자</h2>

<p>리눅스는 여러 사용자가 동시에 로그인할 수 있는 멀티 유저 시스템(multi-user system)이다. 여기서 사용자는 ‘사용하는 사람’이 아니라 사용자(user)라는 이름의 ‘계정(account)’을 의미한다. 한 사람이 여러 계정을 소유할 수 있고, 여러 계정을 동시에 사용할 수도 있다.</p>

<ul>
  <li>root 사용자: 리눅스 시스템의 모든 권한을 가진 관리자 계정</li>
  <li>시스템 사용자: 리눅스 시스템에서 만든 사용자</li>
  <li>일반 사용자: root 사용자와 시스템 사용자를 제외한 모든 사용자</li>
</ul>

<p>리눅스에서 사용자를 구분하는 이유는 보안을 강화하고 시스템의 효율성을 높이기 위함이다.</p>

<h3 id="명령어">명령어</h3>

<ul>
  <li>su(switch user 또는 substitute user): 현재 셸의 사용자를 입력한 사용자로 전환</li>
  <li>sudo: 특정 사용자의 권한으로 단일 명령을 실행, 보통 root 사용자의 권한으로 명령을 실행할 때 사용한다.</li>
  <li>runuser: 사용자를 전환하거나 다른 사용자의 권한으로 명령을 실행 (보통 root 사용자만 이용할 수 있다.)</li>
</ul>

<blockquote>
  <ul>
    <li>사용자 정보를 관리하는 파일: <code class="language-plaintext highlighter-rouge">/etc/passwd</code></li>
    <li>사용자 그룹 정보를 관리하는 파일: <code class="language-plaintext highlighter-rouge">/etc/group</code></li>
  </ul>
</blockquote>

<h2 id="파일-소유권과-권한">파일 소유권과 권한</h2>

<h3 id="소유권">소유권</h3>

<p>리눅스에서 파일이 생성되면 루트 디렉터리 아래 어딘가에 존재한다. 그러나 리눅스는 여러 사용자의 수많은 파일의 위치나 디렉터리 이름 같은 정보로는 파일 소유자를 판단할 수 없는데, 이를 해결하기 위해 리눅스의 모든 파일에는 파일 소유권이 설정되어 있다.</p>

<p>소유권(ownership)은 파일이 사용자의 소유임을 나타내는 속성이다. 파일이 생성되는 순간 파일을 생성한 사용자와 그룹이 소유권을 가지도록 설정된다. <code class="language-plaintext highlighter-rouge">ls</code> 명령어를 통해 파일 목록 조회하는 동시에 소유권도 확인할 수 있다.</p>

<h3 id="권한">권한</h3>

<p>파일 권한(permission)은 해당 파일에 어떤 행위를 할 수 있는지를 정의한 것이다.</p>

<ul>
  <li>읽기(read)</li>
  <li>쓰기(write)</li>
  <li>실행(execution)</li>
</ul>

<p>리눅스에서는 파일 권한을 파일의 소유자, 소유 그룹, 일반 사용자별로 따로 설정이 가능하다.</p>

<ul>
  <li>-rw-rw-r–: 3개씩 끊어서 권한 부여
    <ul>
      <li>rw-: 소유자 읽기와 쓰기 가능</li>
      <li>rw-: 소유 그룹 읽기와 쓰기 가능</li>
      <li>r–: 일반사용자 읽기만 가능</li>
    </ul>
  </li>
</ul>

<p>이것은 각 권한 8진수 3자리로 표현하는 방법이다. 8진수를 2진수로 바꾸면 3자리 수가 나오고 각 자리는 읽기(r), 쓰기(w), 실행(x) 권한을 나타낸다.</p>

<blockquote>
  <p>chmod u+x msg_from_dog: u+x는 소유자(u)에게 실행(x) 권한을 추가(+)한다는 의미이다.</p>
</blockquote>

<ul>
  <li>디렉터리 역시 rwx로 권한을 줄 수 있다.</li>
</ul>

<h2 id="시그널">시그널</h2>

<p>파이프나 소켓 등은 주로 데이터를 교환하기 위한 목적으로 사용한다. 반면에 시그널(signal)은 어떤 이벤트가 발생했음을 알려주기 위해 사용한다. 파이프나 소켓처럼 구체적인 데이터를 전송할 수는 없고, 어떤 이벤트인지 나타내는 시그널 종류만 전달할 수 있다.</p>

<p>시그널은 POSIX(Portable Operating System Interface) 표준에 정의된 IPC 도구이다. 시그널 종류도 POSIX 표준에 정의되어 있다. 리눅스는 주로 POSIX 표준을 따르는데 그 외에 리눅스에 특화된 시그널도 제공한다. SIGWINCH(창 크기 변경 시그널)나 SIGIO(I/O 가능 시그널) 등은 모든 유닉스 시스템에서 표준화되지 않은 리눅스 특화 시그널이다.</p>

<p>프로세스가 시그널을 수신하면 프로세스는 수신한 시그널을 처리해야 한다. 예를 들어 <code class="language-plaintext highlighter-rouge">kill</code>로 시그널을 전송하게 되면 프로세스를 종료시킬 수 있다.</p>

<blockquote>
  <p>셸 스크립트는 따로 정리하지 않았습니다. 대부분 문법이고 문법은 필요할 때마다 어떤게 필요한지 검색하면 됩니다. 요즘은 AI도 잘되어 있으니 이를 잘 활용하면 더 빨리 작성할 수 있을 것 입니다.</p>
</blockquote>

<h2 id="패키지-관리-시스템">패키지 관리 시스템</h2>

<p>패키지 관리 시스템(PMS, Package Management System)은 패키지의 설치, 업데이트, 구성, 제거를 자동화하고 관리하는 시스템이다.</p>

<ul>
  <li>패키지 설치</li>
  <li>종속성 관리</li>
  <li>업데이트</li>
  <li>설정 관리</li>
  <li>제거</li>
</ul>

<p>JavaScript의 NPM, Java의 Maven 혹은 Gradle이라 생각하면 편하다.</p>

<h2 id="systemd">systemd</h2>

<p>systemd는 리눅스에서 사용하는 초기화 및 서비스 관리 시스템이다. 시스템의 부팅 과정을 관리하고, 다양한 서비스를 시작/종료/관리하는 역할을 담당한다. 전통적인 유닉스 스타일의 init 시스템을 대체한 systemd는 더 현대적이고 효율적인 방법을 제공해 여러 리눅스 배포판에서 초기화 시스템으로 채택하고 있다.</p>

<p>systemd는 시스템의 부팅과 설정 과정에서 할 일들을 서비스라는 단위로 구분해 관리한다. 서비스는 백그라운드에서 실행되는 프로세스를 말한다. 다 그런것은 아니지만 특정 기능을 하는 프로세스를 백그라운드에서 실행하는 경우가 많다. 이런 프로세스를 데몬(daemon) 프로세스라고 한다.</p>

<h2 id="자주-사용하는-커맨드라인">자주 사용하는 커맨드라인</h2>

<ul>
  <li>grep: Global Regular Expression Print의 약자로 표준 입력이나 텍스트 파일에서 특정 패턴이나 문자열을 검색</li>
  <li>find: 파일이나 디렉터리를 검색</li>
  <li>stat: 파일이나 파일시스템의 상세한 상태 정보를 표시</li>
  <li>wc: Word Count의 약자로, 텍스트 파일의 줄 수, 단어 수, 바이트 수 등을 계산한다.</li>
  <li>df: Disk Free, 파일 시스템의 디스크 사용량을 출력
    <ul>
      <li>주로 모니터링 할 때 사용한다.</li>
    </ul>
  </li>
  <li>du: Disk Usage, 파일이나 디렉터리가 차지하는 디스크 사용량을 추정해 출력</li>
  <li>tar: Tape Archive, 파일과 디렉터리를 아카이브하고 압축하는데 사용
    <ul>
      <li>대용량 데이터를 관리하거나 백업할 때 유용하다.</li>
      <li>아카이브는 여러 파일과 디렉터리를 한 덩어리로 만드는 것을 뜻한다.</li>
    </ul>
  </li>
  <li>read: 표준 입력(보통 키보드)에서 한 줄을 읽어 변수에 저장하는데 사용</li>
  <li>tr: 표준입력을 받은 문자들을 변환, 삭제, 압축</li>
</ul>]]></content><author><name>플라이팬</name></author><category term="책 공부" /><category term="리눅스" /><category term="우분투" /><summary type="html"><![CDATA[입문자를 위한 가장 쉬운 리눅스 자습서]]></summary></entry><entry><title type="html">[책 공부] 단위테스트</title><link href="https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/14/book-%EB%8B%A8%EC%9C%84%ED%85%8C%EC%8A%A4%ED%8A%B8.html" rel="alternate" type="text/html" title="[책 공부] 단위테스트" /><published>2025-12-14T18:15:00+09:00</published><updated>2025-12-14T18:15:00+09:00</updated><id>https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/14/book-%EB%8B%A8%EC%9C%84%ED%85%8C%EC%8A%A4%ED%8A%B8</id><content type="html" xml:base="https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/14/book-%EB%8B%A8%EC%9C%84%ED%85%8C%EC%8A%A4%ED%8A%B8.html"><![CDATA[<h2 id="책-소개">책 소개</h2>

<p><img src="/assets/images/단위테스트.png" alt="단위테스트" /></p>

<p>이 책은 테스트에 대해 조금이라도 공부를 해본 사람이라면 추천합니다. 단위테스트를 왜 작성해야하는지, 어떤 구조로 작성해야 좋은지를 굉장히 잘 설명하고 있습니다. 테스트코드를 작성함으로써 어떻게 지속 가능한 성장을 이룰 수 있는지를 회귀테스트, 리팩토링, 거짓 양성 등 다양한 개념을 도출하여 설명하고 있습니다. 또한 테스트코드를 잘 작성하는 것이 도메인 계층 간에 관심사를 분리하여 더욱 유지보수하기가 좋아지는 코드를 만들 수 있습니다.</p>

<h2 id="단위테스트-현황">단위테스트 현황</h2>

<p>단위테스트의 목표는 소프트웨어 프로젝트의 지속 가능한 성장을 가능하게 하는 것이다. 지속 가능하다는 것이 단위테스트의 핵심이다. 테스트코드의 커버리지를 나타내는 지표가 있는데 반드시 100%일 필요는 없다. 너무 적으면 테스트코드가 부족하다는 문제가 되지만 100%에 가깝다고 좋은 테스트코드는 아니다. 100%에 가깝게 만드려고 오히려 시스템을 속일 방법을 찾기 시작하며 좋은 코드를 고려하지 않은채 테스트 통과만 된다고 생각한다. (Mocking만 계속 하는 것이다.) 테스트코드 작성의 목표를 잃은 것이다. 80%만 채우더라도 핵심 도메인 로직 위주로 잘 작성하는 것이 중요하다.</p>

<blockquote>
  <p>좋은 단위 테스트 스위트는 개발 속도를 지키면서 침체 단계에 빠지지 않게 한다. 이러한 테스트 스위트가 있다면 변경 사항이 회귀로 이어지지 않을 것이라고 확신해도 좋다. 또한 코드를 리팩토링하거나 새로운 기능을 추가하는 것이 더 쉬워진다.</p>
</blockquote>

<h2 id="단위테스트란">단위테스트란</h2>

<p>단위테스트란</p>

<ul>
  <li>작은 코드 조각(단위)을 검증하고,</li>
  <li>빠르게 수행하고,</li>
  <li>격리된 방식으로 처리하는 자동화된 테스트다.</li>
</ul>

<p>단위테스트의 정의에서 격리 문제를 어떻게 다루는지에 대한 주요 차이는 고전파와 런던파가 있다.</p>

<h3 id="고전파detrioit">고전파(Detrioit)</h3>

<ul>
  <li>실제 협력 객체를 최대한 사용</li>
  <li>테스트 대상 시스템(SUT)과 그 의존성을 함께 테스트하는 것을 선호</li>
  <li>Mock은 외부 시스템(DB, API 등)이나 공유 의존성에만 제한적으로 사용</li>
  <li>테스트가 실제 운영 환경과 유사하게 동작하는 것을 중시
(예시: 계산기 클래스가 로그 객체를 사용한다면, 실제 로그를 함께 테스트합니다)</li>
</ul>

<h3 id="런던파-mockist">런던파 (Mockist)</h3>

<ul>
  <li>Mock 객체를 적극적으로 활용</li>
  <li>테스트 대상을 최대한 격리시켜 테스트하는 것을 선호</li>
  <li>모든 의존성을 Mock으로 대체하여 순수하게 하나의 클래스만 테스트</li>
  <li>객체 간 상호작용과 메시지 전달을 검증하는 데 중점을 둔다 (예: 계산기 클래스를 테스트할 때 로그를 Mock으로 만들고, 로그의 특정 메서드가 호출되었는지 검증합니다)</li>
</ul>

<p>개인적으로 고전파를 더 선호한다. 고품질의 테스트를 만들고 단위테스트의 궁극적인 목표인 프로젝트의 지속 가능한 성장을 달성하는데 더 적합하다. 목을 사용하는 테스트는 고전적인 테스트보다 불안정한 경향이 있다.</p>

<p>테스트를 작성하는데 단순히 검증하는 것을 넘어서 코드 리팩토링, 유지보수 가능한 코드를 만들게 해준다. 이러한 점에서 고전파로 테스트를 작성하는 것이 더 수월하다. 특히 런던파의 가장 큰 문제는 과잉 명세, 즉 SUT 세부 구현에 결합된 테스트 문제다. 이것은 구현에 종속되어 있기 때문에 기능이 똑같아도 리팩토링을 하게되면 테스트가 깨지는 현상이 발생한다. 또한 Mock은 테스트대역을 만들어서 테스트가 항상 성공하도록 설정되어 있어서 객체간에 제대로 협력하지 못해도 테스트는 통과하는 현상이 있다.</p>

<h2 id="단위테스트-구조">단위테스트 구조</h2>

<p>모든 테스트는 AAA 패턴(준비, 실행, 검증)을 따라야 한다.</p>

<p>테스트에서 준비, 실행 또는 검증 구절이 여러 개 있는 테스트를 볼 수 있다. 즉, 여러 개의 동작 단위를 검증하는 하나의 테스트를 의미하는데 이것은 단위테스트가 아닌 통합테스트로 피하는 것이 좋다. 각 동작을 고유의 테스트로 도출해야 한다. 그리고 테스트 내 if, for문 등은 피해야 한다. 준비 구절이 큰 경우에는 팩토리 클래스를 생성하여 분리시키는 것도 좋다.</p>

<h3 id="테스트-픽스처-재사용">테스트 픽스처 재사용</h3>

<p>테스트 픽스처는 테스트 실행 대상 객체다. 테스트를 실행하기 위해 필요한 준비물을 말하는데 이러한 테스트 픽스처는 테스트 간에 재사용하는 것이 좋다. 자바 언어에서는 <code class="language-plaintext highlighter-rouge">@Before</code> 이라는 애노테이션을 제공해준다. 특히 테스트 간에는 모두 독립적으로 실행되어야 하며 절대 결합되서는 안된다.</p>

<ul>
  <li>테스트 픽스처 초기화 코드는 생성자에 두지 말고 팩토리 메서드를 도입해서 재사용하자.</li>
</ul>

<h2 id="테스트-대역">테스트 대역</h2>

<p>테스트 대역은 모든 유형의 비운영용 가짜 의존성을 설명하는 포괄적인 용어다. 테스트 대역의 주 용도는 테스트를 편리하게 하는 것이다. 테스트 대상 시스템으로 실제 의존성 대신 전달되므로 설정이나 유지보수가 어려울 수 있다.</p>

<p>테스트 대역에는 더미(dummy), 스텁(stub), 스파이(spy), 목(mock), 페이크(fake)가 있다.</p>

<ul>
  <li>더미(dummy): 아무 동작도 안 하고, 그냥 파라미터를 채우기 위해 전달되는 객체</li>
  <li>스텁(stub): 미리 준비된 답변을 반환하는 객체. 질문하면 항상 같은 대답만 한다.</li>
  <li>스파이(spy): Stub처럼 답변도 하지만, 호출 내역을 몰래 기록한다. 나중에 확인 가능하다.</li>
  <li>목(mock): 예상되는 호출을 미리 프로그래밍하고, 그대로 호출됐는지 검증한다.</li>
  <li>페이크(fake): 실제로 동작하는 간단한 구현체. 진짜처럼 작동하지만 운영에는 사용하지 못한다.</li>
</ul>

<p>목은 SUT에서 관련 의존성으로 나가는 상호작업을 모방하고 검사하는 방면, 스텁은 내부로 들어오는 상호작업만 모방하고 검사하지는 않는다. SUT에서 스텁으로의 호출은 SUT가 생성하는 최종결과가 아니다. 이러한 호출은 최종결과를 산출하기 위한 수단일 뿐이다. 즉, 스텁은 SUT가 출력을 생성하도록 입력을 제공하는 것이지 스텁으로 상호작용을 검증하면 안된다. 스텁 호출을 검증하면 ‘과잉 명세’ 문제가 생긴다.</p>

<blockquote>
  <p>최종 결과가 아닌 사항을 검증하는 이러한 관행을 과잉 명세(overspecification)이라 한다.</p>
</blockquote>

<p>SMTP 같은 외부서비스에 대한 호출을 목으로 하는 이유는 타당하다. 리팩토링 후에도 통신 유형이 그대로 유지되도록 하기 때문에 테스트 취약성을 야기하지 않는다. 이메일 발송은 애플리케이션의 최종 목적이다. 사이드 이펙트이자 관찰 가능한 결과물이다. 그 밖에 메시지 큐 발생, 외부 API 호출 등이 목을 사용해도 괜찮다.</p>

<p>애플리케이션에는 시스템 내부 통신과 시스템 간 통신이라는 두가지 유형이 있다. 시스템 내부 통신은 애플리케이션 내 클래스 간의 통신이다. 시스템 간 통신은 애플리케이션이 외부 애플리케이션과 통신할 때는 말한다. 시스템 내 통신을 검증하고자 목을 사용하면 취약한 테스트로 이어진다. 시스템 내 통신은 구현 세부 사항이기 때문이다. 따라서 시스템 간 통신(애플리케이션 경계를 넘는 통신)과 해당 통신의 사이드 이펙트가 외부 환경에서 보일 때만 목을 사용하는 것이 타당하다.</p>

<h2 id="통합테스트">통합테스트</h2>

<p>단위테스트에만 전적으로 의존하면 시스템이 전체적으로 잘 작동하는지 확신할 수 없다. 데이터베이스나 메시지 버스 등의 외부 시스템과 어떻게 통합되는지 확인해야 한다.</p>

<p>단위테스트가 아닌 모든 테스트가 바로 통합테스트에 해당된다. 대부분은 단위테스트로 작성하고, 중요한 통합테스트가 비즈니스 시나리오당 하나, 두개정도 있으면 시스템 전체의 정확도를 보장할 수 있다.</p>

<blockquote>
  <p>통합테스트는 컨트롤러를 다루고, 단위테스트는 알고리즘과 도메인 모델을 다룬다.</p>
</blockquote>

<p>통합테스트에서만 목을 사용하고 단위테스트에서는 사용하지 말아야 한다. 목은 바로 통합테스트만을 위한 것이다. 이 규칙을 적용하면 자연스레 도메인 모델과 컨트롤러라는 고유 계층 두개로 만들어지게 된다.</p>]]></content><author><name>플라이팬</name></author><category term="책 공부" /><category term="Unit Testing" /><category term="단위테스트" /><summary type="html"><![CDATA[생산성과 품질을 위한 단위 테스트 원칙과 패턴]]></summary></entry><entry><title type="html">[책 공부] 루비로 배우는 객체지향 디자인</title><link href="https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/13/book-%EB%A3%A8%EB%B9%84%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%EB%94%94%EC%9E%90%EC%9D%B8.html" rel="alternate" type="text/html" title="[책 공부] 루비로 배우는 객체지향 디자인" /><published>2025-12-13T21:15:00+09:00</published><updated>2025-12-13T21:15:00+09:00</updated><id>https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/13/book-%EB%A3%A8%EB%B9%84%EB%A1%9C%20%EB%B0%B0%EC%9A%B0%EB%8A%94%20%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%20%EB%94%94%EC%9E%90%EC%9D%B8</id><content type="html" xml:base="https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/13/book-%EB%A3%A8%EB%B9%84%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%EB%94%94%EC%9E%90%EC%9D%B8.html"><![CDATA[<h2 id="책-소개">책 소개</h2>

<p><img src="/assets/images/루비로_배우는_객체지향_디자인.png" alt="루비로 배우는 객체지향 디자인" /></p>

<p>객체지향 OOP에 대해서 공부하고 싶다면 이 책을 추천합니다. 좀 더 실무적인 관점에서 공부할 수 있어 좋았고 어떤 코드가 유지보수하기 좋은지 어떻게 설계해야 하는지 예시 도메인을 제시하고 설명해나갑니다.</p>

<h3 id="디자인은-어떻게-실패하는가">디자인은 어떻게 실패하는가</h3>

<p>디자인이 없는 애플리케이션은 그 자체로 붕괴의 씨앗을 품고 있다. 이런 애플리케이션은 쉽게 작성할 수 있지만, 점점 더 수정할 수 없는 애플리케이션이 된다. 나중에는 프로그래머가 새로운 수정 사항이 들어올 때마다 이렇게 말하기 시작한다. <strong>“응, 그 기능 추가할 수 있어. 대신 다른 기능은 다 고장날거야.”</strong></p>

<blockquote>
  <p>변화를 손쉽게 받아들일 수 있도록 코드를 배치하는 일, 이것이 디자인이다.</p>
</blockquote>

<h3 id="데이터data가-아닌-행동behavior에-기반한-코드를-작성하라">데이터(data)가 아닌 행동(behavior)에 기반한 코드를 작성하라</h3>

<p>행동은 메서드 속에 담겨 있고 메시지를 보내는 행위를 통해 실행된다. 하나의 책임만 지는 클래스(SRP)를 만들면 각각의 작은 행동들은 단 한 곳에만 존재한다. “반복하지 말 것(Don’t Repeat Yourself, DRY)”. DRY한 코드는 변화를 잘 견뎌 내는데, 클래스의 행동을 수정하기 위해 코드의 오직 한 부분만 수정하면 되기 때문이다. 그리고 데이터 구조는 숨겨라. 데이터가 필요하다면 객체가 가지는 책임, 즉 행위을 만들어라. <strong>데이터 구조를 들여다보던 작업을 객체에 대한 메시지를 전송으로 대체한다.</strong> 객체가 행동에 집중하게 되면 그 객체가 하는 일이 무엇인지 더욱 명확해진다.</p>

<ul>
  <li>주석을 넣어야 할 필요가 없어진다. - 클린코드(Clean Code) 책에서도 나오는 말</li>
  <li>재사용을 유도한다.</li>
  <li>다른 클래스로 옮기기 쉽다. - 리팩토링</li>
</ul>

<p>객체의 책임이 많다면 추가적인 책임들을 격리시킨다. 즉, 별도의 클래스를 만들어서 책임을 분리시키는 것이다. 이렇게 해도 복잡하지 않은 이유는 응집도가 높기 때문이다.</p>

<h3 id="도메인">도메인</h3>

<p>완전히 새로운 애플리케이션의 첫 코드를 작성하는건 두려운 일이다. 이미 있는 코드에 새로운 코드를 추가할 때는 기존 디자인을 따르지만 새 하얀 도화지에 애플리케이션의 패턴을 영원히 규정하게 될 결정을 내려야만 한다.</p>

<p>먼저 패스트핏의 사업모델을 보면서 이 애플리케이션에 들어갈 법한 클래스가 어떠 것들이 있는지 떠올른다. 자전거 여행 회사라면, Customer(여행객), Trip(여행), Route(여행길), Bike(자전거) 그리고 Mechanic(정비공) 클래스 정도를 떠올랐다. 이런 클래스들을 바로 떠올릴 수 있었던 이유는 이 클래스들이 애플리케이션 속의 명사들, 정보(data)와 행동(behavior) 둘 다를 가지고 있는 명사들을 표현하기 때문이다. 이것들을 <strong>도메인 객체(domain objects)</strong>라고 부른다.</p>

<p>전문가들은 도메인 객체가 아니라 도메인 객체들이 주고받는 메시지에 집중한다. 이 메시지들은 새로운 객체를 찾도록 도와주는 가이드다. 도메인 객체만큼이나 꼭 필요하지만 잘 드러나지 않는 새로운 객체를 찾도록 도와준다. 키보드를 잡고 타이핑을 시작하기 전에 우리의 유스케이스를 만족시켜 줄 수 있는 객체들 그리고 메시지들의 의도를 구상할 필요가 있다.</p>

<ul>
  <li>시퀀스 다이어그램</li>
</ul>

<p>객체들간의 협력 관계속에서 중요한 것은 어떻게(How)해야 하는지 말해주지 말고, 어떤 것(What)을 달라고 요구(요청)하는 것이다. 그러면 협력하는 객체가 응답해주는 것이다. 즉, Bike 클래스에서 계산하는 하는 로직을 Trip 클래스에서 대신 하지 말자는 것이다. 계산은 Bike가 하도록 시키고, 그 결과를 달라고 Trip 클래스에서는 요청만 하는 것이다. 그러면 Bike 클래스가 응답을 내려줄 것이다. 이렇게 해야 객체들간의 응집도가 높아지고 결합도는 낮아진다. 각 도메인 객체들이 독립적으로 자신의 역할과 책임을 다하는 것이 된다.</p>

<blockquote>
  <p>Trip 클래스와 Customer 클래스 간의 협력 관계에서 메시지에 주목하면 여행을 여행객과 어울리게 만드는 클래스인 TripFinder 클래스가 필요하게 된다. 즉, 객체가 아닌 메시지에 주목하면 클래스는 자동적으로 따라 나온다.</p>
</blockquote>

<h3 id="덕-타이핑">덕 타이핑</h3>

<p>덕 타이핑(Duck Typing)은 동적 타입의 한 종류로 객체의 변수 및 집합이 객체의 타입을 결정하는 것을 말한다. 클래스 상속이나 인터페이스 구현으로 타입을 구분하는 대신, 덕 타이핑은 객체가 어떤 타입에 걸맞은 변수와 메소드를 지니면 객체를 해당 타입에 속하는 것으로 간주한다.</p>

<blockquote>
  <p>Duck의 뜻이 오리인데, 만약 어떤 새가 오리처럼 걷고, 헤엄치고, 꽥꽦거리는 소리를 낸다면 나는 그 새를 오리라고 부를 것이다.</p>
</blockquote>

<p>이렇게 보면 덕 타이핑은 유연하다고 볼 수 있지만, 동적 타입인 만큼 컴파일 타임에 체크하지 않기 때문에 런타임 에럭가 발생할 수 있고, 상속이나 인터페이스처럼 제약을 주는 것이 아니기 때문에 의도치 않은 에러를 발생시킬 수 있다.</p>

<p>그럼에도 덕 타이핑을 사용하는 것은 컴파일 타임에 일일히 체크하지 않고 타입도 코드 작성시 명시 않으므로 좀 더 빠르게 코드를 작성할 수 있다. 또한 이미 존재하는 외부 라이브러리나 서드파티 객체들을 통합할 때 유용하다.</p>

<blockquote>
  <p>정적 타입 언어인 자바에서는 덕 타이핑 대신에 어댑터 패턴을 사용하여 인터페이스 타입을 지원하는지 체크(supports)한 다음에 사용(handler) 가능하다.</p>
</blockquote>

<h3 id="상속">상속</h3>

<p>상속은 코드 재사용성 공통 기능을 부모 클래스에 한 번만 작성하면 여러 자식 클래스에서 재사용할 수 있다. 또한 부모 클래스에서 한 번만 수정하면 모든 자식 클래스가 반영된다. 사실 이것이 장점이 아닐 수 있다.</p>

<p>그 이유는 강한 결합 때문이다. 부모의 내부 구현에 의존되어 있기 때문에 자식 클래스가 독립적으로 책임을 갖기 어렵다. 그렇기 때문에 부모 클래스에서 수정한다고 자식 클래스에도 영향이 가는 것은 좋지 않은 설계이다. 자식 클래스들은 독립적으로 존재해야 한다. 이렇게 설계가 되어야 응집도가 높아지는 것이다. 상속을 사용한 설계는 결국 SOLID 원칙에 위배되기도 한다.</p>

<p>그래서 추상 클래스를 사용한다. 부모의 구현을 따라 쓰는 것이 아닌 자식 클래스에서 직접 구현하여 독립적으로 기능을 수행한다. 이것이 응집도를 높이는 추상 클래스의 장점이다. 더 이상 부모 클래스에 영향을 받지 않아도 된다는 의미이다.</p>

<h3 id="테스트코드">테스트코드</h3>

<p>테스트코드 작성의 의미</p>

<ul>
  <li>나중에 애플리케이션은 점점 커질수록 버그를 만나기 쉬운데 테스트코드는 이러한 버그를 쉽게 찾아낼 수 있다.</li>
  <li>도메인 로직을 이해할 수 있다. 테스트코드 작성이 곧 문서화이다.</li>
  <li>자연스레 추상화 설계를 높일 수 있다.</li>
  <li>안전하게 리팩토링할 수 있다.</li>
  <li>자연스레 디자인 패턴, 아키텍처를 적용할 수 있다.</li>
</ul>

<p>테스트는 꼭 필요하다. 잘 디자인된 애플리케이션은 매우 추상적이고, 계속 변경된다. 테스트가 없다면 이해할 수도 없고 안전하게 수정할 수도 없을 것이다. 최상의 테스트는 실제 코드와 느슨하게 결합되어 있어야 한다. 그리고 모든 코드를 한 번만, 제대로 된 장소에서 테스트해야 한다. 이런 테스트는 코드 작성 비용을 높이지 않으면서도 새로운 가치를 제공한다. 모든 상황에 적응할 수 있으며 예상치 못했던 그 어떤 요구사항에도 대처할 수 있다.</p>]]></content><author><name>플라이팬</name></author><category term="책 공부" /><category term="OOP" /><category term="객체지향" /><summary type="html"><![CDATA[지속가능한 소프트웨어를 만드는 방법]]></summary></entry><entry><title type="html">[책 공부] 객체지향의 사실과 오해</title><link href="https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/11/book-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9D%98-%EC%82%AC%EC%8B%A4%EA%B3%BC-%EC%98%A4%ED%95%B4.html" rel="alternate" type="text/html" title="[책 공부] 객체지향의 사실과 오해" /><published>2025-12-11T22:30:00+09:00</published><updated>2025-12-11T22:30:00+09:00</updated><id>https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/11/book-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9D%98%20%EC%82%AC%EC%8B%A4%EA%B3%BC%20%EC%98%A4%ED%95%B4</id><content type="html" xml:base="https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/11/book-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9D%98-%EC%82%AC%EC%8B%A4%EA%B3%BC-%EC%98%A4%ED%95%B4.html"><![CDATA[<h2 id="책-소개">책 소개</h2>

<p><img src="/assets/images/객체지향의_사실과_오해.png" alt="객체지향의 사실과 오해" /></p>

<p>인터페이스와 구현을 분리하자. 자연스레 테스트코드 작성이 쉬워지고, 도메인 모델이 설계가 유연하며, 변경이 용이한 시스템을 만들 수 있다. 객체를 클래스로 보지 않고 객체간의 협력 관계를 생각하고 그 협력 안에서 메시지가 어떻게 전달되고 받는지 행동을 먼저 생각하자.</p>

<p>이 책은 신입이 볼 때와 3년차가 볼 때와 또 다릅니다. 꼭 읽었으면 하는 책이니 추천합니다!</p>

<h2 id="역할과-책임">역할과 책임</h2>

<p>손님은 카페인을 채우기 위해 커피를 주문할 책임을 수행하고, 캐시어는 손님의 주문을 받는 책임을 성실히 수행하고, 바리스타는 주문된 커피를 제조하는 책임을 수행한다.</p>

<p>커피 주문이라는 <strong>협력</strong>에 참여하는 모든 사람들은 커피가 정확하게 주문되고 주문된 커피가 손님에게 정확하게 전달될 수 있도록 맡은 받 <strong>역할</strong>과 <strong>책임</strong>을 다하고 있는 것이다. 사람 사는 곳이라면 어디서나 역할, 책임, 협력이 존재한다.</p>

<h2 id="다형성">다형성</h2>

<ul>
  <li>역할은 대체 가능성을 의미한다. 손님 입장에서 캐시어는 다른 사람이 캐시어 역할을 할 수 있다면 다른 사람으로 대체할 수 있다는 것이다.</li>
  <li>책임을 수행하는 방법은 자율적으로 선택할 수 있다. 동일한 요청에 대해 서로 다른 방식으로 응답할 수 있는 능력, 이것을 <strong>다형성</strong>이라 한다.</li>
  <li>또한 한 사람이 동시에 여러 역할을 수행할 수도 있다.</li>
</ul>

<h2 id="협력">협력</h2>

<p>객체는 충분히 ‘협력적’이어야 한다. 외부의 도움을 무시한 채 모든 것을 스스로 처리하려는 전지전능한 객체는 내부적인 복잡도에 의해 자멸하고 만다. 그리고 객체는 다른 객체의 명령에 복종하는 것이 아닌 요청에 응답할 뿐이다.</p>

<p>객체의 사적인 부분은 객체 스스로 관리하고 외부에서 일체 간섭할 수 없도록 차단해야 하며, 객체의 외부에서는 접근이 허락된 수단을 통해서만 객체와 의사소통해야 한다.</p>

<h3 id="여기까지-정리하면">여기까지 정리하면</h3>

<p>아키텍처를 설계할 때 도메인을 먼저 설계해야 하고, 각 도메인 객체가 책임을 가지고 있어야 한다. 우리가 작성하는 서비스 레이어에서는 각 도메인이 가지고 있는 책임과 역할들을 가져다가 사용하는 정도이다. 그리고, getter와 setter를 사용하지 않고 필요한 행동이 있다면 메서드를 따로 만들어서 역할을 수행하도록 만들어야 한다.(캡슐화) 우리는 그 역할을 가져다 사용할 뿐이다. 이렇게 설계함으로써 어떠한 라이브러리에도 의존하지 않는 순수 도메인을 만들 수 있다. 이러한 아키텍처가 바로 DDD(Domain Driven Design)이다.</p>

<h2 id="클래스가-아닌-객체">클래스가 아닌 객체</h2>

<p>훌륭한 객체지향 설계자가 되기 위해 거쳐야 할 첫 번째 도전은 코드를 담는 클래스의 관점에서 메시지를 주고받는 객체의 관점으로 사고의 중심을 전환하는 것이다. 중요한 것은 어떤 클래스가 필요한가가 아니라 어떤 객체들이 어떤 메시지들을 주고받으며 협력하는가이다. 클래스는 객체들의 협력관계를 코드로 옮기는 도구에 불과하다.</p>

<p>객체는 상태(state), 행동(behavior), 식별자(identity)를 지닌 실체로 보는 것이 가장 효과적이다. 실제 도메인 엔티티를 설계할 때 이렇게 설계할 것이다. (식별자=id, 상태=필드, 행동=메서드) 행동에 의해 상태값이 변경된다.</p>

<blockquote>
  <p>참고로 값 객체(Value Object)는 식별자를 가지지 않는 값을 가리키는 용어다. 그리고 값 객체는 불변이다.</p>
</blockquote>

<h2 id="기계로서의-객체">기계로서의 객체</h2>

<p>객체지향의 세계를 창조하는 개발자들의 주된 업무는 객체의 상태를 조회하고 객체의 상태를 변경하는 것이다. 일반적으로 객체의 상태를 조회하는 작업을 <strong>쿼리(Query)</strong>라고 하고 객체의 상태를 변경하는 <strong>명령(Command)</strong>라고 한다. 객체가 외부에 제공하는 행동의 대부분은 쿼리와 명령으로 구성된다. 그리고 이 쿼리와 명령의 책을 분리한 패턴을 바로 <strong>CQRS(Command And Query Segregation)</strong>라고 한다.</p>

<p>객체지향 설계는 애플리케이션에 필요한 협력을 생각하고 협력에 참여하는데 필요한 행동을 생각한 후 행동을 수행할 객체를 선택하는 방식으로 수행된다. 즉, 행동을 결정한 후에야 행동에 필요한 정보가 무엇인지를 고려하게 되며 이 과정에서 필요한 상태가 결정된다. 책임-주도 설계(Responsibility-Driven Design, RDD)는 협력이라는 문맥 안에서 객체의 행동을 생각하도록 도움으로써 응집도를 높이고, 재사용 가능한 객체를 만들 수 있다.</p>

<h2 id="타입">타입</h2>

<p>타입 없는 무질서가 초래한 혼돈의 세상에 질려버린 사람들은 메모리 안의 데이터에 특정한 의미를 부여하기 시작했다. 타입 시스템의 목적은 메모리 안의 모든 데이터가 비트열로 보임으로써 야기되는 <strong>혼란을 방지</strong>하는 것이다. 타입 시스템은 메모리 안에 저장된 0과 1에 대해 수행 가능한 작업과 불가능한 작업을 구분함으로써 데이터가 잘못 사용되는 것을 방지한다. 즉, 타입 시스템은 데이터가 잘못 사용되지 않도록 제약사항을 부과하는 것이다. 객체는 데이터가 아니다. 객체가 이웃하는 객체와 협력하기 위해 어떤 <strong>행동</strong>을 해야 할지를 결정하는 것이다. 만약 서로 다른 객체가 같은 데이터를 가지고 있더라도 다른 행동을 한다면 그 객체들은 서로 다른 타입으로 분류되어야 한다. 즉, 객체의 타입을 결정하는 것은 객체의 행동뿐이다.</p>

<h2 id="객체에-대한-선입견">객체에 대한 선입견</h2>

<ol>
  <li>시스템에 필요한 데이터를 저장하기 위해 객체가 존재한다는 선입견</li>
</ol>

<ul>
  <li>객체가 존재하는 이유는 행위를 수행하며 협력에 참여하기 위해서다.</li>
</ul>

<ol>
  <li>객체지향이 클래스와 클래스 간의 관계를 표현하는 시스템의 정적인 측면에 중점을 둔다는 선입견</li>
</ol>

<ul>
  <li>협력에 참여하는 동적인 객체이며, 클래스는 단지 시스템에 필요한 객체를 표현하고 생성하기 위해 프로그래밍 언어가 제공하는 구현 메커니즘이다.</li>
</ul>

<h2 id="tda">TDA</h2>

<p>묻지 말고 시켜라.</p>

<p>메시지를 먼저 결정하고 객체가 메시지를 따르게 하는 설계 방식은 객체가 외부에 제공하는 인터페이스가 독특한 스타일을 따르게 한다. 이 스타일을 묻지 말고 시켜라(Tell, Don’t Ask) 스타일 또는 데메테르 법칙(Law of Demeter)이라고 한다. 어떤 객체가 필요한지를 생각하지 말고 어떤 메시지가 필요한지를 먼저 고민해라. 모든 객체는 자신의 상태를 기반으로 스스로 결정을 내려야 한다.</p>]]></content><author><name>플라이팬</name></author><category term="책 공부" /><category term="OOP" /><category term="객체지향" /><summary type="html"><![CDATA[역할, 책임, 협력 관점에서 본 객체지향]]></summary></entry><entry><title type="html">[책 공부] SQL 코딩의 기술</title><link href="https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/07/book-sql-coding-tech.html" rel="alternate" type="text/html" title="[책 공부] SQL 코딩의 기술" /><published>2025-12-07T18:30:00+09:00</published><updated>2025-12-07T18:30:00+09:00</updated><id>https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/07/book-sql-coding-tech</id><content type="html" xml:base="https://azurealstn.github.io/%EC%B1%85%20%EA%B3%B5%EB%B6%80/2025/12/07/book-sql-coding-tech.html"><![CDATA[<h2 id="책-소개">책 소개</h2>

<p><img src="/assets/images/sql-coding-tech.png" alt="책 - SQL 코딩의 기술" /></p>

<p>SQL을 공부하고 싶다면 해당 책을 추천합니다. 예시들을 보여주면서 어떤 쿼리가 안좋은건지 어떻게 개선할 수 있는지를 보여줍니다.</p>

<h2 id="기본키">기본키</h2>

<ul>
  <li>테이블당 반드시 하나의 기본키가 있어야 한다.</li>
  <li>복합 기본키는 최대한 사용하지 말아야 한다.</li>
</ul>

<h2 id="인덱스는-신중히">인덱스는 신중히</h2>

<p>이 책에서도 인덱스를 활용하는 것이 중요하다고 말하고 있다. 인덱스를 활용함으로써 성능을 크게 높일 수 있지만 좋다고 남발하면 갱신 속도를 늦추며, 용량을 크게 차지할 수 있다. 즉, 인덱스를 생성하기 전에 해당 테이블에서 정말 필요한 용도인지를 신중히 판별한 후에 사용한다. 또한 인덱스는 테이블이 클 때만 사용하는 것이 좋다.</p>

<ul>
  <li>인덱스를 단순 필터링 이상의 목적으로 사용하자.</li>
</ul>

<h2 id="트리거">트리거</h2>

<p>트리거는 갱신 작업을 자동으로 실행하는 기능을 제공한다. 다만, 트리거는 이식성과 가시성이 좋지 않으며 개발자 입장에서 어떤 기능을 수행해서 이러한 결과가 나왔는지 디버깅이 어렵다. 또한 성능 측면에서도 좋지 않을 수 있다. (실행계획 불가)</p>

<h2 id="핵심정리">핵심정리</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">NOT IN</code>보다는 <code class="language-plaintext highlighter-rouge">NOT EXISTS</code>를 사용하는 것이 더 빠르다.</li>
  <li>datetime 값은 반올림 오류가 있음. BETWEEN 대신 &gt;=, &lt;= 연산자를 사용한다.</li>
  <li>LIKE 연산자를 사용할 때 % 문자는 검색 문자열 끝에 붙여야 인덱스를 사용할 수 있다. (ex. ‘something%’)</li>
  <li>SQL에서 차감 연산을 할 때는 OUTER JOIN을 사용한다.</li>
  <li>집계 표현식에 조건을 주려면 HAVING절을 사용한다.</li>
  <li>‘주’테이블을 셀프 조인할 때는 LEFT JOIN을 사용한다.</li>
  <li>ON절에 있는 컬럼을 인덱스로 만들면 성능을 높일 수 있다.</li>
  <li>널 값이 있는 로우를 포함해 모든 로우의 개수를 세려면 COUNT(*)를 사용한다.</li>
  <li>중복을 제거하려면 DISTINCT를 사용한다.</li>
  <li>공통 테이블 표현식(CTE)으로 동일한 서브쿼리를 한 번 이상 사용하는 복잡한 쿼리를 간단하게 만들 수 있다.</li>
  <li>서브쿼리 대신 조인을 사용해 더 효율적인 쿼리를 작성할 수 있다.</li>
</ul>]]></content><author><name>플라이팬</name></author><category term="책 공부" /><category term="데이터베이스" /><category term="DB" /><summary type="html"><![CDATA[데이터베이스의 SQL쿼리 심화 내용]]></summary></entry></feed>