AWS AppSync リゾルバ (VTL) の書き方サンプル No.1 – Amazon DynamoDB GetItem

こんにちは、広野です。

AWS AppSync を使用したアプリケーションを開発する機会があり、リゾルバ、主に VTL の書き方に関してまとまった知識が得られたので紹介します。

本記事では、VTL の書き方にフォーカスしています。ご了承ください。

AWS AppSync とは

AWS AppSync はサーバーレスアプリケーションのバックエンドを作成するときに使用するサービスです。GraphQL というクエリ言語を使用してアプリケーションからリクエストします。AWS AppSync が受けたリクエストを処理するためにリゾルバという設定があり、その中で VTL (Apache Velocity Template Language) という言語でコードを書きます。同じ目的で Amazon API Gateway と AWS Lambda を使用する構成の方がメジャーですよね。ここでは、AWS AppSync = Amazon API Gateway、リゾルバ = AWS Lambda と置き換えてもらえたらわかりやすいと思います。

AWS AppSync には Amazon API Gateway + AWS Lambda の構成と比較して、以下の利点があります。(ChatGPT に聞いてみた)

  • パフォーマンス
    VTL は AWS AppSync 内で実行されるため、AWS Lambda 関数を呼び出すよりもレイテンシーが低い。サブスクリプションの機能をデフォルトで備えていることも含め、リアルタイム性が高いアプリケーションにおいて優位性がある。
  • コスト効率
    VTL は AWS AppSync の一部として実行されるため、追加のコストがかからない。
  • スケーラビリティ
    AWS AppSync と VTL は自動的にスケーリングされる。AWS Lambda 関数は同時実行数の制約がある。

ただし、AWS AppSync のデメリットもあります。

  • VTL は低機能なため、AWS Lambda 関数で書いていたこと全てを実現できるわけではない。
    → JavaScript で書けるようにもなっているので、それにより解決していると思われる。(本記事では紹介しない)
    また、本記事では本末転倒ではあるが、AWS AppSync と AWS Lambda を統合することも一般的に行われている。
  • AWS AppSync を使用するために必要な GraphQL が難解で、そもそも AWS AppSync に手を出せない。w

私の実感を加味してまとめると

・AWS AppSync のレスポンスは高速。
・しかし裏で使われるリゾルバ (VTL) でできることが少ない。簡易な CRUD 処理であれば実装可。
・GraphQL も勉強しないといけない。学習コスト高い。

リゾルバとは

リゾルバは、AWS AppSync がアプリケーションから受け取った GraphQL クエリによるリクエストから、引数を取得してバックエンドにあるデータソース(データベース等)とのやり取りを仲介する役割を持ちます。Amazon API Gateway と Amazon DynamoDB の構成と比較すると以下のようになると思えばよいでしょう。

  • Amazon API Gateway → AWS Lambda → Amazon DynamoDB
  • AWS AppSync → リゾルバ → Amazon DynamoDB

1つの Amazon API Gateway が複数の AWS Lambda と統合できるのと同じように、1つの AWS AppSync は複数のリゾルバを持つことができます。

しかし、注意しないといけないのは、基本、1つのリゾルバは 1つのデータソースしか統合できません。AWS Lambda 関数であれば、関数コード内で自由に複数のデータソースとのやり取りを書くことができると思いますが、そのような自由度はリゾルバにはありません。
※リゾルバの書き方によっては一部例外はあります。後ほど紹介します。

リゾルバでは VTL という言語で以下 2つの処理を定義します。

  • リクエストマッピングテンプレート
  • レスポンスマッピングテンプレート

役割としては

  • リクエストマッピングテンプレートは、AWS AppSync が受けたリクエストの引数を受け取り、バックエンドに処理をさせるコードに置き換える。(マッピングする)
    バックエンドが Amazon DynamoDB であればそれ用の記述を、Amazon Aurora であればまたそれ用の記述になる。アプリケーション側ではバックエンドの違いを気にせず同じクエリでデータを取得することができる。(これは GraphQL のメリット)
  • レスポンスマッピングテンプレートは、バックエンドが返してきたレスポンスを加工し、アプリケーションが期待するフォーマットに置き換える。(マッピングする)

以降、Amazon DynamoDB から 1つのアイテムだけを取得する VTL のサンプルを紹介します。今後、シリーズものとしていくつか他のサンプルも用意するつもりです。

Amazon DynamoDB に GetItem する VTL

例えば、AWS AppSync から以下のリクエストを受けたとします。Amazon DynamoDB には適切なデータがある想定です。テーブル名はリゾルバの別の設定で行います。

  • 引数となるパラメータ: パーティションキー pkey、ソートキー skey
  • 必要なレスポンス: data1, data2 という属性の値

マッピングテンプレートは JSON 形式で記述します。その中に VTL が混在する感じです。わかりにくい。

リクエストマッピングテンプレート

