LoginSignup
13
5

More than 3 years have passed since last update.

Amazon API GatewayのAWS 統合を使い、AWS Lambdaを通さずにAmazon DynamoDBへアクセスする

Posted at

Amazon API Gateway の「AWS 統合(AWS integration)」を使うことで、下の図のBのようにAWS Lambdaを通さずAmazon DynamoDBにアクセスができます。

これにより、例えば DynamoDB の中にあるItemを GET /items/ID のように読み出す程度の実装を、素早く行うことが可能です。

「Lambda 統合」を利用した場合のPros/Cons

Amazon API Gateway から Amazon DynamoDB 等の AWS サービスへのアクセスで利用されるパターンが、AWS Lambdaを経由する「Lambda プロキシ統合(Lambda proxy integration)」でしょう。構成は先の図のAになり、API Gatewayの設定では「統合タイプ = Lambda関数」を使用します。

クライアントからのリクエストや、呼び出し先AWSサービスからのレスポンスの加工をプログラムできるので便利ですが、管理対象が一つ増えるわけです。

API Gateway の "マッピングテンプレート"

API Gateway の AWS 統合を設定すると、「統合リクエスト」内の "マッピングテンプレート" で作ったJSONをAWSサービスのAPIに投げ込むことができます。また、AWSサービスからのレスポンスJSONを「統合レスポンス」で受けて、"マッピングテンプレート" で整形し直すことができます。

即ち、この "マッピングテンプレート" でLambdaで書くような変換実装ができるわけです。

DynamoDB の GetItem を API Gateway で実装する

今回は DynamoDBの TABLE1 に以下のようなデータが入っているとして、これを GET /items/{iid} で値を取得するようにします。
image.png

DynamoDB JSON 形式で表示
{
  "iid": {
    "S": "a45f4a4998a"
  },
  "payloads": {
    "M": {
      "batteryLevel": {
        "N": "0.75"
      },
      "clickType": {
        "N": "1"
      },
      "clickTypeName": {
        "S": "SINGLE"
      }
    }
  },
  "timestamp": {
    "N": "1610886990671"
  }
}

全体の流れ

API Gateway リソースの作成

GET /items/{iid} を作り、パス名をこの後のマッピングテンプレートで参照できるようにします。
image.png

GET メソッドのセットアップ

項目 設定内容
統合タイプ AWS サービス
AWS リージョン (DynamoDBのあるリージョン)
AWS サービス DynamoDB
AWS サブドメイン <空>
HTTP メソッド POST
アクションの種類 アクション名の使用
アクション GetItem
実行ロール (後述で作成したロールのARN)
コンテンツの処理 パススルー
デフォルトタイムアウトの使用 チェックをつける

ここでのポイントはアクションの GetItem と HTTP メソッドの POST です。これらの情報は API リファレンスから得ることができます。
アクションは API 名になります。HTTP メソッドは当該の API リファレンスを参照することで得られますが、一番手っ取り早いのは Examples を見る事です。例えば DynamoDB の GetItem の Examples を見れば HTTP メソッドがPOSTであることが判明します。

ロールの作成

後日、改めて詳細。ポイントだけ。

  • 信頼されたエンティティのID プロバイダーを apigateway.amazonaws.com
  • ポリシーでAmazon DynamoDB の GetItem に Allow されていること

統合リクエストの設定

統合リクエストでは、クライアントからのリクエストをDynamoDBのGetItem APIリクエストに変換します。

マッピングテンプレートで以下のように設定します。

項目 設定内容
リクエスト本文のパススルー なし

マッピングテンプレートの追加で Content-Type に application/json を入力、その後表示されるテキストエリアに以下を入力して保存します。

マッピングテンプレート
{
  "TableName": "TABLE1",
  "Key": {
    "iid": {
      "S": "$input.params('iid')"
    }
  }
}

これは先ほど設定した「アクション(= API)」のリクエストボディです。
そのため、例えば Scanなら、以下のような形になります。

Scanの場合
{
  "TableName": "TABLE1",
  "Limit": 10
}

マッピングテンプレートの開発TIPS

マッピングテンプレートには、テンプレートエンジンであるApache Velocityが利用されています。それに加えて、API Gatewayで独自に設定されている変数を組み合わせてJSONを構築していきます。

クライアントからのリクエストの中身を見る方法は「テスト」を利用します。以下のように入力をしてテストをすると "Method request" に値が表示されます。これをマッピングテンプレートで読み出します。
読み出し方は $input.params('NAME') です。パスとQueryStringとで名前が競合した場合はパス側が優先されます。詳しくは $input変数を参照ください。

