建立 DynamoDB

SAM 有定義一個 AWS::Serverless::SimpleTable 的 Resource 給 DynamoDb。

我在 template.yaml 的 Resources 下增加了以下的 code

1  Table:
2    Type: AWS::Serverless::SimpleTable # More info about SimpleTable Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-simpletable.html
3    Properties:
4      PrimaryKey:
5        Name: id
6        Type: String
7      TableName: Users

接著在 SAM Deploy 時,會看到以下的 Change Set

 1CloudFormation stack changeset
 2-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 3Operation                                                           LogicalResourceId                                                   ResourceType                                                        Replacement
 4-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 5+ Add                                                               Table                                                               AWS::DynamoDB::Table                                                N/A
 6-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 7
 8
 9Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:129824596365:changeSet/samcli-deploy1711462102/d5561ddb-0426-4de1-be4d-08b4a0ab02aa
10
11
12Previewing CloudFormation changeset before deployment
13======================================================
14Deploy this changeset? [y/N]: y
15
162024-03-26 22:08:41 - Waiting for stack create/update to complete
17
18CloudFormation events from stack operations (refresh every 5.0 seconds)
19-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
20ResourceStatus                                                      ResourceType                                                        LogicalResourceId                                                   ResourceStatusReason
21-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
22UPDATE_IN_PROGRESS                                                  AWS::CloudFormation::Stack                                          sam-app                                                             User Initiated
23CREATE_IN_PROGRESS                                                  AWS::DynamoDB::Table                                                Table                                                               -
24CREATE_IN_PROGRESS                                                  AWS::DynamoDB::Table                                                Table                                                               Resource creation Initiated
25CREATE_COMPLETE                                                     AWS::DynamoDB::Table                                                Table                                                               -
26UPDATE_COMPLETE_CLEANUP_IN_PROGRESS                                 AWS::CloudFormation::Stack                                          sam-app                                                             -
27UPDATE_COMPLETE                                                     AWS::CloudFormation::Stack                                          sam-app                                                             -
28-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

從 AWS Console 可以看到,我們建立的DynamoDB Table Users dynamodb

建立 Items

點入 Table Users 以後,右上角的 Action 裡面,有一個 Create Item dynamodbaddItems1

接著艾倫會建立一個 item ,id 是 1,firstName 是 Allen ,lastName 是 Hsieh dynamodbaddItems2

點下 Create Item ,我們就可以在 Table 裡面看到有第一筆 Item

dynamodbaddItems3

建立 Get Users API

這邊都是基於 Part1 的 Hello World 去修改

package.json

第一件事情,先在 package.json 加入 aws-sdk lib (line11)

 1{
 2  "name": "hello_world",
 3  "version": "1.0.0",
 4  "description": "hello world sample for NodeJS",
 5  "main": "app.js",
 6  "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs",
 7  "author": "SAM CLI",
 8  "license": "MIT",
 9  "dependencies": {
10    "axios": ">=1.6.0",
11    "aws-sdk": "2.1419.0"
12  },
13  "scripts": {
14    "test": "mocha tests/unit/"
15  },
16  "devDependencies": {
17    "chai": "^4.3.6",
18    "mocha": "^10.2.0"
19  }
20}

修改 hello_world 資料夾名稱

接著修改 hello_world 改成 users

 1$ tree . 
 2.
 3├── README.md
 4├── events
 5│   └── event.json
 6├── samconfig.toml
 7├── template.yaml
 8└── users
 9    ├── app.mjs
10    ├── package.json
11    └── tests
12        └── unit
13            └── test-handler.mjs

修改 Lambda Function

Line1 : 將 DynamoDBClient 匯入 Line16: 根據 Http Method 來判斷執行什麼 Action ,由於現在只處理 Get Method

 1import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
 2import {
 3  DynamoDBDocumentClient,
 4  ScanCommand
 5} from "@aws-sdk/lib-dynamodb";
 6
 7const client = new DynamoDBClient({});
 8
 9const dynamo = DynamoDBDocumentClient.from(client);
10
11const tableName = "Users";
12
13export const lambdaHandler = async (event, context) => {
14    console.log("EVENT: \n" + JSON.stringify(event, null, 2));
15    console.log("httpmethod" + event.httpMethod);
16    let body;
17    switch(event.httpMethod) {
18      case "GET":
19        console.log("Http Get");
20        body = await dynamo.send(
21          new ScanCommand({ TableName: tableName })
22        );
23        body = body.Items;
24        break;
25    }
26    const response = {
27      statusCode: 200,
28      body: JSON.stringify(body)
29    };
30
31    return response;
32};

