3.1 分解架构

在和客户讨论分解数据、服务和团队后,客户经常向我提出这样的问题,“太棒了!但是我们要怎样实现呢?”这是个好问题。如何拆分已有的单体应用并把他们迁移上云呢?

事实证明,我已经看到了很多成功的例子,使用增量迁移这种相当可复制的模式,我现在向我所有的客户推荐这种模式。SoundCloud 和 Karma 就是公开的例子。

本节中,我们将讲解如何一步步地将单体服务分解并将它们迁移到云上。

新功能使用微服务形式

您可能感到很惊奇,第一步不是分解单体应用。我们假设您依然要在单体应用中构建服务。事实上,如果您没有任何新的功能来构建,那么您甚至不应该考虑这个分解。(鉴于我们的主要动机是速度,您如何维持原状还能获取速度呢?)

团队决定,处理架构变化的最佳方法不是立即分解 Mothership 架构,而是不添加任何新的东西。我们所有的新功能以微服务形式构建…

——Phil Calcado, SoundCloud

所以不要继续再向单体应用中增加代码,将所有的新功能以微服务的形式构建。这是第一步就要考虑好的,因为从头开始构架一个服务比分解一个单体应用并提出服务出来容易和快速的多。

然而有一点不可避免,就是新构建的微服务需要与已有的单体应用通信才能完成工作,这个问题怎么解决?

隔离层

因为我们大部分的业务逻辑都是基于 Rails 的单体应用,所以我们的微服务基本也要跟它们通信。

——Phil Calcado, SoundCloud

Eric Evans(Addison-Wesley)的领域驱动设计(DDD)讨论了隔离层的思想。其目的是允许两个系统的集成,而不允许一个系统的领域模型破坏另一个系统的领域模型。当您将新功能集成到微服务中时,不希望这些新服务与整体的紧密结合,让他们深入了解整体的内部结构。隔离层是创建 API 协议的一种方式,使得整体架构看起来像其他微服务。

Evans 将隔离层的实施划分为三个子模块,前两个代表着经典设计模式。

(来自 Gamma 等人,Design Patterns:Elements of Reusable Object-Oriented So ware [Addison Wesley]):

表现层

表现层的目的是为了简化与单体应用接口集成的过程。单体应用设计之初很可能没有考虑这个集成,因此我们引入了表现层来解决这个问题。它没有改变单体应用的模型,这很重要,注意不要将转换和集成问题耦合到一起。

适配器

我们用适配器来定义 service,用来提供我们需要的新功能。它知道如何获取系统请求并使用协议将请求发送给单体应用的表层。

转换器

转换器的职责是在单体应用与新的微服务之间进行请求和响应的领域模型转换。

这三个松耦合的组件解决了以下三个问题:

  1. 系统集成
  2. 协议转换
  3. 模型转换

剩下的是通信链路的位置。在 DDD 中,Evans 讨论了两种选择。当您无法访问或更改遗留系统时,第一,将系统的表现层设置为主要功能。我们的重点在于我们控制的整体,所以我们将倾向于 Evans 的第二个建议,适配器到表现层。使用这种替代方法,我们将表现层构筑到单体中,允许在适配器和表现层之间进行通信,因为在为此专门构建的组件之间创建连接更容易。

最后,要注意隔离层可以促进双向通信。正如我们新的微服务可能需要与整体进行通信以完成工作一样,反之亦然,特别是当我们进入下一阶段时。

扼杀单体应用

在架构调整后,我们的团队可以在更加灵活的环境中自由构建新功能和增强功能。然而,一个重要的问题仍然存在:我们如何从名为 Mothership 的单体 Rails 应用程序中提取功能?

——Pilil Calcado,SoundCloud

我从 Martin Fowler 的题为“扼杀应用”的文章中借用了“扼杀巨石”的想法。在这篇文章中,Fowler 解释了逐渐创造“围绕旧系统边缘的新系统,让它几年来慢慢增长,直到旧系统被扼杀”的想法。这种情况同样适用于我们。通过提取的微服务和其他隔离层的组合,我们将围绕现有单体的边缘构建一个新的云原生系统。

两个标准帮助我们选择要提取哪些组件:

  1. SoundCloud 指出了第一个标准:识别单体中的有界上下文。如果您回想起我们之前讨论有限上下文,它需要一个内部一致的领域模型。我们的单体领域模型极有可能不是内部一致的。现在是开始识别子模型的时候了,里面有我们要提取的候选者。
  2. 第二个标准是优先考虑的:在众多的候选者中我们应该首先提取哪一个呢?我们可以回顾一下迁移到云原生架构的第一个原因:创新速度。什么候选微服务将最受益于创新速度?我们显然希望选择那些正在改变我们当前业务需求的服务。看看单体应用的积压。确定需要更改的代码的区域,以便提交更改的要求,然后在进行所需更改之前提取适当的有界上下文。

潜在的结束状态

我们怎么知道何时结束?下面有两个基本的结束状态:

  1. 单体架构已经被完全扼杀。所有的有界上下文都被提取为微服务。最后一步是确定消除不再需要隔离层的机会。
  2. 单体架构被扼杀到了这样一个点:额外服务提取的成本超过必要开发努力的回报。单体的一些部分可能相当稳定 —— 它们几年来都没有改变,还是一直都在运行得好好的。迁移这些部分可能没有太大的价值,维持必要的隔离层与其集成的成本足够低,我们可以长期负担。