领域驱动设计(Domain-Driven Design, DDD)是一种软件设计方法,强调将业务领域放在软件设计的中心,通过统一语言(Ubiquitous Language)让技术团队和业务团队进行有效沟通。
传统分层架构的问题:
Controller → Service → DAO → Database
↓ ↓ ↓
事务脚本 业务逻辑混乱 数据访问
❌ 问题:
- Service 层变成大杂烩,逻辑混乱
- 难以理解业务含义
- 代码重用困难
- 扩展困难DDD 架构的优势:
限界上下文 A 限界上下文 B
┌─────────────┐ ┌─────────────┐
│ Controller │ │ Controller │
├─────────────┤ ├─────────────┤
│ 应用服务 │ │ 应用服务 │
├─────────────┤ ├─────────────┤
│ 领域模型 │ │ 领域模型 │
│ 领域服务 │ │ 领域服务 │
├─────────────┤ ├─────────────┤
│ 仓储 │ │ 仓储 │
└─────────────┘ └─────────────┘
✅ 优势:
- 明确的业务边界
- 代码组织清晰
- 易于扩展和维护
- 便于团队协作定义: 业务专家和开发人员共同使用的、精确的、无歧义的语言。
示例(订单系统):
如何建立统一语言:
业务专家和开发人员定期对齐
用业务术语命名代码
在文档和代码中保持一致
避免技术黑话
// ❌ 不好:用技术术语
public class OrderProcessor {
public void process(OrderDTO dto) {
OrderEntity entity = mapper.toEntity(dto);
entityManager.persist(entity);
}
}
// ✅ 好:用业务术语
public class OrderApplicationService {
public void createOrder(CreateOrderCommand command) {
Order order = Order.create(command.getCustomerId(), command.getItems());
orderRepository.save(order);
}
}定义: 明确的业务边界,每个上下文有自己的统一语言和模型。
示例(电商系统):
┌────────────────────────────────────────────────────────┐
│ 电商系统 │
├────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 商户管理域 │ │ 订单域 │ │ 用户域 │ │
│ │ (Merchant) │ │ (Order) │ │ (Customer) │ │
│ │ │ │ │ │ │ │
│ │ Company │ │ Order │ │ User │ │
│ │ BusinessUser │ │ OrderItem │ │ Address │ │
│ │ BusinessRole │ │ OrderPayment │ │ Wallet │ │
│ │ │ │ │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 门店管理域 │ │ 商品域 │ │ 活动域 │ │
│ │ (Store) │ │ (Product) │ │ (Activity) │ │
│ │ │ │ │ │ │ │
│ │ StoreInfo │ │ Goods │ │ Activity │ │
│ │ StoreTable │ │ GoodsGroup │ │ Discount │ │
│ │ StoreDelivery│ │ GoodsSpec │ │ RedPacket │ │
│ │ │ │ │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└────────────────────────────────────────────────────────┘每个限界上下文的模型可以不同:
// 在订单域中,Product 只是一个值对象
@Value
public class Product {
private Long productId;
private String name;
private BigDecimal price;
}
// 在商品域中,Product 是一个聚合根
@Data
@Builder
public class Goods {
private Long goodsId;
private String goodsName;
private List<GoodsSpec> specs;
private List<GoodsAttribute> attributes;
}
// 两个模型在各自域中都是正确的定义: 聚合根是限界上下文中的关键实体,是聚合的入口,负责保持聚合内部的一致性。
特征:
有唯一标识
对外暴露统一接口
聚合内的修改都通过聚合根进行
只能通过聚合根访问聚合内的对象
示例:
@Data
@Builder
public class Order { // 聚合根
private Long orderId; // 唯一标识
private Long customerId;
private List<OrderItem> items; // 聚合内的实体
private OrderPayment payment; // 聚合内的实体
private OrderStatus status;
// 聚合根保证内部一致性
public void addItem(OrderItem item) {
if (this.status != OrderStatus.PENDING) {
throw new IllegalStateException("订单已确认,不能添加商品");
}
this.items.add(item);
}
public void confirm(Payment payment) {
if (this.items.isEmpty()) {
throw new IllegalStateException("订单不能为空");
}
this.payment = new OrderPayment(payment);
this.status = OrderStatus.CONFIRMED;
}
// 只能通过聚合根访问内部对象
public List<OrderItem> getItems() {
return Collections.unmodifiableList(this.items);
}
}
@Data
public class OrderItem { // 聚合内的实体
private Long itemId;
private Long productId;
private Integer quantity;
private BigDecimal price;
}
@Data
public class OrderPayment { // 聚合内的实体
private Long paymentId;
private BigDecimal amount;
private PaymentMethod method;
private LocalDateTime paymentTime;
}聚合根的选择原则:
定义: 没有唯一标识,只关心属性值,不可变的对象。
特征:
无唯一标识(ID)
不可变性(immutable)
通过值比较(equals/hashCode)
可以被安全地共享
示例:
@Value // Lombok @Value 自动使其不可变
public class Money {
private BigDecimal amount;
private String currency;
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("货币不一致");
}
return new Money(
this.amount.add(other.amount),
this.currency
);
}
}
@Value
public class AddressInfo {
private String province;
private String city;
private String district;
private String street;
public boolean isComplete() {
return province != null && city != null &&
district != null && street != null;
}
}
// 使用
Order order = Order.builder()
.totalAmount(new Money(BigDecimal.valueOf(100), "CNY"))
.shippingAddress(new AddressInfo("北京", "朝阳", "建国路", "123号"))
.build();
// 值对象可以安全地共享(因为不可变)
Money originalPrice = new Money(BigDecimal.valueOf(100), "CNY");
Money discountPrice = originalPrice; // 安全,因为不会被修改定义: 有唯一标识且可变的对象。
特征:
有唯一标识(ID)
可变(mutable)
生命周期长
通过标识比较
示例:
@Data
@Builder
public class User { // 实体
private Long userId; // 唯一标识
private String name;
private String email;
private LocalDateTime createTime;
private LocalDateTime updateTime;
// 实体的 equals 基于 ID
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return Objects.equals(userId, user.userId);
}
@Override
public int hashCode() {
return Objects.hash(userId);
}
}定义: 伪装成集合的持久化机制,聚合根通过仓储实现持久化。
原则:
一个聚合根对应一个仓储
仓储接口在领域层,实现在基础设施层
仓储通过聚合根的ID查询
示例:
// 领域层 - 接口定义
public interface OrderRepository {
void save(Order order);
Optional<Order> findById(Long orderId);
List<Order> findByCustomerId(Long customerId);
void delete(Long orderId);
}
// 基础设施层 - 实现
@Repository
public class OrderRepositoryImpl implements OrderRepository {
@Autowired
private OrderMapper orderMapper;
@Override
public void save(Order order) {
OrderDO orderDO = OrderConverter.toDO(order);
orderMapper.insertOrUpdate(orderDO);
}
@Override
public Optional<Order> findById(Long orderId) {
OrderDO orderDO = orderMapper.selectById(orderId);
return Optional.ofNullable(orderDO)
.map(OrderConverter::toDomain);
}
}定义: 处理跨越多个聚合根的业务逻辑的无状态服务。
何时使用领域服务:
业务逻辑跨越多个聚合根
业务逻辑不属于任何一个聚合根
需要多个仓储的协调
示例:
@Slf4j
public class OrderDomainService {
private final OrderRepository orderRepository;
private final InventoryRepository inventoryRepository;
private final PricingService pricingService;
// 跨越 Order 和 Inventory 两个聚合根的业务逻辑
public void confirmOrder(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException(orderId));
// 检查库存
for (OrderItem item : order.getItems()) {
Inventory inventory = inventoryRepository
.findByProductId(item.getProductId())
.orElseThrow();
if (inventory.getAvailableQuantity() < item.getQuantity()) {
throw new InsufficientInventoryException(item.getProductId());
}
}
// 扣减库存
for (OrderItem item : order.getItems()) {
Inventory inventory = inventoryRepository
.findByProductId(item.getProductId())
.orElseThrow();
inventory.reserve(item.getQuantity());
inventoryRepository.save(inventory);
}
// 确认订单
order.confirm();
orderRepository.save(order);
log.info("订单已确认: {}", orderId);
}
}定义: 处理用例的协调者,不包含业务逻辑,只协调领域对象和基础设施。
应用服务 vs 领域服务:
示例:
@Slf4j
@Service
@Transactional
public class OrderApplicationService {
private final OrderRepository orderRepository;
private final OrderDomainService orderDomainService;
private final PaymentGateway paymentGateway;
private final EventPublisher eventPublisher;
public OrderDTO createOrder(CreateOrderCommand command) {
log.info("收到创建订单命令: {}", command);
// 1. 创建聚合根
Order order = Order.create(
command.getCustomerId(),
command.getItems(),
command.getDeliveryAddress()
);
// 2. 保存
orderRepository.save(order);
// 3. 发布事件
eventPublisher.publish(new OrderCreatedEvent(order.getOrderId()));
log.info("订单创建成功: {}", order.getOrderId());
return OrderConverter.toDTO(order);
}
public OrderDTO confirmOrder(ConfirmOrderCommand command) {
log.info("收到确认订单命令: {}", command);
// 1. 获取聚合根
Order order = orderRepository.findById(command.getOrderId())
.orElseThrow(() -> new OrderNotFoundException(command.getOrderId()));
// 2. 调用领域服务(跨聚合根的业务逻辑)
orderDomainService.confirmOrder(command.getOrderId());
// 3. 调用外部服务
PaymentResult paymentResult = paymentGateway.pay(
order.getTotalAmount(),
command.getPaymentMethod()
);
// 4. 更新聚合根
order.recordPayment(paymentResult);
orderRepository.save(order);
// 5. 发布事件
eventPublisher.publish(new OrderConfirmedEvent(order.getOrderId()));
return OrderConverter.toDTO(order);
}
}定义: 表示领域中发生的重要事实的不可变对象。
特征:
代表已发生的事情
使用过去时命名
携带上下文信息
发布给其他限界上下文
示例:
@Value // 不可变
public class OrderCreatedEvent {
private final Long orderId;
private final Long customerId;
private final LocalDateTime createdAt;
private final List<OrderItemDTO> items;
public OrderCreatedEvent(Long orderId, Long customerId,
LocalDateTime createdAt, List<OrderItemDTO> items) {
this.orderId = orderId;
this.customerId = customerId;
this.createdAt = createdAt;
this.items = items;
}
}
@Value
public class OrderConfirmedEvent {
private final Long orderId;
private final BigDecimal totalAmount;
private final LocalDateTime confirmedAt;
}
@Value
public class OrderDeliveredEvent {
private final Long orderId;
private final String trackingNumber;
private final LocalDateTime deliveredAt;
}
// 发布事件
public class Order {
private List<DomainEvent> events = new ArrayList<>();
public void confirm() {
this.status = OrderStatus.CONFIRMED;
this.events.add(new OrderConfirmedEvent(
this.orderId,
this.totalAmount,
LocalDateTime.now()
));
}
public List<DomainEvent> getDomainEvents() {
return Collections.unmodifiableList(this.events);
}
}战略设计关注于整体架构和限界上下文的划分。
方法1:按业务能力划分
电商系统
├── 商户管理:企业、员工、角色
├── 门店管理:门店、桌位、营业时间
├── 商品管理:商品、规格、属性
├── 订单管理:订单、订单项
├── 用户管理:用户、地址、钱包
├── 活动管理:活动、优惠、红包
├── 财务管理:财务交易、退款
└── 支付管理:支付、结算方法2:按组织结构划分
组织结构
├── 商户部门 → 商户管理域
├── 门店部门 → 门店管理域
├── 商品部门 → 商品管理域
├── 订单部门 → 订单管理域
└── 财务部门 → 财务管理域方法3:按数据流划分
用户下单流程
用户域 → 商品域 → 订单域 → 支付域 → 财务域同步通信(使用接口):
// 订单域需要调用商品域的价格
public interface ProductService {
BigDecimal getPrice(Long productId);
}
// 订单应用服务
public void createOrder(CreateOrderCommand command) {
// 同步调用
BigDecimal price = productService.getPrice(command.getProductId());
Order order = Order.create(command.getCustomerId(), price);
}异步通信(使用事件):
// 订单域发布事件
public class Order {
public void confirm() {
this.status = OrderStatus.CONFIRMED;
this.events.add(new OrderConfirmedEvent(this.orderId));
}
}
// 财务域订阅事件
@Component
public class OrderConfirmedEventHandler {
@EventListener
public void handle(OrderConfirmedEvent event) {
// 生成财务记录
FinancialOrder financialOrder = new FinancialOrder(event.getOrderId());
financialOrderRepository.save(financialOrder);
}
}当需要集成外部系统或遗留系统时,使用防腐层隔离。
// 外部支付系统的接口(不能改)
public class ExternalPaymentAPI {
public PaymentResponse pay(PaymentRequest request) {
// 外部系统的复杂接口
}
}
// 防腐层:将外部接口转换为自己的接口
@Component
public class PaymentAdapter implements PaymentGateway {
@Autowired
private ExternalPaymentAPI externalAPI;
@Override
public PaymentResult pay(BigDecimal amount, PaymentMethod method) {
// 将自己的接口转换为外部系统的接口
PaymentRequest externalRequest = new PaymentRequest();
externalRequest.setAmount(amount.doubleValue());
externalRequest.setMethod(method.getExternalCode());
// 调用外部系统
PaymentResponse externalResponse = externalAPI.pay(externalRequest);
// 将外部的响应转换为自己的模型
return new PaymentResult(
externalResponse.getTransactionId(),
PaymentStatus.valueOf(externalResponse.getStatus())
);
}
}战术设计关注于限界上下文内部的代码组织和实现模式。
┌─────────────────────────────────────────────────┐
│ Controller 层 │
│ 处理 HTTP 请求/响应 │
└────────────────────┬────────────────────────────┘
│
┌─────────────────────▼────────────────────────────┐
│ Application Service 层 │
│ 处理用例、事务、权限检查 │
└────────────────────┬────────────────────────────┘
│
┌─────────────────────▼────────────────────────────┐
│ Domain 层(核心) │
│ ┌──────────────────────────────────────────┐ │
│ │ Aggregate Root (聚合根) │ │
│ │ Entity (实体) │ │
│ │ Value Object (值对象) │ │
│ │ Domain Service (领域服务) │ │
│ │ Domain Event (领域事件) │ │
│ │ Repository Interface (仓储接口) │ │
│ └──────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────┘
│
┌─────────────────────▼────────────────────────────┐
│ Infrastructure 层 │
│ ┌──────────────────────────────────────────┐ │
│ │ Repository Implementation (仓储实现) │ │
│ │ Database Access (数据库访问) │ │
│ │ External Service Call (外部服务调用) │ │
│ │ Message Queue (消息队列) │ │
│ │ Cache (缓存) │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘src/main/java/
├── controller/ # 控制层
│ └── OrderController.java
│
├── application/ # 应用层
│ ├── service/
│ │ └── OrderApplicationService.java
│ ├── dto/
│ │ ├── CreateOrderCommand.java
│ │ ├── ConfirmOrderCommand.java
│ │ └── OrderDTO.java
│ └── assembler/
│ └── OrderAssembler.java
│
├── domain/ # 领域层(核心)
│ ├── order/ # 订单限界上下文
│ │ ├── aggregate/
│ │ │ └── Order.java # 聚合根
│ │ ├── entity/
│ │ │ ├── OrderItem.java
│ │ │ └── OrderPayment.java
│ │ ├── valueobject/
│ │ │ ├── OrderStatus.java
│ │ │ ├── OrderNumber.java
│ │ │ └── Money.java
│ │ ├── repository/
│ │ │ └── OrderRepository.java # 接口定义
│ │ ├── service/
│ │ │ └── OrderDomainService.java
│ │ └── event/
│ │ ├── OrderCreatedEvent.java
│ │ ├── OrderConfirmedEvent.java
│ │ └── OrderDeliveredEvent.java
│ │
│ └── customer/ # 客户限界上下文
│ ├── aggregate/
│ │ └── Customer.java
│ ├── repository/
│ │ └── CustomerRepository.java
│ └── service/
│ └── CustomerDomainService.java
│
└── infrastructure/ # 基础设施层
├── persistence/
│ ├── mapper/
│ │ └── OrderMapper.java # MyBatis Mapper
│ ├── dataobject/
│ │ └── OrderDO.java # 数据库对象
│ └── repository/
│ └── OrderRepositoryImpl.java # 仓储实现
├── gateway/
│ └── PaymentGatewayImpl.java
├── converter/
│ └── OrderConverter.java # DO ↔ Domain 转换
└── event/
└── OrderEventPublisher.java@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Order {
// === 聚合根标识 ===
private Long orderId;
// === 关键属性 ===
private Long customerId;
private OrderStatus status;
private BigDecimal totalAmount;
private LocalDateTime createdAt;
private LocalDateTime confirmedAt;
// === 聚合内的实体(私有) ===
private List<OrderItem> items;
private OrderPayment payment;
// === 领域事件 ===
@Getter(AccessLevel.PACKAGE)
private List<DomainEvent> domainEvents = new ArrayList<>();
// === 工厂方法 ===
public static Order create(Long customerId, List<OrderItemDTO> items,
AddressInfo address) {
if (items.isEmpty()) {
throw new InvalidOrderException("订单不能为空");
}
Order order = Order.builder()
.customerId(customerId)
.status(OrderStatus.PENDING)
.items(items.stream()
.map(item -> new OrderItem(item.getProductId(),
item.getQuantity(),
item.getPrice()))
.collect(Collectors.toList()))
.totalAmount(items.stream()
.map(item -> item.getPrice().multiply(new BigDecimal(item.getQuantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add))
.createdAt(LocalDateTime.now())
.build();
// 发布创建事件
order.addDomainEvent(new OrderCreatedEvent(
order.orderId, order.customerId, order.createdAt, items
));
return order;
}
// === 业务方法(只能通过聚合根修改) ===
public void addItem(OrderItem item) {
if (this.status != OrderStatus.PENDING) {
throw new InvalidOrderException("订单已确认,不能添加商品");
}
this.items.add(item);
this.totalAmount = this.totalAmount.add(
item.getPrice().multiply(new BigDecimal(item.getQuantity()))
);
}
public void confirm(PaymentResult paymentResult) {
if (this.status != OrderStatus.PENDING) {
throw new InvalidOrderException("订单已确认");
}
this.payment = new OrderPayment(
paymentResult.getTransactionId(),
paymentResult.getAmount(),
paymentResult.getPaymentMethod()
);
this.status = OrderStatus.CONFIRMED;
this.confirmedAt = LocalDateTime.now();
// 发布确认事件
this.addDomainEvent(new OrderConfirmedEvent(
this.orderId, this.totalAmount, this.confirmedAt
));
}
// === 内部方法 ===
private void addDomainEvent(DomainEvent event) {
this.domainEvents.add(event);
}
// === 清空事件(发布后调用) ===
public void clearDomainEvents() {
this.domainEvents.clear();
}
// === 不可变访问 ===
public List<OrderItem> getItems() {
return Collections.unmodifiableList(this.items);
}
}@Value // Lombok,自动使其不可变
@Builder
public class Money {
private BigDecimal amount;
private Currency currency;
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new CurrencyMismatchException();
}
return Money.builder()
.amount(this.amount.add(other.amount))
.currency(this.currency)
.build();
}
public Money subtract(Money other) {
if (!this.currency.equals(other.currency)) {
throw new CurrencyMismatchException();
}
return Money.builder()
.amount(this.amount.subtract(other.amount))
.currency(this.currency)
.build();
}
public Money multiply(BigDecimal factor) {
return Money.builder()
.amount(this.amount.multiply(factor))
.currency(this.currency)
.build();
}
public boolean isGreaterThan(Money other) {
if (!this.currency.equals(other.currency)) {
throw new CurrencyMismatchException();
}
return this.amount.compareTo(other.amount) > 0;
}
}
// 使用
Money price = new Money(BigDecimal.valueOf(100), Currency.CNY);
Money discount = new Money(BigDecimal.valueOf(10), Currency.CNY);
Money finalPrice = price.subtract(discount); // 100 - 10 = 90// 领域层 - 仓储接口定义
public interface OrderRepository {
// CURD 操作
void save(Order order);
Optional<Order> findById(Long orderId);
void delete(Long orderId);
// 聚合根 ID 查询
List<Order> findByCustomerId(Long customerId);
List<Order> findByStatus(OrderStatus status);
// 聚合统计
long countByStatus(OrderStatus status);
BigDecimal sumAmountByStatus(OrderStatus status);
}
// 基础设施层 - 仓储实现
@Repository
@Slf4j
public class OrderRepositoryImpl implements OrderRepository {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderItemMapper orderItemMapper;
@Override
public void save(Order order) {
OrderDO orderDO = OrderConverter.toDO(order);
if (orderDO.getId() == null) {
orderMapper.insert(orderDO);
} else {
orderMapper.update(orderDO);
}
// 保存关联的订单项
orderItemMapper.deleteByOrderId(orderDO.getId());
for (OrderItem item : order.getItems()) {
OrderItemDO itemDO = OrderConverter.toItemDO(item, orderDO.getId());
orderItemMapper.insert(itemDO);
}
log.info("订单已保存: {}", order.getOrderId());
}
@Override
public Optional<Order> findById(Long orderId) {
OrderDO orderDO = orderMapper.selectById(orderId);
if (orderDO == null) {
return Optional.empty();
}
List<OrderItemDO> itemDOs = orderItemMapper.selectByOrderId(orderId);
return Optional.of(OrderConverter.toDomain(orderDO, itemDOs));
}
@Override
public List<Order> findByCustomerId(Long customerId) {
List<OrderDO> orderDOs = orderMapper.selectByCustomerId(customerId);
return orderDOs.stream()
.map(orderDO -> {
List<OrderItemDO> itemDOs = orderItemMapper.selectByOrderId(orderDO.getId());
return OrderConverter.toDomain(orderDO, itemDOs);
})
.collect(Collectors.toList());
}
// ... 其他方法
}@Slf4j
@Service
@Transactional
public class OrderApplicationService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private OrderDomainService orderDomainService;
@Autowired
private PaymentGateway paymentGateway;
@Autowired
private ApplicationEventPublisher eventPublisher;
/**
* 创建订单用例
*/
public OrderDTO createOrder(CreateOrderCommand command) {
log.info("创建订单: 客户={}, 商品数={}",
command.getCustomerId(), command.getItems().size());
// 1. 创建聚合根
Order order = Order.create(
command.getCustomerId(),
command.getItems(),
command.getDeliveryAddress()
);
// 2. 持久化
orderRepository.save(order);
// 3. 发布事件(异步处理)
for (DomainEvent event : order.getDomainEvents()) {
eventPublisher.publishEvent(event);
}
log.info("订单创建成功: {}", order.getOrderId());
return OrderAssembler.toDTO(order);
}
/**
* 确认订单用例
*/
public OrderDTO confirmOrder(ConfirmOrderCommand command) {
log.info("确认订单: {}", command.getOrderId());
// 1. 获取聚合根
Order order = orderRepository.findById(command.getOrderId())
.orElseThrow(() -> new OrderNotFoundException(command.getOrderId()));
// 2. 调用领域服务(包含业务逻辑)
orderDomainService.confirmOrder(command.getOrderId());
// 3. 调用外部服务
PaymentResult paymentResult = paymentGateway.pay(
order.getTotalAmount(),
command.getPaymentMethod()
);
// 4. 重新加载聚合根(确保最新状态)
order = orderRepository.findById(command.getOrderId()).orElseThrow();
// 5. 更新聚合根
order.confirm(paymentResult);
orderRepository.save(order);
// 6. 发布事件
for (DomainEvent event : order.getDomainEvents()) {
eventPublisher.publishEvent(event);
}
log.info("订单已确认: {}", order.getOrderId());
return OrderAssembler.toDTO(order);
}
}Fangzhu 是一个电商 SaaS 平台,支持多商户、多门店、多商品类型。
Fangzhu 系统
├── 商户管理域 (Merchant Bounded Context)
│ ├── Company(企业聚合根)
│ ├── BusinessUser(员工)
│ ├── BusinessRole(角色)
│ └── 业务规则:企业启用需要许可证验证
│
├── 门店管理域 (Store Bounded Context)
│ ├── StoreInfo(门店聚合根)
│ ├── StoreTable(桌位)
│ ├── StoreDelivery(配送配置)
│ └── 业务规则:门店需要属于某个企业
│
├── 商品管理域 (Product Bounded Context)
│ ├── HallGoods/TakeGoods(商品聚合根)
│ ├── GoodsGroup(商品分组)
│ ├── GoodsSpec(商品规格)
│ └── 业务规则:商品库存管理
│
├── 订单管理域 (Order Bounded Context)
│ ├── BusinessOrder(订单聚合根)
│ ├── OrderItem(订单项)
│ ├── OrderPayment(支付信息)
│ └── 业务规则:订单金额计算、状态流转
│
├── 活动管理域 (Activity Bounded Context)
│ ├── ActivityGoods(商品活动聚合根)
│ ├── ActivityFullDecrease(满减活动聚合根)
│ ├── ActivityRedPacket(红包活动聚合根)
│ └── 业务规则:活动优惠计算
│
├── 用户管理域 (Customer Bounded Context)
│ ├── CustomerUser(用户聚合根)
│ ├── CustomerAddress(地址)
│ ├── CustomerWallet(钱包)
│ └── 业务规则:钱包余额管理
│
├── 财务管理域 (Financial Bounded Context)
│ ├── BusinessFinancialOrder(商户财务聚合根)
│ ├── FinancialOrderRefund(退款)
│ └── 业务规则:财务记录、结算
│
└── 支付管理域 (Payment Bounded Context)
└── 业务规则:支付宝、微信支付集成聚合根 - Company
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Company {
private Long companyId;
private CompanyRegisterInfo registerInfo;
private AddressInfo addressInfo;
private LicenseInfo licenseInfo;
private CompanyStatus status;
private Boolean deleted;
private ProductOpenInfo appOpenInfo;
private ProductOpenInfo walletOpenInfo;
@ToString.Exclude
private List<BusinessUser> userList;
@ToString.Exclude
private List<BusinessRole> roleList;
// 业务方法
public void enable() {
if (!registerInfo.isComplete() || !licenseInfo.isValid()) {
throw new IllegalStateException("信息不完整或许可证无效");
}
this.status = CompanyStatus.ENABLED;
}
public boolean isEnabled() {
return status == CompanyStatus.ENABLED && !deleted;
}
}领域服务 - MerchantDomainService
@Slf4j
public class MerchantDomainService {
private final CompanyRepository companyRepository;
private final BusinessUserRepository businessUserRepository;
public Company enableCompany(Company company) {
// 验证注册信息
if (!company.getRegisterInfo().isComplete()) {
throw new IllegalStateException("企业注册信息不完整");
}
// 验证许可证
if (!company.getLicenseInfo().isValid()) {
throw new IllegalStateException("许可证未验证或已过期");
}
// 验证主账号存在
BusinessUser rootUser = businessUserRepository
.findRootByCompanyId(company.getCompanyId())
.orElseThrow(() -> new IllegalStateException("必须有主账号"));
// 启用企业
company.enable();
return companyRepository.save(company);
}
public BusinessUser addBusinessUser(Long companyId, BusinessUser user) {
// 检查登录账号重复
if (businessUserRepository.existsByLoginAccount(user.getLoginAccount())) {
throw new IllegalStateException("登录账号已存在");
}
user.setCompanyId(companyId);
return businessUserRepository.save(user);
}
}应用服务 - MerchantApplicationService
@Slf4j
@Service
@Transactional
public class MerchantApplicationService {
@Autowired
private CompanyRepository companyRepository;
@Autowired
private MerchantDomainService merchantDomainService;
@Autowired
private ApplicationEventPublisher eventPublisher;
public CompanyDTO enableCompany(EnableCompanyCommand command) {
log.info("启用企业: {}", command.getCompanyId());
Company company = companyRepository.findById(command.getCompanyId())
.orElseThrow();
Company enabledCompany = merchantDomainService.enableCompany(company);
// 发布事件
eventPublisher.publishEvent(
new CompanyEnabledEvent(enabledCompany.getCompanyId())
);
return CompanyAssembler.toDTO(enabledCompany);
}
}❌ 错误做法
// 只有 getter/setter 的贫血模型
@Data
public class Order {
private Long orderId;
private Long customerId;
private BigDecimal totalAmount;
private OrderStatus status;
}
// 所有业务逻辑都在 Service 中
@Service
public class OrderService {
public void confirm(Long orderId) {
Order order = orderRepository.findById(orderId).get();
order.setStatus(OrderStatus.CONFIRMED); // 直接修改状态
orderRepository.save(order);
}
}✅ 正确做法
// 富血模型,包含业务逻辑
@Data
@Builder
public class Order {
private Long orderId;
private Long customerId;
private BigDecimal totalAmount;
private OrderStatus status;
// 业务方法
public void confirm() {
if (this.status != OrderStatus.PENDING) {
throw new IllegalStateException("订单已确认");
}
this.status = OrderStatus.CONFIRMED;
}
}
// Service 只负责协调
@Service
public class OrderApplicationService {
public void confirm(Long orderId) {
Order order = orderRepository.findById(orderId).get();
order.confirm(); // 调用业务方法
orderRepository.save(order);
}
}❌ 错误做法
@Data
public class Order {
private Long orderId;
private List<OrderItem> items;
private List<OrderPayment> payments;
private List<OrderRefund> refunds;
private List<OrderLog> logs;
private List<OrderEvaluation> evaluations;
// ... 更多聚合内容
}✅ 正确做法
// 细粒度的聚合根
@Data
public class Order {
private Long orderId;
private List<OrderItem> items;
private OrderPayment payment;
// 保持聚合简小、内聚
}
// 其他内容通过仓储独立管理
public interface OrderRefundRepository {
List<OrderRefund> findByOrderId(Long orderId);
}❌ 错误做法
// 通用的仓储,什么都能查
public interface GenericRepository<T> {
List<T> findAll();
List<T> findByPredicate(Predicate<T> predicate);
T findOne(Long id);
void save(T entity);
}✅ 正确做法
// 针对聚合根的专用仓储
public interface OrderRepository {
void save(Order order);
Optional<Order> findById(Long orderId);
List<Order> findByCustomerId(Long customerId);
List<Order> findByStatus(OrderStatus status);
}❌ 错误做法
// 直接修改其他聚合根的内部状态
Order order = orderRepository.findById(1L).get();
Inventory inventory = inventoryRepository.findById(1L).get();
// 直接修改库存
inventory.quantity = inventory.quantity - order.items.size();✅ 正确做法
// 通过聚合根的业务方法
public class OrderDomainService {
public void confirmOrder(Long orderId) {
Order order = orderRepository.findById(orderId).get();
for (OrderItem item : order.getItems()) {
Inventory inventory = inventoryRepository
.findByProductId(item.getProductId()).get();
// 调用聚合根的业务方法
inventory.reserve(item.getQuantity());
inventoryRepository.save(inventory);
}
order.confirm();
orderRepository.save(order);
}
}❌ 错误做法
// 事件只是聚合根的属性,从未发布
public class Order {
private List<DomainEvent> events = new ArrayList<>();
public void confirm() {
this.status = OrderStatus.CONFIRMED;
this.events.add(new OrderConfirmedEvent(this.orderId));
// 事件被创建了,但没有人处理它
}
}✅ 正确做法
// 应用服务负责发布事件
@Service
@Transactional
public class OrderApplicationService {
public void confirmOrder(ConfirmOrderCommand command) {
Order order = orderRepository.findById(command.getOrderId()).get();
order.confirm();
orderRepository.save(order);
// 发布所有事件
for (DomainEvent event : order.getDomainEvents()) {
eventPublisher.publishEvent(event);
}
order.clearDomainEvents();
}
}与业务团队进行事件风暴,识别核心概念
确定统一语言词汇表
绘制限界上下文地图
确定各限界上下文的主要聚合根
设计限界上下文间的通信方式
为每个聚合根设计实体和值对象
定义仓储接口
设计领域服务接口
规划领域事件
确定应用服务的用例
实现聚合根(包含业务方法)
实现值对象(确保不可变性)
实现仓储接口
实现领域服务
实现应用服务
实现仓储实现类
设计 DO(数据对象)和转换器
实现数据库映射(MyBatis/JPA)
实现事件发布
实现事件订阅处理
单元测试(聚合根、值对象、领域服务)
集成测试(应用服务)
端到端测试(用例)
性能测试和优化
文档完善
聚合根是否包含了关键业务逻辑?
值对象是否真正不可变?
仓储接口是否清晰?
领域服务是否无状态?
应用服务是否只做协调?
事件是否被真正处理?
是否避免了贫血模型?
是否存在跨聚合根的直接引用?
DDD 落地的关键要点:
战略设计先行 - 清晰的限界上下文和统一语言是基础
聚合根设计 - 聚合根应该是业务核心,包含关键业务逻辑
值对象使用 - 通过值对象表达领域概念,确保不可变性
接口驱动 - 仓储、服务等应该面向接口设计
事件驱动 - 通过领域事件解耦聚合根之间的依赖
分层清晰 - 应用服务协调,领域服务实现业务逻辑
测试优先 - 富血模型更容易进行单元测试
DDD 不是一蹴而就的,而是在实践中不断优化的过程。重要的是保持对业务本质的关注,用代码表达业务意图。