将无服务器、事件驱动的架构扩展到现有的容器工作负载

作者: 詹姆斯·贝斯威克 | 202

这篇文章由南非首席专家迪拉吉·马哈帕特罗、南澳首席专家萨莎·穆勒林以及整合服务WW负责人艾米丽·谢伊撰写。

许多无服务器服务自然适合 事件驱动架构 (EDA),因为事件会调用它们,并且只有在有事件需要处理时才会运行。在云中构建时,许多服务默认会发出事件,并具有用于管理事件的内置功能。这种组合使客户能够比以往任何时候都更容易、更快地构建事件驱动架构。

本博客系列 中的 保险索赔处理示例应用程序 使用事件驱动的架构原理和无服务器服务,例如亚马逊云科技 Lambda 、 亚马逊云科技 Step Fun ctions、A mazon API Gat eway、 Amazon Ev ent Bridge和A mazon SQS。

在构建事件驱动架构时,您可能有现有服务可以与新架构集成,理想情况下,无需对这些服务进行重大重构更改。由于服务通过事件进行通信,因此将应用程序扩展到新的和现有的微服务是使用 EDA 进行构建的主要好处。你可以用不同的编程语言编写这些微服务,也可以在不同的计算选项上运行。

这篇博文介绍了将现有的容器化服务(结算服务)集成到本博客文章中描述的无服务器、事件驱动的保险索赔处理应用程序的场景。

事件驱动架构示例概述

示例应用程序 使用前端注册新用户,并允许用户上传其汽车和驾驶执照的图像。注册后,他们可以提出索赔并上传受损汽车的图片。以前,它尚未与结算服务整合以完成索赔和结算流程。

在这种情况下,结算服务是一个 棕地应用程序 ,使用 亚马逊云科技 Fargate 在 亚马逊 EC S 上运行 Spring Boot 3 。亚马逊云科技 Fargate 是一款无服务器、按使用量付费的计算引擎,可让您在不管理服务器的情况下专注于构建容器应用程序。

Spring Boot 应用程序公开了一个 REST 端点,该端点接受 POST 请求。它应用结算业务逻辑,并在数据库中为汽车保险索赔创建结算记录。您的目标是使用专为索赔处理而设计的全新 EDA 应用程序进行结算,而无需重新设计架构或重写。客户、索赔、欺诈、文档和通知是下图中以蓝色方框显示的其他域:

Reference architecture

项目结构

该应用程序使用 亚马逊云科技 云开发套件 (CDK) 来构建堆栈。借助 CDK,您可以灵活地使用所选语言创建模块化和可重复使用的结构。示例应用程序使用用于 CDK 的 TypeScript。

以下项目结构使您能够构建不同的 边界上下文 。事件驱动架构依赖于领域间事件的编排。CDK 的 面向 对象编程 (OOP) 概念有助于提供基础架构,将领域问题分开,同时通过事件松散地耦合它们。

您可以将更高级别的 CDK 结构分解为以下相应的域:

Comparing domains

应用程序和基础架构代码存在于每个域中。这种项目结构创建了一种无缝的方式,可以在不影响其他业务领域的情况下,通过其应用程序和基础设施代码添加结算等新域。

在前面的结构中,你可以在 claims-processing-stack.ts 中使用settlement-service.ts CDK结构:

const settlementService = new SettlementService(this, "SettlementService", {
  bus,
});

SettlementService 结构唯一需要的信息是在索赔处理堆栈中创建的 EventBridge 自定义事件总线资源。

要运行示例应用程序,请按照示例应用程序的 README 文件 中的 设置步骤 进行操作。

现有容器工作负载

结算域为组织的其他部门提供 REST 服务。Docker 容器化 Spring Boot 应用程序使用 亚马逊云科技 Fargate 在亚马逊 ECS 上运行。以下序列图显示了从外部 REST 客户端到服务的同步请求-响应流:

Settlement service

  1. 外部 REST 客户端通过内部 应用程序负载均衡器 (ALB) 前面的 HTTP API 进行 POST /结算调用。
  2. SettlementController.java 代表参加 SettlementService.java。
  3. 结算服务应用业务逻辑并调用结算存储库以保持数据持久性。
  4. 结算存储库将该项目保留在结算 DynamoDB 表中。

对 HTTP API 端点的请求如下所示:

