LoginSignup
3
5

More than 5 years have passed since last update.

Serverless Application Model に入門してみた

Last updated at Posted at 2017-05-03

はじめに

クラウドを低コストで運用できるAWS Serverless(API Gateway + Lambda + DynamoDB)に入門してみました。

Serverless.png

基本的には、クラスメソッドさんの記事が分かりやすく参考になりました。

WS Serverless Application Model (AWS SAM) を使ってサーバーレスアプリケーションを構築する

ただし、そのままでは動作しない部分があったので、今回は、その変更部分を中心に記載します。

環境

私の環境は以下です。

  • OS: Windows 10
  • AWS CLI: aws-cli/1.11.82 Python/2.7.9 Windows/8 botocore/1.5.45

Windows環境というのが参考にした記事との差分になります。

ディレクトリ構成

ディレクトリ構成はこのようにしました。
dir.png

コマンドは、C:\Git\AWS\Serverless\api_backend で実行しました。

Lambda

get, put, delete を1つのJavaScriptファイルにまとめて記載します。
テーブル名は環境変数から取得するようにしています。

index.js
'use strict';

const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();

const tableName = process.env.TABLE_NAME;

const createResponse = (statusCode, body) => {

    return {
        statusCode: statusCode,
        body: body
    };
};

exports.get = (event, context, callback) => {

    let params = {
        TableName: tableName,
        Key: {
            id: event.pathParameters.resourceId
        }
    };

    let dbGet = (params) => { return dynamo.get(params).promise() };

    dbGet(params).then( (data) => {
        if (!data.Item) {
            callback(null, createResponse(404, "ITEM NOT FOUND"));
            return;
        }
        console.log(`RETRIEVED ITEM SUCCESSFULLY WITH doc = ${data.Item.doc}`);
        callback(null, createResponse(200, data.Item.doc));
    }).catch( (err) => { 
        console.log(`GET ITEM FAILED FOR doc = ${params.Key.id}, WITH ERROR: ${err}`);
        callback(null, createResponse(500, err));
    });
};

exports.put = (event, context, callback) => {

    let item = {
        id: event.pathParameters.resourceId,
        doc: event.body
    };

    let params = {
        TableName: tableName,
        Item: item
    };

    let dbPut = (params) => { return dynamo.put(params).promise() };

    dbPut(params).then( (data) => {
        console.log(`PUT ITEM SUCCEEDED WITH doc = ${item.doc}`);
        callback(null, createResponse(200, null));
    }).catch( (err) => { 
        console.log(`PUT ITEM FAILED FOR doc = ${item.doc}, WITH ERROR: ${err}`);
        callback(null, createResponse(500, err)); 
    });
};

exports.delete = (event, context, callback) => {

    let params = {
        TableName: tableName,
        Key: {
            id: event.pathParameters.resourceId
        },
        ReturnValues: 'ALL_OLD'
    };

    let dbDelete = (params) => { return dynamo.delete(params).promise() };

    dbDelete(params).then( (data) => {
        if (!data.Attributes) {
            callback(null, createResponse(404, "ITEM NOT FOUND FOR DELETION"));
            return;
        }
        console.log(`DELETED ITEM SUCCESSFULLY WITH id = ${event.pathParameters.resourceId}`);
        callback(null, createResponse(200, null));
    }).catch( (err) => { 
        console.log(`DELETE ITEM FAILED FOR id = ${event.pathParameters.resourceId}, WITH ERROR: ${err}`);
        callback(null, createResponse(500, err));
    });
};

テンプレート

template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Simple CRUD webservice. State is stored in a SimpleTable (DynamoDB) resource.
Resources:
  GetFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.get
      Runtime: nodejs6.10
      Policies: AmazonDynamoDBReadOnlyAccess
      CodeUri: /Git/AWS/Serverless/api_backend/src
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
      Events:
        GetResource:
          Type: Api
          Properties:
            Path: /resource/{resourceId}
            Method: get

  PutFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.put
      Runtime: nodejs6.10
      Policies: AmazonDynamoDBFullAccess
      CodeUri: /Git/AWS/Serverless/api_backend/src
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
      Events:
        PutResource:
          Type: Api
          Properties:
            Path: /resource/{resourceId}
            Method: put

  DeleteFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.delete
      Runtime: nodejs6.10
      Policies: AmazonDynamoDBFullAccess
      CodeUri: /Git/AWS/Serverless/api_backend/src
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
      Events:
        DeleteResource:
          Type: Api
          Properties:
            Path: /resource/{resourceId}
            Method: delete

  Table:
    Type: AWS::Serverless::SimpleTable

