도메인 주도 설계(DDD)란

ssongkim·2022년 8월 19일
0

MSA와 DDD

목록 보기
2/9
post-custom-banner

Overview

저번 시간에는 마이크로서비스 아키텍처에 대해 알아보았다.

마이크로서비스 아키텍처로 프로젝트를 하기 위해서는 분석 설계 단계에서 마이크로서비스를 도출해내는 작업을 해야한다. 이때 주로 사용하는 기법이 DDD(도메인 주도 설계)의 전략설계이다.

DDD는 2003년에 처음 등장한 설계 접근 방식이며 마이크로서비스는 2011년 소프트웨어 설계자를 위한 행사에서 처음 선보였다. 당시 Netflix와 Amazon은 마이크로서비스의 초기 개척자였다.

MSA에서 DDD가 왜 부각되었을까? MSA를 위해 분석 설계하는 단계에서 서비스를 식별하는 과정에 DDD가 많은 도움이 되었기 때문이다.

마이크로서비스 아키텍처를 분석, 설계하는 과정에서 DDD의 모든 개념이 사용되지 않는다. DDD의 여러 개념 중 필요한 개념만 골라 사용한다.

이번 시간에는 DDD(도메인 주도 설계)에 대해 알아보려고 한다.

도메인 주도 설계란

소프트웨어 산업은 다른 산업 내에서 발생하는 다양한 비즈니스 문제를 해결한다는 점에서 독특하다. 소프트웨어 산업에 종사하는 우리는 도메인 전문가의 요청을 해석하고 이를 소프트웨어로 만들어 다양한 비즈니스 문제를 해결한다.

이전의 프로젝트 설계 방법론에서는 설계 및 개발 단계에서 도메인 전문가(고객) 가 참여하지 않았다. 또한 고객의 요구사항은 개발 도중 자주 바뀌게 되어 개발자들이 많은 스트레스를 받고 결과물을 받은 고객 또한 "내가 말한건 이게 아닌데"라며 결과물을 만족하지 않은 경우가 많았다.

여기서 말하는 도메인 전문가(고객)은 기획자, 디자이너 등 프로젝트 이해 관계자를 의미한다.

왜 이런 일이 발생하냐면 고객과 개발자는 살아온 길도 다르고 소통하는 언어도 다르기 때문이다. 개발자는 코드로 말하지만 고객은 그렇지 않다.

도메인 주도 설계는 도메인 모델을 중심으로 설계해 나가는 기법이다.

설계 단계에서 도메인 전문가(고객)가 참여하여 서로 의사소통을 통해 개발자와 도메인 전문가가 모두 이해할 수 있는 도메인 모델을 만들어 발전시켜 나간다.

DDD는 사용자(고객, 회사 등)에서 제시하는 복잡한 비즈니스 상황에서 문제를 해결하기 위해 사용할 수 있는 방법론이다.

도메인이란

여기서 말하는 도메인이란 소프트웨어로 해결하고자하는 문제 영역 을 의미한다.
하나의 도메인은 또 여러 서브 도메인으로 나누어질 수 있다.
온라인 서점이라는 도메인이 있다면 이 안에는 또 제품, 주문 재고 등의 여러 서브 도메인으로 나누어질 수 있다.

도메인 모델이란

도메인 모델이란 이러한 도메인을 개념적으로 표현한 것으로 전체 도메인을 하위 업무로 세분화하면서 표현하거나 각 하위업무 중심으로 구성요소를 추상화하는 행위이다.

도메인 모델링은 화이트보드 그림, A4 등 어떤 형태로든 추상화하여 표현할 수 있다.

데이터 주도 설계와 도메인 주도 설계

도메인 주도 개발은 데이터 중심 설계에서 벗어나 도메인 모델이 비즈니스 로직과 항상 함께 움직이도록 하는 것을 지향한다.
DDD를 알기 전 우리가 흔히 해오던 방식이 주로 데이터 주도 설계이다.

데이터 주도 설계이란 객체가 가져야할 데이터에 초점을 두고 설계를 하는 방식이다. 해결해야하는 비즈니스 문제를 만나면 해결에 필요한 데이터를 먼저 정의하고 이를 제어하는 방식으로 설계, 개발한다.