curl --location <settlement-api-endpoint-from-cloudformation-output> \
--header 'Content-Type: application/json' \
--data '{
  "customerId": "06987bc1-1234-1234-1234-2637edab1e57",
  "claimId": "60ccfe05-1234-1234-1234-a4c1ee6fcc29",
  "color": "green",
  "damage": "bumper_dent"
}'

来自 API 调用的响应是:

API response

您可以在此处了解有关在 亚马逊云科技 Far gate 上 优化 Spring Boot 应用程序 的更多信息。

扩展事件的容器工作负载

要集成结算服务,您必须更新服务以异步接收和发出事件。结算服务的核心逻辑保持不变。当您提出索赔、上传损坏的汽车图像且应用程序未检测到任何文件欺诈时,结算域会订阅 Fraud.Not.Detected 事件并应用其业务逻辑。结算服务在应用业务逻辑后会重新发出事件。

以下序列图显示了与 EDA 配合使用的新结算接口。结算服务订阅制作人发布的事件。在这里,事件制作者是将事件置于 EventBridge 自定义事件总线中的欺诈服务。

Sequence diagram

  1. 制作者向 EventBridge 自定义事件总线发出 Fraud.Not.Tectived 事件。
  2. EventBridge 评估结算域提供的规则,并将事件负载发送到目标 SQS 队列。
  3. SubscriberService.java 在 SQS 队列中 轮询新消息。
  4. 在消息上,它将消息正文转换为结算服务接受的输入对象。
  5. 然后,它将呼叫委托给结算服务,类似于SettlementController在REST实现中的工作方式。
  6. 结算服务应用业务逻辑。流程就像从 7 到 10 的 REST 用例。
  7. 收到来自结算服务的回复后,订阅服务会转换响应,将事件发布回事件总线,事件类型为 Settlement.Financed。

架构的其余部分消耗了这个 Settle ment.Financed 事件。

使用 EventBridge 架构注册表和发现

Schema 强制执行生产者与消费者之间的合同。每次事件到达时,消费者都期望事件负载的确切结构。EventBridge 提供 架构注册和发现功能 来维护此合同。消费者(结算服务)可以下载代码绑定并在源代码中使用它们。

在下载代码绑定并在存储库中使用它们之前,在 EventBridge 中启用架构发现。 代码绑定提供了一个编组器,可以将传入的事件从 SQS 队列解组为 普通的旧 Java 对象 (POJO) FraudNotDetected.java。 您可以选择 IDE 来下载代码绑定。 适用于 IntelliJ 的 亚马逊云科技 工具包 可以方便地下载和使用它们。

Download code bindings

采用 REST 和事件驱动架构的结算服务的最终架构如下所示:

Final architecture

过渡到完全由事件驱动的状态

凭借处理事件的新功能,Spring Boot 应用程序现在通过不同的接口运行相同的业务逻辑,同时支持 REST 端点和事件驱动架构。在此示例场景中,随着事件驱动架构的成熟以及组织其他部门的采用,使用 POST 端点保存结算的需求可能会减少。将来,您可以弃用端点并完全依赖来自 SQS 队列的轮询消息。

你从使用 ALB 和 Fargate 服务 CDK ECS 模式开始:

const loadBalancedFargateService = new ecs_patterns.ApplicationLoadBalancedFargateService(
  this,
  "settlement-service",
  {
    cluster: cluster,
    taskImageOptions: {
      image: ecs.ContainerImage.fromDockerImageAsset(asset),
      environment: {
        "DYNAMODB_TABLE_NAME": this.table.tableName
      },
      containerPort: 8080,
      logDriver: new ecs.AwsLogDriver({
        streamPrefix: "settlement-service",
        mode: ecs.AwsLogDriverMode.NON_BLOCKING,
        logRetention: RetentionDays.FIVE_DAYS,
      })
    },
    memoryLimitMiB: 2048,
    cpu: 1024,
    publicLoadBalancer: true,
    desiredCount: 2,
    listenerPort: 8080
  });

为了适应 EDA,您需要更新资源以改装 SQS 队列以接收消息,并将 EventBridge 改造为放置事件。向 Applicati onLoadBal ancerFargateService 资源添加新的环境变量:

environment: {
  "SQS_ENDPOINT_URL": queue.queueUrl,
  "EVENTBUS_NAME": props.bus.eventBusName,
  "DYNAMODB_TABLE_NAME": this.table.tableName
}

