Skip to content
第 216 / 250 章架构⏱ 16 分钟阅读

第 216 章:DDD 与领域驱动设计

学习目标

  • 理解 DDD 核心思想
  • 掌握战略设计方法
  • 学会战术设计工具
  • 在 SpringBoot 中应用 DDD

一、DDD 简介

领域驱动设计(Domain-Driven Design)由 Eric Evans 在 2003 年提出,核心是让软件模型与业务领域对齐

1.1 为什么需要 DDD

问题DDD 解决
业务理解不一致通用语言(Ubiquitous Language)
模型与代码割裂模型即代码
业务逻辑分散聚合封装不变性
微服务拆分难限界上下文作切分依据

1.2 两层设计

二、战略设计

2.1 领域与子域

子域类型特征投入
核心域差异化竞争最多资源
支撑域必要但非核心中等资源
通用域通用功能考虑外包或 SaaS

2.2 限界上下文(Bounded Context)

业务边界,上下文内的模型有明确含义。

电商系统
├── 销售上下文(下单、订单)
├── 商品上下文(SPU、SKU)
├── 库存上下文(库位、扣减)
├── 支付上下文(交易、退款)
└── 用户上下文(账户、积分)

同一概念在不同上下文含义不同:

  • 商品 在销售上下文 = SPU 信息
  • 商品 在库存上下文 = SKU + 库位

2.3 上下文映射

模式说明
Shared Kernel共享同一子集
Customer/Supplier上下游
Conformist下游强依赖上游
ACL(防腐层)隔离外部系统
OHS(开放主机服务)定义协议供外部调用
Published Language共享交换格式(JSON/XML)

2.4 通用语言(Ubiquitous Language)

业务专家与技术团队共同使用的术语。

❌ 错误:
   业务方说"下单",开发说"创建交易单"

✅ 正确:
   双方都使用"下单"或 trade → order

三、战术设计

3.1 实体(Entity)

有唯一标识,生命周期内可变。

java
public class Order {
    @Getter
    private OrderId id;        // 唯一标识

    private Money amount;
    private OrderStatus status;

    public void pay() {
        if (status != OrderStatus.PENDING) {
            throw new IllegalStateException("订单状态不允许支付");
        }
        this.status = OrderStatus.PAID;
        registerEvent(new OrderPaidEvent(this.id));
    }
}

3.2 值对象(Value Object)

无标识,不可变,相等由属性决定。

java
public class Money {
    private final BigDecimal amount;
    private final String currency;

    public Money add(Money other) {
        if (!currency.equals(other.currency)) {
            throw new IllegalArgumentException("币种不同");
        }
        return new Money(amount.add(other.amount), currency);
    }

    // equals / hashCode 由属性计算
}

常用值对象:

  • Money(金额+币种)
  • Address(地址)
  • DateRange(日期范围)
  • Email / PhoneNumber

3.3 聚合(Aggregate)

一致性边界,根实体管理内部对象。

规则:

  • 聚合内强一致,聚合间最终一致
  • 跨聚合通过 ID 引用,不直接操作
  • 外部只能通过聚合根访问内部
java
public class Order {
    private OrderId id;
    private List<OrderItem> items;   // 内部对象
    private CustomerId customerId;  // 外部引用 ID

    public void addItem(Product product, int qty) {
        // 业务规则校验
        items.add(new OrderItem(product, qty));
    }
}

3.4 领域服务(Domain Service)

跨实体、跨聚合的业务逻辑。

java
@Service
public class PricingService {
    public Money calculatePrice(Order order, Customer customer) {
        Money base = order.itemsAmount();
        Money discount = customer.discountFor(order);
        return base.subtract(discount);
    }
}

判断标准:实体放不下时,放领域服务。

3.5 领域事件(Domain Event)

聚合内发生的业务事件,用于解耦。

java
public class OrderPaidEvent {
    private OrderId orderId;
    private Money amount;
    private Instant occurredOn;
}
java
// 聚合内发布
public class Order {
    private List<DomainEvent> events = new ArrayList<>();

    public void pay() {
        // ...
        events.add(new OrderPaidEvent(id, amount));
    }

