EventBus
接下来我们看一个 Actor 的应用:EventBus。在异步处理场景下,运用最为广泛的消息处理模式即是 Pub-Sub 模式。基于 Pub-Sub 模式,还可以根据不同的场景衍生出特殊的模式,例如针对一个 Publisher 和多个 Subscriber,演化为 Broadcast 模式和 Message Router 模式。
EventBus 则通过引入总线来彻底解除 Publisher 与 Subscriber 之间的耦合,类似设计模式中的 Mediator 模式。总线就是 Mediator,用以协调 Publisher 与 Subscriber 之间的关系。对于 Publisher 而言,只需要把消息发布给 EventBus 即可;对于 Subscriber 而言,只需要在 EventBus 注册需要处理的事件并实现处理流程即可。
在没有使用 EventBus 的时候,Publisher 必须显式的调用 Subscriber 的方法。例如订单支付成功后,必须在订单处理模块调用积分模块处理积分,调用服务号模块进行通知。而且这样的显示调用会越来越多,每次都要去修改订单模块加一个调用。这样订单处理模块和那些模块就都紧密耦合在一起了。我们看看 EventBus 怎么解决这个问题。
EventBus 定义
要使用 Akka EventBus, 首先要实现一个 EventBus 接口。
trait EventBus {
type Event
type Classifier
type Subscriber
//#event-bus-api
def subscribe(subscriber: Subscriber, to: Classifier): Boolean
def unsubscribe(subscriber: Subscriber, from: Classifier): Boolean
def unsubscribe(subscriber: Subscriber): Unit
def publish(event: Event): Unit
//#event-bus-api
}
如上所示:
- Event 就是需要发布到总线上的事件
- Classifier 分类器用于对订阅者进行绑定和筛选
- Subscriber 注册到总线上的订阅者。
所幸的是,我们不需要要从头实现 EventBus 接口,Akka 提供了一个 LookupClassification 帮助我们实现 Pub-Sub 模式,我们要做的最主要就是实现 publish 方法。
class XrEventBus extends EventBus with LookupClassification {
type Event = XrEvent
type Classifier = XrEventType
type Subscriber = ActorRef
override protected def publish(event: Event, subscriber: Subscriber): Unit = {
subscriber ! event
}
// 其他方法...
}
可以看到:
- Event 的类型是我们自己定义的 XrEvent。
- 分类起是基于 XrEventType,也就是事件类型的。我们系统中定义了很多时间类型,例如 XrEventType.ORDER_PAID 是订单支付事件,XrEventType.DOC_REGISTERED 是用户注册事件。
- Subscriber 其实就是一个 Actor。
- Publisher 只是简单的将 Event 作为一个消息发布给所有 Subscriber。
事件发布和订阅
Subscriber 这边则需要实现对事件的处理。
class ScoreEventHandler extends Actor with Logging {
override def receive = {
// 订单支付成功
case XrEvent(XrEventType.ORDER_PAID, order: OrderResponse) =>
// 处理订单支付成功事件
// 处理其他事件
}
}
然后我们通过调用 EventBus.subscribe 进行事件订阅。
val eventBus = new XrEventBus
// 积分事件处理模块
val scoreEventHandler = XingrenSingletons.akkaSystem.actorOf(
Props[ScoreEventHandler], name = "scoreEventHandler"))
eventBus.subscribe(scoreEventHandler, XrEventType.ORDER_PAID)
// 订阅其他事件..
// 微信服务号事件处理模块
val weixinXrEventHandler = XingrenSingletons.akkaSystem.actorOf(
Props[WeixinXrMessageActor], name = "weixinXrEventHandler"))
eventBus.subscribe(weixinXrEventHandler, XrEventType.ORDER_PAID)
// 订阅其他事件..
最后,我们的订单处理模块只需要调用 EventBus.publish 发布订单支付事件就好了。至于那些需要处理该事件的模块,自然会去订阅这个事件。上面 XrEventBus 的实现里可以看到,发布其实就是用 Actor 的消息发送机制,将消息发布给了所有的 Subscriber。
XrEventBus.publish(XrEventType.ORDER_PAID, new OrderResponse(order, product))
至此,我们的订单处理模块和积分处理模块、微信服务号模块就安全解耦了,很漂亮不是吗?