授予 Fargate 任务权限,将事件放入自定义事件总线并使用来自 SQS 队列的消息:

props.bus.grantPutEventsTo(loadBalancedFargateService.taskDefinition.taskRole);
queue.grantConsumeMessages(loadBalancedFargateService.taskDefinition.taskRole);

当您将结算服务过渡到完全由事件驱动时,您不再需要 HTTP API 端点和 ALB,因为 SQS 是事件的来源。

更好的选择是使用 QueueProcessingFargateService ECS 模式作为 Fargate 服务 。除了 CPU 利用率外,该模式还基于 SQS 队列中可见消息的数量提供自动扩展。在以下示例中,您还可以在设置 Fargate 服务 时添加两种 容量提供商策略 :FAR GATE_SPOT 和 FARGATE 。这意味着,对于使用 FARGATE 运行的每一项任务,就有两个使用 FARGATE_SPOT 的任务。这可以帮助优化成本。

const queueProcessingFargateService = new ecs_patterns.QueueProcessingFargateService(this, 'Service', {
  cluster,
  memoryLimitMiB: 1024,
  cpu: 512,
  queue: queue,
  image: ecs.ContainerImage.fromDockerImageAsset(asset),
  desiredTaskCount: 2,
  minScalingCapacity: 1,
  maxScalingCapacity: 5,
  maxHealthyPercent: 200,
  minHealthyPercent: 66,
  environment: {
    "SQS_ENDPOINT_URL": queueUrl,
    "EVENTBUS_NAME": props?.bus.eventBusName,
    "DYNAMODB_TABLE_NAME": tableName
  },
  capacityProviderStrategies: [
    {
      capacityProvider: 'FARGATE_SPOT',
      weight: 2,
    },
    {
      capacityProvider: 'FARGATE',
      weight: 1,
    },
  ],
});

此模式根据队列深度抽象化 Fargate 服务的自动扩展行为。

运行应用程序

要测试应用程序,请在初始设置 后按照 “ 如何使用应用程序 ” 进行操作。完成后,您会看到浏览器接收 Settlement.Finabiled 事件:

{
  "version": "0",
  "id": "e2a9c866-cb5b-728c-ce18-3b17477fa5ff",
  "detail-type": "Settlement.Finalized",
  "source": "settlement.service",
  "account": "123456789",
  "time": "2023-04-09T23:20:44Z",
  "region": "us-east-2",
  "resources": [],
  "detail": {
    "settlementId": "377d788b-9922-402a-a56c-c8460e34e36d",
    "customerId": "67cac76c-40b1-4d63-a8b5-ad20f6e2e6b9",
    "claimId": "b1192ba0-de7e-450f-ac13-991613c48041",
    "settlementMessage": "Based on our analysis on the damage of your car per claim id b1192ba0-de7e-450f-ac13-991613c48041, your out-of-pocket expense will be $100.00."
  }
}

正在清理

该堆栈创建自定义 VPC 和其他相关资源。一定要在使用后清理资源,以避免持续运行这些服务的成本。要清理基础架构,请按照示例应用程序中显示的 清理 步骤进行操作。

结论

该博客介绍了一种将在 亚马逊云科技 Fargate 上运行的现有容器工作负载与新的事件驱动架构相集成的方法。您可以使用 EventBridge 将使用不同的计算技术、语言和框架构建的不同服务相互分离。使用 亚马逊云科技 CDK,您可以获得相互分离的建筑服务的模块化。

这篇博客展示了一种不断演变的架构,它允许您以最少的更改对现有容器工作负载进行现代化改造,这仍然为您提供了在 亚马逊云科技 上使用无服务器和 EDA 进行构建的额外好处。

事件驱动方法和 REST 方法之间的主要区别在于,一旦制作者发出事件,你就会解除对其的封锁。订阅该事件的结算域中的事件制作者是松散耦合的。业务功能保持不变,无需进行大量的重构或重新架构工作。有了这些敏捷性的提高,你可以更快地进入市场

示例应用程序 显示了实现详细信息以及设置、运行和清理应用程序的步骤。该应用程序使用ECS Fargate作为域名服务,但您不能将其限制为仅限Fargate。您还可以让基于容器的应用程序在 Amazon EKS 上运行, 类似于 事件驱动架构。

详细了解 无服务器 领域的事件驱动架构。


*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您发展海外业务和/或了解行业前沿技术选择推荐该服务。