带示例的迁移分步说明 - AWS SDK for Java 2.x

带示例的迁移分步说明

本部分提供了分步指南,说明如何将当前使用适用于 Java 的 SDK v1.x 的应用程序迁移到适用于 Java 的 SDK 2.x。第一部分概述了各个步骤,然后是详细的迁移示例。

此处介绍的步骤说明了正常使用案例的迁移,应用程序使用模型驱动的服务客户端调用 AWS 服务。如果您需要迁移使用更高级别 API(例如 S3 Transfer ManagerCloudFront 预签名)的代码,请参阅适用于 Java 的 AWS SDK 1.x 和 2.x 之间的差异目录下的部分。

此处描述的方法仅为建议。您可以使用其他技术并利用 IDE 的代码编辑功能来获得相同的结果。

步骤概述

1. 首先添加适用于 Java 的 SDK 2.x BOM

通过将适用于 Java 的 SDK 2.x 的 Maven BOM(物料清单)元素添加到您的 POM 文件中,可以确保所需的所有 v2 依赖项都来自同一个版本。您的 POM 可以同时包含 v1 和 v2 依赖项。这样您就可以通过增量方式迁移代码,而不必一次全部更改。

<dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>bom</artifactId> <version>2.27.21</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

您可以在 Maven Central 存储库中找到最新版本

2. 在文件中搜索 v1 类导入语句

通过在应用程序的文件中扫描 v1 导入中使用的 SERVICE_ID,您可以找到所使用的唯一 SERVICE_ID。SERVICE_ID 是 AWS 服务的简短、唯一名称。例如,cognitoidentity 是 Amazon Cognito Identity 的 SERVICE_ID。

3. 从 v1 导入语句中确定 v2 Maven 依赖项

找到所有唯一的 v1 SERVICE_ID 后,您可以通过参考 软件包名称到 Maven artifactId 的映射 来确定 v2 依赖项的相应 Maven 构件。

4. 将 v2 依赖项元素添加到 POM 文件中

使用步骤 3 中确定的依赖项元素更新 Maven POM 文件。

5. 在 Java 文件中,逐渐将 v1 类更改为 v2 类

在用 v2 类替换 v1 类时,请进行必要的更改以支持 v2 API,例如使用生成器而不是构造函数,以及使用 fluent getter 和 setter。

6. 从 POM 中移除 v1 Maven 依赖项,从文件中移除 v1 导入

将代码迁移到使用 v2 类后,从文件中移除所有剩余的 v1 导入,并从构建文件中移除所有依赖项。

7. 重构代码以使用 v2 API 增强功能

代码成功编译并通过测试后,您可以利用 v2 增强功能,例如使用不同的 HTTP 客户端或分页器来简化代码。此为可选步骤。

示例迁移

在此示例中,我们迁移了一个使用适用于 Java 的 SDK v1 并可以访问多个 AWS 服务的应用程序。我们在步骤 5 中详细解析了以下 v1 方法。这是包含八个方法的类中的一个方法,应用程序中有 32 个类。

下面仅列出了来自 Java 文件的 v1 SDK 导入。

