

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# strangler fig 模式
<a name="strangler-fig"></a>

## 意图
<a name="strangler-fig-intent"></a>

strangler fig 模式有助于逐步将单体应用程序迁移到微服务架构，从而降低转型风险和业务中断。

## 动机
<a name="strangler-fig-motivation"></a>

开发单体应用程序是为了在单个流程或容器中提供大部分功能。代码紧密耦合。因此，应用程序更改需要进行彻底的重新测试，以避免出现回归问题。这些更改无法单独进行测试，这会影响周期时间。随着应用程序的功能越来越丰富，高度复杂性会导致更多的维护时间，增加上市时间，从而减缓产品创新。

当应用程序规模扩大时，它会增加团队的认知负担，并可能导致团队所有权界限不明确。根据负载扩展单个功能是不可能的，必须扩展整个应用程序以支持峰值负载。随着系统的老化，该技术可能会过时，从而推高支持成本。单体遗留应用程序遵循开发时可用的最佳实践，并非为分发而设计。

当单体应用程序迁移到微服务架构时，可以将其拆分为较小的组件。这些组件可以独立扩展，可以独立发布，也可以由各个团队拥有。这会导致更快的更改速度，因为更改是局部的，可以快速测试和发布。更改的影响范围较小，因为组件是松耦合的，可以单独部署。

通过重写或重构代码将单体完全替换为微服务应用程序是一项艰巨的任务，也是一个很大的风险。大爆炸式迁移即在单个操作中迁移单体，这会带来转型风险和业务中断。在重构应用程序时，添加新功能极其困难，甚至是不可能的。

解决此问题的一种方法是使用 Martin Fowler 引入的 strangler fig 模式。此模式涉及通过逐步提取功能并围绕现有系统创建新应用程序，来迁移到微服务。单体中的功能逐渐被微服务所取代，应用程序用户可以逐步使用新迁移的功能。当所有功能都迁移到新系统时，可以安全地停用单体应用程序。

## 适用性
<a name="strangler-fig-applicability"></a>

在以下情况下使用 strangler fig 模式：
+ 您想逐步将单体应用程序迁移到微服务架构。
+ 由于单体规模庞大且复杂，采用大爆炸式迁移方法存在风险。
+ 该企业想要添加新功能，迫不及待地想完成转型。
+ 在转型期间，必须将最终用户受到的影响降至最低。

