Amplify AppSync Simulator を直接使ってマッピングテンプレートを検証
Amplify AppSync Simulator は、AWS Amplify CLI に含まれているモジュールで、AppSync をローカル環境で動作確認するためのものです。(AppSync の GraphQL を処理する Web サーバーが起動するようになっている)
ソースコードを見てみたところ、AppSync 用の処理を適用した GraphQL.js の GraphQLSchema
を作り、これを 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 の処理を定義するようになっています。
※ リゾルバーは、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
関数で処理する際のコンテキストに requestAuthorizationMode
と appsyncErrors
の項目が最低限必要でした。
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}}}