做者:张晓龙\
来源:https://www.jianshu.com/p/a77...java
在讨论DDD分层架构的模式以前,咱们先一块儿回顾一下DDD和分层架构的相关知识。程序员
DDD(Domain DrivenDesign,领域驱动设计)做为一种软件开发方法,它能够帮助咱们设计高质量的软件模型。在正确实现的状况下,咱们经过DDD完成的设计偏偏就是软件的工做方式。面试
UL(Ubiquitous Language,通用语言)是团队共享的语言,是DDD中最具威力的特性之一。无论你在团队中的角色如何,只要你是团队的一员,你都将使用UL。因为UL的重要性,因此须要让每一个概念在各自的上下文中是清晰无歧义的,因而DDD在战略设计上提出了模式BC(BoundedContext,限界上下文)。UL和BC同时构成了DDD的两大支柱,而且它们是相辅相成的,即UL都有其肯定的上下文含义,而BC中的每一个概念都有惟一的含义。算法
一个业务领域划分红若干个BC,它们之间经过Context Map进行集成。BC是一个显式的边界,领域模型便存在于这个边界以内。领域模型是关于某个特定业务领域的软件模型。一般,领域模型经过对象模型来实现,这些对象同时包含了数据和行为,而且表达了准确的业务含义。spring
从广义上来说,领域便是一个组织所作的事情以及其中所包含的一切,表示整个业务系统。因为“领域模型”包含了“领域”这个词,咱们可能会认为应该为整个业务系统建立一个单一的、内聚的和全功能式的模型。然而,这并非咱们使用DDD的目标。正好相反,领域模型存在于BC内。数据库
在微服务架构实践中,人们大量地使用了DDD中的概念和技术:编程
分层架构的一个重要原则是每层只能与位于其下方的层发生耦合。分层架构能够简单分为两种,即严格分层架构和松散分层架构。在 严格分层架构中,某层只能与位于其直接下方的层发生耦合,而在 松散分层架构 中,则容许某层与它的任意下方层发生耦合。后端
分层架构的好处是显而易见的。首先,因为层间松散的耦合关系,使得咱们能够专一于本层的设计,而没必要关心其余层的设计,也没必要担忧本身的设计会影响其它层,对提升软件质量大有裨益。其次,分层架构使得程序结构清晰,升级和维护都变得十分容易,更改某层的具体实现代码,只要本层的接口保持稳定,其余层能够没必要修改。即便本层的接口发生变化,也只影响相邻的上层,修改工做量小且错误能够控制,不会带来意外的风险。浏览器
要保持程序分层架构的优势,就必须坚持层间的松散耦合关系。设计程序时,应先划分出可能的层次,以及此层次提供的接口和须要的接口。设计某层时,应尽可能保持层间的隔离,仅使用下层提供的接口。 关于分层架构的优势,Martin Fowler在《Patterns of Enterprise Application Architecture》一书中给出了答案:缓存
“金无足赤,人无完人”,分层架构也不可避免具备一些缺陷:
在每一个BC中为了凸显领域模型,DDD中提出了分层架构模式。最近几年,笔者在实践DDD的过程当中,也常用分层架构模式,本文主要分享DDD分层架构中比较经典的三种模式。
Eric Evans在《领域驱动设计-软件核心复杂性应对之道》这本书中提出了传统的四层架构模式,以下图所示:
笔者在四层架构模式的实践中,对于分层的本地化定义主要为:
说明:严格意义上来讲,User Interface指的是用户界面,Restful消息和配置文件解析等处理应该放在Application层,User Interface层没有的话就空缺。但User Interface也能够理解为用户接口,因此将Restful消息和配置文件解析等处理放在User Interface层也行。
James O. Coplien和Trygve Reenskaug在2009年发表了一篇论文《DCI架构:面向对象编程的新构想》,标志着DCI架构模式的诞生。有趣的是James O.Coplien也是MVC架构模式的创造者,这个大叔一生就干了两件事,即年轻时创造了MVC和年老时创造了DCI,其余时间都在思考,让我辈可望不可即。
面向对象编程的本意是将程序员与用户的视角统一于计算机代码之中:对提升可用性和下降程序的理解难度来讲,都是一种恩赐。但是虽然对象很好地反映告终构,但在反映系统的动做方面却失败了,DCI的构想是指望反映出最终用户的认知模型中的角色以及角色之间的交互。
传统上,面向对象编程语言拿不出办法去捕捉对象之间的协做,反映不了协做中往来的算法。就像对象的实例反映出领域结构同样,对象的协做与交互一样是有结构的。协做与交互也是最终用户心智模型的组成部分,但你在代码中找不到一个内聚的表现形式去表明它们。在本质上,角色体现的是通常化的、抽象的算法。角色没有血肉,并不能作实际的事情,归根结底工做仍是落在对象的头上,而对象自己还担负着体现领域模型的责任。
人们心目中对“对象”这个统一的总体却有两种不一样的模型,即“系统是什么”和“系统作什么”,这就是DCI要解决的根本问题。用户认知一个个对象和它们所表明的领域,而每一个对象还必须按照用户心目中的交互模型去实现一些行为,经过它在用例中所扮演的角色与其余对象联结在一块儿。正由于最终用户能把两种视角合为一体,类的对象除了支持所属类的成员函数,还能够执行所扮演角色的成员函数,就好像那些函数属于对象自己同样。换句话说,咱们但愿把角色的逻辑注入到对象,让这些逻辑成为对象的一部分,而其地位却丝绝不弱于对象初始化时从类所获得的方法。咱们在编译时就为对象安排好了扮演角色时可能须要的全部逻辑。若是咱们再聪明一点,在运行时才知道了被分配的角色,而后注入恰好要用到的逻辑,也是能够作到的。
算法及角色-对象映射由Context拥有。Context“知道”在当前用例中应该找哪一个对象去充当实际的演员,而后负责把对象“cast”成场景中的相应角色(cast这个词在戏剧界是选角的意思,此处的用词至少符合该词义,另外一方面的用意是联想到cast在某些编程语言类型系统中的含义)。
在典型的实现里,每一个用例都有其对应的一个Context 对象,而用例涉及到的每一个角色在对应的Context 里也都有一个标识符。Context 要作的只是将角色标识符与正确的对象绑定到一块儿。而后咱们只要触发Context里的“开场”角色,代码就会运行下去。
因而咱们有了完整的DCI架构(Data、Context和Interactive三层架构):
面向对象建模面临的一个棘手问题是数据边界和行为边界每每不一致。遵循模块化的思想,咱们经过类将行为和其紧密耦合的数据封装在一块儿。可是在复杂的业务场景下,行为每每跨越多个领域对象,这样的行为若是放在某一个对象中必然会致使别的对象须要向该对象暴漏其内部状态。因此面向对象发展的后来,领域建模出现两种派别之争,一种倾向于将跨越多个领域对象的行为建模在领域服务中。若是这种作法使用过分,则会致使领域对象变成只提供一堆get方法的哑对象,这种建模结果被称之为贫血模型。而另外一派则坚决的认为方法应该属于领域对象,因此全部的业务行为仍然被放在领域对象中,这样致使领域对象随着支持的业务场景变多而变成上帝类,并且类内部方法的抽象层次很难一致。另外因为行为边界很难恰当,致使对象之间数据访问关系也比较复杂,这种建模结果被称之为充血模型。
关于多角色对象,举个生活中的例子:
人有多重角色,不一样的角色履行的职责不一样:
这里人(大对象)聚合了多个角色(小类),人在某种场景下,只能扮演特定的角色:
引入DCI后,DDD四层架构模式中的Domain层变薄了,之前Domain层对应DCI中的三层,而如今:
笔者在实践中,将这五层的本地化定义为:
DDD五层架构模式讨论完了吗?故事尚未结束…
笔者参与的不少DDD落地实践,都是面向控制面或管理面且消息交互比较多的系统。这类系统的一次业务,包含一组同步消息或异步消息构成的序列,若是都放在Context层,会致使该层的代码比较复杂,因而咱们考虑:
所以,在面向控制面或管理面且消息交互比较多的系统中,DDD分层架构模式就变成了六层,以下图所示:
笔者在实践中,将这六层的本地化定义为:
事务层的核心是事务模型,事务模型的框架代码通常放在基础设施层。关于事务模型,笔者之前分享过一篇文章— 《Golang事务模型》 ,感兴趣的同窗能够看看。
综上所述,DDD六层架构能够看作是DDD五层架构在特定领域的变体,咱们统称为DDD五层架构,而DDD五层架构与传统的四层架构相似,都是限定型松散分层架构 。
有一种方法能够改进分层架构,即依赖倒置原则(Dependency Inversion Principle,DIP),它经过改变不一样层之间的依赖关系达到改进目的。
依赖倒置原则由Robert C. Martin提出,正式定义为:
高层模块不该该依赖于底层模块,二者都应该依赖于抽象。
抽象不该该依赖于细节,细节应该依赖于抽象。
根据该定义,DDD分层架构中的低层组件应该依赖于高层组件提供的接口,即不管高层仍是低层都依赖于抽象,整个分层架构好像被推平了。若是咱们把分层架构推平,再向其中加入一些对称性,就会出现一种具备对称性特征的架构风格,即六边形架构。六边形架构是AlistairCockburn在2005年提出的,在这种架构中,不一样的客户经过“平等”的方式与系统交互。须要新的客户吗?不是问题。只须要添加一个新的适配器将客户输入转化成能被系统API所理解的参数就行。同时,对于每种特定的输出,都有一个新建的适配器负责完成相应的转化功能。
六边形架构也称为端口与适配器,以下图所示:
六边形每条不一样的边表明了不一样类型的端口,端口要么处理输入,要么处理输出。对于每种外界类型,都有一个适配器与之对应,外界经过应用层API与内部进行交互。上图中有3个客户请求均抵达相同的输入端口(适配器A、B和C),另外一个客户请求使用了适配器D。假设前3个请求使用了HTTP协议(浏览器、REST和SOAP等),然后一个请求使用了AMQP协议(好比RabbitMQ)。端口并无明确的定义,它是一个很是灵活的概念。不管采用哪一种方式对端口进行划分,当客户请求到达时,都应该有相应的适配器对输入进行转化,而后端口将调用应用程序的某个操做或者向应用程序发送一个事件,控制权由此交给内部区域。
应用程序经过公共API接收客户请求,使用领域模型来处理请求。咱们能够将DDD战术设计的建模元素Repository的实现看做是持久化适配器,该适配器用于访问先前存储的聚合实例或者保存新的聚合实例。正如图中的适配器E、F和G所展现的,咱们能够经过不一样的方式实现资源库,好比关系型数据库、基于文档的存储、分布式缓存或内存存储等。若是应用程序向外界发送领域事件消息,咱们将使用适配器H进行处理。该适配器处理消息输出,而上面提到的处理AMQP消息的适配器则是处理消息输入的,所以应该使用不一样的端口。
咱们在实际的项目开发中,不一样层的组件能够同时开发。当一个组件的功能明确后,就能够当即启动开发。因为该组件的用户有多个,而且这些用户的侧重点不一样,因此须要提供多个不一样的接口。同时,这些用户的认识也是不断深刻的,可能会屡次重构相关的接口。因而,组件的多个用户常常会找组件的开发者讨论这些问题,无形中下降了组件的开发效率。
咱们换一种方式,组件的开发者在明确了组件的功能后就专一于功能的开发,确保功能稳定和高效。组件的用户本身定义组件的接口(端口),而后基于接口写测试,并不断演进接口。在跨层集成测试时,由组件开发者或用户再开发一个适配器就能够了。
尽管六边形架构模式已经很好,可是没有最好只有更好,演变没有尽头。在六边形架构模式提出后的这些年,又依次衍生出三种六边形架构模式的变体,感兴趣的读者能够点击连接自行学习:
本文先和读者一块儿回顾了DDD和分层架构的相关知识,而后将DDD分层架构中经常使用的三种模式(四层架构、五层架构和六边形架构)结合实践经验分别进行详细阐述,使得读者深入理解DDD分层架构模式,以便在微服务的开发实践中根据具体状况选择最合适的DDD分层架构模式,从而交付结构清晰且易维护的软件产品。
近期热文推荐:
1.600+ 道 Java面试题及答案整理(2021最新版)
2.终于靠开源项目弄到 IntelliJ IDEA 激活码了,真香!
3.阿里 Mock 工具正式开源,干掉市面上全部 Mock 工具!
4.Spring Cloud 2020.0.0 正式发布,全新颠覆性版本!
以为不错,别忘了随手点赞+转发哦!