一、DDD概述
1.1 什么是领域驱动设计(DDD)?
领域驱动设计(Domain-Driven Design,简称DDD)是一种软件设计方法,由Eric Evans在他的著作《Domain-Driven Design: Tackling Complexity in the Heart of Software》中提出。它主要关注于复杂系统的设计,强调通过领域模型来捕获业务逻辑和规则,并将软件系统的设计重心放在业务领域上。
1.2 为什么需要DDD?
- 处理复杂业务:当业务逻辑复杂,且变化频繁时,传统的分层架构可能无法很好地组织代码,导致代码混乱、难以维护。
- 统一语言:DDD强调使用统一的语言(Ubiquitous Language)来沟通,使得业务专家和开发人员能够更好地协作。
- 清晰的边界:通过限界上下文(Bounded Context)和聚合(Aggregate)等模式,DDD帮助划分复杂的业务边界,使系统更易于理解和扩展。
1.3 DDD的核心目标
- 将业务复杂性转化为可管理的模型。
- 通过模型驱动设计,使软件系统能够灵活应对业务变化。
- 提高代码的可读性、可维护性和可测试性。
二、核心概念
DDD(领域驱动设计)的核心思想是将软件系统的实现与复杂的业务领域模型紧密结合起来,以应对核心业务逻辑的复杂性。
其核心概念可以分为两大板块:战略设计 和 战术设计。
2.1 战略设计
战略设计关注的是宏观层面的职责划分和边界界定,用于管理和分解复杂系统的复杂性。它回答的是“做什么”和“边界在哪里”的问题。
-
领域与子领域
- 领域:一个组织所从事的业务范围以及其中包含的一切知识。例如,“电商领域”、“保险领域”。
- 子领域:将巨大的领域分解为更小的、可管理的问题空间。DDD强调聚焦于核心域。
- 核心域:公司的核心竞争力,业务成功的关键所在。应该投入最好的资源和技术。(例如:电商的“商品推荐系统”、Uber的“实时调度系统”)
- 支撑子域:不是核心竞争力,但对业务不可或缺,需要专门定制开发。(例如:电商的“库存管理系统”)
- 通用子域:非常通用、常见的功能,通常可以直接购买或使用现成方案。(例如:“用户身份认证”、“支付网关集成”)
-
限界上下文
- 定义:这是DDD中最重量级的概念。它是一个语义和语境的边界,在一个边界内,领域模型(例如,一个“产品”的概念)是精确的、无二义的,每个上下文有自己独立的领域模型和统一语言。
- 核心思想:同一个词(如“客户”),在不同的限界上下文中,可能代表完全不同的含义、属性和行为。
- 在销售上下文中,“客户”关注的是联系方式、购买历史。
- 在物流上下文中,“客户”关注的是收货地址、配送偏好。
- 作用:限界上下文是微服务架构的理想设计边界。每个限界上下文通常可以独立开发、部署,并对应一个微服务。
// 电商系统中的不同限界上下文示例 // 订单上下文 package order.context; public class Order { private OrderId orderId; private List<OrderItem> items; private OrderStatus status; public void confirm() { /* 订单确认逻辑 */ } } // 库存上下文 package inventory.context; public class ProductStock { private ProductId productId; private Quantity availableQuantity; public void reserve(Quantity quantity) { /* 库存预留逻辑 */ } } // 支付上下文 package payment.context; public class Payment { private PaymentId paymentId; private Money amount; private PaymentStatus status; public void process() { /* 支付处理逻辑 */ } } -
上下文映射图
- 定义:一种描述不同限界上下文之间如何集成和通信的图纸。
- 作用:它揭示了系统的集成架构,帮助团队理解彼此的工作边界和协作方式。
- 关系模式:
- 合作关系:两个团队/上下文相互依赖,共同完成一个功能。
- 客户-供应商关系:上游团队(供应商)为下游团队(客户)提供所需的东西,但供应商占主导。
- 遵奉者关系:下游团队无条件遵从上游团队的模型。
- 防腐层:一种非常重要的模式。在下游上下文创建一个隔离层,用来转换和隔离上游的模型,防止“腐败”的模型侵入自己的领域。
- 开放主机服务:上游上下文通过定义一套明确的协议(如REST API)为所有下游提供服务。
- 发布语言:与OHS配合,定义一套公共的、文档化的交换格式。
- 共享内核:两个团队共享一小部分公共的模型和代码,需要紧密协作。
2.2 战术设计
战术设计关注的是微观层面,即在单个限界上下文内部,如何通过一系列建模工具来构建一个丰富、有效的领域模型。它回答的是“如何做”的问题。
-
实体
- 定义:具有唯一标识和生命周期的对象。它的身份标识比其属性更重要。
- 特点:通过ID(而非属性)判断相等性。属性可以改变,但ID不变。
- 例子:User(用户ID)、Order(订单ID)、Product(产品SKU)。
// 实体 - 订单项 public class OrderItem { private ProductId productId; private String productName; private Money unitPrice; private Quantity quantity; public OrderItem(ProductId productId, String productName, Money unitPrice, Quantity quantity) { this.productId = productId; this.productName = productName; this.unitPrice = unitPrice; this.quantity = quantity; } public Money calculateSubTotal() { return unitPrice.multiply(quantity.getValue()); } } -
值对象
- 定义:描述一个事物的属性,但没有概念上的标识。它完全通过其属性值来定义。
- 特点:不可变、无ID、通过所有属性值判断相等性。可以替换整个对象。
- 例子:Money(金额和货币)、Address(省市区街道)、Color(RGB值)。
// 值对象 - 订单ID public class OrderId { private final String value; public OrderId(String value) { this.value = Objects.requireNonNull(value, "订单ID不能为空"); } public static OrderId generate() { return new OrderId("ORD_" + System.currentTimeMillis() + "_" + ThreadLocalRandom.current().nextInt(1000, 9999)); } public String getValue() { return value; } } -
聚合
- 定义:这是战术设计中最关键的概念。它是一组相关实体和值对象的集群,并有一个根实体作为整个聚合的入口点。
- 聚合根:聚合的“大门”,外部对象只能通过聚合根来引用聚合内的对象。它负责维护聚合内部的不变条件(业务规则)。
- 作用:聚合是数据修改和持久化的基本单位,它定义了事务和一致性的边界。
- 例子:Order(聚合根)和其下的 OrderItem(实体)。你不能直接访问 OrderItem,必须通过 Order。
// 聚合根 - 订单 public class Order extends AbstractAggregateRoot<Order> { private OrderId orderId; private CustomerId customerId; private OrderStatus status; private Money totalAmount; private List<OrderItem> items; private Address shippingAddress; private DateTime orderDate; // 私有构造函数,通过工厂创建 private Order() {} // 领域方法 public static Order create(CustomerId customerId, List<OrderItem> items, Address shippingAddress, DateTime orderDate) { Order order = new Order(); order.orderId = OrderId.generate(); order.customerId = customerId; order.items = new ArrayList<>(items); order.shippingAddress = shippingAddress; order.orderDate = orderDate; order.status = OrderStatus.CREATED; order.totalAmount = calculateTotalAmount(items); // 添加领域事件 order.registerEvent(new OrderCreatedEvent(order.orderId, customerId)); return order; } public void place() { if (this.status != OrderStatus.CREATED) { throw new IllegalOrderStateException("只能放置状态为CREATED的订单"); } this.status = OrderStatus.PLACED; registerEvent(new OrderPlacedEvent(this.orderId, this.customerId, this.totalAmount)); } private static Money calculateTotalAmount(List<OrderItem> items) { return items.stream() .map(OrderItem::calculateSubTotal) .reduce(Money.ZERO, Money::add); } // Getters public OrderId getOrderId() { return orderId; } public CustomerId getCustomerId() { return customerId; } public OrderStatus getStatus() { return status; } public Money getTotalAmount() { return totalAmount; } } -
领域服务
- 定义:当某个操作或业务逻辑不适合放在实体或值对象中时(因为它不是一个事物的内在行为),就可以将其建模为领域服务。
- 特点:是无状态的操作,实现的是领域概念本身。
- 例子:“资金转账服务”,它涉及两个账户实体和复杂的业务规则,不适合放在任何一个账户实体内部。
public interface OrderCalculationService extends DomainService { /** * 计算订单折扣 - 复杂的定价策略 */ OrderDiscount calculateDiscount(Order order, Customer customer, Promotion promotion); } @Service public class DefaultOrderCalculationService implements OrderCalculationService { private final PromotionRepository promotionRepository; private final CustomerRepository customerRepository; @Override public OrderDiscount calculateDiscount(Order order, Customer customer, Promotion promotion) { // 复杂的折扣计算逻辑 Money baseAmount = order.calculateTotal(); Money discountAmount = Money.ZERO; // 会员折扣 if (customer.isVIP()) { discountAmount = discountAmount.add(baseAmount.multiply(0.1)); // 10% VIP折扣 } // 促销活动折扣 if (promotion.isApplicable(order, customer)) { discountAmount = discountAmount.add(promotion.calculateDiscount(order)); } // 满减规则 if (baseAmount.compareTo(new Money(new BigDecimal("200"), Currency.USD)) >= 0) { discountAmount = discountAmount.add(new Money(new BigDecimal("20"), Currency.USD)); } // 确保折扣不超过订单总额 if (discountAmount.compareTo(baseAmount) > 0) { discountAmount = baseAmount; } return new OrderDiscount(discountAmount, Arrays.asList("VIP折扣", "促销折扣", "满减优惠")); } } -
领域事件
- 定义:表示在领域中发生的、对业务有重要意义的事情。它是对过去已发生事实的陈述。
- 特点:不可变、通常以过去时命名。
- 作用:用于在同一个限界上下文内部或不同限界上下文之间进行松耦合的通信,实现最终一致性。
- 例子:OrderConfirmedEvent(订单已确认事件)、PaymentReceivedEvent(支付已接收事件)。
// 订单已创建事件 public class OrderCreatedEvent extends DomainEvent { private final OrderId orderId; private final CustomerId customerId; public OrderCreatedEvent(OrderId orderId, CustomerId customerId) { super(DateTime.now()); this.orderId = orderId; this.customerId = customerId; } // getters } // 订单已放置事件 public class OrderPlacedEvent extends DomainEvent { private final OrderId orderId; private final CustomerId customerId; private final Money totalAmount; public OrderPlacedEvent(OrderId orderId, CustomerId customerId, Money totalAmount) { super(DateTime.now()); this.orderId = orderId; this.customerId = customerId; this.totalAmount = totalAmount; } // getters } // 领域事件发布接口 public interface DomainEventPublisher { void publish(DomainEvent event); } -
仓储
- 定义:一种抽象,用于管理聚合的持久化和检索。它模拟了一个“内存中的集合”。
- 作用:隔离领域模型和数据持久化技术(数据库)。领域层只依赖于 Repository 接口,而不关心其具体实现(如MySQL、MongoDB)。
- 关键点:仓储通常以聚合根为操作单位。你保存或加载的是整个聚合。
// 订单仓储 public interface OrderRepository { OrderId save(Order order); Optional<Order> findById(OrderId orderId); boolean exists(OrderId orderId); } // 客户仓储 public interface CustomerRepository { Optional<Customer> findById(CustomerId customerId); boolean exists(CustomerId customerId); } -
工厂
- 定义:当创建一个聚合或复杂对象的过程本身就很复杂或暴露了过多的内部结构时,可以使用工厂来封装这些创建逻辑。
- 作用:保持领域模型的纯洁性和封装性。
// 订单工厂 - 负责复杂订单创建逻辑 public interface OrderFactory { Order createOrder(CustomerId customerId, List<OrderItem> items, Address shippingAddress, DateTime orderDate); } // 实现 @Component public class OrderFactoryImpl implements OrderFactory { private final ProductRepository productRepository; private final PricingService pricingService; public OrderFactoryImpl(ProductRepository productRepository, PricingService pricingService) { this.productRepository = productRepository; this.pricingService = pricingService; } @Override public Order createOrder(CustomerId customerId, List<CreateOrderItem> orderItems, Address shippingAddress, DateTime orderDate) { // 转换为领域对象并验证业务规则 List<OrderItem> items = orderItems.stream() .map(this::createOrderItem) .collect(Collectors.toList()); // 调用领域模型的静态工厂方法 return Order.create(customerId, items, shippingAddress, orderDate); } private OrderItem createOrderItem(CreateOrderItem item) { // 验证产品存在性 Product product = productRepository.findById(item.getProductId()) .orElseThrow(() -> new ProductNotFoundException("产品不存在: " + item.getProductId())); // 验证库存 if (!product.isAvailable(item.getQuantity())) { throw new InsufficientStockException("产品库存不足: " + product.getProductId()); } // 获取最终价格(可能包含折扣) Money finalPrice = pricingService.calculateFinalPrice( product.getProductId(), item.getQuantity()); return new OrderItem( product.getProductId(), product.getName(), finalPrice, item.getQuantity() ); } } -
应用服务
- 定义:它不属于领域模型,但它是领域的直接客户。它负责协调领域对象来完成一个用例级别的任务。
- 特点:很薄的一层,不包含业务逻辑,只负责事务控制、安全、调用领域服务、聚合、仓储等。
- 例子:一个 PlaceOrderApplicationService 可能会调用 OrderFactory 创建订单,调用 CustomerRepository 检查客户,然后调用 OrderRepository 保存订单,最后发布 OrderPlacedEvent
// 命令对象 - 应用服务输入 public class PlaceOrderCommand { private final CustomerId customerId; private final List<CreateOrderItem> items; private final Address shippingAddress; public PlaceOrderCommand(CustomerId customerId, List<CreateOrderItem> items, Address shippingAddress) { this.customerId = Objects.requireNonNull(customerId, "客户ID不能为空"); this.items = Objects.requireNonNull(items, "订单项不能为空"); this.shippingAddress = Objects.requireNonNull(shippingAddress, "配送地址不能为空"); if (items.isEmpty()) { throw new IllegalArgumentException("订单项不能为空列表"); } } // Getters public CustomerId getCustomerId() { return customerId; } public List<CreateOrderItem> getItems() { return items; } public Address getShippingAddress() { return shippingAddress; } } // 应用服务 @Service @Transactional public class PlaceOrderApplicationService { private final OrderFactory orderFactory; private final CustomerRepository customerRepository; private final OrderRepository orderRepository; private final DomainEventPublisher eventPublisher; // 构造函数注入依赖 public PlaceOrderApplicationService(OrderFactory orderFactory, CustomerRepository customerRepository, OrderRepository orderRepository, DomainEventPublisher eventPublisher) { this.orderFactory = orderFactory; this.customerRepository = customerRepository; this.orderRepository = orderRepository; this.eventPublisher = eventPublisher; } /** * 处理下订单用例 * @param command 下订单命令 * @return 创建的订单ID */ public OrderId placeOrder(PlaceOrderCommand command) { // 1. 验证输入参数 validateCommand(command); // 2. 检查客户是否存在 CustomerId customerId = command.getCustomerId(); if (!customerRepository.exists(customerId)) { throw new CustomerNotFoundException("客户不存在: " + customerId); } // 3. 使用工厂创建订单聚合(包含复杂的创建逻辑) Order order = orderFactory.createOrder( customerId, command.getItems(), command.getShippingAddress(), DateTime.now() ); // 4. 执行业务操作 order.place(); // 5. 保存聚合 OrderId orderId = orderRepository.save(order); // 6. 发布领域事件 publishDomainEvents(order); // 7. 记录日志(可选) logOrderPlacement(order); return orderId; } /** * 验证命令参数 */ private void validateCommand(PlaceOrderCommand command) { // 应用服务级别的参数验证 if (command.getItems().size() > 20) { throw new IllegalArgumentException("单笔订单最多包含20个商品"); } // 验证配送地址格式 if (!isValidAddress(command.getShippingAddress())) { throw new IllegalArgumentException("配送地址格式不正确"); } } /** * 发布领域事件 */ private void publishDomainEvents(Order order) { // 通常框架会处理,这里展示手动处理的方式 Collection<DomainEvent> domainEvents = order.getDomainEvents(); for (DomainEvent event : domainEvents) { eventPublisher.publish(event); } order.clearDomainEvents(); } /** * 记录订单日志 */ private void logOrderPlacement(Order order) { // 这里可以使用日志框架,如SLF4J System.out.println("订单创建成功: " + order.getOrderId() + ", 客户: " + order.getCustomerId() + ", 总金额: " + order.getTotalAmount()); } private boolean isValidAddress(Address address) { // 简单的地址验证逻辑 return address != null && address.getStreet() != null && !address.getStreet().trim().isEmpty() && address.getCity() != null && !address.getCity().trim().isEmpty(); } }
三、DDD的架构模式
DDD的领域驱动设计架构模式主要分为多种,其中常见的包括四层架构、六边形架构、清洁架构和CQRS。
3.1 分层架构(经典四层架构)
架构图示:
┌─────────────────────────────────────────┐
│ 表现层 (Presentation) │ ← 用户界面、API接口
├─────────────────────────────────────────┤
│ 应用层 (Application) │ ← 用例协调、事务管理
├─────────────────────────────────────────┤
│ 领域层 (Domain) │ ← 业务逻辑、领域模型
├─────────────────────────────────────────┤
│ 基础设施层 (Infrastructure) │ ← 技术实现、持久化
└─────────────────────────────────────────┘
DDD通常采用分层架构,将系统分为以下层次:
- 用户界面层(Presentation Layer):处理用户交互和显示。
- 应用层(Application Layer):协调领域对象完成业务用例,不包含业务逻辑。
- 领域层(Domain Layer):包含领域模型,是业务逻辑的核心。
- 基础设施层(Infrastructure Layer):提供技术支持,如持久化、消息等。
依赖方向
表示层 → 应用层 → 领域层 ← 基础设施层
- 领域层是核心,不依赖任何其他层。
-
表示层
@RestController public class OrderController { private final OrderApplicationService orderAppService; // 处理HTTP请求,转换为应用层理解的命令 @PostMapping("/orders") public ResponseEntity<OrderResponse> createOrder(@RequestBody CreateOrderRequest request) { CreateOrderCommand command = OrderAssembler.toCommand(request); OrderId orderId = orderAppService.createOrder(command); return ResponseEntity.ok(new OrderResponse(orderId.getValue())); } } -
应用层
@Service @Transactional public class OrderApplicationService { private final OrderRepository orderRepository; private final DomainEventPublisher eventPublisher; public OrderId createOrder(CreateOrderCommand command) { // 协调领域对象完成业务用例 Order order = OrderFactory.create(command); OrderId orderId = orderRepository.save(order); eventPublisher.publish(new OrderCreatedEvent(orderId)); return orderId; } } -
领域层
// 聚合根 public class Order extends AbstractAggregateRoot<Order> { private OrderId orderId; private CustomerId customerId; private List<OrderItem> items; public static Order create(CustomerId customerId, List<OrderItem> items) { Order order = new Order(); order.orderId = OrderId.generate(); order.customerId = customerId; order.items = items; order.registerEvent(new OrderCreatedEvent(order.orderId)); return order; } // 核心业务逻辑 public void cancel() { // 业务规则验证 if (this.status == OrderStatus.SHIPPED) { throw new IllegalOrderOperationException("已发货订单不能取消"); } this.status = OrderStatus.CANCELLED; } } -
基础设施层
@Repository public class JpaOrderRepository implements OrderRepository { @PersistenceContext private EntityManager em; @Override public OrderId save(Order order) { em.persist(order); return order.getOrderId(); } @Override public Optional<Order> findById(OrderId orderId) { return Optional.ofNullable(em.find(Order.class, orderId)); } }
3.2 六边形架构(Hexagonal Architecture)
六边形架构强调系统的内外分离,核心领域与外部依赖通过端口和适配器交互。
架构图示:
┌─────────────────────────────────────┐
│ 领域模型 │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 应用服务 │ │ 领域服务 │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────┘
↑ ↑
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 驱动适配器 │←→ │ 端口 │←→ │ 被驱动适配器 │
│ (Controller)│ │ (Repository │ │ (数据库实现) │
└─────────────┘ └─────────────┘ └─────────────┘
-
端口定义(领域层)
// 输入端口 - 供外部调用 public interface OrderService { OrderId placeOrder(PlaceOrderCommand command); void cancelOrder(OrderId orderId); } // 输出端口 - 供基础设施层实现 public interface OrderRepository { OrderId save(Order order); Optional<Order> findById(OrderId orderId); } public interface PaymentGateway { PaymentResult processPayment(PaymentRequest request); } -
驱动适配器(输入适配器)
@RestController public class OrderController { private final OrderService orderService; @PostMapping("/orders") public ResponseEntity placeOrder(@RequestBody PlaceOrderRequest request) { PlaceOrderCommand command = toCommand(request); OrderId orderId = orderService.placeOrder(command); return ResponseEntity.ok(toResponse(orderId)); } } -
被驱动适配器(输出适配器)
@Repository public class JpaOrderRepositoryAdapter implements OrderRepository { private final JpaOrderRepository jpaRepository; private final OrderMapper mapper; @Override public OrderId save(Order order) { OrderEntity entity = mapper.toEntity(order); OrderEntity saved = jpaRepository.save(entity); return mapper.toDomain(saved).getOrderId(); } }
3.3 清洁架构(洋葱架构)
清洁架构是六边形架构的演进,强调依赖指向内层,业务核心完全独立。
架构层次:
┌─────────────────────────────────────────┐
│ 框架层 │ ← Web框架、数据库ORM等
├─────────────────────────────────────────┤
│ 接口适配器层 │ ← Controller、Repository实现
├─────────────────────────────────────────┤
│ 应用业务规则层 │ ← 用例、DTO
├─────────────────────────────────────────┤
│ 企业业务规则层 │ ← 实体、值对象、领域服务
└─────────────────────────────────────────┘
依赖规则:
- 内层不依赖外层
- 依赖方向永远指向中心
- 外层为内层提供实现
实现示例:
-
实体(最内层)
public class Order { // 纯粹的领域逻辑,无外部依赖 } -
用例(应用层)
public class PlaceOrderUseCase { private final OrderRepository orderRepository; public OrderId execute(PlaceOrderCommand command) { // 用例逻辑 Order order = Order.create(command); return orderRepository.save(order); } } -
控制器(外层)
@RestController public class OrderController { private final PlaceOrderUseCase placeOrderUseCase; @PostMapping("/orders") public ResponseEntity placeOrder(@RequestBody PlaceOrderRequest request) { PlaceOrderCommand command = toCommand(request); OrderId orderId = placeOrderUseCase.execute(command); return ResponseEntity.ok(toResponse(orderId)); } }
3.4 事件驱动架构
在DDD中,领域事件是实现限界上下文之间松耦合通信的重要方式。
架构模式:
// 领域事件定义
public class OrderPlacedEvent extends DomainEvent {
private final OrderId orderId;
private final CustomerId customerId;
private final Money totalAmount;
public OrderPlacedEvent(OrderId orderId, CustomerId customerId, Money totalAmount) {
super(Instant.now());
this.orderId = orderId;
this.customerId = customerId;
this.totalAmount = totalAmount;
}
}
// 在聚合中发布事件
public class Order {
public void place() {
this.status = OrderStatus.PLACED;
registerEvent(new OrderPlacedEvent(this.orderId, this.customerId, this.totalAmount));
}
}
// 事件处理器
@Component
public class OrderPlacedEventHandler {
private final InventoryService inventoryService;
private final NotificationService notificationService;
@EventListener
public void handle(OrderPlacedEvent event) {
// 更新库存
inventoryService.reserveItems(event.getOrderId());
// 发送通知
notificationService.sendOrderConfirmation(event.getCustomerId(), event.getOrderId());
}
}
3.5 CQRS(命令查询职责分离)
CQRS将系统的读写操作分离,适用于复杂查询场景。
架构模式:
// 命令侧 - 写操作
@Service
@Transactional
public class OrderCommandService {
private final OrderRepository orderRepository;
private final DomainEventPublisher eventPublisher;
public OrderId createOrder(CreateOrderCommand command) {
Order order = Order.create(command);
OrderId orderId = orderRepository.save(order);
eventPublisher.publishAll(order.getDomainEvents());
return orderId;
}
}
// 查询侧 - 读操作
@Service
@Transactional(readOnly = true)
public class OrderQueryService {
private final OrderReadRepository orderReadRepository;
public OrderDTO getOrderDetails(OrderId orderId) {
return orderReadRepository.findOrderDetails(orderId)
.orElseThrow(() -> new OrderNotFoundException(orderId));
}
public List<OrderSummaryDTO> getCustomerOrders(CustomerId customerId) {
return orderReadRepository.findOrderSummariesByCustomer(customerId);
}
}
// 读模型更新器(响应领域事件)
@Component
public class OrderReadModelUpdater {
private final OrderReadRepository readRepository;
@EventListener
public void onOrderPlaced(OrderPlacedEvent event) {
OrderReadModel readModel = new OrderReadModel(
event.getOrderId().getValue(),
event.getCustomerId().getValue(),
event.getTotalAmount().getValue()
);
readRepository.save(readModel);
}
}
四、DDD的实施步骤
DDD的实施步骤是一个从战略设计到战术设计,再到实现和迭代的循环过程。它强调对业务领域的深入理解,并通过模型驱动设计来构建软件。通过限界上下文划分和上下文映射,DDD可以帮助团队在复杂的业务系统中保持模型的清晰和独立。主要实施整体流程如下:

4.1 第一阶段:战略设计
战略设计阶段关注于理解业务领域,划分边界,确保团队对问题空间有一致的理解。
步骤1: 业务愿景和目标分析
- 目的: 明确项目目标和范围,确保团队与业务方对要解决的问题达成共识。
- 活动:
- 与领域专家和业务方进行会议,了解业务愿景、目标和关键需求。
- 确定项目的核心价值和高层次需求。
- 识别业务的关键流程和痛点。
步骤2: 领域划分和子领域识别
- 目的: 将巨大的业务领域划分为更小的子领域,以便分而治之。
- 活动:
- 识别核心域(Core Domain)、支撑子域(Supporting Subdomain)和通用子域(Generic Subdomain)。
- 确定团队需要重点关注的核心域,因为这里是投入最多精力的地方。
步骤3: 限界上下文划分
- 目的: 定义模型的边界,确保每个限界上下文内的模型是一致且无二义的。
- 活动:
- 通过事件风暴(Event Storming)或用例分析,识别业务过程中的关键事件、命令、聚合等。
- 根据业务职责和语言边界,划分限界上下文(Bounded Context, BC)。
- 为每个限界上下文命名,并明确其职责。
步骤4: 上下文映射图
- 目的: 描述不同限界上下文之间的集成关系,明确团队之间的协作方式。
- 活动:
- 识别限界上下文之间的关系(如合作关系、客户-供应商关系、遵奉者关系、防腐层等)。
- 绘制上下文映射图,显示BC之间的交互模式。
- 确定团队之间的契约和集成方式。
4.2 第二阶段:战术设计
在限界上下文内部,使用战术设计工具构建领域模型。
步骤5: 领域模型设计
- 目的: 在限界上下文内部创建丰富的领域模型。
- 活动:
- 识别实体(Entity)、值对象(Value Object)、聚合(Aggregate)、聚合根(Aggregate Root)。
- 定义领域服务(Domain Service)和领域事件(Domain Event)。
- 使用 ubiquitous language(统一语言)来命名模型元素,确保业务和技术人员使用相同的术语。
步骤6: 应用服务和领域服务设计
- 目的: 定义用例的协调层和领域逻辑的封装。
- 活动:
- 识别应用服务(Application Service),负责用例的协调,不包含业务逻辑。
- 识别领域服务,用于处理不适合放在实体或值对象中的领域逻辑。
- 定义领域事件的发布和订阅机制。
步骤7: 仓储设计
- 目的: 定义聚合的持久化接口,隔离领域模型和数据持久化技术。
- 活动:
- 为每个聚合根设计仓储接口(Repository)。
- 仓储接口定义在领域层,实现在基础设施层。
步骤8: 工厂设计(如需要)
- 目的: 封装复杂对象的创建逻辑。
- 活动:
- 当聚合的创建逻辑复杂时,使用工厂(Factory)来封装创建过程。
4.3 第三阶段:实现
步骤9: 项目结构划分
- 目的: 按照DDD的架构模式(如分层架构、六边形架构等)组织代码结构。
- 活动:
- 创建模块或包,对应分层架构的各层(表示层、应用层、领域层、基础设施层)或限界上下文。
- 在代码中体现限界上下文的边界,可以是模块、包或微服务。
步骤10: 编码实现领域模型
- 目的: 将领域模型转化为代码。
- 活动:
- 实现实体、值对象、聚合、领域事件等。
- 确保领域模型不依赖外部框架和技术细节。
步骤11: 实现应用层和基础设施层
- 目的: 实现用例的协调和技术的集成。
- 活动:
- 实现应用服务,调用领域模型和仓储完成用例。
- 实现基础设施层的组件,如仓储实现、消息发送等。
步骤12: 集成和测试
- 目的: 确保限界上下文之间的集成正确,并验证领域模型。
- 活动:
- 实现限界上下文之间的集成(如通过REST、消息等)。
- 编写单元测试、集成测试,特别是对核心域进行测试。
4.4 第四阶段:迭代和重构
步骤13: 持续建模和重构
- 目的: 随着业务的发展,不断调整和优化模型。
- 活动:
- 与业务方持续沟通,发现模型的不匹配之处。
- 重构代码和模型,使其更符合业务需求。
步骤14: 监控和反馈
- 目的: 确保系统运行符合预期,并收集反馈以改进。
- 活动:
- 监控系统运行情况,特别是核心域的指标。
- 收集用户反馈,调整业务需求和模型。
五、最佳实践与常见陷阱
5.1 最佳实践
// ✅ 好的实践:富领域模型
public class Order {
public void confirm() {
// 业务逻辑在领域模型中
validateCanBeConfirmed();
this.status = OrderStatus.CONFIRMED;
this.confirmedAt = Instant.now();
registerDomainEvent(new OrderConfirmedEvent(this));
}
}
// ❌ 坏的做法:贫血模型
public class Order {
// 只有getter/setter,没有行为
}
public class OrderService {
public void confirmOrder(Order order) {
// 业务逻辑在服务中,领域对象只是数据容器
if (order.getItems().isEmpty()) {
throw new ValidationException();
}
order.setStatus(OrderStatus.CONFIRMED);
}
}
5.2 常见陷阱
- 聚合设计过大 → 导致并发冲突
- 贫血领域模型 → 业务逻辑泄漏到服务层
- 过度工程化 → 简单业务使用复杂DDD
- 忽略统一语言 → 业务与技术脱节
六、DDD 与现代架构结合
6.1 DDD + 微服务
// 每个微服务对应一个限界上下文
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
// 订单服务 - 独立的限界上下文
@Service
public class OrderApplicationService {
private final OrderRepository orderRepository;
private final PaymentService paymentService; // 外部服务
@Transactional
public void confirmOrder(String orderId) {
Order order = orderRepository.findById(new OrderId(orderId));
order.confirm();
orderRepository.save(order);
// 通过事件与外部服务通信
DomainEvents.publish(new OrderConfirmedEvent(order));
}
}
6.2 DDD + 事件驱动架构
// 使用领域事件实现服务间解耦
@Component
public class OrderConfirmedEventHandler {
@EventListener
public void handleInventoryReservation(OrderConfirmedEvent event) {
// 通知库存服务预留库存
inventoryService.reserveItems(event.getOrderId(), event.getItems());
}
@EventListener
public void handleCustomerNotification(OrderConfirmedEvent event) {
// 通知客户服务发送确认邮件
notificationService.sendConfirmationEmail(event.getCustomerId(), event.getOrderId());
}
@EventListener
public void handleAnalytics(OrderConfirmedEvent event) {
// 更新分析数据
analyticsService.recordOrder(event);
}
}
七、DDD的优缺点
优点:
- 业务聚焦:使开发人员更关注业务逻辑,而不是技术实现。
- 可维护性:通过清晰的边界和模型,使代码更易于理解和修改。
- 灵活性:领域模型能够更好地应对业务变化。
- 团队协作:统一语言促进业务专家和开发人员的沟通。
缺点:
- 学习曲线:DDD概念较多,需要时间学习和实践。
- 设计复杂度:对于简单业务,使用DDD可能会过度设计。
- 实施成本:需要团队对DDD有深入理解,并且需要持续重构。
327

被折叠的 条评论
为什么被折叠?



