1. 概述

之前的文章中,我们已经基于 AWS 实现了一个完整的无服务器(serverless)全栈应用:使用 API Gateway 暴露 REST 接口,Lambda 函数处理业务逻辑,DynamoDB 作为数据存储。

但当时整个部署过程依赖大量手动操作,随着项目复杂度提升或环境增多(如测试、预发、生产),这种方式很快就会变得难以维护。

本文将介绍如何使用 AWS Serverless Application Model(SAM) 来解决这个问题——它支持通过模板定义无服务器应用,并实现自动化部署。

我们将重点讲解以下内容:

✅ SAM 的基本概念及其底层依赖 CloudFormation
✅ 使用 SAM 模板语法定义无服务器应用
✅ 借助 CloudFormation CLI 实现自动化部署


2. 基础知识

正如前文所述,AWS 提供了 API Gateway、Lambda 和 DynamoDB 的组合,可以构建完全无服务器的应用架构。这种模式在性能、成本和可扩展性方面优势明显。

但痛点也很清晰:需要在控制台中手动创建函数、上传代码、配置 DynamoDB 表、设置 IAM 角色、设计 API 路由等,步骤繁琐且容易出错。

当应用变复杂,或者需要管理多个环境时,这种“手敲式”部署就成了效率瓶颈。

这时候就需要 CloudFormation(通用基础设施即代码工具)和专门针对无服务器场景优化的 Serverless Application Model(SAM) 登场了。

2.1 AWS CloudFormation

CloudFormation 是 AWS 提供的基础设施即代码(IaC)服务。你可以通过一个模板(template)声明所需的所有资源,AWS 会自动完成创建和配置。

理解 CloudFormation 和 SAM 的核心概念如下:

  • 模板(Template):描述应用运行时结构的蓝图,支持 JSON 或 YAML 格式。你可以在其中定义资源及其配置。
  • 资源(Resource):CloudFormation 的基本构建单元。它可以是任何 AWS 资源,比如 RestApi、DynamoDB 表、EC2 实例、IAM 角色等。目前官方支持约 300 种资源类型。
  • 栈(Stack):模板的一次实例化。CloudFormation 会根据模板创建并管理这个栈中的所有资源。

⚠️ 注意:CloudFormation 是通用基础设施管理工具,不仅限于无服务器场景。

2.2 Serverless Application Model (SAM)

虽然 CloudFormation 功能强大,但其模板语法相对复杂,写起来不够简洁。

于是 AWS 推出了 SAM —— 一个专为无服务器应用设计的简化层。它的目标很明确:提供更干净、更直观的语法来定义 Lambda、API Gateway 和 DynamoDB 等常见无服务器组件。

目前 SAM 主要支持三种资源类型:

  • AWS::Serverless::Function:Lambda 函数
  • AWS::Serverless::Api:API Gateway 接口
  • AWS::Serverless::SimpleTable:简化的 DynamoDB 表

SAM 本质上是对 CloudFormation 的扩展,你写的 SAM 模板最终会被转换成标准的 CloudFormation 模板执行。

📌 更多细节可参考:


3. 准备工作

要完成本教程,你需要:

  1. 一个 AWS 账号(免费套餐即可)
  2. 安装 AWS CLI
  3. 在当前区域创建一个 S3 存储桶(用于存放部署包)

创建 S3 桶命令如下:

aws s3 mb s3://baeldung-sam-bucket

🔔 注意:S3 桶名全局唯一,请替换 baeldung-sam-bucket 为你自己的名称。

示例代码基于 《使用 AWS Lambda 与 API Gateway》 一文中的 Java 项目。


4. 编写 SAM 模板

本节将逐步构建 SAM 模板,先看整体结构,再填充具体资源。

4.1 模板结构

SAM 模板通常由头部和资源体组成:

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: Baeldung Serverless Application Model example

Resources:
  PersonTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      # 表属性
  StorePersonFunction:
    Type: AWS::Serverless::Function
    Properties:
      # 函数属性
  GetPersonByHTTPParamFunction:
    Type: AWS::Serverless::Function
    Properties:
      # 函数属性
  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      # API 属性

关键字段说明:

  • AWSTemplateFormatVersion:CloudFormation 模板版本
  • Transform:指定使用 SAM 转换器,这是启用 SAM 语法的关键
  • Description:模板描述
  • Resources:定义所有资源,每个资源包含名称、类型和属性

SAM 当前支持的核心资源类型只有三个:

  • AWS::Serverless::Api
  • AWS::Serverless::Function
  • AWS::Serverless::SimpleTable

我们接下来将依次定义这些资源。

4.2 DynamoDB 表定义

