Skip to content

DDD(Domain-Driven Design,领域驱动设计)是复杂业务建模的方法论。传统三层架构(Controller/Service/Dao)在业务简单时够用,一旦业务复杂——规则多、状态多、对象关系复杂——就会露出"贫血模型"的问题。这篇把 DDD 的核心概念(实体/值对象/聚合/仓储)讲清楚,以及它和传统分层的本质区别。

一、解决什么问题:贫血模型的痛点

传统三层里,实体常常是"贫血"的——只有 getter/setter,没有业务逻辑,逻辑全堆在 Service:

java
// 贫血模型:实体是"哑巴",只有数据没有行为
public class Order {
    private Long id;
    private BigDecimal amount;
    private String status;
    // 只有 getter/setter,没有任何业务逻辑
}

// 业务逻辑散落在 Service 里
public class OrderService {
    public void pay(Order order) {
        if ("PENDING".equals(order.getStatus())) {   // 业务规则在 Service
            order.setStatus("PAID");
        }
    }
}

问题:业务规则散落在各个 Service 里,实体自己"管不住"自己的状态。订单能不能支付、支付后状态怎么变,这些规则本该属于"订单"自己,却写在了 Service 里。业务一复杂,Service 里全是 if/else,规则东一块西一块,改一处漏一处。

DDD 的思路:把业务逻辑放回实体,让实体"充血"、自己管自己

二、贫血 vs 充血

java
// 充血模型:实体包含业务逻辑,自己管自己的状态
public class Order {
    private Long id;
    private BigDecimal amount;
    private OrderStatus status;      // 用枚举而不是 String

    public void pay() {               // 业务逻辑在实体里
        if (status != OrderStatus.PENDING) {
            throw new IllegalStateException("订单不是待支付状态");
        }
        this.status = OrderStatus.PAID;
        // 还可以发领域事件、更新相关值对象...
    }
}

现在"订单怎么支付"的规则内聚在 Order 里,谁想支付订单都调 order.pay(),规则只有一处,改也只改一处。这就是 DDD 的核心精神:让领域对象承载业务规则,而不是让 Service 去 if/else

三、核心概念(战术设计)

DDD 有一套概念,理解它们的"身份"就不会混:

概念是什么例子
实体(Entity)有唯一标识、可变的对象订单(有 orderId)、用户
值对象(Value Object)无标识、不可变、靠属性区分金额、地址、电话号码
聚合(Aggregate)一组强相关的实体/值对象,有明确边界订单 + 订单项
聚合根(Aggregate Root)聚合的入口,外部只能通过它访问聚合内部订单(Order)
仓储(Repository)聚合的持久化接口(存/取聚合)OrderRepository
领域服务(Domain Service)跨聚合的业务逻辑转账(涉及两个账户)
领域事件(Domain Event)领域内发生的重要事件订单已支付

最容易混的两对

  1. 实体 vs 值对象:实体有"身份证"(唯一 ID,比如订单号),值对象没有("100 元"和另一个"100 元"没区别,属性相同就是同一个)。值对象要不可变,改了就是新对象。

  2. 聚合根:聚合是一个"整体",订单和它的订单项是一个聚合,订单是聚合根——外部要改订单项,必须通过订单这个入口(order.addItem()),不能直接拿订单项改。这是为了保证聚合内部的一致性。

java
// 聚合根:Order 是入口,外部只能通过它操作内部
public class Order {
    private Long id;
    private List<OrderItem> items = new ArrayList<>();   // 聚合内部

    public void addItem(Product product, int qty) {
        // 业务规则:加商品时校验、算价,都封装在聚合根里
        items.add(new OrderItem(product, qty));
        recalcAmount();
    }
    // items 不对外暴露 setter,外部改不了内部,只能走 addItem()
}

四、DDD 分层

DDD 通常搭配一种分层(和传统三层不同,更强调"领域"在中心):

接口层(Controller)→ 应用层(Application Service)→ 领域层(Domain:实体/聚合/领域服务)→ 基础设施层(Repository 实现/DB)
                                        ↑ 领域层是核心,不依赖任何框架和数据库

关键:领域层不依赖 Spring、不依赖数据库——它是纯 Java 对象,业务规则就在这。数据库、框架都是外圈,通过接口(Repository)接入。这个思想往下走就是六边形/洋葱/整洁架构(分层架构演进那条线)。

DDD 分层:领域层在中心,不依赖框架和数据库,外圈通过接口接入

五、战略设计(一句话了解)

DDD 还有"战略设计"层面,核心是限界上下文(Bounded Context)——把大系统按业务边界拆成多个"上下文",每个上下文有自己的模型(同一个"订单",在订单上下文和物流上下文里含义不同)。它解决的是"大系统怎么拆"的问题,是微服务拆分的思想来源之一。

六、什么时候用 DDD

DDD 不是银弹,别硬套:

  • 适合:业务复杂、规则多、状态流转多、需要长期演进的系统(电商、金融、交易)
  • 不适合:简单 CRUD、纯数据展示、业务规则少——硬套 DDD 只会徒增复杂度

一句话:业务简单用三层就够,业务复杂到"if/else 塞满 Service"时才上 DDD

小结

  • DDD 解决"贫血模型业务规则散落"的问题,让实体充血、自己管自己
  • 核心概念:实体(有 ID)/值对象(无 ID 不可变)/聚合根(聚合入口)/仓储(持久化接口)/领域事件
  • 分层:领域层在中心,不依赖框架和数据库
  • 战略设计:限界上下文,按业务边界拆系统
  • 复杂业务才用,简单 CRUD 别硬套

想了解 DDD 分层再往下走是什么,看《分层架构》和《Clean Architecture 整洁架构》——它们是"领域核心 + 依赖指向中心"这条线的延续。