跳到主要内容

DDD 改造待办与实施计划(2026-04)

1. 目的

本文档基于当前代码仓库实际实现情况,而不是仅基于 README 或架构说明,梳理 FreeKitModules 当前 DDD 落地现状、主要缺口、推荐演进路线,以及优先级明确的改造待办。

目标不是一次性“重做架构”,而是在尽量不破坏现有业务的前提下,把已经存在的模块化代码逐步推进到更完整、可维护、可演进的 .NET DDD 实践。

2. 当前代码里的 DDD 实现现状

2.1 已经存在的部分

2.2 现状的本质判断

当前项目更接近“带有 DDD 结构的分层业务系统”,而不是“聚合边界严格、事件驱动清晰、模型封装完整的 DDD 系统”。

也就是说:

  • DDD 的目录和术语已经有了
  • 领域服务和事务也具备了
  • 但聚合根边界、值对象、规约模式、CQRS、反腐层这些关键能力仍不完整

3. 真实代码分析结论

3.1 聚合根存在,但边界不严

src/Services/CmsKit/FreeKit.CmsKit/Domain/Articles/Article.cs 为例:

  • Article 具备部分业务方法,如 SetRecommendTime、SetStickie、SetScheduledPublish
  • 但大部分核心属性仍然是 public set
  • 这意味着 Application 层或其他层可以直接改字段,绕过聚合根规则

这会导致两个问题:

  1. 业务规则分散在 Service 中,实体只剩数据壳
  2. 后续增加校验、事件、状态转换时,没有统一入口

3.2 Application 层职责过重

例如 src/Services/CmsKit/FreeKit.CmsKit/Application/Articles/ArticleService.cs 中的删除逻辑同时承担:

  • 权限判断
  • 分类统计更新
  • 标签计数更新
  • 关联数据清理
  • 搜索索引删除

这类代码可以工作,但不利于演进:

  • 单个方法承载太多副作用
  • 很难沉淀为稳定的领域模型
  • 业务变更时容易引起回归

3.3 领域事件基础设施有,但业务模块用得很少

Identity 模块中已经有比较标准的模式,例如用户创建和变更事件会通过领域事件处理器桥接为集成事件。

但 CmsKit 模块中,像这些行为目前还没有形成清晰的领域事件模型:

  • 文章创建
  • 文章发布
  • 文章删除
  • 评论创建
  • 点赞/收藏/浏览

这意味着很多副作用只能继续堆在 ApplicationService 里处理。

3.4 值对象不足

当前大多数业务概念仍以原始字段存在,例如:

  • 标题、摘要、SEO 信息
  • 评分
  • 发布时间区间
  • 访问权限组合

这些如果继续以标量属性散落在实体中,约束和语义会越来越弱。

3.5 仓储模式存在,但查询封装度不高

当前大量代码直接在应用层拼接 FreeSql 查询,这本身没有错,但从 DDD 角度看存在两个问题:

  • 查询条件重复出现
  • 仓储没有承担“屏蔽查询细节”的职责

比较明显的重复条件包括:

  • 审核状态
  • 软删除
  • 隐私状态
  • 创建人过滤
  • 频道/分类过滤

4. 如果按较成熟的 .NET DDD 方案来实施,还需要补哪些东西

结合当前社区较主流的 .NET 方案,可以拆成四块:

4.1 聚合根边界修复

这是第一优先级。

需要做的事:

  • 把核心聚合根的重要属性从 public set 收敛为 private set 或 internal set
  • 为状态变化提供明确业务方法,而不是外部直接赋值
  • 统一使用工厂方法或构造约束控制创建过程
  • 对删除、发布、审核、推荐、置顶等动作形成明确方法边界

优先对象:

  • Article
  • ShortMsg
  • Comment

4.2 领域事件体系补齐

推荐参考 Identity 模块已有模式。

优先补的事件:

  • ArticleCreatedEvent
  • ArticlePublishedEvent
  • ArticleDeletedEvent
  • CommentCreatedEvent
  • ShortMsgPublishedEvent

配套工作:

  • 聚合根持有 DomainEvents
  • 事务提交前后统一分发事件
  • 事件处理器里做索引更新、通知推送、统计同步、CAP 集成事件发布

4.3 值对象与规约模式引入

推荐补齐两个基础能力:

  • ValueObject 基类
  • Specification 基类

优先可落地的值对象:

  • SeoMeta
  • StarRating
  • PublishWindow
  • ArticleContentSummary

优先可落地的规约:

  • PublicArticlesSpec
  • ApprovedCommentsSpec
  • AuthorArticlesSpec
  • VisibleCollectionsSpec

4.4 CQRS 渐进引入

不建议全量重写现有 Service。

更合理的做法是:

  • 新功能优先使用 Command/Query/Handler
  • 高复杂度查询优先拆到 QueryHandler
  • 高副作用写操作逐步迁移到 CommandHandler

这样可以逐步把 Application 层从“超大服务类”收敛为“用例驱动”。