我们使用 AWS::Serverless::SimpleTable 快速定义一张表:

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: Baeldung Serverless Application Model example

Resources:
  PersonTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      PrimaryKey:
        Name: id
        Type: Number
      TableName: Person

只需指定主键(id,类型为 Number)和表名即可。

✅ 适用场景:仅通过主键访问数据
❌ 复杂需求(如二级索引)建议改用原生 AWS::DynamoDB::Table

详细属性参考:SAM 官方文档 - SimpleTable

4.3 Lambda 函数定义

定义两个函数:StorePersonFunctionGetPersonByHTTPParamFunction

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: Baeldung Serverless Application Model example

Resources:
  StorePersonFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: com.baeldung.lambda.apigateway.APIDemoHandler::handleRequest
      Runtime: java8
      Timeout: 15
      MemorySize: 512
      CodeUri: ../target/aws-lambda-0.1.0-SNAPSHOT.jar
      Policies: DynamoDBCrudPolicy
      Environment:
        Variables:
          TABLE_NAME: !Ref PersonTable
      Events:
        StoreApi:
          Type: Api
          Properties:
            Path: /persons
            Method: PUT
            RestApiId:
              Ref: MyApi

  GetPersonByHTTPParamFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: com.baeldung.lambda.apigateway.APIDemoHandler::handleGetByParam
      Runtime: java8
      Timeout: 15
      MemorySize: 512
      CodeUri: ../target/aws-lambda-0.1.0-SNAPSHOT.jar
      Policies: DynamoDBReadPolicy
      Environment:
        Variables:
          TABLE_NAME: !Ref PersonTable
      Events:
        GetByPathApi:
          Type: Api
          Properties:
            Path: /persons/{id}
            Method: GET
            RestApiId:
              Ref: MyApi
        GetByQueryApi:
          Type: Api
          Properties:
            Path: /persons
            Method: GET
            RestApiId:
              Ref: MyApi

各属性详解:

属性 说明
Handler Java 类全路径 + 方法名,如 包名.类名::方法名
Runtime 运行环境,这里是 java8
Timeout 最大执行时间(秒)
MemorySize 分配内存(MB),AWS 会按比例分配 CPU
CodeUri 本地代码路径,部署时会自动上传到 S3
Policies IAM 权限策略,SAM 提供了快捷模板(如 DynamoDBCrudPolicy
Environment 环境变量,用 !Ref 引用其他资源
Events 触发事件,这里绑定 API Gateway 的路径和方法

📌 踩坑提醒:MemorySize 不仅影响内存,还影响 CPU 性能!CPU 密集型任务可通过调大内存提升性能。

完整属性列表见:SAM Function 文档

4.4 使用 Swagger 定义 API(显式方式)

除了在函数中隐式定义路由,也可以直接用 Swagger 定义整个 API:

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: Baeldung Serverless Application Model example

Resources:
  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: test
      EndpointConfiguration: REGIONAL
      DefinitionBody:
        swagger: "2.0"
        info:
          title: "TestAPI"
        paths:
          /persons:
            get:
              parameters:
              - name: "id"
                in: "query"
                required: true
                type: "string"
              x-amazon-apigateway-request-validator: "Validate query string parameters and headers"
              x-amazon-apigateway-integration:
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetPersonByHTTPParamFunction.Arn}/invocations
                responses: {}
                httpMethod: "POST"
                type: "aws_proxy"
            put:
              x-amazon-apigateway-integration:
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${StorePersonFunction.Arn}/invocations
                responses: {}
                httpMethod: "POST"
                type: "aws_proxy"
          /persons/{id}:
            get:
              parameters:
              - name: "id"
                in: "path"
                required: true
                type: "string"
              responses: {}
              x-amazon-apigateway-integration:
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetPersonByHTTPParamFunction.Arn}/invocations
                responses: {}
                httpMethod: "POST"
                type: "aws_proxy"
        x-amazon-apigateway-request-validators:
          Validate query string parameters and headers:
            validateRequestParameters: true
            validateRequestBody: false

关键点:

  • StageName:API 阶段名(如 test、prod)
  • EndpointConfiguration:接口类型(REGIONAL 或 EDGE)
  • DefinitionBody:内联 Swagger 定义

⚠️ 最关键的是 x-amazon-apigateway-integration 扩展字段:

  • uri:指定调用的 Lambda ARN
  • type: aws_proxy:使用代理集成模式
  • httpMethod: POST:Lambda 期望的调用方式

这种方式适合复杂 API 结构,但可读性较差,容易出错。

4.5 隐式 API 定义(推荐)

更简洁的方式是:不在 Resources 中定义 AWS::Serverless::Api,而是在函数的 Events 中自动触发 API 创建

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: Baeldung Serverless Application Model Example with Implicit API Definition