예시를 코드로 살펴보자

데이터 주도 설계 코드

데이터 주도 설계 에서는 객체 자신이 포함하고 있는 데이터를 조작하는 데 필요한 행동을 정의한다.

public class Person {
	private String name;
    private int age;
    private double weight;
    ...
    getter
    setter
}

데이터 주도 설계는 편리하고 익숙하다 하지만 운영 여러 비즈니스를 적용해야하다보면 문제가 많이 발생한다.

도메인 주도 설계에서는 도메인 객체가 생성되는 시점에 필요한 모든 정보를 주고 상태 변경에 대한 로직 등을 도메인 객체가 가지도록 한다.

setName(String name) - x
setAge(int age) - x

changeName(String name) - o
increaseAge() - o
completePayment() - o

전략설계와 전술설계

MSA를 위한 DDD에서 설계는 마이크로서비스를 식별하는 전략적 설계와 식별된 마이크로서비스의 내부 구조를 상세하게 정의하는 전술적 설계로 구성된다.

전략설계

전략 설계란 비즈니스를 분석해 바운디드 컨텍스트를 식별하고 유비쿼터스 랭귀지를 정의하며 식별된 바운디드 컨텍스트 간 매핑 관계인 컨텍스트 맵을 그리는 활동을 말한다.

마이크로서비스를 식별하기 위해 전략설계를 수행할 수 있다. 이때 반드시 알아야하는 중요한 두가지 개념이 있다 바로 유비쿼터스 랭귀지와 바운디드 컨텍스트이다.

유비쿼터스 랭귀지(보편언어)

같은 단어지만 고객이나 개발자는, 혹은 개발자 사이에서도 서로가 각자 해석한 바에 따라 다른 의미로 단어를 해석할 수 있다.
예를 들어 상품 이라는 단어가 있으면 재고 관점의 상품과 결제 관점의 상품은 맥락은 비슷하지만 조금 다른 의미를 가지고 있고 이는 고객과 개발자, 그리고 개발자 사이에서도 혼용해 해석될 여지가 있다.

유비쿼터스(ubiquitous)는 "언제 어디서나 동시에 존재하는" 이라는 뜻을 가지고 있다.

특정 도메인에 대해 고객, 개발자, 기획자 등 프로젝트와 관련된 모두가 도메인 내에서 공통으로 사용하는 언어를 유비쿼터스 언어라고 한다.

언어를 혼용해 해석하지 않도록 용어사전 등을 만들어 공유해 유비쿼터스 언어를 정의한다.

바운디드 컨텍스트와 유비쿼터스 언어는 보통 1대1로 매칭되며 각 바운디드 컨텍스트 별로 보편언어가 정의된다.

즉 같은 단어라도 바운디드 컨텍스트 별로 조금씩 의미가 다르다. 재고 마이크로서비스의 상품과 결제 마이크로서비스의 상품은 맥락은 비슷하나 의미는 조금씩 다를 것이다.

마이크로서비스 식별 시에는 이 유비쿼터스 랭귀지에 따라 식별해내야 한다고 한다.

bounded context

도메인 모델들을 구성하다보면 각 도메인 모델과 다른 도메인 모델 간의 경계가 보인다. 이곳에서 사용하는 언어(유비쿼터스 언어)와 저곳에서 사용하는 언어(유비쿼터스 언어)가 상이한 경계가 바로 도메인의 경계인 바운디드 컨텍스트가 된다.

bounded context란 도메인 모델 간의 경계로 비즈니스 응집성이 있는 컨텍스트 단위를 바운디드 컨텍스트라고 한다.

바운디드 컨텍스트는 보편언어와 마이크로서비스를 식별하기 위한 훌륭한 단위가 될 수 있다.

1. 바운디드 컨텍스트는 오직 단일 팀에만 할당돼야 한다.

하나의 팀은 여러 바운디드 컨텍스트를 할당받을 수 있으나 하나의 바운디드 컨텍스트를 여러 팀이 할당받아 관리하면 안된다.

2. 각 바운디드 컨텍스트마다 소스코드 레포지토리를 할당받아야 한다.

