在 Python 中落地领域驱动设计(DDD)的核心在于:避免“数据库驱动开发”,转向以领域模型为中心的设计。正确结合 Pydantic 与 Python 原生类(dataclass/普通类),可以有效实现限界上下文划分、聚合根与值对象的构建,并提升系统的可维护性、可扩展性与业务逻辑表达能力。

需要特别强调的是:Pydantic 应作为边界层的验证与序列化工具,而非领域模型的基类。聚合根应使用原生 Python 类封装行为与不变性,Pydantic 则负责 API 输入输出与持久化映射。
以下是修正后的完整实践指南,包括关键设计模式与示例代码。
一、限界上下文划分(Bounded Context)
DDD 强调将复杂系统拆分为多个限界上下文,每个上下文有独立的责任边界和语言模型。在 Python 中,通常使用包或模块结构划分上下文,并严格遵循依赖倒置原则。
示例:一个电商系统的推荐目录结构:
project_root/
├── domain/ # 领域层(纯Python,无外部依赖)
│ ├── orders/ # 订单限界上下文
│ │ ├── __init__.py
│ │ ├── order.py # 聚合根 + 实体
│ │ ├── events.py # 领域事件定义
│ │ └── repository.py # 仓储抽象接口
│ ├── customers/ # 客户限界上下文
│ └── products/ # 商品限界上下文
├── application/ # 应用服务层
│ ├── dto.py # Pydantic DTO(输入/输出模型)
│ └── order_service.py # 应用服务
├── infrastructure/ # 基础设施层
│ ├── persistence/ # 仓储具体实现(ORM/NoSQL)
│ └── messaging/ # 消息总线实现
关键点:每个上下文的
repository.py只定义抽象接口(Protocol/ABC),具体实现在infrastructure层,确保领域层不依赖任何框架。
二、聚合根与值对象建模
1. 值对象(Value Object)
值对象通过字段定义其语义,必须保证不可变性。金融相关字段必须使用 Decimal,严禁使用 float。
from decimal import Decimal
from pydantic import BaseModel, field_validator
class Money(BaseModel):
"""值对象:金额(不可变)"""
amount: Decimal # ✅ 必须使用 Decimal,避免浮点精度丢失
currency: str = "CNY"
model_config = {"frozen": True} # ✅ 值对象必须不可变
@field_validator('amount')
@classmethod
def validate_amount(cls, v: Decimal) -> Decimal:
if v <= 0:
raise ValueError("金额必须大于0")
return v
def __add__(self, other: "Money") -> "Money":
if self.currency != other.currency:
raise ValueError(f"币种不一致: {self.currency} vs {other.currency}")
return Money(amount=self.amount + other.amount, currency=self.currency)
2. 聚合根(Aggregate Root)
聚合根是业务中不可再分的最大实体,负责维护内部不变性。不应继承 Pydantic BaseModel,而应使用 dataclass 或普通类,将业务行为封装在方法中。
from __future__ import annotations
import uuid
from dataclasses import dataclass, field
from typing import List
from decimal import Decimal
@dataclass
class OrderItem:
product_id: str
quantity: int
unit_price: Decimal
@property
def subtotal(self) -> Decimal:
return self.unit_price * self.quantity
@dataclass
class OrderAggregate:
"""
✅ 聚合根:使用 dataclass 而非 BaseModel
- 封装业务行为,保护不变性
- 收集领域事件,不与序列化耦合
"""
id: str
customer_id: str
items: List[OrderItem] = field(default_factory=list)
status: str = "PENDING"
_domain_events: list = field(default_factory=list, repr=False)
# ✅ 通过工厂方法创建,而非直接构造
@classmethod
def create(cls, customer_id: str, items: List[OrderItem]) -> OrderAggregate:
if not items:
raise ValueError("订单至少包含一个商品")
order = cls(
id=str(uuid.uuid4()),
customer_id=customer_id,
items=items,
status="PENDING"
)
# ✅ 注册领域事件
from .events import OrderCreated
order._domain_events.append(
OrderCreated(order_id=order.id, total_amount=order.total)
)
return order
@property
def total(self) -> Decimal:
"""✅ 计算属性由聚合根自身维护,不依赖外部验证器"""
return sum((item.subtotal for item in self.items), Decimal("0"))
def confirm(self) -> None:
"""✅ 业务行为封装在聚合根内部"""
if self.status != "PENDING":
raise ValueError(f"只有待处理订单可确认,当前状态: {self.status}")
self.status = "CONFIRMED"
from .events import OrderConfirmed
self._domain_events.append(OrderConfirmed(order_id=self.id))
def collect_events(self) -> list:
"""提取并清空领域事件,供应用服务发布"""
events = self._domain_events.copy()
self._domain_events.clear()
return events
为什么不用 Pydantic BaseModel 做聚合根?
- Pydantic 模型默认是数据容器,鼓励外部直接赋值,破坏封装性;
field_validator是声明式验证,无法表达复杂的跨字段业务规则与状态流转;- 聚合根需要
collect_events()、状态机等行为,与 Pydantic 的序列化职责冲突;- Pydantic 应用于 API 层 DTO 和持久化映射,而非领域模型本身。
三、应用服务层与事务边界
聚合根不应直接暴露于 HTTP 层,应通过应用服务协调业务流程。仓储接口定义在领域层,实现在基础设施层。
# domain/orders/repository.py
from typing import Protocol, Optional
from .order import OrderAggregate
class OrderRepository(Protocol):
"""✅ 仓储接口定义在领域层(依赖倒置)"""
def save(self, order: OrderAggregate) -> None: ...
def find_by_id(self, order_id: str) -> Optional[OrderAggregate]: ...
# application/order_service.py
from decimal import Decimal
from typing import List
from domain.orders.order import OrderAggregate, OrderItem
from domain.orders.repository import OrderRepository
class OrderService:
def __init__(self, repo: OrderRepository):
self.repo = repo
def create_order(
self,
customer_id: str,
items: List[dict],
) -> OrderAggregate:
# ✅ 输入验证建议用 Pydantic DTO,此处简化演示
order_items = [
OrderItem(
product_id=i["product_id"],
quantity=i["quantity"],
unit_price=Decimal(str(i["unit_price"])) # ✅ float→Decimal
)
for i in items
]
# ✅ 通过聚合根工厂方法创建,而非在服务层拼装
order = OrderAggregate.create(customer_id=customer_id, items=order_items)
# ✅ 保存并发布事件
self.repo.save(order)
events = order.collect_events()
# TODO: 发布事件到消息总线
return order
四、领域事件机制
领域事件应定义为独立的不可变数据类,与聚合根解耦。
# domain/orders/events.py
from dataclasses import dataclass
from decimal import Decimal
@dataclass(frozen=True)
class OrderCreated:
order_id: str
total_amount: Decimal
@dataclass(frozen=True)
class OrderConfirmed:
order_id: str
关于事件溯源:初学者建议先采用「状态存储 + 领域事件发布」模式。真正的事件溯源需要按序存储所有事件、通过重放重建状态、配合快照优化,复杂度较高,不建议在未充分理解时直接使用。
五、Pydantic 的正确定位:边界防腐层
Pydantic 在 DDD 中的正确角色是边界层的验证与序列化工具,而非领域模型本身:
# application/dto.py — ✅ Pydantic 用于 API 输入/输出
from pydantic import BaseModel, Field, field_validator
from decimal import Decimal
from typing import List
class CreateOrderRequest(BaseModel):
customer_id: str
items: List[dict]
@field_validator('items')
@classmethod
def validate_items(cls, v):
if not v:
raise ValueError("订单项不能为空")
return v
class OrderResponse(BaseModel):
order_id: str
status: str
total: Decimal
model_config = {"from_attributes": True} # ✅ 支持从 dataclass 转换
六、脱离数据库驱动开发的实践路径
- 领域模型优先:先设计聚合根与值对象,再选择 ORM 或事件存储,禁止从数据库表结构反推模型;
- Pydantic 归位:仅用于 DTO、配置校验、序列化,不作为聚合根基类;
- 行为封装:业务规则写在聚合根方法中,而非 Service 层或 Validator 中;
- 值对象不可变:使用
frozen=True或dataclass(frozen=True),金额一律用Decimal; - 依赖倒置:仓储接口在领域层,实现在基础设施层,领域层零外部依赖;
- 事件驱动解耦:跨上下文通信通过领域事件,而非直接调用其他上下文的 Service。
七、总结与建议
| 实践维度 | ❌ 常见错误做法 | ✅ 推荐做法 |
|---|---|---|
| 上下文划分 | 按技术分层(models/services) | 按业务模块划分目录,每个上下文独立 |
| 聚合根建模 | Pydantic BaseModel + field_validator | dataclass/普通类 + 工厂方法 + 行为封装 |
| 值对象封装 | mutable dict / float | frozen dataclass/BaseModel + Decimal |
| 业务规则 | 声明式 validator / Service 层 | 聚合根方法 + property 计算 |
| 领域事件 | 内联未定义类 / 直接存库 | 独立 frozen dataclass + 事件收集模式 |
| Pydantic 定位 | 领域模型基类 | DTO / 序列化 / 输入验证(边界层) |
| 仓储接口 | 混入服务层或基础设施层 | 定义在领域层(Protocol/ABC),实现在基础设施层 |
| 金额类型 | float | Decimal(始终) |
最终目标:将业务逻辑封装在领域模型中,通过类型系统与不可变性保障一致性,让 Pydantic 回归其擅长的边界验证与序列化职责,避免“数据库驱动开发”与“框架绑定过深”的双重陷阱,提升系统的领域表达能力与长期可维护性。
在 Python 中的落地实践&spm=1001.2101.3001.5002&articleId=162149596&d=1&t=3&u=334706c1e1e24102b7ccef820b7dd4db)
623

被折叠的 条评论
为什么被折叠?



