-
[책 리뷰] DDD START! 도메인 주도 설계 구현과 핵심 개념 익히기자바/기타 2021. 5. 7. 11:07
도메인
- 소프트웨어로 해결하고자 하는 문제 영역
- 한 도메인은 다시 하위 도메인으로 나눌 수 있다.
도메인 모델
- 특정 도메인을 개념적으로 표현한 것
도메인 모델 패턴
- 아키텍쳐상의 도메인 계층을 개체 지향 기법으로 구현하는 패턴
- 핵심 규칙을 구현한 코드는 도메인 모델에만 위치하기 때문에 규칙을 바꾸거나 규칙을 확장해야 할 때 다른 코드에 영향을 덜 주고 변경 내역을 모델에 반영할 수 있다.
- 도메인 계층은 도메인의 핵심 규칙을 구현한다.
- 주문 도메인의 경우 '출고 전에 배송지를 변경할 수 있다'는 규칙과 '주문 취소는 배송 전에만 할 수 있다'는 규칙을 구현한 코드가 도메인 계층에 위치하게 된다.
public class Order { private OrderState state; private ShippingInfo shippingInfo; public void changeShippingInfo(ShippingInfo newShippingInfo) { if(!isShippingChanagable()) { throw new IllegalStateException("can't change shipping in " + state); } this.shippingInfo = newShippingInfo; } private boolean isShippingChanagable(){ return state == OrderState.PAYMENT_WAITING || state == OrderState.PREPARING; } ... } public enum OrderState { PAYMENT_WAITING, PREPARING, SHIPPED, DELIVERING, DELIVERY_COMPLETED; }
엔티티
- 엔티티의 가장 큰 특징은 식별자를 갖는다.
- 식별자는 엔티티마다 고유해서 각 엔티티는 서로 다른 식별자를 갖는다.
- 엔티티의 식별자는 바뀌지 않고 고유하기 때문에 두 엔티티 객체의 식별자가 같으면 두 엔티티는 같다고 판단할 수 있다.
밸류 타입
- 2개 이상의 필드가 개념적으로 완전한 하나를 표현할 때 사용
- 엔티티 타입의 두 객체가 같은지 비교할 때 주로 식별자를 사용한다면 두 밸류 객체가 같은지 비교할 때는 모든 속성이 같은지 비교해야 한다.
public class ShippingInfo { private String receiverName; private String receiverPhoneNumber; // private Receiver receiver; 밸류 타입 사용 ... } public class Receiver { private String name; private String phoneNumber; }
- 의미를 명확하게 표현하기 위해 하나의 필드를 밸류 타입으로 사용하는 경우도 있다.
- 밸류 타입을 사용할 때 또 다른 장점은 밸류 타입을 위한 기능을 추가할 수 있다.
- Money처럼 데이터 변경 기능을 제공하지 않는 타입을 불변(immutable)이라고 표현한다. 밸류 타입을 불변으로 구현하면 보다 안전한 코드를 작성할 수 있다.
public class OrderLine { private Product product; private int price; private int quantity; private int amount; ... } public class Money { private int value; public Money add(Money money) { return new Money(this.value + money.value); } public Money muliply(int multiplier) { return new Money(this.value * multiplier); } } public class OrderLine { private Product product; private Money price; private int quantity; private Money amount; ... }
인티티 식별자와 밸류 타입
식별자를 위한 밸류 타입을 사용해서 의미가 잘 드러나도록 할 수 있다.
public class Order { // OrderNo 타입 자체로 id가 주문번호임을 알 수 있다. private OrderNo id; ... public OrderNo getId() { return id; } }
도메인 모델에 set 메서드 넣지 않기
- set 메서드는 도메인의 핵심 개념이나 의도를 코드에서 사라지게 한다.
- 도메인 객체를 생성할 때 완전한 상태가 아닐 수도 있다.
- 도메인 객체가 불완전한 상태로 사용되는 것을 막으려면 생성 시점에 필요한 데이터를 전달해주어야 한다. 즉 생성자를 통해 모든 데이터를 받아야 한다.
- 생성자로 필요한 것을 모두 받으므로 생성자를 호출하는 시점에 필요한 데이터가 올바른지 검사할 수 있다.
public class Order { public Order(Orderer orderer, List<OrderLine> orderLines, ShippingInfo shippingInfo, OrderState state) { setOrderer(orderer); setOrderLines(orderLines); ... } private void setOrderer(Orderer orderer) { if(orderer == null) throw new IllegalArgumentException("no orderer"); this.orderer = orderer; } private void setOrderLines(List<OrderLine> orderLines) { verifyOneOrMoreOrderLines(orderLines); this.orderLines = orderLines; } private void verifyOneOrMoreOrderLines(List<OrderLine> orderLines){ if(orderLines == null || orderLines.isEmpty()) { throw new IllegalArgumentException("no OrderLine"); } } }
애그리거트
애그리거트
- 복잡한 도메인을 이해하고 관리하기 쉬운 단위로 만들려면 상위 수준에서 모델을 조망할 수 있는 방법이 필요한데, 그 방법이 바로 애그리거트이다.
- 애그리거트는 모델을 이해하는데 도움을 줄 뿐만 아니라 일관성을 관리하는 기준이 된다.
- 애그리거트는 관련된 모델을 하나로 모은 것이기 때문에 한 애그리거트에 속한 객체는 유사하거나 동일한 라이프사이클을 갖는다.
- 애그리거트는 경계를 갖는다.
- 애그리거트는 독립된 객체 군이며, 각 애그리거트는 자기 자신을 관리할 뿐 다른 애그리거트를 관리하지 않는다.
- 경계를 설정할 때 기본이 되는 것은 도메인 규칙과 요구 사항이다.
- 함께 변경되는 빈도가 높은 객체는 한 애그리거트에 속할 가능성이 높다.
애그리거트 루트
- 애그리거트에 속한 모든 객체가 일관성을 유지하려면 애그리거트 전체를 관리할 주체가 필요한데 이 책임을 지는 것이 애그리거트의 루트 엔티티이다.
- 애그리거트 루트가 아닌 다른 객체가 애그리거트에 속한 객체를 직접 변경하면 안된다. 이는 애그리거트 루트가 강제하는 규칙을 적용할 수 없어 모델의 일관성을 깨는 원인이 된다.
도메인 모델과 BOUNDED CONTEXT
도메인 모델과 경계
- 하위 도메인마다 사용하는 용어가 다르기 때문에 올바른 도메인 모델을 개발하려면 하위 도메인마다 모델을 만들어야 한다.
BOUNDED CONTEXT
- 모델의 경계를 결정하며 한 개의 BOUNDED CONTEXT는 논리적으로 한 개의 모델을 갖는다.
BOUNDED CONTEXT 간 통합
- 외부 연동을 위한 도메인 서비스 구현 클래스는 도메인 모델과 외부 시스템 간의 모델 변환을 처리한다.
- 모델 간 변환이 복잡하면 별도 변환기를 둔다.
- 메시지 큐를 이용한 통합
마이크로서비스와 BOUNDED CONTEXT
BOUNDED CONTEXT는 모델의 경계를 형성하는데, BOUNDED CONTEXT를 마이크로서비스로 구현하면 자연스럽게 컨텍스트별로 모델이 분리된다.BOUNDED CONTEXT 간 관계
- 고객/공급자 관계를 갖는 BOUNDED CONTEXT
- 두 BOUNDED CONTEXT가 같은 모델을 공유하는 경우
- 독립 방식
컨텍스트 맵
- BOUNDED CONTEXT 간의 관계를 표시한 것
이벤트
이벤트
- 시스템 간 강결합의 문제
- 트랜잭션 처리 이슈
- 외부 서비스 성능에 직접적인 영향을 받는 문제
- 도메인 객체에 서로 다른 도메인 로직이 섞이는 문제
public class Order { verifyNotYetShipped(); this.state = OrderState.CANCELED; this.refundStatus = REFUND_STARTED; try { refundSvc.refund(getPaymentId()); this.refundStatus = State.REFUND_COMPLETED; } catch (Exception ex) { ... } }
지금까지 언급한 문제가 발생하는 이유는 주문 BOUNDED_CONTEXT와 결제 BOUNDED_CONTEXT간의 강결합(high coupling)때문이다.
이런 강한 결합을 없앨 수 있는 방법이 있는데 그것은 바로 이벤트를 사용하는 것이다.이벤트의 개요
- 이벤트가 발생한다는 것은 상태가 변경됐다는 것을 의미한다.
이벤트의 용도
- 도메인의 상태가 바뀔 때 다른 후처리를 해야 할 경우 후처리를 실행하기 위한 트리거로 이벤트를 사용할 수 있다.
- 서로 다른 시스템 간의 데이터 동기화
이벤트의 장점
이벤트를 사용하면 서로 다른 도메인 로직이 섞이는 것을 방지할 수 있다.
'자바 > 기타' 카테고리의 다른 글
[UML] 클래스 다이어그램 (0) 2021.05.07 [JAVA] Array 클래스 (0) 2021.04.30 [JAVA] String 클래스 (0) 2021.04.30 [JAVA] Map (0) 2021.04.30 [JAVA] 클래스의 구성 관계 (0) 2021.04.28