本篇是“事件驱动的微服务”系列的第二篇,主要讲述事件驱动设计。如果想要了解总体设计,请看第一篇"事件驱动的微服务-总体设计"
程序流程
我们通过一个具体的例子来讲解事件驱动设计。 本文中的程序有两个微服务,一个是订单服务(Order Service), 另一个是支付服务(Payment Service)。用户调用订单服务的用例createOrder()来创建订单,创建之后的订单暂时还没有支付信息,订单服务然后发布命令(Command)给支付服务,支付服务完成支付,发送支付完成(Payment Created)消息。订单服务收到消息(Event),在Order表里增加Payment_Id并修改订单状态为“已付款”。
下面就是组件图:
事件处理
事件分成内部事件和外部事件,内部事件是存在于一个微服务内部的事件,不与其他微服务共享。如果用DDD的语言来描述就是在有界上下文(Bounded Context)内的域事件(Domain Event)。外部事件是从一个微服务发布,而被其他微服务接收的事件。如果用DDD的语言来描述就是在不同有界上下文(Bounded Context)之间传送的域事件(Domain Event)。这两种事件的处理方式不同。
内部事件:
对于内部事件的处理早已有了成熟的方法,它的基本思路是创建一个事件总线(Event Bus),由它来监听事件。然后注册不同的事件处理器(Event Handler)来处理事件。这种思路被广泛地用于各种领域。
下面就是事件总线(Event Bus)的接口,它有两个函数,一个是发布事件(Publish Event),另一个是添加事件处理器(Event Handler)。一个事件可以有一个或多个事件处理器。
type EventBus interface {
PublishEvent(EventMessage)
AddHandler(EventHandler, ...interface{
})
}
事件总线的代码的关键部分是加载事件处理器。我们以订单服务为例,下面就是加载事件处理器(Event Handler)的代码,它是初始化容器代码的一部分。在这段代码中,它只注册了一个事件,支付完成事件(PaymentCreateEvent),和与之相对应的事件处理器-支付完成事件处理器(PaymentCreatedEventHandler)。
func loadEventHandler(c servicecontainer.ServiceContainer) error {
var value interface{
}
var found bool
rluf, err := containerhelper.BuildModifyOrderUseCase(&c)
if err != nil {
return err
}
pceh := event.PaymentCreatedEventHandler{
rluf}
if value, found = c.Get(container.EVENT_BUS); !found {
message := "can't find key=" + container.EVENT_BUS + " in container "
return errors.New(message)
}
eb := value.(ycq.EventBus)
eb.AddHandler(pceh,&event.PaymentCreatedEvent{
})
return nil
}
由于在处理事件时要调用相应的用例,因此需要把用例注入到事件处理器中。在上段代码中,首先从容器中获得用例,然后创建事件处理器,最后把事件和与之对应的处理器加入到事件总线中。
事件的发布是通过调用事件总线的PublishEvent()来实现的。下面的例子就是在订单服务中通过消息中间件来监听来自外部的支付完成事件(PaymentCreatedEvent),收到后,把它转化成内部事件,然后发送到事件总线上,这样已经注册的事件处理器就能处理它了。
eb := value.(ycq.EventBus)