AWS AppSync環境を AWS Cloud Formation で構築する
先日、プロトタイプ開発のために利用していたAppSyncをGUIでポチポチ構築するのからCloud Formationで構築するように移行したので、備忘録がてら概要を記事にまとめました。意外とこのあたりの記事が少なかったので参考になればと思います。
構成
今回の記事では、あれこれ簡略化するためDynamoDBにidとnameのみを持つ User情報を保存しそのデータをAppSync経由で取得する、というシンプルな構成とします。
※ 本来はUser情報がもっと複雑だったり複数のデータソースがあったりするかと思いますが、ここでは割愛
AppSync × DynamoDBの環境を構築するために必要なすべてのリソースをCloud Formationで作成していきます。(実際に作成するものは以下の通りです)
- DynamoDB
- IAM Role
- AppSync
AppSyncの設定を行うのにDynamoDB・IAM Roleの設定値が必要となるので、上から順に構築するようにしていきます。
DynamoDB テーブルの作成
以下のようにしてDynamoDBのTableを作成します。
AttributeDefinitionsとKeySchemaでidフィールドをプライマリキーにしています。ProvisionedThroughputはとりあえずデフォルトのReadCapacityUnits・WriteCapacityUnits両方5の設定です。
Cloud Formation × DynamoDB の詳細については公式ドキュメントをご確認ください。
※ yamlの中身についてはResources: 以下の要素を 抜粋しています。今後も同様に抜粋しますので、完成形はこちらのGistでご確認ください。
UserTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: 'id'
AttributeType: 'S'
KeySchema:
- AttributeName: 'id'
KeyType: 'HASH'
ProvisionedThroughput:
ReadCapacityUnits: '5'
WriteCapacityUnits: '5'
TableName: 'users'
IAM Roleの作成
次に、AppSyncからDynamoDBにアクセスするためのIAM Roleを作成します。
AppSyncから使うため、AssumeRolePolicyDocumentのServiceに appsync.amazonaws.comをPoliciesにDynamoDBを読み書きするための許可を記載しています。
また、複数の環境で使うことを想定し、リージョンとアカウントはパラメータ化して、Fn::Join: を利用して文字列を組み立てています。
AppSyncDynamoRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- appsync.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /cloudformation/
RoleName:
Fn::Join:
- ''
- - !Ref awsRegion
- '-appsync-dynamodb-role'
Policies:
- PolicyName: test-appsync-dynamodb
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 'dynamodb:DeleteItem'
- 'dynamodb:GetItem'
- 'dynamodb:PutItem'
- 'dynamodb:Query'
- 'dynamodb:Scan'
- 'dynamodb:UpdateItem'
Resource:
- Fn::Join:
- ''
- - 'arn:aws:dynamodb:'
- !Ref awsRegion
- ':'
- !Ref awsAccount
- ':table/users'
- Fn::Join:
- ''
- - 'arn:aws:dynamodb:'
- !Ref awsRegion
- ':'
- !Ref awsAccount
- ':table/users/*'
リージョンとアカウントのパラメーター化は以下のようにしています。適切なデフォルト値を設定してそのまま利用するなり、aws cloudformation deploy の parameter-overridesオプションで設定するなりして使います。
Parameters:
awsRegion:
Type: String
Default: ap-northeast-1
awsAccount:
Type: String
Default: set-your-account-here
AppSync の構築
ようやく本題です。
実際にymlの設定を見る前に、AppSyncの構築に必要な作業を確認しましょう。GUIからAppSyncを構築する場合は以下の作業が必要になります。
※ 今回はAPI認証をAPI KEYで行っています。別の認証方式を採用したり、追加の認証をプロバイダーを設定する場合については機会があればまた記事にしたいと思います。
- APIを作成する
- GraphQLのSchemaを定義する
- AppSyncとデータベースを接続するためDataSourceを作成する
- APIの認証のためにAPI KEYを作成する
- GraphQLのMutationやQueryなどのResolverを定義する
Cloud Formationでは、それぞれのサービスリソースタイプが割り当てられており、以下のように対応しています。
Type | 内容 |
AWS::AppSync::GraphQLApi | GraphQL API作成 |
AWS::AppSync::GraphQLSchema | GraphQLのSchema定義 |
AWS::AppSync::DataSource | DataSource定義 |
AWS::AppSync::Resolver | Resolver定義 |
AWS::AppSync::ApiKey | API KEY作成 |
Schema・DataSource・ApiKeyの作成はAPIが存在しない場合失敗してしまうため、明示的に依存関係を定義しておくようにしましょう。
AWS::AppSync::GraphQLApi
GraphQL API作成に最低限必要な設定は Name と AuthenticationTypeです。Nameは任意のAPI名を設定し、AuthenticationTypeには今回利用するAPI 認証方式のAPI_KEYを設定します。
AppSyncGraphqlAPI:
Type: 'AWS::AppSync::GraphQLApi'
Properties:
Name: GraphqlAPI
AuthenticationType: 'API_KEY'
AuthenticationType は API設定画面の「デフォルトの認証モード」に該当します。API_KEY以外に、AWS_IAM、AMAZON_COGNITO_USER_POOLS、OPENID_CONNECTが設定可能です。
また、LogやX-Rayトレースの設定なども可能ですが、今回は省略しています。詳しくは公式ドキュメントを参照してください。
AWS::AppSync::ApiKey
APIを作成したのでとりあえずAPI KEYを作っておきましょう。
ApiIdにAPI KEYを作成するAPIのIDを、ExpiresにAPI KEYの有効期限を設定します。ApiIdはAPI作成時に動的に割り当てられるため、Fn::GetAtt を利用して取得しています。ExpiresはUNIX秒の日時を指定です。
また、先述したとおり、APIが存在しないとAPI KEYの作成に失敗してしまうため、DependsOnで依存関係を設定しています。
AppSyncAPIKey:
Type: AWS::AppSync::ApiKey
DependsOn: [AppSyncGraphqlAPI]
Properties:
ApiId:
Fn::GetAtt: [AppSyncGraphqlAPI, ApiId]
Expires: 1598885999 # 2020-08-31 23:59:59(秒単位)
AWS::AppSync::GraphQLSchema
次に、スキーマの定義を行います。
ApiIdの設定は先程と同様です。GraphQLのスキーマはDefinitionに記載していますが、スキーマの定義ファイルをS3に置いて、DefinitionS3Location にS3上のファイルの位置を設定してやることも可能です。
今回はGraphQLのスキーマが短いのでベタ書きでもなんとかなりましたが、長くなってくるとymlのメンテナンスが大変になりそうなので、スキーマは別ファイルにしてしまったほうがよいかもしれません。
なお、こちらも同様にDependsOnでAPIの存在を担保しています。
AppSyncSchema:
Type: AWS::AppSync::GraphQLSchema
DependsOn: [AppSyncGraphqlAPI]
Properties:
ApiId:
Fn::GetAtt: [AppSyncGraphqlAPI, ApiId]
Definition: |
input CreateUserInput {
name: String!
}
input DeleteUserInput {
id: ID!
}
type Mutation {
createUser(input: CreateUserInput!): User
updateUser(input: UpdateUserInput!): User
deleteUser(input: DeleteUserInput!): User
}
type Query {
getUser(id: ID!): User
}
input UpdateUserInput {
id: ID!
name: String!
}
type User {
id: ID!
name: String!
}
schema {
query: Query
mutation: Mutation
}
AWS::AppSync::DataSource
DataSourceの作成方法は以下のように設定しました。
- ApiId:先程同様に設定。Nameは今回 はusersとしました。
- Type:利用するデータソースの種類を設定します。今回はDynamoDBを利用するため、AMAZON_DYNAMODBを指定。他のデータソースを利用する場合は公式ドキュメントでご確認ください。
- ServiceRoleArn:データソースに設定するIAM RoleのArnを設定します。先程作ったIAM RoleのArnをFn::GetAttで取得して設定しています。
- DynamoDBConfig:DynamoDBをデータソースに選択した場合はDynamoDBConfigに接続先の情報などを設定します。 今回はリージョンとテーブル名のみ指定し、その他の設定値はデフォルトにしています。
また、Schema同様DependsOnで依存関係を設定していますが、今回はDyanmoDBとIAM Roleにも依存しているため、それらも追加しています。
AppSyncUserDataSource:
Type: AWS::AppSync::DataSource
DependsOn:
[AppSyncGraphqlAPI, AppSyncDynamoRole, UserTable]
Properties:
ApiId:
Fn::GetAtt: [AppSyncGraphqlAPI, ApiId]
Name: 'users'
Type: 'AMAZON_DYNAMODB'
ServiceRoleArn:
Fn::GetAtt: [AppSyncDynamoRole, Arn ]
DynamoDBConfig:
AwsRegion:
Ref: awsRegion
TableName: users
AWS::AppSync::Resolver
最後にGraphQLのQueryやMutationのリゾルバーを作成していきます。
少なくとも各Query・Mutationごとにリゾルバーが必要になりますが、作り方は同じなので今回はcreateUser Mutationのみ取り上げます。
- ApiId:これまで同様、APIのIDをFn::GetAttで取得しています
- TypeName:リゾルバーの種類です。Mutation・Queryなどのリゾルバーの場合はGraphQLのクエリの種類を指定します。また、フィールドがリゾルバーを持つような複雑な型の場合はその型の名前を指定します。
- FieldName:Mutation・Queryの名前・リゾルバーを設定するフィールド名を設定します。
- DataSourceName:先程作成したデータソースの名前を設定します。あとからデータソース名を変更しやすいようにFn::GetAttで取得しています。
- RequestMappingTemplate/ResponseMappingTemplate:リクエスト・レスポンスのテンプレートマッピングを記述しています。Schema同様、S3に配置する方式でも設定可能です。(RequestMappingTemplateS3Location/ResponseMappingTemplateS3Location)
AppSyncCreateUserResolver:
Type: AWS::AppSync::Resolver
DependsOn:
[AppSyncGraphqlAPI, AppSyncUserDataSource]
Properties:
ApiId:
Fn::GetAtt: [AppSyncGraphqlAPI, ApiId]
TypeName: Mutation
FieldName: createUser
DataSourceName:
Fn::GetAtt: [AppSyncUserDataSource, Name]
RequestMappingTemplate: |
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
"id": $util.dynamodb.toDynamoDBJson($util.autoId()),
},
"attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.input),
"condition": {
"expression": "attribute_not_exists(#id)",
"expressionNames": {
"#id": "id",
}
}
}
ResponseMappingTemplate: $util.toJson($ctx.result)
終わりに
以上で、AppSync + DynamoDBの環境をCloud Formation で構築するための設定が完了しました。完成形はこちらに置いてあるので、aws cliやCloud FormationのGUI等からお試しください。
なお、元も子もないですが、Serverless Framework の AppSync Plugin を使えばSchemaやマッピングテンプレートの部分を別ファイルにできるため、より管理しやすいです。