바운디드 컨텍스트는 보편언어와 매우 연관이 되어있으며 소스코드 레벨에서 보편언어의 혼란이 발생하는 것을 방지하기 위해서이다 또한 소스코드 레벨에서 다른 바운디드 컨텍스트에 접근할 수 있게될 경우, 바운디드 컨텍스트의 경계를 매우 손쉽게 넘어갈 수 있게되는 문제가 발생한다.

바운디드 컨텍스트를 소스코드 레벨에서 서로를 아예 모르게 해야한다. 이를 위해 바운디드 컨텍스트 별로 소스코드와 데이터베이스 스키마를 명확히 분리한다.

각 바운디드 컨텍스트는 각자의 소스코드와 데이터베이스 스키마를 소유하며, 컨텍스트 간의 통신이 필요한 경우 공식 인터페이스를 통해서만 주고받도록 한다.
이를 통해 다른 바운디드 컨텍스트 내부의 세부사항을 몰라도 되도록 한다. 세부사항을 몰라도 되므로 다른 바운디드 컨텍스트의 세부사항이 바뀌어도 인터페이스만 바뀌지 않는다면 나는 영향받지 않을 수 있다.

그렇다면 모놀리식 애플리케이션 안에서 바운디드 컨텍스트가 여러 개 존재할 수 있을까? DDD Europe에서는 모듈러 모놀리식을 소개한다. https://www.youtube.com/watch?v=DhMrqX_qrJE

context map

바운디드 컨텍스트를 식별할 때 각 컨텍스트는 내부적으로 응집성이 높고 다른 컨텍스트와는 의존관계가 낮아야 한다는 원칙 하에 설계되지만 각 컨텍스트 간에 아무런 관계가 없다는 의미가 아니다. 이러한 컨텍스트 간의 의존관계를 표현하는 것을 컨텍스트 매핑이라고 한다.

즉 컨텍스트 맵은 하나의 큰 도메인을 여러 바운디드 컨텍스트로 식별하고 이들 간의 관계를 표현한 그림을 의미한다.

좋은 링크: https://engineering-skcc.github.io/microservice%20modeling/ddd-Srategic-design/

Aggregate

먼저 도메인 모델을 쫙 펼쳐보자.
개별 객체 수준에서 모델을 바라보면 상위 수준에서 관계를 파악하기 어렵다.

복잡한 도메인을 이해하고 관리하기 쉬운 단위로 만들려면 상위 수준에서 모델을 조율할 수 있는 방법이 필요한데, 그 방법이 바로 애그리거트이다. 수많은 객체를 애그리거트로 묶어서 바라보면 좀 더 상위 수준에서 도메인 모델 간의 관계를 쉽게 파악할 수 있다.

애그리거트는 전략설계, 전술설계 모두에서 중요한 개념이다.

애그리거트는 객체 간의 관계를 정의하는 방법 중 하나로, 애그리거트 루트(최상위에 존재하는 엔티티)를 중심으로 관련된 엔티티, VO들을 묶은 시스템이 기대하는 책임을 수행하며 일관성을 유지하는 단위를 의미한다.
이러한 애그리거트는 데이터 변경 단위가 되므로 트랜잭션과도 밀접한 관계를 맺는다.

여기서 엔티티란 독립적으로 존재 가능한 개체를 의미합니다.


하나의 애그리거트에는 하나의 애그리거트 루트(루트 엔티티)가 존재하고 루트 엔티티와 루트 엔티티와 관련된 엔티티들 그리고 VO들로 구성된다.
애그리거트는 복잡한 도메인 구조를 단순한 구조로 만들어준다.

한 애그리거트에 속한 객체는 다른 애그리거트에 속하지 않는다. 애그리거트는 독립된 객체 군이며, 각 애그리거트는 자기 자신을 관리할 뿐 다른 애그리거트를 관리하지 않는다.

