DDD 的概念

可能一听 DDD,很多人都有点懵圈:MVC 都没弄明白,怎么来了个 DDD?没错,DDD 是一种软件设计方法,提供了用切割工程模型的各类技巧,如:领域、界限上下文、实体、值对象、聚合、工厂、仓储等(这一堆名词下面会逐一进行解释)。

下面开始详细讲解 DDD。

1. DDD 的概念

1.1. 充血模型

充血模型,是指将对象的属性与其相关的行为逻辑封装在同一个类中,形成一个内聚性更强的实体。常见的做法包括:在该对象内部实现与其自身相关的功能,例如数据校验、缓存键(Key)的生成,以及不依赖外部服务调用的业务逻辑处理等。

图片

  • 采用充血模型的方式,能让对象在被使用时,自然携带与其职责紧密相关的一系列行为方法。这样一来,开发者无需在外部重复编写同类逻辑——所有与该对象相关的操作,都已内聚在其内部,调用起来更直观、逻辑也更统一。
  • 值得注意的是,充血模型不应被狭义地理解为 “仅在一个类中塞入方法”。它的思想可以延伸到更宏观的架构层面。例如,一个功能模块的包结构本身也可以是 “充血” 的:该包内不仅包含核心的领域模型,还聚合了支撑该模块业务所需的仓储(Repository)、模型(Model)、**服务(Service)**等组件,形成一个高内聚、自包含的单元。
  • 此外,在具体实现中,我们常借助内部类来组织复杂业务。比如 “用户实名认证” 这一概念,可能涉及通信验证、实名卡信息、银行卡绑定、四要素校验等多个子维度。将这些相关数据和逻辑封装为用户类(User)下的内部子类,不仅能清晰表达它们的归属关系,还能显著提升代码的可读性与可维护性——开发者一眼就能看出哪些功能属于哪个上下文,后续迭代也更加顺畅。

1.2. 领域模型

领域模型是对特定业务领域中核心规则、策略与流程的高度抽象与封装。在设计实践中,通常借助事件风暴(Event Storming)等方法识别业务边界,划分出清晰的限界上下文(Bounded Context)unded Context)。其关键转变在于:摒弃传统 “大而全” 的 Service + 贫血数据模型 架构,转而将系统拆解为多个等方法识别业务边界,划分出清晰的**。每个限界上下文内部,围绕自身业务职责,独立定义完整的领域构件。每个领域内创建自身所属的:等方法识别业务边界,划分出清晰的、高内聚、低耦合的领域模块、领域对象(实体、聚合、值对象)、仓储服务 (DAO 操作)(调用外部接口的手段)等。

那么,现在这里有几个概念:工厂、端口适配器 Port、领域服务、领域对象、仓储定义。我们先来看他们是怎么从贫血模型演变过来的,再细分讲解每个概念。

图片

  • 在传统的 事件消息 开发模式中,业务逻辑几乎全部集中在 Service 层,数据对象仅作为 “数据容器” 存在,缺乏行为封装。Service 负责串联各个功能模块,频繁调用 DAO、缓存、RPC 等基础设施,导致代码高度耦合、逻辑散落。这种模式本质上偏离了面向对象设计原则——对象没有行为,只有过程式调用,开发过程往往止步于 “详细设计”,难以体现业务语义。
  • 换到端口适配器下,现在我们以一个领域功能为聚合,拆分一个领域内所需的 Service 为Service + 贫血模型,VO、Req、Res 重新设计为充血模型,DAO、Redis 等持久化操作为领域服务等。举例:一套账户服务中的,授信认证、开户、提额降额等,每一个都是一个独立的领域,在每个独立的领域内,创建自身领域所需的各项信息。
  • 领域对象还有一个特点,它自身只关注业务功能实现,不与外部任何接口和服务直连。如:不会直接调用 DAO 操作库,也不会调用缓存操作 Redis,更不会直接引入 RPC 连接其他微服务。而是通过仓储和领域模型,定义调用外部数据的含有出入参对象的接口标准,让基础设施层做具体的调用实现——通过这样的方式让领域只关心业务实现,同时做好防腐。

1.3. 实体、聚合、值对象

在仓库的开发实践中,方法的入参和出参往往缺乏明确的业务语义。常见做法是多个服务共享同一个 VO(Value Object)类,只要能 “把字段传进来” 即可,很少考虑该对象是否真正代表某个业务概念。这种 “字段搬运工” 式的参数设计,不仅模糊了职责边界,也削弱了代码的可读性与可维护性。

而在端口适配器CEHOLDER}充血模型贫血模型}充血模型**。从的中,DDD(领域驱动设计)的充血模型CEHOLDERmrumrbuf24j5b896}充血模型**。从领域对象本身就是业务语义的载体,整个建模过程始终围绕 “对象应该表达什么业务含义” 展开,而非仅仅传递数据。

