01.架构风格与模式

软件架构风格与模式

软件架构风格是描述某一特定应用领域中系统组织方式的惯用模式(idiomatic paradigm)。架构风格定义了一个系统家族,即一个架构定义一个词汇表和一组约束。词汇表中包含一些构件和连接件类型,而这组约束指出系统是如何将这些构件和连接件组合起来的。架构风格反映了领域中众多系统所共有的结构和语义特性,并指导如何将各个模块和子系统有效地组织成一个完整的系统。按这种方式理解,软件架构风格定义了用于描述系统的术语表和一组指导构建系统的规则。

一种架构风格以一种结构化组织模式定义一组这样的系统。具体来说,一种架构风格定义了一个构件及连接器类型的词汇表,以及一组关于它们如何能够被关联的约束对于许多风格来说,可能也存在一个或多个语义模型,从系统部件的特性来确定系统的整体特性。许多架构风格已经发展了很多年。

  • 数据流风格:批处理序列,管道/过滤器。
  • 调用/返回风格:主程序/子系统,面向对象风格,层次结构。
  • 独立构件风格:进程通信,事件系统。
  • 虚拟机风格:解释器,基于规则的系统。
  • 仓库风格:数据库系统,超文本系统,黑板系统。

软件架构模式的核心就是核心业务逻辑和技术细节的分离与解耦。业务逻辑和技术细节糅杂在一起的情况,所有的代码都写在服务实现里面,将校验、数据转化、逻辑处理、数据存取等不同的操作混杂在一起,再简单的业务,按照上面这种写代码的方式,都会变得复杂,难维护。好的架构风格应该让核心业务逻辑可以反映领域模型和领域应用,可以复用,可以很容易被看懂。让技术细节在辅助实现业务功能的同时,可以被替换。

常见的风格与模式

传统的三层架构分而治之、降低耦合、提高复用,但存在弊端,业务逻辑在不同层泄露,导致替换某一层变得困难、难以对核心逻辑完整测试。领域驱动设计给出了 DDD 分层架构、六边形架构、整洁架构等分层架构,它们遵循“关注点分离”原则,旨在分离和隔离业务复杂度和技术复杂度,凸显了领域模型,保证了领域模型的稳定性和一致性。

DDD 分层架构

DDD 分层架构将属于业务逻辑的关注点放到领域层(Domain Layer)中,将支撑业务逻辑的技术实现放到基础设施层(Infrastructure Layer)中,DDD 创新性地引入了应用层(Application Layer),应用层扮演了两重角色。一作为业务逻辑的门面(Facade),暴露了能够体现业务用例的应用服务接口,又是业务逻辑与技术实现的粘合剂,实现二者之间的协作。下图展现的是一个典型的领域驱动设计分层架构。蓝色区域和业务逻辑相关,灰色区域与技术实现相关,二者泾渭分明,然后汇合在应用层。应用层确定了业务逻辑与技术实现的边界,通过直接依赖或者依赖注入(DI,Dependency Injection)的方式将二者结合起来。

分层架构

我们详细介绍 DDD 分层架构中每一层的用意和设计:

  • 表现层(User Interface Layer):负责向用户显示信息和解释用户命令,完成前端界面逻辑
  • 应用层(Application Layer):很薄的一层,负责展现层与领域层之间的协调,不包含任何的业务逻辑和业务规则,也不保留业务对象的状态,是对领域服务的编排和转发。应用层扮演了两重角色。一作为业务逻辑的门面(Facade),暴露了能够体现业务用例的应用服务接口,又是业务逻辑与技术实现的粘合剂,实现二者之间的协作。一个 Application Service 代表一个 Use Case,一个 Use Case 代表了一个完整的业务场景,对于外部的客户来说,应用层是与客户协作的应用服务,接口代表是业务的含义。我们知道 DDD 分层架构的主要目标是分离业务复杂度与技术复杂度,应用层扮演的就是这样的分界线。从设计模式的角度来理解,应用层的 Application Service 是一个 Facade,对外部客户,作为代表 Use Case 的整体应用,对架构内部,它负责整合领域层的领域逻辑与非业务相关的横切关注点。
  • 应用中,存在与具体的业务逻辑无关,在整个系统中会被诸多服务调用的横切关注点实现,他们在职责上是内聚的,散布在所有代码层次中,包括异常处理、事务、监控、日志、认证和授权等。所以与横切关注点协作的服务应被定义为应用服务。
  • 领域层(Domain Layer):是业务软件的核心所在,也是软件架构的核心,包含了业务所涉及的领域对象(实体、值对象)、领域服务,负责表达业务概念、业务状态信息以及业务规则,具体表现形式就是领域模型。领域驱动设计提倡富领域模型,将业务逻辑归属到领域对象上。
  • 基础设施层(Infrastructure Layer):基础层为各层提供通用的技术能力,包括:为应用层传递消息、提供 API 管理,为领域层提供数据库持久化机制等。它还能通过技术框架来支持各层之间的交互。