Globals:
  Api:
    EndpointConfiguration: REGIONAL
    Name: "TestAPI"

Resources:
  StorePersonFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: com.baeldung.lambda.apigateway.APIDemoHandler::handleRequest
      Runtime: java8
      Timeout: 15
      MemorySize: 512
      CodeUri: ../target/aws-lambda-0.1.0-SNAPSHOT.jar
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref PersonTable
      Environment:
        Variables:
          TABLE_NAME: !Ref PersonTable
      Events:
        StoreApi:
          Type: Api
          Properties:
            Path: /persons
            Method: PUT

  GetPersonByHTTPParamFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: com.baeldung.lambda.apigateway.APIDemoHandler::handleGetByParam
      Runtime: java8
      Timeout: 15
      MemorySize: 512
      CodeUri: ../target/aws-lambda-0.1.0-SNAPSHOT.jar
      Policies:
        - DynamoDBReadPolicy:
            TableName: !Ref PersonTable
      Environment:
        Variables:
          TABLE_NAME: !Ref PersonTable
      Events:
        GetByPathApi:
          Type: Api
          Properties:
            Path: /persons/{id}
            Method: GET
        GetByQueryApi:
          Type: Api
          Properties:
            Path: /persons
            Method: GET

亮点:

  • 使用 Globals 统一设置 API 全局配置(如区域、名称)
  • 删除了冗余的 MyApi 资源
  • 每个函数通过 Events 自动绑定路由

✅ 优点:简洁、易维护
⚠️ 缺点:无法自定义 Stage 名称,AWS 会默认创建名为 Prod 的阶段

💡 建议:中小型项目优先使用隐式定义,复杂项目再考虑显式 Swagger。


5. 部署与测试

完成模板编写后,进入部署阶段。

流程如下:

  1. 将代码打包上传至 S3
  2. 使用 CloudFormation 部署栈
  3. 测试接口
  4. 清理资源

5.1 上传代码到 S3

使用 cloudformation package 命令自动上传代码并生成新模板:

aws cloudformation package \
  --template-file ./sam-templates/template.yml \
  --s3-bucket baeldung-sam-bucket \
  --output-template-file ./sam-templates/packaged-template.yml

执行后输出类似:

Uploading to 4b445c195c24d05d8a9eee4cd07f34d0 92702076 / 92702076.0 (100.00%)
Successfully packaged artifacts and wrote output template to file packaged-template.yml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file c:\zz_workspace\tutorials\aws-lambda\sam-templates\packaged-template.yml --stack-name <YOUR STACK NAME>

新生成的 packaged-template.yml 中,CodeUri 已更新为 S3 地址。

5.2 执行部署

使用 deploy 命令创建或更新栈:

aws cloudformation deploy \
  --template-file ./sam-templates/packaged-template.yml \
  --stack-name baeldung-sam-stack \
  --capabilities CAPABILITY_IAM

📌 注意:由于涉及 IAM 角色创建,必须添加 --capabilities CAPABILITY_IAM 显式授权。

成功输出:

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - baeldung-sam-stack

5.3 查看部署资源

确认部署结果:

aws cloudformation describe-stack-resources --stack-name baeldung-sam-stack

该命令会列出栈内所有资源及其状态。

5.4 接口测试

使用 curl 测试三个接口:

存储人员信息(PUT)

curl -X PUT 'https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons' \
  -H 'content-type: application/json' \
  -d '{"id": 1, "name": "John Doe"}'

通过路径参数查询(GET /persons/{id})

curl -X GET 'https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons/1' \
  -H 'content-type: application/json'

通过查询参数查询(GET /persons?id=1)

curl -X GET 'https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons?id=1' \
  -H 'content-type: application/json'

🔁 替换 URL 中的 API ID 和区域为你实际生成的值。

5.5 清理资源

测试完毕后,一键删除整个栈:

aws cloudformation delete-stack --stack-name baeldung-sam-stack

S3 桶可手动删除或保留复用。


6. 总结

本文介绍了 **AWS Serverless Application Model (SAM)**,它通过模板化方式实现了无服务器应用的自动化部署,显著提升了开发效率和可维护性。

核心要点回顾:

✅ SAM 是 CloudFormation 的简化层,专为无服务器场景设计
✅ 支持三种核心资源:Function、Api、SimpleTable
✅ 推荐使用隐式 API 定义 + Globals 提升可读性
✅ 部署流程:packagedeploytestdelete

所有示例代码已上传至 GitHub: 👉 https://github.com/eugenp/tutorials/tree/master/aws-modules/aws-lambda-modules


原始标题:Introduction to AWS Serverless Application Model