애그리거트 패턴에서는 애그리거트를 한 단위로 일관되게 처리하기 위해 다음과 같은 규칙을 부여한다.

  • 애그리거트 루트만 참조한다.
  • 애그리거트 내 상세 클래스를 바로 참조하지않고 루트를 통해 참조해야한다. 수정도 마찬가지
  • 하나의 트랜잭션으로 하나의 애그리거트만 생성 및 수정한다.
  • 애그리거트 간의 참조는 객체를 직접 참조하는 대신 기본키(id) 를 사용한다.
    기본키를 사용하면 애그리거트 간 느슨하게 연관되고 개발자로 하여금 수정이 필요하지 않은 애그리거트를 함께 수정하는 실수를 원천적으로 방지할 수 있다.

루트 엔티티

루트 엔티티는 애그리거트의 핵심적인 역할과 책임을 가지고있으며, 애그리거트 내부의 모든 객체들은 애그리거트 루트 엔티티를 중심으로 관리된다.

애그리거트 루트 엔티티의 주요 책임

애그리거트 내부의 모든 객체들을 관리하고 제어한다.
애그리거트 루트 엔티티의 불변성을 보장한다.
애그리거트 루트 엔티티와 관련된 다른 객체 간의 관계를 관리한다.
애그리거트 루트 엔티티를 통해 애그리거트 전체를 제어한다.

불변성 유지와 캡슐화를 지키는 이유는 애그리거트 내부 객체들을 외부로부터 보호하면서 일관성을 유지할 수 있으며, 객체 간 결합도를 낮추어 유지 보수성과 확장성을 높이기 위함이다.

애그리거트 생명주기

하나의 애그리거트 안에 있는 엔티티들은 생명주기(라이프싸이클)가 동일하다.
생명주기가 동일하다는 말은 관련된 엔티티들과 VO가 탄생하고 소멸함에 있어서 흐름이 동일해야한다는 의미이다.
한 애그리거트 안에서 생명주기가 다른 엔티티(개별 개체)가 있다면, 별도의 애그리거트로 분리하는 것이 좋다.

애그리거트 간에 경계

애그리거트 간에 경계를 설정할 때 기본이 되는 것은 도메인 규칙과 요구사항이다. 도메인 규칙에 따라 함께 생성되는 구성요소는 한 애그리거트에 속할 가능성이 높다. 또는 사용자 요구사항에 따라 주문 상품 개수와 배송지를 함께 변경하기도 한다. 이렇게 함께 변경되는 빈도가 높은 객체는 한 애그리거트에 속할 가능성이 높다.

조심해야할 것은 'A가 B를 갖는다'로 해석할 수 있는 요구사항이 있다고 하더라도 이것은 반드시 A와 B가 한 애그리거트에 속한다는 것을 의미하는 것은 아니다. 좋은 예가 상품과 리뷰다. 상품 상세 페이지에 들어가면 상품 상세 정보와 함께 리뷰 내용을 보여줘야 한다는 요구사항이 있다면 Product 엔티티와 Review 엔티티가 한 애그리거트에 속한다고 생각할 수 있다. 하지만 Product와 Review는 함께 생성되지 않고 함께 변경되지도 않는다. 게다가 Product를 변경하는 주체가 상품 담당자라면 Review를 생성하고 변경하는 주체는 고객이다.

애그리거트의 적절한 크기

애그리거트는 올바른, 적절한 크기를 가져야한다. 하나의 애그리거트가 너무 많은 엔티티를 가지게 된다면 DB에서 긁어 애플리케이션의 메모리 상에 로드할 때 더 느리게 로드되고 더 많은 메모리를 차지할 것이다. 그만큼 GC도 느릴 것이고, 트랜잭션 수행 시간도 갱신해야하는 데이터 개수만큼 길어질 것이다.
ex) 유저 엔티티 1000개, 게시판 엔티티 2000개, 댓글 엔티티 2000개인데 이걸 하나의 애그리거트라고 본다면??

애그리거트 사이즈가 작으면 작을수록 그만큼 더 작은 단위로 트랜잭션이 동작할 것이도 수행 시간도 그만큼 작을 것이며 롤백이 될 경우의 수도 줄어들테니 트랜잭션 성공 확률이 올라갈 것이다.

처음에는 하나의 엔티티로만 애그리거트를 구성해보고, 애그리거트 패턴에 따라 하나의 트랜잭션에서 처리되어야하는 생명주기가 같은 단위를 묶는 모델링을 통해 적절한, 작은 사이즈의 애그리거트를 정하자.

