第1章描述了微服务架构的关键思想是如何进行功能分解。你可以将应用程序构建为组服务,而不是开发一个大型的单体应用程序。一方面,将微服务架构描述为一种功能分解是有用的。但另一方面,它留下了几个未解决的问题,包括:微服务架构如何与更广泛的软件架构概念相结合?什么是服务?服务的规模有多重要?
为了回答这些问题?我们需要退后一步,看看软件架构的含义。软件的架构是一种抽象的结构,它由软件的各个组成部分和这些部分之间的依赖关系构成。正如你将在本节中看到的,软件的架构是多维的,因此有多种方法可以对其进行描述。架构很重要的原因是它决定了应用程序的质量属性或能力。传统上,架构的目标是可扩展性、可靠性和安全性。但是今天,该架构能够快速安全地交付软件,这一点非常重要。你将了解微服务架构是一种架构风格,可为应用程序提供更高的可维护性、可测试性和可部署性。
我将通过描述软件架构的概念及其重要性来开始本节。接下来,我将讨论架构风格的概念。然后我将微服务架构定义为特定的架构风格。让我们从理解软件架构的概念开始。
计算机系统的软件架构是构建这个系统所需要的一组结构,包括软件元素、它们之间的关系以及两者的属性。
Bass等著《 Documenting Software Architectures: Views and Beyond》
这显然是一个非常抽象的定义。但其实质是应用程序的架构是将软件分解为元素(element)和这些元素之间的关系( relation)。由于以下两个原因,分解很重要:
应用程序有两个层面的需求。
特定的架构风格提供了有限的元素(组件)和关系(连接器),你可以从中定义应用程序架构的视图。应用程序通常使用多种架构风格的组合。微服务架构将应用程序构造为一组松散耦合的服务。
六边形架构风格的一个重要好处是它将业务逻辑与适配器中包含的表示层和数据访问层的逻辑分离开来。业务逻辑不依赖于表示层逻辑或数据访问层逻辑。
由于这种分离,单独测试业务逻辑要容易得多。另一个好处是它更准确地反映了现代应用程序的架构。可以通过多个适配器调用业务逻辑,每个适配器实现特定的API或用户界面。业务逻辑还可以调用多个适配器,每个适配器调用不同的外部系统。六边形架构是描述微服务架构中每个服务的架构的好方法。
微服务架构强加的一个关键约束是服务松耦合。因此,服务之间的协作方式存在一定限制。为了解释这些限制,我将尝试定义什么是服务,解释松耦合意味着什么,并告诉你为什么这很重要。
服务是一个单一的、可独立部署的软件组件,它实现了一些有用的功能。图中显示了服务的外部视图,在此示例中是 Order Service。服务具有API,为其客户端提供对功能的问。有两种类型的操作:命令和查询。API由命令、查询和事件组成。命令如createorder()执行操作并更新数据。查询,如 findorderByid()检索数据。服务还发布由其客户端使用的事件,例如 Ordercreated服务的API封装了其内部实现。与单体架构不同,开发人员无法绕过服务的API直接访问服务内部的方法或数据。因此,微服务架构强制实现了应用程序的模块化。
微服务架构中的每项服务都有自己的架构,可能还有独特的技术栈。但是典型的服务往往都具有六边形架构。其API由与服务的业务逻辑交互的适配器实现。操作适配器调用业务逻辑,事件适配器对外发布业务逻辑产生的事件。
微服务架构的最核心特性是服务之间的松耦合性。服务之间的交互采用API完成,这样做就封装了服务的实现细节。这允许服务在不影响客户端的情况下,对实现方式做出修改。松耦合服务是改善开发效率、提升可维护性和可测试性的关键。小的、松耦合的服务更容易被理解、修改和测试。
我们通过API来实现松耦合服务之间的协调调用,这样就避免了外界对服务的数据库的直接访问和调用。服务自身的持久化数据就如同类的私有属性一样,是不对外的。保证数据的私有属性是实现松耦合的前提之一。这样做,就允许开发者修改服务的数据结构,而不用提前与其他服务的开发者互相协商。这样做在运行时也实现了更好的隔离。例如,一个服务的数据库加锁不会影响另外的服务。但是你稍后就会看到在服务间不共享数据库的弊端,特别是处理数据一致性和跨服务查询都变得更为复杂。
开发人员经常把一些通用的功能打包到库或模块中,以便多个应用程序可以重用它而无须复制代码。毕竟,如果没有 Maven或npm库,我们今天的开发工作都会变得更困难。你可能也想在微服务架构中使用共享库。从表面上看,它似乎是减少服务中代码重复的好方法。但是你需要确保不会意外地在服务之间引入耦合。
例如,想象一下多个服务需要更新 Order业务对象的场景。一种选择是将该功能打包为可供多个服务使用的库。一方面,使用库可以消除代码重复。另一方面,如果业务需求的变更影响了order业务对象,开发者需要同时重建和重新部署所有使用了共享库的服务。更好的选择是把这些可能会更改的通用功能(例如 Order管理)作为服务来实现,而不是共享库。
你应该努力使用共享库来实现不太可能改变的功能。例如,在典型的应用程序中,在每个服务中都实现一个通用的 Money类(例如用来实现币种转换等固定功能)没有任何意义。相反,你应该创建一个供所有服务使用的共享库。
那么如何定义一个微服务架构呢?跟所有的软件开发过程一样,一开始我们需要拿到领域专家或者现有应用的需求文档。跟所有的软件开发一样,定义架构也是一项艺术而非技术。本节我们将介绍一种定义应用程序架构的三步式流程。
定义应用程序架构的第一步是定义系统操作。起点是应用程序的需求,包括用户故事及其相关的用户场景。图中所示的两步式流程识别和定义系统操作。
领域模型主要源自用户故事中提及的名词,系统操作主要来自用户故事中提及的动词。你还可以使用名为事件风暴的技术定义领域模型。
定义系统操作的第一步是为这个应用程序描绘一个抽象的领域模型。注意这个模型比我们最终要实现的简单很多。创建领域模型会采用一些标准的技术,例如通过与领域专家沟通后,分析用户故事和场景中频繁出现的名词。
当定义了抽象的领域模型之后,接下来就要识别系统必须处理的各种请求。前端的用户界面向后端的业务逻辑发出请求,后端的业务逻辑进行数据的获取和处理。
有以下两种类型的系统操作:
抽象的领域模型和系统操作能够回答这个应用“做什么”这一问题。这有助于推动应用程序的架构设计。每一个系统操作的行为都通过领域模型的方式来描述。每一个重要的系统操作都对应着架构层面的一个重大场景,是架构中需要详细描述和特别考虑的地方。
现在我们来看看如何定义应用程序的微服务架构。
创建微服务架构的策略之一就是采用业务能力进行服务拆分。业务能力是一个来自于业务架构建模的术语。业务能力是指一些能够为公司(或组织)产生价值的商业活动。特定业务的业务能力取决于这个业务的类型。例如,保险公司业务能力通常包括承保、理赔管理账务和合规等。在线商店的业务能力包括:订单管理、库存管理和发货,等等。
Eric evans在他的经典著作中( Addison- Wesley Professional,2003)提出的领域驱动设计是构建复杂软件的方法论,这些软件通常都以面向对象和领域模型为核心。领域模型以解决具体问题的方式包含了一个领域内的知识。它定义了当前领域相关团队的词汇表,DDD也称之为通用语言(Ubiquitous language)。领域模型会被紧密地映射到应用的设计和实现环节。
在微服务架构的设计层面,DDD有两个特别重要的概念,子域和限界上下文。
传统的企业架构建模方式往往会为整个企业建立一个单独的模型,DDD则采取了完全不同的方式。在这样的模型中,会有适用于整个应用全局的业务实体定义,例如客户或订单。这类传统建模方式的挑战在于,让组织内的所有团队都对全局单一的建模和术语定义达成一致是非常困难的。另外,对于组织中的特定团队而言,这个单一的业务实体定义可能过于复杂,超出了他们的需求。此外,这些传统的领域模型可能会造成混乱,因为组织内有些团队可能针对不同的概念使用相同的术语,而也有些团队会针对同一个概念使用不同的术语。
DDD通过定义多个领域模型来避免这个问题,每个领域模型都有明确的范围。领域驱动为每一个子域定义单独的领域模型。子域是领域的一部分,领域是DDD中用来描述应用程序问题域的一个术语。识别子域的方式跟识别业务能力一样:分析业务并识别业务的不同专业领域,分析产出的子域定义结果也会跟业务能力非常接近。FTGO的子域包括:订单获取、订单管理、餐馆管理、送餐和会计。正如你所见:这些子域跟我们之前定义的业务能力非常接近。
DDD把领域模型的边界称为限界上下文( bounded context)。限界上下文包括实现这个模型的代码集合。当使用微服务架构时,每一个限界上下文对应一个或者一组服务。换一种说法,我们可以通过DDD的方式定义子域,并把子域对应为每一个服务,这样就完成了微服务架构的设计工作。图中展示了子域和服务之间的映射,每一个子域都有属于它们自己的领域模型。
DDD和微服务架构简直就是天生一对。DDD的子域和限界上下文的概念,可以很好地跟微服务架构中的服务进行匹配。而且微服务架构中的自治化团队负责服务开发的概念,也跟DDD中每个领域模型都由一个独立团队负责开发的概念吻合。更有趣的是,子域用于它自己的领域模型这个概念,为消除上帝类和优化服务拆分提供了好办法。按子域分解和按业务能力分解是定义应用程序的微服务架构的两种主要模式。
通过定义与业务能力或子域相对应的服务来创建微服务架构的策略看起来很简单。但是,你可能会遇到几个障碍:
到目前为止,我们有一个系统操作列表和一个潜在服务列表。下一步是定义每个服务的API:也就是服务的操作和事件。存在服务API操作有以下两个原因:首先,某些操作对应于系统操作。它们由外部客户端调用,也可能由其他服务调用。另次,存在一些其他操作用以支持服务之间的协作。这些操作仅由其他服务调用。