개발is:e
기타
DDD(Domain-Driven Design)
Java
Spring
TDD(Test-Driven Development)
Contact
Copyright © 2024 | LeeJae-H
Home
> DDD(Domain-Driven Design)
Now Loading ...
DDD(Domain-Driven Design)
애그리거트
객체 기반 도메인 모델의 구성요소는 다음과 같다. 엔티티 밸류 애그리거트 리포지토리 -> 구현을 위한 도메인 모델 도메인 서비스 애그리거트(Aggregate) - 연관된 엔티티와 밸류를 개념적으로 하나로 묶은 것 애그리거트는 관련된 객체를 하나의 군으로 묶어 주며, 개념상 완전한 한 개의 도메인 모델을 표현한다. 애그리거트를 사용하면 모델 간의 관계를 상위 수준과 개별 모델 수준에서 모두 파악할 수 있다. 위와 같이 개별 객체 수준에서만 모델을 바라보면 상위 수준에서의 관계를 파악하기 어렵다. 이로 인해 전반적인 구조에서 도메인 간의 관계를 파악하기 어려워지고, 이는 코드를 변경하고 확장하는 것을 어렵게 만든다. 애그리거트의 경계를 설정할 때 기본이 되는 것은 도메인 규칙과 요구사항이다. 도메인 규칙에 따라 함께 생성되는 구성요소는 한 애그리거트에 속할 가능성이 높다. 그러나 “A가 B를 갖는다”라는 요구사항이 있을 때, 반드시 A와 B가 한 애그리거트에 속한다는 것을 의미하는 것은 아니다. “상품 상세 페이지에 들어가면 리뷰 내용도 함께 보여줘야 한다.”라는 요구사항이 있을 때, Product 엔티티와 Review 엔티티는 함께 생성되지 않고 함께 변경되지도 않는다. 한 애그리거트에 속한 객체는 유사하거나 동일한 라이프 사이클을 갖는다. 도메인 규칙에 따라 일부 객체를 같은 시점에 생성할 필요가 없는 경우도 있지만 애그리거트에 속한 구성요소는 대부분 함께 생성하고 함께 제거한다. 주문 애그리거트를 만들려면 Order(엔티티), ShippingInfo(밸류), Orderer(밸류)와 같은 관련 객체를 함께 생성해야 한다. Order는 생성했는데 ShippingInfo는 생성하지 않거나, ShippingInfo를 생성했는데 Orderer를 생성하지 않는 경우는 없다. 각 애그리거트는 독립된 객체 군이며 다른 애그리거트를 관리하지 않는다. 주문 애그리거트에서 회원의 비밀번호를 변경하지는 않는다. 애그리거트 루트 애그리거트 루트는 한 애그리거트 전체를 관리하는 책임을 가진 엔티티이다. 도메인 규칙을 지키려면 애그리거트에 속한 모든 객체가 정상 상태를 가져야 하는데, 이 책임을 지는 것이 애그리거트 루트이다. 애그리거트에 속한 객체는 애그리거트 루트에 직·간접적으로 속하게 된다. 주문 애그리거트의 애그리거트 루트는 Order이며, ShippingInfo, Orderer 등 주문 애그리거트에 속한 객체는 Order에 직·간접적으로 속한다. // 엔티티, 애그리거트 루트 public class Order { // 밸류 타입, 불변 객체 private ShippingInfo shippingInfo; // 애그리거트 루트는 도메인 규칙을 구현한 기능을 제공한다. public void changeShippingInfo(ShippingInfo newShippingInfo) { verifyNotYetShipped(); setShippingInfo(newShippingInfo); } private void verifyNotYetShipped() { if (state != OrderState.PAYMENT_WAITING && state != OrderState.PREPARING) { throw new IllegalStateException("Already shipped"); } } // 애그리거트 루트를 통해서만 애그리거트에 속한 객체를 변경해야 한다. // set 메서드의 접근 허용 범위는 private이다. private void setShippingInfo(ShippingInfo newShippingInfo) { // 밸류 타입인 ShippingInfo는 불변이므로 새로운 객체를 할당해서 값을 변경해야 한다. // this.shippingInfo.setAddress(newShippingInfo.getAddress())와 같은 코드를 사용할 수 없다. this.shippingInfo = newShippingInfo; } } 애그리거트 루트에서 도메인 규칙을 구현한 기능을 제공한다. 애그리거트 루트가 제공하는 메서드는 도메인 규칙에 따라 애그리거트에 속한 객체의 일관성이 깨지지 않도록 구현해야 한다. (객체의 일관성이란 객체의 속성이 불완전하거나 모순되지 않도록 유지하는 원칙이다.) “배송이 시작되기 전까지만 배송지 정보를 변경할 수 있다”는 도메인 규칙이 있다면, 애그리거트 루트인 Order가 이 기능을 구현한 메서드(changeShippingInfo())를 제공한다. 이 때, 해당 메서드는 이 규칙에 따라 배송 시작 여부를 확인하고 규칙을 충족할 때만 배송지 정보를 변경해야 한다. 애그리거트에 속한 객체의 일관성을 위해, 애그리거트 루트를 통해서만 애그리거트에 속한 객체를 변경해야 한다. 이를 위해, 엔티티에서 공개 set 메서드를 가급적 피하고 밸류 타입은 불변으로 구현하는 것을 습관적으로 적용해야 한다. 애그리거트 루트인 Order에서 ShippingInfo를 가져와 직접 정보를 변경하면 안된다. 참고 자료 도메인 주도 개발 시작하기(저자: 최범균) https://velog.io/@andy230/Aggregate-%EC%95%A0%EA%B7%B8%EB%A6%AC%EA%B1%B0%ED%8A%B8
DDD(Domain-Driven Design)
· 2025-02-18
엔티티와 밸류
객체 기반 도메인 모델의 구성요소는 다음과 같다. 엔티티 밸류 애그리거트 리포지토리 -> 구현을 위한 도메인 모델 도메인 서비스 도메인 모델은 크게 엔티티와 밸류로 구분된다. 엔티티(Entity) - 식별자를 갖는 객체 // 엔티티 public class Order { private String orderNumber; ... } 엔티티는 도메인의 고유한 개념을 표현하며, 도메인 모델의 엔티티는 데이터와 함께 도메인 기능을 제공한다. 주문 도메인 모델에서 주문에 해당하는 클래스가 Order이므로 Order가 엔티티가 된다. 식별자는 엔티티 객체마다 고유하며, 엔티티를 생성하고 속성을 바꾸고 삭제할 때까지 식별자는 바뀌지 않는다. 주문 도메인 모델에서 Order 엔티티는 주문번호를 속성으로 갖는다. 대표적인 엔티티의 식별자 생성 방식 특정 규칙에 따라 생성 ex) 현재 시간과 다른 값의 조합 사용자가 값을 직접 입력 ex) 회원의 아이디나 이메일 고유 식별자 생성기 사용 ex) UUID 일련번호 사용(주로 데이터베이스가 제공하는 자동 증가 기능 사용) ex) MySQL의 자동 증가 칼럼 -> 자동 증가 칼럼 방식의 경우, DB 테이블에 데이터를 추가하기 전에는 식별자를 알 수 없다. 엔티티에서 공개(public) set 메서드를 피해야한다. 이유는 다음과 같다. 도메인의 핵심 개념이나 의도를 코드에서 사라지게 한다. 객체의 일관성이 깨질 가능성이 높아진다. (객체의 일관성이란 객체의 속성이 불완전하거나 모순되지 않도록 유지하는 원칙이다.) 밸류(Value) - 식별자를 갖지 않는 객체 public class ShippingInfo { private Receiver receiver; // private String receiverName; // private String receiverPhoneNumber; ... } // 밸류 public class Receiver { private String name; private String phoneNumber; ... } 밸류 타입은 개념적으로 완전한 하나를 표현할 때 사용한다. ShippingInfo 클래스의 receiverName과 receiverPhoneNumber 필드는 서로 다른 두 데이터를 담고 있지만, 두 필드는 개념적으로 “받는 사람”이라는 하나의 개념을 표현한다. “받는 사람”이라는 개념을 위와 같이 Receiver로 표현할 수 있다. 밸류 타입은 불변으로 구현한다. 불변이란 데이터 변경 기능(ex) set 메서드)을 제공하지 않는 것을 의미하며, 불변 객체는 참조 투명성과 스레드에 안전한 특징을 가지고 있다. 밸류 타입이 꼭 두 개 이상의 데이터를 가져야 하는 것은 아니다. 의미를 명확하게 표현하기 위해 밸류 타입을 사용하는 경우도 있다. 또한, 밸류 타입은 밸류 타입을 위한 기능을 추가할 수 있다는 장점이 있다. 참고 자료 도메인 주도 개발 시작하기(저자: 최범균) https://sungman.tistory.com/16
DDD(Domain-Driven Design)
· 2025-02-16
도메인 모델
DDD(Domain-Driven Design)는 도메인과 일치하도록 소프트웨어를 모델링하는 데 중점을 둔 소프트웨어 설계 접근 방식이다. (feat. 위키백과) 이번 글을 포함한 DDD 관련 글에서, “온라인 서점”이라는 도메인을 예시로 들어서 설명하겠다. 도메인 도메인 = 소프트웨어로 해결하고자 하는 문제 영역 개발자 입장에서 바라보면 온라인 서점은 구현해야 할 소프트웨어의 대상, 즉 도메인에 해당한다. 한 도메인은 다시 하위 도메인으로 나눌 수 있다. 한 하위 도메인은 다른 하위 도메인과 연동하여 완전한 기능을 제공한다. 온라인 서점 도메인은 위와 같이 몇 개의 하위 도메인으로 나눌 수 있다. 주문 도메인은 고객의 주문을 처리하고, 카탈로그 도메인은 고객에게 구매할 수 있는 상품 목록을 제공한다. 특정 도메인을 위한 소프트웨어라고 해서 도메인이 제공해야 할 모든 기능을 직접 구현하는 것은 아니다. 많은 온라인 쇼핑몰이 배송 도메인의 일부 기능은 자체 시스템으로 구현하고, 나머지 기능은 외부 업체의 시스템을 사용한다. 결제 시스템도 직접 구현하기보다는 결제 대행업체를 이용해서 처리할 때가 많다. 도메인 모델 정의 1 : 도메인 모델 (개념 모델) = 도메인 자체를 이해하기 위한 모델 위와 같이 클래스 다이어그램(UML 표기법)을 사용하여 주문 도메인의 도메인 모델을 표현할 수 있다. 주문 도메인을 생각해보자. 온라인 서점에서 주문을 하려면 상품을 몇 개 살지 선택하고 배송지를 입력한다. 선택한 상품 가격을 이용해서 총 지불 금액을 계산하고, 금액 지불을 위한 결제 수단을 선택한다. 주문한 뒤에도 배송 전이면 배송지 주소를 변경하거나 주문을 취소할 수 있다. 이를 위한 주문 도메인 모델을 객체로 모델링한 것이 위 그림이다. 도메인 모델을 객체로만 모델링할 수 있는 것은 아니며, UML 표기법만 사용해야 하는 것도 아니다. 도메인을 이해하는 데 도움이 된다면 표현 방식이 무엇인지는 중요하지 않다. 주문 도메인의 경우에는 상태 다이어그램을 사용하여 주문의 상태 전이를 모델링할 수도 있다. 또한, 관계가 중요한 도메인이라면 그래프를 이용해서 도메인을 모델링할 수 있고, 계산 규칙이 중요하다면 수학 공식을 활용해서 도메인 모델을 만들 수도 있다. 각 하위 도메인마다 별도로 도메인 모델을 만들어야 한다. 각 하위 도메인이 다루는 영역은 서로 달라서 같은 용어라도 하위 도메인마다 의미가 달라질 수 있기 때문이다. 카탈로그 도메인의 상품이 상품 가격, 상세 내용을 담고 있는 정보를 의미한다면 배송 도메인의 상품은 고객에게 실제 배송되는 물리적인 상품을 의미한다. 처음부터 완벽한 개념 모델을 만들기보다는 전반적인 개요를 알 수 있는 수준으로 개념 모델을 작성하고, 구현하는 과정에서 개념 모델을 구현 모델로 점진적으로 발전시켜 나가야 한다. 소프트웨어를 개발하는 동안 개발자와 관계자들은 해당 도메인을 더 잘 이해하게 된다. 또한, 프로젝트 초기에 이해한 도메인 지식이 시간이 지나 새로운 통찰을 얻으면서 완전히 다른 의미로 해석되는 경우도 있다. 즉, 프로젝트 초기에 완벽한 도메인 모델을 만들더라도 결국 도메인에 대한 새로운 지식이 쌓이면서 모델을 보완하거나 변경하는 일이 발생한다. 도메인 모델을 사용하면 여러 관계자들이 동일한 모습으로 도메인을 이해하고 도메인 지식을 공유하는 데 도움이 된다. 정의 2 : 도메인 모델 (구현 모델) = 도메인 계층의 객체 모델 일반적인 애플리케이션의 아키텍처는 위와 같이 네 개의 영역으로 구성되며, 도메인 계층은 도메인 규칙을 구현한다. 도메인 규칙이란 도메인에서 반드시 지켜야 하는 제약 조건이나 로직을 의미하며, 도메인 계층의 주요 구성요소는 다음과 같다. 엔티티 밸류 애그리거트 리포지토리 도메인 서비스 도메인 모델은 도메인 모델 패턴을 의미하며, 도메인 모델 패턴은 아키텍처 상의 도메인 계층을 객체 지향 기법으로 구현하는 패턴을 말한다. (feat. 마틴 파울러의 “엔터프라이즈 애플리케이션 아키텍처 패턴”) // 엔티티 public class Order { private String orderNumber; private OrderState state; private ShippingInfo shippingInfo; public void changeShippingInfo(ShippingInfo newShippingInfo) { if (!isShippingChangeable()) { throw new IllegalStateException("Can't change shipping in" + state); } this.shippingInfo = newShippingInfo; } private boolean isShippingChangeable() { return state == OrderState.PAYMENT_WAITING || state = OrderState.PREPARING; } } public enum OrderState { PAYMENT_WAITING, PREPARING, SHIPPED, DELIVERING, DELIVERY_COMPLETED; } 위와 같이 Java 코드로 주문 도메인의 도메인 모델(엔티티)을 구현할 수 있다. Order 클래스는 주문 도메인 모델의 엔티티이며, 주문 도메인의 “출고 전에 배송지를 변경할 수 있다”라는 도메인 규칙을 구현하고 있다. 어떤 도메인과 관련된 중요 업무 규칙은 해당 도메인 모델에서 구현한다. 도메인 규칙을 구현한 코드는 도메인 모델에만 위치하기 때문에, 규칙이 바뀌거나 규칙을 확장해야 할 때 다른 코드에 영향을 덜 주고 변경 내역을 도메인 모델에 반영할 수 있게 된다. 주문 도메인의 경우, 주문과 관련된 중요 업무 규칙을 주문 도메인 모델인 Order나 OrderState 등에서 구현한다. 도메인 모델 정의에 대한 나의 요약 도메인 모델에 대한 나의 생각을 정리한 내용이다. 누군가 나에게 “도메인 모델이 무엇인가요?”라고 물어본다면, 아래와 같이 이야기할 것이다. 도메인 모델은 일반적으로 도메인 자체를 이해하기 위한 모델을 의미하며, “개념 모델”이다. 이때, 조직의 구성원들이 이러한 도메인 모델(개념 모델)을 보고 도메인을 이해할 수 있다면, 도메인 모델(개념 모델)을 꼭 클래스 다이어그램으로 나타낼 필요는 없다. 조직의 궁극적인 목표는 소프트웨어를 개발하는 것이고, 이는 도메인 모델(개념 모델)을 구현해야 하는 것을 의미한다. 객체 지향 프로그래밍을 기반으로 개발한다면, 도메인 모델(개념 모델)의 구현은 객체(클래스) 형태가 된다. 따라서, 도메인 모델(개념 모델)을 꼭 클래스 다이어그램으로 나타낼 필요는 없지만, 객체 지향 프로그래밍을 기반으로 개발하는 개발자 입장에서 도메인 모델(개념 모델)이 클래스 다이어그램으로 나타내져 있다면 압도적으로 구현하기 편리하다.(개념 모델 그대로 코드로 구현하면 되기 때문이다.) 요약하자면, 객체 지향 프로그래밍을 기반으로 소프트웨어를 개발하는 조직에서 도메인 모델(개념 모델)은 도메인을 객체로 모델링한 것, 즉 객체 기반 도메인 모델을 의미한다고 볼 수 있다! 도메인 모델의 구성요소 객체 기반 도메인 모델의 구성요소는 다음과 같다. 엔티티 밸류 애그리거트 리포지토리 -> 구현을 위한 도메인 모델 도메인 서비스 참고 자료 도메인 주도 개발 시작하기(저자: 최범균)
DDD(Domain-Driven Design)
· 2025-02-15
<
>
Touch background to close