目录
1、软件架构概述
1.1 软件架构概念
1.2 软件架构分类
1.3 软件架构模式
1.4 软件架构风格
2、领域驱动软件架构
2.1 架构风格
六边行架构(领域驱动设计首选)
为什么选择REST架构
松耦合
可伸缩性
易用性
约束性
2.2 架构模型
命令和查询职责分离(CQRS)
大家好,我是老王随聊。筹备了一周之久的架构篇终于出炉了……
前面我们介绍了关于领域驱动设计业务战略层面的几个概念:领域、子域、界限上下文以及界限上下文映射图。那接下来我们要知道如何基于技术战略层面进行领域驱动软件架构设计。本篇主要内容包含两部分:软件架构概述和领域驱动软件架构。
下图是从软件架构层面整理的DDD架构图(完善中)。
在开始讲领域驱动架构之前,我们需要先了解关于软件架构的一些常见架构风格和模型,因为这些内容也将会在领域驱动设计中使用到,便于我们更好的了解这些架构模型在领域驱动中应该如何发挥其价值,如何集众所长。另外,即便是领域驱动设计这种新的方法论,在技术层面也依然采用现有的一些常见设计架构思想,无非就是新瓶装旧酒,关键在于酒怎么装的问题。
在软件工程领域,“架构”主要是指软件架构,其主要目的在于指导架构师和开发人员如何进行软件设计,也就是为我们提供软件系统各个方面的设计方向。
软件架构,并不是指可编码能实际落地的文档,而是一个系统的草图。它的主要工作是对一系列相关业务的一种抽象建模,把各种对象抽象成可直接构成系统应用的组件,并对各组件之间如何映射、如何通信进行了明确且细致的描述。最终,在实现编码阶段,这些抽象组件将被细化为具体的某个类或者对象。
那在领域驱动设计中,除了我们看到的软架构外,还隐含着另外一种架构—即业务架构。该业务架构的设计思想将伴随着整个业务系统的生命周期,关于业务架构方面的内容我在后面的文章会讲到。
通常一个良好的软件架构,需要具备以下特性:可靠性、安全性、扩展性、可定制化、可伸缩、可维护、易用性和市场机制这八个特点,不同的软件架构模式侧重的特点不同。
从我们日常所关注的角度来看,大体可以分为三类:逻辑架构、物理架构和系统架构。
逻辑架构指的是软件系统中元件之间的关系。比如常用的分层架构就是一种逻辑架构,每一次包含多个逻辑元件;物理架构通常是指软件系统在硬件上的部署方式。比如微服务架构、垮机房垮区域的分布式架构等;系统架构指的是系统非功能性特征,比如云架构,考虑系统的稳定性、可扩展等。
从软件架构模式角度看,大致可以划分为:分层模式(常用的标准架构)、客户端/服务模式、事件总线模式、管道和过滤器模式、微核模式、微服务模式和云模式等;
分层架构,将软件分成若干个水平层,每一层都有清晰的角色和分工,不需要知道其他层的细节,层与层之间是通过接口通信;
事件驱动架构,就是通过事件进行通信的软件架构,主要用于系统之间解耦或异步任务处理;
微核模式,也叫插件模式,指的是软件的内核相对较小,主要功能和业务逻辑都通过插件实现,主要用于提升系统组件单元的可插拔性;
云架构,主要解决系统的扩展性和并发的问题,是最容易扩展的架构;
管道和过滤器模式,是面向数据流的软件体系结构,其优点将整个系统的输入输出行为理解为单个过滤器行为的叠加与组合,目的在于将复杂问题分解,做到化繁为简的效果。
从架构风格的抽象纬度划分,常见的分布式应用架构风格有以下三种:
分布式对象(简称DO),它要解决的主要问题是位于不同进程中的对象之间的调用问题,常见的架构实例有CORBA、RMI、EJB、DCOM、NET Remoting等。
远程过程调用(简称RPC),远程过程调用采用客户机/服务器(C/S)模式。请求程序就是一个客户机,而服务提供程序就是一台服务器。和常规或本地过程调用一样,远程过程调用是同步操作,在远程过程结果返回之前,需要暂时中止请求程序。使用相同地址空间的低权进程或低权线程允许同时运行多个远程过程调用。常见的架构实例有SOAP、XML-RPC、Hessian、DWR等。WEB服务提供一个分布式函数或方法接口供用户调用,这是一种比较传统的方式。通常,在WSDL中对RPC接口进行定义(类似于早期的XML-RPC)。
表述性状态转移(简称REST),WEB服务类似于HTTP或其他类似协议,它们把接口限定在一组广为人知的标准动作中(比如HTTP的GET、PUT、DELETE)以供调用。此类WEB服务关注与那些稳定的资源的互动,而不是消息或动作。此种服务可以通过WSDL来描述SOAP消息内容,通过HTTP限定动作接口;或者完全在SOAP中对动作进行抽象。架构实例有HTTP、WebDAV。
不同架构模式和风格会之间有非常大的差别。这里暂且不聊对比差异,先了解在领域驱动设计该如何使用这些架构模式和架构风格。
我们知道,六边形架构是一种对称性的架构风格,其原理采用了端口适配器模式。其目的在于构建一种持久生命力的架构,各端口之间是一种平等方式与系统交互,是内部和外部的关系,不存在前端和后端严格划分的概念。六边形架构如下图所示。
那为什么领域驱动设计会将六边形架构作为首选?
核心原因在于,领域驱动设计与传统的分层架构相比,其希望我们将重点的工作放在领域层设计上。领域层、基础设施层只依赖由领域模型所定义的抽象接口,而客户层是一种平等的方式与业务模型进行交互。因此与传统风层结构相比,其采用了依赖倒置原则。以上的交互和依赖倒置正好与六边形架构,所以这才是真正选择六边形架构的原因。
当然,如果你的项目采用了领域驱动设计的方式,不一定必须采用六边形架构,具体采用哪种风格需要我们在做架构设计前,从性价比通盘考虑后再选择。
另外需要注意的一点,即使你的系统采用了六边形架构,那同时也依然可以在六边形架构内部使用其他类型架构。比如SOA架构、REST或者事件驱动架构,也有可能采用CQRS;或者数据网织或基于网格的分布式缓存;还有可能采用Map- Reduce这种分布式并行处理方式。六边形架构可为系统其他架构提供坚实的基础支撑,这也体现了六边行架构很好的包容性。
为什么选择SOA架构
面向服务的体系结构(SOA),是一个组件模型,它将应用程序的不同功能(即服务)通过服务之间定义良好的接口和契约联系起来。接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构建在各种这样的系统中的服务可以以一种统一和通用的方式进行交互。
这里提到SOA架构,主要是与从六边形架构相比,它们都为领域驱动设计提供了共同的基础价值。
在SOA架构中,这8大设计原则依然适应六边形架构,而且有一部分特性对六边形架构进行了很好的补充。比如服务抽象、松耦合、服务重用性、服务组合性、服务契约、服务自治性、服务无状态性、服务可发现性,这些特性均可以与六边形架构进行很好的结合。在上图中,服务边界位于最左侧,而领域模型位于中心位置,消费方可以通过REST、SOAP和消息机制获取服务。
REST,表述性状态传递,属于Web架构的一种软件架构风格,REST也是web架构的理论扩展。它是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。
在了解领域设计驱动之前,要说明两点:
第一,REST并不是一种具体的技术,也不是一种具体的规范,而是一种内涵非常丰富的架构风格,一整套研究和评价软件架构的方法论,这套方法论的核心词在“架构风格”。
第二,架构风格是一种研究和评价软件架构设计的方法,它是比架构本身更加抽象的概念。一种架构风格是由一组相互协作的架构约束来定义的,而架构约束是对软件的运行环境施加在架构设计之上的一种约束行为,对架构进行更好的规范。
尤其是第二点,对于我们进行软件设计所起到的作用更大。
在三种主流的Web服务实现方案中,因为REST模式的Web服务与复杂的SOAP和XML-RPC对比来讲明显的更加简洁,当前很多企业开发系统,越来越多的web服务开始采用REST风格设计和实现。
我们知道,在 REST 架构风格中,数据和功能被视为资源,并使用统一资源标识符 (URI) 进行访问。通过使用一组简单的、定义良好的操作来处理资源。客户端和服务器通过使用标准化的接口和协议(通常是 HTTP)来交换资源。另外,其资源是与表现形式进行分离的,以便可以以各种格式访问其内容,例如 HTML、XML、纯文本、PDF、JPEG、JSON 等。
那REST在领域驱动设计中的作用是什么呢?依然是其与六边行架构所提供的共同价值所决定的,即松耦合、可伸缩和易用性。
符合REST原则的系统具有更好的松耦合。通常来讲,添加新资源并在已有资源中创建到新资源的链接是非常简单的,要添加新的格式同样如此。REST API使用URI来进行资源的定位,所以这个URI需要直观并且容易使用。
基于基于REST的系统也是非常容易理解的。因为此时系统被分为很多较小的资源模块,每一个资源块都可以独立测试和调试,并且每一个资源模块都表示了一个可重用的入口点。该架构本身具有很好的松耦合和可伸缩性。
采用REST架构风格,对于开发、测试、运维人员来说,都会更简单。可以充分利用大量HTTP服务器端和客户端开发库、Web功能测试/性能测试工具、HTTP缓存、HTTP代理服务器、防火墙。这些开发库和基础设施早已成为了日常用品,不需要什么火箭科技(例如神奇昂贵的应用服务器、中间件)就能解决大多数可伸缩性方面的问题。
另外,REST架构风格最重要的架构约束有6个:客户-服务器(Client-Server)、无状态(Stateless)、通信的会话状态(Session State)、缓存(Cache)、统一接口(Uniform Interface)、分层系统(Layered System)。这些特性对六边形架构也进行了很好的规范约束。
我们前面提到,架构模式通常分分层模式(常用的标准架构)、客户端/服务模式、事件总线模式、命令和查询职责分离模式、管道和过滤器模式、微核模式、微服务模式和云模式等。从作者的视角,为什么要单独大篇幅去讲命令查询模式呢?这是我们需要重点关注的地方。
CQRS 是“命令和查询责任分离”的英文缩写,它是一种将数据存储的读取操作和更新操作分离的模式,类似于我们在数据库层面的读写分离。它主要解决了,在复杂系统中当多种读取形式和写入工作负载非对称,并且读写的性能要求有很大差异时,会倒置模型执行太多操作且过度复杂的问题。这样做的好处就是可以最大限度地提高系统的性能、可缩放性和安全性。
但CQRS这种模式在领域驱动设计当中是最优的吗?凡事都有两面性,固然优点需要借鉴学习,但也需要我们用批判精神来看待其不足。那CQRS在领域驱动设计中不好的一面也体现的会更明显。
第一,CQRS无法很好的解决事务性。
对于之前采用的单一数据源,我们通常依靠关系型数据库的事务特性能够很好的保证数据的完整性。但是在 CQRS 中这一切都发生了变化。因为在CQRS中一个 command 触发的事件,在 query 端可能需要更新很多个数据模型,而这是有可能失败的。一旦更新失败那么数据就会长时间的处于不一致状态,这时需要外部的介入单独处理。从事务的角度来看 CQRS,需要面对的是问题是如何解决最终一致性。
第二,数据时效性问题。
在 CQRS模式中,当command 端完成数据更新后,需要通过事件形式通知查询端系统,这也就意味着系统之间会存在一定时间差,此时如果业务对于数据的实时性要求非常高,那么可能 CQRS 的技术架构选型就不适合了,此时可能需要对实时的数据接口进行区分加以特殊处理。
第三,查询模式设计的复杂性问题。
虽然 CQRS 为我们分离了领域模型和服务于查询功能的数据模型,但这意味着我们需要单独设计另一套针对查询功能的数据模型。
这种做法带来的问题就是当查询接口越来越多时就会难以管理,仍然需要按照 DDD 中划分领域的思路将属于一个领域的查询集中管理作为整个查询系统的一个上下文,甚至需要独立出一个新的微服务,这也无形之中,CQRS 在带来架构自由与便利的同时也不可避免的引入了额外的复杂性与技能要求。
那作者为什么还要在DDD中提及CQRS架构模式呢?
我个人理解主要有以下两点:
1)选择与领域驱动契合的点—边界清晰职责明确
如果你所面对的业务系统已经非常庞大,而且业务流程庞杂逻辑繁琐,那么不妨尝试使用 CQRS 将 Command 与 Query 进行拆分,将领域模型与数据模型的边界划分的更清晰些。
2)领域驱动架构设计需要集众所长
从作者所的文章中,我们看到无论采用CQRS、消息总线模型、长时处理模型哪种单一架构模型,都不能完美解决系统中所存在的各种问题。所以需要我们使用多种架构模式来共同协作,以此完善整个系统架构。
总之,在软件架构设计中,无论是架构风格还是架构模式,都需要集众所长,避其所短,方能设计出架构良好的系统。