Eric Evans 的《领域驱动设计》,不是一种具体架构模式,而是一套思维方式,告诉你怎么划分边界、怎么建模。它可以和上面任何架构模式结合使用
背景
在最近的项目开发中,我的项目体量来到了一个更大的层面,且业务需求改动频率也变高了。我也首次被自己写的“屎山”代码绊住脚,修改一个需求,新加一个东西,发现只能用全局变量硬塞。
所以借这个机会深度了解了一下软件架构,以此作记录。
介绍

DDD 是一套软件设计方法论,是一套思维方式,内心先有思路,再去学习我认为会好很多,所以先学习 DDD。
核心思想
软件的设计应该由业务领域(Domain)来驱动,用业务概念来组织代码,而不是用技术概念。
方法论
战略设计(Strategic Design)
战略设计解决架构问题,决定系统怎么拆,更重要。
通用语言(Ubiquitous Language)
定义: 追求开发和业务用同一套术语,代码里的命名直接反映业务语言
Note
你有没有遇到过这种情况 — 业务说"把这个款下架",开发理解成把数据库记录删了,但业务的意思是把商品状态改成不可见。问题出在"下架"这个词,双方理解不一样。 通用语言要求团队先对齐术语。比如大家约定:
- "下架" = 商品状态改为 inactive,不删除数据
- "删除" = 物理删除记录
就比如说 sku,在电商项目里,sku ID 必须约定好,平台生成的 sku 叫平台 SKU,我们设定的 sku 要叫,在代码里的体现就是前者是 platform_sku 后者是 sku。
方法名、类名、模块名、甚至团队开会时嘴里说的词,都应该和代码里一致。它是一整套语言体系,不只是命名规范。
限界上下文(Bounded Context)
定义: 在这个边界里,所有术语和模型都有唯一含义,不同上下文里的同一个名字可以是完全不同的模型。
!!!这是 DDD 战略设计里最核心的概念。
Note
继续用电商业务。"商品"这个词在不同业务场景里含义完全不同:
- 商品管理 — 商品有标题、描述、图片、分类、上下架状态。运营关心的是怎么编辑、怎么展示。
- 订单履约 — 商品变成了"订单里的一行",关心的是 SKU、数量、单价、是否发货。标题描述无所谓。
- 库存管理 — 商品变成了"库存单位",关心的是在库数量、仓库位置、补货阈值。价格无所谓。
- 财务结算 — 商品变成了"收入项",关心的是成本、售价、利润率、税率。图片无所谓。
可能商品还是商品,本来就有很多很多字段, 不同业务场景里的“商品”本质上就是不同的业务概念,只不过碰巧都叫这个名字。
Bounded Context 的做法就是,让每个业务场景有自己的 Product,有多个 Product 类,只包含自己需要的类。
所以在限界上下文思维的指导下,自然整个项目会先按照业务场景分文件夹,每个业务场景有自己所需的数据(哪怕这些数据都可以归为 Product 类),但是每个业务场景会自己定义一个只包含自己所需的类。
在实践过程中,通过 Application 的调用来分配传入每个类自己所需的东西。
上下文映射(Context Map)
Context Map 本身不是代码,它是一张架构图,画在白板或文档里,帮你理清上下文之间的关系。真正落到代码里的是各种集成方式,其中最常用的就是防腐层。理想状态每个项目可以先花心思做设计,设计图就是这玩意。
描述不同限界上下文之间的联系和通讯的。
- 上游/下游(Upstream/Downstream)
数据提供方是上游 消费方是下游
例如,Excel 模板分析、Excel 填写是一对上下游
- 防腐层(Anti-Corruption Layer, ACL)
对接外部系统的中间层,将外部信息格式对应到内部系统,让信息格式解耦。
可以单独放个 acl 目录,里面都是做防腐的。
ACL 的职责就是把外部数据结构转成自己上下文的领域模型,比如把 Temu API 返回的 JSON 转成自己定义的 Order 对象
- 共享内核(Shared Kernel)
两个上下文共用一小部分模型,尽量少用
子域(Subdomain)
把整个业务领域拆成核心域、支撑域、通用域,决定资源投入的优先级
避免过度设计带来的开销,先做优先级,判断资源倾斜。
- 核心域(Core Domain)
例如我们的印花生成、Excel 生成,值得精心设计做更好的架构,且需要经常维护更新。
- 支撑域(Supporting Subdomain)
能用就行。
- 通用域(Generic Subdomain)
用谁的都行,目的是有这个。 第三方,比如说什么支付功能。
战术设计(Tactical Design)
实体(Entity)
有唯一 ID、有生命周期的业务对象(就像一个衣服工厂里的一个订单,从下单开始创建,跟着业务一直到发货,收到款,订单结束)
值对象(Value Object)
没有 ID、只关心属性值的对象(可能是用来封装一系列数据的对象,例如描述订单里的商品,可能这个商品对象就包含了数量、颜色等等,商品需要独立追踪,所以应该是 Entity),例如地址(Adress),信息本身比较复杂,可能会需要校验等,适合封装成值对象。
# 这不是 Value Object,这只是一个普通数据容器
data = {"province": "广东", "city": "深圳", "detail": "南山区xxx"}
# 这是 Value Object
class Address:
def __init__(self, province: str, city: str, detail: str):
self.province = province
self.city = city
self.detail = detail
def __eq__(self, other):
"""相等性通过属性值判断,不是通过 ID"""
return (
self.province == other.province
and self.city == other.city
and self.detail == other.detail)
def full_address(self) -> str:
"""可以有业务方法"""
return f"{self.province}{self.city}{self.detail}"
只要是不只是一个普通变量,有含义或者内容丰富了 不需要通过 ID 追踪身份,且有业务含义值得封装(有校验规则、有组合属性、或有业务方法)时,封装成 Value Object。
聚合(Aggregate)
Aggregate 是一组紧密关联的对象(Entity + Value Object)的集合,作为一个整体来维护业务规则的一致性。外部代码不能直接修改聚合内部的对象,必须通过聚合根来操作。
聚合就是一系列对象的集合,例如订单对象和订单项对象,在业务上二者紧密联系,且有唯一入口 订单。
聚合根(Aggregate Root)
聚合根就是聚合的唯一入口,就比如说订单,所有订单里面的订单项、计算订单信息等,都只能通过订单作为唯一入口。
因为聚合根负责在每次操作时保证内部业务规则不被破坏。
仓储(Repository)
聚合的持久化接口,隔离数据库细节
定义: Repository 是聚合根的持久化接口。它让领域层觉得"我只是在从一个集合里存取对象",完全不需要知道背后是数据库、文件、API 还是 Excel。
Repo 代表一个聚合根 Repo 为聚合根提供存取服务,这个聚合根可能有不同来源,不同来源再用子类来区分,核心是业务需求,子类是不同来源或者其他。
关键约束:
- 一个聚合根对应一个 Repo。
- repo 要说业务语言,名字和行为对应的是业务,而不是技术
领域服务(Domain Service)
不属于任何单个 Entity 的业务逻辑
定义: 当一段业务逻辑不自然地属于任何一个 Entity 或 Value Object 时,放到 Domain Service 里。
Service 就是处理逻辑,真正写判断做计算的地方,如果能放在 Entity 里就放 Entity 里,如果放 Entity 需要引入别的 Entity,那就单开一个 Domain Service。
领域事件(Domain Event)
领域事件就是这个业务场景发生后,会有一系列需要做事情,而且这一系列事情是动态的,有可能会变化,如果传统的写成 pipeline,那这个地方的代码就需要根据需求经常改。
领域事件的作用就是让这个地方只是发布个状态,或者修改某个状态,让其他依赖这个状态的业务逻辑自己监听并更新。
如果业务逻辑不会经常修改,就不需要领域事件,写死一样用。重点要看业务逻辑是否有需要需改的需求。
工厂(Factory)
封装复杂对象的创建逻辑(专门用于构建“订单”或者“商品”的对象)
为了做出这个聚合需要很多校验或者其他事,多了就专门搞个 Factory。
判断标准: 构造函数超过 5-6 个参数、创建时有校验逻辑、或者需要根据条件组装不同结构 → 考虑 Factory。否则 → 直接构造。
总结
战略设计是业务文件夹,战术设计是对每个业务文件夹里业务实现的思路。
完整项目的开发思路
| 步骤 | 做什么 | 目的 / 产出 |
|---|---|---|
| 1. 确定业务场景 | 梳理业务,识别不同的业务场景和职责边界 | 划出 Bounded Context,创建顶层业务文件夹 |
| 2. 画 Context Map | 明确各上下文之间谁是上游谁是下游、怎么通信 | 产出架构图,指导上下文之间的集成方式 |
| 3. 划分子域 | 判断每个上下文是核心域、支撑域还是通用域 | 决定每个上下文的设计精力投入:核心域全套 DDD,支撑域从简,通用域用第三方 |
| 4. 统一通用语言 | 和业务对齐术语,约定每个上下文内的命名 | 确保代码里的类名、方法名直接反映业务语言 |
| 5. 识别 Entity 和 Value Object | 在每个上下文内分析:哪些对象需要 ID 追踪,哪些只关心值 | 产出 models 目录,明确哪些是 Entity、哪些是 Value Object |
| 6. 划定聚合边界 | 找出哪些对象必须一起保持一致性,确定聚合根 | 明确每个聚合的入口和内部结构 |
| 7. 定义 Repository 接口 | 为每个聚合根定义持久化接口(ABC) | 产出 repositories 目录,一个聚合根一个 Repository |
| 8. 确定数据源和防腐层 | 分析每个 Repository 背后的数据来源,外部系统加 ACL | 产出 infrastructure 的具体实现 + acl 目录 |
| 9. 识别 Domain Service | 找出不属于任何单个 Entity 的跨聚合业务逻辑 | 产出 services 目录 |
| 10. 识别 Domain Event | 找出"发生后需要触发多个可变后续动作"的业务节点 | 产出 events 目录,解耦后续动作 |
| 11. 按需添加 Factory | 聚合创建逻辑复杂时才抽 Factory | 产出 factories 目录(不复杂就不建) |
| 12. 编写 Application 层 | 编排各上下文的协作,不含业务逻辑 | 产出 application 目录,每个用例一个文件 |
根据开发思路的一个理想规范项目的架构
project/
│
├── shared_kernel/ # 共享内核:跨上下文共用的极少量基础定义
│ ├── sku_id.py # Value Object:SKU ID 的格式定义和校验
│ └── money.py # Value Object:金额(数值+币种)
│
│
│ ============ 以下每个目录 = 一个 Bounded Context(限界上下文)============
│ ============ 顶层按业务场景分,不按技术层分(战略设计)============
│
│
├── catalog/ # 限界上下文:商品管理(核心域 → 完整 DDD 战术设计)
│ ├── models/ # 领域模型
│ │ ├── product.py # Entity:有唯一 ID、有生命周期的商品
│ │ ├── product_category.py # Value Object:分类信息,无 ID,按值比较
│ │ └── price_rule.py # Value Object:定价规则
│ ├── aggregates/ # 聚合:product 是聚合根,外部只能通过它操作内部对象
│ │ └── product_aggregate.py # Product(聚合根)+ 关联的 Value Object 们
│ ├── services/ # 领域服务:不属于任何单个 Entity 的业务逻辑
│ │ └── pricing_strategy_service.py
│ ├── events/ # 领域事件:业务上有意义的事发生了,解耦后续动作
│ │ └── product_listed_event.py # "商品上架了" → 后续可能要同步到其他渠道
│ ├── factories/ # 工厂:创建聚合的逻辑复杂时才用
│ │ └── product_factory.py
│ └── repositories/ # 仓储:聚合根的持久化接口,隔离存储细节
│ └── product_repository.py # 接口(ABC),不是具体实现
│
│
├── fulfillment/ # 限界上下文:订单履约(核心域 → 完整 DDD 战术设计)
│ ├── models/
│ │ ├── order.py # Entity + 聚合根:订单,唯一入口
│ │ └── order_item.py # Entity:订单项,只能通过 Order 访问和修改
│ ├── services/
│ │ └── shipping_cost_service.py # 领域服务:计算运费需要多个聚合的数据
│ ├── events/
│ │ └── order_paid_event.py # 领域事件:"订单付款了" → 通知仓库、发邮件等
│ ├── acl/ # 防腐层:把外部系统数据转成自己的领域模型
│ │ └── temu_order_translator.py # Temu API JSON → 自己的 Order 模型
│ └── repositories/
│ └── order_repository.py # 接口(ABC)
│
│
├── asset/ # 限界上下文:素材管理(支撑域 → 简单设计够用就行)
│ ├── models/
│ │ └── product_asset.py # 这里的"商品"只有 sku_id + 图片 URL
│ ├── services/ # 和 catalog 里的 Product 是完全不同的类
│ │ └── image_download_service.py # 同名不同义 = Bounded Context 的体现
│ └── repositories/
│ └── asset_repository.py
│
│
├── reporting/ # 限界上下文:报表(支撑域 → 不需要完整 DDD)
│ ├── excel_generator.py # 直接写,不需要 Entity/Aggregate
│ └── report_config.py
│
│
├── infrastructure/ # 基础设施层:所有接口的具体实现
│ ├── persistence/
│ │ ├── sqlite_product_repository.py # 实现 catalog 的 ProductRepository
│ │ └── sqlite_order_repository.py # 实现 fulfillment 的 OrderRepository
│ ├── external_api/
│ │ └── temu_api_client.py # Temu HTTP 调用,纯技术
│ └── storage/
│ └── r2_asset_repository.py # 实现 asset 的 AssetRepository(Cloudflare R2)
│
│
└── application/ # 应用层:编排各上下文,不含业务逻辑
├── create_order_use_case.py # 用例:协调 fulfillment + catalog
├── export_excel_use_case.py # 用例:协调 catalog + reporting
└── sync_images_use_case.py # 用例:协调 asset + external API