DDD 改造待办与实施计划(2026-04)
1. 目的
本文档基于当前代码仓库实际实现情况,而不是仅基于 README 或架构说明,梳理 FreeKitModules 当前 DDD 落地现状、主要缺口、推荐演进路线,以及优先级明确的改造待办。
目标不是一次性“重做架构”,而是在尽量不破坏现有业务的前提下,把已经存在的模块化代码逐步推进到更完整、可维护、可演进的 .NET DDD 实践。
2. 当前代码里的 DDD 实现现状
2.1 已经存在的部分
- 有清晰的分层目录:Application、Domain、Data、Models、Controllers
- 有领域服务基础设施:src/BuildingBlocks/IGeekFan.FreeKit.Infrastructure/DDD/DomainService.cs
- 有应用服务基类与通用 CRUD 模板
- 有工作单元与事务能力,Application 层通过 Transactional 特性进行事务控制
- 有领域事件分发器:src/BuildingBlocks/IGeekFan.FreeKit.Infrastructure/DDD/IDomainEventDispatcher.cs
- Identity 模块已经在使用领域事件和事件处理器
- CmsKit 中存在部分具有业务行为的方法,例如 Article 的置顶、推荐、定时发布,以及计数更新校验
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 层或其他层可以直接改字段,绕过聚合根规则
这会导致两个问题:
- 业务规则分散在 Service 中,实体只剩数据壳
- 后续增加校验、事件、状态转换时,没有统一入口
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”。
建议路线:
- 先修复 P0 聚合根边界
- 再补 CmsKit 的领域事件
- 然后引入 Specification,消除查询散落
- 最后在新增功能中逐步启用 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 才有稳定基础。