ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

微服务架构设计模式-第二章

2026/8/14 12:23:32 拓冰建站 浏览量
微服务架构设计模式-第二章

第2章 服务的拆分策略

2.1 微服务架构到底是什么

传统上,架构的目标是可扩展性、可靠性和安全性。但是今天,该架构能够快速安全地交付软件,这一点非常重要。

2.1.1 软件架构是什么,为什么它如此重要

O'Reilly的软件架构会议

SATURN会议

卡耐基梅隆大学软件工程研究所Len及其同事:

计算机系统的软件架构是构建这个系统所需要的一组结构,包括软件元素、他们之间的关系以及两者的属性。

这显然是一个非常抽象的定义。但其实质是应用程序的架构是将软件分解为元素(element)和这些元素之间的关系(relation)。由于以下两个原因,分解很重要:

  • 它促进了劳动和知识的分工。它使具有特定专业知识的人们(或多个团队)能够就应用程序高效地协同工作
  • 它定义了软件元素的交互方式

软件架构的4+1视图模型

phillip kruchen在他经典的论文《architectural blueprints——the 4+1 view model of software architecture》中提出了软件架构的4+1视图(www.cs.ubc.ca/~gregor/teaching/papers/4+1 view-architecture.pdf)。下图展示的这套视图定义了四个不同的软件架构视图,每一个视图都只描述架构的一个特定方面。每个视图包括一些特定的软件元素和他们相互之间的关系。

9-4+1软件架构视图
每个视图的目的如下:

  • 逻辑视图:开发人员创建的软件元素。在面向对象的语言中,这些元素是类和包。他们之间的关系是类和包之间的关系,包括继承、关联和依赖。
  • 实现视图:构建编译系统的输出。此视图由表示打包代码的模块和组件组成,组件是由一个或多个模块组成的可执行或可部署单元。在Java中,模块是JAR文件,组件通常是WAR文件或可执行JAR文件。它们之间的关系包括模块之间的依赖关系以及组件和模块之间的组合关系。
  • 进程视图:运行时的组件。每个元素都是一个进程,进程之间的关系代表进程间通信
  • 部署视图:进程如何映射到机器。此视图中的元素由(物理或虚拟)计算机和进程组成。机器之间的关系代表网络。该视图还描述了进程和机器之间的关系。

除了这四个视图以外,4+1中的+1是指场景,它负责把视图串联在一起。每个场景负责描述在一个视图中的多个架构元素如何协作,以完成一个请求。


为什么架构如此重要

应用程序有两个层面的需求:

  • 功能性需求:决定应用程序做什么,这些通常都包含在用例或用户故事中
  • 非功能性需求:质量属性需求。架构的重要性在此。这些非功能性需求决定一个应用程序在运行时的质量,比如可扩展性和可靠性。它们也决定了开发阶段的质量,包括可维护性、可测试下、可扩展性和可部署性。

2.1.2 什么是架构的风格

David Garlan和Mary Shaw(an introduction to software architecture,january 1994)

定义如下架构风格:

架构风格根据结构组织模式定义了一系列此类系统。更具体地说,架构风格确定可以在该风格的实例中使用的组件和连接器的词汇表,以及关于如何它们的一组约束

分层架构风格

  • 表现层
  • 业务逻辑层
  • 数据持久化层

弊端:

  • 单个表现层:它无法展现应用程序可能不仅仅由单个系统调用的事实
  • 单一数据持久化层:它无法展现应用程序可能与多个数据库进行交互的事实
  • 将业务逻辑层定义为依赖于数据持久化层:理论上,这样的依赖性会妨碍你在没有数据库的情况下测试业务逻辑

关于架构风格的六边形

六边形架构是分层架构风格的替代品。

9-六边形架构
应用程序具有一个或多个入站适配器,而不是表示层,它通过调用业务逻辑来处理来自外部的请求。同样,应用程序具有一个活多个出站适配器,而不是数据持久化层,这些出站适配器由业务逻辑调用并调用外部应用程序。此架构的一个关键特性和优点是业务逻辑不依赖于适配器。相反,各种适配器都依赖业务逻辑。

业务逻辑具有一个或多个端口(port)。端口定义了一组操作,关于业务逻辑如何与外部交互。例如,在Java中,端口通常是Java接口。有两种端口:入站和出站端口。入站端口是业务逻辑公开的API,它使外部应用程序可以调用它。入站端口的一个实例是服务接口,它定义服务的公共方法。出站端口是业务逻辑调用外部系统的方式。出站端口的一个实例是存储库接口,它定义数据访问操作的集合。

业务逻辑的周围是适配器。与端口一样,有两种类型的适配器:入站和出站。入站适配器通过调用入站端口来处理来自外部世界的请求。入站适配器的一个实例是SpringMVCController,它实现一组 REST接口(endpoint)或一组 Web页面。另一个实例是订阅消息的消息代理客户端。多个入站适配器可以调用相同的入站端口。

出站适配器实现出站端口,并通过调用外部应用程序或服务处理来自业务逻辑的请求。出站适配器的一个实例是实现访问数据库的操作的数据访问对象(DAO)类。另一个实例是调用远程服务的代理类。出站适配器也可以发布事件。

六边形架构风格的一个重要好处是它将业务逻辑与适配器中包含的表示层和数据访问层的逻辑分离开来。业务逻辑不依赖于表示层逻辑或数据访问层逻辑。

