AppSync + GraphQL + DynamoDBでのVTLテンプレートHowTo

3 min読了の目安(約3100字TECH技術記事

はじめに

AppSync + GraphQL をデータソースDynamoDBに対するVTLについて、GraphQLのSchemaやDynamoDBの設計をふまえ紹介します。
案外実際のユースケースから探しても出てこなく、VTLの記法をキャッチアップしつつ実装必要がありました。

DynamoDBのリゾルバーのテンプレートリファレンスはこちらです。

随時これは有益かもしれないと思った情報を追記していこうと思います。

テンプレート上で文字を結合する

よくDynamoDBでは PK SK というKey名に対して、様々な値を保存する設計があります。

  • PK: パーティションキー
  • SK: ソートキー

たとえば PK には、 user-{uuidv4}message-{uuidv4} など、そのドキュメントに保存されているデータの名称をprefixとしておいて- のあとにUUIDv4などのIDをつなげて保存することがあります。

ex)

PK SK data
user-xxxx user-name Test Taro
message-xxxx message-content Sample message.

こうすることで、 PK = user-xxxx AND SK = user-name で、 user-xxxx のユーザー名を取得できたりします。
DynamoDBの設計については、以下の記事が非常に参考になりました。

https://mizumotok.hatenablog.jp/entry/2019/08/14/175525

Schemaとしては以下のような形を想定しています。

schema {
  query: Query
}

type Query {
  getUser(userId: ID!): User
}

type User {
  id: ID!
}

この場合 QuerygetUser に渡す引数はどうするべきでしょか。
単純にprefixの user- をつなげた状態のIDを投げればよいのですが、今回はリクエストマッピングテンプレートで文字列結合をしてあげて、
アプリケーションではUUIDv4のみを取り扱えるようにします。

{
  "version": "2017-02-28",
  "operation": "GetItem",
  "key": {
    "pk": $util.dynamodb.toDynamoDBJson("user-${ctx.args.userId}"),
  }
}

$util.dynamodb.toDynamoDBJson("user-${ctx.args.userId}") の部分で、
toDynamoDBJson()の引数で "user-${ctx.args.userId}" のようにしてあげると、文字列が結合されます。

レスポンスを整形する

次に、レスポンスについて考えます。

今回DynamoDBでは PK というKey名に user-{uuidv4} という形の値が入っています。
これをSchemaに合わせて、

  • PK → id にKey名を変更したい。
  • user-{uuidv4}{uuidv4} のみにしたい。

というのを実装します。

レスポンスマッピングテンプレートは以下のようになります。

#set($ctx.result.id = $ctx.PK.replace('user-', ''))
$util.toJson($ctx.result)
  1. #set($ctx.result.id = $ctx.result.PK.replace('user-', '')) result.id というkeyに対して、 user- を取り除いた状態の PK を代入します。
  2. $util.toJson($ctx.result) で結果を出力します。

複数レコードの場合

複数取得するような Query の場合、リクエストマッピングテンプレートは以下のようになります。

{
  "version": "2017-02-28",
  "operation": "Query",
  "query": {
    "expression": "PK = :PK",
    "expressionValues": {
      ":PK": $util.dynamodb.toDynamoDBJson("user-${ctx.args.userId}")
    }
  },
  "limit": $util.defaultIfNull(${ctx.args.limit}, 20),
  "nextToken": $util.toJson($util.defaultIfNullOrBlank($ctx.args.nextToken, null))
}

"expressionValues"$util.dynamodb.toDynamoDBJson("user-${ctx.args.userId}") をすることで、文字列結合をしています。

レスポンスマッピングテンプレートは以下のようにします。

#set($responseItems = [])
#foreach($item in $ctx.result.items)
    #set($item.id = $item.PK.replace('user-', ''))
    #set($added = $responseItems.add($item))
#end
{
  "items": $util.toJson($responseItems),
  "nextToken": $util.toJson($ctx.result.nextToken)
}
  1. #set($responseItems = []) レスポンス用の配列を定義します。
  2. #foreach($item in $ctx.result.items) ... #end DynamoDBから返ってきた結果の配列をループします。
  3. #set($item.id = $item.PK.replace('user-', '')) $item.id というkeyに対して、 user- を取り除いた状態の PK を代入します。
  4. 最後に JSON を出力します。 ※ "nextToken" はページネーションのための出力

まとめ

VTL結構辛いですがテンプレートで済ませられるところと、Lambdaを使うところをうまく使い分けてあげると良さそうかな?と個人的には思っています。