整洁架构(Clean Architecture)

整洁架构中,同心圆代表应用软件架构的不同部分,也是一种以领域模型为中心的架构,从里到外依次是 Entities、Use Cases、Interface Adapters、Frameworks and Drivers。整洁架构明确了各层的依赖关系,越往里,依赖越低,越抽象,外圆代码依赖只能指向内圆,内圆不知道外圆的任何事情。

Clean Architecture 示意图

六边形架构(Hexagonal Architecture)

又称为端口-适配器,六边形架构也是一种分层架构,不是从上下或左右分,而是从内部和外部来分。六边形架构在领域驱动设计和微服务架构设计中扮演了较重要的角色。六边形架构将系统分为内部(内部六边形)和外部,内部代表了应用的业务逻辑,外部代表应用的驱动逻辑、基础设施(诸如 REST,SOAP,NoSQL,SQL,Message Queue 等)或其他应用,UI 层、DB 层、和各种中间件层实际上是没有本质上区别的,都只是数据的输入和输出。内部通过端口和外部系统通信,端口代表了一定协议,以 API 呈现。一个端口对应多个适配器,对应多个外部系统,对这一类外部系统的归纳,不同的外部系统需要使用不同的适配器,适配器负责对协议进行转换。六边形架构有一个明确的关注点,一开始就强调把重心放在业务逻辑上,外部的驱动逻辑或被驱动逻辑存在可变性、可替换性,依赖具体技术细节。而核心的业务领域相对稳定,体现应用的核心价值。六边形的六并没有实质意义,只是为了留足够的空间放置端口和适配器,一般端口数不会超过 4 个。适配器可以分为 2 类,“主”、“从”适配器,也可称为“驱动者”和“被驱动者”。

代码依赖只能使由外向内。对于驱动者适配器(也称主适配器,Driving Adapter),就是外部依赖内部的。但是对于被驱动者适配器(也称次适配器,Driven Adapter),实际是内部依赖外部,这时需要使用依赖倒置,由驱动者适配器将被驱动者适配器注入到应用内部,这时端口的定义在应用内部,但是实现是由适配器实现。

六边形架构

CQRS(命令与查询职责分离)

CQRS 使用分离的接口将数据查询操作(Queries)和数据修改操作(Commands)分离开来,这也意味着在查询和更新过程中使用的数据模型也是不一样的,这样读和写逻辑就隔离开来了。使用 CQRS 分离了读写职责之后,可以对数据进行读写分离操作来改进性能,可扩展性和安全。DDD 和 CQRS 结合,可以分别对读和写建模:

CQRS

查询模型是一种非规范化数据模型,不反映领域行为,只用于数据查询和显示。命令模型执行领域行为,在领域行为执行完成后通知查询模型。如果查询模型和领域模型共享数据源,则可以省略这一步;如果没有共享数据源,可以借助于发布订阅的消息模式通知到查询模型,从而达到数据最终一致性。对于写少读多的共享类通用数据服务(如主数据类应用)可以采用读写分离架构模式。单数据中心写入数据,通过发布订阅模式将数据副本分发到多数据中心。通过查询模型微服务,实现多数据中心数据共享和查询。

综合模型

六边形架构的内部六边形、DDD 分层架构的领域层和应用层、以及整洁架构 Use Cases 和 Entities 区域实现了核心业务逻辑。但是核心业务逻辑又由两部分来完成:应用层和领域层逻辑。领域层实现了最核心的业务领域部分的逻辑,对外提供领域模型内细粒度的领域服务,应用层依赖领域层业务逻辑,通过服务组合和编排通过 API 网关向前台应用提供粗粒度的服务。业务需求变幻莫测,但我们总能在这些变化找出一些规律,用户体验、操作交互、以及业务流程的变化,往往只会导致 UI 层和流程的变化,总体来说,不管前端和外部如何变化,核心领域逻辑基本不会大变。把握好这个规律,我们就知道如何设计应用层和领域层,如何进行逻辑划界了。架构模型正是通过分层方式来控制需求变化对系统的影响,确保从外向里受的影响逐步减小。面向用户端的展现层可以快速响应外部需求进行调整和发布,灵活多变;应用层通过服务组合和编排实现业务流程的快速适配上线,以满足不同的业务场景;领域层是经过抽象和提炼的业务原子单元,是非常稳定的。这些架构设计的好处是可以保证领域层的核心业务逻辑不会因为外部需求和流程的变动而调整,对于建立前台灵活、中台稳固的架构能力是很有好处的。下面是 Herberto Graca 的一张包含了六边形、整洁、CQRS 等架构的综合图,全面的说明了这些架构的设计要点和不同的出发点。

Herberto Graca 综合图

Links