はじめに
タイトルに書いたとおり AWSCDK 1.19.0 で AppSync の High-level constructs がきて嬉しくなっちゃったので書きます!
AWSCDKとは
CloudFormaiton(以下CFn)のテンプレートをTypeScriptやPythonといったプログラミング言語で書くことができるフレームワークです。
詳しくは公式サイトを見てください。
AWS AppSyncとは
AWSでGraphQLを使うならAppSync!って感じのサービスです。
GraphQLのDataSourceにDynamoDBやLambdaを指定することができ、GraphQLのAPIから操作することができます。
詳しくは公式サイトを見てください。
書いていくぞ!
今までAWSCDKでAppSyncを書こうと思うと Cfnxxxx
といったLow-level constructsで書かなければいけませんでした。
そのため必要なプロパティをすべて指定しないといけなかったりと手間がかかりました…
今回のアップデートでHigh-level constructsが来たので、どういう風に書き方が変わったのか書いていきます。
GraphQLのスキーマとなるgraphqlファイル
type Post {
id: String,
title: String,
create_time: String
}
input InputPost {
id: String
title: String,
create_time: String
}
type Query {
all: [Post]
query(id: String!, start: String!, end: String!): [Post]
}
type Mutation {
save(input: InputPost!): Post
delete(id: ID!): Post
}
type Schema {
query: Query
mutation: Mutation
}
Stackとなるソースコード
import cdk = require("@aws-cdk/core")
import { join } from "path"
import { UserPool, SignInType } from "@aws-cdk/aws-cognito"
import {
GraphQLApi,
FieldLogLevel,
UserPoolDefaultAction,
MappingTemplate
} from "@aws-cdk/aws-appsync"
import {
TableProps,
AttributeType,
BillingMode,
Table
} from "@aws-cdk/aws-dynamodb"
export class AppSyncStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props)
const userPool = new UserPool(this, "UserPool", {
userPoolName: "DemoAPIUserPool",
signInType: SignInType.USERNAME
})
const table = new Table(this, "CDKPostTable", {
tableName: "CDKPostTable",
partitionKey: {
name: "id",
type: AttributeType.STRING
},
sortKey: {
name: "create_time",
type: AttributeType.STRING
},
billingMode: BillingMode.PROVISIONED,
readCapacity: 1,
writeCapacity: 1
})
const api = new GraphQLApi(this, "PostAPI", {
name: "PostAPI",
logConfig: {
fieldLogLevel: FieldLogLevel.ALL
},
userPoolConfig: {
userPool,
defaultAction: UserPoolDefaultAction.ALLOW
},
schemaDefinitionFile: join(__dirname, "schema.graphql")
})
const datasource = api.addDynamoDbDataSource("PostAPIDataSource", "", table)
datasource.createResolver({
typeName: "Query",
fieldName: "all",
requestMappingTemplate: MappingTemplate.dynamoDbScanTable(),
responseMappingTemplate: MappingTemplate.dynamoDbResultList()
})
datasource.createResolver({
typeName: "Mutation",
fieldName: "save",
requestMappingTemplate: MappingTemplate.dynamoDbPutItem(
PrimaryKey.partition("id").is("input.id"),
Values.projecting("input")
),
responseMappingTemplate: MappingTemplate.dynamoDbResultItem()
})
datasource.createResolver({
typeName: "Mutation",
fieldName: "delete",
requestMappingTemplate: MappingTemplate.dynamoDbDeleteItem("id", "id"),
responseMappingTemplate: MappingTemplate.dynamoDbResultItem()
})
const queryMappingTemplate = `
{
"version": "2017-02-28",
"operation": "Query",
"query": {
"expression": "#id = :id AND #createTime BETWEEN :start AND :end",
"expressionNames": {
"#id": "id"
"#createTime": "create_time"
},
"expressionValues": {
":id": $util.dynamodb.toDynamoDBJson($ctx.args.id),
":start": $util.dynamodb.toDynamoDBJson($ctx.args.start),
":end": $util.dynamodb.toDynamoDBJson($ctx.args.end)
}
}
}
`
datasource.createResolver({
typeName: "Query",
fieldName: "query",
requestMappingTemplate: MappingTemplate.fromString(queryMappingTemplate),
responseMappingTemplate: MappingTemplate.dynamoDbResultList()
})
}
}
説明していく
スタックとなるTSファイルの中身を説明していきます。
今回の例ではGraphQLでDynamoDBにデータを読み書きするAppSyncを定義しています。
またその認証にCognitoを使っています。
AppSync以外のリソース定義
CognitoとDynamoDBを定義します。
// CognitoのUserPool定義
const userPool = new UserPool(this, "UserPool", {
userPoolName: "DemoAPIUserPool",
signInType: SignInType.USERNAME
})
// DynamoDBのTable定義
const table = new Table(this, "CDKPostTable", {
tableName: "CDKPostTable",
partitionKey: {
name: "id",
type: AttributeType.STRING
},
sortKey: {
name: "create_time",
type: AttributeType.STRING
},
billingMode: BillingMode.PROVISIONED,
readCapacity: 1,
writeCapacity: 1
})
AppSync本体の定義
AppSync本体の定義をします。
propaty | description |
---|---|
name | API名 |
logConfig | 残すログのレベル |
userPoolConfig | CognitoUserPoolの情報 |
schemaDefinitionFile | GraphQLのSchemaファイルのパス |
const api = new GraphQLApi(this, "PostAPI", {
name: "PostAPI",
logConfig: {
fieldLogLevel: FieldLogLevel.ALL
},
userPoolConfig: {
userPool,
defaultAction: UserPoolDefaultAction.ALLOW
},
schemaDefinitionFile: join(__dirname, "schema.graphql")
})
DataSource定義
AppSyncのDataSourceを定義します。
先程定義した GraphQLAApi
に addDynamoDbDataSource()
というメソッドが生えているので、それを使ってDataSourceを定義します。
第1引数にDataSource名、第2引数に説明、第3引数にDynamoDBのTableを渡します。
const datasource = api.addDynamoDbDataSource("PostAPIDataSource", "", table)
Resolver定義
GraphQLの各Query/MutationにResolverを定義します。
先程定義したDataSourceに createResolver()
というメソッドが生えているので、それを使ってResolverを定義します。
propaty | description |
---|---|
typeName | QueryなのかMutationなのか |
fieldName | GraphQLのフィールド名 |
requestMappingTemplate | リクエスト時のマッピングテンプレート |
responseMappingTemplate | レスポンス時のマッピングテンプレート |
datasource.createResolver({
typeName: "Query",
fieldName: "all",
requestMappingTemplate: MappingTemplate.dynamoDbScanTable(),
responseMappingTemplate: MappingTemplate.dynamoDbResultList()
})
また MappingTemplate
にDynamoDBを操作するときのテンプレートを生成してくれるメソッドがあります。
現時点では Scan / GetItem / PutItem / DeleteItem が用意されています。
MappingTemplate.dynamoDbScanTable()
MappingTemplate.dynamoDbGetItem()
MappingTemplate.dynamoDbPutItem()
MappingTemplate.dynamoDbDeleteItem()
ここで気づく人は気づくと思うんですが、 Query がないんですよね…
そういう場合はMappingTemplateを自分で書く必要があります。
自分でMappingTemplateを書いた場合は MappingTemplate.fromString()
を使って定義します。
あくまでも先程紹介したのは便利関数なので、もちろんこうやって自分で書いて定義することもできます。
const queryMappingTemplate = `
{
"version": "2017-02-28",
"operation": "Query",
"query": {
"expression": "#id = :id AND #createTime BETWEEN :start AND :end",
"expressionNames": {
"#id": "id"
"#createTime": "create_time"
},
"expressionValues": {
":id": $util.dynamodb.toDynamoDBJson($ctx.args.id),
":start": $util.dynamodb.toDynamoDBJson($ctx.args.start),
":end": $util.dynamodb.toDynamoDBJson($ctx.args.end)
}
}
}
`
datasource.createResolver({
typeName: "Query",
fieldName: "query",
requestMappingTemplate: MappingTemplate.fromString(queryMappingTemplate),
responseMappingTemplate: MappingTemplate.dynamoDbResultList()
})
またレスポンスのMappingTemplateについても用意されています。
配列で返す場合は dynamoDbResultList()
、単体で返す場合は dynamoDbResultItem()
を使います。
MappingTemplate.dynamoDbResultList()
MappingTemplate.dynamoDbResultItem()
まとめ
AppSync の High-level constructs が来て何が嬉しいのか、個人的に感じているのは、Resolverの定義のところです。
MappingTemplate.dynamoDbPutItem()
といった便利関数が用意されたおかげで、すべてのResolverを手書きする必要がなくなったので、コード量が少なくなりました。
また Cfnxxxx
で定義していたときはリソースのデプロイされる順番を addDependsOn()
でコード側で担保しなければいけませんでしたが、それも内部でやってくれるようになったので、そこもかなり助かる部分です。(忘れてしまうこと多いので…)
AWSCDK、みなさんもぜひ試してみてください!
ではまた!!!