← 返回文章列表

DDD 实战(一):DDD 到底在解决什么问题

2026年6月24日架构设计
DDD领域驱动设计架构设计复杂业务

DDD 实战(一):DDD 到底在解决什么问题

推荐 BeWild 代充 Codex ,工作猛猛提效。

很多人第一次接触 DDD,会先看到一堆名词:限界上下文、核心域、聚合根、领域服务、仓储、领域事件。再往后看,往往又会看到一套目录结构:domainapplicationinfrastructureinterfaces

于是 DDD 很容易被误解成一种“高级分层架构”:只要把代码放进这些目录,把数据库表映射成实体,再把 service 改名叫 application service,好像就完成了 DDD。

这其实把问题看反了。DDD 首先不是目录规范,而是一套管理业务复杂度的方法。目录结构只是模型边界落到代码里的结果。

1. 复杂业务不是功能多,而是变化多

业务复杂度不等于页面多、接口多、表多。一个后台管理系统可能有上百张表,但如果所有功能都是简单增删改查,它并不复杂。真正复杂的是这些情况:

  • 同一个词在不同场景里含义不同。
  • 一个业务动作会牵动多个模块,且每个模块有自己的规则。
  • 规则经常变化,而且变化点不集中。
  • 不同团队对同一对象有不同理解。
  • 数据一致性、权限、审计、通知、搜索、开放接口同时参与同一个流程。

拿协同办公 IM 来说,“用户”这个词就不稳定。

在登录场景里,用户是账号、凭证、设备、登录会话。在组织通讯录里,用户是某个企业下的成员。在外部联系人里,用户可能是客户、好友、供应商。在群聊里,用户又是群成员、群主、管理员、普通成员或被禁言成员。

如果整个系统都共享一个 User 模型,短期看起来很省事,长期就会出现一个越来越大的“用户中心”。所有模块都往里面加字段、加规则、加判断,最后谁都不敢改。

DDD 要解决的第一类问题,就是让不同业务语境拥有自己的模型,而不是强迫全系统共享一个万能模型。

2. 普通三层架构为什么会失效

经典三层架构通常分为 Controller、Service、DAO。对于简单业务,这种分层很有效:入口、业务逻辑、数据访问各司其职。

但业务复杂之后,三层架构经常演变成这样:

  • Controller 只负责转发请求。
  • DAO 只负责读写数据库。
  • 所有业务规则都堆到 Service。
  • Service 之间互相调用,互相知道对方的数据结构。
  • 数据库表结构逐渐变成事实上的业务模型。

这种结构最大的问题,不是代码不够优雅,而是业务知识没有稳定的位置。一个规则到底属于账号、组织、关系、群组还是消息,没人能说清楚。只要需求来了,就在最顺手的 Service 里补一个判断。

几年之后,系统会出现三种典型症状。

第一,模型失血。实体只是数据库字段的搬运对象,业务规则散落在各种 service、handler、util 里。代码里有对象,但没有业务模型。

第二,边界失焦。账号模块可以改好友关系,群聊模块可以改成员资料,消息模块可以直接查组织表。调用链越来越短,耦合越来越深。

第三,语言断裂。产品、业务、测试、开发说的都是“用户”“群”“联系人”,但每个人心里的含义不一样。需求评审时看似达成一致,落到代码里才发现理解完全不同。

DDD 不是为了替代三层架构,而是为了补上三层架构没有回答的问题:业务概念如何建模,模型边界如何确定,变化如何被限制在局部。

3. DDD 的核心:模型、语言和边界

Microsoft 的微服务领域分析文档强调,服务边界没有机械的标准答案,需要从业务域、需求、架构特征和目标中推导;它也把 DDD 分成战略设计和战术设计两部分。战略设计关注大系统结构,战术设计关注实体、聚合、领域服务等模型模式。Microsoft: Domain analysis for microservices

Martin Fowler 在解释 Bounded Context 时,也强调模型只有在某个上下文边界内才有明确含义,同一个术语在不同上下文里可以对应不同模型。Martin Fowler: Bounded Context

这两点正好说明 DDD 的核心不是“把项目分成几层”,而是三件事。

