AWS CDKでAppSyncのスタックを作ってみた
AWS CDKをさわれば触るほど、素晴らしさに気づけます。
今回は、AWS CDKを使ってAppSyncとDynamoDBを使用したサービスを作ってみたいと思います。
本記事では、TypeScriptの使用方法や、CDKの基本的な使い方は紹介しません。
もくもくと、CDKでAppSyncを使うまでにしたことを記載します。
また本ブログで使用した、AWS CDKのバージョンは1.3.0です。
プロジェクトの初期設定
AWS CDKのスタックを作成するための初期設定を行ないます。
個人の趣向ですが、cdk init
をせずにプロジェクトの設定を行います。
$ mkdir cdk-appsync-101 && cd $_ $ git init $ npm init -y $ npm i @aws-cdk/{aws-appsync,aws-dynamodb,aws-iam,core} $ npm i -D aws-cdk @types/node typescript
次に、package,json
の設定をしてます。
下記のscriptsの部分を変更してください。
{ "name": "cdk-appsync-101", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "tsc", "watch": "tsc -w", "deploy": "tsc && cdk deploy" }, "author": "37108", "license": "MIT", "devDependencies": { "@types/node": "^12.7.1", "aws-cdk": "^1.3.0", "typescript": "^3.5.3" }, "dependencies": { "@aws-cdk/aws-appsync": "^1.3.0", "@aws-cdk/aws-dynamodb": "^1.3.0", "@aws-cdk/aws-iam": "^1.3.0", "@aws-cdk/core": "^1.3.0" } }
ハイライトした部分の変更はできましたか。author
やLICENSE
などの細かい部分は人によってまちまちなのであまり気にしないでください。
次に、tsconfig.json
を作成します。
あまりルールを厳しくはしていないのでお好みで変えてください。
{ "compilerOptions": { "target": "ES2018", "module": "commonjs", "outDir": "./dist/", "lib": [ "es2016", "es2017.object", "es2017.string" ], "noUnusedLocals": false, "noUnusedParameters": false } }
最後にcdk.json
を追加して初期設定は終わりです。
{ "app": "node dist/index" }
これでプロジェクトの初期設定は終了です。
CDKのコーディング
コーディングをしてAppSync API、DynamoDBのテーブルを作成していきます。
クラスの作成
まずはクラスの作成をしていきます。
ディレクトリ配下にsrc
ディレクトリを作成し、中に、index.ts
というファイルを作成します。
このファイルに下記コードを書きます。
#!/usr/bin/env node import cdk = require('@aws-cdk/core') export class AppSync101Stack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props) } } const app = new cdk.App() new AppSync101Stack(app, 'AppSync101Stack') app.synth()
普段作成している、AWS CDKのプロジェクトと同じ様に記載すれば問題ありません。
スキーマの定義
GraphQLで使用するスキーマを定義します。
クラス内で定義しても良いのですが、可読性がよくないので別ファイルに分離します。
新たにsrc
配下にschema.ts
というファイルを作成し下記の様に書きます。
getItemというクエリと、addItem、deleteItemというミューテーションを記述しただけの簡素なスキーマです。
const tableName = 'items' export const definition = ` type ${tableName} { id: ID! name: String } type Query { getItem(id: ID!): ${tableName} } type Mutation { addItem(name: String!): ${tableName} deleteItem(id: ID!): ${tableName} } type Schema { query: Query mutation: Mutation } `
AppSync APIの作成
いよいよAppSync APIを作成していきます。
認証方式はAPIで、スキーマは先ほど作ったものを使用していきます。
#!/usr/bin/env node import cdk = require('@aws-cdk/core') import { CfnGraphQLApi, CfnApiKey, CfnGraphQLSchema } from '@aws-cdk/aws-appsync' import { definition } from './schema' const tableName = 'items' export class AppSync101Stack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props) const graphQLApi = new CfnGraphQLApi(this, 'itemsApi', { name: 'items-api', authenticationType: 'API_KEY' }) new CfnApiKey(this, 'ItemsApiKey', { apiId: graphQLApi.attrApiId }) const Schema = new CfnGraphQLSchema(this, 'ItemsSchema', { apiId: graphQLApi.attrApiId, definition, }) } } const app = new cdk.App() new AppSync101Stack(app, 'AppSync101Stack') app.synth()
ここまでできたら一旦デプロイしてみましょう。
もちろんリゾルバがないので何もできない、侘しいAPIが出来上がります。
$ npm run deploy AppSync101Stack: deploying... AppSync101Stack: creating CloudFormation changeset... ✅ AppSync101Stack
AppSync APIが作成できましたね。
DynamoDB テーブルの作成
AppSyncのリゾルバとしてDynamoDBのテーブルを使用するので作成します。
#!/usr/bin/env node import cdk = require('@aws-cdk/core') import { CfnGraphQLApi, CfnApiKey, CfnGraphQLSchema } from '@aws-cdk/aws-appsync' import { Table, AttributeType } from '@aws-cdk/aws-dynamodb' import { definition } from './schema' const tableName = 'items' export class AppSync101Stack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props) const graphQLApi = new CfnGraphQLApi(this, 'itemsApi', { name: 'items-api', authenticationType: 'API_KEY' }) new CfnApiKey(this, 'ItemsApiKey', { apiId: graphQLApi.attrApiId }) const Schema = new CfnGraphQLSchema(this, 'ItemsSchema', { apiId: graphQLApi.attrApiId, definition, }) const dynamoTable = new Table(this, 'items', { partitionKey: { name: 'id', type: AttributeType.STRING }, tableName, removalPolicy: cdk.RemovalPolicy.DESTROY, }) } } const app = new cdk.App() new AppSync101Stack(app, 'AppSync101Stack') app.synth()
IAM ロールの作成
AppSyncからDynamoDBに対して接続できるようにIAM ロールを作成します。 DynamoDBへのフルアクセスを許可していますが、本番で使う場合は十分に権限を検討してください。
#!/usr/bin/env node import cdk = require('@aws-cdk/core') import { CfnGraphQLApi, CfnApiKey, CfnGraphQLSchema } from '@aws-cdk/aws-appsync' import { Table, AttributeType } from '@aws-cdk/aws-dynamodb' import { Role, ServicePrincipal, ManagedPolicy } from '@aws-cdk/aws-iam' import { definition } from './schema' const tableName = 'items' export class AppSync101Stack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props) const graphQLApi = new CfnGraphQLApi(this, 'itemsApi', { name: 'items-api', authenticationType: 'API_KEY' }) new CfnApiKey(this, 'ItemsApiKey', { apiId: graphQLApi.attrApiId }) const Schema = new CfnGraphQLSchema(this, 'ItemsSchema', { apiId: graphQLApi.attrApiId, definition, }) const dynamoTable = new Table(this, 'items', { partitionKey: { name: 'id', type: AttributeType.STRING }, tableName, removalPolicy: cdk.RemovalPolicy.DESTROY, }) const dataSourceIamRole = new Role(this, 'dataSourceIamRole', { assumedBy: new ServicePrincipal('appsync.amazonaws.com') }) dataSourceIamRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('AmazonDynamoDBFullAccess')) } } const app = new cdk.App() new AppSync101Stack(app, 'AppSync101Stack') app.synth()
データソースの作成とリゾルバのアタッチ
DynamoDB テーブルもできIAM ロールの準備もできたので、最後にリゾルバのアタッチを行いましょう。
スキーマで定義したクエリとミューテーション分だけリゾルバを作成していきます。
#!/usr/bin/env node import cdk = require('@aws-cdk/core') import { CfnGraphQLApi, CfnApiKey, CfnGraphQLSchema, CfnDataSource, CfnResolver } from '@aws-cdk/aws-appsync' import { Table, AttributeType } from '@aws-cdk/aws-dynamodb' import { Role, ServicePrincipal, ManagedPolicy } from '@aws-cdk/aws-iam' import { definition } from './schema' const tableName = 'items' export class AppSync101Stack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props) const graphQLApi = new CfnGraphQLApi(this, 'itemsApi', { name: 'items-api', authenticationType: 'API_KEY' }) new CfnApiKey(this, 'ItemsApiKey', { apiId: graphQLApi.attrApiId }) const Schema = new CfnGraphQLSchema(this, 'ItemsSchema', { apiId: graphQLApi.attrApiId, definition, }) const dynamoTable = new Table(this, 'items', { partitionKey: { name: 'id', type: AttributeType.STRING }, tableName, removalPolicy: cdk.RemovalPolicy.DESTROY, }) const dataSourceIamRole = new Role(this, 'dataSourceIamRole', { assumedBy: new ServicePrincipal('appsync.amazonaws.com') }) dataSourceIamRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('AmazonDynamoDBFullAccess')) const dataSource = new CfnDataSource(this, 'dataSource', { apiId: graphQLApi.attrApiId, name: 'ItemsDynamoDataSource', serviceRoleArn: dataSourceIamRole.roleArn, type: 'AMAZON_DYNAMODB', dynamoDbConfig: { tableName, awsRegion:this.region } }) const getItemResolver = new CfnResolver(this, 'getItemResolver', { apiId: graphQLApi.attrApiId, typeName: 'Query', fieldName: 'getItem', dataSourceName: dataSource.name, requestMappingTemplate: `{ "version": "2017-02-28", "operation": "GetItem", "key": { "id": $util.dynamodb.toDynamoDBJson($ctx.args.id) } }`, responseMappingTemplate: `$util.toJson($ctx.result)` }) getItemResolver.addDependsOn(Schema) const addItemResolver = new CfnResolver(this, 'addItemResolver', { apiId: graphQLApi.attrApiId, typeName: 'Mutation', fieldName: 'addItem', dataSourceName: dataSource.name, requestMappingTemplate: `{ "version": "2017-02-28", "operation": "PutItem", "key": { "id": { "S": "$util.autoId()" } }, "attributeValues": { "name": $util.dynamodb.toDynamoDBJson($ctx.args.name) } }`, responseMappingTemplate: `$util.toJson($ctx.result)` }) addItemResolver.addDependsOn(Schema) const deleteItemResolver = new CfnResolver(this, 'deleteItemResolver', { apiId: graphQLApi.attrApiId, typeName: 'Mutation', fieldName: 'deleteItem', dataSourceName: dataSource.name, requestMappingTemplate: `{ "version": "2017-02-28", "operation": "DeleteItem", "key": { "id": $util.dynamodb.toDynamoDBJson($ctx.args.id) } }`, responseMappingTemplate: `$util.toJson($ctx.result)` }) deleteItemResolver.addDependsOn(Schema) } } const app = new cdk.App() new AppSync101Stack(app, 'AppSync101Stack') app.synth()
デプロイ
最後にデプロイして完了です。
$ npm run deploy AppSync101Stack: deploying... AppSync101Stack: creating CloudFormation changeset... ✅ AppSync101Stack
クエリを実行する
デプロイしたAppSync APIにクエリを投げてみます。
まずは、AWSマネジメントコンソールログインします。
次に画像のようにミューテーションを書きます。
準備ができたので実行します。
データができたので、次はクエリを実行します。 先ほどと同じ要領でクエリを書きます。
準備ができたので実行します。
問題なく動作していますね。
さいごに
AWS CDKでAppSyncとDynamoDBを作成しましたが、非常にサクサク書くことができました。
また、AppSyncやDynamoDBについても少し詳しく慣れたので非常によかったです。