AWS AppSyncをGitでバージョン管理する

2019.04.13

Serverless FrameworkにAWS AppSync用のプラグインを追加することで、AWS AppSyncのスキーマ・リゾルバーをはじめとした設定が全てコードで書けて、Gitを使ったバージョン管理も行えるようになる。

動作確認

  • macOS Mojave 10.14.3
  • Node.js v10.4.1
  • Serverless 1.40.0
  • AWS CLI 1.15.4 Python/2.7.10 Darwin/18.2.0 botocore/1.10.4

目次

  • Serverless Frameworkとは
  • Serverless Frameworkのインストール
  • 必要なファイルを準備する
  • AppSyncを扱うためのプラグインを追加する
  • serverless.ymlにバックエンドの設定を書く
  • 設定をバックエンドに反映させる
  • 設定をGit管理する
  • バックエンドを破棄する
  • DynamoDBテーブルを作成して、データソースとして登録する
  • GraphQLのリクエストをデータソースにマッピングするリゾルバーを作成する
  • まとめ
  • 参考

Serverless Frameworkとは

AppSyncをバージョン管理する方法

Serverless FrameworkはAWSをはじめとしたクラウドサービスのリソースを効率的に構築することができるフレームワーク。

https://serverless.com/

Serverless Frameworkのインストール

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

AppSyncを扱うためのプラグインを追加する

Serverless Frameworkは現時点でデフォルトではAWS AppSyncには対応していないので、プラグインをインストールする。

# serverless-appsync-pluginをインストール
$ npm install serverless-appsync-plugin

serverless.ymlにバックエンドの設定を書く

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が作成できていた。

AppSyncをバージョン管理する方法

設定をGit管理する

設定をGit管理する上で、管理しなくて良いファイル・ディレクトリを.gitignoreファイルに記述する。CLIでプロジェクトを作成すると以下のような.gitignoreファイルが生成されるので、その内容を使う。

# package directories
node_modules
jspm_packages

# Serverless directories
.serverless

バックエンドを破棄する

Serverless Frameworkでデプロイしたバックエンドは、以下のコマンドにより一気に削除することができる。もちろん、DynamoDBのようなデータを持つリソースも一撃で削除できてしまうので、実行するときは注意が必要。

$ serverless remove

DynamoDBテーブルを作成して、データソースとして登録する

先ほど書いた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のデータソースを確認すると、確かに追加されていることがわかる。

AppSyncをバージョン管理する方法

GraphQLのリクエストをデータソースにマッピングするリゾルバーを作成する

最後に、GraphQLスキーマとデータソースをつなぐリゾルバーを定義する。

リゾルバーはApache Velocity Language(VTL)というプログラミング言語で書くが、これをYAML形式であるserverless.ymlにうまく入れていくのは非常にしんどい。

そこで、リゾルバーのファイルは1リゾルバー1ファイルという形で管理して、serverless.ymlからはそのパスを記述することで参照するように設定を組む。

リゾルバーは量が増えてくるので、 mapping-templatesというディレクトリを作って、その中に以下のような命名規則でファイルを作成していく。

  • (クエリの種類).(クエリ名).(リゾルバーの種類)

一般化するとわかりづらいが、具体例にすると非常にシンプル。

  • Query.listPosts.request:listPosts(Query)のリクエストマッピングテンプレート
  • Query.listPosts.response:listPosts(Query)のレスポンスマッピングテンプレート
  • Mutation.createPost.request:createPost(Mutation)のリクエストマッピングテンプレート
  • Mutation.createPost.response:createPost(Mutation)のレスポンスマッピングテンプレート

先ほどの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 コマンド一発で環境が綺麗に消え去るので、操作ミスでサービスが丸ごと消えないような安全対策を取った方が良さそう。

その方法はまた別途調査してまとめたい。

参考