DDD(领域驱动设计)的充血模型CEHOLDERmrumrbufezc2peoc}充血模型**、的、到,三者位于每个领域下的领域对象内,服务于领域内的领域服务。三个对象定义具体如下:

图片

  1. 胳膊、腿、身体,都是事件风暴,和这个人生命有关。
  2. 帽子、手套、裤衩,都是四色建模,是这个人穿戴的,不具有生命属性。
  3. 实体,就是要写库一个事务,一次形成,可以聚合。把胳膊、腿、身体,放一块,一起让数据库执行事务。

1.3.1. 实体

是依托于持久化层数据(PO)以领域服务功能目标为指导设计的领域对象。聚合,而值对象。如:用户 PO 持久化对象,会涵盖,用户的开户实体、授信实体、额度实体对象。也包括如商品下单时候的购物车实体对象。这个对象也通常是领域服务方法的入参对象。

  • 概念:实体对象,是 DDD 中的一个基本构建块,它代表了具有唯一标识的领域对象。实体不仅仅包含数据(状态属性),还包含了相关的行为(功能),并且它的标识在整个生命周期中保持不变。
  • 特征:

1.3.2. 值对象

值对象。它通常是配合实体对象使用。如为实体对象提供对象属性值的描述,比如:一个公司雇员的级别值对象,一个下单的商品收货的四级地址信息对象。所以在开发值对象的时候,聚合。这个对象持久化 PO 对象是原子类对象,不具有业务语义。

  • 概念:实体对象是具有业务语义且有唯一标识的对象,跟随于领域服务方法的全生命周期对象是由一组属性组成的,它们共同描述了一个领域概念。与**实体(Entity)**不同,值对象不需要有一个唯一的标识符来区分它们。值对象通常是不可变的,这意味着一旦创建,它们的状态就不应该改变。
  • 特征:

1.3.3. 聚合

实体 = 唯一标识 + 状态属性 + 行为动作(功能)。这个对象在领域服务方法的生命周期过程内是不可变对象,也没有唯一标识。聚合中的实体可以由聚合提供创建操作,实体也被称为通常不会提供 setter 方法,而是提供构造函数或者 Builder 方法来实例化对象。一个订单的聚合,会涵盖:下单用户实体对象、订单实体、订单明细实体和订单收货四级地址值对象。而那个作为入参的购物车实体对象,已经被转换为实体对象了。——通常不会独立作为方法的入参对象,但可以独立作为出参对象使用。

1.4. 仓储和适配器

传统 Service + 贫血模型 常见的问题是:Service 层直接依赖 Redis、RPC 客户端、配置中心、MQ 等基础设施,导致业务逻辑与技术实现深度耦合,难以测试、演进和复用。

而 DDD 通过值对象(DIP) 彻底解耦这一问题:

  • 领域层只定义接口:在领域层定义一个或多个 当你对数据库的操作需要使用到多个实体时,可以创建聚合对象,这些接口声明了所需的数据访问方法。
  • 基础设施层负责实现:在基础设施层实现这些接口,具体实现可能是使用 ORM(对象关系映射)框架,如 MyBatis、Hibernate 等,或者直接使用数据库访问 API,如 JDBC 等。

图片

  • 用途:
  • 实现要点:

1.5. 触发器

当所有领域模型(实体、值对象、聚合、领域服务等)定义完毕,业务能力已在领域层内聚完成。接下来的关键问题是如何一个聚合对象,代表着一个数据库事务,具有事务一致性。

在 DDD 架构中,聚合根对象。它们本身不包含业务逻辑,仅作为聚合内事务一致性,聚合外最终一致性,负责接收外部请求并调用对应的依赖倒置原则。

  • 定时任务触发信贷计息流程;
  • 监听到 “开户成功” 事件后,调用返利服务发放优惠券;
  • 通过 HTTP 接口暴露授信能力,供上游系统调用。

这些触发动作的职责非常明确:Repository 接口,然后委托给应用服务进行用例编排。而真正的业务规则,始终保留在领域层中,确保核心逻辑不受技术细节污染,实现 “业务与基础设施解耦”。

2. DDD 的案例

下面以大营销抽奖项目的结构为例,对 DDD 项目结构进行拆解:

2.1. DDD 项目结构

2.1.1. 宏观

图片

2.1.2. 微观

2.1.3. 触发这些能力

图片

任何外部交互方式——无论是 HTTP/RPC 接口、消息队列监听,还是定时任务——都被统一视为 “触发动作”

图片

2.1.4. 入口点

图片

2.1.5. 应用层编排逻辑

图片

2.1.6. 解析输入、校验边界、启动流程

图片

2.1.7. api 接口定义层

图片