도메인 주도 설계로 시작하는 마이크로 서비스 개발을 읽고

1. 마이크로서비스를 위한 조건

  1. 업무 기능 중심의 팀

    • 기술별로 팀이 나눠지게 되면 서비스 한개를 개발하는데 많은 의사소통이 필요하고 의사결정이 느려진다. 업무기능을 중심으로 다양한 기술을 가진 사람들이 하나의 팀이 되어 서비스를 만들어야 한다.
  2. 폴리글랏 프로그래밍

    • 각각의 서비스에 맞는 효율적인 방법론과 도구, 기술을 찾아 적용.
  3. 개발 생명주기는 프로젝트단위가 아닌 제품 중심

    • 초기에 모든 일정을 계획하고 요구사항 정의, 설계, 개발을 진행하는 것은 변경이나 새로운 아이디어를 포용하기 힘들다. 제품중심의 애자일 개방 방식을 채용하여 단기간의 스프린트로 계속한 개발/피드백으로 제품을 지속적으로 변화, 개선하는 것이 마이크로서비스이다.
  4. 통합 저장소가 아닌 데이터를 나눠 관리

    • 과거에는 스토리지 가격 및 네트워크 속도에 따라 통합저장소를 많이 이용했지만 현재는 가격도 저렴하고 네트워크 대역폭이 매우 커졌다.

    • 폴리그랏 저장소 접근법 : 서비스별로 db를 갖도록 설계 ( 각 저장소가 서비스별로 분산 )

      다른 서비스의 저장소를 직접 호출할 수 없고 API를 통해서만 접근해야 한다는 의미

      • 문제점 : 데이터의 비즈니스 정합성를 맞춰야하는 데이터 일관성 문제
      • 해결방법
        • 비동기 이벤트 처리를 통한 협업 (결과적 일관성) : 두 서비스의 데이터가 일시적으로 불일치하는 시점이 있고 일관성이 없는 상태지만 결국에는 두 데이터가 같아진다는 개념

          여러 트랜잭션을 하나로 묶지 않고 별도의 트랜잭션을 각각 수행하고 일관성이 달라진 부분은 체크해서 보상 트랜잭션으로 일관성을 맞추는 개념

          아래와 같이 메시지 큐를 이용

  1. 실패를 고려한 설계
    • 테스트 환경 갖추기

    • 모니터링 체계 갖추기

    • 서킷 브레이커 패턴 : 각 서비스를 모니터링 하다가 한 서비스가 다운되거나 실패하면 이를 호출하는 서비스의 연계를 차단하고 적절하게 대응하는 것

      넷플릭스카오스 몽키라는 일부러 장애를 발생시키는 도구를 만들어 탄력적인 아키텍처가 제대로 동작하는지 점검한다.



2. MSA의 이해

2.1 리액티브 시스템의 4가지 특성

  • 응답성 : 사용자에게 신뢰성 있는 응답을 빠르고 적절하게 제공
  • 탄력성 : 장애가 발생하거나 부분적으로 고장나더라도 시스템 전체가 고장 나지 않고 빠르게 복구하는 능력
  • 유연성 : 시스템의 사용량에 변화가 있더라도 균일한 응답성을 제공하는 것 ( 시스템 사용량에 비래하여 자원을 늘리거나 줄이는 능력)
  • 메시지 기반 : 비동기 메시지 전달을 통해 위치 투명성, 느슨한 결합, 논블로킹 통신을 지향


2.2 운영과 관리를 위한 플랫폼 패턴

서비스 디스커버리(서비스 레지스트리) 패턴

클라이언트가 여러개의 마이크로서비스를 호출하기 위해서 최적 경로를 찾아주는 라우팅(ex. Zuul), 부하분산을 위한 로드밸런싱(Ribbon) 기능이 필요하다. 이때 라우터는 최적의 경로를 탐색하기 위해 서비스 명칭에 해당하는 IP를 알아야 하는데 유동 IP의 정보를 매핑해서 보관할 저장소(Eureka)도 필요하는데 이러한 패턴을 서비스 레지스트리 패턴이라고 한다.

Netflix OSS 뿐만이 아닌 쿠버네티스 DNS 및 서비스로도 제공하는 기능으로 다른 솔루션들도 존재

API 게이트웨이 패턴

다양한 클라이언트가 다양한 서비스에 접근하기 위해 단일 진입점을 만들어 두는 패턴으로 L4와 같이 하드웨어로 구현할 수도 있고 소프트웨어로 구현할 수도 있다. API 게이트웨이는 아래와 같은 기능을 제공한다.
  • 레지스트리 서비스와 연계한 동적 라우팅, 로드 밸런싱
  • 권한 서비스와 연계한 인증/인가
  • 로그 집계 서비스와 연계한 로깅
  • 메트릭
  • 트레이싱 서비스와 연계한 서비스 추적
  • 모니터링 서비스와 연계한 장애 격리

