はじめに
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

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

メソッドタイプを作成の際は
- メソッドタイプ: 各メソッド(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"
}
}
}
統合レスポンスはなし
リクエストのバリデーションの実装
モデルを作成して、メソッドリクエストの設定でモデルを指定します。

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
}
作成したモデルをそれぞれのメソッドリクエストの設定で指定します。

- リクエストバリデーター: 本文を検証
- リクエスト本文: コンテンツタイプは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