본문 바로가기

프로그래밍, 개발

DDD 개발 이해하기 : 도메인(Domain)과 애그리거트(Aggregate)

현업에서 서버들이 MSA 아키텍처로 구성되어 있기 때문에, 자연스럽게 이 MSA를 탄생시킨 도메인 주도 개발, DDD에 대해서도 알아야 할 필요성이 생겼다. 또한, 이 DDD 에서 아키텍처의 핵심은 도메인 객체를 지켜내는 것이다. 그리고 이 도메인 객체를 잘 지켜내고 관리하기 위해서 헥사고날 아키텍처를 적용하는 것이 좋다라는 선배 개발자의 말을 듣고, 헥사고날 아키텍처를 현재 담당하고 있는 서버 구조에 적용하기 위해서라도 먼저 DDD 자체에 대해 공부를 할 필요성이 있다고 생각했다. 그래서 이번에 [도메인 주도 개발 시작하기 : DDD 핵심 개념 정리부터 구현까지] 책을 읽어보며 정리하게 되었다.

도메인(Domain)이 무엇인가요?

도메인은 소프트웨어로 해결하고자하는 문제의 영역을 말한다. 예를 들어, 교보문고와 같이 온라인 서점을 개발해야 한다고 치면, 온라인 서점은 도메인이 되고 그 하위 도메인으로 주문, 정산, 리뷰, 배송이 있을 수 있다. 정확히 어떤 기능을 제공할지에 따라 하위 도메인이 변경될 수 있다. 또한 개발 리소스와 상황에 따라 하위 도메인을 외부 오픈소스 등을 사용해서 구현할지 직접 구현할지도 정할 수 있다. 그리고 이것에 따라 아키텍처도 정해질 수 있겠다.

도메인을 잘 정의한 다음에는 도메인 속에 해당 도메인의 핵심 규칙을 잘 구현해두어야 한다. 예를 들어, 교보문고의 서버를 운영하는 서버 개발자는 주문(Order) 이라는 엔티티(Entity)를 하나 만들고, 그 안에서 1. 출고 전에 배송지를 변경할 수 있다. 라는 규칙과 2. 주문 취소는 배송 전에만 할 수 있다. 라는 규칙을 구현할 수 있다.

 

아, 그리고 보통은 도메인 계층을 도메인 모델이라고 하기 보다는, 그 도메인 계층을 구성하는 객체 모델을 언급할 때, 도메인 모델이라고 많이들 표현한다고 한다.

또 처음에 도메인 개발을 시작할 때, 도메인에 대한 이해가 100% 완벽할 수가 없다. 도메인에 대한 이해는 개발 초기에 비해 점점 발전하게 되니까, 처음부터 너무 완벽하게 하려고 하는 욕심을 버려야 겠다. 점점 더 구현하면서 이해하게 되고, 기획자와 소통하면서 내부 규칙들이 명확하게 되면 이제 개발자로서는 필드와 내부 메소드에 대한 변수명도 조금 더 명확하게 정할 수 있게 되고, 구현해야 하는 규칙들을 생성자에도 더 능숙하게 담을 수 있게 되겠지.

 

도메인 영역의 주요 구성요소

일반적으로 계층구조 아키텍처는 표현, 응용, 도메인, 인프라 이렇게 구성된다. 표현과 응용 영역은 도메인 영역을 사용하고 도메인 영역은 인프라를 사용하게 된다. 전체적인 구조는 그렇고, 우선 도메인 영역만 보자.

엔티티

고유의 식별자를 갖는 객체로 자신의 라이프 사이클을 갖는다.

벨류

엔티티 속성으로 사용할 뿐만 아니라, 다른 밸류타입의 속성으로도 사용할 수 있다.

애그리거트

연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것이다. 예를 들어서, Order 엔티티와 Orderer 밸류 객체를 하나의 애그리거트로 묶을 수가 있다. 전체 구조에서 모델을 이해하기 위해 꼭 필요한 개념이다.

리포지토리

도메인 모델의 영속성을 처리한다. DBMS 테이블에서 도메인 모델을 로딩하거나 저장하는 역할을 한다.

도메인 서비스

특정 엔티티에 속하지 않는 도메인 로직을 제공한다. 도메인 로직이 여러 엔티티와 밸류 값들이 필요하면, 도메인 서비스에서 로직을 구현한다.

 