    public List<DomainEvent> getEvents() {
        return Collections.unmodifiableList(events);
    }
}

订阅方:

java
@Component
public class InventorySubscriber {
    @EventListener
    public void on(OrderPaidEvent event) {
        inventoryService.deduct(event.orderId());
    }
}

3.6 仓储(Repository)

聚合的持久化抽象,对外隐藏实现细节。

java
public interface OrderRepository {
    Order findById(OrderId id);
    void save(Order order);
    void delete(OrderId id);
}
java
@Repository
public class OrderRepositoryImpl implements OrderRepository {

    @PersistenceContext
    private EntityManager em;

    @Override
    public Order findById(OrderId id) {
        return em.find(Order.class, id.value());
    }

    @Override
    @Transactional
    public void save(Order order) {
        // Order 是聚合根,级联保存 OrderItem
        em.merge(order);
        // 发布事件
        order.getEvents().forEach(eventPublisher::publish);
    }
}

四、分层架构

4.1 四层架构

职责
用户接口层接收请求、参数校验、返回响应
应用层用例编排、事务控制、权限
领域层业务逻辑、领域模型、领域服务
基础设施层持久化、消息、缓存等实现

4.2 SpringBoot 项目结构

src/main/java/com/example/order/
├── interfaces/              # 用户接口层
│   ├── web/                # REST Controller
│   │   └── OrderController.java
│   └── dto/                # DTO
│       └── CreateOrderRequest.java
├── application/             # 应用层
│   ├── service/
│   │   └── OrderApplicationService.java
│   └── event/
│       └── OrderEventHandler.java
├── domain/                  # 领域层
│   ├── model/              # 实体 + 值对象
│   │   ├── Order.java
│   │   ├── OrderItem.java
│   │   └── Money.java
│   ├── repository/         # 仓储接口
│   │   └── OrderRepository.java
│   ├── service/            # 领域服务
│   │   └── PricingService.java
│   └── event/              # 领域事件
│       └── OrderPaidEvent.java
└── infrastructure/         # 基础设施层
    ├── persistence/        # JPA 实现
    │   └── OrderRepositoryImpl.java
    ├── mq/                 # 消息队列
    └── config/

4.3 代码示例

Controller(接口层):

java
@RestController
@RequestMapping("/orders")
@RequiredArgsConstructor
public class OrderController {
    private final OrderApplicationService orderService;

    @PostMapping
    public Result<OrderResponse> create(@RequestBody @Valid CreateOrderRequest req) {
        OrderId id = orderService.createOrder(
            new CustomerId(req.customerId()),
            req.items()
        );
        return Result.ok(OrderResponse.of(id));
    }
}

ApplicationService(应用层):

java
@Service
@RequiredArgsConstructor
public class OrderApplicationService {
    private final OrderRepository orderRepo;
    private final PricingService pricingService;

    @Transactional
    public OrderId createOrder(CustomerId customerId, List<OrderItemReq> items) {
        Order order = Order.create(customerId);
        items.forEach(i -> order.addItem(i.productId(), i.quantity()));

        Money price = pricingService.calculatePrice(order, ...);
        order.confirm(price);

        orderRepo.save(order);
        return order.id();
    }
}

Order(领域层):

java
public class Order {
    private OrderId id;
    private CustomerId customerId;
    private List<OrderItem> items = new ArrayList<>();
    private Money totalAmount;
    private OrderStatus status;

    public static Order create(CustomerId customerId) {
        Order order = new Order();
        order.id = OrderId.generate();
        order.customerId = customerId;
        order.status = OrderStatus.PENDING;
        return order;
    }

    public void addItem(ProductId productId, int qty) {
        items.add(new OrderItem(productId, qty, ...));
    }

    public void confirm(Money totalAmount) {
        if (items.isEmpty()) {
            throw new IllegalStateException("订单必须包含商品");
        }
        this.totalAmount = totalAmount;
        this.status = OrderStatus.CONFIRMED;
        registerEvent(new OrderConfirmedEvent(id, totalAmount));
    }

