Amplify AppSync Simulator を直接使ってマッピングテンプレートを検証

Amplify AppSync Simulator は、AWS Amplify CLI に含まれているモジュールで、AppSync をローカル環境で動作確認するためのものです。(AppSync の GraphQL を処理する Web サーバーが起動するようになっている)

ソースコードを見てみたところ、AppSync 用の処理を適用した GraphQL.jsGraphQLSchema を作り、これを graphql 関数で実行するようになっていました。

そこで試しに、Amplify AppSync Simulator の API を直接使う事で、Amplify CLI を使わず Web サーバーも起動せずに、リクエスマッピングテンプレートの処理結果を出力する Node.js スクリプトを作ってみました。

今回のソースは http://github.com/fits/try_samples/tree/master/blog/20210315/

準備

amplify-appsync-simulator モジュールをインストールしておきます。

インストール例
> npm install amplify-appsync-simulator

Lambda 用マッピングテンプレートの処理

今回は、Lambda 用のマッピングテンプレートを処理してみます。

AppSync では下記を設定する事で GraphQL の処理を定義するようになっています。

  • (1) GraphQL スキーマから GraphQL API を作成
  • (2) データソース(実際の処理を行う部分)を追加
  • (3) リゾルバー ※ を作成
 ※ リゾルバーは、GraphQL API とデータソースとの紐づけ、
    入出力データの加工をマッピングテンプレートとして設定するようになっています

マッピングテンプレートは、VTL(Apache Velocity Template Language)の形式でリクエストやレスポンスの内容を加工して JSON 文字列を作ります。

Amplify AppSync Simulator では AmplifyAppSyncSimulator クラスの init メソッドに対して、上記 (1) ~ (3) と defaultAuthenticationType を設定した AmplifyAppSyncSimulatorConfig を渡す事で、AppSync 用の処理を適用した GraphQLSchema を内部的に作るようになっており、それを schema で取得する事が可能です。

defaultAuthenticationType の設定は、今回のように GraphQLSchema を直接利用するケースでは使われませんが、設定自体は必須のようです。

また、Amplify AppSync Simulator が対応しているデータソースは今のところ下記 3タイプのようで、タイプ毎に用意されている DataLoader が処理を担うようになっています。

各 DataLoader の load メソッドの第一引数にリクエスマッピングテンプレートの処理結果がそのまま渡ってくるようになっています。

NONE データソースの利用

NONE タイプ用 NoneDataLoader の load メソッドは、単純に payload の値を処理結果として返すような実装になっています。

そこで、この load メソッドを上書きする事でリクエスマッピングテンプレートの結果を出力するようにしてみました。

なお、AmplifyAppSyncSimulator が作成した GraphQLSchema を graphql 関数で処理する際のコンテキストに requestAuthorizationModeappsyncErrors の項目が最低限必要でした。

defaultAuthenticationType は必須のようなので API_KEY を設定していますが、実際には使われないので API_KEY の値を設定したりする必要はありませんでした。

sample1.js
const { AmplifyAppSyncSimulator } = require('amplify-appsync-simulator')
const { graphql } = require('graphql')
// GraphQL スキーマ定義
const schema = `
    type Item {
        id: ID!
        value: Int!
    }

    input FindInput {
        id: ID!
    }

    type Query {
        find(input: FindInput!): Item
    }
`
// データソースの設定
const dataSources = [
    { type: 'NONE', name: 'ItemFunc' }
]
// リゾルバーの設定
const resolvers = [
    {
        kind: 'UNIT', 
        // GraphQL の Query で定義した find フィールドと ItemFunc データソースとのマッピング
        typeName: 'Query', fieldName: 'find',
        dataSourceName: 'ItemFunc'
        // リクエストマッピングテンプレートの設定(GraphQL クエリの input をそのまま payload に設定)
        requestMappingTemplate: `
            {
                "version": "2018-05-29",
                "operation": "Invoke",
                "payload": $utils.toJson($ctx.args.input)
            }
        `,
        // レスポンスマッピングテンプレートの設定
        responseMappingTemplate: '$utils.toJson($context.result)'
    }
]
// 設定
const config = {
    schema: { content: schema },
    resolvers,
    dataSources,
    appSync: {
        // 下記の設定は必須
        defaultAuthenticationType: {
            authenticationType: 'API_KEY'
        }
    }
}

const simulator = new AmplifyAppSyncSimulator()

simulator.init(config)

// ItemFunc(NoneDataLoader)の load メソッド上書き
simulator.getDataLoader('ItemFunc').load = (req) => {
    console.log(`*** load: ${JSON.stringify(req)}`)

    return { id: req.payload.id, value: 123 }
}
// AmplifyAppSyncSimulator用 GraphQL の実行コンテキスト
const ctx = {
    requestAuthorizationMode: 'API_KEY',
    appsyncErrors: []
}
// GraphQL クエリ
const q = `
    query FindItem($id: ID!) {
        find(input: {id: $id}) {
            id
            value
        }
    }
`

const run = async () => {
    // GraphQL クエリ実行
    const r = await graphql(simulator.schema, q, null, ctx, {id: 'id1'})
    console.log(JSON.stringify(r))
}

run().catch(err => console.error(err))

実行結果は以下の通り。 リクエスマッピングテンプレートの処理結果が出力されています。

実行結果
> node sample1.js
*** load: {"version":"2018-05-29","operation":"Invoke","payload":{"id":"id1"}}
{"data":{"find":{"id":"id1","value":123}}}

AWS_LAMBDA データソースの利用

AWS_LAMBDA タイプの場合は、データソース設定の invoke へ設定した関数が呼び出されるようになっているので ※、これを使って Lambda の関数ハンドラーを直接実行する事もできます。

 ※ リクエストマッピングテンプレート処理結果の
    payload の値が引数として与えられるようになっています
lambda_func.js(関数ハンドラー)
exports.handler = async (event) => {
    console.log(`*** handler: ${JSON.stringify(event)}`)

    return { id: event.id, value: 234 }
}
sample2.js
const { AmplifyAppSyncSimulator } = require('amplify-appsync-simulator')
const { graphql } = require('graphql')

const schema = `
    ・・・
`

const dataSources = [
    // invoke へ lambda_func.js の handler を設定
    { type: 'AWS_LAMBDA', name: 'ItemFunc', invoke: require('./lambda_func.js').handler }
]

const resolvers = [
    ・・・
]

const config = {
    schema: { content: schema },
    resolvers,
    dataSources,
    appSync: {
        defaultAuthenticationType: {
            authenticationType: 'API_KEY'
        }
    }
}

const simulator = new AmplifyAppSyncSimulator()

simulator.init(config)

const ctx = {
    requestAuthorizationMode: 'API_KEY',
    appsyncErrors: []
}

const q = `
    query FindItem($id: ID!) {
        find(input: {id: $id}) {
            id
            value
        }
    }
`

const run = async () => {
    const r = await graphql(simulator.schema, q, null, ctx, {id: 'id2'})
    console.log(JSON.stringify(r))
}

run().catch(err => console.error(err))
実行結果
> node sample2.js
*** handler: {"id":"id2"}
{"data":{"find":{"id":"id2","value":234}}}