开源地址:https://github.com/tangxuehua/enodehtml
上一篇文章,我给你们分享了个人一个基于DDD以及EDA架构的框架enode,可是只是介绍了一个大概。接下来我准备用不少一篇篇详细但不冗长的文章介绍每一个点。尽可能争取一次不介绍太多内容,但但愿每次介绍完后都能让你们知道这个小点的设计思想,以及为了解决的问题。node
好了,这篇文章,我主要想介绍的是EDA思想在enode框架中如何体现?git
通常的应用程序,若是一个用户动做会涉及多个聚合根的修改,咱们一般会在应用层服务中建立一个unit of work,而后,咱们可能会设计一个领域服务类,在该领域服务类里,修改多个聚合根,而后应用层服务将整个unit of work中的修改一次性以事务的方式提交到数据库。这种方式就是以事务的方式来实现涉及多个聚合根修改的强一致性。以银行转帐这个经典的场景做为分析案例:github
public interface IBankAccountService { void TransferMoney(Guid sourceBankAccountId, Guid targetBankAccountId, double amount); } public class BankAccountService : IBankAccountService { private IContextManager _contextManager; private TransferMoneyService _transferMoneyService; public BankAccountService(IContextManager contextManager, TransferMoneyService transferMoneyService) { _contextManager = contextManager; _transferMoneyService = transferMoneyService; } public void TransferMoney(Guid sourceBankAccountId, Guid targetBankAccountId, double amount) { using (var context = _contextManager.GetContext()) { var sourceAccount = context.Load<BankAccount>(sourceBankAccountId); var targetAccount = context.Load<BankAccount>(targetBankAccountId); _transferMoneyService.TransferMoney(sourceAccount, targetAccount, amount); context.SaveChanges(); } } }
一次银行转帐,最核心的动做就是源帐号转出钱,目标帐号转入钱;固然实际的银行转帐确定不是这么简单,也确定不是这么实现。我拿这个做为例子只是为了经过这个你们都熟知的简单例子来分析若是一个用户场景涉及不止一个聚合根的修改的时候,若是基于经典的DDD的方式,咱们是如何实现的。如上面的代码所示,咱们可能会设计一个应用层服务,如上面的IBankAccountService,该应用层服务里有一个TransferMoney的方法,表示用于实现银行转帐的功能;而后该应用层服务会进一步调用一个领域层的转帐领域服务,就是上面代码中的TransferMoneyService,按照Eric Evans所说,领域服务应该是一个以动词命名的服务,一个领域服务能够明确对应到领域中的一个有业务含义的领域动做,此例就是“转帐”,因此我设计了一个TransferMoneyService的以动词来命名的领域服务,该服务的TransferMoney方法实现了银行转帐的核心业务逻辑。数据库
上面这个例子中,按照经典DDD,咱们应该在应用层实现流程控制逻辑以及事务等东西;因此你们能够看到,以上代码中,咱们是先获取一个unit of work,即上面代码中的context,最后调用context.SaveChanges方法,该方法的职责就是将当前上下文的全部修改以事务的方式提交到数据库。好了,上面这个例子咱们分析了经典DDD关于如何实现一个会涉及多个聚合根新建或修改的用户场景;缓存
我一直说enode是一个基于事件驱动架构(EDA,Event-Driven Architecture)的框架。且深蓝医生在前面的回复中也对什么是事件驱动的架构有疑惑。因此我想说一下我对事件驱动架构的理解。架构
EDA,顾名思义,我以为就是事件驱动的,那事件到底驱动了什么呢?我以为就是事件驱动状态的修改。如何理解呢?就是说,假如你要修改一个对象的状态,那就不是直接调用该对象的某个方法来修改它或者直接经过修改某个对象的属性来达到修改该对象状态的目的;取而代之的是,咱们须要先触发一个事件,而后该对象会响应该事件,而后在响应函数中修改对象本身的状态。固然,更广义和权威的事件驱动架构的定义和解释,我以为很容易找啊,好比直接去百度上搜一下或直接到wikipedia上搜一下,也很容易就能找到标准的解释。好比这里就是我找到的解释。其实,更大范围的解释,就是一种publish-subscriber模式,就是有一个事件生产者产生事件,而后有一个相似event publisher的东西会把这个事件广播出去,而后全部的事件消费者就能消费该事件了。经过这样的pub-sub,咱们的应用程序的各个组件之间能够作到很完全的解耦,而且能够作到更灵活的扩展性。这两点的好处应该是很容易体会到的。好比更完全的解耦是,好比原本一个对象要和另外一个对象交互,那它可能要引用该对象,而后调用该对象的某个方法,从而实现对象之间的交互。这种实现方式会让两个对象绑定在一块儿,好比a对象调用b对象的方法,那意味着a须要依赖b对象;而经过事件驱动的方式,a对象只要publish一个事件,而后b对象响应该事件便可,这样a对象就不知道b对象的存在了,也就是a对象不在依赖b对象;扩展性,就是原本一个事件,可能只有1个事件响应者,可是后面可能因为功能扩展等缘由,咱们须要增长一个事件响应者,这样就能方便的作到在不改变原来任何代码的基础之上,增长新功能了;其余的好处就很少分析了,有兴趣的能够再去看看资料吧。并发
上面这一段,我简单介绍了我所理解的EDA,以及它的基本的好处。下面咱们看看,在enode中,咱们是如何利用EDA这种原理的。为了简化,我先用一个简单的例子说明一下,就用我源代码中的NoteSample吧,反正也能同样说明事件驱动的影子在哪里。看如下的代码:框架
[Serializable] public class Note : AggregateRoot<Guid>, IEventHandler<NoteCreated>, //订阅事件 IEventHandler<NoteTitleChanged> { public string Title { get; private set; } public DateTime CreatedTime { get; private set; } public DateTime UpdatedTime { get; private set; } public Note() : base() { } public Note(Guid id, string title) : base(id) { var currentTime = DateTime.Now; //触发事件 RaiseEvent(new NoteCreated(Id, title, currentTime, currentTime)); } public void ChangeTitle(string title) { //触发事件 RaiseEvent(new NoteTitleChanged(Id, title, DateTime.Now)); } //事件响应函数 void IEventHandler<NoteCreated>.Handle(NoteCreated evnt) { //在响应函数中修改本身的状态,这里能够体现出EDA的影子,就是事件驱动状态的修改 Title = evnt.Title; CreatedTime = evnt.CreatedTime; UpdatedTime = evnt.UpdatedTime; } //事件响应函数 void IEventHandler<NoteTitleChanged>.Handle(NoteTitleChanged evnt) { //同上解释 Title = evnt.Title; UpdatedTime = evnt.UpdatedTime; } }
上面的例子中,Note是一个聚合根,它会响应两个事件:NoteCreated, NoteTitleChanged。要实现事件响应,咱们能够经过实现框架提供的IEventHandler<T>接口,就能告诉框架,我要订阅什么事件了。分布式
上面代码中,应该比较详细的注释了每段代码的含义了,应该都能看懂吧。上面这个例子说明了,聚合跟本身的状态不是在public方法中直接改的,而是基于事件驱动的方式来修改的,因此,你们能够看到,聚合根状态的修改是在一个内部响应函数中修改的。下面咱们再来看一下外部其余对象,如何响应该事件:
//这是一个事件订阅者,它也响应了Note的两个事件 public class NoteEventHandler : IEventHandler<NoteCreated>, IEventHandler<NoteTitleChanged> { public void Handle(NoteCreated evnt) { //这里为了简单,因此只是输出了一串文字,实际咱们能够在这里作任何你想作的事情; Console.WriteLine(string.Format("Note created, title:{0}", evnt.Title)); } public void Handle(NoteTitleChanged evnt) { Console.WriteLine(string.Format("Note title changed, title:{0}", evnt.Title)); } }
经过上面两个简单的例子,不知道有没有解释清楚,在enode框架中,如何体现EDA?
总结:
我之因此比较喜欢事件驱动这种思想是基于如下理由: