DDD에 대해서 알아가면 알아갈수록 개념을 하나로 잡기가 어렵다는 걸 느끼게 됐습니다. 여러 블로그에서 DDD에 대한 정보를 검색해봐도 정확한 적용방식이나 개념이 이해가 가지 않아서 정리하게 된 글입니다. 저와 같은 분들에게 도움이 되면 좋겠습니다.
DDD란 뭘까?
DDD란 말 그대로만 보자면 도메인 패턴을 중심에 놓고 프로그램을 설계해가는 방식을 의미합니다. 이러한 설계 방식을 올바르게 적용한다면 도메인 모델이라고 하는 소프트웨어 추상화를 달성할 수 있게 되고 이것이 목표인 설계 방식이라고 보여집니다. 그럼 도메인은 뭘까요?
Domain (도메인)
도메인 - 영어 단어로써의 의미는 영토, 영역, 범위를 의미합니다. 프로그래밍 부분에서 의미는 소프트웨어 프로그램이 제공하는 기능 중 하나(영역)입니다. 도메인은 여러 하위 도메인으로 구성될 수 있습니다.
도메인 모델 - 특정 도메인을 개념적으로 표현한 것, 도메인을 프로그래밍상의 모델로 만든 것이라고 생각합니다.
EX) 인터넷 쇼핑몰에서 주문, 상품, 회원 정보 같은 것이 도메인, 이것들을 모델로 만들고 만약 주문을 구현하는 상황에서 필요한 모델들은 주문서 모델, 상품 모델 정도가 되겠습니다.
일단 도메인은 기존의 OOP(객체 지향 프로그래밍)에서 객체랑 비슷하구나 생각이 듭니다. 여기까지 이해를 하고 DDD에 대한 정보를 찾아보니 유비쿼터스 언어(보편 언어)란 단어가 나옵니다.
Ubiquitous Language (유비쿼터스 언어)
유비쿼터스 언어 - 도메인과 관계된 사람들이 공통적으로 의미를 이해할 수 있도록 정의하는 모든 단어나 용어들
예를 들어, 쇼핑몰을 같이 개발하는 사람들끼리 "우리 쇼핑몰에서 주문서는 OrderForm(오더폼)이라고 명명합니다." 라고 한다면 OrderForm은 유비쿼터스 언어를 사용한 용어가 되는 것이고 이는 같이 개발하는 사람들끼리는 문서로 정리해두는것이 필요합니다.
또한 주의해야할 점이 있습니다. 쇼핑몰에서 주문이라는 도메인에 관계된 사람들은 개발자만 있는 것은 아닐겁니다. 기획자도 있고 쇼핑몰 사장님도 해당 단어에 대해서 알아야 하겠죠. 개발자들이 쇼핑몰 사장 혹은 기획자에게 에러 이슈에 대해 설명할 상황이 온다고 했을 때 "16:00시에 들어온 OrderForm에서 특정 이슈가 있어서..." 라고 이야기 했는데 "OrderForm이 뭐죠?" 라는 질문이 돌아온다면 유비쿼터스 언어로써의 역할을 못한게 됩니다. 도메인과 관계된 사람들이 공통적으로 의미를 이해할 수 있어야 하기 때문이죠.
따라서 유비쿼터스 언어로 용어를 정의하고 사용할때는 문서화와 충분한 공유가 필요할것 같습니다.
그렇다면 일단 D(도메인)가 뭔지는 알겠으니 DD(주도 설계)를 어떻게 해야하는건지 그 방식에 대해 알아보겠습니다.
DDD를 시도해보자!
이 부분이 DDD에 대해서 이해해보려고 시작하면서 가장 어려운 부분이였습니다. 설계 방식이라고 했는데 구체적으로 정의되어 있지 않고 내가 이해하는 방식이 정답인지를 모르겠더라구요. 그래서 이 부분부터는 주의해서 보셔야 합니다. 이게 정답이다가 아니고 한 사람이 이렇게 이해할 수 있겠구나 라고 보시면 됩니다.
하지만 부족한 제가 한마디로 정의해보자면 몇 가지 규칙과 권고 사항을 정해놓고 그 안에서 편하게 코딩하면 되는 것 같습니다.
그렇다면 DDD를 가볍게 적용해보면서 그 안에서 규칙과 권고 사항을 살펴보면 될 것 같네요.
0. 도메인 모델 정제하기
DDD를 시작하는 방법은 위의 그림과 같이 시작하면 될 것 같습니다. 위의 그림에서 설명하고 있는 내용은 아래와 같습니다.
- 시나리오 정의 내리기
- 시나리오에 맞춰 모델 구성
- 해당 모델로 코딩
- 시나리오를 수정
- 문제가 생기면 위의 사항들을 반복
1. 시나리오 구성
시나리오 구성하는 것을 알아보기 위해 예를 들어보겠습니다. 우리는 DDD를 하기 위하여 도메인부터 생각해 봐야합니다. 쇼핑몰을 만들 때 주문이라는 도메인을 보죠. 이 도메인에는 어떠한 시나리오들이 있을까요?
- 사용자가 제품을 주문서를 통해 주문한다.
- 사용자가 주문한 내용을 취소한다.
- 사용자가 주문서를 확인한다.
이 정도만 봐도 도메인 모델들이 보여집니다. <사용자>, <제품>, <주문서> 등이 있겠네요.
이제 이 모델들을 구성하고 코딩에 활용하면 됩니다. 간단하게 구성해보겠습니다.
2. 모델 구성하기
* 주문서 모델
public class OrderForm {
private String orderId;
private String userId;
private List<Item> itemList;
private String address;
}
* 사용자 모델
public class User {
private String userId;
private String userName;
private String password;
}
* 아이템 모델
public class Item {
private String itemId;
private String itemName;
}
모델을 간단하게 구성해봤습니다. 여기서 앞에서 살펴본 유비쿼터스 언어가 적용되어 있습니다. OrderForm이 그 주인공이죠. 하지만 모델을 구성하면서 참고해야할 DDD의 권고 사항과 기본 요소들이 더 있습니다.
- Entity, Value
- 모델에 set 메서드를 무조건 추가하는것은 자제
- Aggregate
Entity
- 고유한 식별자를 갖는 모델
Value
- Value 모델은 불변을 원칙으로 합니다.
- 식별자가 존재하지 않는다
- 의미를 명확하게 표현하거나 두 개 이상의 데이터가 하나의 범위로 묶일 수 있을 경우 Value 모델로 이용
모델을 구성할 때는 Entity 모델과 Value 모델로 나누어서 적절히 구성하는 것이 필요합니다. 그리고 각 모델은 특성들이 있으니 지켜줘야겠죠.
위와 같은 정의를 보았을 때 Entity는 쉽게 이해가는 모델입니다. 위에서 정의한 OrderForm, User, Item들 모두 Entity 모델입니다. 각각 id로 식별자가 존재하니까요. Value는 위의 정의만 봐서는 잘 모르겠네요 예시를 보겠습니다.
위의 모델 예시에서 주문서 모델 내에 address(주소)는 단순히 String으로 저장해도 이용하는데 문제는 없습니다.
하지만 시, 도, 군, 상세주소 등으로 나눌 수 있겠죠? 즉, 두 개 이상의 데이터가 하나의 범위로 묶일 수 있습니다.
그리고 주소는 주소 그 자체가 식별자이지 따로 식별자를 두는 것은 어색합니다. User의 경우에는 userName이 중복이 가능하다면 userName만 가지고는 식별할 수는 없으니 식별자를 두는것이 타당하지만요.
이러한 조건들로 보니 Value 모델로 만들어도 문제가 없어 보입니다.
* 개선된 주문서 모델
public class OrderForm {
// Entity 모델
private String orderId;
private String userId;
private List<Item> itemList;
private Address address;
}
private class Address {
// Value 모델
private String city;
private String dong;
private String detailAddress;
}
Entity와 Value는 해결했고 set 메서드는 왜 마음대로 추가하면 안될까요? 이유는 아래와 같습니다.
- 도메인의 핵심 개념이나 의도를 망칠 수 있다.
- set 메서드를 사용하는 것은 도메인 객체가 불완전한 상태로 존재 할 수 있다는 것이다.
- 불완전한 상태로 존재하는 도메인 자체는 또 다른 도메인으로 나뉠 수 있다.
- 불완전한 상태의 도메인을 막으려면 생성 시점에 필요한 것을 전달해 주어야 한다.
즉, 도메인의 개념에 맞게 사용하려면 set 메서드가 아닌 생성자를 사용해서 객체를 선언하고 사용해야한다는 것입니다.
이 부분이 제가 처음 DDD에 맞춰 코드를 구성할 때 가장 어색한 부분이였습니다. 하지만 이해하게 된 시점은 위에 해당하는 객체들이 기존에 개발자들이 사용하는 DTO와는 조금 다르다는 것에서 시작했습니다.
예를 들면, 위의 User(사용자) 도메인 모델에서 password 같은 경우는 보통 DB에 password 그대로 저장하는 일은 드뭅니다. 보안을 위해 인코딩을 거쳐서 저장하는 것이 일반적이죠.
그럼 User를 처음 등록할 때 Controller에서 User객체로 받았다면 그 객체에서 password를 get->인코딩->set->User객체 DB 저장 해야겠네?!
이와 같이 머리 속에 박혀있어서 헷갈렸지만 <인코딩을 하는 행위>는 <setPassword>가 아니라 <encodePassword>가 되는 것입니다. 단순히 set메서드를 만들어 필드값만 변경하는 메소드를 만들지 말고 Entity가 해야할 행위를 구현하는 메서드를 만들라는 의미로 저는 받아들여졌습니다.
물론 단순히 필드값을 변경하는 행위가 있을 수 있고 set과 다름없이 동작하는 로직이더라도 그 메소드의 이름은 <set...>이 아니게 될겁니다.
그래서 User 엔티티 모델이 encodePassword라는 메소드를 가지게 하고 비지니스 로직 중간에 해당 메서드를 통해서 사용하면 되었습니다.
* password 인코딩이 추가된 User 모델
public class User {
private String userId;
private String userName;
private String password;
public static void encodePassword() {
encode(this.password);
}
}
마지막으로 Aggregate를 살펴보겠습니다. Aggregate의 특성은 아래와 같습니다.
- 관련 객체를 하나로 묶은 그룹
- Aggregate는 그룹에 속한 객체들을 관리하는 루트 Entity를 갖는다.
- Aggregate 루트 Entity
- 핵심 역할은 Aggregate의 일관성이 깨지지 않도록 하는 것이다.
- Aggregate 루트는 제공해야 할 도메인 기능을 구현한다.
- Aggregate 단위로 구현을 캡슐화 하도록 돕는다.
- 한 Aggregate에 속한 객체는 다른 Aggregate에 속합니다.
- 대부분의 Aggregate는 한 개의 혹은 두 개 이상의 Entity로 존재한다.
Aggregate는 즉, 그룹입니다. 위의 예시 모델들로 따지면 주문서와 주문된 Item은 Aggregate를 나눌 수도 있지만 Order Aggregate로 묶을 수도 있을 것 같습니다. 사용자는 따로 Aggregate를 구성해서 묶어야겠습니다.
Order 모델이 Aggregate root가 되고 해당 모델이 Order에 대한 메소드들을 포함하게 되겠네요.
3. 구현과 테스트
위의 시나리오와 모델을 가지로 비지니스 로직을 구현하면 DDD를 얼추 맞춘 프로그래밍을 하게 됩니다.
하지만 아직도 저도 확신이 없네요 ㅎㅎ (어렵다 어려워..)
정리가 부족하고 틀린 부분이 많을 수 있습니다.
많은 지적 혹은 조언 부탁드려요~
'Web > 웹 상식' 카테고리의 다른 글
[Spring] 스프링 DI(Dependency Injection)은 어떻게 이루어지나 (0) | 2021.05.24 |
---|---|
SPA에 대해서 (Single Page Application) (0) | 2021.05.01 |
Java에서 외부 프로세스 실행하기 (0) | 2021.04.07 |
Http와 Rest API에 대한 간단한 정리 (0) | 2021.03.05 |
Spring Retry에 대해서 (0) | 2020.10.14 |