2015年10月23日星期五

Event Sourcing - ENode(三) - DoPeter

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
Event Sourcing - ENode(三) - DoPeter  阅读原文»

接上一篇

http://www.cnblogs.com/dopeter/p/4903328.html

老板昨天在第二篇介绍中回复代码和文字无法一一对应。为了更好的让老板为大家解惑,把第二篇最后的猜测的问题搞清楚后,就补上其他文字说明的代码图。

在上篇中泛泛介绍了Commanding,比较跳跃,目前是想到哪写到哪,后续分门别类的整理。

在后续中会补全ENode框架的装配关系,其实作者的接口命名已经非常清楚了。

无论作者使用了什么样的装配的设计模式,目的都是为了更好的扩展与维护。一般能直接组合的就直接组合,能隔离的就直接隔离,如果遇到无法处理的情况有句经典的话,当我们遇到无法解决的问题的时候,就增加一个中间层来专门解决这个问题。其实和现实中是很匹配的,还是借鉴了现实的智慧啊。同事A,同事B、我,当A与B在一件事有争执的时候,那我就充当Broker的角色吧,如果这件事非常重要,很有可能出现矛盾,于是我可能会求助他人,例如同事C或者领导,让他们分别与A、B沟通,又也许我会集中精神,在A的面前与B的面前使用不同的方式来沟通,那么就是Proxy了。呵呵,想到了就写下来了。继续正题。

EDA


目前很火的架构模型,这里的Event不是DDD中的Domain Event,纯粹是指这种架构风格的Event,当然某些情况下我们可以这么认为,不过这里先不套用。

假如现在在一个分布式的系统中,有2个子系统实例,ServiceA与ServiceB。

假如ServiceA的某一个功能是创建Account,ServiceB的某一个功能是发送邮件。

现在有个系统级的功能是当一个Account被创建后,向这个Account发送邮件。

那么ServiceA创建好Account后,会通知ServiceB发送邮件。它们之间会进行通讯。

Event可以认为是一种它们之间传递消息的模型,例如ServiceA创建了一个Account并发布一个Event叫做OnAccountCreated,当然在我们实际的实现中,会被描述成各种不同的类,在ENode中这个Event消息被作者定义为Message,Message就是一个Event,在一个Event里面会包含这个Event的信息,例如OnAccountCreated事件里面包含了新增账户的邮箱。

还有另外一种模型,就是我们传统实现的模型,可以借鉴CQRS的Command/Query,例如ServiceA创建了一个Account,调用ServiceB的发送邮件的方法,调用发送邮件就是一个Command,ServiceA命令ServiceB发送邮件。

是不是觉得有点像Pub/Sub与Request/Reply,对应的实现是RPC和MOM。

可以这么理解,但也不完全是。

一般我们使用Request/Reply的RPC框架,两边定义契约,假如我们这里的契约是面向功能,例如ServiceB定义一个SendMail(Account account);当ServiceA完成创建Account后就可以调用这个接口发送邮件。

如果我们使用Request/Reply能不能实现Event呢,完全可以,ServiceA使用RPC框架发送一个OnAccountCreated的事件至ServiceB,在OnAccountCreated事件中包含了Account的信息,ServiceB开始发送邮件。Event的处理有很多优雅实现,最简单暴力的方式是直接规定一个通用接口,根据传递过来的事件处理逻辑。

问题就在这里,使用RPC我们需要知道对方的契约,即便是REST我们也要知道URL即资源地址。通过Event的Wrapper我们实际上是消除了RPC当中双方的契约,REST这边的资源地址。我们的契约面向的是什么角度,也就是解耦了什么角度,例如如果我们契约是业务型契约,就是解耦了业务也可以认为是功能,ServiceA不用知道ServiceB有发送邮件的功能。ServiceA只需要发送一个Event至ServiceB即可,也许不是OnAccountCreated事件。

这样通过Event我们实现了消息层面的解耦,本质上Event是一个消息的抽象。Event包裹了真实的业务消息,再次证明中间层这句话的适用性。不过这句话还有后面半截,这里不扯远了。

但是这时ServiceA是必须知道ServiceB的,MOM可以不让ServiceA不必知道ServiceB。

所以MOM+Event实现了分布式的EDA,对ServiceA来说,不用知道在创建完Account后下一步需要做什么,由谁来做。通过MOM来隔离主从关系。

从另外一个角度来看Request/Reply以及Pub/Sub。

Request/Reply发送Command,还是刚才的场景,ServiceB需要返回结果或者不返回结果,从组件运行层面来看,它是同步的。我们可以这样实现,使用Event,例如ServiceA发送OnAccountCreated事件至ServiceB,ServiceB发送OnMailSendCompleted事件至ServiceA,如果这个业务场景ServiceA必须得到邮件发送成功才能执行后续操作,那么仍然是同步的,另外种解释我们可以认为是同步非阻塞的。

Pub/Sub+Event,我们也就可以认为是异步非阻塞的。

那么EDA的真正威力就体现出来,可扩展性,吞吐量,都会上升。

EDA In ENode


说了很多EDA的话题,还是来ENode里面看看作者的实现。

