AppSync向けのDynamoDBのマッピング定義(VTL:Velocity Template Language)をローカルで検証する

「なんのこっちゃ?」という人のほうが多そうですが、伝わる人に伝われば良いのです。 VTLとは AWSでAPI GatewayやAppSyncを触りだすとどうしても仲良くならざるを得ない存在です。ようはPugやJade・T […]

広告ここから
広告ここまで

目次

    「なんのこっちゃ?」という人のほうが多そうですが、伝わる人に伝われば良いのです。

    VTLとは

    AWSでAPI GatewayやAppSyncを触りだすとどうしても仲良くならざるを得ない存在です。ようはPugやJade・Twigのようなテンプレート言語で、AWSではリクエストやレスポンスの値をマッピングするのに使われています。

    AppSyncでの使い所

    DynamoDBをリソースに使う場合、AppSyncが受け取るGraphQLのクエリをDynamoDBのクエリに書き換える必要があります。ここの値の受け渡しにVTLが使われています。

    たとえばuser_nameでDynamoDBをクエリする想定で、こんなクエリを定義するとします。

    type Query {
      queryDevelopmentsByUserNameIndex(
    		user_name: String!,
    		first: Int,
    		after: String
      ): DevelopmentConnection
    }

    これをDynamoDBのクエリに投げるために、以下のようなVTLが必要となります。

    {
            "version": "2017-02-28",
            "operation": "Query",
            "query": {
              "expression": "#user_name = :user_name",
              "expressionNames": {
                "#user_name": "user_name"
              },
              "expressionValues": {
                ":user_name": $util.dynamodb.toDynamoDBJson($ctx.args.user_name)
              }
            },
            "index": "user_name-index",
            "limit": $util.defaultIfNull($ctx.args.first, 20),
            "nextToken": $util.toJson($util.defaultIfNullOrEmpty($ctx.args.after, null)),
            "scanIndexForward": true,
            "select": "ALL_ATTRIBUTES",
          }

    「うげっ」となるかもしれませんが、よくよく見ていくと普通にDynamoDBのクエリを書いているだけです。

    VTLのデバッグがしたい

    で、このVTLがちゃんと書けているかどうかはだいたいApp Syncのコンソールから確認することになります。が、Serverless / CloudFormation / AWS CDK / Terraformあたりで管理したい身としては、わざわざマネージメントコンソールを開きたくないわけです。

    「そもそもVTLはテンプレート言語なのだから、ローカルでレンダリングできればテストできるのでは?」ということで、さっそく試してみました。

    レンダリングのためのライブラリを追加

    レンダリングはvelocityjsというライブラリを使うことでできそうです。また、DynamoDBのクエリを実行することから、AWS SDKも入れておきましょう。

    $ mkdir vtl-test && cd vtl-test
    $ npm init -y
    $ npm i -S velocityjs aws-sdk
    $ vim index.js

    そして先程のVTLをjsファイルに文字列の変数として定義します。

    const vtl = `
    {
            "version": "2017-02-28",
            "operation": "Query",
            "query": {
              "expression": "#user_name = :user_name",
              "expressionNames": {
                "#user_name": "user_name"
              },
              "expressionValues": {
                ":user_name": $util.dynamodb.toDynamoDBJson($ctx.args.user_name)
              }
            },
            "index": "user_name-index",
            "limit": $util.defaultIfNull($ctx.args.first, 20),
            "nextToken": $util.toJson($util.defaultIfNullOrEmpty($ctx.args.after, null)),
            "scanIndexForward": true,
            "select": "ALL_ATTRIBUTES",
          }
    `

    続いて$util.defaultIfNullなどのVTL関数をまとめたオブジェクトを用意します。それぞれの関数が何をしているかは、ドキュメントを見ながらやるとよいでしょう。

    const appSyncContext = {
      ctx: {
        args: {
          user_name: 'hoge',
          first: undefined,
          after: null
        }
      },
      util: {
        toJson: (obj) => {
          return JSON.parse(obj)
        },
        defaultIfNull: (obj, def) => {
          if (obj && obj !== null) return arg
          return def
        },
        defaultIfNullOrEmpty: (str, def) => {
          if (str) return str
          return  def
        },
        dynamodb: {
          toDynamoDBJson: (str) => {
            return JSON.stringify(str)
          }
        }
      }
    }

    そしてvelocityjsで変換します。

    const Velocity = require('velocityjs')
    const query =JSON.parse(Velocity.render(vtl, appSyncContext))

    これでJSから操作できるようになりました。あとはDynamoDBのクエリパラメーターに値を入れていくことで、VTLに基づいたクエリを実行できます。

    const { DynamoDB } = require('aws-sdk')
    const client = new DynamoDB.DocumentClient()
    
    const param = {
      TableName: 'Development',
      IndexName: query.index,
      Select: query.select,
      Limit: query.limit,
      ScanIndexForward: query.scanIndexForward,
      KeyConditionExpression: query.query.expression,
      ExpressionAttributeNames: query.query.expressionNames,
      ExpressionAttributeValues: query.query.expressionValues
    }
    
    client.query(param).promise()
      .then(r => console.log(r))
      .catch(e => console.error(e))

    使い所

    vtlをjs / tsファイルで管理することで、上記のようなローカルテストを行いつつ、AWS CDKでresolverのパラメーターにも利用できます。DynamoDBについてもdynamodb-localを使うようにすれば、より高速でテストが可能になるでしょう。

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark