ㄷㄷㄷ: Domain Driven Design과 적용 사례공유 / if(kakao)2022 (youtube.com)

이 글에서는 위 동영상의 내용을 보고 공부한 내용을 기록합니다.

Domain Driven Design

도메인 드리븐 디자인(Domain Driven Desion)이란 소프트웨어 개발 방법론 중 하나로, 단순히 데이터나 기술적인 요소에 집중하는 것이 아닌 도메인 모델에 초점을 맞추어 설계를 진행하는 방식이다. 도메인이란 비즈니스의 핵심 개념과 규칙을 담고 있는 영역으로, 소프트웨어가 다루는 비즈니스의 실제 요구사항을 의미한다. DDD는 이러한 도메인을 정확하게 반영하는 소프트웨어를 설계하고 개발하는 데 중점을 둔다.

Domain Driven Design의 특징

 

  • 도메인의 모델과 로직에 집중: DDD는 도메인의 본질적인 부분을 파악하고 그에 따라 소프트웨어를 설계한다. 이는 단순한 CRUD(Create, Read, Update, Delete) 작업이나 데이터 관리에 머물지 않고, 실제 비즈니스 로직을 모델링하여 그 흐름을 반영한 코드를 작성하는 것을 의미다. 도메인의 복잡성을 정확하게 반영하는 것이 목표이다.
  • 유비쿼터스 언어(Ubiquitous Language): 유비쿼터스 언어는 DDD의 중요한 요소로, 소프트웨어 개발 팀과 도메인 전문가(비즈니스 전문가)가 사용하는 공통의 언어이다. 이 언어는 개발자, 기획자, 도메인 전문가가 모두 이해할 수 있는 용어로 구성되며, 소프트웨어 설계와 구현 전반에 걸쳐 사용된다. 이를 통해 커뮤니케이션 오해를 줄이고, 모든 팀원이 도메인에 대한 일관된 이해를 가질 수 있다.
  • Entity와 Domain 간 개념의 일치: 소프트웨어의 엔티티(Entity)와 도메인 모델 간의 개념 일치가 중요하다. DDD에서는 도메인 모델을 소프트웨어 엔티티에 정확히 반영하여, 비즈니스의 주요 개념이 코드에 그대로 표현되도록 설계한다. 예를 들어, 회사에서 '주문'이라는 개념이 있다면, 소프트웨어에서도 'Order'라는 엔티티를 정의하여 도메인에서의 주문 흐름을 구현하는 방식이다.

 

카카오 파트너사이트와 점진적 향상 방식의 채택

카카오 파트너사이트와 같은 레거시 시스템은 이미 검증된 비즈니스 로직과 구조를 가지고 있어, 빅뱅 방식(Big Bang Approach)을 통한 대대적인 재설계보다는 점진적 향상 방식을 채택하는 것이 적합했다. 빅뱅 방식은 모든 시스템을 한 번에 교체하거나 개선하려는 접근 방식으로, 리스크가 크고 실패 가능성이 높다. 반면, 점진적 향상 방식은 기존의 로직을 유지하면서 조금씩 새로운 기능을 추가하거나 성능을 개선하는 방식이다. 이는 기존 시스템의 안정성을 유지하면서 점진적으로 도메인 모델을 개선하는데, DDD는 이러한 점진적 향상에 적합한 방법론으로 평가된다.

DDD 적용에 필요한 것들

Bounded Context

Bounded Context (martinfowler.com)

Bounded Context는 도메인의 특정 영역을 독립적으로 분리하여 관리하는 단위이다. 도메인의 범위와 책임을 명확히 정의하고, 해당 영역에서 사용하는 모델과 개념이 다른 영역과 충돌하지 않도록 경계를 설정하는 역할을 한다. 이를 통해 도메인 모델의 복잡성을 낮추고, 여러 팀이 서로 독립적으로 작업할 수 있는 환경을 조성한다.

 

Context Map

DDD Context Mapping by example: Policy Management – Alok Mishra (alok-mishra.com)

Context Map은 여러 Bounded Context 간의 관계를 시각적으로 표현한 지도이다. 각 컨텍스트가 어떻게 상호작용하는지, 어느 영역에서 어떤 책임을 지고 있는지를 명확히 정의한다. 특히, UpstreamDownstream 관계를 설정하여 컨텍스트 간의 의존성과 데이터 흐름을 파악할 수 있다.

  • Upstream: 데이터를 제공하는 쪽이다. 즉, 다른 컨텍스트가 데이터를 의존하거나 참조하는 컨텍스트를 의미한다.
  • Downstream: 데이터를 소비하는 쪽이다. 다른 컨텍스트로부터 데이터를 받아와 사용하는 컨텍스트를 뜻한다.

Aggregate

