DDD 领域模型
领域驱动设计(Domain-driven design)是一种通过将实现连接到持续进化的模型来满足复杂需求的软件开发方法。
DDD 核心思想是通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。
领域驱动设计是一种由域模型来驱动着系统设计的思想。
- 一种解决复杂系统的建模与分析方法
- 将业务概念和规则转为对象和对象的属性行为
- 通过封装、继承和多态降低业务复杂性
- 不是数据库表结构的映射,而是对业务逻辑、规则、流程的建模。
- 包含实体、值对象、聚合、领域服务、领域事件等元素。
举个例子:在一个电商系统中,“订单”不是一个简单的 Order 表,而是一个包含下单、支付、取消、发货等行为和状态流转逻辑的复杂对象。
五层架构
原则:每层只能与位于其下方的层发生耦合。
分层架构可以简单分为两种,即严格分层架构和松散分层架构。在严格分层架构中,某层只能与位于其直接下方的层发生耦合,而在松散分层架构中,则允许某层与它的任意下方层发生耦合。
-
领域层domain(核心业务层)
- 领域对象:实体对象、值对象、聚合、聚合根 (充血模型:业务方法在实体对象里,负责维护实体自身的生命周期和状态)
- 领域服务:接口服务,业务的具体实现(围绕实体):商品服务、电子围栏、订单服务、运单服务、
- 领域仓储:持久化的细节隐藏,减少领域层对基础设施层的依赖(需要使用到基础设施层,包括DB、Feign调用等)
-
应用层(业务逻辑层)
- 应用服务:调用和组合领域层的服务,不包含任何业务逻辑(简单除外),只是组合模型业务(类似模块化)
- 数据转换:接口参数DTO的简单校验,以及DTO和实体值对象的数据转换
- 事件订阅:管理多个消息主体
- 消息订阅:多个微服务间协作解耦的异步实现方式
-
基础设施层
- 数据库、缓存、消息队列、配置
- 对外隐藏技术实现细节,提供粗粒度的数据输出服务
- 数据库操作:领域层传递的是数据对象,在这里可以按数据表的实现方式进行拆分实现
- 防腐层(外部API):隔离业务,防止污染业务代码,可通过代理或适配器实现转换
-
用户接口层(表现层)
- 面向服务间API调用,WEB接口
- 身份认证和权限验证
- 限流和熔断服务
- VO和DTO数据转换
简单查询不涉及业务,是可以直接从应用层穿透到PO查询,不需要经过domain层
DTO是不能存在于domain层的,DDD设计不认为DTO是业务对象,entity才是
完整Github项目
-
DDD 分层模型:
用户接口层(Interface / API 层):提供 REST 接口,接收外部请求。
应用层(Application 层):编排领域服务,处理用例逻辑,如“创建订单”。
领域层(Domain 层):包含核心业务逻辑,如 Order 聚合根、领域事件、状态机、仓储接口等。
基础设施层(Infrastructure 层):实现仓储接口、防腐层、外部服务调用、事件发布等。
-
防腐层(Anti-Corruption Layer, ACL):
用于与外部系统(如库存服务)交互,避免领域模型被外部污染。
在基础设施层实现,通过 ACL 将外部模型转换为内部领域模型。
-
事件与状态机:
使用领域事件(如 OrderCreatedEvent, OrderPaidEvent)表达领域内发生的重要事情。
使用状态机(如 OrderStateMachine)管理订单状态的合法流转(如:待支付 -> 已支付 -> 已发货 / 已取消)。
代码结构展示
├── src/
│ └── main/
│ ├── java/
│ │ └── com/example/order/
│ │ ├── OrderApplication.java
│ │ │
│ │ ├── application/ # 应用层(Service)
│ │ │
│ │ ├── domain/ # 领域层(核心)
│ │ │ ├── model/ # 聚合根、实体、值对象
│ │ │ ├── event/ # 领域事件
│ │ │ ├── repository/ # 领域仓库接口
│ │ │ ├── service/ # 领域服务
│ │ │ └── statemachine/ # 状态机配置
│ │ │
│ │ ├── infrastructure/ # 基础设施层
│ │ │ ├── persistence/ # 持久化实现
│ │ │ ├── messaging/ # 消息发布(事件)
│ │ │ └── integration/ # 外部系统集成(含防腐层)
│ │ │
│ │ └── interfaces/ # 接口层(Controller)
订单管理建模
核心对象:
实体与值对象定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class Money { private BigDecimal amount; private String currency;
public Money(BigDecimal amount, String currency) { if (amount.compareTo(BigDecimal.ZERO) < 0) { throw new BusinessRuleViolation("金额不能为负"); } this.amount = amount; this.currency = currency; }
public Money add(Money other) { if (!this.currency.equals(other.currency)) { throw new BusinessRuleViolation("货币不一致"); } return new Money(this.amount.add(other.amount), this.currency); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class OrderItem { private String productId; private String productName; private Money unitPrice; private int quantity; private Money totalPrice;
public OrderItem(String productId, String productName, Money unitPrice, int quantity) { this.productId = productId; this.productName = productName; this.unitPrice = unitPrice; this.quantity = quantity; this.totalPrice = unitPrice.multiply(quantity); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| public class Order { private String orderId; private String customerId; private List<OrderItem> items = new ArrayList<>(); private Address shippingAddress; private Money totalAmount; private OrderStatus status; private DateTime createdAt; private List<DomainEvent> events = new ArrayList<>();
private Order() {}
public static Order createFromCart( String orderId, String customerId, List<CartItem> cartItems, Address address) {
Order order = new Order(); order.orderId = orderId; order.customerId = customerId; order.shippingAddress = address; order.createdAt = LocalDateTime.now(); order.status = OrderStatus.PENDING;
for (CartItem item : cartItems) { ProductPrice price = ProductService.getCurrentPrice(item.getProductId()); order.addItem(new OrderItem( item.getProductId(), item.getProductName(), price.getAmount(), item.getQuantity() )); }
order.calculateTotal(); return order; }
private void addItem(OrderItem item) { if (status != OrderStatus.PENDING) { throw new BusinessRuleViolation("订单已锁定,无法添加商品"); } items.add(item); }
private void calculateTotal() { this.totalAmount = items.stream() .map(OrderItem::getTotalPrice) .reduce(Money.ZERO, Money::add); }
public void confirmPayment() { if (status != OrderStatus.PENDING) { throw new BusinessRuleViolation("订单状态异常,无法确认支付"); } this.status = OrderStatus.PAID; DomainEventPublisher.publish(new OrderPaidEvent(orderId, totalAmount)); }
public void cancel() { if (status == OrderStatus.SHIPPED) { throw new BusinessRuleViolation("已发货订单不能取消"); } if (status == OrderStatus.CANCELLED) { return; } this.status = OrderStatus.CANCELLED; DomainEventPublisher.publish(new OrderCancelledEvent(orderId, customerId)); }
public void ship() { if (status != OrderStatus.PAID) { throw new BusinessRuleViolation("订单未支付,不能发货"); } this.status = OrderStatus.SHIPPED; DomainEventPublisher.publish(new OrderShippedEvent(orderId)); }
private void addEvent(DomainEvent event) { this.events.add(event); } }
|
1 2 3 4 5 6
| public enum OrderStatus { PENDING, PAID, SHIPPED, CANCELLED }
|
领域事件(Events)
领域事件可通过事件总线异步通知库存、物流、用户积分等系统。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class OrderPaidEvent implements DomainEvent { private String orderId; private Money amount; private LocalDateTime occurredAt;
public OrderPaidEvent(String orderId, Money amount) { this.orderId = orderId; this.amount = amount; this.occurredAt = LocalDateTime.now(); } }
public class OrderCancelledEvent implements DomainEvent { private String orderId; private String customerId;
public OrderCancelledEvent(String orderId, String customerId) { this.orderId = orderId; this.customerId = customerId; } }
public class OrderShippedEvent implements DomainEvent { private String orderId;
public OrderShippedEvent(String orderId) { this.orderId = orderId; } }
|
领域服务(Service)
处理跨聚合或跨上下文的复杂逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Service public class OrderFulfillmentService {
private InventoryService inventoryService; private PaymentQueryService paymentQueryService;
@Transactional public void handleOrderPaymentConfirmed(String orderId) { Order order = orderRepository.findById(orderId);
if (!paymentQueryService.isPaymentValid(orderId)) { throw new BusinessRuleViolation("支付未验证通过"); }
boolean locked = inventoryService.reserveInventory(order.getItems()); if (!locked) { throw new BusinessRuleViolation("库存不足"); }
order.confirmPayment(); orderRepository.save(order); } }
|
仓储接口(Repository)
定义在领域层,实现放在基础设施层:
1 2 3 4 5 6
| public interface OrderRepository { Order findById(String orderId); void save(Order order); List<Order> findByCustomer(String customerId); }
|
工厂封装(Factory)
封装复杂创建逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class OrderFactory {
public static Order createFromCart( String orderId, Customer customer, ShoppingCart cart, Address address) {
if (!customer.isActive()) { throw new BusinessRuleViolation("用户被禁用"); }
if (cart.getItems().isEmpty()) { throw new BusinessRuleViolation("购物车为空"); }
return Order.createFromCart(orderId, customer.getId(), cart.getItems(), address); } }
|
事件驱动的订单状态机设计
-
订单的状态流转不再由“硬编码 if-else”控制,而是通过 监听领域事件 触发状态变更。
-
使用 状态机(State Machine) 模型来管理 OrderStatus 的合法转换。
-
所有状态变更由 领域事件驱动,保证逻辑集中、可追溯、可扩展。
| 当前状态 → 新状态 |
触发事件 |
是否允许 |
| PENDING → PAID |
OrderPaidEvent |
是 |
| PENDING → CANCELLED |
OrderCancelledEvent |
是 |
| PAID → SHIPPED |
OrderShippedEvent |
是 |
| PAID → CANCELLED |
OrderRefundedEvent |
是(退款后取消) |
状态机实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| @Component public class OrderStateMachine {
private final Map<OrderStatus, List<OrderStatus>> transitions = Map.of( OrderStatus.PENDING, Arrays.asList(OrderStatus.PAID, OrderStatus.CANCELLED), OrderStatus.PAID, Arrays.asList(OrderStatus.SHIPPED, OrderStatus.CANCELLED) );
public boolean canTransition(OrderStatus from, OrderStatus to) { if (from == null || to == null) return false; List<OrderStatus> allowed = transitions.getOrDefault(from, Collections.emptyList()); return allowed.contains(to); }
public void applyEvent(Order order, DomainEvent event) { OrderStatus newStatus = null;
if (event instanceof OrderPaidEvent) { newStatus = OrderStatus.PAID; } else if (event instanceof OrderShippedEvent) { newStatus = OrderStatus.SHIPPED; } else if (event instanceof OrderCancelledEvent || event instanceof OrderRefundedEvent) { newStatus = OrderStatus.CANCELLED; }
if (newStatus != null) { if (canTransition(order.getStatus(), newStatus)) { order.setStatusInternal(newStatus); } else { throw new BusinessRuleViolation( "非法状态转移: " + order.getStatus() + " → " + newStatus); } } } }
|
事件处理器(Event Handlers)
使用 Spring 的事件机制或自定义事件总线监听领域事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Component @RequiredArgsConstructor public class OrderEventHandlers {
private final OrderRepository orderRepository; private final OrderStateMachine stateMachine;
@EventListener public void handle(OrderPaidEvent event) { Order order = orderRepository.findById(event.getOrderId()); stateMachine.applyEvent(order, event); orderRepository.save(order); }
@EventListener public void handle(OrderShippedEvent event) { Order order = orderRepository.findById(event.getOrderId()); stateMachine.applyEvent(order, event); orderRepository.save(order); }
@EventListener public void handle(OrderCancelledEvent event) { Order order = orderRepository.findById(event.getOrderId()); stateMachine.applyEvent(order, event); orderRepository.save(order); } }
|
支持事件溯源
1 2 3 4 5 6 7 8
| public Order reconstituteFromHistory(String orderId, List<DomainEvent> events) { Order order = new Order(); for (DomainEvent event : events) { event.applyTo(order); } return order; }
|
防腐层(支付、库存)
在限界上下文之间建立翻译与隔离层,防止外部概念“侵蚀”本领域的纯洁性。 外部系统(如支付系统、库存系统)
外部系统(支付宝)
↓ HTTP 回调
[ 支付回调 Controller ]
↓
[ AlipayAclAdapter ] → 验签 + 校验
↓
[ PaymentEventMapper ] → 映射为 OrderPaidEvent
↓
[ Domain Event Bus ]
├──→ [OrderEventHandlers] → 触发 Order 状态机
└──→ [OrderStateMachine] → 更新订单状态
↓
[OrderRepository] → 数据持久化
-
外部支付回调 DTO(来自支付宝)
1 2 3 4 5
| public class AlipayCallback { private String tradeNo; private String outTradeNo; private String tradeStatus; }
|
-
防腐层适配器(ACL Adapter)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Component public class AlipayAclAdapter {
private final PaymentEventMapper eventMapper; private final DomainEventPublisher eventPublisher;
public void handleCallback(AlipayCallback callback) { if (!verifySignature(callback)) { throw new SecurityException("非法回调"); }
DomainEvent domainEvent = eventMapper.toDomainEvent(callback);
if (domainEvent != null) { eventPublisher.publish(domainEvent); } }
private boolean verifySignature(AlipayCallback callback) { return AlipaySignature.rsaCheckV2(...) == true; } }
|
-
事件映射器(Mapping Logic)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Component public class PaymentEventMapper {
public DomainEvent toDomainEvent(AlipayCallback callback) { String orderId = callback.getOutTradeNo();
switch (callback.getTradeStatus()) { case "TRADE_SUCCESS": case "TRADE_FINISHED": return new OrderPaidEvent(orderId, Money.of(999, "CNY")); case "TRADE_CLOSED": return new OrderRefundedEvent(orderId); default: return null; } } }
|
应用层(Service)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| @Service @Transactional public class OrderService {
@Autowired private OrderRepository orderRepository;
@Autowired private PaymentService paymentService;
@Autowired private DomainEventPublisher eventPublisher;
public OrderId createOrder(CreateOrderCommand command) { Order order = new Order(command.getOrderId(), command.getAmount()); order.create(); orderRepository.save(order);
order.getEvents().forEach(eventPublisher::publish); return order.getId(); }
public void payOrder(PayOrderCommand command) { Order order = orderRepository.findById(command.getOrderId()) .orElseThrow(() -> new BusinessException("Order not found"));
PaymentResult result = paymentService.processPayment( new PaymentRequest(order.getAmount(), command.getCardToken()) );
if (result.getStatus() == PaymentStatus.SUCCESS) { order.pay(); orderRepository.save(order); order.getEvents().forEach(eventPublisher::publish); } else { throw new BusinessException("Payment failed"); } } }
|
接口层(Controller)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @RestController @RequestMapping("/api/orders") public class OrderController {
@Autowired private OrderService orderService;
@PostMapping public ResponseEntity<String> createOrder(@RequestBody OrderRequestDTO dto) { OrderId id = orderService.createOrder(new CreateOrderCommand(dto.getAmount())); return ResponseEntity.ok(id.getValue()); }
@PostMapping("/{orderId}/pay") public ResponseEntity<String> payOrder(@PathVariable String orderId, @RequestBody PaymentRequestDTO dto) { orderService.payOrder(new PayOrderCommand(new OrderId(orderId), dto.getCardToken())); return ResponseEntity.ok("Paid"); } }
|