새롭게 알게 된 사실

밸류타입을 만들어서, 따로 관리하면 더 도메인의 특징을 잘 드러낼 수 있다

엔티티는 식별자를 가진다. 근데 그 식별자는 일반적으로 데이터 타입이 String(문자열)인 경우가 많다. 하지만 이것보다 식별자를 위한 밸류타입을 따로 지정해서 관리하면 조금 더 가독성이 좋은 코드를 만들 수 있다. 예를 들어, 교보문고에서 책을 주문하게 되면 주문이라는 객체가 한개 생성될 것이고, 임의로 uuid 와 같은 unique key 식별자가 생성된다고 하자. 이것보다는 OrderNo 라는 밸류값이 생겨서 요런식으로 관리를 한다면, 필드 이름이 id 이기만 해도 데이터 타입으로 주문번호를 의미하는 것이 드러나서 더 관리하기가 용이하다.

private OrderNo id;

도메인 모델에 set 넣지 않기

왜 넣는 것이 안 좋을까?

  • 단순히 setter 를 사용해서 상태값만 변경하게 되면, 상태 변경과 관련된 도메인 지식이 코드에서 사라지게 된다. 예를 들어, 상태값이 변경되었을 때, 따라오는 다른 처리들이 있을 것이다. 근데 단순히 setOrderState() 이런식으로만 설정하면, 다른 처리를 하기가 어렵다. 하지만 만일 completePayment() 이런식으로 메서드명을 정하게 되면, orderState 필드도 complete로 바꾸어주고, 상태값이 바뀜에 따라서 따라오는 추가적인 처리들도 함께 넣어서 해줄 수가 있다. 이렇게 되면 도메인의 규칙이 더 잘 보이고, 기획에서 규칙 변경을 요청해도 빠르게 고쳐야 하는 지점을 찾고 처리하기도 편할 것이다.
  • 도메인 객체를 생성할 때 온전하지 않은 상태가 될 수 있다. setter를 사용하면, 특정 값들이 누락될 수가 있다. 필요한 모든 값들은 생성자에서 받아서 처리하여 주는 것이 좋다.

참고로, DTO 에도 setter 를 적용하지 않고 불변 객체로 만드는 것이 좋다고 한다.

 

좀 더 생소한, 애그리거트가 뭔지 더 살펴보자!

애그리거트의 특징은 하나의 애그리거트에 속한 엔티티와 밸류들은 공통된 상태값을 공유해야 한다는 것이다. 그리고 또 애그리거트에 속한 객체는 애그리거트 루트 엔티티에 직접 또는 간접적으로 속하게 된다. 그리고 이 애그리거트 루트의 핵심 역할은 애그리거트의 일관성을 유지하기 위함이다. 애그리거트 루트의 기능 구현은 애그리거트 내부의 다른 객체들을 조합해서 가능한데, 이 때 중요한 것은 애그리거트 외부에서 다른 애그리거트에 속한 값들을 변경할 수 없도록 접근 제어자를 protected로 하던지 혹은 불변값으로 생성을 할 수 있게만 하는 것이다.

그리고 애그리거트와 리포지토리의 관계는 1:1이라고 볼 수 있다. 하나의 애그리거트에는 루트 애그리거트 리포지토리만 존재하게 될 것이고, 그 리포지토리에서 다른 밸류값들도 저장되고 통제된다. 즉 DB 테이블은 루트 애그리거트에 대한 것만 존재하는 것이다.

 

결국 애그리거트 이것도 도메인의 영역을 잘 정의해서 일관성있게 도메인을 관리하는 측면에서 중요한 거군. 점점 개발하다보면 엔티티 객체가 늘어날 것이고, 애그리거트를 신경쓰지 않고 늘리다보면 코드 구조가 엉망이 될 수 있다. 이 책을 이번에 읽으면서 느낀 것은 예전에 읽을 때는 단순히 문자 그대로만 읽히는 느낌이었는데, 지금은 현업에서 내가 고민하고 있는 것들에 대해 답을 제시해주는 그런 느낌이다. 책을 완전히 다 읽고 도메인 모델에 대해 깊이 생각해보고 꼭 헥사고날 아키텍처까지 지금 담당하고 있는 서버에 적용시켜 봐야겠다...!