应用服务 vs 领域服务 = 用例编排 vs 业务规则
一、业务问题
DDD 团队最常混淆的:应用服务(Application Service) 和 领域服务(Domain Service)。
错误 1:所有逻辑都在应用服务
- 应用服务 = 业务逻辑(应该是领域层)
- 实体 = 数据结构(应该是充血)
- 等于"面向过程"
错误 2:把领域服务当应用服务
- 领域服务 = 业务规则(但被用作用例编排)
- 缺少事务管理
- 缺少权限校验Vernon 在 Ch 14 明确区分——这是 DDD 分层的核心。
二、控制论视角
应用层 = 控制器(接收目标、调节被控对象)
领域层 = 被控对象 + 控制律
类比(更精确的对位 = Ch 9 §9.4 控制器-控制律):
- 应用服务 = 控制器(接收目标、选择控制律)
- 领域服务 = 控制律(业务规则)
- 实体 = 被控对象(状态)
- 仓储 = 测量装置(读出状态)
Ch 4 §4.6 反馈更偏向"测量→反馈→调节"的链路(应用服务是反馈入口)
Ch 9 §9.4 控制器-控制律更偏向"接收目标→选择控制律→执行"Vernon 的两类服务精确对位控制论的两层:
- 应用服务 = 控制器(外部接口)
- 领域服务 = 控制律(业务实现)
钱学森 Ch 9 §9.4(最优控制):
"在进行控制设计时,首先重要的是搞清楚最优控制是否存在"——应用服务要 justify 调用哪个控制律(领域服务)。
钱学森 Ch 4 §4.6(反馈决定稳定):
"没有反馈,就没有自动控制"——应用服务是"反馈的入口"。
3.1 应用服务(Application Service)
位置:应用层
职责:用例编排
状态:无状态
依赖:仓储、领域服务、基础设施
方法粒度:用例级别(动词性)
事务边界:在此管理
特点:
- 薄
- 不含业务规则
- 管理事务、安全
- 协调多个对象3.2 领域服务(Domain Service)
位置:领域层
职责:业务规则
状态:无状态
依赖:实体、值对象
方法粒度:业务级别(领域操作)
事务边界:不管理
特点:
- 厚(业务逻辑)
- 涉及多个实体
- 不属于任何实体的业务
- 用 Ubiquitous Language 命名3.3 对比表
| 维度 | 应用服务 | 领域服务 |
|---|---|---|
| 职责 | 用例编排 | 业务规则 |
| 位置 | 应用层 | 领域层 |
| 状态 | 无状态 | 无状态 |
| 依赖 | 仓储、领域服务、基础设施 | 实体、值对象 |
| 方法粒度 | 用例(动词) | 业务(领域操作) |
| 事务边界 | 在此管理 | 不管理 |
| 薄/厚 | 薄(< 30 行) | 厚(业务规则) |
| 命名 | 技术性(SaveUser) | 业务性(RegisterUser) |
四、典型反模式
4.1 反模式 1:贫血应用 + 厚应用服务
python
# ❌ 贫血实体 + 厚应用服务
class Order:
id = None
items = []
status = None
class OrderApplicationService:
def place_order(self, customer_id, items):
# 业务逻辑全在应用服务
if not customer_id:
raise ValueError("Customer required")
order = Order()
order.id = generate_id()
order.customer_id = customer_id
order.items = items
order.status = "NEW"
# 业务规则在应用服务(错误位置)
if sum(item.price for item in items) > 10000:
raise ValueError("Order too large")
self.order_repo.save(order)问题:
- 业务逻辑散落在应用服务
- 实体是数据容器
- 不能复用业务规则
- 测试困难
4.2 反模式 2:把领域服务当应用服务
python
# ❌ 领域服务做用例编排
class TransferMoneyService:
def transfer(self, from_account_id, to_account_id, amount):
# 业务规则(应该)
if not self._is_valid_transfer(from_account_id, amount):
raise BusinessRuleViolation("Invalid transfer")
# 事务管理(应用层职责)
with self.transaction_manager:
from_account = self.account_repo.find_by_id(from_account_id)
to_account = self.account_repo.find_by_id(to_account_id)
from_account.withdraw(amount)
to_account.deposit(amount)
self.account_repo.save(from_account)
self.account_repo.save(to_account)
# 权限校验(应用层职责)
if not self.security.has_permission(...):
raise PermissionDenied()
# 事件发布(应用层职责)
self.event_publisher.publish(...)问题:
- 领域服务做了应用层的事
- 业务规则和事务/权限混在一起
- 难以测试
五、正模式
5.1 薄应用服务 + 厚领域
python
# ✓ 应用服务:只做编排
class TransferMoneyApplicationService:
def transfer(self, command: TransferMoneyCommand):
# 1. 加载聚合
from_account = self.account_repo.find_by_id(command.from_account_id)
to_account = self.account_repo.find_by_id(command.to_account_id)
# 2. 业务规则(委托给领域服务)
self.transfer_service.transfer(from_account, to_account, command.amount)
# 3. 持久化
self.account_repo.save(from_account)
self.account_repo.save(to_account)
# 4. 事件发布
self.event_publisher.publish(
MoneyTransferred(from_account.id, to_account.id, command.amount)
)
# ✓ 领域服务:只做业务规则
class TransferMoneyDomainService:
def transfer(self, from_account, to_account, amount):
# 业务规则
if not from_account.can_withdraw(amount):
raise BusinessRuleViolation("Insufficient funds")
if from_account.currency != to_account.currency:
raise BusinessRuleViolation("Currency mismatch")
# 执行业务操作
from_account.withdraw(amount)
to_account.deposit(amount)六、判断标准
6.1 何时用应用服务
信号:
- 接收外部请求(UI/API/消息)
- 协调多个对象
- 需要事务管理
- 需要权限校验
- 需要事件发布
- 用例粒度("创建一个订单")6.2 何时用领域服务
信号:
- 业务规则涉及多个实体
- 业务规则不属于任何实体
- 需要 Ubiquitous Language 表达
- 不需要事务/权限6.3 何时用实体方法
信号:
- 业务规则只涉及一个实体
- 实体的自然行为
- 应该下沉到实体七、工程实践
实战 1:判断逻辑放在哪
问 3 个问题:
1. 这段逻辑涉及几个对象?
- 1 个 → 实体方法
- 多个 → 领域服务
2. 这段逻辑是业务流程还是业务规则?
- 流程 → 应用服务
- 规则 → 领域服务/实体
3. 这段逻辑需要事务/权限吗?
- 是 → 应用服务
- 否 → 领域服务/实体实战 2:应用服务的标准模式
python
class StandardApplicationService:
def execute(self, command):
# 1. 加载
aggregate = self.repo.find_by_id(command.id)
# 2. 业务(委托)
aggregate.do_something(command.param)
# 3. 持久化
self.repo.save(aggregate)
# 4. 事件
self.event_publisher.publish_all(aggregate.events)实战 3:领域服务的标准模式
python
class StandardDomainService:
def business_operation(self, entity1, entity2, ...):
# 1. 业务规则校验
self._validate_invariants(...)
# 2. 业务操作
entity1.modify(...)
entity2.modify(...)
# 3. 返回(可选)
return result八、性能指标
| 指标 | 衡量 |
|---|---|
| 应用服务行数 | 平均 < 30 行(薄) |
| 领域服务行数 | 业务复杂度决定(可厚) |
| 业务逻辑下沉率 | 多少业务逻辑在领域层 vs 应用层 |
| 贫血实体率 | 应该 0 |
九、边界判断
何时用应用服务:
- 任何外部入口
- 任何用例
- 跨 BC 协调
何时用领域服务:
- 跨实体的业务规则
- 不属于任何实体的业务
何时用实体方法:
- 单个实体的行为
十、与其他 L 层方法论的关系
| 方法论 | 关系 |
|---|---|
| domain-service-event | 领域服务基础 |
| aggregate | 实体方法 vs 领域服务 |
| saga | 跨 BC 编排 |
| cqrs-es | 应用服务 vs 命令处理 |
| tell-dont-ask-demeter | 行为下沉到实体 |
十一、钱学森原话
Ch 4 §4.6(反馈决定稳定): "没有反馈,就没有自动控制"——应用服务是"反馈的入口"。
Ch 6 §6.7(耦合取舍): "有些耦合作用是有益的"——应用服务耦合用例是必要的。
十二、总结
应用服务 vs 领域服务 = 用例编排 vs 业务规则:
| 服务 | 职责 | 位置 |
|---|---|---|
| 应用服务 | 用例编排 | 应用层 |
| 领域服务 | 业务规则 | 领域层 |
核心原则:
- 薄应用 + 厚领域(反贫血)
- 应用服务管理事务/权限(基础设施)
- 领域服务只管业务规则
- 能下沉到实体的不下沉到服务
一句话总结:应用服务编排用例,领域服务执行业务,实体承担状态。