第一,建立统一语言。开发、产品、业务专家要围绕同一套业务词汇沟通。这个语言不只写在文档里,也要进入代码。

第二,识别模型边界。同一个现实对象,在不同业务上下文里可以有不同模型。账号、组织成员、好友、群成员不必强行合并成同一个大对象。

第三,保护业务规则。重要规则应该沉淀在领域模型里,而不是散落在接口层和数据库访问层。

DDD 的价值不在于让代码看起来更“架构化”,而在于让业务规则有稳定表达,让变化有明确落点。

4. 用协同办公 IM 看 DDD 的必要性

协同办公 IM 是一个非常适合讲 DDD 的业务场景。它看起来是“聊天软件”,实际上覆盖了很多不同业务域:

  • 账号、认证、设备、安全策略。
  • 企业、部门、成员、角色、通讯录。
  • 好友、联系人、外部联系人、黑名单。
  • 单聊、群聊、群成员、群治理。
  • 消息、已读未读、撤回、转发、引用、附件。
  • 通知、离线推送、站内提醒。
  • 工作台、审批、日程、任务、文档。
  • 开放平台、机器人、Webhook、事件订阅。
  • 搜索、审计、风控、合规。

如果只按技术分层组织这些能力,系统很容易变成“一个 IM 大服务 + 一堆辅助模块”。早期开发会很快,但业务持续演进后,几个问题会越来越突出。

比如“从企业组织中移除一个成员”这件事,至少会影响:

  • 登录权限是否立即失效。
  • 通讯录是否还能搜索到这个人。
  • 历史消息是否保留。
  • 群成员身份是否移除。
  • 文档权限是否回收。
  • 机器人和开放平台事件是否通知。
  • 审计日志是否记录。

这些动作不应该由一个超级 Service 同步完成。更合理的方式,是让组织上下文发布“成员离职”或“成员移除”事件,由消息、群组、文档、搜索、开放平台、审计等上下文分别响应。

这里就自然引出了 DDD 的几个关键问题:

  • 哪些模型应该属于同一个限界上下文?
  • 哪些操作必须在一个聚合内强一致?
  • 哪些动作可以通过领域事件最终一致?
  • 哪些能力是核心域,哪些是支撑域,哪些是通用域?
  • 当前系统应该是模块化单体,还是拆成多个服务?

这些问题没有标准答案,但必须有清晰的推理过程。

5. DDD 不是什么

为了避免走偏,先把几个常见误解说清楚。

DDD 不是微服务。 微服务可以以 DDD 作为边界识别方法,但 DDD 不要求一开始就拆服务。很多系统更适合先做模块化单体,把领域边界在代码里立住,再按演进压力拆部署。

DDD 不是贫血模型加目录结构。 如果 domain 目录里只有字段和 getter/setter,业务规则仍然写在 application service 里,那只是换了文件夹。

DDD 不是数据库建模。 聚合边界不是外键关系,聚合根不是主表对象。聚合关注的是业务一致性边界,不是表结构层级。

DDD 不是越细越好。 边界切得太粗会耦合,切得太细会增加协作成本。DDD 追求的是当前业务阶段下足够清晰、足够可演进的模型。

DDD 不是一次性设计。 业务演进几年后,今天合理的边界可能会变得别扭。领域模型需要持续复盘和重构。

6. 这个系列会怎么展开

这个系列采用一条从认知到落地的路线:

  1. 先讲 DDD 解决什么问题。
  2. 再讲战略设计:领域、子域、核心域、支撑域、通用域、限界上下文。
  3. 再讲战术设计:实体、值对象、聚合根、领域服务、领域事件。
  4. 然后基于钉钉、飞书/Lark 等公开产品和开放平台能力,推导协同办公 IM 的业务域地图。
  5. 接着专门讨论账号、组织、关系、群组这些最容易争议的边界。
  6. 再讨论消息、通知、搜索、开放平台这些跨域协作能力。
  7. 最后落到部署模块和 Go 项目代码组织。

我的基本观点是:DDD 的严格性不在于找到唯一标准答案,而在于能够说清楚为什么这样建模、代价是什么、什么时候需要推翻它。

这也是程序员真正敢在项目中实践 DDD 的前提。

返回博客列表