はじめに
今日はGWアドベントカレンダーの7日目です。今回は番外編ということでAppSyncからLambdaを呼び出す実装について書いていきます。
Lambdaを使いたい理由
本当は面倒な実装をやりたくないのですが、どうしても自分でLambdaを書かないといけない場合も存在するようです。このシリーズではItem
という型のスキーマを最初に定義しましたが、例えばそこにcategory
という属性を足したいとします。最初に書いたスキーマを以下のように書き換えます。
enum Category {
HOGEHOGE
HUGAHUGA
}
type Item {
id: String!
data: String!
category: Category!
}
type Query {
allItem: [Item]
allItemOnCategory(category: Category): [Item]
}
input ItemInput {
data: String!
category: Category!
}
type Mutation {
addItem(item: ItemInput!): Item
}
Item
にcategory
というenumで定義した値が追加されました。また、allItemOnCategory
を実行するとカテゴリ別でデータを取得させるようにスキーマを定義しました。
ここでid
は必ず一意な値として必要なのでプライマリキーとして、category
をソートキーとして定義します。ここまでは特に困ることはないのですが、このallItemOnCategory
のリゾルバーを実装するにはプライマリキーではなくソートキーから値を引っ張ってくる必要があるのですが、調べたところAppSyncのDynamoDBへのマッピングテンプレートは現在プライマリキーのみしかサポートされていないようです(わたしの勘違いかもしれないのでその場合はご指摘ください。。。)。
なので少し複雑なデータ操作をする場合は自分でLambdaを書くしかなさそうです。
テーブルの定義の拡張
category
の値をプライマリキーのように扱いたいので今回はグローバルセカンダリインデックスをしようします。こんな感じでcdkを書き換えました。
const itemTable = new Table(this, 'itemTable', {
partitionKey: {
name: 'id',
type: AttributeType.STRING,
},
sortKey: {
name: 'category'
type: AttributeType.STRING,
}
})
itemTable.addGlobalSecondaryIndex({
indexName: 'categoryKey',
partitionKey: {
name: 'category',
type: AttributeType.STRING,
}
})
Lambdaを呼び出す
GraphQLApi
にaddLambdaDataSource
というメソッドがサポートされているのでそれを用います。実装はこんな感じです。
const allItemOnCategory = new Function(this, 'allItemOnCategory', {
code: Code.asset('lambda/allItemOnCategory'),
handler: 'index.handler',
runtime: Runtime.NODEJS_12_X,
environment: {
TABLE_NAME: itemTable.tableName,
},
})
itemTable.grantReadData(allItemOnCategory)
api.addLambdaDataSource('ItemLambda', 'Item lambda source', allItemOnCategory).createResolver({
typeName: 'Query',
fieldName: 'allItemOnCategory',
requestMappingTemplate: MappingTemplate.lambdaRequest(),
responseMappingTemplate: MappingTemplate.lambdaResult(),
})
MappingTemplate.lambdaRequest()
にはペイロードが指定できるみたいですが、クエリ変数とは別なので別段使う理由がなければ何も指定しなくて問題なさそうです。
Lambdaの実装
AppSyncからarguments: {...}
という形でクエリ変数を受け取れるので、それを用いてデータの取得を実装します。
import { DynamoDB } from 'aws-sdk'
const dynamodb = new DynamoDB()
interface AllItemOnCategoryEvent {
arguments: {
category: string
}
}
export const handler = async (event: AllItemOnCategoryEvent) => {
const result = await dynamodb.query({
TableName: process.env.TABLE_NAME!,
ExpressionAttributeValues: {
':category': {
S: event.arguments.category,
},
},
KeyConditionExpression: 'category = :category',
IndexName: 'categoryKey',
}).promise()
return result.Items?.map(item=>({
id: item.id.S,
data: item.data.S,
category: item.data.S,
}))
}
必要分のデータ(つまりは[Item]
にマッチするデータ)さえ返せばクライアントとのリクエストやレスポンスのマッピングとかは勝手にAppSyncがやってくれるので何も考えずにデータのみを返します。
実装はこれだけです。API Gateway+Lambdaで書くよりも実装がシンプルになるのがいいですよね。
さいごに
全体のコードはこちらで公開しています。とりあえずベーシックでないDynamoDBの操作とかは、今の所自分でLambdaを組む必要があるみたいです(しかしもしかしたら勘違いしていることもあるかもしれないので知見のある方のご指摘を頂きたいです。。。)。
さて、7日間に渡ってAppSyncを追いかけてきました。そしてなんとかAppSyncというテーマで連日投稿してゴールをすることができました(そのせいで内容が薄くなった記事がほとんどでしたがw)。AppSyncの素晴らしさを少し学べたので今後もいかしていきたいなと思います。
明日は@nnhiguchiさんの投稿です。お楽しみに!