由于这种分离,单独测试业务逻辑要容易得多。另一个好处是它更准确地反映了现代应用程序的架构。可以通过多个适配器调用业务逻辑,每个适配器实现特定的 API 或用户界面。业务逻辑还可以调用多个适配器,每个适配器调用不同的外部系统。六边形架构是描述微服务架构中每个服务的架构的好方法。

2.1.3 微服务架构是一种架构风格

单体架构是一种架构风格,它的实现视图是单个组件:单个可执行文件或 WAR 文件。

微服务架构也是一种架构风格。它的实现视图由多个组件构成:一组可执行文件或 WAR 文件。它的组件是服务,连接器是使这些服务能够协作的通信协议。每个服务都有自己的逻辑视图架构,通常也是六边形架构。


微服务架构强加的一个关键约束是服务松耦合。因此,服务之间的协作方式存在一定限制。为了解释这些限制,我将尝试定义什么是服务。

什么是服务

服务是一个单一的、可独立部署的软件组件,它实现了一些有用的功能。服务具有 API,为其客户端提供对功能的访问。有两种类型的操作:命令和查询。API 由命令、查询和事件组成。

服务的大小并不重要:微服务这个术语的一个问题是会将你的关注点错误地聚焦在微上。它暗示服务应该非常小。其他基于大小的术语也是如此。实际上,大小不是一个重要的考虑因素。

更好的目标是将精心设计的服务定义为能够由小团队开发的服务,并且交付时间最短,与其他团队协作最少。理论上,团队可能只负责单一服务,因此服务绝不是微小的。

2.2 为应用程序定义微服务架构

如何定义一个微服务架构?

从需求开始,通常是用户故事。一个系统操作代表一个外部的请求。

  • 第一步,定义系统操作
  • 第二步,定义服务
  • 第三部:定义服务 API 和协作方式

服务分解的障碍:

  • 网络延迟,服务之间的网络往返太多,特定的分解是不切实际的
  • 服务之间的同步通信降低了可用性(自包含服务 第三章)
  • 需要维护跨服务的数据一致性
  • 所谓的上帝类,它广泛应用在整个应用程序中

2.2.1 识别系统操作

craig larman《applying uml and patterns》

第一步创建由关键类组成的抽象领域模型,这些关键类提供由于描述系统操作的词汇表。

第二步确定系统操作,并根据领域模型描述每个系统操作的行为。
10-系统操作
创建抽象领域模型

定义系统操作

2.2.2 根据业务能力进行服务拆分

业务能力是指一些能够为公司(或组织)产生价值的商业活动。

业务能力定义了一个组织的工作

识别业务能力

从业务能力到服务

2.2.3 根据子域进行服务拆分

传统的企业架构模式往往会为整个企业建立一个单独的模型。这类传统建模方式的挑战在于,让组织内的所有团队都对全局单一的建模和术语定义达成一致是非常困难的。另外,对于组织中的特定团队而言,这个单一的业务实体定义可能过于复杂,超出了他们的需求。此外,这些传统的领域模型可能会造成混乱,因为组织内有些团队可能针对不同的概念使用相同的术语,而也有些团队会针对同一概念使用不同的术语。

2.2.4 拆分的指导原则

Robert C.Martin《designing object oriented C++ Application using the booch method》

第一个原则:遵循单一职责原则

第二个原则:把类组成包时,遵循闭包原则(在包中包含的所有类都应该是对同类的变化的一个集合,也就是说,如果对包做出修改,需要调整的类应该都在这个包之内)

单一职责原则 (SRP):一个类应该只有一个引起它变化的原因。
开放封闭原则 (OCP):软件实体应该是可扩展的,但不可修改的。
里氏替换原则 (LSP):子类型必须能够替换掉它们的基类型。
依赖倒置原则 (DIP):依赖于抽象,不要依赖于具体。
接口隔离原则 (ISP):客户端不应该依赖它不需要的接口。
(注:以上 5 个就是后来著名的 SOLID 原则)
发布-重用等价原则 (REP):重用的粒度就是发布的粒度。
共同封闭原则 (CCP):一起修改的类应该放在同一个包里。
共同重用原则 (CRP):一个包里的类应该是一起被重用的。
无环依赖原则 (ADP):包之间的依赖关系图中不允许出现环。
稳定依赖原则 (SDP):依赖的方向必须指向更稳定的包。
(注:REP, CCP, CRP, ADP, SDP 这 5 个是包/组件级别的设计原则)

2.2.5 拆分单体应用为服务的难点

网络延迟:实施批处理 API 在一次往返中获取多个对象,从而将延迟减少到可接受的数量。在其他情况下,解决方案是把多个相关的服务组合在一起,用编程语言的函数调用替换昂贵的进程间通信。

同步进程间通信导致可用性降低:异步消息

在服务之间维持数据一致性:传统解决方案是使用基于两阶段提交的分布式事务管理机制。现在的话使用 sage 来处理事务管理。

获取一致的数据视图:

上帝类阻碍了拆分:订单类(order),更好的方法是应用 DDD 并将每个服务视为具有自己的领域模型的单独子域,这意味着个 FTGO 应用程序中与订单有关的每个服务都有自己的领域模型及其对应的 order 类的版本。

2.2.6 定义服务API

定义服务 API 的起点是将每个系统操作映射到服务。之后确定服务是否需要与其他服务协作以实现系统操作。如果需要协作,我们将确定其他服务必须提供哪些 API 才能支持协作。首先来看一下如何将系统操作分配给服务。

系统操作分配给服务

consumer service createConsumer()

把操作分配给服务后,下一步是确定在处理每一个系统操作时,服务之间如何交互。