拖放式开发无服务器应用程序:使用 Amazon 应用程序编译器 (Application Composer) 和 Amazon 无服务器应用程序模型 (SAM)

Amazon 应用程序编译器 和 Amazon 无服务器应用程序模型 (SAM) 可以帮助您直观地设计和构建无服务器应用程序。具体如下。
发布时间:2023 年 6 月 9 日
DevOps
应用程序编译器
无服务器
Amazon-SAM
教程
亚马逊云科技
Olawale Olaleye
亚马逊云科技使用经验
100 - 初级
完成所需时间
30 分钟
所需费用

支持亚马逊云科技免费套餐

示例代码

本教程中使用的示例代码来自 Serverless Land

上次更新时间
2023 年 6 月 9 日

在当今的数字时代,构建应用程序并将其部署到云已成为企业保持竞争力的关键要素。无服务器计算在开发人员中广受欢迎,因为它在部署应用程序方面提供了一种经济高效且可扩展的解决方案,且无需管理底层基础设施。

Amazon 无服务器应用程序模型 (SAM) 是一种流行的开源框架,只需在 YAML 模板中编写几行代码即可构建无服务器应用程序。在部署过程中,SAM 会转换此模板并扩展语法,实现 Amazon CloudFormation 的强大功能。

但说实话,没人真的会愿意维护无服务器应用程序的 YAML 代码,哪怕是在使用 SAM 的情况下,更不用说可视化应用程序架构并非易事。

Amazon 应用程序编译器 提供了一个解决方案。这是一款可视化开发工具,提供拖放式界面,便于开发人员快速轻松地创建新应用程序或修改现有应用程序。

在这篇博文中,我们将探讨在新应用程序和现有应用程序中结合使用 Amazon SAM 与应用程序编译器带来的益处。我们还将示例说明如何使用这些工具简化应用程序开发过程,以及更轻松地在云中管理应用程序。让我们开始吧!

学习内容:

  • 如何从空白画布开始构建 SAM 应用程序
  • 如何加载现有的 SAM 应用程序,实现可视化以及根据需要进行扩展

如果您想使用 Amazon 应用程序编译器快速创建架构图和 SAM 应用程序,请观看此视频:

此具体操作视频将带您了解 Amazon 应用程序编译器、Cloudcraft 和 Lucidchart 的功能、选项和独特优势,助力您创建视觉震撼的架构图。

设计一个新的无服务器应用程序

我们将使用 3 个亚马逊云科技无服务器服务创建一个简单的应用程序:Amazon API GatewayAmazon Lambda 和 Amazon DynamoDB

这是一个简单的无服务器模式,演示了如何向 Amazon API Gateway 端点发出 HTTP POST 请求,调用 Amazon Lambda 函数并将项目插入 Amazon DynamoDB 表。

我们将创建具有两个 Lambda 函数的 Nodejs 应用程序,一个函数用于将数据插入 DynamoDB 表,另一个函数用于列出数据。

首先在本地计算机中创建一个文件夹。将该文件夹命名为 buildon-sam-app。这篇博文的所有内容都将放入该文件夹中。

在亚马逊云科技管理控制台中,前往 Application Composer(应用程序编译器)服务。

点击应用程序编译器控制面板中的 Create project(创建项目)按钮,以创建新项目。如下图所示,对于 Type of Project(项目类型),选择 New blank project(新的空白项目)。

在 New blank project(新的空白项目)中,有 2 种模式:Connected(已连接)和 Unconnected(未连接)。

应用程序编译器仅在浏览器中运行,不会将任何文件存储在亚马逊云科技账户中。因此,模式的选择决定了设计文件的存储方式以及存储位置。

  • 在 Connected mode(已连接模式)下,应用程序编译器可以访问计算机上的本地文件夹。然后,它将在设计过程中自动同步并在本地保存模板文件和项目文件夹。

  • 在 Unconnected mode(未连接模式)下,必须手动导入和导出模板文件。我们必须根据需要选择 Menu(菜单)> save changes(保存更改),保存和下载模板文件的最新配置。在未连接模式下进行设计时,仅生成应用程序模板文件,并且可以手动导出。

