ddd-code-organization
使用 DDD 思想组织你的代码
核心目标
优先按业务能力、子系统、限界上下文组织代码,而不是把 models/、services/、repositories/、schemas/、api/ 这类技术类型目录平铺在项目根下。
目标不是机械套用 DDD 术语,而是让一个功能的核心代码尽量收敛在同一业务子系统内,做到高内聚、低耦合、边界清晰、易于协作和演进。
适用原则
在开始设计结构或新增模块前,先判断“这段代码属于哪个业务能力”,再决定目录位置。
始终优先回答这几个问题:
- 这段代码服务于哪个业务子系统?
- 这个子系统的核心职责是什么?
- 它需要暴露什么公共接口给其他子系统?
- 它依赖哪些共享能力,哪些依赖不应该直接发生?
如果一个需求横跨多个目录才能完成,优先怀疑目录组织方式有问题,而不是继续往现有的按类型结构里堆代码。
默认组织方式
先按业务子系统切分顶层目录,再在子系统内部按需要保留小范围的技术分层。
推荐结构:
src/
├── core/ # 基础设施与共享内核
│ ├── config.py
│ ├── database.py
│ ├── events.py
│ └── types.py # 跨子系统共享类型
├── customers/ # 客户子系统
│ ├── __init__.py
│ ├── models.py
│ ├── repository.py
│ ├── service.py
│ ├── schemas.py
│ ├── api.py
│ └── tasks.py
├── courses/ # 课程子系统
│ ├── __init__.py
│ ├── models.py
│ ├── repository.py
│ ├── service.py
│ ├── schemas.py
│ └── api.py
└── allocation/ # 分配引擎子系统
├── __init__.py
├── models.py
├── repository.py
├── service.py
├── schemas.py
├── api.py
└── tasks.py
不推荐的结构:
src/
├── models/
├── services/
├── repositories/
├── schemas/
├── api/
└── tasks/
上面这种结构在项目变大后,通常会导致:
- 一个业务功能散落在 5-7 个目录中
- 修改一个需求需要跨多层跳转
- 合并冲突集中在公共类型目录
- 新成员只能看到“技术层”,看不到“业务边界”
子系统划分方法
按业务能力划分,而不是按数据库表名机械划分,也不是按前后端接口名随意划分。
优先寻找这些切分信号:
- 明确的业务职责
- 相对稳定的领域语言
- 独立演进的规则集合
- 相对清晰的数据边界
- 可以通过接口与其他模块协作
例如:
customers:客户生命周期与状态流转courses:课程与配置管理allocation:客户分配算法与策略engagement:外联、提醒、跟进monitoring:仪表盘、统计、告警
不要把“用户提了一类接口”直接当成子系统,也不要把“ORM 模型很多”当成拆目录依据。
子系统内部约束
允许子系统内部存在技术分层,但这些分层只能服务该子系统,不应重新退化成全局的按类型组织。
遵循以下约束:
api.py只负责对外输入输出与路由编排。service.py承载业务规则和用例编排。repository.py负责数据访问。models.py放领域模型、ORM 模型或聚合相关对象。schemas.py放接口层 DTO / Pydantic Schema。tasks.py只放该子系统的异步任务。- 更复杂的子能力可以在子系统内部继续拆子目录,但仍然围绕该业务边界组织。
如果某个子系统开始膨胀,不要先把内容抽到根级 services/;先考虑是否需要在该子系统内部拆出更清晰的子模块。
跨子系统依赖规则
始终让依赖关系尽量单向、显式、可解释。
默认规则:
- 子系统之间通过公共 service 接口、应用服务或事件协作。
- 共享枚举、基础类型、跨域通用对象放到
core/types.py或等价共享内核中。 - 基础设施能力集中在
core/,不要散落在各个业务目录重复实现。 - 异步解耦优先使用领域事件、消息或任务,而不是互相直接深入调用内部实现。
- 避免循环依赖;一旦出现循环依赖,优先检查共享类型和边界划分是否错误。
设计或重构时的工作流
1. 先识别业务地图
列出系统的主要业务能力、核心流程、外部集成点和共享基础设施。
2. 再确定子系统边界
把高度相关、一起变化的代码放入同一个子系统。不要因为“它们都是 service”就放在一起。
3. 为每个子系统定义最小骨架
只创建当前真正需要的文件。不要为了对称性无脑生成所有文件。
4. 明确共享内核
把跨子系统复用且不属于单一业务的内容收敛到 core/,尤其是共享类型、配置、数据库连接、事件总线、认证上下文等。
5. 限制跨边界访问
一个子系统调用另一个子系统时,优先通过公开接口,而不是直接 import 对方内部实现细节。
6. 输出结构说明
给出目录树时,同时说明:
- 每个子系统负责什么
- 为什么这样切分
- 哪些依赖是允许的
- 哪些共享能力放在
core/
AI 在生成代码时必须遵守
- 新增功能时,先判断归属子系统,再创建文件。
- 优先把一个功能所需的模型、服务、接口、任务放在同一业务目录下。
- 只有在确实跨子系统共享时,才放入
core/或共享模块。 - 解释结构设计时,使用业务语言,不要只说“分层更清晰”。
- 如果用户给的是按文件类型组织的旧项目,优先提出按子系统重组的方案,而不是继续复制旧结构。
明确禁止
不要默认生成这些根级目录作为主组织方式:
models/services/repositories/schemas/controllers/tasks/
除非用户明确要求保留旧架构,或项目规模极小到没有引入子系统边界的必要,否则不要采用这种纯技术分层的顶层结构。
不要把“DDD”误解为:
- 一定要上复杂的战术模式
- 一定要实现完整的聚合根、领域事件、仓储抽象全家桶
- 一定要把每个概念都拆成很多类
这里的核心是按领域和业务边界组织代码,不是为了术语而术语化。
输出模板
当用户要求设计项目结构、模块边界或重构目录时,优先输出:
- 业务子系统列表
- 每个子系统的职责
- 推荐目录树
- 跨子系统依赖规则
- 为什么这比按
models/services/api平铺更合适
可直接使用这种表达:
## 推荐的业务子系统划分
- customers: 负责客户生命周期与状态流转
- courses: 负责课程与配置管理
- allocation: 负责分配规则与执行
## 推荐目录结构
```text
src/
├── core/
├── customers/
├── courses/
└── allocation/
依赖规则
- allocation 可以依赖 customers 暴露的公共服务
- customers 不直接依赖 allocation 内部实现
- 共享枚举统一放在 core/types.py
判断质量的检查清单
在给出方案前,检查:
- 一个业务功能是否仍然被拆散在多个根级技术目录里
- 顶层目录是否主要体现业务能力而不是文件类型
- 共享内容是否真的跨子系统复用
- 是否存在容易形成循环依赖的边界
- 新成员看到目录树后,能否快速理解系统有哪些业务模块
如果这些问题大多回答为“否”,说明组织方式还不够面向领域。