template.yaml

新增 Get Uesr Function 在 Resource 下面。

 1  GetUsers:
 2    Type: AWS::Serverless::Function
 3    Properties:
 4      CodeUri: users/
 5      Handler: app.lambdaHandler
 6      Runtime: nodejs20.x
 7      Architectures:
 8      - x86_64
 9      Events:
10        HelloWorld:
11          Type: Api
12          Properties:
13            Path: /users
14            Method: get

重新部署

這邊記得,要先 sam build 之後執行 sam deploy 才會生效,接著我們使用 Curl 去打 apigateway

1$ curl https://kxkhl0spi1.execute-api.ap-northeast-1.amazonaws.com/Prod/users                      
2{"message": "Internal server error"}

這邊會發現,得到的 message 是錯誤,接著去 CloudWatch 看 Log ,會發現 Lambda 沒有權限去 access DynamoDB.

 1{
 2    "timestamp": "2024-02-10T14:23:01.191Z",
 3    "level": "ERROR",
 4    "requestId": "def17c42-6b0b-4c96-a081-948984ae0c8f",
 5    "message": {
 6        "errorType": "DynamoDBServiceException",
 7        "errorMessage": "User: arn:aws:sts::129824596365:assumed-role/sam-app-GetUsersRole-e2VmZDAl4NoY/sam-app-GetUsers-PGeiGDJRikRx is not authorized to perform: dynamodb:Scan on resource: arn:aws:dynamodb:ap-northeast-1:129824596365:table/users because no identity-based policy allows the dynamodb:Scan action",
 8        "stackTrace": [
 9            "AccessDeniedException: User: arn:aws:sts::129824596365:assumed-role/sam-app-GetUsersRole-e2VmZDAl4NoY/sam-app-GetUsers-PGeiGDJRikRx is not authorized to perform: dynamodb:Scan on resource: arn:aws:dynamodb:ap-northeast-1:129824596365:table/users because no identity-based policy allows the dynamodb:Scan action",
10            "    at throwDefaultError (/var/runtime/node_modules/@aws-sdk/node_modules/@smithy/smithy-client/dist-cjs/index.js:838:20)",
11            "    at /var/runtime/node_modules/@aws-sdk/node_modules/@smithy/smithy-client/dist-cjs/index.js:847:5",
12            "    at de_CommandError (/var/runtime/node_modules/@aws-sdk/client-dynamodb/dist-cjs/index.js:2150:14)",
13            "    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)",
14            "    at async /var/runtime/node_modules/@aws-sdk/node_modules/@smithy/middleware-serde/dist-cjs/index.js:35:20",
15            "    at async /var/runtime/node_modules/@aws-sdk/lib-dynamodb/dist-cjs/index.js:174:30",
16            "    at async /var/runtime/node_modules/@aws-sdk/node_modules/@smithy/core/dist-cjs/index.js:165:18",
17            "    at async /var/runtime/node_modules/@aws-sdk/node_modules/@smithy/middleware-retry/dist-cjs/index.js:320:38",
18            "    at async /var/runtime/node_modules/@aws-sdk/middleware-logger/dist-cjs/index.js:33:22",
19            "    at async Runtime.lambdaHandler [as handler] (file:///var/task/app.mjs:34:16)"
20        ],
21        "name": "AccessDeniedException",
22        "$fault": "client",
23        "$metadata": {
24            "httpStatusCode": 400,
25            "requestId": "ULUQ6S15FJ9GN2KDDMG9EF7NT3VV4KQNSO5AEMVJF66Q9ASUAAJG",
26            "attempts": 1,
27            "totalRetryDelay": 0
28        },
29        "__type": "com.amazon.coral.service#AccessDeniedException",
30        "message": "User: arn:aws:sts::129824596365:assumed-role/sam-app-GetUsersRole-e2VmZDAl4NoY/sam-app-GetUsers-PGeiGDJRikRx is not authorized to perform: dynamodb:Scan on resource: arn:aws:dynamodb:ap-northeast-1:129824596365:table/users because no identity-based policy allows the dynamodb:Scan action"
31    }
32}

