2019.04.13
Serverless FrameworkにAWS AppSync用のプラグインを追加することで、AWS AppSyncのスキーマ・リゾルバーをはじめとした設定が全てコードで書けて、Gitを使ったバージョン管理も行えるようになる。
Serverless FrameworkはAWSをはじめとしたクラウドサービスのリソースを効率的に構築することができるフレームワーク。
npmパッケージとして提供されているので、グローバル環境にインストールする。
npm install -g serverless
Serverless Frameworkではserverless.ymlという名前のYAMLファイルにバックエンドの構成を記述していく。$ serverless create
コマンドで必要なファイルを準備することもできるが、必要なファイルのみ作成するなら手作業でも十分簡単なので、手作業で作成する。
# 作業ディレクトリを作成する
$ mkdir serverless_appsync_sample
# 作業ディレクトリに移動する
$ cd serverless_appsync_sample
# serverless.ymlを作成する
$ touch serverless.yml
Serverless Frameworkは現時点でデフォルトではAWS AppSyncには対応していないので、プラグインをインストールする。
# serverless-appsync-pluginをインストール
$ npm install serverless-appsync-plugin
AWSでAppSync APIを作成するための最小限のserverless.ymlは以下のようになる。
DynamoDBテーブルを作成して、AppSyncのデータソースとして登録してリゾルバーでマッピングするための設定は後述。
# プロジェクト名
service: ServerlessAppSyncSample
provider:
name: aws # バックエンドにAWSを使う設定
region: ap-northeast-1 # リソースを作成するリージョン
plugins:
- serverless-appsync-plugin
# プロジェクト名
service: ServerlessAppSyncSample
provider:
name: aws # バックエンドにAWSを使う設定
region: ap-northeast-1 # リソースを作成するリージョン
plugins:
- serverless-appsync-plugin # AppSyncプラグインを読み込む設定
custom:
appSync:
name: ServerlessAppSyncSample # AppSync API名
authenticationType: API_KEY # AppSyncの認証タイプ
schema: schema.graphql # スキーマファイルの名前
AppSyncのスキーマを以下の内容で作成し、 schema.graphql
という名前で作業ディレクトリの直下に配置する。スキーマは投稿サービスを意識してPostという型とその一覧表示のQueryと作成のMutationを定義した。
type Post {
id: ID
content: String!
}
type Mutation {
createPost(input: CreatePostInput!): Post
}
type Query {
listPosts: [Post]
}
type Subscription {
onCreatePost(id: ID): Post
@aws_subscribe(mutations: ["createPost"])
}
input CreatePostInput {
content: String!
}
この時点での作業ディレクトリは以下のような構成になっている。
$ tree -L 1 .
.
├── node_modules
├── package-lock.json
├── schema.graphql
└── serverless.yml
deployコマンドを実行して、記述した設定をAWSに反映させる。このコマンドを実行した時点で設定ファイルにエラーがあると表示してくれる。
# ログ出力を詳細にして実行
$ serverless deploy -v
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
CloudFormation - UPDATE_IN_PROGRESS - AWS::CloudFormation::Stack - ServerlessAppSyncSample-dev
CloudFormation - UPDATE_IN_PROGRESS - AWS::AppSync::ApiKey - GraphQlApiKeyDefault
CloudFormation - UPDATE_COMPLETE - AWS::AppSync::ApiKey - GraphQlApiKeyDefault
(以下省略)
コマンドが正常終了したあとにマネジメントコンソールで確認してみると、確かにAppSync APIが作成できていた。
設定をGit管理する上で、管理しなくて良いファイル・ディレクトリを.gitignoreファイルに記述する。CLIでプロジェクトを作成すると以下のような.gitignoreファイルが生成されるので、その内容を使う。
# package directories
node_modules
jspm_packages
# Serverless directories
.serverless
Serverless Frameworkでデプロイしたバックエンドは、以下のコマンドにより一気に削除することができる。もちろん、DynamoDBのようなデータを持つリソースも一撃で削除できてしまうので、実行するときは注意が必要。
$ serverless remove
先ほど書いたserverless.ymlで生成されるAppSync APIはデータソースもリゾルバーもないので、APIリクエストを受け取ってもnullを返すだけになる。
データベースとしてDynamoDBテーブルを作成して、AppSyncのデータソースに割り当てたいが、DynamoDBのテーブルの作成すらコード管理したい。
DynamoDBテーブルの生成とデータソースへの登録を加えたserverless.ymlは以下のようになる。
service: ServerlessAppSyncSample
provider:
name: aws
region: ap-northeast-1
plugins:
- serverless-appsync-plugin
resources:
Resources:
DynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ServerlessAppSyncSampleTable # DynamoDBテーブル名
AttributeDefinitions:
- AttributeName: id
AttributeType: S
- AttributeName: createdAt
AttributeType: S
KeySchema:
- AttributeName: id # ハッシュキーの指定
KeyType: HASH
- AttributeName: createdAt # レンジキーの指定
KeyType: RANGE
BillingMode: PAY_PER_REQUEST # オンデマンドモードを選択
custom:
appSync:
name: ServerlessAppSyncSample
authenticationType: API_KEY
schema: schema.graphql
dataSources:
- type: AMAZON_DYNAMODB
name: ServerlessAppSyncSampleTable
config:
tableName: ServerlessAppSyncSampleTable
iamRoleStatements:
- Effect: 'Allow'
Action:
- 'dynamodb:Scan'
- 'dynamodb:Query'
- 'dynamodb:GetItem'
- 'dynamodb:PutItem'
- 'dynamodb:UpdateItem'
- 'dynamodb:DeleteItem'
Resource:
- 'Fn::Join':
- ':'
-
- 'arn:aws:dynamodb'
- Ref: 'AWS::Region'
- Ref: 'AWS::AccountId'
- 'table/ServerlessAppSyncSampleTable'
serverless.ymlの変更が完了したら、再度 $ serverless deploy
コマンドを実行することでバックエンドのリソースを更新することができる。
マネジメントコンソールでAppSyncのデータソースを確認すると、確かに追加されていることがわかる。
最後に、GraphQLスキーマとデータソースをつなぐリゾルバーを定義する。
リゾルバーはApache Velocity Language(VTL)というプログラミング言語で書くが、これをYAML形式であるserverless.ymlにうまく入れていくのは非常にしんどい。
そこで、リゾルバーのファイルは1リゾルバー1ファイルという形で管理して、serverless.ymlからはそのパスを記述することで参照するように設定を組む。
リゾルバーは量が増えてくるので、 mapping-templates
というディレクトリを作って、その中に以下のような命名規則でファイルを作成していく。
一般化するとわかりづらいが、具体例にすると非常にシンプル。
先ほどのserverless.ymlにリゾルバーの設定も加えると以下のようになる。
service: ServerlessAppSyncSample
provider:
name: aws
region: ap-northeast-1
plugins:
- serverless-appsync-plugin
resources:
Resources:
DynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ServerlessAppSyncSampleTable # DynamoDBテーブル名
AttributeDefinitions:
- AttributeName: id
AttributeType: S
- AttributeName: createdAt
AttributeType: S
KeySchema:
- AttributeName: id # ハッシュキーの指定
KeyType: HASH
- AttributeName: createdAt # レンジキーの指定
KeyType: RANGE
BillingMode: PAY_PER_REQUEST # オンデマンドモードを選択
custom:
appSync:
name: ServerlessAppSyncSample
authenticationType: API_KEY
schema: schema.graphql
dataSources:
- type: AMAZON_DYNAMODB
name: ServerlessAppSyncSampleTable
config:
tableName: ServerlessAppSyncSampleTable
iamRoleStatements:
- Effect: 'Allow'
Action:
- 'dynamodb:Scan'
- 'dynamodb:Query'
- 'dynamodb:GetItem'
- 'dynamodb:PutItem'
- 'dynamodb:UpdateItem'
- 'dynamodb:DeleteItem'
Resource:
- 'Fn::Join':
- ':'
-
- 'arn:aws:dynamodb'
- Ref: 'AWS::Region'
- Ref: 'AWS::AccountId'
- 'table/ServerlessAppSyncSampleTable'
mappingTemplatesLocation: mapping-templates # リゾルバーがあるディレクトリ名
mappingTemplates:
- dataSource: ServerlessAppSyncSampleTable
type: Query
field: listPosts
request: Query.listPosts.request
response: Query.listPosts.response
- dataSource: ServerlessAppSyncSampleTable
type: Mutation
field: createPost
request: Mutation.createPost.request
response: Mutation.createPost.response
AppSyncの構成をコード管理する方法がメインなので、リゾルバーのVTLの内容はここでは省略。
Serverless FrameworkとそのAppSync用のプラグインを使うことで、AppSyncの構成をコードで簡単に管理することができるようになった。
裏を返せば、 $ serverless remove
コマンド一発で環境が綺麗に消え去るので、操作ミスでサービスが丸ごと消えないような安全対策を取った方が良さそう。
その方法はまた別途調査してまとめたい。