## 问题和注意事项
<a name="strangler-fig-issues"></a>
+ **代码库访问权限：**要实现 strangler fig 模式，您必须有权访问单体应用程序的代码库。随着功能从单体中迁移，您将需要对代码进行细微的更改，并在单体内实现防腐层，以将调用路由到新的微服务。如果没有代码库访问权限，则无法拦截调用。代码库访问权限对于重定向传入请求也至关重要，可能需要进行一些代码重构，以便代理层可以拦截对迁移功能的调用并将其路由到微服务。
+ **领域不明确：**过早地对系统进行分解可能会付出高昂的代价，尤其是在领域不明确的情况下，并且可能会错误地划分服务边界。领域驱动型设计（DDD）是一种理解领域的机制，而事件风暴是一种确定领域边界的技术。
+ **识别微服务：**您可以使用 DDD 作为识别微服务的关键工具。要识别微服务，请寻找服务类别之间的自然划分。许多服务将拥有自己的数据访问对象，且可以轻松解耦。具有相关业务逻辑的服务和没有依赖关系或几乎没有依赖关系的类别是微服务的理想选择。您可以在分解单体之前重构代码，以防止紧密耦合。您还应该考虑合规性要求、发布节奏、团队的地理位置、扩展需求、使用案例驱动型技术需求以及团队的认知负荷。
+ **防腐层：**在迁移过程中，当单体内的功能必须调用作为微服务迁移的功能时，您应该实现防腐层（ACL），将每次调用路由到相应的微服务。为解耦单体内的现有调用方并防止对其进行更改，ACL 可用作适配器或 Facade，以将调用转换为较新的接口。本指南前面的 ACL 模式的[“实现”部分](acl.md#acl-implementation)对此进行了详细讨论。
+ **代理层故障：**在迁移期间，代理层会拦截发送到单体应用程序的请求，并将这些请求路由到遗留系统或新系统。但是，此代理层可能成为单点故障或性能瓶颈。
+ **应用程序复杂性：**大型单体从 strangler fig 模式中获益最大。对于小型应用程序而言，由于完全重构的复杂性较低，与其迁移应用程序，不如采用微服务架构重写应用程序，这样可能更有效率。
+ **服务交互：**微服务可以同步或异步通信。需要同步通信时，考虑超时是否会导致连接或线程池消耗，从而导致应用程序性能问题。在这种情况下，对于可能长时间失败的操作，使用[断路器模式](circuit-breaker.md)可立即返回失败结果。异步通信可以通过使用事件和消息队列来实现。
+ **数据聚合：**在微服务架构中，数据分布在数据库之间。需要数据聚合时，您可以在前端使用 [AWS AppSync](https://aws.amazon.com/appsync/)，或者在后端使用命令查询责任分割（CQRS）模式。
+ **数据一致性：**微服务拥有自己的数据存储，单体应用程序也可能使用此数据。要启用共享，您可以使用队列和代理将新微服务的数据存储与单体应用程序的数据库同步。但是，这可能会导致数据冗余以及两个数据存储之间的最终一致性，因此我们建议您将其视为战术解决方案，直到您可以建立长期解决方案，例如数据湖。

## 实施
<a name="strangler-fig-implementation"></a>

在 strangler fig 模式中，您可以将特定功能替换为新的服务或应用程序，一次一个组件。代理层会拦截发送到单体应用程序的请求，并将这些请求路由到遗留系统或新系统。由于代理层会将用户路由到正确的应用程序，因此您可以向新系统添加功能，同时确保单体继续运行。新系统最终取代了旧系统的所有功能，您可以将其停用。

### 架构简析
<a name="fig-high-level-arch"></a>

在下图中，单体应用程序具有三项服务：用户服务、购物车服务和账户服务。购物车服务依赖于用户服务，应用程序使用单体关系数据库。

![具有三种服务的单体应用程序。](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/fig-1.png)


第一步是在店铺界面和单体应用程序之间添加代理层。开始时，代理会将所有流量路由到单体应用程序。

![向单体应用程序添加代理。](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/fig-2.png)


当您想向应用程序添加新功能时，可以将它们作为新的微服务来实现，而不是向现有的单体添加功能。但是，为了确保应用程序的稳定性，您需继续修复单体中的错误。在下图中，代理层根据 API URL 将调用路由到单体或新的微服务。

![代理将调用路由到单体或新的微服务。](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/fig-3.png)


#### 添加防腐层
<a name="strangler-fig-implementation-acl"></a>

在以下架构中，用户服务已迁移到微服务。购物车服务调用用户服务，但在单体中不再提供该实现。此外，新迁移的服务的接口可能与其在单体应用程序内部的先前接口不匹配。为了应对这些更改，您需要实施 ACL。在迁移过程中，当单体内的功能需要调用作为微服务迁移的功能时，ACL 会将调用转换为新接口并将其路由到相应的微服务。

![添加 ACL 以将调用转换为新接口。](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/fig-4.png)


您可以在单体应用程序内部实现 ACL，将其作为特定于已迁移服务的类别；例如 `UserServiceFacade` 或 `UserServiceAdapter`。在所有从属服务都迁移到微服务架构后，必须停用 ACL。

使用 ACL 时，购物车服务仍会调用单体内的用户服务，而用户服务通过 ACL 将呼叫重定向到微服务。购物车服务仍应在不知道微服务迁移的情况下调用用户服务。此松耦合是减少回归和业务中断所必需的。

#### 处理数据同步
<a name="strangler-fig-implementation-sync"></a>

作为最佳实践，微服务应拥有自己的数据。用户服务将其数据存储在自己的数据存储中。它可能需要将数据与单体数据库同步，以处理报告等依赖关系，并支持尚未准备好直接访问微服务的下游应用程序。单体应用程序可能还需要其他尚未迁移到微服务的功能和组件的数据。因此，需要在新的微服务和单体之间进行数据同步。要同步数据，您可以在用户微服务和单体数据库之间引入同步代理，如下图所示。每当用户微服务的数据库更新时，用户微服务都会向队列发送一个事件。同步代理会侦听队列，并持续更新单体数据库。对于正在同步的数据，单体数据库中的数据最终会保持一致。

![添加同步代理。](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/fig-5.png)


#### 迁移其他服务
<a name="strangler-fig-implementation-more"></a>

当购物车服务从单体应用程序中迁移出来时，其代码会修改为直接调用新服务，因此 ACL 不再路由这些调用。下图阐明了此架构。

![迁移其他服务。](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/fig-6.png)


下图显示了最终绞杀状态，其中所有服务都已从单体中迁出，仅剩单体的骨架。历史数据可以迁移到各个服务拥有的数据存储。ACL 可以删除，此阶段单体即可停用。

![迁移所有服务后的最终绞杀状态。](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/fig-7.png)


下图显示了单体应用程序停用后的最终架构。您可以根据应用程序的要求通过基于资源的 URL（例如 `http://www.storefront.com/user`）或通过其自己的域（例如 `http://user.storefront.com`）托管各个微服务。有关使用主机名和路径 APIs 向上游使用者公开 HTTP 的主要方法的更多信息，请参阅 [API 路由模式部分](api-routing.md)。

![停用单体后的最终架构。](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/fig-8.png)


### 使用 AWS 服务实施
<a name="strangler-fig-implementation-aws-services"></a>

#### 使用 API Gateway 作为应用程序代理
<a name="strangler-fig-implementation-api-gateway"></a>

下图显示了单体应用程序的初始状态。假设它是 AWS 通过使用 lift-and-shift策略迁移到的，因此它在[亚马逊弹性计算云 (Amazon EC2) 实例上运行，并使用亚马逊](https://aws.amazon.com/ec2/)[关系数据库服务 (Amazon RDS) 数据库](https://aws.amazon.com/rds/)。为简单起见，该架构使用具有一个私有子网和一个公有子网的单个虚拟私有云（VPC），假设微服务最初将部署在同一个 AWS 账户内。（生产环境中的最佳实践是使用多账户架构来确保部署的独立性。） EC2 实例驻留在公有子网中的单个可用区中，RDS 实例驻留在私有子网中的单个可用区中。[亚马逊简单存储服务 (Amazon S3) S](https://aws.amazon.com/s3/) ervice 为 JavaScript网站存储静态资产，例如、CSS 和 React 文件。

![使用 strangler fig 模式时单体应用程序的初始状态。](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/fig-9.png)


在以下架构中，[AWS Migration Hub Refactor Spaces](https://docs.aws.amazon.com/migrationhub-refactor-spaces/latest/userguide/what-is-mhub-refactor-spaces.html) 在单体应用程序前部署 [Amazon API Gateway](https://aws.amazon.com/api-gateway/)。Refactor Spaces 会在您的账户内部创建重构基础设施，而 API Gateway 充当将调用路由到单体的代理层。最初，所有调用都通过代理层路由到单体应用程序。如前所述，代理层可能成为单点故障。但是，使用 API Gateway 作为代理可降低风险，因为它是一项无服务器的多可用区服务。

**注意**  
AWS Migration Hub Refactor Spaces 自 2025 年 11 月 7 日起，不再向新客户开放。要获得类似的功能 AWS Migration Hub Refactor Spaces，请探索[AWS Transform](https://aws.amazon.com/transform)。

![使用 API Gateway 实现 strangler fig 模式。](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/fig-10.png)


用户服务迁移到 Lambda 函数中，[Amazon DynamoDB](https://aws.amazon.com/dynamodb/) 数据库存储其数据。Lambda 服务端点和默认路由已添加到 Refactor Spaces，且 API Gateway 会自动配置为将调用路由到 Lambda 函数。

![使用 API Gateway 实现 strangler fig 模式：配置路由。](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/fig-11.png)


在下图中，购物车服务也已从单体迁移到 Lambda 函数。Refactor Spaces 中添加了额外的路由和服务端点，流量会自动割接到 `Cart` Lambda 函数。[Lambda 函数的数据存储由亚马逊管理。 ElastiCache](https://aws.amazon.com/elasticache/)单体应用程序仍与 Amazon RDS 数据库一起保留在 EC2 实例中。

![使用 strangler fig 模式将服务从单体中迁移出来。](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/fig-12.png)


在下图中，最后一个服务（账户）从单体迁移到 Lambda 函数中。它继续使用原始 Amazon RDS 数据库。新架构现在具有三个带有独立数据库的微服务。每种服务均使用不同类型的数据库。使用专用数据库来满足微服务的特定需求这一概念称为*多语言持久性*。Lambda 函数也可以用不同的编程语言实现，具体取决于使用案例。在重构期间，Refactor Spaces 会自动将流量割接和路由到 Lambda。这可以为您的构建者节省架构、部署和配置路由基础设施所需的时间。

![使用 strangler fig 模式将所有服务从单体中迁移出来。](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/fig-13.png)


#### 使用多个账户
<a name="strangler-fig-implementation-migration-hub"></a>

在之前的实现中，我们使用了具有私有子网和公有子网的单个 VPC 作为单体应用程序，为了简单起见，我们在同一个 AWS 账户 内部署了微服务。但是，在现实场景中，这种情况很少见，在这些场景中， AWS 账户 为了独立于部署，微服务通常被分成多个部署。在多账户结构中，您需要配置从单体到不同账户的新服务的路由流量。

[重构空间](https://docs.aws.amazon.com/migrationhub-refactor-spaces/latest/userguide/what-is-mhub-refactor-spaces.html)可帮助您创建和配置 AWS 基础架构，以便将 API 调用从单体应用程序中路由出去。作为应用程序资源的一部分，Refactor Spaces 在您的 AWS 账户内编排 [API Gateway](https://aws.amazon.com/api-gateway/)、[网络负载均衡器](https://aws.amazon.com/elasticloadbalancing/)和基于资源的 [AWS Identity and Access Management （IAM）](https://aws.amazon.com/iam/)策略。您可以透明地将单个 AWS 账户 或多个账户中的新服务添加到外部 HTTP 端点。所有这些资源都在您的内部编排，可以在 AWS 账户 部署后进行自定义和配置。

假设用户和购物车服务部署到两个不同的账户，如下图所示。使用 Refactor Spaces 时，您只需要配置服务端点和路由。Refactor Spaces 可自动执行 [API Gateway—Lambda](https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-with-lambda-integration.html) 集成和 Lambda 资源策略的创建，因此您可以专注于安全地从单体上重构服务。

![使用实现 strangler fig 模式。 AWS Migration Hub Refactor Spaces](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/fig-14.png)


有关使用 Refactor Spaces 的视频教程，请参阅 [Refactor Apps Incrementally with AWS Migration Hub Refactor Spaces](https://www.youtube.com/watch?v=2KAyPNEi9aw)。

## 研讨会
<a name="strangler-fig-workshop"></a>
+ [Iterative App Modernization 讲习会](https://catalog.us-east-1.prod.workshops.aws/workshops/f2c0706c-7192-495f-853c-fd3341db265a/en-US)

## 参考博客文章
<a name="strangler-fig-blog"></a>
+ [AWS Migration Hub Refactor Spaces](https://aws.amazon.com/blogs/aws/new-aws-migration-hub-refactor-spaces-helps-to-incrementally-refactor-your-applications/)
+ [Deep Dive on an AWS Migration Hub Refactor Spaces](https://aws.amazon.com/blogs/mt/deep-dive-on-an-aws-migration-hub-refactor-spaces-environment/)
+ [部署管线参考架构和参考实现](https://aws.amazon.com/blogs/aws/new_deployment_pipelines_reference_architecture_and_-reference_implementations/)

## 相关内容
<a name="strangler-fig-resources"></a>
+ [API 路由模式](api-routing.md)
+ [Refactor Spaces 文档](https://docs.aws.amazon.com/migrationhub-refactor-spaces/latest/userguide/what-is-mhub-refactor-spaces.html)