Designing DDD aggregates. The aim of software engineering is to… ❘ by Albert Llousas ❘ Medium

Aggregate(어그리게이트)는 라이프사이클이 비슷한 도메인 객체들을 논리적으로 묶은 집합을 의미한다. 어그리게이트는 여러 도메인 객체가 모여 하나의 큰 도메인 개념을 형성하며, 이들이 함께 동작하고 일관성을 유지하도록 설계된다.

어그리게이트의 주요 특징은 다음과 같다:

  • 하나의 루트 엔티티(Aggregate Root)를 가지고 있으며, 해당 루트 엔티티가 어그리게이트 전체를 대표한다. 예를 들어, '주문' 도메인에서 'Order' 엔티티가 루트 엔티티가 될 수 있다.
  • 루트 엔티티를 통해서만 어그리게이트 내부의 다른 객체에 접근할 수 있다. 이를 통해 외부에서 어그리게이트 내부의 세부 사항을 제어하지 못하도록 보호한다.
  • 어그리게이트는 일관성을 유지해야 하는 객체들의 묶음으로, 트랜잭션 경계로 작동할 수 있다. 즉, 어그리게이트 안의 객체들이 함께 변경되어야 할 때 하나의 트랜잭션으로 처리된다.

DDD에 대표적인 아키텍처

Layered Architecture (계층형 아키텍처)

https://www.hibit.dev/images/posts/2021/previews/ddd_layers.png

Layered Architecture는 여러 개의 계층(layer)으로 소프트웨어를 분리하여 구성하는 아키텍처 패턴이다. 각 계층은 명확한 역할과 책임을 가지며, 다른 계층에 대한 의존성을 줄이고 독립성을 보장함으로써 유지보수성과 테스트 용이성을 향상시킵니다. DDD에서 자주 사용되는 전통적인 계층형 아키텍처는 주로 다음과 같은 네 가지 주요 계층으로 나뉜다.

User Interface (UI, 사용자 인터페이스) 계층

  • 역할: 사용자가 시스템과 상호작용하는 계층으로, 화면을 통해 사용자에게 정보를 제공하고, 사용자의 입력을 받아들이는 역할을 한다.
  • 책임: 사용자 인터페이스 계층에서는 사용자의 요청을 받아 애플리케이션 계층에 전달하고, 그 결과를 다시 사용자에게 표시한다.
  • 특징: UI 계층은 시스템의 비즈니스 로직을 직접 처리하지 않고, 단순히 사용자와 상호작용하는 부분에만 집중한다. 웹 애플리케이션에서는 웹 페이지, 모바일 앱에서는 화면, API의 경우에는 요청과 응답을 처리하는 부분이 이 계층에 속한다.

Infrastructure 계층

  • 역할: 도메인 계층과 애플리케이션 계층이 실제 동작할 수 있도록 필요한 기술적 세부 사항을 처리하는 계층이다. 예를 들어, 데이터베이스 접근, 파일 시스템 처리, 외부 API 통신 등의 기능이 이 계층에 포함된다.
  • 책임: Infrastructure 계층은 기술적인 의존성을 관리하고, 도메인 계층이 외부 시스템에 독립적으로 동작할 수 있도록 돕는다.
  • 특징: 도메인 로직은 Infrastructure에 의존하지 않기 때문에, 도메인 계층의 순수성을 유지하면서도 외부 기술적 요구 사항을 충족시킬 수 있다.

Application 계층

  • 역할: 애플리케이션의 구체적인 비즈니스 로직을 처리하는 계층이다. 사용자나 외부 시스템으로부터 요청을 받아 도메인 계층과 상호작용하며, 그 결과를 UI 계층에 전달하는 역할을 한다.
  • 책임: 애플리케이션 계층은 애플리케이션의 흐름을 관리하지만, 도메인 로직의 구체적인 세부 사항은 다루지 않는다. 대신, 도메인 계층에 의존하여 실제 비즈니스 규칙을 처리하고 그 결과를 전달한다.
  • 특징: 트랜잭션을 관리하거나, 서비스의 비즈니스 흐름을 조정하는 역할을 하며, 도메인 모델의 메서드를 호출하여 결과를 조합하고 전달하는 식으로 동작한다.

Domain 계층

  • 역할: 시스템의 핵심 비즈니스 로직과 규칙을 구현하는 계층이다. 이 계층에서 도메인 모델을 정의하고, 시스템의 도메인 로직을 처리한다.
  • 책임: 도메인 계층은 애플리케이션 계층과 달리, 순수한 비즈니스 규칙을 포함하고 있으며, 시스템의 핵심 로직을 관리한다. 도메인 엔티티(Entity), 값 객체(Value Object), 서비스(Service)와 같은 개념이 포함된다.
  • 특징: 도메인 계층은 애플리케이션 전반에서 재사용되며, 외부의 의존성에 영향을 받지 않도록 설계된다. 즉, Infrastructure나 UI와의 직접적인 연관성을 두지 않습니다.

