热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

分层架构、六边形架构、CQRS架构模式解读

DDDDDD(DomainDrivenDesign,领域驱动设计)作为一种软件开发方法,它可以帮助我们设计高质量的软件模型

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端非常方便的提供可扩展的读模型;

缺陷


  • 数据有一定的延迟


推荐阅读
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • 本文介绍了在Oracle数据库中创建序列时如何选择cache或nocache参数。cache参数可以提高序列的存取速度,但可能会导致序列丢失;nocache参数可以避免序列丢失,但在高并发访问时可能导致性能问题。文章详细解释了两者的区别和使用场景。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 本文介绍了OkHttp3的基本使用和特性,包括支持HTTP/2、连接池、GZIP压缩、缓存等功能。同时还提到了OkHttp3的适用平台和源码阅读计划。文章还介绍了OkHttp3的请求/响应API的设计和使用方式,包括阻塞式的同步请求和带回调的异步请求。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • Hibernate延迟加载深入分析-集合属性的延迟加载策略
    本文深入分析了Hibernate延迟加载的机制,特别是集合属性的延迟加载策略。通过延迟加载,可以降低系统的内存开销,提高Hibernate的运行性能。对于集合属性,推荐使用延迟加载策略,即在系统需要使用集合属性时才从数据库装载关联的数据,避免一次加载所有集合属性导致性能下降。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • 本文介绍了一种轻巧方便的工具——集算器,通过使用集算器可以将文本日志变成结构化数据,然后可以使用SQL式查询。集算器利用集算语言的优点,将日志内容结构化为数据表结构,SPL支持直接对结构化的文件进行SQL查询,不再需要安装配置第三方数据库软件。本文还详细介绍了具体的实施过程。 ... [详细]
  • ElasticSerach初探第一篇认识ES+环境搭建+简单MySQL数据同步+SpringBoot整合ES
    一、认识ElasticSearch是一个基于Lucene的开源搜索引擎,通过简单的RESTfulAPI来隐藏Lucene的复杂性。全文搜索,分析系统&# ... [详细]
  • SQL Server 内存中OLTP内部机制概述(一)
    内存中OLTP(项目名为“Hekaton”)是一个新的完全集成到SQLServer中的数据库引擎组件。它专为访问内存常驻数据的OLTP工作负荷而进行优化。内存中OLTP有助于OLT ... [详细]
  • 本文总结了初学者在使用dubbo设计架构过程中遇到的问题,并提供了相应的解决方法。问题包括传输字节流限制、分布式事务、序列化、多点部署、zk端口冲突、服务失败请求3次机制以及启动时检查。通过解决这些问题,初学者能够更好地理解和应用dubbo设计架构。 ... [详细]
author-avatar
手机用户2502858065
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有