Skip to content

用例编排 = 薄应用服务的标准模式 ​

一、业务问题 ​

应用服务是 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(通信 = "事件"步骤)

从名家方法论与工程化思路中蒸馏出自己的工程体系。