DDD领域驱动设计如何指导系统开发

一、先给你一个“总答案”(心里先有主线)

**DDD 指导系统开发的方式不是“告诉你怎么写代码”,
而是告诉你:
**
什么时候做什么决定、这些决定应该由谁来负责

它主要在 5 个关键决策点上给你方向。


二、DDD 指导系统开发的 5 个关键阶段

我按你真实做项目的顺序来讲。


① 需求阶段:DDD 指导你“先搞清楚什么”

传统做法(常见翻车)

  • PRD → 接口 → 表结构 → 写代码
  • 需求评审重点:字段、接口、状态码

DDD 的指导方式

DDD 在这一阶段只做一件事:

抽“业务动作”和“业务规则”

你要问的不是:

  • 表怎么设计?

而是:

  • 系统里有哪些业务行为
  • 哪些行为是一个整体
  • 状态怎么流转?谁有权限触发?

📌 产出不是代码,而是:

  • 业务动作清单(下单 / 支付 / 退款)
  • 状态机草图(不是技术状态机)
  • 统一语言(订单 ≠ 支付单)

👉 DDD 把“理解业务”变成可交付物


② 架构阶段:DDD 指导你“系统怎么拆”

这是 DDD 最核心的指导点

传统拆法

  • 按功能
  • 按人分工
  • 按表

DDD 指导原则

一个“完整业务生命周期” = 一个领域边界

你要判断:

  • 这个行为改动,会不会牵连大量模块?
  • 能不能被一个模型“兜住”?

📌 产出:

  • 限界上下文图(Bounded Context Map)
  • 哪些是核心域 / 支撑域 / 通用域

⚠️ 这一步 直接决定系统能不能活 3 年


③ 设计阶段:DDD 指导你“代码怎么组织”

DDD 在代码层面,只管一件事

业务规则住在哪里

DDD 给你的明确规则

1️⃣ 业务规则只能在领域层

  • 不在 Controller
  • 不在 Application Service
  • 不在 Repository

2️⃣ 实体必须有行为

order.pay();
order.cancel();

不是:

order.setStatus(PAID);

3️⃣ 应用层只做“流程编排”

@Transactional
public void cancelOrder(Long orderId) {
    Order order = repo.load(orderId);
    order.cancel();
    repo.save(order);
}

👉 应用层像导演,领域层才是演员


④ 实现阶段:DDD 指导你“怎么防止系统腐烂”

这是 DDD 被严重低估的价值。

DDD 的三个“护栏”

① 聚合边界 = 一致性边界

  • 一个事务只修改一个聚合
  • 防止“跨对象随便改”

② 外部只能操作聚合根

  • 不允许直接操作子对象
  • 防止业务规则被绕开

③ 统一语言 = 命名约束

  • 方法名即业务规则
  • 不允许“技术命名污染业务”

📌 这些不是“风格”,是系统免疫力


⑤ 演进阶段:DDD 指导你“怎么改而不炸”

DDD 假设一件事:

需求一定会变,而且会变得很脏

所以它教你:

  • 变更先发生在上下文内部
  • 不跨上下文直接修改模型
  • 用事件而不是调用传播变化

结果是:

  • 改需求 ≠ 改全系统
  • 重构可以局部进行

三、我用一个“真实开发对照”帮你落地

场景:订单退款

非 DDD 思路

refundService.refund(orderId);

内部:

  • 查订单
  • 校验状态
  • 调支付
  • 改库存
  • 写日志

👉 Service 上帝类


DDD 思路

order.refund();
  • 校验是否可退
  • 进入退款中状态
  • 发布 RefundRequestedEvent

支付、库存系统:

  • 订阅事件
  • 各自处理

👉 职责天然分离


四、DDD 真正“指导”的不是技术,而是思考顺序

我给你一个非常关键的对照:

你原来思考顺序 DDD 的思考顺序
表怎么设计 业务怎么运转
接口怎么拆 行为怎么归属
谁来调谁 谁对规则负责
怎么防 bug 怎么防腐烂

五、给你的现实建议(不讲虚的)

你不需要“全面 DDD”,你只需要做到这 3 条

  1. 一个复杂业务动作 → 一个聚合根方法
  2. Service 不写 if/else 业务规则
  3. 跨模块变化用事件,不用直接调用

做到这三条,
你已经是 “工程级 DDD” 了。

posted @ 2025-12-25 15:45  中登程序猿  阅读(1)  评论(0)    收藏  举报