Conference架构概述
先贴一下Conference案例的在线地址,UI由于彻底拿了微软的实现,因此都是英文的,之后我有空再改成中文的。html
前一篇文章介绍了Conference案例的上下文划分和领域模型的设计思路,本文想介绍一下Conference案例的架构设计。我作Conference案例的出发点是为了给你们展现如何使用ENode框架来开发DDD+CQRS+ES+EDA风格的应用程序。因此,Conference案例的架构天然就是使用这个架构了。下面我展开来讲一下:node
- DDD:是软件设计的一种方式,出发点是经过领域模型封装业务逻辑和业务规则,解决领域内的复杂业务问题;
- CQRS+ES:命令查询分离的架构,经过将命令查询分离,作处处理业务逻辑的部分和查询的部分能够分离,方便两部分能够各自发展,不受对方约束;CQRS的实现方式主要有两种:1)共享存储的CQRS,即背后的数据库存储是一份,数据是一份,只是在代码、架构层面作到命令和查询逻辑的分离。这种方式很好的利用了CQRS的思想,同时也不会有数据一致性的问题,由于是共享存储的,因此CQ两端不须要有消息通讯,大部分应用程序用这种方式便可。2)存储分离的CQRS,这种方式主要用ES(Event Sourcing,事件溯源)技术来完全实现CQ两端的彻底分离,这种架构比较复杂。C端不存储对象的最新状态,而是存储对象产生的全部事件;让咱们要还原一个对象时,经过ES的方式来还原。而后Q端经过订阅C端产生的事件来更新读库。这种架构是一种EDA的架构,CQ两端须要经过事件来进行联系,因此是一种面向最终一致性思路的架构。那这种方式有何好处呢?它和前面我说的第一种方式的CQRS架构,我以为主要的好处是,咱们能够设计一套框架,帮咱们从架构层面解决并发问题、消息的幂等处理问题;同时结合in-memory, group commit等技术,还能大大提升系统的吞吐量以及抵御高峰的能力(由于消息能够堆积);从而可让开发人员不用关心技术问题,专心实现业务逻辑和设计业务流程便可。而共享存储的CQRS架构,是须要咱们每次Command修改完聚合根以后,须要主动保存(可能经过ORM实现)聚合根的状态的。总之两种实现方式各有优缺点,关于CQRS架构的更多介绍,我博客里已经写过不少文章了,有兴趣的朋友能够进一步看看。
- EDA:这是一种事件驱动的架构,他和SOA架构属于同一个层次;EDA是事件驱动的思想,即B订阅A产生的消息来进行主动响应的思路;而SOA是一种面向服务而后经过服务之间相互调用(RPC)的思想;这是两种不一样的架构风格,咱们在不一样的场景会使用不一样的方式。ENode实现的是EDA架构,面向的是最终一致性。ENode在不少方面都体现出了EDA的思想。好比:
- ENode规定,一个Command不能同时修改两个聚合根,必须经过事件驱动的方式,先修改一个聚合根,而后该聚合根产生事件,而后另外一个聚合根订阅响应事件,再修改本身的状态,从而实现两个聚合根之间的交互。这个思路 背后的原则是:聚合内强一致性、聚合之间最终一致性。
- CQRS两端,也是最终一致性,经过事件来同步数据。C端产生的事件经过MQ被Q端订阅,而后Q端更新本身的读库,从而实现数据的最终一致性;
- 两个BC(Bounded Context,上下文)之间的数据传递,也是基于消息驱动的思想。好比支付上下文在完成支付后,会产生一个消息,而后订单上下文订阅响应该消息,实现上下文之间的交互。
ENode架构图
也许有人没看过ENode架构图,呵呵。我这里再贴一下,谁若是要进一步了解ENode的架构设计,能够看我博客中的其余关于ENode框架的介绍文章。git

Conference项目结构介绍
 |