在这篇博文中,我们将使用 Connected mode(已连接模式)。

点击 Select folder(选择文件夹),并提供一个空的本地文件夹。

请注意,所有这些都在浏览器中进行!因此,我们必须提供对浏览器的访问权限,以便在已连接模式下查看和编辑本地文件。当系统提示允许访问时,选择 View files(查看文件)和 Edit files(编辑文件)。

现在我们新建了一个空白项目。在下图中,我在同一文件夹中打开了一个本地 IDE(如 Visual Studio Code),向您展示应用程序编译器如何自动创建本地文件。

在已连接模式下设计时,以下文件和文件夹会同步并保存到本地文件系统:

  • 应用程序模板文件 – 在应用程序编译器中设计时,会生成一个单独的应用程序模板文件。
  • 项目文件夹 – 设计 Lambda 函数时,会生成一个通用的 Lambda 目录结构。
  • 备份模板文件 – 将在项目位置的根目录下创建名为 .aws-composer 的备份目录,其中包含应用程序模板文件和项目文件夹的备份副本。

应用程序编译器控制台有两个部分:"Canvas" 和 "Template"。您可以在设计时随时在两者之间切换。

在 Application Composer(应用程序编译器)控制台中,可以在 Resources(资源)选项卡中找到大多数可用的无服务器资源。可以在 List(列表)选项卡中快速识别画布/模板上已有的资源。为生成这种模式,请将组件从 Resources(资源)选项卡拖放到 Canvas(画布)上。

当我们在画布上拖动资源时,应用程序编译器会自动在 Template(模板)中编写所需的 YAML 代码。此外,由于这是已连接模式,本地模板文件也会进行这些更改。我们在画布或模板上所做的任何更改都会自动反映在另一端。

在下面的 GIF 中,可以看到,当我们将资源(API Gateway、Lambda 和 DynamoDB)拖放到 Canvas(画布)上时,template.yaml 会自动填充适当的 yaml 定义。

克隆项目并切换到正确的目录:

我们可以在 "Template" 选项卡中进一步自定义模板。也可以在 "Canvas" 上选择资源,然后点击 "Details",修改资源。

例如,拖动 API Gateway 资源;点击 "Details" 后,可以在右侧看到可配置的 "Resource properties"。我们可以重命名 CloudFormation 的资源逻辑 ID,快速添加路由、授权方和 CORS 配置。

对于我们新建的 SAM 应用程序,定义以下资源及其属性(不含括号注释内容)。确保每次修改资源属性时都点击 Save(保存):

  • Resource(资源): API Gateway
    • Properties(属性):
      • Name(名称): Api
      • Routes(路由):
      • Method(方法): GET; Path(路径): /
      • Method(方法): POST; Path(路径): /
  • Resource(资源): Lambda Function
    • Properties(属性):
      • Name(名称): CustomerFunctionCreate
      • Source path(源路径): src/CreateCustomer
      • Runtime(运行时): nodejs18.x
      • Handler(处理程序): index.handler
  • Resource(资源): Lambda Function
    • Properties(属性):
      • Name(名称): CustomerFunctionList
      • Source path(源路径): src/ListCustomer
      • Runtime(运行时): nodejs18.x
      • Handler(处理程序): index.handler
  • Resource(资源): DynamoDB Table
    • Properties(属性):
      • 逻辑 ID: CustomerTable
      • 分区键: id
      • 分区键类型: String

我们有 2 个 Lambda 函数,分别用于 Create 和 List 客户。我们可以将这些函数组合为一个 Functions group,更便于直观显示。点击其中一个函数,然后选择 Group(组)。

这将创建一个组。将另一个 Lambda 函数拖放到此组。如果双击该组,可以修改其名称。我们将该组重命名为 CustomerFunctionsGroup。

最佳实践

Amazon 应用程序编译器旨在配置遵循亚马逊云科技最佳实践的基础设施即代码定义。例如,当我们将 Lambda 函数添加到画布中时,应用程序编译器将默认启用 "Tracing" 并为该函数添加 Amazon CloudWatch 日志组。Lambda 记录函数处理的所有请求,并通过 Amazon CloudWatch Logs 自动存储代码生成的日志。