대표적인 솔루션으로 Spring API Gateway Service나 k8s service와 ingress resources가 있다.

BFF 패턴 (Backend for Frontend)

다양한 종류의 클라이언트를 위해 특화된 처리를 위한 패턴으로 프런트엔드의 유형에 따라 각각 진입점을 두는 패턴.

처리를 수행하는 BFF를 두고 이후에 통합적인 API 게이트웨이를 둠으로써 공통적인 인증/인가, 로깅 등을 처리하는 방식으로 구성

외부 구성 저장소 패턴

Config원칙 : 애플리케이션이 배포되는 환경(스테이징, 프로덕션, 개발, 테스트)이 매번 달라지기 때문에 코드에서 사용하는 환경 설정 정보는 코드와 완전히 분리되어 관리해야 한다는 원칙.

한마디로 클라우드에서 운영되는 애플리케이션은 특정한 배포 환경에 종속된 정보를 코드에 두면 안된다는 원칙

인증/인가 패턴

  • 중앙 집중식 세션 관리 : 서비스들은 언제든 스케일아웃할 수 있으므로 각자의 서비스에 저장하는 것이 아니라 공유저장소에 세션을 저장
  • 클라이언트 토큰 : 세션은 중앙 서버에 저장되고 토큰은 사용자의 브라우저에 저장되어 사용자의 신원 정보를 포함하고 있기 때문에 서버에서 인가 처리를 할 수 있다.
  • API 게이트웨이를 사용한 클라이언트 토큰 : Auth service로 별도로 인증/인가를 처리하는 전담 서비스를 두어 인증/인가의 처리를 위임할 수 있다. Auth serivce를 이용하면 각 리소스 서비스가 자체적으로 인증/인가를 처리하지 않고 자신의 역할에 집중할 수 있도록 해준다.

서킷 브레이커 패턴

하나의 서비스에 장애가 발생했을때 다른 서비스가 영향을 받을 수 있는데 이때 장애가 발생한 서비스를 격리하여 유연하게 처리할 수 있도록 해주는 패턴

서비스A가 서비스B를 호출하여 서비스를 할때 서비스B에서 장애가 발생하면 동기 요청 특성상 A까지도 장애가 발생한 것 처럼 느끼게 되는데 연속 실패 횟수가 임계값을 초과하면 fallback 메서드를 통해 대체응답을 보내는 방법

모니터링과 추적

서비스 모니터링(Hystrix Dashboard), 분산 트레이싱 서비스(Zipkin) 등 다양한 솔루션을 활용하여 모니터링과 장애, 지연구간을 추적하여 개선할 수 있도록 해준다.

로그 집계 패턴

로그는 시작과 끝이 고정된 것이 아니라 서비스가 실행되는 동안 계속 흐르는 흐름으로 이벤트 스트림으로 처리해야 한다.

서비스 메시 패턴

문제 해결을 위한 기능(서비스 탐색, 서킷 브레이크, 추적, 로드 밸런싱) 등을 비즈니스 로직과 분리해서 네트워크 인프라 계층에서 수행하게 하는 패턴

사이드카 패턴 : 모는 서비스 컨테이너에 추가로 사이드카 컨테이너가 배포되는 패턴으로 각 서비스를 연계할 때 한 서비스가 다른 서비스를 직접 호출하지 않고 사이드카인 프록시를 통해 연계해서 개발자가 별도의 작업 없이 관리 및 운영에 대한 서비스 등을 적용


2.3 애플리케이션 패턴

UI 컴포지드 패턴 ( 마이크로 프런트엔드 패턴 )

프론트엔드도 기능별로 분리하고 프레임 형태의 창을 통해 여러 프레임을 조합하여 동작하게 한다. 이 부모 서비스는 틀만 가지고 있고 실제 각 기능 표현은 마이크로 프론트엔드 조각이 구현하게 하고 이 각각의 프론트엔드들은 여러개의 백엔드 마이크로 서비스API를 호출한다.

통신 패턴

  • 동기 통신 : 일반적인 REST API같은 방법.

    호출받은 마이크로서비스에 장애가 발생하면 호출한 서비스는 반응이 올 때까지 기다리게 되어 장애가 연쇄적으로 발생할 우려가 있다.

  • 비동기 통신 : 메시지 큐를 통해 event driven 방식으로 구현

    kafka,rabbitMQ, ActiveMQ 등의 메시지 브로커를 이용하거나, AWS의 SQS/SNS, Azure의 EventHub/EventGrid 등의 완전관리형 클라우드 벤더를 이용해 구현할 수 있다.

저장소 분리 패턴

각 서비스는 각자의 비즈니스를 처리하기 위해 데이터를 직접 소유한다는 것. 자신의 데이터를 다른 서비스에 직접 노출하지 않고 API를 통해서만 노출하기 때문에 정보은닉/폴리그랏 저장소를 만족하고 데이터를 토한 변경의 파급효과를 줄일 수 있다.