5. 当前更流行的 .NET DDD 设计方向

5.1 Clean Architecture + Specification

代表思路:

  • 领域模型保持纯粹
  • 仓储只接受规约,不暴露过多 ORM 细节
  • 查询复用通过 Specification 实现

适合当前仓库的原因:

  • 可以在不更换 ORM 的情况下落地
  • 适合先解决“查询重复”和“应用层过重”问题

5.2 DDD + CQRS + MediatR

代表思路:

  • 写操作走 CommandHandler
  • 读操作走 QueryHandler
  • 聚合根负责业务规则
  • 事件处理器负责副作用扩展

适合当前仓库的原因:

  • 项目已经引入 MediatR
  • 当前 Application Service 体积偏大,适合逐步切分

5.3 Vertical Slice Architecture

代表思路:

  • 按功能切,而不是按层切
  • 每个功能自带命令、查询、校验、映射、接口

适合当前仓库的方式:

  • 不建议直接改老模块整体结构
  • 适合新模块或新特性采用

6. 现阶段建议采用的路线

不是“纯理论 DDD”,而是“适合 FreeKitModules 的渐进式 DDD”。

建议路线:

  1. 先修复 P0 聚合根边界
  2. 再补 CmsKit 的领域事件
  3. 然后引入 Specification,消除查询散落
  4. 最后在新增功能中逐步启用 CQRS

7. 分阶段改造计划

Phase 1:P0 聚合根边界修复

目标:让核心聚合不再被外部任意改写。

计划:

  • 盘点 Article、ShortMsg、Comment 中真正属于聚合根控制的属性
  • 区分哪些属性必须保留给 ORM,哪些必须收敛到方法控制
  • 先为最重要的状态变化补业务方法
  • 再逐步收紧 setter
  • 同步修正对应 ApplicationService 调用方式

交付物:

  • 核心聚合根边界分析
  • 第一批聚合根方法重构
  • 最小范围的调用侧修复

Phase 2:领域事件补齐

目标:把副作用从 Service 中拆出来。

计划:

  • 为文章、评论、短内容生命周期建模事件
  • 让聚合根在关键业务动作中产生事件
  • 让 Handler 负责通知、索引、消息发布、统计同步

交付物:

  • CmsKit 领域事件目录
  • 至少一条主业务链的事件化改造

Phase 3:规约与查询治理

目标:解决查询条件重复和仓储职责不清。

计划:

  • 添加 Specification 基础设施
  • 收敛通用查询条件
  • 用规约替换高频重复查询

交付物:

  • Specification 基类
  • 第一批文章/评论查询规约

Phase 4:CQRS 渐进引入

目标:让复杂用例按命令与查询解耦。

计划:

  • 新功能优先切 Command/Query
  • 老功能挑高复杂度模块逐步迁移

交付物:

  • 第一批 CommandHandler / QueryHandler 示例

8. DDD-TODO

执行准则(新增,价值优先)

  • 禁止仅做“属性赋值改同名方法”的无效改造;如果没有新增业务规则/一致性约束/事件语义,不计入 DDD 进度
  • 每个小任务必须满足至少一项:
    • 新增不变量校验(如状态转换合法性、非空与边界)
    • 收敛跨字段一致性更新(一次业务动作同时维护相关字段)
    • 明确副作用边界(通过领域事件或统一入口处理)
  • 每个小任务执行顺序固定:先跑测试 -> 实施改造 -> 再跑测试 -> 单独提交
  • 不追求一次性重构全量模块,按高风险路径(发布、审核、删除、分类迁移)优先推进

P0 必做

  • 分析 Article 聚合根边界,形成属性分级清单
  • 分析 ShortMsg 聚合根边界,形成属性分级清单
  • 分析 Comment 聚合根边界,形成属性分级清单
  • 为 Article 增加明确的业务方法入口,减少外部直接赋值
  • 为 ShortMsg 增加明确的业务方法入口,减少外部直接赋值
  • 为 Comment 增加明确的业务方法入口,减少外部直接赋值
  • 修正对应 Application 层对聚合根的直接字段写入

P1 应做

  • 引入 ValueObject 基类
  • 引入 Specification 基类
  • 为 CmsKit 增加文章、评论、短内容领域事件
  • 梳理 Application 层副作用并迁移到事件处理器

P2 可逐步推进

  • 新功能采用 CQRS
  • 老功能按收益优先级逐步迁移到 CQRS
  • 补充共享内核与反腐层边界设计

9. 当前结论

从真实代码来看,这个项目已经具备 DDD 的骨架和不少基础设施,但目前最值得优先投入的不是“再讲一遍 DDD 理论”,而是把聚合根边界真正收回来。

如果只做一件事,应该先做这一件:

  • 先修复核心 PO 的聚合根边界,让实体从“可随意改写的数据对象”回到“承载业务规则的聚合根”

这一步做完,后面的领域事件、规约、CQRS 才有稳定基础。