{
  "version": "2018-05-29",
  "operation" : "GetItem",
  "key": {
    "pkey": $util.dynamodb.toDynamoDBJson($context.arguments.pkey),
    "skey": $util.dynamodb.toDynamoDBJson($context.arguments.skey)
  },
  "consistentRead" : false,
  "projection": {
    "expression": "#data1, #data2",
    "expressionNames": {
      "#data1": "data1",
      "#data2": "data2"
    }
  }
}

operation には、GetItem を書きます。これは Amazon DynamoDB に GetItem をするぞ、という意思表示です。

key には、受け取った引数を書きます。ここでは、pkey, skey というキーに対して受け取った引数 $context.arguments.pkey と $context.arguments.skey を指定していると思って下さい。受け取った引数はマッピングテンプレート内では $context.arguments 内に格納されるので、この記述のように取り出すことができます。

$util.dynamodb.toDynamoDBJson で囲んでいる理由ですが、Amazon DynamoDB にキーを渡すときに特有の JSON フォーマットにする必要があるため、それをいちいち手書きしなくてもよいようにしてくれます。手書きだと、例ですが “pkey” : { “S” : $util.toJson($context.arguments.pkey) } と書かないといけないことになります。

consistentRead は、結果整合性でよいか、強整合性が必要かを指定します。

projection は、Amazon DynamoDB から受け取りたい属性を指定します。expressionNames では、例えば data1 という属性名を内部的に #data1 という名前に置き換えて、expression で指定しています。これは、Amazon DynamoDB の予約語や AWS AppSync の禁止文字列などが属性名に使われているときにエラーを回避するお作法です。ちなみに projection の項目を書かなければそのアイテムの全属性を取得します。

これで Amazon DynamoDB に GetItem をかけることができます。

ドキュメントによっては $context.arguments を $ctx.args と書いてあるサンプルもあります。私の経験上ですが、どちらでも同じ動きをするようです。

レスポンスマッピングテンプレート

#if(!$context.result || $context.result.isEmpty())
  $util.toJson({
    "data1": "no data",
    "data2": "no data"
  })
#else
  $util.toJson($context.result)
#end

超基本は、$util.toJson($context.result) を 1行書くだけで成り立ちます。$context.result に Amazon DynamoDB から返ってきたデータが格納されます。気を付けないといけないのは、$util.toJson で囲まないといけないことです。実はマッピングテンプレート内部では、$context.result が JSON フォーマットで書かれていないからです。それを JSON に Parse してくれるのが $util.toJson なのです。

このサンプルで少々手を入れているのは、データが無かったときは data1 と data2 に no data というテキストを入れて返すようにしていることです。$context.result と同じ JSON フォーマットに合わせており、受け取るアプリ側はデータがあろうが無かろうが同じフォーマットでレスポンスを受け取れるようにしています。※すみません、エラーのときの処理は入れていません。

レスポンスマッピングテンプレートでは、受け取ったレスポンス $context.result の内容に応じて、if 文でデータを加工することができます。if 構文を書くときには、その構文の先頭に必ず # を入れます。最後には #end で閉じます。if 文のネストも可能です。

任意の JSON データをレスポンスとして返すときも、必ず $util.toJson で囲むようにしましょう。ドキュメントによっては $util を $utils と書いているサンプルがありますが、私の経験上はどちらでも同じように動いています。

VTL に関しては以下の AWS 公式ドキュメントも必要に応じてご確認ください。

まとめ

いかがでしたでしょうか。

Amazon DynamoDB への CRUD 処理の基本中の基本とも言える GetItem ですが、すでにおなかいっぱいなのではないかと思います。ですが、一度 VTL がどんなものか理解できれば、そうでもなく感じてしまうので不思議です。今後他の VTL パターンも紹介しますね。お楽しみに。

本記事が皆様のお役に立てれば幸いです。

著者について
広野 祐司

AWS サーバーレスアーキテクチャを駆使して社内クラウド人材育成アプリとコンテンツづくりに勤しんでいます。React で SPA を書き始めたら快適すぎて、他の言語には戻れなくなりました。サーバーレス & React 仲間を増やしたいです。AWSは好きですが、それよりもAWSすげー!って気持ちの方が強いです。
取得資格:AWS 認定は12資格、ITサービスマネージャ、ITIL v3 Expert 等
2020 - 2023 Japan AWS Top Engineer 受賞
2022 - 2023 Japan AWS Ambassador 受賞
2023 当社初代フルスタックエンジニア認定
好きなAWSサービス:AWS Amplify / AWS AppSync / Amazon Cognito / AWS Step Functions / AWS CloudFormation

広野 祐司をフォローする
クラウドに強いによるエンジニアブログです。
SCSKは専門性と豊富な実績を活かしたクラウドサービス USiZE(ユーサイズ)を提供しています。
USiZEサービスサイトでは、お客様のDX推進をワンストップで支援するサービスの詳細や導入事例を紹介しています。
AWSアプリケーション開発クラウドソリューションデータベース
シェアする
タイトルとURLをコピーしました