분산 트랜잭션 처리 패턴

전통적인 방법은 2단계 커밋을 통해 구현할 수 있지만 이 방법은 각 서비스에 잠금이 걸려 발생하는 성능 문제 탓에 효율적인 방법이 아니며 MongoDB같은 NoSQL은 2단계 커밋을 지원하지 않는다.

  • Saga 패턴 : 각 서비스의 로컬 트랜잭션을 순차적으로 처리하는 패턴으로 분산된 여러 서비스를 하나의 트랜잭션으로 묶지 않고 각 로컬 트랜잭션과 보상트랜잭션을 통해 데이터의 정합성을 맞춘다.

    보상 트랜잭션? 어떤 서비스에서 트랜잭션 처리에 실패할 경우 그 서비스의 앞선 다른 서비스에서 처리된 트랜잭션을 되돌리게 하는 트랜잭션

결과적 일관성? 모든 비즈니스 규칙들이 실시간으로 일관성을 맞출 필요가 없다. 예를 들어, 결제 주문이 완료된후 메일을 보내는 시스템인데 주문이 몰리는 블랙프라이데이라고 가정을 해보자. 수만개의 주문이 발생하는데 결제 서비스에서 타사 외부 연동 장애가 발생해 더는 주문을 받을 수 없는 상황이 발생할 수 있는데 비즈니스 관점에서 보면 이를 모두 순차적으로 처리하기보다 주문을 먼저 많이 받아놓는 것이 더 좋을 수도 있다. 그 후 결제 서비스가 복구되었을때 처리하여 결과적으로는 일관성을 만족하게 되는 것이 더 좋을 수 있다.

이 처럼 데이터의 일관성이 실시간으로 맞지 않더라도 어느 일정 시점이 되었을때 일관성을 만족해도 되는 것.

CQRS 패턴

Command Query Responsibility Segregation의 약자로 명령 조회 책임 분리를 의미.

하나의 저장소에 쓰기 모델과 읽기 모델을 분리하는 방식으로 변화시켜 쓰기 서비스와 조회 서비스를 분리하거나, 물리적으로 쓰기 트랜잭션용 저장소와 조회용 저장소를 따로 준비하여 쓰기 시스템의 부하를 줄이고 조회 대기 시간을 줄이는 방법.

이벤트 소싱패턴

저장소에 저장하는 것과 메시지를 보내는 것이 원자성을 지녀야 하는데 이 과정에서 객체의 상태변화를 메시지로 발행하고 객체 상태를 관계형 db에 저장하는 경우 SQL질의어로 변환해서 처리하기가 매우 번거롭고 까다로우며 성능이 빠르지 않은데 이를 해결할 수 있는 패턴

비즈니스로직을 모두 수행후 최종 결과값을 저장하는 방식이 아닌 상태 트랜잭션 자체를 저장하는 전략으로 상태 변경 이벤트를 계산해서 데이터 모델로 변경하지 않고 바로 이벤트 저장소에 그대로 저장하는 방법. 메시지 브로커와 데이터 저장소를 분리하지 않고 하나로 사용해 쓰기 속도가 훨씬 빠르다.

만일 현재 시점의 상태가 필요하다면 상태의 출발점부터 모든 기록된 상태 변경 트랜잭션을 순차적으로 계산하면 되며, 이것이 부담된다면 매일 자정에 상태를 계산한 후 스냅샷으로 저장하여 이 스냅샷 이후의 트랜잭션만 처리하면 된다. 이렇게 되면 특정 시점의 상태를 재현할 수 있기 때문에 로그 데이터를 기록할 필요도 없다.

이렇게 되면 삭제/수정없이 입력/조회만 일어나기 때문에 명령 서비스를 확장해도 동시 업데이트/교착 상태가 발생하지 않는다.



3. 애플리케이션 아키텍처

3.1 레이어드 아키텍처

  • 프레젠테이션 : 화면 표현 및 전환 처리
  • 비즈니스 로직 : 비즈니스 개념 및 규칙, 흐름제어
  • 데이터 액세스 : 데이터 처리

규칙

  1. 상위 계층이 하위 계층을 호출하는 단방향성 유지
  2. 상위 계층은 하위의 여러 계층을 모두 알 필요없이 바로 밑 계층만 활용
  3. 상위 계층이 하위 계층에 영향을 받지 않게 구성
  4. 하위 계층은 상위 계층을 알지 못하게 구성
  5. 계층간의 호출은 인터페이스를 통해 호출하도록 구성

구현 방식

규칙 5에 의해 각 계층의 인터페이스를 통해 호출하도록 구성했다고 해보자. 데이터 액세스 계층의 인터페이스를 이용하여 비즈니스 로직을 처리한다고 할때 DIP는 만족한 것 처럼 보인다. 하지만 OCP에 문제가 발생한다. 각 계층이 자기 자신의 인터페이스를 정의하고 소유하고 있는 구조로 하위 계층의 유형이 추가되어 확장되어 인터페이스가 바뀌게 되면 이를 사용하는 닫혀있어야 할 상위 계층에도 영향이 가기 때문이다.