import com.amazonaws.ClientConfiguration; import com.amazonaws.regions.Region; import com.amazonaws.regions.RegionUtils; import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.model.AmazonEC2Exception; import com.amazonaws.services.ec2.model.CreateTagsRequest; import com.amazonaws.services.ec2.model.DescribeInstancesRequest; import com.amazonaws.services.ec2.model.DescribeInstancesResult; import com.amazonaws.services.ec2.model.Instance; import com.amazonaws.services.ec2.model.InstanceStateName; import com.amazonaws.services.ec2.model.Reservation; import com.amazonaws.services.ec2.model.Tag; import com.amazonaws.services.ec2.model.TerminateInstancesRequest; ... private static List<Instance> getRunningInstances(AmazonEC2Client ec2, List<String> instanceIds) { List<Instance> runningInstances = new ArrayList<>(); try { DescribeInstancesRequest request = new DescribeInstancesRequest() .withInstanceIds(instanceIds); DescribeInstancesResult result; do { // DescribeInstancesResponse is a paginated response, so use tokens with multiple requests. result = ec2.describeInstances(request); request.setNextToken(result.getNextToken()); // Prepare request for next page. for (final Reservation r : result.getReservations()) { for (final Instance instance : r.getInstances()) { LOGGER.info("Examining instanceId: "+ instance.getInstanceId()); // if instance is in a running state, add it to runningInstances list. if (RUNNING_STATES.contains(instance.getState().getName())) { runningInstances.add(instance); } } } } while (result.getNextToken() != null); } catch (final AmazonEC2Exception exception) { // if instance isn't found, assume its terminated and continue. if (exception.getErrorCode().equals(NOT_FOUND_ERROR_CODE)) { LOGGER.info("Instance probably terminated; moving on."); } else { throw exception; } } return runningInstances; }

1. 添加 v2 Maven BOM

将适用于 Java 的 SDK 2.x 的 Maven BOM 添加到 POM 的 dependencyManagement 部分,与任何其他依赖项放在一起。如果您的 POM 文件中有 SDK v1 的 BOM,请暂时将其保留。您将在后面的步骤中将其删除。

<dependencyManagement> <dependencies> <dependency> <groupId>org.example</groupId> <!--Existing dependency in POM. --> <artifactId>bom</artifactId> <version>1.3.4</version> <type>pom</type> <scope>import</scope> </dependency> ... <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk-bom</artifactId> <!--Existing v1 BOM dependency. --> <version>1.11.1000</version> <type>pom</type> <scope>import</scope> </dependency> ... <dependency> <groupId>software.amazon.awssdk</groupId> <!--Add v2 BOM dependency. --> <artifactId>bom</artifactId> <version>2.27.21</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

2. 在文件中搜索 v1 类导入语句

在应用程序的代码中搜索唯一出现的 import com.amazonaws.services。这有助于我们确定项目使用的 v1 依赖项。如果您的应用程序有一个 Maven POM 文件列出了 v1 依赖项,则可以改用此信息。

在这个例子中,我们使用 ripgrep (rg) 命令来搜索代码库。

在代码库的根目录中执行以下 ripgrep 命令。ripgrep 找到导入语句后,会将它们通过管道传送到 cutsortuniq 命令,用于隔离 SERVICE_ID。

rg --no-filename 'import\s+com\.amazonaws\.services' | cut -d '.' -f 4 | sort | uniq

对于此应用程序,在控制台会记录以下 SERVICE_ID。

autoscaling cloudformation ec2 identitymanagement

这表明 import 语句中使用的以下每个软件包名称至少出现一次。就我们当前的目的而言,具体的类名无关紧要。我们只需要找到使用的 SERVICE_ID。

com.amazonaws.services.autoscaling.* com.amazonaws.services.cloudformation.* com.amazonaws.services.ec2.* com.amazonaws.services.identitymanagement.*

3. 从 v1 导入语句中确定 v2 Maven 依赖项

我们从步骤 2 中分离出来的 v1 SERVICE_ID(例如 autoscalingcloudformation)在大多数情况下可以映射到相同的 v2 SERVICE_ID。由于 v2 Maven artifactId 在大多数情况下都与 SERVICE_ID 匹配,因此您已拥有所需的信息,可以向 POM 文件添加依赖项块。

下表说明了我们如何确定 v2 依赖项。

v1 SERVICE_ID 映射到 ...

软件包名称

v2 SERVICE_ID 映射到 ...

软件包名称

v2 Maven 依赖项

ec2

com.amazonaws.services.ec2.*

ec2

software.amazon.awssdk.services.ec2.*

<dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>ec2</artifactId> </dependency>

自动扩缩

com.amazonaws.services.autoscaling.*

自动扩缩

software.amazon.awssdk.services.autoscaling.*

<dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>autoscaling</artifactId> </dependency>
cloudformation:*

com.amazonaws.services.cloudformation.*

cloudformation:*

software.amazon.awssdk.cloudformation.*

<dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>cloudformation</artifactId> </dependency>
identitymanagement*

com.amazonaws.services.identitymanagement.*

iam/

software.amazon.awssdk.iam.*

<dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>iam</artifactId> </dependency>

* identitymanagementiam 映射是一个例外,不同版本的 SERVICE_ID 有所不同。如果 Maven 或 Gradle 无法解析 v2 依赖项,请参阅软件包名称到 Maven artifactId 的映射以了解例外情况。

4. 将 v2 依赖项元素添加到 POM 文件中

在步骤 3 中,我们确定了需要添加到 POM 文件中的四个依赖项块。因为我们在步骤 1 中指定了 BOM,我们不需要添加版本。添加导入后,我们的 POM 文件具有以下依赖项元素。

... <dependencies> ... <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>autoscaling</artifactId> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>iam</artifactId> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>cloudformation</artifactId> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>ec2</artifactId> </dependency> ... </dependencies> ...

5. 在 Java 文件中,逐渐将 v1 类更改为 v2 类

在我们正在迁移的方法中,我们看到

  • 来自 com.amazonaws.services.ec2.AmazonEC2Client 的 EC2 服务客户端。

  • 使用了几个 EC2 模型类。例如,DescribeInstancesRequestDescribeInstancesResult

import com.amazonaws.ClientConfiguration; import com.amazonaws.regions.Region; import com.amazonaws.regions.RegionUtils; import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.model.AmazonEC2Exception; import com.amazonaws.services.ec2.model.CreateTagsRequest; import com.amazonaws.services.ec2.model.DescribeInstancesRequest; import com.amazonaws.services.ec2.model.DescribeInstancesResult; import com.amazonaws.services.ec2.model.Instance; import com.amazonaws.services.ec2.model.InstanceStateName; import com.amazonaws.services.ec2.model.Reservation; import com.amazonaws.services.ec2.model.Tag; import com.amazonaws.services.ec2.model.TerminateInstancesRequest; ... private static List<Instance> getRunningInstances(AmazonEC2Client ec2, List<String> instanceIds) List<Instance> runningInstances = new ArrayList<>(); try { DescribeInstancesRequest request = new DescribeInstancesRequest() .withInstanceIds(instanceIds); DescribeInstancesResult result; do { // DescribeInstancesResponse is a paginated response, so use tokens with multiple re result = ec2.describeInstances(request); request.setNextToken(result.getNextToken()); // Prepare request for next page. for (final Reservation r : result.getReservations()) { for (final Instance instance : r.getInstances()) { LOGGER.info("Examining instanceId: "+ instance.getInstanceId()); // if instance is in a running state, add it to runningInstances list. if (RUNNING_STATES.contains(instance.getState().getName())) { runningInstances.add(instance); } } } } while (result.getNextToken() != null); } catch (final AmazonEC2Exception exception) { // if instance isn't found, assume its terminated and continue. if (exception.getErrorCode().equals(NOT_FOUND_ERROR_CODE)) { LOGGER.info("Instance probably terminated; moving on."); } else { throw exception; } } return runningInstances; } ...

我们的目标是用 v2 导入替换所有 v1 导入。我们一次只处理一个类。

a. 替换导入语句或类名称

我们看到 describeRunningInstances 方法的第一个参数是 v1 AmazonEC2Client 实例。请执行以下操作之一:

  • com.amazonaws.services.ec2.AmazonEC2Client 的导入替换为 software.amazon.awssdk.services.ec2.Ec2Client,然后将 AmazonEC2Client 更改为 Ec2Client

  • 将参数类型更改为 Ec2Client,然后让 IDE 为我们提示正确的导入。由于客户端名称不同(AmazonEC2ClientEc2Client),我们的 IDE 会提示我们导入 v2 类。如果两个版本中的类名称相同,则此方法不起作用。

b. 将 v1 模型类替换为 v2 等效项

在对 v2 Ec2Client 进行更改之后,如果我们使用 IDE,则会在以下语句中看到编译错误。

result = ec2.describeInstances(request);

编译错误是由于使用 v1 的 DescribeInstancesRequest 实例作为 v2 Ec2Client describeInstances 方法的参数所致。要修复此问题,请使用以下替换或导入语句。

替换 替换为
import com.amazonaws.services.ec2.model.DescribeInstancesRequest
import software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest

c. 将 v1 构造函数更改为 v2 生成器。

因为 v2 类中没有构造函数,我们仍然会看到编译错误。要修复此问题,进行以下更改。

更改
final DescribeInstancesRequest request = new DescribeInstancesRequest() .withInstanceIds(instanceIdsCopy);
final DescribeInstancesRequest request = DescribeInstancesRequest.builder() .instanceIds(instanceIdsCopy) .build();

d. 将 v1 *Result 响应对象替换为 v2 *Response 等效项

v1 和 v2 之间一个始终一致的区别是:v2 中的所有响应对象都以 *Response 而不是以 *Result 结尾。将 v1 DescribeInstancesResult 导入替换为 v2 导入 DescribeInstancesResponse

d. 进行 API 更改

以下语句需要进行一些更改。

request.setNextToken(result.getNextToken());

在 v2 中,setter 方法不使用 set prefix。在适用于 Java 的 SDK 2.x 中,getter 方法也不再使用 get 前缀

模型类(例如 request 实例)在 v2 中是不可变的,因此我们需要使用生成器创建一个新的 DescribeInstancesRequest

在 v2 中,该语句变成了下面这样。

request = DescribeInstancesRequest.builder() .nextToken(result.nextToken()) .build();

d. 重复直到方法能够使用 v2 类成功编译

继续执行代码的其余部分。用 v2 导入替换 v1 导入并修复编译错误。如有必要,请参阅 v2 API 参考主要变更

在我们迁移这一个方法之后,我们有了以下 v2 代码。

import com.amazonaws.ClientConfiguration; import com.amazonaws.regions.Region; import com.amazonaws.regions.RegionUtils; import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.model.AmazonEC2Exception; import com.amazonaws.services.ec2.model.CreateTagsRequest; import com.amazonaws.services.ec2.model.InstanceStateName; import com.amazonaws.services.ec2.model.Tag; import com.amazonaws.services.ec2.model.TerminateInstancesRequest; import software.amazon.awssdk.services.ec2.Ec2Client; import software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest; import software.amazon.awssdk.services.ec2.model.DescribeInstancesResponse; import software.amazon.awssdk.services.ec2.model.Ec2Exception; import software.amazon.awssdk.services.ec2.model.Instance; import software.amazon.awssdk.services.ec2.model.Reservation; ... private static List<Instance> getRunningInstances(Ec2Client ec2, List<String> instanceIds) { List<Instance> runningInstances = new ArrayList<>(); try { DescribeInstancesRequest request = DescribeInstancesRequest.builder() .instanceIds(instanceIds) .build(); DescribeInstancesResponse result; do { // DescribeInstancesResponse is a paginated response, so use tokens with multiple re result = ec2.describeInstances(request); request = DescribeInstancesRequest.builder() // Prepare request for next page. .nextToken(result.nextToken()) .build(); for (final Reservation r : result.reservations()) { for (final Instance instance : r.instances()) { // if instance is in a running state, add it to runningInstances list. if (RUNNING_STATES.contains(instance.state().nameAsString())) { runningInstances.add(instance); } } } } while (result.nextToken() != null); } catch (final Ec2Exception exception) { // if instance isn't found, assume its terminated and continue. if (exception.awsErrorDetails().errorCode().equals(NOT_FOUND_ERROR_CODE)) { LOGGER.info("Instance probably terminated; moving on."); } else { throw exception; } } return runningInstances; } ...

由于我们正在对一个包含八个方法的 Java 文件中的单个方法进行迁移,因此在处理该文件期间会同时存在 v1 和 v2 版本的导入语句。我们在执行这些步骤时添加了最后六个导入语句。

在我们迁移所有代码之后,将不再有 v1 导入语句。

6. 从 POM 中移除 v1 Maven 依赖项,从文件中移除 v1 导入

迁移文件中的所有 v1 代码后,我们有以下 v2 SDK 导入语句。

import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.ServiceMetadata; import software.amazon.awssdk.services.ec2.Ec2Client; import software.amazon.awssdk.services.ec2.model.CreateTagsRequest; import software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest; import software.amazon.awssdk.services.ec2.model.DescribeInstancesResponse; import software.amazon.awssdk.services.ec2.model.Ec2Exception; import software.amazon.awssdk.services.ec2.model.Instance; import software.amazon.awssdk.services.ec2.model.InstanceStateName; import software.amazon.awssdk.services.ec2.model.Reservation; import software.amazon.awssdk.services.ec2.model.Tag; import software.amazon.awssdk.services.ec2.model.TerminateInstancesRequest;

在我们迁移应用程序中的所有文件后,我们不再需要 POM 文件中的 v1 依赖项。从 dependencyManagement 部分移除 v1 BOM(如果已使用)以及所有 v1 依赖项块。

7. 重构代码以使用 v2 API 增强功能

对于我们一直在迁移的代码段,我们可以选择使用 v2 分页器,让 SDK 管理基于令牌的请求,以获得更多数据。

我们可以将整个 do 子句替换为以下内容。

DescribeInstancesIterable responses = ec2.describeInstancesPaginator(request); responses.reservations().stream() .forEach(reservation -> reservation.instances() .forEach(instance -> { if (RUNNING_STATES.contains(instance.state().nameAsString())) { runningInstances.put(instance.instanceId(), instance); } }));

软件包名称到 Maven artifactId 的映射

当您将 Maven 或 Gradle 项目从适用于 Java 的 SDK v1 迁移到 v2 时,您需要弄清楚要将哪些依赖项添加到构建文件中。带示例的迁移分步说明(步骤 3)中描述的方法使用导入语句中的软件包名称作为起点来确定要添加到构建文件中的依赖项(如 artifactId)。

您可以使用本主题中的信息将 v1 软件包名称映射到 v2 artifactId。

软件包名称和 Maven artifactId 中使用的常见命名约定

下表显示了 SDK 为给定 SERVICE_ID 使用的常见命名约定。SERVICE_ID 是 AWS 服务 的唯一标识符。例如,Amazon S3 服务的 SERVICE_ID 是 s3,Amazon Cognito Identity 的 SERVICE_ID 是 cognitoidentity

v1 软件包名称(导入语句) v1 artifactId v2 artifactId v2 软件包名称(导入语句)
com.amazonaws.services.SERVICE_ID aws-java-sdk-SERVICE_ID SERVICE_ID software.amazon.awssdk.services.SERVICE_ID
Amazon Cognito Identity 的示例(SERVICE_ID:cognitoidentity
com.amazonaws.services.cognitoidentity aws-java-sdk-cognitoidentity cognitoidentity software.amazon.awssdk.services.cognitoidentity

SERVICE_ID 差异

在 v1 中

在某些情况下,同一服务的 SERVICE_ID 在软件包名称和 artifactId 中有所不同。例如,下表的 CloudWatch 指标行显示,软件包名称中的 SERVICE_ID 是 metrics,而 artifactId 的 SERVICE_ID 是 cloudwatchmetrics

在 v2 中

软件包名称和 artifactId 中使用的 SERVICE_ID 没有差异。

v1 和 v2 之间

对于大多数服务,v2 中的 SERVICE_ID 与 v1 在软件包名称和 artifactId 中的 SERVICE_ID 相同。这方面的一个例子是 cognitoedentity SERVICE_ID,如上表所示。但是,某些 Service_ID 在不同 SDK 版本之间有所不同,如下表所示。

任一个 v1 列中的粗体 SERVICE_ID 表示它与 v2 中使用的 SERVICE_ID 不同。

服务名称 v1 软件包名称 v1 artifactId v2 artifactId v2 软件包名称

所有软件包名称都以 com.amazonaws.services 开头,如第一行所示。

所有 artifactId 都包含在标签中,如第一行所示。

所有 artifactId 都包含在标签中,如第一行所示。

所有软件包名称都以 software.amazon.awssdk 开头,如第一行所示。

API Gateway com.amazonaws.services.apigateway <artifactId>aws-java-sdk-api-gateway</artifactId> <artifactId>apigateway</artifactId> software.amazon.awssdk.services.apigateway
App Registry appregistry appregistry servicecatalogappregistry servicecatalogappregistry
Application Discovery applicationdiscovery discovery applicationdiscovery applicationdiscovery
Augmented AI Runtime augmentedairuntime augmentedairuntime sagemakera2iruntime sagemakera2iruntime
Certificate Manager certificatemanager acm acm acm
CloudControl API cloudcontrolapi cloudcontrolapi cloudcontrol cloudcontrol
CloudSearch cloudsearchv2 cloudsearch cloudsearch cloudsearch
CloudSearch Domain cloudsearchdomain cloudsearch cloudsearchdomain cloudsearchdomain
CloudWatch Events cloudwatchevents events cloudwatchevents cloudwatchevents
CloudWatch Evidently cloudwatchevidently cloudwatchevidently evidently evidently
CloudWatch Logs logs logs cloudwatchlogs cloudwatchlogs
CloudWatch Metrics metrics cloudwatchmetrics cloudwatch cloudwatch
CloudWatch Rum cloudwatchrum cloudwatchrum rum rum
Cognito Identity Provider cognitoidp cognitoidp cognitoidentityprovider cognitoidentityprovider
Connect Campaign connectcampaign connectcampaign connectcampaigns connectcampaigns
Connect Wisdom connectwisdom connectwisdom wisdom wisdom
Database Migration Service databasemigrationservice dms databasemigration databasemigration
DataZone datazone datazoneexternal datazone datazone
DynamoDB dynamodbv2 dynamodb dynamodb dynamodb
Elastic File System elasticfilesystem efs efs efs
Elastic Map Reduce elasticmapreduce/ emr emr emr
Glue DataBrew gluedatabrew gluedatabrew databrew databrew
IAM Roles Anywhere iamrolesanywhere iamrolesanywhere rolesanywhere rolesanywhere
身份管理 identitymanagement IAM IAM IAM
IoT Data iotdata iot iotdataplane iotdataplane
Kinesis Analytics kinesisanalytics kinesis kinesisanalytics kinesisanalytics
Kinesis Firehose kinesisfirehose kinesis Firehose Firehose
Kinesis Video Signaling Channels kinesisvideosignalingchannels kinesisvideosignalingchannels kinesisvideosignaling kinesisvideosignaling
Lex lexruntime Lex lexruntime lexruntime
Lookout For Vision lookoutforvision lookoutforvision lookoutvision lookoutvision
Mainframe Modernization mainframemodernization mainframemodernization m2 m2
Marketplace Metering marketplacemetering marketplacemeteringservice marketplacemetering marketplacemetering
Managed Grafana managedgrafana managedgrafana grafana grafana
Mechanical Turk mturk mechanicalturkrequester mturk mturk
Migration Hub 策略建议 migrationhubstrategyrecommendations migrationhubstrategyrecommendations migrationhubstrategy migrationhubstrategy
Nimble Studio nimblestudio nimblestudio nimble nimble
Private 5G private5g private5g privatenetworks privatenetworks
Prometheus prometheus prometheus amp amp
Recycle Bin recyclebin recyclebin rbin rbin
Redshift 数据 API redshiftdataapi redshiftdataapi redshiftdata redshiftdata
Route 53 route53domains route53 route53domains route53domains
Sage Maker Edge Manager sagemakeredgemanager sagemakeredgemanager sagemakeredge sagemakeredge
Security Token securitytoken sts sts sts
Server Migration servermigration servermigration sms sms
Simple Email simpleemail ses ses ses
Simple Email V2 simpleemailv2 sesv2 sesv2 sesv2
Simple Systems Management simplesystemsmanagement ssm ssm ssm
Simple Workflow simpleworkflow simpleworkflow swf swf
Step Functions stepfunctions stepfunctions sfn sfn