修復權限問題

SAM 有定義許多 policy template ,而 Get 只需要 Read DyanmoDB ,所以我們可以選擇 DynamoDBReadPolicy 就可以

最終 template.yaml 的 Resources 會長這樣

 1Resources:
 2  Table:
 3    Type: AWS::Serverless::SimpleTable 
 4    Properties:
 5      PrimaryKey:
 6        Name: id
 7        Type: String
 8      TableName: Users
 9  GetUsers:
10    Type: AWS::Serverless::Function
11    Properties:
12      CodeUri: users/
13      Handler: app.lambdaHandler
14      Runtime: nodejs20.x
15      Architectures:
16      - x86_64
17      Events:
18        HelloWorld:
19          Type: Api
20          Properties:
21            Path: /users
22            Method: get
23      Policies:
24      - DynamoDBReadPolicy:
25          TableName: !Ref Table

部署時,從 ChangeSet 可以看到 IAM Role 有修改

1CloudFormation stack changeset
2-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
3Operation                                                           LogicalResourceId                                                   ResourceType                                                        Replacement
4-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
5* Modify                                                            GetUsersRole                                                        AWS::IAM::Role                                                      False
6* Modify                                                            GetUsers                                                            AWS::Lambda::Function                                               False
7* Modify                                                            ServerlessRestApi                                                   AWS::ApiGateway::RestApi                                            False
8-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

部署完以後再用 Curl 打 ApiGateway 會拿到結果

1$ curl https://kxkhl0spi1.execute-api.ap-northeast-1.amazonaws.com/Prod/users
2[{"id":"1","lastName":"Hsieh","firstName":"Allen"}]

建立 Create User API

修改 Lambda Fuction

艾倫在 swtich case 中,多加了 Post 的 case (line19-33)

 1import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
 2import {
 3  DynamoDBDocumentClient,
 4  ScanCommand,
 5  PutCommand
 6} from "@aws-sdk/lib-dynamodb";
 7
 8const client = new DynamoDBClient({});
 9
10const dynamo = DynamoDBDocumentClient.from(client);
11
12const tableName = "Users";
13
14export const lambdaHandler = async (event, context) => {
15    console.log("EVENT: \n" + JSON.stringify(event, null, 2));
16    console.log("httpmethod" + event.httpMethod);
17    let body;
18    switch(event.httpMethod) {
19      case "POST": 
20        console.log("Http Post");
21        let item = JSON.parse(event.body)
22        await dynamo.send(
23          new PutCommand({
24            TableName: tableName,
25            Item: {
26              id: item.id,
27              lastName: item.lastName,
28              firstName: item.firstName
29            },
30          })
31        );
32        body = "{}";
33        break;
34      case "GET":
35        console.log("Http Get");
36        body = await dynamo.send(
37          new ScanCommand({ TableName: tableName })
38        );
39        body = body.Items;
40        break;
41    }
42    const response = {
43      statusCode: 200,
44      body: JSON.stringify(body)
45    };
46
47    return response;
48};

修改 template.yaml

這邊要注意的是,因為我們是需要寫入 DynamoDB,所已這邊使用的 Policies 是 DynamoDBCrudPolicy

 1  CreateUsers:
 2    Type: AWS::Serverless::Function
 3    Properties:
 4      CodeUri: users/
 5      Handler: app.lambdaHandler
 6      Runtime: nodejs20.x
 7      Architectures:
 8      - x86_64
 9      Events:
10        HelloWorld:
11          Type: Api
12          Properties:
13            Path: /users
14            Method: post
15      Policies:
16      - DynamoDBCrudPolicy:
17          TableName: !Ref Table

測試

在部署完後,使用 curl 測試 Post ,可以看到正常的回覆。

1curl -X POST https://kxkhl0spi1.execute-api.ap-northeast-1.amazonaws.com/Prod/users -d '{"id":"2", "lastName" : "Chung", "firstName": "Leo"}'
2"{}"

再 call 一次,我們原先寫好的 get ,可以看到剛剛建立的 Item

1$ curl https://kxkhl0spi1.execute-api.ap-northeast-1.amazonaws.com/Prod/users
2[{"id":"2","lastName":"Chung","firstName":"Leo"},{"id":"1","lastName":"Hsieh","firstName":"Allen"}]