저수준이 가지고 있는 인터페이스를 고수준 계층으로 옮김으로써 DIP와 OCP를 해결할 수 있다.


3.2 헥사고날 아키텍처

현대 애플리케이션은 위처럼 단순하게 3가지의 계층만 존재하는 것이 아니라 더 다양한 인터페이스,시스템 유형, 저장소 들이 존재하기 때문에 한계가 존재한다.

앨리스테어 콕번이 제시한 아키텍처로 포트 & 어댑터 아키텍처라고도 한다.

고수준의 비즈니스 로직을 표현하는 내부 영역과 인터페이스 처리를 담당하는 저수준의 외부영역으로 분리하는 아키텍처. 그리고 외부 영역과 연계되는 포트를 가지고 있다.

포트

헥사고날의 기본 어댑터로 인터페이스 라고도 불린다.

  • 인바운드 포트 : 내부 영역의 사용을 위해 호출된 api로 외부영역의 인바운드 어댑터가 호출한다.
  • 아웃바운드 포트 : 내부 영역이 외부를 호출하는 방법을 정의

어댑터

헥사고날의 보조 어댑터로 인프라 라고도 불린다.

  • 인바운드 어댑터 : 외부에서 들어오는 요청을 처리하는 어댑터로 외부영역이 소유.
    컨트롤러, 커맨드 핸들러, 이벤트 메시지 구독 핸들러 등이 될 수 있다.
  • 아웃바운드 어댑터 : 비즈니스 로직에 의해 호출되어 외부와 연계.
    DAO, 이벤트 발행 클래스, 외부 서비스 호출하는 프록시 등이 될 수 있다.


3.3 클린 아키텍처

로버트 C.마틴은 소프트웨어는 행위 가치보다 구조 가치가 더 중요하다라고 말했는데 구조가치가 소프트웨어를 더 부드럽게 만들기 때문이라고 한다.

  • 엔티티 : 가장 중앙에 위치한 부분으로 비즈니스 업무 규칙이 정의 되어 있는 부분
  • 유스케이스 : 어플리케이션 업무 규칙으로 엔티티 내부의 핵심 업무 규칙을 호출하며 시스템을 사용하는 흐름을 담는다.
  • 세부사항 : 위 두개을 제외한 모든 영역으로 입출력 장치, 저장소, 웹 시스템, 서버, 프레임워크, 통신 프로토콜들이 될 수 있다. 이때 세부사항과 유스케이스의 관계를 의존 관계 역전의 원칙을 이용해 플러그인처럼 유연하게 처리해야 한다.

로버트 C.마틴은 소프트웨어는 부드러워야 한다고 했다. 클라우드 플랫폼이나 k8s같은 외부 아키텍처를 적용하는 것만으로도 처음에 는 꽤나 유연해 질 수 있는데. 시스템의 핵심은 결국 소프트웨어이고 실제로 비즈니스를 제공하는 것은 마이크로 서비스이다. 영리하고 빠른 비즈니스를 제공하기 위해서는 핵심(내부)가 유연해야 한다. 내부가 유연해야 마이크로서비스 간의 관계도 느슨하게 구현할 수 있고 이러한 구조여야 비로소 서비스를 독립적으로 확장, 변경, 배포 할 수 있다.

3.4 추천하는 추가 도서

  1. 마이크로서비스 패턴(길벗)
  2. 클린 아키텍처(인사이트)
  3. 엔터프라이즈 애플리케이션 아키텍쳐 패턴(위키북스)



4. 개발 프로세스

4.1 스크럼

기본 생명주기는 스크럼의 스프린트 활용. 점진/반복적인 생명주기로 1~4주 동안 실행되며 백로그라는 일감 목록을 기반으로 각 스프린트에 배분되어 매 스프린트마다 실제 동작하는 소프트웨어를 시연하고 피드백을 얻는다.

  • 스크럼 팀 : 스프린트가 진행되는 팀으로 다기능 팀으로 구성.
  • 스크럼 미팅 : 매일 아침 각자의 일을 투명하게 공유
  • 스프린트 계획 수립 : 모든 요구사항은 백로그에 담기고 일정에 맞게 스프린트를 몇번 수행할 것인지 결정. 스프린트 횟수가 결정되면 제품 백로그에 담긴 백로그를 각 스프린트에 적절히 배분한다.
  • 시연 : 스프린트 마지막 활동중 하나로 초기에 정의한 백로그가 모두 구현되고 그 요건을 만족하는지 확인하고 피드백시간
  • 회고 : 팀원들이 자기 스스로를 돌아보는 과정으로 설계 및 개발 과정에서 좋았던 점/안 좋았던점등을 공유하고 개선시키는 활동


