通过前面的文章我们总结了几个常见的actor设计模式,但此处不得不提前介绍一下在Akka中消息的设计模式。随着对Akka的使用,我们会发现,使用Akka设计系统其实就是面向消息编程。actor之间消息设计的是否合理,往往意味着Akka应用设计的是否合理。那么actor之间的消息该如何设计呢?
命令和事件
actor之间都是通过“消息”进行通信的,对各种各样的“消息”进行分析,我们可以把它简单的分为命令和事件两类。命令,是指一个actor给另外一个actor发送指令做相关的业务逻辑;事件,则是actor对某个命令的响应结果,或者对其他事件的响应结果。简单来说,命令是主动让actor做出某种响应,例如发送Stop命令让其他actor销毁自己;事件,往往意味着某种通知,例如actor销毁之前发送Stopped事件给它的supervisor。
我们在用Akka开发应用时候,一定要仔细认真的把系统间的消息按照定义划分为命令和事件。具体如何划分可以参考这两种消息的具体定义或DDD(领域驱动设计)相关概念。此处我们只介绍在代码结构上如何设计它们。
trait Message{ // 消息创建的时间 final val at:Long = System.currentTimeMillis() } trait Command extends Message trait Event extends Message trait MasterCommand extends Command{ // 定义MasterCommand公有的方法或字段 } object MasterCommand{ case class StartWork(message:String) extends MasterCommand } trait MasterEvent extends Event{ // 定义MasterEvent公有的方法或字段 } object MasterEvent{ case class WorkStarted(message:String) extends MasterCommand }
上面是我在开发时常用的设计消息的框架。其中Message这个trait表示对消息的抽象,其中只有一个at字段,是消息创建的时间。Command继承Message,表示对命令的抽象。Event也继承Message,表示对事件的抽象。MasterCommand这个trait继承Command标志master发出的命令。所有master发出的具体命令放在与MasterCommand这个trait同名的object中。当然也可以不同名,只要在一个object中即可。把master具体命令放到一个object中,只是为了对其进行名称的限定。如果其他类型的actor也有一个StartWork命令,在同一个actor中要对这两个StartWork同时做响应的话,会引起歧义。所以我一般都用object来进行隔离,当然是用package或者把StartWork改名也都可以对其进行隔离,这个就是个人爱好了。MasterEvent这个特质表示master收到某个命令或事件进行响应的事件,同样也是master发出的。
上一段话中,我用红色字体标志出了“发出”二字。这意味着,不管是命令还是事件,都是针对发送actor而言的。具体什么意思呢?这其实是一个代码设计的问题,但我觉得比较重要,这里需要单独解释一下。我们知道actor之间是通过消息传递来通信的,那么消息传递一个有一个发送方和接收方。在设计代码时,我们是把消息放到发送方的域中,还是放到接收方的域中呢。比如StartWork这个命令,如果放到masterActor中,就意味着master发出了一个StartWork这个命令,具体是谁收到该消息就不关心了,接收方一定知道该消息是masterActor发出的。如果放到workActor中,就意味着workActor一定会接收该命令,对其作出反应,具体是谁发送的就不关心了。对于事件可以做类比理解。
具体是按照发送方还是按照接收方封装消息,这个就仁者见仁智者见智了,只不过我比较推崇上面代码中的设计方案。因为我们可以围绕actor设计上下文边界,不会对其他领域内的对象进行引用,高内聚低耦合。
其实我们还可以通过消息名称来区分,比如有两个actor都会发出StartWork的消息,那么可以分别用StartType1Work和StartType2Work来命令。但这种方式有个问题就是没法按照类型对不同的命令进行筛选,比如我们可以使用match来对不同类型的消息进行过滤,如果消息是用消息名称进行区分的,在偏函数或者match时是很不方便的,关于这一点希望大家自己好好体会。
其实对于上图中的代码,我一般都会优化成下面的形式。就是把master相关的命令和事件上面再加一个抽象层:master消息。其实都差不多,只不过层次更深而已。优点是可以单独修改公共字段或方法,缺点是设计复杂。
trait Message{ // 消息创建的时间 final val at:Long = System.currentTimeMillis() } trait Command extends Message trait Event extends Message trait MasterMessage extends Message{ // 定义Master消息共有的方法或字段 } trait MasterCommand extends MasterMessage with Command{ // 定义MasterCommand公有的方法或字段 } trait MasterEvent extends MasterMessage with Event{ // 定义MasterEvent公有的方法或字段 } object MasterCommand{ case class StartWork(message:String) extends MasterCommand } object MasterEvent{ case class WorkStarted(message:String) extends MasterCommand }
关于Akka消息的设计模式就介绍到这里了,在Akka中消息设计的好坏往往决定着应用的质量,希望大家多多实践,多多体会。当然了每个人的设计理念不同,这里也只是抛砖引玉,如果你有更好的设计模式,欢迎留言讨论。