用例编排 = 薄应用服务的标准模式
一、业务问题
应用服务是 DDD 架构的入口,承载所有用户用例。但很多团队把"用例"做错:
- 把业务逻辑放在应用服务(贫血)
- 不管理事务(数据不一致)
- 不处理权限(安全漏洞)
- 不知道如何处理跨 BC 查询
Vernon 在 Ch 14 给出应用层的标准模式。
二、控制论视角
应用服务 = 控制律的"调度中心"
领域服务 = 控制律的"具体实现"
实体 = 被控对象
仓储 = 测量装置
事件 = 跨子系统通信
应用服务的 4 步标准模式(按控制论节点分别对位):
1. 加载(从仓储) → Ch 15 信号测量(读出状态)
2. 业务(调用领域) → Ch 9 最优控制(选择控制律)
3. 持久化(保存) → Ch 4 §4.6 反馈(状态反馈回系统)
4. 事件(发布) → Ch 20 通信(跨子系统通信)每步都有明确的控制论对位:
- 加载 = 测量(Ch 15)
- 业务 = 最优(Ch 9)
- 持久化 = 反馈(Ch 4)
- 事件 = 通信(Ch 20)
Vernon 原话:
"Application services are thin. They just coordinate. Business logic lives in the domain layer."
钱学森 Ch 4 §4.6(反馈决定稳定):
"没有反馈,就没有自动控制"——应用服务的"持久化"步骤就是反馈环节。
钱学森 Ch 15(信号测量):
"测量是被控对象状态反馈给控制律的关键环节"——应用服务的"加载"步骤就是测量。
钱学森 Ch 20(信息论):
"通信系统的设计有明确的极限"——应用服务的"事件"步骤就是跨子系统通信。
三、4 步标准模式
3.1 标准应用服务
python
class OrderApplicationService:
def place_order(self, command: PlaceOrderCommand):
# 1. 加载(从仓储)
customer = self.customer_repo.find_by_id(command.customer_id)
product = self.product_repo.find_by_id(command.product_id)
# 2. 业务(创建聚合 + 业务操作)
order = Order.create(customer, product, command.quantity)
# 业务规则在 Order 内(TDA)
order.place()
# 3. 持久化
self.order_repo.save(order)
# 4. 事件(发布领域事件)
for event in order.events:
self.event_publisher.publish(event)
order.clear_events()3.2 4 步详解
Step 1:加载(Load)
- 从仓储加载需要的聚合
- 不在仓储内做业务
- 一次加载所有需要的对象
Step 2:业务(Business)
- 创建/修改聚合
- 业务规则下沉到实体/领域服务
- 应用服务只调用
Step 3:持久化(Save)
- 保存所有修改
- 事务边界
- 失败回滚
Step 4:事件(Publish)
- 发布领域事件
- 异步通知其他 BC
- 清理已发布事件四、事务管理
4.1 应用服务是事务边界
原则:
- 一个应用服务方法 = 一个事务
- 跨多个仓储操作 = 同一事务
- 跨 BC 不在同一事务(最终一致)4.2 实现
python
class OrderApplicationService:
@transactional # 装饰器管理事务
def place_order(self, command):
# 所有操作在同一事务
customer = self.customer_repo.find_by_id(command.customer_id)
order = Order.create(customer, ...)
self.order_repo.save(order)
# 事务在方法返回时提交
# 异常时回滚4.3 跨 BC 事务
反模式:跨 BC 分布式事务
- 性能差
- 难调试
- CAP 不允许
正模式:Saga 模式
- 每步独立事务
- 失败时补偿
- 见 l2-design/04-patterns/saga.md五、权限管理
python
class OrderApplicationService:
def cancel_order(self, command):
# 1. 权限校验(应用层职责)
if not self.security.can_cancel_order(
self.current_user, command.order_id
):
raise PermissionDenied("Cannot cancel this order")
# 2. 加载
order = self.order_repo.find_by_id(command.order_id)
# 3. 业务
order.cancel(self.current_user) # 业务规则在实体
# 4. 持久化
self.order_repo.save(order)
# 5. 事件
self.event_publisher.publish_all(order.events)六、跨 BC 查询
6.1 场景
UI 展示订单详情:
- 订单信息(Order BC)
- 客户信息(Customer BC)
- 商品信息(Product BC)
- 物流信息(Shipping BC)6.2 方案 1:客户端组合(不推荐)
UI 直接调用多个 BC
- 多次网络请求
- 数据不一致
- 性能差6.3 方案 2:应用服务组合(推荐)
python
class OrderQueryApplicationService:
def get_order_details(self, order_id):
# 并行调用多个 BC
order = self.order_api.get_order(order_id)
customer = self.customer_api.get_customer(order.customer_id)
product = self.product_api.get_product(order.product_id)
shipping = self.shipping_api.get_shipping(order.id)
# 组合为 DTO
return OrderDetailsDTO(
order=order,
customer=customer,
product=product,
shipping=shipping
)6.4 方案 3:CQRS(高级)
读模型独立:
- 订阅多个 BC 的事件
- 维护统一的读模型
- UI 直接读
见 l2-design/04-patterns/cqrs-es.md七、DTO 使用
7.1 何时用 DTO
适用:
- 跨层传输数据
- 跨 BC API
- 避免暴露领域对象
不适用:
- 内部方法调用
- 单元测试7.2 DTO 设计
python
# 领域对象(完整)
class Order:
id, customer_id, items, status, ...
# DTO(按需)
@dataclass
class OrderSummaryDTO:
id: str
status: str
total_amount: float
# 映射
def to_dto(order: Order) -> OrderSummaryDTO:
return OrderSummaryDTO(
id=order.id,
status=order.status,
total_amount=order.get_total_amount()
)八、工程实践
实战 1:识别标准模式
检查应用服务方法:
1. 加载
2. 业务
3. 持久化
4. 事件
如果有 5+ 步 → 考虑拆分
如果有业务规则 → 下沉到领域层
如果有跨 BC → 用 Saga实战 2:避免反模式
反模式 1:业务逻辑在应用服务
# ❌
if order.items == []:
raise ValueError
order.status = "NEW"
# ✓
order = Order.create(...)
反模式 2:应用服务 = 数据库事务
# ❌
conn.begin()
...
conn.commit()
# ✓ 用 @transactional
反模式 3:应用服务做缓存
# ❌
if self.cache.has(order_id):
return self.cache.get(order_id)
# ✓ 缓存由仓储或专门层处理实战 3:异常处理
python
class OrderApplicationService:
def place_order(self, command):
try:
order = self._create_order(command)
self._save(order)
self._publish_events(order)
except DomainRuleViolation as e:
# 业务规则错误 → 400
raise BadRequest(str(e))
except ConcurrencyConflict:
# 冲突 → 409
raise Conflict("Order was modified by another user")
except Exception as e:
# 其他错误 → 500
self.logger.error(f"Place order failed: {e}")
raise九、性能指标
| 指标 | 衡量 |
|---|---|
| 应用服务行数 | 平均 < 30 行 |
| 业务下沉率 | 多少业务逻辑在领域层 |
| 事务管理 | 多少应用服务有事务管理 |
| 异常处理 | 多少应用服务有清晰的异常处理 |
十、边界判断
何时用应用服务:
- 任何用例入口
- 任何外部 API 端点
- 任何消息处理器
何时用 Saga:
- 跨 BC 业务事务
- 多步协调
- 需要补偿
何时用 CQRS:
- 读多写少
- 复杂查询
- 高性能
十一、与其他 L 层方法论的关系
| 方法论 | 关系 |
|---|---|
| application-vs-domain-service | 应用服务定位 |
| saga | 跨 BC 编排 |
| cqrs-es | 读模型 |
| aggregate | 业务聚合 |
| tell-dont-ask-demeter | 行为下沉 |
十二、钱学森原话
Ch 4 §4.6(反馈决定稳定): "没有反馈,就没有自动控制"——应用服务是反馈的入口。
Ch 9 §9.4(最优控制): "在进行控制设计时,首先重要的是搞清楚最优控制是否存在"——应用服务要 justify。
十三、总结
用例编排 = 薄应用服务的 4 步标准模式:
| 步骤 | 职责 |
|---|---|
| 加载 | 从仓储读 |
| 业务 | 委托给实体/领域服务 |
| 持久化 | 保存到仓储 |
| 事件 | 发布领域事件 |
核心原则:
- 薄应用服务(< 30 行)
- 业务下沉到领域层
- 事务管理在应用层
- 异常处理清晰
一句话总结:应用服务 = 4 步编排(加载/业务/持久化/事件)。
相关链接
L 层方法论
控制结构
业务主题
源典
- IDDD Ch 14(Application)
- 工程控制论 Ch 4 §4.6(反馈决定稳定)
- 工程控制论 Ch 15(信号测量 = "加载"步骤)
- 工程控制论 Ch 20(通信 = "事件"步骤)