4.2 아키텍처 정의와 마이크로서비스 도출

  • 아키텍처 정의 : 내부 영역과 외부 영역으로 구분해서 개발할때 외부 영역은 언제든지 교체될 수 있으므로 핵심인 내부영역에 집중하고 외부영역은 천천히 결정해도 된다. 변화에 염두에 둔 유연한 구조를 초기에 정의해야 한다.
  • 마이크로서비스 도출 : 개발에 들어가기 위해 개발할 전체 마이크로서비스들을 파악하는 작업으로 마이크로서비스들을 도출하고 그것들 간의 대략적인 매핑관계를 정의후에 개발 우선순위에 근거해 스프린트를 진행해야 한다.


4.3 개발 공정

백엔드

API 설계를 가장 우선적으로 해 프론트엔드 영역과 협의 및 조정을 해야 프론트엔드도 이를 기반으로 설계를 진행할 수 있다.

도메인 모델데이터 모델을 설계.

이때 UML등을 활용해 설계 모델을 작성하고 이를 코드로 변환하는 OOAD방식과는 달리 간략한 도메인 모델 등을 화이트보드나 포스트잇 등의 단순한 도구로 작성해서 공유한 후 개발을 진행

프론트엔드

UI 레이아웃을 정의하고 API를 통해 받은 데이터를 어떻게 표현할 것인지 정의.

빌드 및 배포

지속적으로 빌드되고 자동으로 배포되도록 빌드 및 배포환경을 자동화해 언제라도 현재 진행된 만큼의 실제로 돌아가는 소프트웨어를 확인할 수 있어야 한다.


4.4 추천하는 추가 도서

  1. 소프트웨어 장인(길벗)



5. 마이크로서비스 설계

5.1 마이크로 서비스 도출 방법

비즈니스 능력에 근거해 도출

비즈니스의 흐름에 따라 비즈니스를 최상위에서 하위까지 대/중/소의 크기로 분리하고 수행하는 일들을 정렬하는 방식으로 도출하면 직관적으로 서비스를 식별할 수 있다. 이러한 방식은 전체적인 대략의 비즈니스를 이해할 때는 유용하지만 서비스 간의 관계를 파악하거나 서비스의 구체 기능과 연관된 서비스가 관리할 독립적인 데이터를 식별하기에는 미흡하다.

DDD의 바운디드 컨텍스트 기반 도출

마이크로서비스를 도출할 때 서비스가 소유권을 가진 데이터를 독립젹으로 식별하는 것이 중요한데 비즈니스 능력에 근거한 도출 방식은 기능과 데이터가 분리되고 하나의 통합 데이터가 여러기능에서 사용되도록 모델링 되는 방식으로 비즈니스를 처리하는 기능과 기능에 영향을 받는 데이터가 분리되는 경향이 있다.

DDD는 문제 영역인 하위 도메인 마다 별도의 도메인 모델(바운디드 컨텍스트)로 정의하는 방법으로 도메인 모델은 각 업무에 특화된 유비쿼터스 언어로 정의되고, 그 업무에 특화된 개념으로 구성된다.


5.2 설계

도메인과 서브도메인

하나의 큰 도메인을 전략적으로 중요한 것들을 찾아 중요도에 따라 도메인을 나누고, 각 도메인을 각각 하나씩 해결하는 방법을 기본으로 삼는데 이때 많은 개념들이 하나로 엮인 복잡한 비즈니스 도메인을 논리적으로 구분되는 여러 개의 하위 영역으로 분리해야 한다. 이렇게 분리된 하위 도메인을 서브도메인이라고 한다.

서브 도메인은 중요도에 따라 핵심 서브도메인, 지원 서브도메인, 일반 서브도메인으로 나누니다.

  • 핵심 서브도메인 : 다른 경쟁자와 차별화를 만들 비즈니스 영역
  • 지원 서브도메인 : 비즈니스에 필수적이지만 핵심은 아닌 부분
  • 일반 서브도메인 : 비즈니스적으로 특화된 부분은 아니지만 전체 비즈니스 솔루션에는 필요한 부분

유비쿼터스 언어와 도메인 모델, 바운디드 컨텍스트

특정 도메인에서 해당 도메인에서의 의도를 명확히 반영하고 도메인의 핵심 개념을 잘 전달할 수 있는 언어를 유비쿼터스 언어라고 한다.

특정 도메인에 특화된 개념이 유비쿼터스 언어로 정의되고, 이 개념들은 서로 관계를 맺는데 이와 같은 관계를 표현한 모델을 도메인 모델이라고 한다.

각 도메인 모델과 다른 도메인 모델 간의 경계에서 사용하는 언어와 개념이 상이한 경계가 존재하는데 이런 도메인의 경계를 바운디드 컨텍스트라고 한다.

컨텍스트 매핑

