臃肿的事件

最近在一个事件驱动的遗留系统中看到一个“有趣”的现象,该系统不持久化业务处理的上下文,而是通过事件来传播, 据说当时这样设计的原因是希望尽可能少访问数据库。 这样能提升多少性能我不知道,但是一个明显的副作用是事件内容非常臃肿, 其中某些信息在十几个事件中传递,仅仅是为了业务流程的最后一步服务。 而当需求变更需要一些额外数据时,就需要修改相关事件传播路径上的所有事件订阅者,逐步将信息传递到后续流程中去。

你无法猜测谁会订阅事件

如果因为新的订阅者有特殊需求而去扩充事件的内容,那么事件驱动带来的松耦合的好处就被抵消了。 作为事件发布者,你无法也不应该去猜测会有谁来订阅你的事件以及它会怎样处理这个事件。

那么作为事件订阅者,如果需要额外的信息该怎么办呢?假设一个遗留订单系统会在订单付款后发布一个事件:

OrderWasPayedEvent {
    trackingId: #a8adfsa,
    totalAmount: 1000
}

现在需要扩展一个用例:当订单已付款后,为客户增加订单金额等额的积分。 事件本身已经包含了订单金额,但是没有包含客户信息,所以无法直接根据时间内容进行处理。 一种常见的解决方案是根据已知的时间内容去查询需要的数据,比如在这个例子中,可以根据trackingId找到订单, 再根据订单找到客户信息。

class MemberCreditHandler {
    void handle(OrderPayedEvent event) {
        OrderDto order = orderQuery.findBy(event.trackingId());
        String memberId = order.getMemberId();
        ……
    }
}

叙事六要素

那么事件应该包含哪些内容?可以参考叙事六要素的改编版:时间、对象、人物、原因、经过和结果。

结果:事件本身就是结果,在内容中可以再加一些细节

时间:时间自不必说了,什么时候发生的总要说明白吧

人物:考虑到审计需求,可以将事件的发起人记录下来并发布

对象:可以通过事件名称并包含标示来描述是领域中哪个对象发生了变化

经过:如果需要溯源,可以考虑将一些计算过程记录下来并发布,比如有时候会把事件发生之前的状态记录下来:

AddressCorrectedEvent {
    customer: 'WallE'
    before: ‘火星’,
    after: '木星'
}

原因:这个不是必须的,但是包含了原因的事件具有很高的业务分析价值,比如同样是订单取消,其原因可能不同:

## 由于客户没有在预定时间付款导致订单被自动取消
OrderCanceledAsOverdueEvent extends OrderCanceledEvent {
}

## 由于客户主动要求取消
OrderCanceledByCustomerEvent extends OrderCanceledEvent {
}


comments powered by Disqus
© Copyright2014-2021