はじめに

AWSでサーバーレスアプリケーションを構築する際、一般的にはLambdaを使用します。しかしLambdaを使用すると言語のバージョンアップを管理する必要が出てきます。そこで今回はLambda関数を使用せずにAPI GatewayとDynamoDBの直接統合のみでToDoアプリのバックエンドを構築してみました。

アーキテクチャ

本構成では以下のAWSサービスを使用しています:

  • Amazon API Gateway
  • Amazon DynamoDB

実装手順

DynamoDBテーブルの作成

まず、ToDoアイテムを保存するためのDynamoDBテーブルを作成します:

  • テーブル名: honda-iret-media-todos
  • パーティションキー: id (String)

IAMロールの作成

API GatewayがDynamoDBにアクセスするためのIAMロールを作成します:

  • ロール名: HondaIretMediaTodoApiGatewayRole

HondaIretMediaTodoApiGatewayRole

API Gatewayの設定

リソースの作成/メソッドの作成

API GatewayはREST APIで作成します。以下の通りリソース、各メソッドを作成します。CORSを有効化するとOPTIONSは勝手に作成されます。

API Gatewayリソース

メソッドタイプを作成の際は

  • メソッドタイプ: 各メソッド(GET, POST, DELETE, PUT)
  • 統合タイプ: AWSのサービス
  • AWSのサービス: DynamoDB
  • HTTPメソッド: POST
  • アクション名: 任意。GetItem, PostItemなど
  • 実行ロール: IAMロールのARN

重要なポイントとしてはサービス統合のHTTPメソッドはどのメソッドでも必ずPOSTを指定するというところです。これはDynamoDBの操作のリクエストがPOSTで行われているのが理由のようです(後述のリファレンス参照)。

マッピングテンプレートの実装

統合リクエスト、統合レスポンスのマッピングテンプレートでDynamoDBへの読み取り、書き込みを定義します。VTL(後述のリファレンス参照)での記述となります。

GET

統合リクエスト

{
    "TableName": "honda-iret-media-todos",
    "Select": "ALL_ATTRIBUTES"
}

統合レスポンス

#set($inputRoot = $input.path('$'))
[
    #foreach($item in $inputRoot.Items)
    {
        "id": $item.id.S,
        "title": $item.title.S,
        "completed": $item.completed.BOOL,
        "createdAt": $item.createdAt.S
    }#if($foreach.hasNext),#end
    #end
]

POST

統合リクエスト

//createdAtはエポックタイムで登録
{
    "TableName": "honda-iret-media-todos",
    "Item": {
        "id": {
            "S": "$context.requestId"
        },
        "title": {
            "S": "$input.path('$.title')"
        },
        "completed": {
            "BOOL": false
        },
        "createdAt": {
            "S": "$context.requestTimeEpoch.toString()"
        }
    }
}

統合レスポンスはなし

PUT

統合リクエスト

{
    "TableName": "honda-iret-media-todos",
    "Key": {
        "id": {
            "S": "$method.request.path.id"
        }
    },
    "UpdateExpression": "SET title = :title, completed = :completed",
    "ExpressionAttributeValues": {
        ":title": {
            "S": "$input.path('$.title')"
        },
        ":completed": {
            "BOOL": $input.path('$.completed')
        }
    },
    "ReturnValues": "ALL_NEW"
}

統合レスポンス

#set($inputRoot = $input.path('$'))
{
    "id": $input.json('$.Attributes.id.S'),
    "title": $input.json('$.Attributes.title.S'),
    "completed": $input.json('$.Attributes.completed.BOOL'),
    "createdAt": $input.json('$.Attributes.createdAt.S')
}

DELETE

統合リクエスト

{
    "TableName": "honda-iret-media-todos",
    "Key": {
        "id": {
            "S": "$method.request.path.id"
        }
    }
}

統合レスポンスはなし

リクエストのバリデーションの実装

モデルを作成して、メソッドリクエストの設定でモデルを指定します。

API Gateway モデル作成

POST

  • 名前: TodoInput
  • コンテンツタイプ: application/json

モデルのスキーマは以下の通り記述します。

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "TodoInput",
  "type": "object",
  "required": ["title"],
  "properties": {
    "title": {
      "type": "string",
      "minLength": 1
    }
  },
  "additionalProperties": false
}

PUT

  • 名前: TodoUpdateInput
  • コンテンツタイプ: application/json

モデルのスキーマは以下の通りです。

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "TodoInput",
  "type": "object",
  "properties": {
    "title": {
      "type": "string",
      "minLength": 1
    },
    "completed": {
        "type": "boolean"
    }
  },
  "additionalProperties": false
}

作成したモデルをそれぞれのメソッドリクエストの設定で指定します。

API Gateway メソッドリクエストの編集

  • リクエストバリデーター: 本文を検証
  • リクエスト本文: コンテンツタイプはapplication/json、モデルは作成したモデルを選択

これでリクエストがバリデーションに引っかかった場合400エラーを返すようになります。

まとめ

Lambdaを使わずAPI GatewayとDynamoDBだけでTodoアプリのバックエンドを構築してみました。バリデーションも実装できましたのでシンプルなCRUDの処理であれば十分ですね。
ただし、懸念点としては

  • 複雑なロジックの実装は困難なので、フロントにロジックを寄せがちになる
  • デバッグが難しい
  • VTLの学習コスト

があります。個人的には適切なユースケースであれば十分有効な選択肢になり得る、と思いました。よかったらやってみて下さい!

リファレンス

DynamoDB low-level API

DynamoDB low-level APIはHTTP(S) POST リクエストを入力として受け付けます。
DynamoDB low-level API

VTLとは

VTLはApache VelocityというオープンソースのJavaベースの汎用テンプレートエンジンで使用されるテンプレート言語です。AWSではAPI GatewayのマッピングテンプレートやAppSyncのリゾルバーテンプレートを記述する時に使用します。
Velocity Template Language (VTL): An Introduction