前篇文章中咱们了解到,Conference案例共有三个上下文,分别是:会议管理(ConferenceManagement)、订单处理(Registration)、订单支付(Payment)。关于这个案例,微软的实现和个人实现有所不一样,但上下文的划分是一致的。微软的实现中,三个上下文用的技术架构是不一样的。github
- ConferenceManagement,因为只是后台管理系统,业务逻辑相对不是很复杂,因此,采用的是普通的三层结构;
- Registration,因为是整个系统的整个Conference系统的核心,业务逻辑和流程都比较负责,因此使用的是CQRS+ES的架构;
- Payment,因为Conference这个项目只是一个案例,因此其实没有真正实现支付的功能,只是简单示范了一下功能,因此这个上下文的业务逻辑也不太复杂。可是因为访问量也比较大,每一个订单都会须要支付,因此也是采用的CQRS的架构,可是由于业务逻辑不复杂(不须要在存储层面也分离),因此没有采用ES技术。Payment聚合根里产生事件,最后Repository保存聚合根的最新状态,而后经过事件总线发布事件,通知Registration上下文订单支付结果。
上面我简要分析了一下微软的实现。我以为微软的实现仍是很是有参考价值的,由于它充分展现了DDD中不一样的BC能够采用不一样的技术架构实现,而后经过EDA的总体架构,来实现3个BC之间的数据交互。很是棒。数据库 下面我说一下ENode实现的Conference案例。微信 前面说过,本案例主要是为了展现ENode框架的使用,因此我给这3个BC都是使用ENode框架实现,因此每一个BC都是采用的DDD+CQRS+ES的技术架构。因为有ENode框架的支持,因此代码实现还不算复杂,可让开发只须要专一于业务逻辑的实现便可,不须要关心消息传递,消息不丢失,消息幂等处理,并发问题,C端数据持久化等技术问题。这些技术问题若是没有框架支持,要由应用开发人员本身实现,是颇有难度的。经过这个案例实践下来,基本能够证实ENode框架是能够被使用来开发出一个可实际使用的项目的,这点我目前颇有信心。架构 采用ENode框架开发一个BC的实现的时候,咱们通常须要定义如下的一些工程: 并发
- Commands,定义全部的命令;
- CommandHandlers,定义全部的CommandHandlers;
- Domain,就是对应DDD领域层;
- ReadModel,表示CQRS的Query Side的实现,主要包括query service的实现,以及event handler用户更新读库;
- Messages,定义了当前BC全部对外的消息,其余BC能够订阅这些消息;消息都是DTO。
- MessagePublishers,该项目的职责是订阅当前BC内部的Domain Event,而后转换为Message,而后把Message发布出去,从而实现通知到其余的BC。之因此要定义出Message这种DTO,是由于,我认为DDD中的Domain Event最好不要跨BC,由于Domain Event是属于当前Domain的东西,Domain Event中可能会包括当前Domain里定义的各类值对象;若是直接发布到其余BC,就会致使BC的边界不清。
- Repositories,这种工程的做用就是实现Domain里可能定义的仓储接口。为什么使用ENode框架后,还须要定义仓储接口?由于有些状况下,咱们须要有一些二级索引的检查,好比建立会议时,咱们要判断会议的某个属性是惟一的,可是ES不支持二级索引,而咱们又不能经过查询读库来判断惟一性。因此咱们须要在C端设计一些存储聚合根索引信息的仓储,用来支持二级索引。左边的图里我把项目名称命名为Repositories.Dapper,说明这个仓储是用Dapper来实现的。若是咱们用EF来实现,那能够命名为Repositories.EF。
- ProcessManagers,这种项目是用来承载CQRS架构中的Saga的,即流程管理器。Saga的做用是对业务流程进行建模。Saga的原理也是事件驱动,一个Saga会响应事件,而后发送命令。经过事件+命令串联的方式实现事件驱动架构的业务流程。Saga(ProcessManager)是无状态的,全部的状态应该都在聚合根里。这点我和微软的Saga的设计也是不一样的,有兴趣的朋友能够看一下微软的实现。
另外,上面介绍了单个BC内部可能出现的项目,这些项目是我经过作这个案例后总结出来的以为能够给开发者作参考的相互划分方式。你们若是以为这样的方式很差,能够本身决定如何划分。经过上面的划分,咱们的顶层Web项目只须要依赖简单的Commands,ReadModel这两个项目,就能够实现命令的发送和读库数据的查询了。而BC之间的交互,另外一个BC只须要依赖当前BC的Messages项目就行,作到了最小的依赖。app 最后介绍一下剩余的几个顶层项目:框架 Conference案例有两个Web项目,分别为用户提供Conference的后台管理和前台订单预约的Web界面。这个不须要多解释了应该。而后,还有3个ProcessorHost项目。这3个项目分别是3个BC负责处理后台业务逻辑的顶层宿主项目。它们只须要用控制台应用或者Windows服务的方式启动便可(案例里的实现同时支持这两种方式的启动,会自动识别当前的应用程序类型)。这3个后台服务,它们都是从EQueue订阅消息,而后处理消息的方式,实现本身的功能。因此它们的惟一数据来源就是EQueue。那EQueue消息队列的服务端是哪一个呢?就是最下面的MessageBroker,这个项目承载了整个系统的消息中心,也就是EQueue的Broker。全部的消息都会发送给MessageBroker,而后相关的ProcessorHost订阅相关的Topic,实现消息的消费。 |
结尾
下一篇文章打算从代码的角度,以建立一个会议为例,从前台Controller到最后更新读库的整个代码链路简单介绍一下,方便读者能对实现某个功能要写哪些代码先有一个清晰整体的认识。