바운디드 컨텍스트를 식별할 때 각 컨텍스트는 내부적으로는 응집성이 높고, 다른 컨텍스트와는 의존관계가 낮도록 설계를 한다. 이때 하나의 큰 도메인을 여러 개의 바운디드 컨텍스트로 식별하면 비즈니스 수행을 위해 여러 개의 컨텍스트가 연계해야 하는 경우가 발생한다. 이러한 컨텍스트 간의 의존관계를 컨텍스트 매핑이라고 한다.

컨텍스트 매핑 패턴

  • 공유 커널
    • 바운디드 컨텍스트 사이에 공통적인 모델을 공유하는 관계로 두 개 이상의 팀에서 작지만 공통의 모델을 공유하는 관계.
    • 보통 공통 라이브러리 등이 여기에 해당 되고 이 부분이 변경되면 여러 컨텍스트에 영향을 미치므로 공유하는 모델의 코드 빌드를 관리하고 테스트하는 것은 한 팀이 맡아 수행해야 한다.
  • 소비자와 공급자
    • 공급하는 컨텍스트를 UPSTREAM, 소비하는 컨텍스트를 DOWNSTREAM으로 표시하고 데이터의 흐름은 반드시 UPSTREAM에서 DOWNSTREAM으로만 흐른다.
    • UPSTREAM이 변화가 생기면 DOWNSTREAM은 변화를 따라야한다.
  • 준수자 (Confirmist)
    • 소비자와 공급자 형식과 유사하지만 upstream이 downstream의 요구를 지원하지 않거나 못할때 상류팀에서 제공하는 모델을 그대로 사용하는 방법
  • 충돌 방지 계층 (Anti-Corruption Layer)
    • downstream이 upstrea의 모델에 영향을 받을 때 downstream의 고유 모델을 지키기 위해 번역 계층을 만들어 두는 것으로 downstream의 독립성을 유지 할 수 있다.
    • MSA를 적용하는 시스템을 레거시 시스템과 통합하기 위해 주로 사용
  • 공개 호스트 서비스(Open Host Service)
    • downstream의 컨텍스트가 upstream 컨텍스트에서 제공하는 기능을 용이하게 사용할 수 있도록 바운디드 컨텍스트에 대한 접근을 제공하는 프로토콜이나 인터페이스를 정의하는 방법
    • 공유된 API가 예이다.
  • 발행된 언어 (Published Language)
    • downstream의 컨텍스트가 upstream 컨텍스트가 제공하는 기능을 사용하게 하기 위한 간단한 사용과 번역을 가능케 하는 문서화된 정보 교환 언어
    • XML이나 JSON 스키마로 표현될 수 있으며, 주로 공개 호스트 서비스와 짝을 이뤄 사용된다.

컨텍스트 맵

  • 개념적인 컨텍스트 맵
    • 연관관계에 있는 두 컨텍스트 사이에 선을 그려 매핑관계를 표시한 다이어그램으로 매핑을 위한 구체적인 기술 등이 정의되지 않은 상태
  • 구체적인 컨텍스트 맵
    • 매핑을 구현할 방안이 구체화 되어 매피 유형들을 구체적으료 표현한 매핑 다이어그램

이벤트 스토밍을 통한 마이크로서비스 도출

이벤트 중심으로 이해관계자들이 모여 브레인 스토밍하는 워크숍을 이벤트 스토밍이라고 한다. 각 관점을 논의하며, 그 차이점을 이해하고 공유할 수 있다는 점에서 기존 방법론에서 장기간 단절하며 수행했던 요구사항, 프로세스 모델링, 설계를 진행하는 과정을 뛰어넘는 민첩성과 효율성을 보여준다.

다양한 스티커, 마커 펜, 라인 테이프 등을 이용해 깨끗한 벽이 있는 넓은 공간에서 서로 토론을 통해 의견을 맞춰나가는 것이 중요하다.

스티커 유형별 의미

유형 크기/색깔 설명
도메인 이벤트 오렌지색 발생한 사건. 과거시제동사로 표현
커맨드 파란색 도메인 이벤트를 트리거하는 명령
외부 시스템 핑크색 도메인 이벤트가 호출하거나 관계가 있는 레거시 또는 외부 시스템
액터 작은 노란색 개인 또는 조직의 역할
애그리거트 노란색 도메인 이벤트와 커맨드가 처리하는 데이터 / 상태가 변경되는 데이터
정책 라일락 색 이벤트 조건에 따라 진행되는 결정
읽기모델 초록색 도메인 이벤트 액터에게 제공되는 데이터
사용자 인터페이스 흰색 스케치 형태의 화면 레이아웃
핫스폿 자주색 의문, 질문, 미결정 사항

