소개: 모듈화의 기초로서의 DDD
Il 도메인 기반 설계(DDD) 소프트웨어 모델링에 대한 하나의 접근 방식이 아닙니다. 모듈식 모놀리스에서 올바른 모듈 경계를 식별하는 가장 강력한 도구입니다. DDD가 없으면 기술 기준이 아닌 기술 기준에 따라 모듈 간의 경계가 임의화됩니다. 실제 도메인 경계에 있습니다. 그 결과 비즈니스를 반영하지 못하는 취약한 아키텍처가 탄생하게 됩니다.
이 기사에서는 모듈화에 적용되는 DDD의 기본 개념을 살펴보겠습니다. 제한된 컨텍스트, 유비쿼터스 언어, 컨텍스트 매핑, 그리고 집계 디자인. 이러한 개념을 모듈 경계로 변환하는 방법을 살펴보겠습니다. 구체적인 내용과 이를 Java/Spring Boot 코드로 구현하는 방법을 알아보세요.
이 기사에서 배울 내용
- DDD의 기초: 엔터티, 값 개체, 집계 및 제한된 컨텍스트
- 애플리케이션의 제한된 컨텍스트를 식별하는 방법
- 유비쿼터스 언어: 각 모듈에는 고유한 어휘가 있으므로
- 컨텍스트 매핑: 부패 방지 계층, 공유 커널, 고객/공급업체
- 집합적 설계: 일관된 거래의 핵심
- 제한된 컨텍스트를 Java 패키지 구조로 변환하는 방법
- 콘웨이의 법칙: 팀과 아키텍처 정렬
모듈형 모놀리스를 위한 DDD 기본 사항
도메인 중심 설계(Domain Driven Design)는 도메인 기반 소프트웨어 모델링에 대한 정확한 용어를 소개합니다. 비즈니스 도메인. 기본 구성 요소를 살펴보겠습니다.
실재
에이'실재 그리고 시간이 지나도 지속되는 독특한 정체성을 지닌 지배의 대상이다.
두 엔터티는 속성 값에 관계없이 ID가 동일하면 동일합니다.
예: Order, User, Product.
값 개체
Un 가치 객체 ID 없이 속성으로 정의된 불변 객체
소유. 동일한 속성을 가진 두 개의 값 개체가 동일합니다. 예: Money,
Address, EmailAddress.
집계
Un 집계된 그리고 하나의 단위로 취급되는 엔터티와 값 객체의 클러스터 수정 작업. 집계에는 루트 엔터티 액세스 포인트 역할을 하는 내부 불변성의 무결성을 보장합니다. 거래는 절대로 교차되어서는 안 됩니다. 집계의 경계.
// Esempio di Aggregate: Order
// Order e la root entity dell'aggregate
// OrderItem e Money sono parte dell'aggregate
public class Order {
private final UUID id; // identità dell'entità
private UUID userId;
private List<OrderItem> items; // entità figlie
private Money total; // value object
private OrderStatus status;
private Instant createdAt;
// Factory method - unico modo per creare un Order
public static Order create(UUID userId, List<ProductDto> products) {
Order order = new Order();
order.id = UUID.randomUUID();
order.userId = userId;
order.items = products.stream()
.map(p -> OrderItem.fromProduct(p))
.toList();
order.total = Money.sum(
order.items.stream().map(OrderItem::getPrice).toList()
);
order.status = OrderStatus.CREATED;
order.createdAt = Instant.now();
return order;
}
// Le invarianti sono protette all'interno dell'aggregate
public void cancel() {
if (this.status == OrderStatus.SHIPPED) {
throw new IllegalStateException(
"Cannot cancel a shipped order"
);
}
this.status = OrderStatus.CANCELLED;
}
}
// Value Object immutabile
public record Money(BigDecimal amount, Currency currency) {
public static Money sum(List<Money> values) {
return new Money(
values.stream()
.map(Money::amount)
.reduce(BigDecimal.ZERO, BigDecimal::add),
values.get(0).currency()
);
}
}
제한된 컨텍스트: 도메인의 경계
Un 제한된 컨텍스트 그리고 그 안에 있는 언어적, 기능적 경계 도메인 모델이 유효하고 일관성이 있습니다. 제한된 컨텍스트 내에서 각 용어는 정확하고 모호하지 않은 의미. 맥락의 경계를 넘어 동일한 개념을 가질 수 있음 다른 의미.
구체적인 예: 개념 "제품" 상황에 따라 다른 의미를 갖습니다.
- 에서 목록: 제품에는 이름, 설명, 이미지, 카테고리가 있습니다.
- 에서 창고: 제품에 사용 가능한 수량, 위치, 재주문 임계값이 있습니다.
- '에서는주문하다: 단가, 주문수량, 할인이 포함된 제품 및 라인
- 에서 해운: 제품의 무게, 크기, 파손 위험이 있음
단일 모델 강제 Product 이러한 모든 컨텍스트를 충족하는 것은
갓 클래스 수십 개의 속성이 있으며 그 중 대부분은 관련이 없습니다.
각 특정 상황.
// SBAGLIATO: Un unico modello Product per tutti i contesti
// "God Class" che viola il principio di bounded context
public class Product {
// Dati catalogo
private String name;
private String description;
private List<String> images;
private Category category;
// Dati magazzino
private int stockQuantity;
private String warehouseLocation;
private int reorderThreshold;
// Dati ordine
private BigDecimal unitPrice;
private BigDecimal discount;
// Dati spedizione
private double weight;
private Dimensions dimensions;
private boolean fragile;
// ... 30+ attributi
}
// CORRETTO: Un modello per ogni bounded context
// Catalogo
public class CatalogProduct {
private UUID id;
private String name;
private String description;
private List<String> images;
private Category category;
}
// Magazzino
public class InventoryItem {
private UUID productId; // riferimento, non l'oggetto intero
private int stockQuantity;
private String location;
private int reorderThreshold;
}
// Ordine
public class OrderLineItem {
private UUID productId; // riferimento
private Money unitPrice;
private int quantity;
private Money discount;
}
기본 규칙
각각의 제한된 컨텍스트에는 다음이 있어야 합니다. 자신의 모델 공유된 개념에 대한 것입니다. 컨텍스트는 다음을 통해 통신합니다. 참조 ID, 물체를 통해서가 아니라 공유. 이것이 모듈을 독립적이고 추출 가능하게 만드는 핵심 원칙입니다.
유비쿼터스 언어: 상황에 대한 어휘
L'유비쿼터스 언어 개발자와 도메인 전문가가 공유하는 어휘 제한된 컨텍스트 내에서. 각 문맥에는 고유한 언어가 있으며 동일한 용어는 다른 맥락에서 다른 의미를 갖습니다.
이 개념은 코드의 이름 지정에 실질적인 영향을 미칩니다.
- 형태로 주문하다:
Customer주문한 사람, 청구서 수신 주소 및 결제 방법 - 형태로 해운:
Customer배송 주소 및 배송 기본 설정이 포함된 수취인 - 형태로 지원하다:
Customer상호작용 기록 및 우선순위 수준을 통해 티켓을 개설한 사람
단일 모델 강제 Customer 모든 상황에 대해 결합을 생성하고 방지합니다.
팀은 모델을 독립적으로 발전시킵니다.
컨텍스트 매핑: 컨텍스트가 통신하는 방법
Il 컨텍스트 매핑 제한된 컨텍스트가 서로 상호 작용하는 방식을 정의합니다. 다양한 관계 패턴이 있으며 각각 특정한 장단점이 있습니다.
ACL(부패 방지 계층)
L'부패 방지 레이어 제한된 컨텍스트를 분리하는 번역 레이어 다른 맥락의 모델로부터. 주문 모듈에 카탈로그 모듈의 데이터가 필요한 경우, 카탈로그 DTO를 직접 사용하지 않고 ACL을 통해 이를 내부 모델로 변환합니다.
// Anti-Corruption Layer nel modulo Order
// Traduce i concetti del Catalog nel linguaggio dell'Order
package com.ecommerce.order.internal.acl;
@Component
class CatalogAntiCorruptionLayer {
private final CatalogModuleApi catalogModule;
// Traduce ProductDto (catalogo) in OrderProduct (ordine)
public OrderProduct resolveProduct(UUID productId) {
ProductDto catalogProduct = catalogModule.findById(productId)
.orElseThrow(() -> new ProductNotAvailableException(productId));
// Traduzione: prende solo ciò che serve al contesto Order
return new OrderProduct(
catalogProduct.id(),
catalogProduct.name(),
Money.of(catalogProduct.price(), Currency.EUR),
catalogProduct.isAvailable()
);
}
}
// OrderProduct: modello interno del contesto Order
// Non e il ProductDto del Catalog
record OrderProduct(
UUID productId,
String displayName,
Money price,
boolean available
) {}
공유 커널
Lo 공유 커널 둘 이상의 제한된 코드 간에 공유되는 작은 코드 세트
컨텍스트. 일반적으로 공통 값 개체(예: Money, Address),
공유되고 지속적인 도메인 이벤트. 공유 커널은 다음과 같아야 합니다. 최소한의
관련된 모든 팀의 동의가 있어야만 변경됩니다.
// Shared Kernel: codice condiviso tra moduli
package com.ecommerce.shared;
// Value objects condivisi
public record Money(BigDecimal amount, Currency currency) {
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new CurrencyMismatchException();
}
return new Money(this.amount.add(other.amount), this.currency);
}
}
public record Address(
String street, String city,
String zipCode, String country
) {}
// Eventi di dominio condivisi
public record OrderCreatedEvent(
UUID orderId, UUID userId, Money total, Instant timestamp
) {}
고객/공급업체
패턴에서는 고객/공급업체, 컨텍스트(공급자)는 데이터나 서비스를 제공합니다. 다른 상황(고객)으로. 공급자는 인터페이스를 정의하지만 요구 사항을 고려합니다. 고객의. 이 패턴은 한 모듈이 어떤 방식으로든 다른 모듈에 분명히 의존하는 경우에 적용됩니다. 단방향.
집계 설계: 트랜잭션 및 일관성
집합체의 디자인은 모듈식 단일체에 매우 중요합니다. 국경 거래상의. ACID 트랜잭션은 단일 집계 내에서 작동해야 합니다. 서로 다른 집계 간의 상호 작용(동일한 모듈에서도)은 다음을 사용하여 관리해야 합니다. 최종 일관성 도메인 이벤트를 통해
집합체 설계 규칙
- 작은: 집계는 가능한 한 작아야 합니다. 일관성이 필요한 항목만 단일 트랜잭션으로 집계
- ID별 참조: 집계에는 다른 집계에 대한 직접 참조가 포함되지 않으며 해당 ID만 포함됩니다.
- 집합당 하나의 트랜잭션: 동일한 거래에서 여러 집계를 편집하지 마십시오.
- 집계 간의 가능한 일관성: 도메인 이벤트를 사용하여 다양한 집계 간의 변경 사항 조정
// Design corretto degli Aggregates
// Order Aggregate - confine transazionale
public class Order {
private UUID id;
private UUID userId; // riferimento per ID, non User object
private UUID paymentId; // riferimento per ID, non Payment object
private List<OrderItem> items; // parte dell'aggregate
private OrderStatus status;
// Operazione transazionale: avviene tutta nello stesso aggregate
@Transactional
public void addItem(UUID productId, int quantity, Money price) {
// Verifica invarianti interne
if (this.status != OrderStatus.DRAFT) {
throw new IllegalStateException("Cannot modify confirmed order");
}
this.items.add(new OrderItem(productId, quantity, price));
this.recalculateTotal();
}
}
// SBAGLIATO: transazione che attraversa più aggregate
@Transactional
public void createOrderAndReserveStock(CreateOrderCmd cmd) {
Order order = orderRepo.save(Order.create(cmd));
// VIOLAZIONE: modifica un altro aggregate nella stessa transazione
inventoryService.reserveStock(order.getItems());
// Se reserveStock fallisce, tutto il rollback e complesso
}
// CORRETTO: eventual consistency tra aggregate
@Transactional
public void createOrder(CreateOrderCmd cmd) {
Order order = orderRepo.save(Order.create(cmd));
// Pubblica evento: il modulo Inventory reagira in modo asincrono
events.publish(new OrderCreatedEvent(order.getId(), order.getItems()));
}
제한된 컨텍스트 식별: 실제 방법
올바른 경계 컨텍스트를 식별하는 것은 과학만큼이나 예술입니다. 방법은 다음과 같습니다. 4단계로 실용적입니다:
- 이벤트 스토밍: 개발자와 도메인 전문가를 한자리에 모으세요. 주황색 포스트잇에 있는 모든 도메인 이벤트를 식별합니다. 이벤트를 논리적 클러스터로 그룹화합니다. 각 클러스터는 잠재적인 제한된 컨텍스트입니다.
- 언어 분석: 동일한 용어가 다른 의미를 갖는 부분을 식별합니다. 각각의 언어적 차이는 맥락 경계를 나타냅니다.
- 의존성 분석: 클러스터 간의 종속성을 매핑합니다. 외부 종속성이 거의 없는 클러스터는 독립 모듈을 위한 좋은 후보입니다.
- 팀과의 검증: 식별된 경계가 팀의 조직 구조와 기술을 반영하는지 확인
콘웨이의 법칙
La 콘웨이의 법칙 조직이 미러링하는 시스템을 설계한다고 말합니다. 의사소통의 구조. 실제로는 모듈 경계가 정렬되어야 합니다. 팀 경계가 있습니다. 주문 모듈을 담당하는 팀은 다음을 수행할 필요가 없습니다. 작업을 완료하기 위해 매일 배송 모듈 팀과 협력합니다.
코드 구조가 팀 구조를 반영하지 않으면 마찰이 발생합니다: 병합 충돌 빈번하고 지속적인 조정 회의, 상호 차단. 아키텍처를 정렬하고 모듈식 모놀리스의 성공을 위한 조직이자 전제 조건입니다.
다음 기사
다음 글에서는 ''이라는 주제를 다루겠습니다. 데이터베이스 디자인 모듈형 모노리스의 경우: 공유 스키마와 모듈별 스키마, 데이터 소유권, 분산 트랜잭션 관리, 최종 일관성을 위한 Saga 및 Event Sourcing과 같은 패턴이 있습니다. 제한된 컨텍스트를 번역하겠습니다. 여기서 구체적인 데이터 구조로 식별됩니다.