LambdaのJavaScriptファイルを配置したディレクトリをCodeUriとして指定しました。
CodeUriはフルパスで指定する必要がありました。
CodeUriを相対パスで CodeUri:src のように指定するとLambdaの参照を解決できず、以下のエラーがでていました。

Unable to upload artifact src referenced by CodeUri parameter of GetFunction resource.
Parameter CodeUri of resource GetFunction refers to a file or folder that does not exist C:\Git\AWS\Serverless\api_backend\config\src

package

packageとは、LambdaをS3にアップロードし、CloudFormationのテンプレート(template.yaml)を環境に合わせて実体化する作業を言います。

  • Lambdaのアップロード
    package.png
     
  • Templateの実体化

    template.png
     

  • コマンド

    事前にS3のバケット()を作成しておきます。
    以下のコマンドの<bucket-name>部分を作成したS3のバケット名に置き換えてpackageコマンドを実行します。

aws cloudformation package --template-file config/template.yaml --output-template-file config/serverless-output.yaml --s3-bucket <bucket-name>
  • 実体化されたTemplate
    packageコマンドを実行すると、以下のような実体化されたTemplateが生成されます。
serverless-output.yam
AWSTemplateFormatVersion: '2010-09-09'
Description: Simple CRUD webservice. State is stored in a SimpleTable (DynamoDB) resource.
Resources:
  DeleteFunction:
    Properties:
      CodeUri: s3://<bucket-name>/a14396c7934ad2583710bf066a1340aa
      Environment:
        Variables:
          TABLE_NAME:
            Ref: Table
      Events:
        DeleteResource:
          Properties:
            Method: delete
            Path: /resource/{resourceId}
          Type: Api
      Handler: index.delete
      Policies: AmazonDynamoDBFullAccess
      Runtime: nodejs6.10
    Type: AWS::Serverless::Function
  GetFunction:
    Properties:
      CodeUri: s3://<bucket-name>/a14396c7934ad2583710bf066a1340aa
      Environment:
        Variables:
          TABLE_NAME:
            Ref: Table
      Events:
        GetResource:
          Properties:
            Method: get
            Path: /resource/{resourceId}
          Type: Api
      Handler: index.get
      Policies: AmazonDynamoDBReadOnlyAccess
      Runtime: nodejs6.10
    Type: AWS::Serverless::Function
  PutFunction:
    Properties:
      CodeUri: s3://<bucket-name>/a14396c7934ad2583710bf066a1340aa
      Environment:
        Variables:
          TABLE_NAME:
            Ref: Table
      Events:
        PutResource:
          Properties:
            Method: put
            Path: /resource/{resourceId}
          Type: Api
      Handler: index.put
      Policies: AmazonDynamoDBFullAccess
      Runtime: nodejs6.10
    Type: AWS::Serverless::Function
  Table:
    Type: AWS::Serverless::SimpleTable
Transform: AWS::Serverless-2016-10-31

deploy

deployコマンドで、API Gateway, Lambda, DynamoDBのテーブルを実際に作成します。
stack-nameは、今回作成する一式を管理するための名前です。

aws cloudformation deploy --template-file config/serverless-output.yaml --stack-name serverless-api-backend-20170503 --capabilities CAPABILITY_IAM

CloudFormationのマネージメントコンソールでスタックが生成されていることが確認できます。

CloudFormation.png

ここで重要なのが --capabilities CAPABILITY_IAM オプションです。これは、LambdaからDynamoDBを呼び出したりするためのポリシー設定をCloudFrontに許可するかどうかの指定になります。これを忘れると以下のエラーメッセージになります。

An error occurred (InsufficientCapabilitiesException) when calling the ExecuteChangeSet operation: Requires capabilities : [CAPABILITY_IAM]

Test

最後に作成したServerless Application ModelのテストとしてAPI GatewayからLambdaを呼び出し、DynamoDBを操作してみます。

API Gatewayのマネージメントコンソールで、serverless-api-backend-20170503を開きます。

リソース → /resource/{resourceId} → PUT → テスト

の順にクリックしてメソッドテストの画面を開き、メソッドテストの各項目には、次のように入力します。

項目
パス res001
クエリ文字列 param1=value1&param2=value2
ヘッダー Accept: application/json
Content-type: application/json
リクエスト本文 {"message1":"Hello","message2":"Lambda"}

最後に「テスト」ボタンを押下すると、DynamoDBのテーブルにitemが追加され、テストが成功します。

  • DynamoDB DynamoDB.png
3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5