连接

所有四个组件准备完毕后,下面将它们连接起来:API Gateway -> Lambda -> DynamoDB。

当我们建立这些连接时,应用程序编译器将自动更新模板以反映此更改。如下图所示,注意所做的更改:

我们必须将 GET / 路由连接到 CustomerFunctionList,将 POST / 连接到 CustomerFunctionCreate。还要将这两个 Lambda 函数连接到 DynamoDB 表。

我们有两种方法来建立这些连接:

  • 可以使用 "Canvas" 手动连接,如上图中所示。(请注意,只能在可以实现服务集成的位置建立连接。)

或者,可以直接在 "Template" 部分中复制并粘贴以下模板,然后设计模式将生动呈现。

  • template.yaml
Transform: AWS::Serverless-2016-10-31
Resources:
 CustomerFunctionList:
 Type: AWS::Serverless::Function
 Properties:
 Description: !Sub
 - Stack ${AWS::StackName} Function ${ResourceName}
 - ResourceName: CustomerFunctionList
 CodeUri: src/ListCustomer
 Handler: index.handler
 Runtime: nodejs18.x
 MemorySize: 3008
 Timeout: 30
 Tracing: Active
 Events:
 Api23GET:
 Type: Api
 Properties:
 Path: /
 Method: GET
 RestApiId: !Ref Api23
 Environment:
 Variables:
 TABLE_NAME: !Ref CustomerTable
 TABLE_ARN: !GetAtt CustomerTable.Arn
 Policies:
 - DynamoDBCrudPolicy:
 TableName: !Ref CustomerTable
 CustomerFunctionListLogGroup:
 Type: AWS::Logs::LogGroup
 DeletionPolicy: Retain
 Properties:
 LogGroupName: !Sub /aws/lambda/${CustomerFunctionList}
 CustomerFunctionCreate:
 Type: AWS::Serverless::Function
 Properties:
 Description: !Sub
 - Stack ${AWS::StackName} Function ${ResourceName}
 - ResourceName: CustomerFunctionCreate
 CodeUri: src/CreateCustomer
 Handler: index.handler
 Runtime: nodejs18.x
 MemorySize: 3008
 Timeout: 30
 Tracing: Active
 Environment:
 Variables:
 TABLE_NAME: '!Ref CustomerTable'
 TABLE_ARN: '!GetAtt CustomerTable.Arn'
 TABLE_NAME_2: !Ref CustomerTable
 TABLE_ARN_2: !GetAtt CustomerTable.Arn
 Events:
 Api23POST:
 Type: Api
 Properties:
 Path: /
 Method: POST
 RestApiId: !Ref Api23
 Policies:
 - DynamoDBCrudPolicy:
 TableName: !Ref CustomerTable
 CustomerFunctionCreateLogGroup:
 Type: AWS::Logs::LogGroup
 DeletionPolicy: Retain
 Properties:
 LogGroupName: !Sub /aws/lambda/${CustomerFunctionCreate}
 CustomerTable:
 Type: AWS::DynamoDB::Table
 Properties:
 AttributeDefinitions:
 - AttributeName: id
 AttributeType: S
 BillingMode: PAY_PER_REQUEST
 KeySchema:
 - AttributeName: id
 KeyType: HASH
 StreamSpecification:
 StreamViewType: NEW_AND_OLD_IMAGES
 Api:
 Type: AWS::Serverless::Api
 Properties:
 Name: !Sub
 - ${ResourceName} From Stack ${AWS::StackName}
 - ResourceName: Api23
 StageName: Prod
 DefinitionBody:
 openapi: '3.0'
 info: {}
 paths:
 /:
 get:
 x-amazon-apigateway-integration:
 httpMethod: POST
 type: aws_proxy
 uri: !Sub arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${CustomerFunctionList.Arn}/invocations
 responses: {}
 post:
 x-amazon-apigateway-integration:
 httpMethod: POST
 type: aws_proxy
 uri: !Sub arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${CustomerFunctionCreate.Arn}/invocations
 responses: {}
 EndpointConfiguration: REGIONAL
 TracingEnabled: true
