利用 Serverless Framework 建置和部署專案

Serverless Framework

本文說明如何利用 Serverless Framework 建立一個經由 RESTful API 讀取 DynamoDB 資料庫的專案。

行前準備

在動手實作前,需要準備的行前工作有 (1) 安裝 Serverless Framework;(2) 取得部署的通關密碼。

安裝 Serverless Framework

Serverless 是一種不用自行管理伺服器,而能讓開發者建置和執行應用程式的一種雲端模型,這樣開發者就能專心在功能開發,而不用花時間在建構底層的架構和設備上,像是 CPU、記憶體、硬碟、作業系統、網路設定等,只要經由設定就能完成這些繁瑣的工作。

若要自己從頭到尾打底建專案就太累了,在這裡會用 Serverless Framework 來幫忙處理。Serverless Framework 是一套協助建立雲端服務的介面工具,在很簡單的撰寫 YAML 設定檔和應用程式的程式碼後,即可利用 CloudFormation 部署整組 stack 到雲端服務上。

安裝 Serverless Framework,稍等會用 Serverless Framework 幫我們建立專案。

yarn add serverless --global

npm install serverless -g

取得部署的通關密碼

由於是透過 Serverless Framework 來做部署,因此會用到 Access key ID 和 Secret access key,這在 AWS console 上是可以取得的。

從 AWS console 取得 deploy 的通關密碼,步驟如下:

取得部署的通關密碼

export AWS_ACCESS_KEY_ID=
export AWS_SECRET_ACCESS_KEY=

建立專案

在這裡我想建立一個甜點店家清單,利用 Serverless Framework 建立專案,在這裡使用 aws-nodejs 樣版,來建立名為 snack-list 的專案。

serverless create --template aws-nodejs --path snack-list

或是打指令 serverless 後選擇樣版「AWS - Node.js - HTTP API」,接著就會幫我們建立名為 aws-node-http-api-project 的專案也是可以的,之後專案裡面的內容都可以再調整。

建立完成!顯示成功訊息。

$ serverless create --template aws-nodejs --path snack-list

✔ Project successfully created in "snack-list" from "aws-nodejs" template (9s)

建立完成後,檢視資料夾,主要是新增 serverless.ymlhandler.js 這兩支檔案,serverless.yml 是 AWS 服務的設定檔,像是稍等會用到的 Lambda function、API Gateway 和 DynamoDB 都會在這支檔案做設定;而 handler.js 則是來實作 Lambda function,像是怎麼讀取資料表、新增一筆資料和刪除指定資料。

設定檔

在實作 serverless.yml 設定檔方面,由於會用到 Lambda、API Gateway 和 DynamoDB,接下來逐步設定。

API Gateway

serverless.yml 定義 API Gateway 要長什麼樣子,在這裡定義的 RESTful API 會對應稍後用到的 Lambda function 的 create、list 和 delete,其中 delete 會帶入要刪除的項目的 id。

functions:
  createSnack:
    handler: handler.create
    events:
      - http:
          path: snacks
          method: POST
  getSnacks:
    handler: handler.list
    events:
      - http:
          path: snacks
          method: GET
  deleteSnack:
    handler: handler.delete
    events:
      - http:
          path: snacks/{id}
          method: DELETE
          request:
            parameters:
              paths:
                id: true

DynamoDB

serverless.yml 定義 DynamoDB 要長什麼樣子,例如:執行的環境和權限。

provider:
  name: aws
  runtime: nodejs14.x
  environment:
    TABLE_NAME:
      Ref: snacks
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:DescribeTable
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource:
        - "Fn::GetAtt": [snacks, Arn]

再來定義 primary key 的欄位與資料型別。

resources:
  Resources:
    snacks:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: snacks
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1

以上定義好之後,就完成簡易的設定了。

Lambda Function

handler.js 實作 Lambda function。

首先設定環境,像是引入 aws-sdk 和稍等會用到的 table。

const { DynamoDB } = require('aws-sdk');

const db = new DynamoDB.DocumentClient();
const TableName = process.env.TABLE_NAME;

在這支檔案裡面會 export 3 支 function - create、list 和 delete。

create 是建立一筆 snack,欄位有 id (primary key)、name (甜點店家名稱) 和 photoUrl (圖片網址)。

module.exports.create = async (event) => {
  const { id, name, photoUrl } = JSON.parse(event.body);
  const newSnack = { id, name, photoUrl };

  await db
    .put({
      TableName,
      Item: newSnack,
    })
    .promise();

  return {
    statusCode: 200,
    body: JSON.stringify(newSnack),
  };
};

list 是取出所有的 snack,在這裡會用 scan 來實作,關於 scan 可參考這裡

module.exports.list = async (event) => {
  const snacks = await db
    .scan({
      TableName,
    })
    .promise();

  return {
    statusCode: 200,
    body: JSON.stringify(snacks),
  };
};

delete 是經由帶入的 id 移除指定的 snack。

module.exports.delete = async (event) => {
  const snackToBeRemovedId = event.pathParameters.id;

  await db
    .delete({
      TableName,
      Key: {
        id: snackToBeRemovedId,
      },
    })
    .promise();

  return {
    statusCode: 200,
  };
};

在測試時,可從 CloudWatch 看 log 來做 debug。

CloudWatch

部署

完成 serverless.ymlhandler.js 的實作,接下來就可以做部署。

serverless deploy

部署完成!顯示成功訊息。

$ serverless deploy

Deploying serverless-framework-example to stage dev (us-east-1)

✔ Service deployed to stack serverless-framework-example-dev (109s)

endpoints:
  POST - https://sample.execute-api.us-east-1.amazonaws.com/dev/snacks
  GET - https://sample.execute-api.us-east-1.amazonaws.com/dev/snacks
  DELETE - https://sample.execute-api.us-east-1.amazonaws.com/dev/snacks/:id
functions:
  createSnack: serverless-framework-example-dev-createSnack (1.4 kB)
  getSnacks: serverless-framework-example-dev-getSnacks (1.4 kB)
  deleteSnack: serverless-framework-example-dev-deleteSnack (1.4 kB)

接下來用 Postman 測試看看部署好的 endpoints。

GET

先到 AWS console 裡面新增一筆 record,這樣在 get 的時候就會拿到資料。

GET

DELETE

刪除剛才新增的資料,注意要將 id 帶入網址,格式是 https://sample.com/snacks/{id},例如:https://sample.com/snacks/1669537453864

DELETE

檢查 table 裡的確沒有資料了。

DELETE

POST

從 request body 帶入想要存的資料。

{
  "id": "1669643175939",
  "name": "通天掌糕糕",
  "photoUrl": ""
}

POST

檢查 table 出現了一筆剛剛丟進去的資料。

POST

總結

比起純手工從無到有打造專案和流程,Serverless Framework 讓開發人員能更專注在功能的開發上,也能更有效的管理各種服務,是很便利的!

關於 Serverless Framework 和 SAM 的比較,SAM 在針對 AWS 服務上的串接更簡單 (畢竟是同一家的人),並且如果已經很熟悉 CloudFormation 的語法和操作,上手 SAM 是相對容易的,不過無法做複雜的專案,像是想要有很多 step function 就是做不到的。有興趣可參考這篇文章。

參考資料


Serverless Framework DynamoDB Lambda API Gateway CloudFormation SAM IAM Serverless AWS Amazon Web Services RESTful API Postman