Clean Architecture (클린 아키텍처)

Clean Architecture는 소프트웨어의 각 계층이 비즈니스 규칙을 중심으로 동작할 수 있도록 설계하는 구조이다. 계층 간의 의존성 역전 원칙(DIP, Dependency Inversion Principle)을 준수하며, 내부 계층이 외부 계층에 의존하지 않고 독립적으로 동작할 수 있도록 구성된다. 클린 아키텍처에서는 외부의 변경(예: UI 변경, 데이터베이스 변경)에 쉽게 적응할 수 있도록 설계하며, 주요 계층은 다음과 같이 구성된다.

External Interface (외부 인터페이스) 계층

  • 역할: 외부와의 상호작용을 처리하는 계층이다. UI, 데이터베이스, 네트워크, 파일 시스템과 같은 외부 시스템에 대한 인터페이스를 담당한다.
  • 책임: 클라이언트의 요청을 받아들이고, 시스템의 내부 계층과 상호작용하여 데이터를 처리합니다. 이 계층은 애플리케이션의 흐름이나 비즈니스 로직에는 관여하지 않으며, 단순히 데이터를 받아서 인터페이스 어댑터 계층으로 전달한다.
  • 특징: 시스템의 최외곽 계층으로, 사용자와 시스템 간의 인터페이스를 정의한다.

Interface Adapter (인터페이스 어댑터) 계층

  • 역할: 외부 시스템과 내부 비즈니스 로직 간의 데이터를 변환하고 연결하는 계층이다.
  • 책임: 데이터 형식이나 요청을 외부 시스템에 맞춰 변환하거나, 내부 시스템에서 처리할 수 있도록 적절한 형식으로 변환하는 역할을 한다. 데이터베이스에서 데이터를 가져와 도메인 모델에 맞게 변환하거나, 외부 API 요청을 받아 내부적으로 처리할 수 있는 형태로 변경한다.
  • 특징: 외부와 내부의 데이터를 매핑하는 역할을 하며, 비즈니스 로직과는 분리되어 있다.

Use Case (유스케이스) 계층

  • 역할: 시스템이 수행해야 할 특정 작업을 정의하고 관리하는 계층이다. 유스케이스는 시스템이 외부 요청에 응답하기 위해 수행하는 구체적인 흐름을 담당한다.
  • 책임: 유스케이스는 시스템이 제공해야 하는 구체적인 기능을 정의하고, 비즈니스 규칙을 적용하여 원하는 결과를 도출한다. 예를 들어, 특정 조건에 따라 데이터를 처리하고, 그 결과를 사용자에게 제공하는 작업이 유스케이스 계층에서 이루어진다.
  • 특징: 유스케이스 계층은 도메인 규칙을 사용하여 애플리케이션의 구체적인 기능을 실행하지만, 외부 시스템과의 직접적인 연관은 없다.

Entity (엔티티) 계층

  • 역할: 시스템의 핵심 비즈니스 규칙과 도메인 모델을 정의하는 계층이다. Clean Architecture에서 가장 중요한 계층으로, 다른 계층의 영향을 받지 않으며, 시스템의 비즈니스 로직이 집중되는 곳이다.
  • 책임: 엔티티 계층은 도메인 규칙을 정의하고, 시스템의 핵심 데이터 구조를 표현한다. 도메인 엔티티, 값 객체, 도메인 서비스 등이 여기에 포함된다.
  • 특징: 이 계층은 가장 높은 수준의 독립성을 가지며, 외부의 변경에 영향을 받지 않는다. 외부에서 어떤 변화가 생기더라도 이 계층의 로직은 그대로 유지되도록 설계된다.

Hexagonal Architecture (헥사고날 아키텍처)

https://miro.medium.com/v2/resize:fit:1400/1*v3t2YGuUL_y3vDaPfrKwmw.png

Hexagonal Architecture, 또는 Ports and Adapters Architecture는 애플리케이션의 비즈니스 로직이 외부 시스템(예: 데이터베이스, 사용자 인터페이스, API 등)에 의존하지 않고 독립적으로 동작할 수 있도록 설계하는 아키텍처 패턴이다. Hexagonal Architecture의 핵심은 포트(Port)와 어댑터(Adapter)라는 명확한 개념을 중심으로, 애플리케이션의 중심을 도메인 로직에 두고 이를 외부와 연결하는 인터페이스를 구분하는 것이다.