Metadata:
 AWS::Composer::Groups:
 Group:
 Label: CustomerFunctionsGroup
 Members:
 - CustomerFunctionList
 - CustomerFunctionCreate
Outputs:
 EndpointUrl:
 Description: HTTP REST endpoint URL
 Value: !Sub https://${Api23}.execute-api.${AWS::Region}.amazonaws.com/Prod

继续之前,在 template.yaml 末尾添加以下 Output 部分,以便在创建堆栈后获取 API 端点:

Outputs:
 EndpointUrl:
 Description: HTTP REST endpoint URL
 Value: !Sub https://${Api}.execute-api.${AWS::Region}.amazonaws.com/Prod

我们的设计应如下所示:

添加 Nodejs 代码

在本地文件夹中,应用程序编译器将创建以下文件夹结构和文件:

现在我们需要添加 Nodejs 代码,这样,每当调用 API 时,就会在 DynamoDB 表中添加/列出客户信息。

在各自的文件中添加以下代码:

  • CreateCustomer/index.js
const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
const { DynamoDBDocumentClient, PutCommand } = require("@aws-sdk/lib-dynamodb");

const client = new DynamoDBClient({});
const ddbClient = DynamoDBDocumentClient.from(client);

exports.handler = async event => {
 try {
 console.log(JSON.stringify(event, undefined, 2));
 const requestBody = JSON.parse(event.body);

 const customer = {
 id: Date.now().toString(), // Use a timestamp as the ID
 ...requestBody,
 };

 console.log(`Adding customer with ID '${customer.id}' to table '${process.env.TABLE_NAME}' with attributes: ${JSON.stringify(customer, null, 2)}`);

 const params = {
 TableName: process.env.TABLE_NAME,
 Item: customer,
 };

 // Add data to DynamoDB table
 await ddbClient.send(new PutCommand(params));
 
 console.log(`Successfully saved customer '${customer.id}'`);
 
 return {
 statusCode: 201,
 body: JSON.stringify(customer),
 };
 } catch (err) {
 console.error(err);

 return {
 statusCode: 500,
 body: 'Internal Server Error',
 };
 }
};
  • CreateCustomer/package.json
{
 "name": "function",
 "version": "1.0.0",
 "devDependencies": {
 "@aws-sdk/lib-dynamodb": "^3.214.0"
 }
}
  • ListCustomer/index.js
const { DynamoDBClient, ScanCommand } = require("@aws-sdk/client-dynamodb");

const client = new DynamoDBClient({});

exports.handler = async event => {
 try {
 // Log the event argument for debugging and for use in local development.
 console.log(JSON.stringify(event, undefined, 2));

 console.log(`Listing customers from table '${process.env.TABLE_NAME}'`);

 let ids = [];

 // Loop over all customers. If there are more customers after a request,
 // the response LastEvaluatedKey will have a non-null value that we can
 // pass in the next request to continue fetching more customers.
 let 
 lastEvaluatedKey;
 do {
 const command = new ScanCommand({
 TableName: process.env.TABLE_NAME,
 ProjectionExpression: "id",
 ExclusiveStartKey: lastEvaluatedKey
 });

 const response = await client.send(command);

 const additionalIds = response.Items.map(customer => customer.id);

 ids = ids.concat(additionalIds);

 lastEvaluatedKey = response.LastEvaluatedKey;
 } while (lastEvaluatedKey);

 console.log(`Successfully scanned for list of IDs: ${JSON.stringify(ids, null, 2)}`);

 return {
 statusCode: 200,
 body: JSON.stringify({
 ids
 })
 };
 } catch (err) {
 console.error(`Failed to list customers: ${err.message} (${err.constructor.name})`);

 return {
 statusCode: 500,
 body: "Internal Service Error"
 };
 }
};
  • ListCustomer/package.json
{
 "name": "function",
 "version": "1.0.0",
 "devDependencies": {
 "@aws-sdk/lib-dynamodb": "^3.214.0"
 }
}

使用 Amazon SAM 构建和部署