처음 도메인 모델을 만들기 시작하면 큰 애그리거트로 보이는 것들이 많지만 도메인에 대한 경험이 생기고 도메인 규칙을 제대로 이해할수록 실제 애그리거트의 크기는 줄어들게 된다.

애그리거트와 레포지토리

애그리거트는 개념적으로 하나이므로 리포지터리는 애그리거트 전체를 저장소에 영속화해야 한다. 예를 들어, Order 애그리거트를 저장할 때 애그리거트 루트와 매핑되는 테이블뿐만 아니라 애그리거트에 속한 모든 구성요소를 위한 테이블에 데이터를 저장해야 한다.

여러 애그리거트를 수정하는 경우

애그리거트 패턴에 따르면 하나의 트랜잭션에서는 하나의 애그리거트만 수정하고 처리하기를 권장한다. 이는 여러 애그리거트를 수정하고자 하는 경우 각각 분리된 트랜잭션으로 처리해야함을 의미한다.

단일 트랜잭션으로 여러 애그리거트를 갱신하고 싶은 욕구가 들겠지만 다른 모두를 위해 일관성 있게 사용할 필요가 있다.

만약 여러 애그리거트를 갱신해야할 필요가 있다면 결과적 일관성을 이용해 다른 애그리거트를 갱신하도록 하자. 권장하는 방법은 미들웨어 솔루션을 이용하는 것이다.

| Spring에서는 이벤트 리스너를 지원한다..!

여러 애그리거트에 걸쳐 도메인 로직을 작성해야할 때는 도메인 서비스를 구현하자.
https://incheol-jung.gitbook.io/docs/study/ddd-start/7

Event Storming

도메인 전문가(고객)은 IT용어를 알지 못한다. 개발자는 반대로 도메인 전문가의 언어를 이해하지 못한다. 이로 인해 걸림돌 및 이슈가 발생하기 마련이다.

프로젝트 이해관계자들이 하나의 장소에서 내용을 공유한다면 DDD를 효과적으로 가져갈 수 있다.

이벤트 스토밍은 이벤트를 중심으로 이해관계자(도메인 전문가, 개발자, 테스터 등)이 모여 브레인 스토밍하는 워크숍을 의미하다.

이벤트 스토밍은 DDD의 전략설계를 가장 쉽게 하는 활동이다.

이벤트 스토밍에 대해서는 다음 게시글에서 자세히 이야기 해보겠다.

전술설계

전략 설계를 통해 마이크로서비스를 도출해냈다면 각 마이크로서비스의 세부 설계를 전술설계를 통해 한다.

전술설계란 전략적 설계로 도출한 마이크로서비스의 내부구조를 상세히 정의하는 활동을 의미한다.

전략설계를 하여 설계를 마쳤다면 이를 실제 코드로 옮겨 구현해야할 것이다.
어떤 소프트웨어 아키텍처를 사용할지, API 설계를 어떻게할지 등을 설계하는 단계를 전술설계라고 보면 된다.

소프트웨어 아키텍처는 대표적으로 다음을 선택할 수 있다.

  • 레이어드 아키텍처
  • 클린 아키텍처
  • 헥사고날 아키텍처

시스템 아키텍처는 대표적으로 다음을 고려해볼 수도 있다.

  • MSA
  • 모놀리식
  • 모듈러 모놀리식

전술설계에 대해서는 다음 이벤트스토밍에 대한 게시글 다음에 자세히 다루어보도록 하겠다.

마무리

이번 시간에는 DDD에 대해 알아보았다.
다음 시간에는 이벤트스토밍을 통해 마이크로서비스를 도출해내는 작업을 해보겠다!

참고 자료

도메인 주도 설계로 시작하는 마이크로서비스 개발
도메인 주도 설계 핵심

https://appleg1226.tistory.com/m/40

https://engineering-skcc.github.io/msa/DDD-StrategicDesign/

보면 좋은 것

https://incheol-jung.gitbook.io/docs/study/ddd-start/3

애그리거트
https://incheol-jung.gitbook.io/docs/study/ddd-start/3

profile
鈍筆勝聰✍️
post-custom-banner

0개의 댓글