이벤트 스토밍 순서

  1. 도메인 이벤트 찾기
    • 데이터나 데이터의 구조가 아닌 비즈니스 흐름에서 발생한 이벤트에 초점을 두는 것이 중요하다.
    • 이벤트명은 과거형 동사로 작성
    • 비즈니스의 어떤 상태를 생성, 변경, 삭제하는 요소
    • 예) 회원가입됨 / 회원정보 수정됨 / 회원정보 삭제됨
  2. 외부 시스템/외부 프로세스 찾기
    • 시스템의 기능 구현을 위해 연계가 필요한 시스템을 모두 도출
    • 시스템 이름을 명사형태로 작성
    • 예) 결제 시스템 / 이메일 시스템
  3. 커맨드 찾기
    • 이벤트를 동작하게 하는 커맨드를 도출
    • 도메인 이벤트를 동작하게 하는 것으로 명령형(동사)로 작성
    • 하나의 커맨드에 의해 동시 또는 연속해서 발생하거나 조건에 따라 여러개의 다른 이벤트가 발생할 수 있다.
    • 예) 회원가입됨 -> 회원가입 / 회원정보 수정됨 -> 회원정보 수정
  4. 핫스팟 찾기
    • 진행과정 중에 의문사항이나 결정하기 힘든 사항, 다른 부서에 문의할 필요가 있는 사항등을 정의
    • 예) 상품 주문 취소에서 취소 가능 시점과 같은 경우
  5. 액터 찾기
    • 추상적으로 식별하지 않고 비즈니스를 수행하는 구체적인 역할을 고려해서 도출
    • 회원/관리자가 아닌 판매자, 구매자, 상품 관리자, 배송 관리자, 시스템 관리자와 같이 명확하게 도출
    • 커맨드의 왼쪽 아래에 붙여 커맨드를 조작한다는 것을 명시적으로 표현
  6. 애그리거트 정의하기
    • 커맨드와 도메인 이벤트가 영향을 주는 데이터 요소를 애그리거트라고 한다.
    • 도메인의 실체 개념을 표현하는 객체인 엔티티
    • 액터와 마찬가지로 구체적으로 도출
    • 커맨드와 이벤트 사이의 상단에 겹쳐서 붙인다.
  7. 바운디드 컨텍스트 정의하기
    • 같거나 유사한 애그리거트를 완전히 다른 애그리거트와 구분해서 경계를 식별
    • 컨텍스트내에 여러개의 애그리거트 이름이 있는 경우 전체를 아우를 수 있는 대표 이름을 정한다.
    • 예 ) 회원 컨텍스트(회원 애그리거트) / 구매 컨텍스트(주문아이템, 주문, 결제) / 상품 컨텍스트(상품, 재고, 카테고리), 배송 컨텍스트(배송, 발송, 수령)
  8. 컨텍스트 매핑하기
    • 식별한 바운디드 컨텍스트간의 관계를 동기/비동기 호출로 나누어 표현

이렇게 도출된 바운디드 컨텍스트가 마이크로 서비스의 후보가 되고 아래와 같은 질문을 통해 최종 확정할 수 있다.

  • (비즈니스 측면) 비즈니스 프로세스를 수행하기 위한 하나의 맥락의 단위로 구분될 수 있는가?
  • (데이터 관점) 마이크로서비스별로 분리된 데이터를 정의할 수 있는가?
  • (운영 조직 측면) 하나의 팀이 독립적으로 운영 가능한 단위인가?
  • (배포 측면) 독립적으로 배포 가능한 단위인가?
  • (변경 영향도) 변경시 영향을 받는 마이크로서비스가 존재하는가?
  • (클라우드/MSA 도입 목적 측면) 도입을 통한 기대효과를 충분히 활용할 수 있는가?


5.3 상세설계

프런트엔드 모델링

  • 프런트 아키텍처 정의
    • 사용자 요건에 적절한 아키텍처 정의
    • 모바일, 앱, 웹 등을 고려하고 UX 고려
  • 표준 레이아웃 정의
    • 목록, 조회, 수정, 삭제 등의 대표적인 업무 화면 유형을 정의하고 표, 그리드, 입출력 폼, 표준 버튼 등 정의
    • 모바일, 웹, 앱 등 채널에 맞게 별도로 정의할 수도 있다.
  • UI레이아웃 설계
    • 각 기능을 만족할 UI를 정의하고 화면에 입출력될 속성 정보를 식별하고 수행할 버튼등을 정의
  • UI 디자인 및 UI레이아웃 반영
  • 이벤트 설계
    • 화면의 이벤트 변화에 따라 백엔드 API를 호출하는 방식을 정의

백엔드 모델링

헥사고날 아키텍처를 적용해 외부 영역과 내부 영역으로 구분되어 진행된다.

이벤트 스토밍의 커맨드는 헥사고날의 인바운드 어댑터의 하나인 REST API가 되고, 애그리거트는 헥사고날의 내부 영역인 도메인 모델이 되며, 도메인 이벤트는 헥사고날 외부 영역의 아웃바운드 메시지 처리 어댑터의 처리 대상이 되고, 외부 시스템은 마찬가지로 아웃바운드 어댑터가 호출해야 할 외부 연계 시스템으로 매핑된다.