헥사곤화 아키텍처에서는 육각형이란 이미지가 특징적인데, 이는 포트에 맞게 알맞게 구현만 해 주면 누구든지 사용할 수 있는 구조이다.핵심은 비즈니스 로직이 표현 로직이나 데이터 접근로직에 의존하지 않도록 하는 것이 중요 목표이다.

Port (포트)

포트는 애플리케이션의 외부와 상호작용하는 인터페이스를 의미한다.

 

Adapter (어댑터)

어댑터는 포트에 맞춰 구현된 구체적인 기술적인 연결부를 의미한다.

카카오 파트너사이트의 Hexagonal Architecture의 도입 이유

레이어드 아키텍처는 비즈니스 로직이 거대해지면서 어플리케이션 레이어가 오염되는 경우가 있다. 또한 클린 아키텍처 대신 Hexagonal Architecture를 선택한 이유는 Hexagonal Architecture는 포트와 어댑터라는 명확한 개념을 중심으로 설계되기 때문에 개발팀에서 구현이 더 용이하다. 특히, 비즈니스 로직이 외부 시스템과 철저히 분리되어 있고, 각 포트와 어댑터를 통해 외부 의존성을 제어할 수 있다는 점에서, 시스템의 유연성과 확장성을 보장할 수 있다.

카카오 파트너사이트의 요청 흐름 

 

  • 파트너 사이트로부터 외부에서 작품에 대한 판매 요청이 들어오면, 컨트롤러는 그것을 서비스 포트로 전달한다.
  • 서비스 포트에서는 도메인 로직에 따라 판매 시작 api를 수행하고, 리퍼스토리를 통해 데이터 변경 처리를 하며, 노티피케이션 시스템으로 메시지를 전송한다.
  • 판매 시작을 위해 작품 도메인 내에는 작품에 대한 정보와 컨텐츠 파일 정보, 기타 판매 요청을 처리하기 위한 기능이 구현되어 있다.
  • 데이터 처리를 위해 로드 포트, 세이브 포트와 이벤트 퍼블리시 포트를 사용하며, CQRS 패턴의 커맨드와 쿼리 책임을 분리하기 위해 조회를 제외한 CRUD 기능은 세이브 포트로 분리되어 있다.
 

Hexagonal Architecture의 장점과 단점

단점

  1. 트랜잭션 관리의 복잡성: 포트와 어댑터를 통해 시스템을 구성할 때, 트랜잭션 관리가 복잡해질 수 있다. 여러 서비스와 모듈 간의 통합 테스트와 트랜잭션 경계를 설정하는 것이 어려워질 수 있다.
  2. 통합 테스트의 난이도: 다양한 포트와 어댑터를 사용하여 외부 시스템과 상호작용하기 때문에, 통합 테스트 작성 및 실행이 어려워진다.
  3. 배포 복잡성 증가: MSA와 같은 구조에서 각 서비스의 독립성과 분리는 배포 시에 관리해야 할 요소들이 많아져 배포 난이도가 상승한다.
  4. 추가 코드 작성 필요: 도메인을 사용하기 위해 추가적으로 작성해야 하는 코드가 많아지며, 포트와 어댑터를 구분하는 과정에서 복잡성이 증가할 수 있다.
  5. 높은 도메인 이해도 필요: 도메인 모델을 명확하게 설계하고 관리하기 위해서는 도메인에 대한 깊은 이해가 필수적이다.

장점

  1. 보편적 언어(Ubiquitous Language)를 사용한 빠른 커뮤니케이션: 팀 내에서 일관된 용어와 개념을 사용함으로써 커뮤니케이션 속도와 정확성이 증가한다.
  2. 도메인 관계 정리: 도메인 간의 복잡한 관계를 어그리게이트(Aggregate)를 사용하여 큰 틀에서 명확하게 정리할 수 있다.
  3. 유지보수 편의성: 도메인의 독립성과 분리에 따라 유지보수가 용이해진다.
  4. 새로운 기능 추가의 유연성: 새로운 기능이나 요구 사항이 추가될 때, 기존 시스템에 영향을 최소화하면서 유연하게 대응할 수 있다.
  5. 캡슐화(Encapsulation): 도메인 로직과 외부 시스템을 분리함으로써 각 시스템 간의 결합도를 낮추고 응집도를 높인다.
  6. 느슨한 결합, 높은 응집도: Hexagonal Architecture는 loose couplinghigh cohesion을 통해 시스템 간의 의존성을 최소화하면서도 도메인 로직을 집중적으로 관리할 수 있다.
  7. 비즈니스 로직에 집중: 도메인 로직을 외부 시스템으로부터 분리함으로써, 비즈니스 로직에 집중할 수 있어 코드 관리가 용이하다.
  8. 코드 가독성 향상: 도메인 중심 설계 덕분에 비즈니스 로직과 기술적 구현이 명확하게 분리되어 코드 가독성이 향상된다.