码森林 - AI 赋以翼,赋 AI 以魂

领域驱动设计(DDD)如何落地

2025/12/03
11
0

DDD 概述

领域驱动设计(Domain-Driven Design, DDD)是一种软件设计方法,强调将业务领域放在软件设计的中心,通过统一语言(Ubiquitous Language)让技术团队和业务团队进行有效沟通。

为什么要使用 DDD?

传统分层架构的问题:

Controller → Service → DAO → Database
     ↓         ↓       ↓
 事务脚本  业务逻辑混乱  数据访问
​
❌ 问题:
- Service 层变成大杂烩,逻辑混乱
- 难以理解业务含义
- 代码重用困难
- 扩展困难

DDD 架构的优势:

   限界上下文 A        限界上下文 B
  ┌─────────────┐    ┌─────────────┐
  │ Controller  │    │ Controller  │
  ├─────────────┤    ├─────────────┤
  │ 应用服务    │    │ 应用服务    │
  ├─────────────┤    ├─────────────┤
  │ 领域模型    │    │ 领域模型    │
  │ 领域服务    │    │ 领域服务    │
  ├─────────────┤    ├─────────────┤
  │ 仓储        │    │ 仓储        │
  └─────────────┘    └─────────────┘
​
✅ 优势:
- 明确的业务边界
- 代码组织清晰
- 易于扩展和维护
- 便于团队协作

核心概念

1. 统一语言(Ubiquitous Language)

定义: 业务专家和开发人员共同使用的、精确的、无歧义的语言。

示例(订单系统):

业务概念

统一语言

代码中的体现

卖方创建商品

发布商品

product.publish()

买方选择商品

加入购物车

cart.addItem(product)

买方提交

生成订单

order = Order.create(cart)

支付完成

订单确认

order.confirm(payment)

卖方发货

订单履行

order.deliver()

如何建立统一语言:

  1. 业务专家和开发人员定期对齐

  2. 用业务术语命名代码

  3. 在文档和代码中保持一致

  4. 避免技术黑话

// ❌ 不好:用技术术语
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);
    }
}

2. 限界上下文(Bounded Context)

定义: 明确的业务边界,每个上下文有自己的统一语言和模型。

示例(电商系统):

┌────────────────────────────────────────────────────────┐
│                    电商系统                             │
├────────────────────────────────────────────────────────┤
│                                                        │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐ │
│  │ 商户管理域   │  │ 订单域       │  │ 用户域       │ │
│  │ (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;
}
​
// 两个模型在各自域中都是正确的

3. 聚合根(Aggregate Root)

定义: 聚合根是限界上下文中的关键实体,是聚合的入口,负责保持聚合内部的一致性。

特征:

  • 有唯一标识

  • 对外暴露统一接口

  • 聚合内的修改都通过聚合根进行

  • 只能通过聚合根访问聚合内的对象

示例:

@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;
}

聚合根的选择原则:

候选对象

是否是聚合根

原因

Order

✅ 是

订单是订单系统的核心,其他对象依附于它

Customer

✅ 是

客户是独立的业务概念

OrderItem

❌ 不是

订单项只有在订单的上下文中才有意义

Payment

❌ 不是

支付信息依附于订单


4. 值对象(Value Object)

定义: 没有唯一标识,只关心属性值,不可变的对象。

特征:

  • 无唯一标识(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;  // 安全,因为不会被修改

5. 实体(Entity)

定义: 有唯一标识且可变的对象。

特征:

  • 有唯一标识(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);
    }
}

6. 仓储(Repository)

定义: 伪装成集合的持久化机制,聚合根通过仓储实现持久化。

原则:

  • 一个聚合根对应一个仓储

  • 仓储接口在领域层,实现在基础设施层

  • 仓储通过聚合根的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);
    }
}

7. 领域服务(Domain Service)

定义: 处理跨越多个聚合根的业务逻辑的无状态服务。

何时使用领域服务:

  • 业务逻辑跨越多个聚合根

  • 业务逻辑不属于任何一个聚合根

  • 需要多个仓储的协调

示例:

@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);
    }
}

8. 应用服务(Application Service)

定义: 处理用例的协调者,不包含业务逻辑,只协调领域对象和基础设施。

应用服务 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);
    }
}

9. 领域事件(Domain Event)

定义: 表示领域中发生的重要事实的不可变对象。

特征:

  • 代表已发生的事情

  • 使用过去时命名

  • 携带上下文信息

  • 发布给其他限界上下文

示例:

@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. 识别限界上下文

方法1:按业务能力划分

电商系统
├── 商户管理:企业、员工、角色
├── 门店管理:门店、桌位、营业时间
├── 商品管理:商品、规格、属性
├── 订单管理:订单、订单项
├── 用户管理:用户、地址、钱包
├── 活动管理:活动、优惠、红包
├── 财务管理:财务交易、退款
└── 支付管理:支付、结算

方法2:按组织结构划分

组织结构
├── 商户部门 → 商户管理域
├── 门店部门 → 门店管理域
├── 商品部门 → 商品管理域
├── 订单部门 → 订单管理域
└── 财务部门 → 财务管理域

方法3:按数据流划分

用户下单流程
用户域 → 商品域 → 订单域 → 支付域 → 财务域

2. 限界上下文的通信

同步通信(使用接口):

// 订单域需要调用商品域的价格
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);
    }
}

3. 防腐层(Anti-Corruption Layer)

当需要集成外部系统或遗留系统时,使用防腐层隔离。

// 外部支付系统的接口(不能改)
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())
        );
    }
}

战术设计

战术设计关注于限界上下文内部的代码组织和实现模式。

1. 标准分层架构

┌─────────────────────────────────────────────────┐
│              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 (缓存)                            │  │
│  └──────────────────────────────────────────┘  │
└─────────────────────────────────────────────────┘

2. 代码组织示例

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

代码组织

1. 聚合根的设计模式

@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);
    }
}

2. 值对象的设计

@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

3. 仓储接口定义

// 领域层 - 仓储接口定义
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());
    }
    
    // ... 其他方法
}

4. 应用服务实现

@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 项目)

项目背景

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);
    }
}

常见陷阱

陷阱 1:贫血模型(Anemic Model)

错误做法

// 只有 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);
    }
}

陷阱 2:聚合设计过大

错误做法

@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);
}

陷阱 3:仓储设计过于通用

错误做法

// 通用的仓储,什么都能查
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);
}

陷阱 4:跨聚合根直接修改

错误做法

// 直接修改其他聚合根的内部状态
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);
    }
}

陷阱 5:事件没有真正发布

错误做法

// 事件只是聚合根的属性,从未发布
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 落地的关键要点:

  1. 战略设计先行 - 清晰的限界上下文和统一语言是基础

  2. 聚合根设计 - 聚合根应该是业务核心,包含关键业务逻辑

  3. 值对象使用 - 通过值对象表达领域概念,确保不可变性

  4. 接口驱动 - 仓储、服务等应该面向接口设计

  5. 事件驱动 - 通过领域事件解耦聚合根之间的依赖

  6. 分层清晰 - 应用服务协调,领域服务实现业务逻辑

  7. 测试优先 - 富血模型更容易进行单元测试

DDD 不是一蹴而就的,而是在实践中不断优化的过程。重要的是保持对业务本质的关注,用代码表达业务意图。