领域驱动设计实战
领域驱动设计(DDD)是应对复杂业务的有效方法论。通过统一语言、限界上下文、聚合根等概念,可以将业务逻辑与技术实现分离,构建可维护、可扩展的系统。本文详解 DDD 落地实践,包括战略设计、战术设计、分层架构等核心内容。
一、DDD 核心概念
1.1 战略设计
mindmap
root((DDD 战略设计))
通用语言 Ubiquitous Language
业务和技术人员<br/>使用统一术语
限界上下文 Bounded Context
明确模型的<br/>适用范围和边界
子域划分
核心域<br/>核心竞争力重点投入
支撑域<br/>支持核心域自主开发
通用域<br/>通用功能可外购
上下文映射
合作关系
上下游关系
共享内核
1.2 战术设计
mindmap
root((DDD 战术设计))
实体 Entity
有唯一标识<br/>有生命周期
值对象 Value Object
无唯一标识<br/>不可变
聚合根 Aggregate Root
实体集合入口<br/>保证一致性
领域服务 Domain Service
不属于单个实体<br/>的业务逻辑
领域事件 Domain Event
领域内发生的<br/>重要事件
仓储 Repository
持久化抽象<br/>隔离基础设施
二、分层架构
2.1 标准分层
graph TB
UI[用户接口层<br/>Controller/DTO/视图] --> App[应用层<br/>应用服务/事务控制/权限校验]
App --> Domain[领域层<br/>实体/值对象/聚合根<br/>领域服务/领域事件/仓储接口]
Domain --> Infra[基础设施层<br/>数据库访问/消息队列<br/>外部服务/仓储实现]
2.2 项目结构
src/main/java/com/example/
├── interfaces/ # 用户接口层
│ ├── controller/
│ │ ├── OrderController.java
│ │ └── UserController.java
│ ├── dto/
│ │ ├── OrderDTO.java
│ │ └── UserDTO.java
│ └── vo/
│ └── ResponseVO.java
│
├── application/ # 应用层
│ ├── service/
│ │ ├── OrderAppService.java
│ │ └── UserAppService.java
│ ├── command/
│ │ ├── CreateOrderCommand.java
│ │ └── UpdateUserCommand.java
│ └── event/
│ └── OrderCreatedEvent.java
│
├── domain/ # 领域层
│ ├── model/
│ │ ├── aggregate/
│ │ │ ├── OrderAggregate.java
│ │ │ └── UserAggregate.java
│ │ ├── entity/
│ │ │ ├── Order.java
│ │ │ ├── OrderItem.java
│ │ │ └── User.java
│ │ ├── valueobject/
│ │ │ ├── Money.java
│ │ │ ├── Address.java
│ │ │ └── OrderStatus.java
│ │ └── event/
│ │ ├── OrderCreatedEvent.java
│ │ └── OrderPaidEvent.java
│ ├── service/
│ │ ├── OrderDomainService.java
│ │ └── PricingService.java
│ └── repository/
│ ├── OrderRepository.java
│ └── UserRepository.java
│
└── infrastructure/ # 基础设施层
├── persistence/
│ ├── mapper/
│ │ ├── OrderMapper.java
│ │ └── UserMapper.java
│ ├── entity/
│ │ ├── OrderPO.java
│ │ └── UserPO.java
│ └── repository/
│ ├── OrderRepositoryImpl.java
│ └── UserRepositoryImpl.java
├── messaging/
│ └── MessagePublisher.java
└── config/
└── DomainConfig.java
三、领域建模
3.1 实体设计
/**
* 订单实体(聚合根)
*/
@Entity
@Table(name = "t_order")
public class Order {
@Id
private String id;
@Embedded
private OrderNo orderNo;
private Long userId;
@Embedded
private Money totalAmount;
@Enumerated(EnumType.STRING)
private OrderStatus status;
private LocalDateTime createTime;
// 关联子实体
@OneToMany(mappedBy = "orderId", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> items = new ArrayList<>();
// 行为方法(充血模型)
public void pay() {
if (this.status != OrderStatus.UNPAID) {
throw new DomainException("只有未支付订单才能支付");
}
this.status = OrderStatus.PAID;
// 发布领域事件
DomainEventPublisher.publish(new OrderPaidEvent(this.id));
}
public void cancel() {
if (this.status != OrderStatus.UNPAID) {
throw new DomainException("只有未支付订单才能取消");
}
this.status = OrderStatus.CANCELLED;
}
public void addItem(Product product, int quantity) {
OrderItem item = new OrderItem(this.id, product, quantity);
this.items.add(item);
this.recalculateTotal();
}
private void recalculateTotal() {
Money total = Money.ZERO;
for (OrderItem item : items) {
total = total.add(item.getSubtotal());
}
this.totalAmount = total;
}
// Getter 方法
public String getId() { return id; }
public OrderNo getOrderNo() { return orderNo; }
public Long getUserId() { return userId; }
public Money getTotalAmount() { return totalAmount; }
public OrderStatus getStatus() { return status; }
public List<OrderItem> getItems() { return Collections.unmodifiableList(items); }
}
/**
* 订单明细(子实体)
*/
@Entity
@Table(name = "t_order_item")
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String orderId;
private Long productId;
private String productName;
@Embedded
private Money price;
private Integer quantity;
@Embedded
private Money subtotal;
// 构造方法
public OrderItem(String orderId, Product product, int quantity) {
this.orderId = orderId;
this.productId = product.getId();
this.productName = product.getName();
this.price = product.getPrice();
this.quantity = quantity;
this.subtotal = product.getPrice().multiply(quantity);
}
// Getter 方法
}
3.2 值对象设计
/**
* 金额值对象
*/
@Embeddable
public class Money implements Serializable {
public static final Money ZERO = new Money(BigDecimal.ZERO);
@Column(name = "amount", precision = 10, scale = 2)
private BigDecimal amount;
@Column(name = "currency", length = 3)
private String currency;
// 默认构造方法(JPA 要求)
protected Money() {}
public Money(BigDecimal amount) {
this(amount, "CNY");
}
public Money(BigDecimal amount, String currency) {
if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("金额不能为负数");
}
this.amount = amount.setScale(2, RoundingMode.HALF_UP);
this.currency = currency;
}
// 值对象方法
public Money add(Money other) {
checkCurrency(other);
return new Money(this.amount.add(other.amount), this.currency);
}
public Money subtract(Money other) {
checkCurrency(other);
return new Money(this.amount.subtract(other.amount), this.currency);
}
public Money multiply(int multiplier) {
return new Money(this.amount.multiply(new BigDecimal(multiplier)), this.currency);
}
private void checkCurrency(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("货币类型不一致");
}
}
// 值对象比较(基于属性,非 ID)
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
return Objects.equals(amount, money.amount) &&
Objects.equals(currency, money.currency);
}
@Override
public int hashCode() {
return Objects.hash(amount, currency);
}
// Getter 方法
public BigDecimal getAmount() { return amount; }
public String getCurrency() { return currency; }
}
/**
* 地址值对象
*/
@Embeddable
public class Address implements Serializable {
@Column(name = "province", length = 50)
private String province;
@Column(name = "city", length = 50)
private String city;
@Column(name = "district", length = 50)
private String district;
@Column(name = "street", length = 200)
private String street;
@Column(name = "zip_code", length = 20)
private String zipCode;
protected Address() {}
public Address(String province, String city, String district, String street, String zipCode) {
this.province = province;
this.city = city;
this.district = district;
this.street = street;
this.zipCode = zipCode;
}
// 值对象不可变,无 setter
public String getProvince() { return province; }
public String getCity() { return city; }
public String getDistrict() { return district; }
public String getStreet() { return street; }
public String getZipCode() { return zipCode; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Address address = (Address) o;
return Objects.equals(province, address.province) &&
Objects.equals(city, address.city) &&
Objects.equals(district, address.district) &&
Objects.equals(street, address.street) &&
Objects.equals(zipCode, address.zipCode);
}
@Override
public int hashCode() {
return Objects.hash(province, city, district, street, zipCode);
}
}
3.3 领域服务
/**
* 订单领域服务
* 处理跨聚合的业务逻辑
*/
@Service
public class OrderDomainService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private UserRepository userRepository;
@Autowired
private ProductRepository productRepository;
@Autowired
private InventoryService inventoryService;
/**
* 创建订单
*/
@Transactional
public Order createOrder(Long userId, List<OrderItemCommand> items) {
// 1. 验证用户
User user = userRepository.findById(userId)
.orElseThrow(() -> new DomainException("用户不存在"));
// 2. 验证商品库存
for (OrderItemCommand item : items) {
Product product = productRepository.findById(item.getProductId())
.orElseThrow(() -> new DomainException("商品不存在"));
if (!inventoryService.checkStock(product.getId(), item.getQuantity())) {
throw new DomainException("库存不足:" + product.getName());
}
}
// 3. 创建订单聚合根
Order order = new Order();
order.setId(IdGenerator.generate());
order.setOrderNo(OrderNo.generate());
order.setUserId(userId);
order.setStatus(OrderStatus.UNPAID);
order.setCreateTime(LocalDateTime.now());
// 4. 添加订单项
for (OrderItemCommand item : items) {
Product product = productRepository.findById(item.getProductId()).get();
order.addItem(product, item.getQuantity());
// 5. 扣减库存
inventoryService.deductStock(product.getId(), item.getQuantity());
}
// 6. 保存订单
orderRepository.save(order);
// 7. 发布领域事件
DomainEventPublisher.publish(new OrderCreatedEvent(order.getId()));
return order;
}
/**
* 订单支付
*/
@Transactional
public void payOrder(String orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new DomainException("订单不存在"));
// 调用聚合根行为方法
order.pay();
// 保存订单状态变更
orderRepository.save(order);
}
/**
* 取消订单
*/
@Transactional
public void cancelOrder(String orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new DomainException("订单不存在"));
// 取消订单
order.cancel();
// 恢复库存
for (OrderItem item : order.getItems()) {
inventoryService.restoreStock(item.getProductId(), item.getQuantity());
}
orderRepository.save(order);
}
}
四、仓储实现
4.1 仓储接口
/**
* 订单仓储接口(定义在领域层)
*/
public interface OrderRepository {
Order findById(String id);
Order findByOrderNo(OrderNo orderNo);
List<Order> findByUserId(Long userId);
List<Order> findByStatus(OrderStatus status);
void save(Order order);
void delete(String id);
Page<Order> findAll(Pageable pageable);
}
4.2 仓储实现
/**
* 订单仓储实现(基础设施层)
*/
@Repository
public class OrderRepositoryImpl implements OrderRepository {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderItemMapper orderItemMapper;
@Override
public Order findById(String id) {
// 查询订单主表
OrderPO orderPO = orderMapper.selectById(id);
if (orderPO == null) {
return null;
}
// 查询订单项
List<OrderItemPO> itemPOs = orderItemMapper.selectByOrderId(id);
// DO 转 Domain
return convertToDomain(orderPO, itemPOs);
}
@Override
public void save(Order order) {
// Domain 转 DO
OrderPO orderPO = convertToPO(order);
if (orderPO.getId() == null) {
// 新增
orderMapper.insert(orderPO);
// 插入订单项
for (OrderItem item : order.getItems()) {
OrderItemPO itemPO = convertItemToPO(item);
orderItemMapper.insert(itemPO);
}
} else {
// 更新
orderMapper.update(orderPO);
// 删除旧的订单项
orderItemMapper.deleteByOrderId(orderPO.getId());
// 插入新的订单项
for (OrderItem item : order.getItems()) {
OrderItemPO itemPO = convertItemToPO(item);
orderItemMapper.insert(itemPO);
}
}
}
private Order convertToDomain(OrderPO po, List<OrderItemPO> itemPOs) {
Order order = new Order();
order.setId(po.getId());
order.setOrderNo(new OrderNo(po.getOrderNo()));
order.setUserId(po.getUserId());
order.setTotalAmount(new Money(po.getTotalAmount()));
order.setStatus(OrderStatus.valueOf(po.getStatus()));
order.setCreateTime(po.getCreateTime());
for (OrderItemPO itemPO : itemPOs) {
OrderItem item = new OrderItem(
po.getId(),
itemPO.getProductId(),
itemPO.getProductName(),
new Money(itemPO.getPrice()),
itemPO.getQuantity(),
new Money(itemPO.getSubtotal())
);
order.getItems().add(item);
}
return order;
}
private OrderPO convertToPO(Order order) {
OrderPO po = new OrderPO();
po.setId(order.getId());
po.setOrderNo(order.getOrderNo().getValue());
po.setUserId(order.getUserId());
po.setTotalAmount(order.getTotalAmount().getAmount());
po.setStatus(order.getStatus().name());
po.setCreateTime(order.getCreateTime());
return po;
}
private OrderItemPO convertItemToPO(OrderItem item) {
OrderItemPO po = new OrderItemPO();
po.setOrderId(item.getOrderId());
po.setProductId(item.getProductId());
po.setProductName(item.getProductName());
po.setPrice(item.getPrice().getAmount());
po.setQuantity(item.getQuantity());
po.setSubtotal(item.getSubtotal().getAmount());
return po;
}
}
五、应用服务
5.1 应用服务设计
/**
* 订单应用服务
*/
@Service
@Validated
public class OrderAppService {
@Autowired
private OrderDomainService orderDomainService;
@Autowired
private OrderRepository orderRepository;
@Autowired
private EventPublisher eventPublisher;
/**
* 创建订单
*/
@Transactional
public OrderDTO createOrder(CreateOrderCommand command) {
// 参数校验
ValidationUtils.validate(command);
// 调用领域服务
Order order = orderDomainService.createOrder(
command.getUserId(),
command.getItems()
);
// Domain 转 DTO
return convertToDTO(order);
}
/**
* 查询订单详情
*/
@Transactional(readOnly = true)
public OrderDTO getOrderDetail(String orderId) {
Order order = orderRepository.findById(orderId);
if (order == null) {
throw new NotFoundException("订单不存在");
}
return convertToDTO(order);
}
/**
* 支付订单
*/
@Transactional
public void payOrder(String orderId) {
orderDomainService.payOrder(orderId);
}
/**
* 取消订单
*/
@Transactional
public void cancelOrder(String orderId) {
orderDomainService.cancelOrder(orderId);
}
private OrderDTO convertToDTO(Order order) {
OrderDTO dto = new OrderDTO();
dto.setId(order.getId());
dto.setOrderNo(order.getOrderNo().getValue());
dto.setUserId(order.getUserId());
dto.setTotalAmount(order.getTotalAmount().getAmount());
dto.setStatus(order.getStatus().name());
dto.setCreateTime(order.getCreateTime());
List<OrderItemDTO> itemDTOs = order.getItems().stream()
.map(this::convertItemToDTO)
.collect(Collectors.toList());
dto.setItems(itemDTOs);
return dto;
}
private OrderItemDTO convertItemToDTO(OrderItem item) {
OrderItemDTO dto = new OrderItemDTO();
dto.setProductId(item.getProductId());
dto.setProductName(item.getProductName());
dto.setPrice(item.getPrice().getAmount());
dto.setQuantity(item.getQuantity());
dto.setSubtotal(item.getSubtotal().getAmount());
return dto;
}
}
六、领域事件
6.1 事件定义
/**
* 订单创建事件
*/
public class OrderCreatedEvent implements DomainEvent {
private final String orderId;
private final Long userId;
private final LocalDateTime occurredOn;
public OrderCreatedEvent(String orderId) {
this.orderId = orderId;
this.userId = extractUserId(orderId);
this.occurredOn = LocalDateTime.now();
}
@Override
public LocalDateTime occurredOn() {
return occurredOn;
}
// Getter 方法
public String getOrderId() { return orderId; }
public Long getUserId() { return userId; }
}
/**
* 订单支付事件
*/
public class OrderPaidEvent implements DomainEvent {
private final String orderId;
private final Money amount;
private final LocalDateTime occurredOn;
public OrderPaidEvent(String orderId, Money amount) {
this.orderId = orderId;
this.amount = amount;
this.occurredOn = LocalDateTime.now();
}
@Override
public LocalDateTime occurredOn() {
return occurredOn;
}
// Getter 方法
public String getOrderId() { return orderId; }
public Money getAmount() { return amount; }
}
6.2 事件发布与订阅
/**
* 领域事件发布器
*/
@Component
public class DomainEventPublisher {
@Autowired
private ApplicationEventPublisher publisher;
public static void publish(DomainEvent event) {
// Spring 事件发布
applicationContext.publishEvent(event);
}
}
/**
* 订单创建事件处理器
*/
@Component
public class OrderCreatedEventHandler {
@Autowired
private InventoryService inventoryService;
@Autowired
private NotificationService notificationService;
@Autowired
private MessageQueueService mqService;
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handle(OrderCreatedEvent event) {
log.info("处理订单创建事件:orderId={}", event.getOrderId());
// 1. 扣减库存(本地事务已处理,这里是补偿)
// inventoryService.confirmDeduct(event.getOrderId());
// 2. 发送通知
notificationService.sendOrderCreated(event.getOrderId());
// 3. 发送到消息队列(供其他系统消费)
mqService.send("order_created", event);
}
}
/**
* 订单支付事件处理器
*/
@Component
public class OrderPaidEventHandler {
@Autowired
private ShippingService shippingService;
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handle(OrderPaidEvent event) {
log.info("处理订单支付事件:orderId={}", event.getOrderId());
// 1. 创建发货单
shippingService.createShippingOrder(event.getOrderId());
// 2. 发送支付成功通知
notificationService.sendPaymentSuccess(event.getOrderId());
}
}
七、总结
7.1 核心要点
- 战略设计:限界上下文、子域划分、通用语言
- 战术设计:实体、值对象、聚合根、领域服务
- 分层架构:接口层、应用层、领域层、基础设施层
- 充血模型:业务逻辑放在实体中
- 仓储模式:隔离持久化细节
- 领域事件:解耦业务流程
7.2 实施建议
mindmap
root((DDD 实施建议))
推荐做法
从核心域开始实施
保持聚合根小而精
值对象不可变
仓储只负责持久化
领域层不依赖基础设施
避免做法
贫血模型
大聚合
领域层依赖数据库
应用层包含业务逻辑
过度设计
DDD 不是银弹,适合复杂业务场景。对于简单 CRUD 系统,传统分层架构可能更合适。