本篇要搞懂的 4 件事:现代 DDD 代码模型为什么"先按模块切、再在模块里分层"、一个完整的模块化单体长什么样(含 core/shared、模块内四层、架构约束)、它和原课 2019 版的差异在哪、以及怎么照着它让 AI 生成结构干净的代码。
⚠️ 本笔记是现代化改写版。原课(欧创新,约 2019)讲的是"顶层按技术层切 + 一个限界上下文从第一天就拆成微服务"的模型,在今天看偏旧。这里按 2020s 主流的"模块化单体(modular monolith)+ 端口适配" 来讲,并全程用你自己的 MemoryEngine / BIE 当真实例子。原课那套结构作为对照放在文末「附:原课 2019 版」,照样保留可查。
承接前文:12 讲用事件风暴建出了领域模型,13 讲要回答"这套模型落到硬盘上、代码目录到底长什么样"。原课的答案是"照着 07 讲四层架构一比一建四个一级目录"。现代答案在它之上更进一步:顶层不再按技术层切,而是按"限界上下文(模块)"切,四层放进每个模块内部。 为什么这么改、改完长什么样,是本讲重点。
07 讲:DDD 四层架构(接口层/应用层/领域层/基础层)—— 思想地基,至今不变
↓
原课 13 讲(2019):顶层 = 四个技术层目录,一个上下文 = 一个微服务(一开始就拆)
↓ 演进
现代 13 讲(本笔记):顶层 = 业务模块,四层放进模块内部;先做模块化单体,要了再拆微服务
🪞 辅助理解(两种摆法的区别):把代码想成一栋写字楼。
- 原课摆法 = 按工种分楼层:1 楼全是"前台接待"、2 楼全是"行政"、3 楼全是"研发"……要办成一件事(一个业务功能),你得在四层楼之间来回跑。
- 现代摆法 = 按部门分房间:每个部门(限界上下文)有自己独立的套间,套间内部再分前台/办公/后勤。一件事在一个套间里就办完了。哪天某部门要独立出去成立子公司(拆微服务),整套间搬走即可。
现代代码模型:完整参考结构
这是一套可以直接照抄的"模块化单体"骨架(语言无关,用 Python 风格示意,你的 MemoryEngine/BIE 就是这个形状):
project-root/
├── apps/ # 启动入口(可执行单元),本身几乎不含业务
│ ├── api/ # HTTP 服务入口(挂路由、中间件、装配依赖)
│ ├── admin_api/ # 后台管理入口
│ └── worker/ # 异步任务 / 消息消费者入口
│
├── core/ (或 shared/) # 跨模块共享内核:所有模块可依赖它,但它不许反向依赖任何业务模块
│ ├── config/ # 配置
│ ├── database/ # DB 连接 / session / 基类
│ ├── events/ + outbox/ # 领域事件总线 + 发件箱(保证"业务+事件"原子落库,见 06 讲)
│ ├── observability/ # 日志 / 指标 / 追踪
│ └── runtime/ # 依赖注入、生命周期
│
├── modules/ # ★核心★ 顶层按"限界上下文"切,一个上下文一个文件夹
│ ├── <bounded_context>/ # 如 memory_kernel / identity_access / knowledge
│ │ ├── domain/ # 领域层:纯业务,零框架依赖
│ │ │ ├── <entity>.py # 实体 / 聚合根(充血:带行为方法,不是纯数据袋)
│ │ │ ├── value_objects.py# 值对象(不可变,frozen dataclass)
│ │ │ ├── events.py # 领域事件("XxxHappened" 过去式命名)
│ │ │ ├── repository.py # 仓储【接口】(Protocol/ABC) —— 只声明、不实现
│ │ │ └── services.py # 领域服务(跨多个实体的业务逻辑)
│ │ ├── application/ # 应用层:用例编排,薄
│ │ │ ├── commands/ # 写用例:每个 handler 一个文件(走聚合 + 仓储)
│ │ │ ├── queries/ # 读用例:直接查只读模型/投影,不经聚合(CQRS lite)
│ │ │ └── event_handlers.py# 订阅其它模块发来的领域事件
│ │ ├── infrastructure/ # 基础层:仓储【实现】+ ORM + 外部客户端
│ │ │ ├── orm.py # 持久化对象 PO(SQLAlchemy 表映射)
│ │ │ ├── repository_impl.py# 实现 domain 里声明的仓储接口(依赖倒置)
│ │ │ └── clients.py # 调外部服务/第三方 SDK 的适配器
│ │ └── api/ # 接口层:本上下文对外暴露
│ │ ├── routes.py # 路由 / Controller
│ │ └── schemas.py # DTO(Pydantic,进出参,收窄字段)
│ └── <another_context>/ # 结构完全一致
│
└── tests/
└── architecture/ # 架构测试:import-linter 合同,自动守边界(见下)
💡 注意:四层(接口/应用/领域/基础)一个没少,只是从"顶层四个大目录"挪进了"每个模块内部"。07 讲的分层思想完整保留,变的只是"先分模块还是先分层"的顺序。
这四层就是 07 讲那张图,只不过现在它住在每个模块里:
三个关键升级(相对原课)
升级 1:模块优先,不是技术层优先
原课顶层是 interfaces/ application/ domain/ infrastructure/ 四个大目录,一个业务功能的代码摊在四处。现代顶层是 modules/<上下文>/,同一上下文的四层收在一个文件夹里——改一个功能,代码集中在一个模块内,不用四处跳。这是 Spring Modulith、各类 vertical-slice 架构的共同主张。
💡 模块里的
domain/该放哪些零件?原课这张"聚合内部"图画得最清楚——实体、事件、仓储、领域服务(现代把它们摊平成domain/下的几个文件,思想一样):
升级 2:模块化单体,不是"上来就拆微服务"
原课默认"一个限界上下文 = 一个微服务,第一天就拆"。这是 2019 微服务狂热期的心态,对中小项目是过早优化——分布式带来的跨服务调用、数据一致性、运维成本,往往把团队拖垮。
现代默认:先做模块化单体——逻辑上分模块、边界清晰,但物理上是一个可部署应用。等到某个模块真的需要独立伸缩/独立发布,再把它整体抽出去成微服务(见文末「演进」)。
🪞 辅助理解:模块化单体 = “一栋楼里的多个独立部门”;微服务 = “把部门搬到不同城市开分公司”。先把部门墙砌好(模块边界清晰),搬不搬城市以后再说——而墙砌好了,搬起来才不痛。
升级 3:架构边界靠工具自动守,不靠自觉
原课只能"告诫你别跨层调用"。现代用 import-linter(Python)/ ArchUnit(Java)/ Tach 这类架构测试,把规则写成合同,违规直接 CI 报错。你的 MemoryEngine 和 BIE 都已经这么做了,合同长这样:
[[tool.importlinter.contracts]]
name = "domain 层禁止依赖外层"
type = "forbidden"
source_modules = ["...modules.*.domain"]
forbidden_modules = ["...modules.*.api", "...modules.*.application", "...modules.*.infrastructure"]
[[tool.importlinter.contracts]]
name = "core 不依赖业务模块"
source_modules = ["...core"]
forbidden_modules = ["...modules"]
💡 这正是原课"想要、但当年没有"的东西——它把 07 讲"严格分层"从"君子协定"变成了"机器执法"。AI 帮你写代码时尤其值钱:AI 一旦把 domain 写得依赖了 infrastructure,CI 立刻红灯,不会等到你 review 才发现。
模块之间怎么通信(最容易写错的地方)
铁律:一个模块绝不直接 import 另一个模块的 domain/ 或内部实现。 只能走两条合法通道:
| 通道 | 怎么用 | 适用 |
|---|---|---|
| 领域事件(首选) | A 模块发 XxxHappened 事件 → B 模块 event_handlers.py 订阅处理 |
异步、解耦、最终一致(见 06 讲) |
| 公开应用服务 / 端口 | 调用对方 application 暴露的接口(或定义一个 Port 接口让对方实现) |
需要同步拿结果时 |
💡 BIE 甚至专门写了合同
"graph 节点禁止反向写业务表"、"shared.jobs 禁止依赖任何 modules"来堵这类越界——这就是把"模块间松耦合"焊死。
对照你自己的项目
你的两个项目就是这套模型的活样本,可直接对号入座:
| 现代模型 | MemoryEngine | BIE |
|---|---|---|
启动入口 apps/ |
apps/{api, admin_api, worker} |
app/(FastAPI 入口)/ studio |
| 共享内核 | core/{config,database,events,observability,runtime} |
app/core + app/shared/{jobs,observability,outbox} |
限界上下文 modules/ |
9 个:memory_kernel、identity_access、retrieval_composer… | 15 个:knowledge、chat、agent、auth… |
| 模块内四层 | <模块>/{api, application, domain, infrastructure} |
同左 |
| 仓储 + DIP | infrastructure 里 repository_impl.py + ORM |
同左(infrastructure/repository_impl.py、orm.py) |
| 事件最终一致 | core/events |
shared/outbox(发件箱模式) |
| 边界自动校验 | import-linter 合同 | import-linter 合同(更细) |
💡 一句话:你已经站在 13 讲想要到达的地方了。剩下要盯的不是结构,而是模块内部
domain/会不会被 AI 写成"贫血空壳"(这是 14 讲的主题)。
演进:模块 → 微服务(原课承诺的"后手",现代兑现得更好)
模块边界清晰带来的最大红利就是可演进:
- 某模块被高频访问、需要独立伸缩 → 把整个
modules/<ctx>/文件夹整体抽出成独立服务。因为它本就自包含(自带四层 + 仓储 + 事件),抽离时不用东拼西凑。 - 模块间原本就只通过事件/接口通信,抽出后把"进程内事件"换成"消息中间件"即可,业务代码几乎不动。
这正是原课"以聚合为单位拆/合微服务"想要的效果,但现代把粒度提到了"模块"层级、并用工具保证了边界,演进起来更稳。
总结
现代 DDD 代码模型 = 模块化单体:顶层按限界上下文切成 modules/<ctx>/,每个模块内部再放 07 讲那四层(api/application/domain/infrastructure),加一个所有模块共享、但不反向依赖业务的 core/shared。
相对原课(2019)的三处升级:①模块优先(功能内聚,不在四个技术层间跳)、②模块化单体优先(先单体后微服务,避免过早分布式)、③用 import-linter 自动守边界(把严格分层从自觉变成执法)。模块之间只通过领域事件或公开接口通信,绝不互相 import 内部。需要时把整个模块文件夹抽出去就是微服务。
地基没变——07 讲的分层、依赖倒置、聚合、仓储全在;变的是摆放方式更内聚、演进路径更稳、边界有工具兜底。
一句话速记
现代 DDD = 模块化单体:先按限界上下文切模块、四层放进模块里,core 共享但不依赖业务,模块间只靠事件/接口通信,import-linter 自动焊死边界;先单体后微服务,要拆时整模块搬走。
思考题
你正在做 MemoryEngine / BIE,正好拿真实代码练:
- 画一条依赖箭头图:挑一个模块(如 memory_kernel),把它
api → application → domain ← infrastructure的依赖方向画出来。domain 是不是真的谁都不依赖?infrastructure 是不是只通过实现 domain 的仓储接口来"反向插入"? - 检查模块间通信:你的模块之间有没有出现"A 模块直接 import 了 B 模块 domain 里的类"?如果有,它该改成发领域事件还是调对方应用服务?(可以让 AI 顺着 import-linter 合同扫一遍)
- 写一版"目录约束 prompt":把本讲这套模块化单体目录树,连同"业务逻辑放 domain、编排放 application、跨模块只发事件"的规则,整理成一段给 AI 的系统提示。下次让 AI 加新模块时直接喂给它,对比有没有这段提示时生成结构的差距。
附:原课 2019 版怎么对应(一句话换算)
原课的目录名其实和现代一一对应,只是"摆放层级"不同——对照着看就懂:
| 原课(2019) | 现代(本笔记) |
|---|---|
顶层 interfaces/ |
模块内 api/ |
顶层 application/、infrastructure/ |
模块内同名层(名字没变) |
顶层 domain/ 里的"聚合包"(entity/event/repository/service) |
模块内 domain/ 的几个文件(摊平了) |
| 一个限界上下文 = 一个微服务 | 一个限界上下文 = modules/<ctx>/ 一个模块(先单体) |
💡 最大差别就一处:原课把四层放在"全局四个大目录"下、且默认一上来就拆微服务;现代把四层收进"每个
modules/<ctx>/模块"里、默认先单体。思想一脉相承,理解了现代版,原课那套自然秒懂。(原课的 7 张目录截图在 原文 里可查,本笔记只保留了仍适用的"四层架构图"和"聚合零件图"。)


