Home
avatar

.𝙃𝙖𝙣

在这个项目里,我对 DDD 架构的阶段理解

这篇偏工程文,主要记我在 vchoo 这类项目里,对 DDD 架构的一些阶段理解。

前面几篇写了任务调度、异常降级、日志权限这些问题,再往回看,很多复杂度都不是某个单点技术单独带出来的。更多时候,是业务对象、状态流转、外部能力、数据存储一起压上来以后,代码开始越来越难收。也是到这个阶段,我才慢慢把 DDD 当成一套能帮我整理代码和业务边界的东西来看。

1. 我当时为什么会开始认真看 DDD

刚开始做后端时,很多项目都可以先用比较直接的方式往前推:

  • Controller 接请求
  • Service 里写逻辑
  • Repository 查库
  • 返回结果

在功能比较少的时候,这种方式其实完全够用。

但 vchoo 这种链路一长,问题会很快冒出来。

因为系统里同时有这些东西:

  • 创作任务
  • 故事结果
  • 分镜对象
  • 生图任务
  • 状态流转
  • 重试、取消、超时、降级
  • 后台配置和运营动作

这些对象往几张表里一摆,事情并不会自己变简单。它们彼此关系很强,很多逻辑靠普通增删改查也说明不白。

这个阶段如果继续把所有规则都往一个 Service 里堆,短时间看还能跑,后面会越来越难改:

  • 状态判断分散
  • 逻辑重复
  • 某些规则写在接口层,某些写在数据层
  • 一次需求变动会连着牵动很多地方

所以我后来才真正开始认真看 DDD。

对当时的我来说,它最实际的价值在这里:

能不能把业务规则和对象关系收进一个更稳定的结构里。

2. 我现在对 DDD 的第一层理解:先别把它想成理论,先把它当成“给业务收边界”的方法

一开始接触 DDD,很容易被术语压住。

比如:

  • 聚合
  • 实体
  • 值对象
  • 仓储
  • 领域服务
  • 应用服务

如果一开始就从定义背,挺容易空。

我后来更能接住它的方式,其实很朴素:

先看项目里哪些东西是真正的业务对象,哪些规则必须跟着这些对象走,再决定代码应该怎么摆。

这个角度会更落地。

因为 DDD 当时最能帮到我的,是逼着我把这些问题先问清楚,而不是急着多写几层文件:

  • 这个对象到底是什么
  • 它的状态应该由谁维护
  • 哪些操作是这个对象自己该管的
  • 哪些动作跨对象了,要放到更高一层处理

这些问题一旦问明白,很多代码摆放其实就不再只是风格问题,而开始和业务边界直接相关。

3. DDD 在我这里最先落地的,是分层思路

如果先不讲太细,我现在对 DDD 最先能用上的,是它把项目拆成了几层职责更清楚的东西。

1)接口层

负责:

  • 接请求
  • 做基础参数校验
  • 调应用层
  • 返回结果

这一层尽量别背业务规则。

2)应用层

负责:

  • 协调整个用例
  • 组织一次业务动作的执行顺序
  • 调领域对象 / 领域服务 / 仓储

这一层更像“流程编排层”。

3)领域层

负责:

  • 核心业务对象
  • 业务规则
  • 状态变化约束
  • 对象之间的重要关系

这一层是 DDD 最核心的地方。

4)基础设施层

负责:

  • 数据库存取
  • 第三方服务调用
  • 消息、缓存、文件、外部接口

这层更多是在解决“怎么接出去”。

这四层一旦分清楚,很多以前混在一起的东西就有地方放了。

4. 对我来说,DDD 更难的地方在“聚合边界到底怎么定”

分层好理解,聚合边界更难。

因为这件事说到底不是把类挪个位置那么简单,它实际上是在回答一个更核心的问题:

哪些东西应该被当成一个整体来维护一致性。

这句话一开始挺抽象,但放到 vchoo 里就会具体很多。

比如:

  • 一次创作任务和它的整体状态,是不是一个整体
  • 分镜是不是创作任务下面的子对象
  • 生图任务要不要和创作任务绑得特别死
  • 哪些状态更新必须在同一个边界里保证一致

我当时最容易走偏的地方,就是想把所有相关对象都硬塞进同一个大聚合里。

后来慢慢会觉得,这样通常不合适。

因为对象一多、状态一多、外部调用一多,一个聚合如果过大:

  • 写起来很重
  • 状态更新很容易互相牵扯
  • Repository 查询和保存也会越来越笨重

所以我后来更能接受的理解是:

聚合边界真正要看的,是哪些规则必须在一次业务变化里一起保证。

这个标准比“它们看起来都有关联”要实用得多。

5. 结合 vchoo 这条链,我当时更容易把“创作任务”看成一个核心聚合入口

在这个项目里,如果只是从数据表角度看,故事、分镜、生图任务都有关联。

但从业务动作看,它们的变化节奏并不完全一样。

我当时比较能抓住的一点是:

  • 一次创作任务有它自己的生命周期
  • 分镜是这次创作下面的中间对象
  • 生图任务又是后面异步执行的一组子任务

