分层架构、六边形架构、CQRS架构模式解读
DDD
DDD(Domain Driven Design,领域驱动设计)作为⼀种软件开发⽅法,它可以帮助我们设计⾼质量的软件模型。在正确实现的情况下,我们通过DDD完成的设计恰恰就是软件的⼯作⽅式。
UL(Ubiquitous Language,通⽤语⾔)是团队共享的语⾔,是DDD中最具威⼒的特性之⼀。不管你在团队中的⾓⾊如何,只要你是团队的⼀员,你都将使⽤UL。由于UL的重要性,所以需要让每个概念在各⾃的上下⽂中是清晰⽆歧义的,于是DDD在战略设计上提出了模式BC(Bounded Context,限界上下⽂)。UL和BC同时构成了DDD的两⼤⽀柱,并且它们是相辅相成的,即UL都有其确定的上下⽂含义,⽽BC中的每个概念都有唯⼀的含义。
⼀个业务领域划分成若⼲个BC,它们之间通过Context Map进⾏集成。BC是⼀个显式的边界,领域模型便存在于这个边界之内。领域模型是关于某个特定业务领域的软件模型。通常,领域模型通过对象模型来实现,这些对象同时包含了数据和⾏为,并且表达了准确的业务含义。
从⼴义上来讲,领域即是⼀个组织所做的事情以及其中所包含的⼀切,表⽰整个业务系统。由于“领域模型”包含了“领域”这个词,我们可能会认为应该为整个业务系统创建⼀个单⼀的、内聚的和全功能式的模型。然⽽,这并不是我们使⽤DDD的⽬标。正好相反,领域模型存在于BC内。
在微服务架构实践中,⼈们⼤量地使⽤了DDD中的概念和技术:
1. 微服务中应该⾸先建⽴UL,然后再讨论领域模型。
2. ⼀个微服务最⼤不要超过⼀个BC,否则微服务内会存在有歧义的领域概念。
3. ⼀个微服务最⼩不要⼩于⼀个聚合,否则会引⼊分布式事务的复杂度。
4. 微服务的划分过程类似于BC的划分过程,每个微服务都有⼀个领域模型。
5. 微服务间的集成可以通过Context Map来完成,⽐如ACL(Anticorruption Layer,防腐层)。
6. 微服务间最好采⽤Domain Event(领域事件)来进⾏交互,使得微服务可以保持松耦合。
分层架构
分层架构的⼀个重要原则是每层只能与位于其下⽅的层发⽣耦合。分层架构可以简单分为两种,即严格分层架构和松散分层架构。在严格分层架构中,某层只能与位于其直接下⽅的层发⽣耦合,⽽在松散分层架构中,则允许某层与它的任意下⽅层发⽣耦合。
分层架构的好处是显⽽易见的。⾸先,由于层间松散的耦合关系,使得我们可以专注于本层的设计,⽽
不必关⼼其他层的设计,也不必担⼼⾃⼰的设计会影响其它层,对提⾼软件质量⼤有裨益。其次,分层架构使得程序结构清晰,升级和维护都变得⼗分容易,更改某层的具体实现代码,只要本层的接⼝保持稳定,其他层可以不必修改。即使本层的接⼝发⽣变化,也只影响相邻的上层,修改⼯作量⼩且错误可以控制,不会带来意外的风险。
要保持程序分层架构的优点,就必须坚持层间的松散耦合关系。设计程序时,应先划分出可能的层次,以及此层次提供的接⼝和需要的接⼝。设计某层时,应尽量保持层间的隔离,仅使⽤下层提供的接⼝。
关于分层架构的优点,Martin Fowler在《Patterns of Enterprise Application Architecture》⼀书中给出了答案:
1. 开发⼈员可以只关注整个结构中的某⼀层。
2. 可以很容易的⽤新的实现来替换原有层次的实现。
3. 可以降低层与层之间的依赖。
4. 有利于标准化。
5. 利于各层逻辑的复⽤。
“⾦⽆⾜⾚,⼈⽆完⼈”,分层架构也不可避免具有⼀些缺陷:
1. 降低了系统的性能。这是显然的,因为增加了中间层,不过可以通过缓存机制来改善。
2. 可能会导致级联的修改。这种修改尤其体现在⾃上⽽下的⽅向,不过可以通过依赖倒置来改善。
六边形架构
六边形架构或六⾓形架构是Alistair Cockburn在2005年提出,解决了传统的分层架构所带来的问题,实际上它也是⼀种分层架构,只不过不是上下或左右,⽽是变成了内部和外部
六边形架构⼜称为端⼝-适配器,这个名字更容器理解。六边形架构将系统分为内部(内部六边形)和外部,内部代表了应⽤的业务逻辑,外部代表应⽤的驱动逻辑、基础设施或其他应⽤。内部通过端⼝和外部系统通信,端⼝代表了⼀定协议,以API呈现。⼀个端⼝可能对应多个外部系统,不同的外部系统需要使⽤不同的适配器,适配器负责对协议进⾏转换。这样就使得应⽤程序能够以⼀致的⽅式被⽤户、程序、⾃动化测试、批处理脚本所驱动,并且,可以在与实际运⾏的设备和数据库相隔离的情况下开发和测试。
六边形架构解决了什么问题
传统的分层架构具有⼴泛的应⽤,例如经典的三层架构,把系统分为表⽰层、业务逻辑层、数据访问层。在Martin Fowler的《企业应⽤架构模式》⼀书中做过深⼊阐述,本书04年出版,时⾄今⽇分层架构仍然是常⽤的设计⽅法,分层架构可以降低耦合、提⾼复⽤、分⽽治之,但同时也还是存在⼀些问题:
1. 分层不清晰、相互依赖严重,系统⽆法⽅便地进⾏⾃动化测试。
2. 另外应⽤程序之间的相互驱动变得很困难,有时甚⾄不可能的
3. 传统的分层架构是⼀维的结构,有时应⽤不光是上下的依赖,可能是多维的依赖,这时⼀维的结构就⽆法适应了。
优势
不论⽤户端交互问题还是服务端数据库编程问题,其同源原因就是在设计和实现过程中出现的业务逻辑混淆,以及与外部实体之间的交互关系。我们要关注的⾮对称性不是应⽤的"左边"和"右边",⽽是它的"内部"和"外部",该属于"内部"的代码就不要泄露到"外部"去,其基础理论还是那些基本的设计原则。
六边形架构的核⼼理念是:应⽤通过"端⼝"跟外部进⾏交互的。"端⼝"让⼈联想到操作系统的端⼝,任何符合协议的设备都可以被插到相应的端⼝上。端⼝的协议是为了两个设备之间能够进⾏通信⽽设计的,位于OSI 7层协议模型中的传输层。
对应⽤来说,API就是协议。对于每⼀个外部设备,都有对应的适配器把API转换成⾃⼰所需要的信号,反之亦然。⽤户图形界⾯就是⼀个很好的例⼦,它是把⽤户操作映射到端⼝API的适配器,还有其它的例⼦如⾃动测试套件,批处理驱动器,以及任何需要跨应⽤程序交互的代码。常用微服务架构
对于应⽤的数据处理层⾯,应⽤通过与外部实体交互得到数据,这⾥⽤到的协议⼀般指数据库协议。从应⽤⾓度来看,把SQL数据库迁移到普通的⽂件或者其它类型的数据库,API仍然保持不变。适应于同⼀个端⼝的适配器还包括SQL适配器,⽂件适配器,以及更重要的数据库mock,它可以是驻存在内存⾥的数据库,不⼀定是真实的数据库。
很多应⽤都只有两个端⼝:⽤户端端⼝和数据库端端⼝。这种情况看起来是对称的,很⾃然地就会⽤单维度多层次的架构来构建它。在开发应⽤程序时,就是我们经常看到的三层、四层或五层架构。这种架构有两个问题:
1. 很容易跨越层间的边界,把业务逻辑渗透到其它层中去。
2. 有的应⽤可能不⽌需要两个端⼝,所以不能⽤单维度架构来构建。
这就是六边形架构提出的原因,它着重解决对称性问题,应⽤通过端⼝与外部进⾏交互,⽽外部的实体也可以⽤同样的⽅式来处理。六边形架构强调以下两点:
⾸先,通过"内外"的不对称性以及端⼝的特点,摆脱单维度多层次架构的束缚。可以定义不同数量的端⼝,2个,3个或者4个,这⾥说的六边形不限于只有六个边, 可以根据需要加⼊更多的端⼝和适配器,"六边形架构"只是视觉上的⼀种叫法。
其次,关注整体架构的结果导向,⼀个端⼝对应⼀个或⼀组有⽬的交互⾏为。但⼀个端⼝⼀般会有多个适配器,可以是⽆⼈应答机,语⾳留⾔机,按键电话,⽤户图形界⾯,测试套件,批处理驱动器,HTTP接⼝,程序之间的接⼝,mock的数据库,或者真实的数据库。
从应⽤层⾯来看,这⼀架构的⽬的是将注意⼒聚焦在内外⾮对称性上,让外部的实体从应⽤视⾓来看都
是⼀样的。
关注的点
关注点
对于分层架构中层次的界定,Martin Fowler给出了⼀个判定的⽅法,就是如果把表⽰层换成其他实现,如果和原来的表⽰层有重复实现的内容,那么这部分内容就应该放到业务逻辑层。那么如何让开发⼈员在系统设计过程中始终保持这种视⾓,传统的分层架构是难以做到的。六边形架构有⼀个明确的关注点,从⼀开始就强调把重⼼放在业务逻辑上,外部的驱动逻辑或被驱动逻辑存在可变性、可替换性,依赖具体技术细节。⽽业务逻辑相对更加稳定,体现应⽤的核⼼价值,需要被详尽的测试。
外部可替换
⼀个端⼝对应多个适配器,是对⼀类外部系统的归纳,它体现了对外部的抽象。应⽤通过端⼝为外界提供服务,这些端⼝需要被良好的设计和测试。内部不关⼼外部如何使⽤端⼝,从⼀开始就要假定外部使⽤者是可替换的。六边形的六并没有实质意义,只是为了留⾜够的空间放置端⼝和适配器,⼀般端⼝数不会超过4个。适配器可以分为2类,“主”、“从”适配器,也可称为“驱动者”和“被驱动者”。
⾃动测试
在六边形架构中,⾃动化测试和⽤户具有同等的地位,在实现⽤户界⾯的同时就需要考虑⾃动化测试。它们对应相同的端⼝。六边形架构不仅让⾃动化测试这件事情成为设计第⼀要素,同时⾃动化测试也保证应⽤逻辑不会泄露到⽤户界⾯,在技术上保证了层次的分界。
依赖倒置
六边形架构必须遵循如下规则:内部相关的代码不能泄露到外部。所谓的泄露是指不能出现内部依赖外部的情况,只能外部依赖内部,这样才能保证外部是可以替换的。对于驱动者适配器,就是外部依赖内部的。但是对于被驱动者适配器,实际是内部依赖外部,这时需要使⽤依赖倒置,由驱动者适配器将被驱动者适配器注⼊到应⽤内部,这时端⼝的定义在应⽤内部,但是实现是由适配器实现。
CQRS架构
CQRS本⾝只是⼀个读写分离的思想,全称是:Command Query Responsibility Segregation,即命令查询职责分离。⼀个命令表⽰⼀种意图,表⽰命令系统做什么修改,命令的执⾏结果通常不需要返回;⼀个查询表⽰向系统查询数据并返回。另外⼀个重要的概念就是事件,事件表⽰领域中的聚合根的状态发⽣变化后产⽣的事件,基本对应DDD中的领域事件;
CQRS架构的核⼼出发点是将整个系统的架构分割为读和写两部分,从⽽⽅便我们对读写两端进⾏分开优化;
CQRS架构的⼀致性模型为最终⼀致性。
采⽤CQRS架构的⼀个前提是,你的系统要接受系统使⽤者查询到的数据可能不是最新的,⽽是有⼏个毫秒的延迟。之所以会有这个前提,是因为CQRS架构考虑到,作为⼀个多⽤户同时访问的互联⽹应⽤,当在⾼并发修改数据的情况下,⽐如秒杀、12306购票等场景,⽤户UI上看到的数据总是旧的。⽐如你秒杀时提交订单前看到库存还⼤于0,但是当你提交订单时,系统提⽰你宝贝卖完了。这个就说明,在这种⾼并发修改同⼀资源的情况下,任何⼈看到的数据总是Stale的,即旧的。
实现⽅式
1.最常见的CQRS架构是数据库的读写分离;
2.底层存储不分离,上层逻辑代码分离;
3.各⾃分离,底层存储分离,上层逻辑代码分离。
Q端的数据,由C端采⽤Event Sourcing(简称 ES)模式技术进⾏同步,所有 C 端的最新数据全部⽤ Domain Event 表达即可,⽽要查询显⽰⽤的数据,则从 Q 端的 ReadDB(关系型数据库)查询即可。
同步⽅式有两种:同步或异步,如果需要 CQ 两端的强⼀致性,则需要⽤同步;如果能接受 CQ 两端数据的最终⼀致性,则可以使⽤异步。
CQRS架构的适⽤场景
1. 应⽤的写模型和读模型差别⽐较⼤时
2. 对系统的查询性能和写⼊性能分开进⾏优化时,尤其是读/写⽐⾮常⾼的系统,CQ分离是必须的。
3. 当希望我们的系统同时满⾜⾼并发的写、⾼并发的读的时候;因为CQRS架构可以做到C端最⼤化的写,Q端⾮常⽅便的提供可扩展的
读模型;
缺陷
数据有⼀定的延迟
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论