    public void pay(Money paid) {
        if (!paid.equals(totalAmount)) {
            throw new IllegalArgumentException("支付金额不匹配");
        }
        this.status = OrderStatus.PAID;
        registerEvent(new OrderPaidEvent(id));
    }
}

五、聚合设计

5.1 划分原则

  1. 强一致性:必须一起修改的对象放同一聚合
  2. 业务不变性:由聚合根强制
  3. 事务边界:聚合即事务边界
  4. 不要过大:聚合越大,性能越差

5.2 案例对比

错误:一个大订单聚合,含订单 + 商品 + 配送 + 支付 + 评价

java
public class Order {
    private List<Item> items;
    private Shipment shipment;
    private Payment payment;
    private Review review;
}

正确:拆分多个聚合

java
// Order 聚合(订单基本信息)
public class Order {
    private OrderId id;
    private CustomerId customerId;
    private ShipmentId shipmentId;   // 引用 ID
    private PaymentId paymentId;     // 引用 ID
}

// 独立的 Shipment / Payment 聚合

5.3 ID 引用 vs 对象引用

java
// ❌ 跨聚合直接引用
class Order {
    private Shipment shipment;  // 直接引用对象
}

// ✅ 通过 ID 引用
class Order {
    private ShipmentId shipmentId;  // 仅引用 ID
}

六、事件风暴(Event Storming)

一种团队工作坊方法,用于领域探索。

元素颜色含义
Event橙色已发生的事
Command蓝色触发事件的命令
Aggregate黄色处理命令
Policy紫色业务规则
External System粉色外部系统
Persona黄色便签角色

七、案例:电商下单

7.1 流程

7.2 限界上下文

下单上下文(订单中心)
├── Order 聚合
└── PricingService

库存上下文(WMS)
└── Inventory 聚合

支付上下文(支付网关)
└── Payment 聚合

通过 ACL/事件 进行跨上下文协作

7.3 跨限界上下文通信

java
// 库存上下文订阅订单事件
@Component
public class OrderEventSubscriber {

    @EventListener
    public void on(OrderCreatedEvent event) {
        inventoryService.lockStock(event.orderId(), event.items());
    }
}

// 或通过消息队列异步处理
@KafkaListener(topics = "order-events")
public void handle(OrderCreatedEvent event) {
    inventoryService.lockStock(event.orderId(), event.items());
}

八、贫血模型 vs 充血模型

8.1 贫血模型(Anemic)

java
// ❌ 只有 getter/setter,业务在 Service
@Data
public class Order {
    private Long id;
    private BigDecimal amount;
    private String status;
}

@Service
public class OrderService {
    public void pay(Order order) {
        if (!"PENDING".equals(order.getStatus())) {
            throw new RuntimeException();
        }
        order.setStatus("PAID");
    }
}

8.2 充血模型(Rich)

java
// ✅ 实体含业务行为
public class Order {
    private OrderStatus status;

    public void pay() {
        if (status != OrderStatus.PENDING) {
            throw new IllegalStateException();
        }
        this.status = OrderStatus.PAID;
        registerEvent(new OrderPaidEvent(id));
    }
}

DDD 必须用充血模型。

九、常见误区

误区纠正
把 DDD 当万能解药简单 CRUD 用 DDD 是过度设计
盲目使用战术工具先战略,后战术
忽视通用语言业务与代码术语必须一致
聚合过大按业务不变性切分
把 Repository 当 DAORepository 是集合操作,非单表
Service 越来越胖把业务迁到 Entity

十、本章小结

概念作用
子域划分问题空间
限界上下文划分解决方案空间
通用语言团队沟通准则
实体有生命周期
值对象不可变描述
聚合一致性边界
领域服务跨实体逻辑
领域事件解耦通信
仓储持久化抽象

动手练习

  1. 找一个业务场景,识别核心域 / 支撑域 / 通用域
  2. 用四色贴纸做事件风暴工作坊
  3. 为"下单流程"设计聚合(明确根、边界、不变性)
  4. 用 SpringBoot 实现一个微聚合(SKU + 商品目录)

推荐阅读


下一章:第 217 章:微服务入门

本站基于 VitePress 构建 · 由 Codebook 团队维护