前面介绍过,ENode分布式的粒度,在开发者应用层范围内定义了4种Event :Application Message、Command、Domain Event、Exception,可以将他们认为是EDA中Event在CQRS框架场景中的实例,其实这么说并不准确,应该是在ENode框架中作者定义好要用Event包裹的框架消息类型。EDA的组合还会有个MOM,则是作者自己实现并开源的EQueue。下图中所示的不是EQueue项目,实际上是EQueue在ENode项目中的Proxy。

一目了然,分别是4种消息的Pub/Sub。让我们看看其中一个的实现,就更清楚了。

先看最简单的ApplicationMessage

Consumer 消费者,Subscribe(string topic);订阅一个主题,这里就能够看到MOM的存在。

IQueueMessageHandler.Handle(QueueMessage queueMessage, IMessageContext context)方法,Consume还充当了一个Dispatcher的角色。

Publisher 发布者,PublishAsync方法,发布一个消息,这里为什么没有Topic呢,如下图主题。

作者已经拆分出去了,在每一个子系统里面可以定义开始所说4种消息类型的不同。我们可以自由的组合,例如将DDD中的应用层定义为一个Project,Domain定义为一个Project,横向或者纵向的扩展都是可行的,其实可以对应目前比较火的一种架构模型,微服务。

让我们继续看看Command

Consumer 消费者

CommandMessage EQueue传递的信息,如下图所示

注意ReplyAddress,这是后面介绍要用到的属性。

CommandExecutedMessage 已经被处理过的Command消息

CommandResultProcessor 命令结果处理者 在这里不仅包括Command被处理的结果,还可以处理当前这条Command触发的Domain Event处理的结果,如下图所示。

可以通过CommandReplyType判别是Command还是DomainEvent的回执。

执行一个Command并获得这条Command处理的结果,是在执行一个Command时指定的。如下图

这里实际上就是前面介绍EDA所描述的Request/Reply+Event的方式,可以是同步非阻塞也可以是异步非阻塞,如下图所示

一般都遵守CQRS的原则,Command无返回并且是异步,作者这里通过Fututre的方式来获取Command执行的回执,针对的是一定要得到结果之后再继续下面逻辑的场景。有时可能也想直接得到结果,例如上图所示。这也是作者考虑到很多场景但不生搬硬套CQRS。

作者实现回执的方式如下面2张图所示

Consumer接收到消息后,处理完毕后,如果发现CommandMessage中的ReplyAddress存在,则通过SendReplyService向这个地址发送回执消息。

剩下的Domain Event和Exception也是差不多的Pub/Sub的组成。就不一一介绍了。


本文链接:Event Sourcing - ENode(三),转载请注明。

关于 Swift 中的 Array.contains 方法 - SwiftCafe  阅读原文»

Swift 2.0 中对语言进行了又一次的改进,这次将整个语言变得更加面向对象化,比如在 Swift 1.x 中如果要判断某个元素是否在数组中,就需要用到 contains 函数:

if contains(array, value) {
...
}

而在 Swift 2.0 中,contains 被作为 Array 的一方法来使用了。所以我们只需这样调用即可:

if array.contains(value) {
...
}

这种方式,更加具有面向对象思维。也更符合广大开发者朋友的习惯~

另外,contains 函数还提供了另外一种参数重载,也可以叫做 predicate 模式,方法签名如下:

contains(predicate: (Self.Generator.Element) throws -> Bool )

这个方法接受一个闭包类型的参数,这个闭包的作用就是用来判断某个元素是否符合我们的判断条件,然后返回相应的 Bool 值。比如这样:


let numbers = [1,2,3,4,5,6,7];
numbers.contains { (element) -> Bool in

if element % 3 == 0 {
return true
}else {
return false
}

}

上面的代码中,我们声明了一个数组,里面包含了 7 个数字,然后我们调用 contains 方法,传入了一个 predicate, 里面进行了一个简单的判断,如果数组中,有 3 的倍数,就返回 true

很显然,我们的数组中的, 3 和 6 都是符合条件的数字,所以这个 contains 调用的返回值肯定就是 true 了。

这个例子虽然简单,但展示了 Swift 闭包和语法的灵活性。

接下来,咱们再看一个更贴近我们应用的例子,比如我们在维护一个图书列表,我们想知道这个列表中有没有图书有更新章节,可以用一个简单的模型来表示图书:


class Book {

var title:String?
var hasUpdate:Bool = false


init(title:String, hasUpdate:Bool) {

self.title = title
self.hasUpdate = hasUpdate

}

}

然后,我们就可以用 predicate 的方式来直接进行判断了,不在需要 for 循环了:

var bookList: = ()
bookList.append(Book(title:"Objective-C", hasUpdate:false))
bookList.append(Book(title:"Cocoa", hasUpdate:false))
bookList.append(Book(title:"Swift", hasUpdate:true))

bookList.contains { (book: Book) -> Bool in

return book.hasUpdate

}

本文链接:关于 Swift 中的 Array.contains 方法,转载请注明。

阅读更多内容

没有评论:

发表评论