现在我们已经创建了设计文件和应用程序文件,下面使用 Amazon Serverless Application Model (SAM) 进行构建和部署。

在本地 IDE 中的以下位置打开终端:应用程序编译器在其中创建上述所有文件的同一根文件夹。确保已经安装好开头前提条件中所述的所有组件。

由于这是一个 Nodejs 示例,我们还要确保安装了应用程序所需的所有模块。在上述选定文件夹的根目录下运行以下命令:

npm install aws-sdk

我们将跳过 sam init 命令,因为我们已经创建了一个示例应用程序。在这一部分中,我们将构建、部署和测试应用程序。

步骤 1:构建应用程序

在此步骤中,我们将使用 Amazon SAM CLI 构建应用程序并做好部署准备。构建时,Amazon SAM CLI 会创建一个 .aws-sam 目录,并在其中组织函数依赖项、项目代码和项目文件。

sam build

输出:

➜ buildon-sam-app sam build
Building codeuri: ../buildon-sam-app/src/ListCustomer runtime: nodejs18.x metadata: {} architecture: x86_64 functions: CustomerFunctionList
Running NodejsNpmBuilder:NpmPack
Running NodejsNpmBuilder:CopyNpmrcAndLockfile
Running NodejsNpmBuilder:CopySource
Running NodejsNpmBuilder:NpmInstall
Running NodejsNpmBuilder:CleanUpNpmrc
Running NodejsNpmBuilder:LockfileCleanUp
Building codeuri: ../buildon-sam-app/src/CreateCustomer runtime: nodejs18.x metadata: {} architecture: x86_64 functions: CustomerFunctionCreate
Running NodejsNpmBuilder:NpmPack
Running NodejsNpmBuilder:CopyNpmrcAndLockfile
Running NodejsNpmBuilder:CopySource
Running NodejsNpmBuilder:NpmInstall
Running NodejsNpmBuilder:CleanUpNpmrc
Running NodejsNpmBuilder:LockfileCleanUp

Build Succeeded

Built Artifacts: .aws-sam/build
Built Template : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided

步骤 2:将应用程序部署到亚马逊云科技云

sam deploy --guided

这将给出多个提示,您可以按如下所示回答:

  • 堆栈名称: buildon-sam-app
  • 亚马逊云科技区域: <REGION>
  • #显示要部署的资源更改,并且需要输入 Y 才能启动部署。部署前确认更改: y
  • #SAM 需要有权创建角色才能连接到模板中的资源。允许 SAM CLI 创建 IAM 角色: y
  • #操作失败时保留以前所预配资源的状态。禁用回滚: n
  • CustomerFunctionList 尚无授权定义,是否继续?: y
  • CustomerFunctionCreate 尚无授权定义,是否继续?: y
  • 将参数保存到配置文件: y
  • SAM 配置文件: samconfig.toml
  • SAM 配置环境: default
  • 部署此更改集?: y

这将使用应用程序编译器生成的 SAM template.yaml 中定义的所有资源(包括 Lambda 函数的所有源代码)部署 CloudFormation 堆栈。

最后,应该会看到如下提示:

输出:

...
CREATE_COMPLETE AWS::CloudFormation::Stack buildon-sam-app - 
------------------------------------------------------------------------
CloudFormation outputs from deployed stack
Outputs 
------------------------------------------------------------------------
Key EndpointUrl 
Description HTTP REST endpoint URL 
Value https://11yakge1yd.execute-api.<REGION>.amazonaws.com/Prod 
------------------------------------------------------------------------

Successfully created/updated stack - buildon-sam-app in<REGION>

记下 HTTP REST API 端点 URL;我们将使用此值调用 API。

此外,还将创建一个本地配置文件 samconfig.toml,这样下次运行 sam deploy 时,将从该文件中获取所有输入。S3 存储桶名称只是 SAM 将使用的一个示例,可以设置一个不同的默认 S3 存储桶。REGION 将是您的亚马逊云科技区域。

  • samconfig.toml
