はじめに
Serverless Framework+AppSync各種プラグインによる開発を進めているのですが、一つのoperationフィールドを追加するだけで割と多くの操作が必要になってくることに気付きました。
個人的に苦手なマッピングテンプレートに工数を割くのが嫌なので、マッピングテンプレートは可能な限り避けて、JavaScript/TypeScript側で操作できる方法をご紹介します。
必要なもの
Serverless Frameworkテンプレートとして以下のものを使わせていただきました。
[daisuke-awaji/serverless-appsync-offline-typescript-template]
解説: Effective AppSync 〜 Serverless Framework を使用した AppSync の実践的な開発方法とテスト戦略 〜
また事前に以下もインストールが必要です。
- JDK(DynamoDB-localに必要)
- Serverless Framework
GraphQL operation
Operationというのは、API Gatewayでいうリソースに相当する(と思う)ものです。
たとえばTodoリストを登録したり表示したりするAPIを作ろうとするとき、Todoのリスト一覧を返してくれる「listTodo」のようなQueryフィールドが必要になります。このフィールドの追加、および実際に処理を実行するLambda関数との紐付けるは、すべてServerless Frameworkの設定ファイルserverless.yml
に記述することになります
一例として以下のようになります。
custom:
appSync:
# ローカルではこの認証方式でないと動かない……
authenticationType: AMAZON_COGNITO_USER_POOLS
userPoolConfig:
awsRegion: ap-northeast-1
userPoolId: ap-northeast-1_xxxxx
defaultAction: ALLOW
mappingTemplates:
- field: {フィールド名}
datasource: {dataSources.nameで定義された名前}
request: default.request.vtl
response: default.response.vtl
dataSources:
- type: AWS_Lambda
name: {dataSources名}
description: Lambda
config:
functionName: {Lambda関数名}
iamRoleStatements:
- Effect: "Allow"
Action:
- "lambda:invokeFunction"
Resource:
- "*"
functions:
{Lambda関数名}:
handler: src/handler.main
以上のうち、フィールドを増やすにはmappingTemplates
という箇所と、Lambda関数のファイルであるsrc/handler.main
を変更します。
Queryフィールド追加の一例
例えばTodoのIDを指定して特定Todoだけを抽出するQueryフィールドgetTodoById(id: String)
を追加したい、という場合を考えてみます。
まずスキーマを定義します。定義はschema.graphql
のQueryタイプに定義を追加します。
type Todo {
id: ID!
name: String!
completed: Boolean!
}
type Query {
getTodoById(id: String!): Todo
}
次に、serverless.ymlのmappingTemplatesに追加をします。dataSources名はds_mainとしています。
mappingTemplates:
- field: getTodoById
datasource: ds_main
request: default.request.vtl
response: default.response.vtl
dataSources:
- type: AWS_LAMBDA
name: ds_main
description: Lambda DataSource for application
config:
functionName: dsMain
iamRoleStatements:
- Effect: "Allow"
Action:
- "lambda:invokeFunction"
Resource:
- "*"
functions:
dsMain:
handler: src/handler.handler
ハンドラー自体は、src/handler.ts
として保存しています。
あとはLambda関数のコードで、event.info.fieldName
でQueryフィールド名getTodoById
を取得できるので、switch構文などで呼び出すTypeScriptコードを呼び出す、ということができます。
src/handler.ts
import getTodoById from './getTodoById';
exports.handler = async(event, _context, _callback)=> {
switch (event.info.fieldName) {
case "getTodoById":
return await getTodoById(event.arguments.id);
default:
return null;
}
}
上記コードで呼び出されるgetTodoById.tsの中身は例えば以下のような感じです。
const AWS = require('aws-sdk');
AWS.config.update({region:'ap-northeast-1'});
const docClient = new AWS.DynamoDB.DocumentClient();
async function getTodoById(id: String) {
const params = {
TableName: process.env.TODO_TABLE,
Key: { id }
}
try {
const { Item } = await docClient.get(params).promise()
return Item
} catch (err) {
console.log('DynamoDB error: ', err)
}
}
export default getTodoById
マッピングテンプレート
AppSyncではリゾルバをマッピングテンプレートとして書きます
マッピングテンプレートはVTLで書きますが、個人的にはこの言語をいじるのは避けたいので適当なファイル名default.request.vtl
などを作ってこの2つだけを使用することにします。
mapping-templates/default.request.vtl
{
"version" : "2017-02-28",
"operation": "Invoke",
"payload": $utils.toJson($context)
}
またレスポンス用のマッピングテンプレートは以下の通りです。
mapping-templates/default.response.vtl
$util.toJson($context.result)
これで一つのフィールドを追加することができます。
マッピングテンプレートは使い回すので、基本的には
- schema.graphqlにフィールドを追加
- serverless.ymlにフィールドを追加
- Lambda用ファイルを追加・修正
の3つを行う必要があります。
終わりに
マッピングテンプレートを書くべきかどうかは開発規模によると思うので、適宜よい方法を見つけることをおすすめします。