외부 영역 설계는 프런트엔드와 연계되는 API 설계로 내부 영역은 비즈니스 로직을 구현하는 도메인 모델링, 데이터 모델링으로 구체화 되어 진행된다.

  • API 설계
    • 프런트엔드의 요구사항을 충족하도록 정의
    • 인바운드 어댑터로써 어떠한 호출 방식도 허용되는 유연한 공간
  • 도메인 모델링
    • 엔티티

      • 다른 엔티티와 구별할 수 있는 식별자를 가진 도메인의 실체 개념을 표현하는 객체
      • 식별자는 고유하되 엔티티의 속성 및 상태는 계속 변할 수 있다.
      • 도메인에서 개별성이 있는 개념을 엔티티로 시별하며, 고유 식별자와 변화 가능성이 엔티티와 값 객체를 구분하는 차이점
      • 예) 구매라는 애그리거트에서 구매번호라는 식별자로 구분이 가능하고, 구매품/수취자 등이 개별적으로 변경될 수 있기 때문에 엔티티로 모델링
    • 값 객체(VO)

      • 각 속성이 개별적으로 변화하지 않는 개념적 완전성을 모델링

        개념적 완전성?
        값 객체를 구성하는 하나 이상의 특성들이 서로 연관되어 전체 의미를 이루는 것. 예를 들어 한국 돈 1000원은 1000이라는 숫자와 한국의 통화기준인 원이라는 특성이 결합되어 의미를 전달

    • 개별 속성이 별개로 수정되지 않고 전체 객체가 한 번에 생성되거나 삭제되는 객체

    • 특성

      • 반 버논은 아래와 같이 값 객체의 특성을 정의

      • 도메인 내의 어떤 대상을 측정하고, 수량화하고 설명
      • 관련 특징을 모은 필수 단위로 개념적 전체를 모델링
      • 측정이나 설명이 변경될 땐 완벽히 대체 가능하다.
      • 다른 값과 등가성을 사용해 비교할 수 있다.
      • 값 객체는 일단 생성되면 변경 할 수 없다.
  • 표준 타입
    • 대상의 타입을 나타내는 서술적 객체
    • 엔티티나 값 객체의 속성을 구분하는 용도로 사용
    • 예) 전화번호가 집 전화인지 핸드폰, 회사 전화번호인지 구분하기위한 객체로 JAVA에서는 열거형으로 정의
  • 애그리거트
    • 엔티티와 값 객체들의 묶음이 애그리거트

    • 애그리거트 내의 엔티티, 값 객체, 애그리거트는 비즈니스 의존관계를 맺고 있어 비즈니스 정합성을 맞출필요가 있기에 트랜잭션의 기본 단위가 된다.

    • 애그리거트 내 가장 상위 엔티티를 애그리거트 루트로 정하고 이를 통해서만 애그리거트 내의 엔티티나 값 객체를 변경

    • 다른 애그리거트를 참조해야 할 필요가 있다면 애그리거트 루트의 식별자를 통해 참조

      다른 애그리거트의 엔티티나 값 객체를 직접참조하게 되면 나중에 별도의 마이크로서비스로 분리하려고 할때 분리가 힘들다.

  • 도메인 서비스
    • 도메인의 비즈니스 로직 처리가 특정 엔티티나 값 객체에 속하지 않을 때 단독 객체를 만들어 처리하게 하는데, 이를 도메인 서비스라고 한다.
    • 상태를 관리하지 않고 행위만 존재
    • 도메인 로직을 처리할 때 엔티티나 값 객체와 함께 특정 작업을 처리하고 상태를 본인이 가지고 있지 않고 엔티티나 값 객체에 전달
  • 도메인 이벤트
    • DDD및 이벤트 스토밍에서 맗나는 도메인 이벤트의 구현 객체
    • 서비스 간 정합성을 일치시키기 위해 단위 애그리거트의 주요 상태 값을 담아 전달되도록 모델링
    • 예) 주문 서비스에서 주문 트랜잭션 처리를 통해 주문/주문아이템 엔티티가 생성되어 저장됨과 동시에 구매완료됨이벤트를 발행 -> 구매완료됨이벤트에는 주문됨 상태를 나타내는 주요 주문 정보를 포함


5.4 추천하는 추가 도서

  1. UML과 패턴의 적용 (홍릉과학출판사)
  2. 도메인 주도 설계 (위키북스)
  3. 도메인 주도 설계 핵심 (에이콘)
  4. 도메인 주도 설계 철저 입문 (위키북스)
  5. Introducing EventStorming (Leanpub)



6. 요약

전체적으로 DDD에대해 얇고 넓게 설명하는 책인 것 같고 내용만 보면 구글링으로 충분히 습득할만한 지식들이다. 하지만, 목차별로 그림들을 삽입하여 이해하기 쉽게 설명해주고 있고 7장부터는 도서 대출 서비스를 예시로 DDD방식의 개발 프로세스를 설명해주고 있어 처음 접하는 개발자라면 읽어볼만한 책인것 같다.