version = 0.1
[default]
[default.deploy]
[default.deploy.parameters]
stack_name = "buildon-sam-app"
s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-<randomletters>"
s3_prefix = "buildon-sam-app"
region = "REGION"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
image_repositories = []

步骤 3:测试应用程序

现在我们的应用程序已经启动并运行,下面调用 API 并向 DynamoDB 表添加一个项目。运行以下代码,并将 <API_ENDPOINT> 替换为在上一步中记下的 URL。显示内容将如下所示:https://<API>.execute-api.<REGION>.amazonaws.com/Prod。

步骤 3.1:向表中添加客户

curl -X POST \ 
 https://<API_ENDPOINT> \ 
 -H 'Content-Type: application/json' \
 -d '{
 "name": "John Doe",
 "email": "johndoe@example.com"
 }'

输出:

{"id":"1680294275167","name":"John Doe","email":"johndoe@example.com"}% 

步骤 3.2:列出客户 ID

curl -X GET https://<API_ENDPOINT>

输出:

{"ids":[{"S":"1680294275167"}]}% 

大功告成!

您可以向该无服务器应用程序添加更多的 API 和 Lambda 函数。每次必须更改应用程序时,都运行步骤 1 和步骤 2,构建和部署应用程序!

可视化现有应用程序

下面我们来尝试在应用程序编译器中可视化现有的应用程序。为此,使用以下命令克隆 Git 存储库:

git clone https://github.com/aws-samples/fresh-tracks.git
cd fresh-tracks/backend/FreshTracks/

在亚马逊云科技管理控制台中打开应用程序编译器。如果您正在进行设计,请确保进行保存,然后点击主页图标。

这次,在 Create project(创建项目)对话框中,选择 Load existing project(加载现有项目)。

再次使用 Connected(已连接)模式,然后在 Project location(项目位置)中,导航到文件夹 fresh-tracks/backend/FreshTracks/,其中包含此 Web 应用程序后端 template.yaml。为浏览器中的应用程序编译器提供 View 文件夹和文件的权限。

应用程序编译器可以读取该文件夹了,将列出该文件夹中所能找到的所有 .yaml 文件。这里,我们选择 template.yaml,然后点击 Create(创建)进行可视化。

提供 Edit 访问权限,我们可以看到生动形象的可视化效果:

大功告成!

现在,我们可以可视化和拖放更多的资源,继续在应用程序编译器中构建应用程序。IDE 中的其余构建和部署步骤将保持不变。

清理资源

完成本教程后,记得使用以下步骤删除创建的 CloudFormation 堆栈:

  1. 删除堆栈。将 STACK_NAME 替换为堆栈名称。在上面的示例中,堆栈名称是 buildon-sam-app。

sam delete --stack-name STACK_NAME

2. 在 Amazon CloudFormation 控制台中或使用以下命令确认堆栈已删除。将 STACK_NAME 替换为堆栈名称并确保名称用单引号括起来。

aws cloudformation list-stacks --query "StackSummaries[?contains(StackName,'STACK_NAME')].StackStatus"

总结

恭喜您!我们学会了如何使用 Amazon 应用程序编译器可视化和构建无服务器应用程序。另外,还学会了如何使用 Amazon SAM CLI 构建和部署这些应用程序。

请注意以下几点:

  • 在应用程序编译器中可视化应用程序不会产生任何费用。但是,如果您在亚马逊云科技账户中运行 sam build 和 sam deploy 命令,将需要为创建的资源付费。此内容不在本博文的涵盖范围内,可能会超出您的免费套餐限制。
  • 尽管应用程序编译器能够帮助您创建可视化模板,但是您应该保持使用自己目前的方式进行本地测试、同行评审或常规的部署流程。

Serverless Land 提供了丰富的无服务器代码存储库资源供您探索。

如果您喜欢本教程、发现任何问题或者想要提供意见反馈,请随时发给我们

查看我的另一篇教程,了解如何使用 Amazon CodeCatalyst 从头开始创建 CI/CD 管道,以使用 Amazon CloudFormation 部署基础设施即代码 (IaC)。

有关更多 DevOps 相关内容,请查看 DevOps 基础知识指南,了解 Amazon 在现实生活中如何进行 DevOps