DynamoDB 内に Item が存在していれば、テスト結果は以下のようになります。

image.png

ここまでくれば、リクエスト側は完了です。

統合レスポンスの設定

統合レスポンスでは、DynamoDBからのレスポンスをクライアントに返すための整形を行います。

統合レスポンスを開くとすでに "メソッドレスポンスのステータス" = 200 の行があるので、その行を開いた後にマッピングテンプレートを開きます。その中の application/json をクリックすると表示されるテキストエリアに以下を入力して保存します。

マッピングテンプレート
{
  "iid": "$input.path('$').Item.iid.S",
  "timestamp": $input.path('$').Item.timestamp.N
  "payloads": {
    "clickType": $input.path('$').Item.payloads.M.clickType.N,
    "clickTypeName": "$input.path('$').Item.payloads.M.clickTypeName.S",
    "batteryLevel": $input.path('$').Item.payloads.M.batteryLevel.N,
  }
}

この状態でテストすると、レスポンス本文が以下のように変化します。今までは何も入力しない = DynamoDB からのレスポンスをパススルーしていたのが、マッピングテンプレートによって整形されたわけです。

image.png

統合レスポンスにおける $input

統合レスポンスでの $input は、DynamoDB からのレスポンスです。$input.path('$') とすれば、それ以降はJSONのパスを直接指定できます。

何度も書くのは面倒なのでApache Velocity(VTL; Velocity Template Language)の set を使って、変数に入れることができます。

setで簡略化
#set($inputRoot = $input.path('$'))
{
  "iid": "$inputRoot.Item.iid.S",
  "timestamp": $inputRoot.Item.timestamp.N
  "payloads": {
    "clickType": $inputRoot.Item.payloads.M.clickType.N,
    "clickTypeName": "$inputRoot.Item.payloads.M.clickTypeName.S",
    "batteryLevel": $inputRoot.Item.payloads.M.batteryLevel.N,
  }
}

繰り返しには foreach を使う方法もあります。VLTAPI Gatewayの変数リファレンスを見ながら、サンプルを見ることで理解が深まるかと思います。

VLTの if で 404 対応をしてみる

このマッピングテンプレートだと Item が存在しなかった場合、以下のようにキーだけが存在するJSONとなってしまうため、返す内容を変更します。
image.png

404対応マッピングテンプレート
#if($input.path('$') == "{}")
{}
#set($context.responseOverride.status = 404)
#else
{
  "iid": "$input.path('$').Item.iid.S",
  "timestamp": $input.path('$').Item.timestamp.N
  "payloads": {
    "clickType": $input.path('$').Item.payloads.M.clickType.N,
    "clickTypeName": "$input.path('$').Item.payloads.M.clickTypeName.S",
    "batteryLevel": $input.path('$').Item.payloads.M.batteryLevel.N,
  }
}
#end

Itemが存在しなかった場合、DynamoDBからは {} という文字が送られてきます。これを if で判定し、{} であれば、改めて {} という文字を返し、同時に HTTP レスポンスコードも 404 に変更しています。

テストしてみるとレスポンス本文は {} で、HTTP レスポンスコード(ステータス)も404となりました。
image.png

curl からの実行例

デプロイ後にcurlからは以下のように実行できるようになります。

$ curl -v https://XXXXX.execute-api.REGION.amazonaws.com/prod/items/a45f4a4998a
< HTTP/2 200
{
  "iid": "a45f4a4998a",
  "timestamp": 1610886990671
  "payloads": {
    "clickType": 1,
    "clickTypeName": "SINGLE",
    "batteryLevel": 0.75,
  }
}

$ curl -v https://XXXXX.execute-api.REGION.amazonaws.com/prod/items/bar
< HTTP/2 404
{}

まとめと、この後

クライアントからのリクエスト、そして、AWSサービスからのレスポンスが単純であればLambdaを使わずともAPI化が可能です。DynamoDBを例として挙げましたが、基本的にはどのサービスでも対応可能です。

一方で、例えば処理中に他の情報を参照したり、複雑な変換をする場合はLambda統合が有利にもなるため、両方を試しておいて目的に応じて使い分けましょう。Lambda統合への切り替えも容易ですので、API化の第一歩として知っておいて損は無いのではないでしょうか。

アクセス制御を忘れずに

今後ですが、このままですとAPIエンドポイントが無防備です。API Gateway での REST API へのアクセスの制御と管理に沿って、いずれかのアクセス制御を行いましょう。

参考資料

EoT

13
5
2

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
13
5