Skip to content

Domain Service / Domain Event(领域服务与领域事件) ​

解决什么问题:有些业务逻辑不属于任何聚合根,跨多个聚合——需要 Domain Service;有些业务状态变化需要通知其他 BC——需要 Domain Event。两者都是聚合根之间的"协调机制"。

它是什么 ​

Domain Service(领域服务):不属于任何聚合根的业务操作——封装跨聚合、跨实体的业务规则。

Domain Event(领域事件):过去发生的业务事实——发布后被订阅者消费,触发后续业务逻辑。

Vernon Ch 7: "A SERVICE is an operation offered by the domain that does not naturally fit into any ENTITY or VALUE OBJECT."

Vernon Ch 8: "Domain Events are domain occurrences that domain experts care about."

V2 视角(V2.7 B3 段 6 改造后):Domain Service = 跨聚合的控制律(调用多个聚合完成业务),Domain Event = 跨 BC 的状态广播(解耦发布者与订阅者)。

关键属性 ​

属性Domain ServiceDomain Event
位置领域层,独立类由聚合根 / Domain Service 发布
状态无状态(或会话状态)不可变(已发生的事实)
命名业务动词(TransferMoney)过去式(MoneyTransferred)
输入业务参数(聚合根、值对象)事件 ID、时间戳、聚合根 ID、关键字段
输出业务结果(可能是新聚合根、事件)仅持久化 + 发布到 EventBus
反模式写成 Application Service 风格(无状态包装)写成命令("CreateOrder" 是命令,"OrderCreated" 是事件)
核心约束不操作持久化、不调用 Repository(除非必要)不可变、不可取消、不可删除

怎么用 ​

场景 1:Domain Service(跨聚合业务规则) ​

场景:转账——一个聚合根(SourceAccount)扣钱,另一个聚合根(TargetAccount)加钱。

python
class TransferService:  # Domain Service
    def transfer(self, source_id, target_id, amount):
        # 1. 加载两个聚合根
        source = account_repo.find_by_id(source_id)
        target = account_repo.find_by_id(target_id)
        # 2. 业务规则(不属于任何单方)
        if not source.can_debit(amount):
            raise InsufficientFunds()
        # 3. 调用聚合根方法
        source.debit(amount)
        target.credit(amount)
        # 4. 发布领域事件
        self.event_bus.publish(MoneyTransferred(source_id, target_id, amount))

场景 2:Domain Event(状态广播) ​

场景:Order 聚合根状态变更(已支付),通知 Inventory BC(扣库存)和 Shipping BC(创建运单)。

python
class Order:
    def mark_paid(self, payment_id):
        self.status = "PAID"
        self.add_event(OrderPaid(self.id, payment_id))

class OrderPaid:  # Domain Event
    def __init__(self, order_id, payment_id):
        self.order_id = order_id
        self.payment_id = payment_id
        self.occurred_at = now()

# 订阅者(Inventory BC)
class InventoryHandler:
    def handle(self, event: OrderPaid):
        # 反查订单明细 + 扣库存
        ...

场景 3:Domain Event + EventBus(解耦发布与订阅) ​

Order.mark_paid() → OrderPaid event
                   ↓
        ┌──────────┴──────────┐
   [EventBus] 发布到多个订阅者
        ↓          ↓          ↓
   Inventory   Shipping   Notification
   BC:扣库存   BC:创建运单  BC:发邮件

发布者不知道订阅者存在——完全解耦。新增订阅者不需要改 Order 代码。

场景 4:Domain Service 与 Application Service 的区别 ​

维度Domain ServiceApplication Service
职责业务规则协调 / 事务 / 安全
依赖聚合根 + RepositoryDomain Service + 基础设施
层领域层应用层
测试纯单元测试集成测试

常见误区 ​

  1. "Domain Service = Application Service"——错。Domain Service 在领域层(含业务规则),Application Service 在应用层(协调事务)。详见 应用 vs 领域服务
  2. "Domain Event 是命令"——错。Event 是过去式(OrderPaid),不是祈使句(PayOrder)。命令是 Command 模式,事件是 Event 模式
  3. "Event 应该可变"——错。Event 是已发生的事实,永远不可变。如果要"修改"事实 = 发新事件(OrderPaid → OrderRefunded)
  4. "Domain Service = 工具类"——错。Domain Service 有业务含义(TransferMoney 不是 Util),工具类是没有业务含义的辅助方法
  5. "事件 = 方法调用"——错。事件是异步广播,方法调用是同步请求。混用 = 强耦合。事件必须经过 EventBus 异步派发
  6. "事件可以乱序到达"——错。事件必须有因果顺序(OrderPaid 在 OrderCreated 之后)。用全局顺序号或聚合根 ID 哈希分片保证

关键洞察 ​

Domain Service = 跨聚合的"控制律"——它不属于任何聚合根,但属于领域。

Domain Event = 跨 BC 的"广播"——它解耦发布与订阅。

两者协作模式:

  • Domain Service 内部用聚合根的方法(保证不变量)
  • 业务完成后发布 Domain Event(通知其他 BC)
  • EventBus 异步派发到订阅者(不阻塞发布者)

这是 V2 B3 分解 + 控制论 Ch 7 "协调"的工程化——跨子系统的协作 = 控制律(Domain Service)+ 状态广播(Domain Event)。

相关链接 ​

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