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
判断质量的检查清单
在给出方案前,检查:
- 一个业务功能是否仍然被拆散在多个根级技术目录里
- 顶层目录是否主要体现业务能力而不是文件类型
- 共享内容是否真的跨子系统复用
- 是否存在容易形成循环依赖的边界
- 新成员看到目录树后,能否快速理解系统有哪些业务模块
如果这些问题大多回答为“否”,说明组织方式还不够面向领域。
More from cruldra/skills
tauri-v2
Tauri v2 项目开发助手 - 提供 CLI 项目管理、最佳实践指导和代码生成。适用于 (1) 创建和管理 Tauri v2 项目 (2) 开发桌面和移动应用 (3) 配置构建和分发流程 (4) 实现安全的前后端通信 (5) 应用架构设计和性能优化。
15pandoc
当用户需要对某个文档进行格式转换时(例如将 Markdown 转换为 DOCX、PDF、HTML 等)使用该技能。
12refine-dev
协助开发基于 Refine 框架的 React 应用。提供项目初始化、核心配置、数据提供者(Data Providers)、认证(Auth Provider)以及 UI 库集成的指导。专注于使用 shadcn/ui 构建现代化的后台管理系统。
11dri-text-analysis
使用 DRI 文本分析法(Data-Rule-Interaction)对自然语言需求描述进行逐词拆解与领域建模。将非结构化的业务需求文本降维为数据(D)、规则(R)、交互(I)三个维度的结构化架构抽象,直接产出可用于系统设计的概念表格。适用于需求分析、领域语言提取、架构设计前的文本解析,以及将长篇需求文档转化为清晰的开发任务拆解。
9vite-starter
使用 Vite 创建现代前端项目,支持 React、Vue、Svelte、Solid、Preact、Lit、Qwik 和 Vanilla JS,可选 TypeScript。当用户需要初始化新的前端项目、搭建 SPA、创建组件库、设置现代构建工具时使用此技能。触发场景:用户说"创建 vite 项目"、"新建 react/vue/svelte 应用"、"初始化前端项目"、"搭建 spa"、"用 vite 起一个项目"、"create vite project"、"new frontend app",或明确提及 Vite、HMR、快速构建工具时。
7plantuml-renderer
Use when the user wants to render PlantUML diagrams from pasted text or files that contain valid PlantUML blocks (such as .puml, .md, or .docx text content), and expects image/text output like svg, png, txt, or utxt via local Java + plantuml.jar.
7