探索微服务的使用时,必须理解一些核心概念。考虑到有些方面经常被忽视,深入探讨这些概念对于确保你理解微服务的工作原理尤为重要。
可独立部署 是指我们可以变更和部署某个微服务,并向用户发布这些变更,而无须同时部署其他微服务。更重要的是,这不仅是可行的,而且应该成为你所管理系统的标准部署方式。这个概念看似简单但在执行过程非常复杂。
如果你从本书和微服务的整体概念中只学一件事,那就是确保你真正接纳了独立部署微服务的概念。要养成将单个微服务的更改部署和发布到生产环境(期间不牵涉其他任何微服务)的习惯。做到这一点,你将受益良多。
为了保持可独立部署,需要确保我们的微服务是 低耦合 的:允许在不影响其他任何功能运作的情况下变更某个服务。这意味着我们需要显式、完善和稳定的服务间契约。但是需要注意,某些技术实现方式会使这一点变得困难——共享数据库便是其中一个突出问题。
可独立部署本身就非常有价值,但是要想实现独立部署,你还需要正确处理很多其他方面的工作,这些工作本身也很有价值。因此,你可以将可独立部署视为强制机制——通过将其作为目标进行持续关注,从而实现增益。
我们期望拥有稳定接口的松耦合服务,这个诉求促使我们首先需要思考如何规划微服务的边界。
领域驱动设计之类的技术可以让你设计的代码更好地表达软件运行的实际业务领域。 [1] 在微服务架构中我们使用相同的思想来定义服务边界。通过围绕业务领域规划微服务,我们可以更轻松地推出新特性,或以不同的方式重组微服务,从而为我们的用户提供新功能。
推出一个需要通过改变多个微服务才能实现的功能成本巨大。你需要协调每个微服务之间(可能还要跨越多个团队)的工作,并谨慎管理这些微服务新版本的部署顺序。这比在一个服务内(或在一个单体架构内)实现相同的变更要更加费力。因此,我们需要找到尽可能减少跨服务变更的办法。
我经常看到分层架构,如图1-2所示的三层架构。图中的每一层代表了架构中的不同服务边界,每个服务边界的划分都基于不同的技术功能诉求。在这个例子中,如果我们只需要改变表示层,那将是相当高效的。但是经验表明,在这类架构中,功能变化通常会跨越多层——需要对表示层、应用层和数据层都做出变更。如果相比图1-2,架构分了更多的层,那么这个问题会更加严重;通常每一层会被分割成更多的层。
图1-2:传统的三层架构
将微服务按照业务端到端的方式切片,可以确保我们的架构尽可能高效地响应业务的变化。可以说,采用微服务的设计理念意味着我们决定优先考虑业务功能的高内聚性,而非技术功能的高内聚性。
本章后面将论及领域驱动设计的相互作用,以及它是如何与组织架构设计互相影响的。
我观察到,很多人最难以接受的概念是微服务应该避免使用共享数据库。如果微服务 A 想要访问微服务 B 的数据,微服务 A 需要向微服务 B 请求获取数据。这样做使得微服务有能力决定什么可以被共享,什么应该被隐藏,从而让我们可以清晰地将想自由变更的功能(内部实现)和不希望经常变更的功能(消费者使用的外部契约)分隔开来。
要想实现独立部署,我们需要尽力避免向后不兼容的变更。如果破坏了与上游消费者的兼容性,就会迫使它们也要跟着改变。在微服务中,明确界定内部实现和外部契约可以帮助减少向后不兼容的变更。
在微服务中,隐藏内部状态类似于面向对象编程中的封装做法。例如,面向对象系统中封装数据就是信息隐藏的一个例子。
除非你真的需要,否则不要共享数据库。即便需要,还是应该尽一切可能避免共享。在我看来,只要你想实现独立部署,那么共享数据库就是最糟糕的事情。
我们在前面讨论过,要将微服务看作端到端的业务功能的切片,要在适当的地方封装用户界面(UI)、业务逻辑和数据。这是因为我们想减少业务功能变更所需的工作量。以这种方式对数据和行为的封装可以带来业务功能的高内聚性。通过将数据库隐藏在服务后面,还可以确保减少耦合。第2章将继续讨论耦合和内聚性。
“一个微服务应该有多大?”这是我最常被问到的问题。考虑到“微服务”这个名字中的“微”字,人们会问这样的问题也就不奇怪了。然而,当你明白了是什么让微服务这种架构类型发挥作用时,大小的概念便毫无意义。
你是如何来衡量大小的?是通过代码行数吗?这对我来说没有多大意义。实现同样的功能,使用 Java 语言编写代码可能需要25行,但 Clojure 语言可能仅需要10行。这并不是说 Clojure 比 Java 更好,而是两者在语言表现力上有所差别。
Thoughtworks 技术总监 James Lewis 曾说过一句微服务领域广为人知的话:“一个微服务最好和我的头一样大。”乍一看,这似乎没有给出答案。毕竟,我们不清楚 James 的头到底有多大。这句话的深意是:一个微服务应该保持在易于理解的大小,其挑战在于人的理解能力是不同的,你需要自行决定适合自己的大小。一个有经验的团队也许比其他团队能够更好地管理更大的代码库。如此说来,也许将 James 的话解读成“一个微服务最好和你的头一样大”会更好。
就微服务“大小”而言,个人认为最有意义的解释来自作者 Chris Richardson 的《微服务架构设计模式》。据其阐述,微服务架构的目标是“接口越小越好”。这与信息隐藏的概念不谋而合,它代表了一种尝试,试图在原本并不存在的“微服务”一词中找到意义。但当这个词第一次被用来定义这类架构时——至少是在最初——重点并没有放在接口的大小上。
归根结底,大小的概念是和上下文高度相关的。对于同一个由10万行代码组成的系统,为其工作了15年的人自然比新人对此系统的理解更为深刻。同样,一家刚刚开始向微服务转型、仅有不到10个微服务的公司和一家多年来一直遵循微服务标准且有数百个微服务的公司相比,即便两家公司规模相当,其对微服务“大小”的解读也会不一样。
我劝告人们不要担心大小。起步阶段,应专注如下两个关键问题:第一,你能处理多少个微服务?你提供的服务越多,系统的复杂度也就越高,且你可能需要学习新技能(或采用新技术)来应对这种情况。向微服务架构转型会引入新的复杂度来源,并带来新的挑战。这也是为什么我强烈主张要以增量方式向微服务架构迁移。第二,如何定义微服务边界来最大化架构的优势而不会让一切变成耦合混乱?在你的架构旅程刚刚开始时,这些问题其实更为重要。
James Lewis 还曾说:“微服务带给你更多的选择。” 此表述非常精准——微服务确实可以带给你更多的选择。但这是有成本的,你必须自己决定这个成本是否值得。但是,微服务在组织、技术、规模、健壮性等多个维度上所带来的灵活性确实极具吸引力。
没有人可以预判未来,所以我们需要一个在理论上可以帮助我们解决未来可能面临的任何问题的架构。在留有选择余地和承担由此带来的成本之间找到平衡是一门真正的艺术。
与其说采用微服务像是在拨动开关,不如说它更像是转动旋钮。当你将旋钮向上转动时,你就有了更多的微服务,增强了灵活性,但这也可能会带来更多痛点。这是我强烈主张以增量方式采用微服务的另一个原因。在一点一点向上转动旋钮的过程中,你可以更好地评估采用过程中所受到的影响,并在必要时停止转动。
MusicCorp 是一家在线销售 CD 的电子商务公司,它使用如图1-2所示的简单三层架构。我们决定推动 MusicCorp 的架构变革以使它适应21世纪的变化。作为这个计划的一部分,我们正在评估现有的系统架构。我们有一个基于网页的用户界面层、一个以后端单体服务形式实现的业务逻辑层,以及一个使用传统数据库的数据层。和常见的情形一样,这些层由不同的团队负责。我们将在整本书中不时提到这家公司所经历的考验。
我们想实现一个简单的功能变更:我们希望客户能够指定他们喜欢的音乐流派。这个变更需要我们改变用户界面以显示选择流派的控件,后端服务需要实现将流派显示到用户界面中并支持流派的更改。最后,数据库需要能保存这个更改。每个团队都需要参与这一变更,并按正确的顺序完成部署,如图1-3所示。
图1-3:涉及三层的一个功能变更
现在这个架构还不错。整体架构都是围绕一组设定的目标完成优化的。三层架构之所以如此常见,部分原因是它的通用性——每个人都听说过它。倾向于选择你见过的通用架构通常是我们总能看到它的一个原因。但是,我认为我们反复见到这种架构的最大原因是,它符合我们组织团队的方式。
如今耳熟能详的康威定律指出:
设计系统的架构受制于产生这些设计的组织的沟通结构。
——Melvin Conway,“How Do Committees Invent?”
三层架构是这一定律在实践中的一个很好的例子。过去,IT 组织主要是根据成员的核心能力进行分组的:数据库管理员与其他数据库管理员组成一个团队;Java 开发人员与其他 Java 开发人员组成一个团队;而前端开发人员(如今,这些人可能掌握了一些奇特技能,比如懂得 JavaScript 编程语言、会开发原生移动应用等)则组成另外一个团队。这种分组方式创建了与这些团队相对应的 IT 资产。
这解释了为什么这种架构如此普遍。这种做法还不错,它是只围绕一类能力进行优化,以基于人员对技能的熟悉程度的传统方式对成员进行分组。但我们对软件的期望已经发生改变,这种方式也需要随之变化。现在,我们将人员分配到多技能团队中,以减少工作交接和信息孤岛。我们希望比以往更快地交付软件,这促使我们根据系统划分来组织团队,这代替了传统的团队组织方式。
我们所做的大多数系统变更都与业务功能的变更有关。但在图1-3中,业务功能实际上分布在三层中的每一层,这大大增加了产生跨层变更的可能性。这是一种技术内聚性高但业务内聚性低的架构。如果想让变更更容易实现,我们就需要改变代码的组织方式,应按业务内聚性而不是技术内聚性组织代码。每个服务最后并非一定包含这三层混合的功能,这是关乎该服务内部实现的问题。
我们将其与另一种可能的替代架构进行比较,如图1-4所示。不对组织和架构做横向的分层,而是按照垂直业务线划分,此处设有一个专门的团队来负责完善与客户信息相关的各种功能,这确保了本示例中的变更仅由一个团队来完成。
图1-4:不同团队同时负责分割后的用户界面以及相关的服务端功能
在这种架构设计里,这一变更可以通过让客户信息团队负责单个微服务来实现。该微服务提供一个用户界面,允许客户更新信息,并将客户状态存储在该微服务中。客户喜欢的音乐流派的选择记录与客户相关联,因此这种变更可以在内部完成。在图1-5中,我们展示了从专辑目录微服务中获取的可用音乐流派列表,这种功能可能已经存在。我们还看到一个新的推荐微服务读取了客户喜欢的音乐流派信息,这在后续版本中很容易实现。
图1-5:一个专门的客户微服务可以让客户轻松地记录喜欢的音乐流派
在这种情况下,客户微服务封装了三层架构中每一层的部分功能——一部分用户界面、一部分业务逻辑以及一部分数据存储。我们的业务领域变成了驱动系统架构的主要力量,希望这种做法能让我们更改起来更轻松,也能让团队和组织内的业务线保持一致。
通常来说,微服务不直接提供用户界面,但我们仍然希望由客户信息团队来负责与该功能相关的用户界面,如图1-4所示。由团队负责面向用户的端到端功能的理念越来越受到人们的认可。《高效能团队模式》 [2] 一书介绍的业务流团队的概念,在这里也得到了体现。
业务流团队对应一条单一、有价值的工作流……团队有能力做到快速、安全和独立地构建并交付用户价值,而不需要移交给其他团队来完成部分工作。
图1-4中展示的团队就是业务流团队,我们将在第14章和15章进一步探讨这个概念,包括现实中这种类型的组织结构是如何工作的,以及它是如何与微服务保持一致的。
在本书的不同地方,我们会遇到好几家公司,包括 MusicCorp、FinanceCo、FoodCo、AdvertCo 以及 PaymentCo。
FoodCo、AdvertCo 以及 PaymentCo 都是真实存在的公司,只是出于保密,我更改了它们的名字。另外,在分享这些公司的信息时,我会省略一些无关的细节,以便提供更清晰的信息。现实世界往往是复杂的,不过我一直努力只删除那些没有帮助的细节,确保呈现出必要的现实情况。
此外,MusicCorp 是一家虚构的公司,它综合了我合作过的许多公司的特点。通过 MusicCorp 所分享的故事将反映我所看到的现实情况,但它们并不一定都发生在同一家公司。
[1] 参阅 Eric Evans 所著的《领域驱动设计:软件核心复杂性应对之道》( Domain-Driven Design: Tackling Complexity in the Heart of Software ,由人民邮电出版社于2010年出版)中对领域驱动设计的深入介绍,或参阅 Vaughn Vernon 的《领域驱动设计精粹》( Domain-Driven Design Distilled ,由电子工业出版社于2018年出版)以获取简洁的概述。——编者注
[2] Matthew Skelton 和 Manuel Pais 所著的《高效能团队模式》( Team Topologies )由电子工业出版社于2021年出版。——编者注