购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.2 合理划分微服务边界

在 MusicCorp 团队全力以赴、开发一个又一个在线服务,并试图向所有人售卖八轨磁带之前,让我们暂停一下,谈谈需要时刻谨记的最重要的基本理念。我们希望微服务能够独立更改和独立部署,并将功能发布给用户。能够独立更改一个微服务而不影响其他微服务的能力至关重要。那么在划定微服务边界时,我们需要考虑哪些因素呢?

从本质上说,微服务只是模块化拆分的另一种方式,尽管它在模块之间具有基于网络的交互问题,以及由此带来的相关挑战。这仍然意味着可以依靠模块化软件和结构化编程领域的大量现有技术,来帮助和指导我们去定义模块间的边界。考虑到这一点,我们需要更深入地探究第1章简要介绍过的3个关键概念—— 信息隐藏 内聚 耦合 ,这些概念对于确定良好的微服务边界至关重要。

2.2.1 信息隐藏

信息隐藏是 David Parnas 提出的一个概念,旨在研究定义模块边界最有效的方法。 信息隐藏描述了一种期望,即将尽可能多的实现细节隐藏在模块(微服务)边界内。Parnas 研究了这种模块划分理论上带来的好处。

提升开发效率

允许模块独立开发,我们可以并行完成更多的工作,并减少添加项目人员带来的影响。

可理解性

每个模块都可以被独立地看待和理解,这使整个系统的功能更易于理解。

灵活性

每个模块可以被独立地更改,即在无须更改其他模块的情况下,我们仍然可以对系统的功能进行更改。此外,模块还可以按不同的方式组合以提供新功能。

以上特征很好地补充了我们试图通过微服务架构实现的目标——事实上,我现在的确将微服务视为模块化架构的另一种方式。Adrian Colyer 回顾了 David Parnas 在这一时期发表的一些论文,并从微服务的角度对它们进行了研读,强烈推荐你学习他的总结。

正如 Parnas 在他的大部分工作中所探究的,仅仅划分模块并不会直接获得收益,获取收益很大程度上取决于模块边界是如何划分的。根据他的研究,信息隐藏这种关键技术有助于充分利用模块化架构,而现在看来,同样适用于微服务。

在 Parnas 的另一篇论文 中,我们还认识到下面这句至关重要的话:

模块之间的连接是模块相互之间做出的假设。

通过减少一个模块(或微服务)对另一个模块的假设数量,可以直接影响它们之间的连接。限制假设的数量更能够确保在更改一个模块时不会影响其他模块。如果更改模块的开发人员清楚地了解其他人如何使用该模块,那么开发人员将更容易且安全地完成更改,从而使得上游调用方也不必更改。

这同样适用于微服务,唯一的区别是我们可以只部署已修改的微服务,而无须部署其他。可以说,微服务架构放大了 Parnas 所描述的3个理想特征,即提升开发效率、可理解性和灵活性。

信息隐藏的影响体现在很多方面,我将在整本书中探讨这个主题。

2.2.2 内聚

关于内聚,我听过的最简明的定义是:“一起变化的代码应该组织在一起。” 就我们的目标而言,这是一个相当合适的定义。之前我们讨论过,优化架构就是为了更容易地进行业务变更,因此我们希望以这种方式对功能进行分组,从而将变更限制在尽可能小的范围内。

为了在行为发生改变时,能够在同一地方进行更改并尽快发布,我们希望将相关行为放在一起,无关行为放在别处。如果必须在很多不同的地方实施改造,就必须发布很多不同的服务(也许需要同时发布)来实现这个变更。在许多不同的地方进行改造会更慢,而一次部署许多服务是有风险的,因此我们需要避免出现这两种情况。

所以,我们希望在问题域中找到边界,确保相关行为放在了一起,并尽可能松散地与其他边界通信。如果相关功能分散在整个系统中,这被称为“低内聚”,而对于微服务架构,我们的目标是要实现高内聚。

2.2.3 耦合

当服务之间耦合度较低时,对一个服务的更改不应依赖于对另一个服务的更改。微服务的全部意义在于能够对服务进行独立更改和部署,而无须更改任何其他部分。这相当重要。

什么样的因素会导致高耦合呢?一种典型的错误是,选择一种将各种服务紧密绑定的集成方式,从而导致服务内部的更改也需要对消费者进行更改。

在构建低耦合系统时,相互协作的服务之间应该只了解最少必要信息,这也意味着需要限制服务间不同类型调用的数量,因为除了潜在的性能问题,过于频繁的通信会导致紧密耦合。

耦合有多种形式,我看到过不少关于耦合的误解是涉及服务架构的,所以我认为很有必要对这个话题进行更详细地探讨。下面将深入探究这一话题。

2.2.4 内聚和耦合的相互作用

正如之前提到的,耦合和内聚的概念显然是一脉相承的。从逻辑上讲,如果某个功能分散在系统中,对该功能的变更就会跨越这些边界,这意味着更高的耦合性。以结构化设计先驱 Larry Constantine 命名的康斯坦丁定律巧妙地总结了这一点:

高内聚低耦合的结构是稳定的。 [1]

这句话里所说的“稳定”的概念很重要。为了让微服务边界的划分可以支持独立部署、实现并行开发以及团队之间更少的协调工作,边界本身就需要具有一定程度的稳定性。如果微服务暴露的契约以向后不兼容的方式不断变化,那么上游消费者也会因此而被迫不断变化。

耦合和内聚密切相关,甚至在某种程度上可以说是相同的,因为这两个概念都描述了事物之间的关系。内聚用于描述 边界内 事物之间的关系(这里是指微服务),而耦合则用于描述 跨边界 事物之间的关系。没有绝对 最佳 的方式来组织代码;对代码分组的方式和原因需要我们进行各种权衡,而内聚和耦合只是用来阐明权衡结果的一种方式。我们能做的就是在这两种想法之间找到平衡,找到一种最适用于当前场景以及问题的做法。

要记住的是,世界瞬息万变。随着需求的变化,你可能需要重新审视曾经做出的决策。有时,系统的某些部分可能正在经历非常大的变化,以至于无法保持稳定。在第3章中分享 Snap CI 的开发团队的经验时,我将举一个例子。

[1] 在我的 Monolith to Microservices 一书中,我将这句话归功于 Larry Constantine。(这本书的中文版《重构到微服务》由中国电力出版社于2023年出版。)虽然这个定律巧妙地总结了 Larry Constantine 在结构化设计领域的大部分工作,但实际上应该归功于 Albert Endres 和 Dieter Rombach,出自两人的著作 A Handbook of Software and Systems Engineering (2003年由 Addison-Wesley 出版)。 swa/aGLJP7vZCjVvtgYXwxQckua5GenmqqYlcK3dQ+kCe1uCbuZM3j6skeCfUDg0

点击中间区域
呼出菜单
上一章
目录
下一章
×