所以如果硬把所有东西都当成一个“超大对象”一次性处理,很多逻辑会很别扭。

反过来,如果完全拆散,也会失去业务上的主线。

所以我当时会更倾向于把“创作任务”理解成一个核心入口,它负责:

  • 记录一次完整创作从开始到结束的主状态
  • 维护它当前进行到哪一步
  • 约束某些关键动作能不能发生

而分镜、生图任务这些对象,再根据它们自己的变化节奏往下拆。

这个拆法至少对我当时是有帮助的。

因为它让我不再只是按表来想系统,而开始按业务动作和一致性来想。

6. 实体和值对象这层,我现在更看重“有没有业务含义”,而不是“语法像不像类”

刚开始学 .NET 时,很容易把实体和值对象都看成普通类。

后来接触 DDD 以后,我才慢慢更在意一件事:

这个类在业务里到底有没有独立意义。

如果有自己的身份标识、状态变化、生命周期,那它更像实体。

如果只是某一组描述信息、配置值、组合值,它更像值对象。

这个区分一开始对我最大的帮助,是防止我把所有东西都写成“带几个属性的普通类”。

因为一旦什么都一样,代码虽然能写,但业务含义会越来越平。

而 DDD 想保住的,恰恰就是这层业务语义。

7. Repository 在 DDD 里对我最大的提醒,是“别让领域层自己知道数据库细节”

以前写简单项目时,很容易在 Service 里一路把查询、拼接、保存全写完。

这当然能跑。

但当业务对象越来越重、状态规则越来越多时,这样写会让领域逻辑和数据存取细节缠得很紧。

Repository 这一层对我最大的帮助,其实是把一个边界立清楚了:

  • 领域层关心对象和规则
  • Repository 负责把这些对象取出来、存回去

Repository 放在这里,更像是在隔开:

  • 业务世界
  • 数据存取世界

这个边界一清楚,后面很多代码就不至于一边处理业务规则,一边还在拼 SQL 或纠结存储细节。

8. 应用服务和领域服务,我现在先按“编排”和“规则”来分

这也是我后来比较有感觉的一块。

一开始最容易把所有逻辑都往 Service 里塞。

后来才慢慢区分:

应用服务

更适合负责:

  • 一次用例怎么跑
  • 调哪些对象
  • 先后顺序是什么
  • 最后返回什么结果

比如:

  • 发起一次创作任务
  • 推进到分镜拆分
  • 触发生图子任务
  • 汇总状态并返回给前端

这些都更像应用服务在做的事。

领域服务

更适合负责:

  • 某些跨实体、跨对象的核心业务规则
  • 又不太适合挂在某一个单独实体上的逻辑

这个我当时接触得不算特别深,但至少开始知道:

并不是所有逻辑都该塞在实体里,也不是所有逻辑都只能扔进“一个超大 Service”里。

这个认识对我很重要。

因为它给了我一种更细的摆放方式。

9. DDD 在这类 AI 项目里最值钱的地方,是能帮我把“状态”和“规则”慢慢收回业务对象

前面写任务调度、异常降级、日志权限的时候,其实已经反复碰到一个问题:

  • 状态多
  • 规则多
  • 外部能力不稳定
  • 一步失败会影响下一步

如果没有一个稳定的业务结构,这些规则很容易分散到:

  • Controller
  • Service
  • 回调处理
  • 数据层
  • 定时任务

哪里方便就写哪里。

短期当然跑得动,后面会越来越难维护。

而 DDD 对我最有帮助的地方,就是它会一直逼着我问:

  • 这个状态变化到底归谁管
  • 这个规则应该挂在哪个对象上
  • 这次业务动作的边界在哪
  • 这个地方是在做业务判断,还是在做基础设施调用

这些问题一问出来,很多以前散着写的逻辑就开始有机会往回收。

10. 我现在对 DDD 的阶段理解,更多是“够用”,不是“教科书完整”

这一点我也想单独记一下。

因为 DDD 很容易走两个极端:

一个极端是完全不管

所有东西都按 CRUD 写,能跑就行。

另一个极端是过度设计

项目还没多复杂,先把术语、模式、层级全堆满。

我现在更能接受的,是中间这条路:

DDD 最适合拿来处理项目里已经出现的业务复杂度,没必要为了“像 DDD”把结构先堆重。

这对我来说很重要。

因为它让我不会一边学架构,一边脱离手上的真实业务。

我现在会更看重:

  • 这套拆法对当前项目有没有帮助
  • 状态和规则是不是更清楚了
  • 后面改需求时是不是更好收口了

这些比“术语是不是用得很标准”更有意义。

11. 这篇先记一个当前够用的版本

先给自己留一个阶段性结论:

  1. DDD 对我最直接的价值,是帮我给业务对象、状态和规则收边界
  2. 分层不算最难,真正绕人的还是聚合边界怎么定
  3. 应用服务负责用例编排,领域层负责核心业务规则,基础设施层负责把能力接出去
  4. Repository 的意义在于隔开业务对象和数据存取细节
  5. DDD 在这类 AI 业务项目里最值钱的地方,是让状态和规则不至于散得到处都是

这篇先写到这里。

.NET DDD 架构 后端 工程化