いわさです。
AWS SAM は CloudFormation の拡張機能で、サーバーレースアプリケーションでよく使われるリソースを CloudFormation よりも抽象化した拡張コンポーネントで定義出来るようにする機能です。
SAM によって迅速にサーバーレスアプリケーションを構築出来るようにすることを目的にしています。
AWS AppSync は AWS マネージドなサーバーレス GraphQL サービスです。
SAM でサポートされていてもおかしくない AppSync でしたが、これまでは SAM でサポートされていませんでした。
そのため、SAM を使ってサーバーレスアプリケーションを管理したい場合でも、AppSync 関係のリソースについては CloudFormation コンポーネントを使う必要がありました。
しかし、本日のアップデートで AWS SAM で AppSync がサポートされました。
今回のアップデートによって、他の SAM コンポーネントと同様に抽象的な定義が出来るようになるはず。
ただ、よく考えてみたら私はそもそも CloudFormation で AppSync を構築したことがありませんでした。
そこで本日は CloudFormation と SAM で次のような基本的な AppSync + DynamoDB な API を作成し、その特徴を比べてみました。
CloudFormation
出来るだけひとつのテンプレートに今回まとめたかったので、リゾルバーやスキーマなども全部インラインです。
作ってみたところ CloudFormation のテンプレートは次のような感じになりました。
AWSTemplateFormatVersion: '2010-09-09'
Resources:
MyDynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: MyDynamoDBTable
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
BillingMode: PAY_PER_REQUEST
MyAppSyncApi:
Type: AWS::AppSync::GraphQLApi
Properties:
Name: MyAppSyncApi
AuthenticationType: API_KEY
MyAppSyncApiKey:
Type: AWS::AppSync::ApiKey
Properties:
ApiId:
Fn::GetAtt:
- MyAppSyncApi
- ApiId
MyAppSyncDynamoDBDataSource:
Type: AWS::AppSync::DataSource
Properties:
Name: MyAppSyncDataSource
ApiId:
Fn::GetAtt:
- MyAppSyncApi
- ApiId
Type: AMAZON_DYNAMODB
ServiceRoleArn:
Fn::GetAtt:
- AppSyncDynamoDBRole
- Arn
DynamoDBConfig:
AwsRegion: !Ref AWS::Region
TableName: !Ref MyDynamoDBTable
MyAppSyncSchema:
Type: AWS::AppSync::GraphQLSchema
Properties:
ApiId:
Fn::GetAtt:
- MyAppSyncApi
- ApiId
Definition: >
schema {
query: Query
mutation: Mutation
}
type Query {
getItem(id: ID!): Item
}
type Mutation {
addItem(id: ID!, hogetext: String!): Item
}
type Item {
id: ID!
hogetext: String
}
GetItemResolver:
Type: AWS::AppSync::Resolver
DependsOn: MyAppSyncSchema
Properties:
ApiId: !GetAtt MyAppSyncApi.ApiId
TypeName: Query
FieldName: getItem
DataSourceName: !GetAtt MyAppSyncDynamoDBDataSource.Name
RequestMappingTemplate: |
{
"version" : "2017-02-28",
"operation" : "GetItem",
"key" : {
"id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
}
}
ResponseMappingTemplate: |
$util.toJson($ctx.result)
AddItemResolver:
Type: AWS::AppSync::Resolver
DependsOn: MyAppSyncSchema
Properties:
ApiId: !GetAtt MyAppSyncApi.ApiId
TypeName: Mutation
FieldName: addItem
DataSourceName: !GetAtt MyAppSyncDynamoDBDataSource.Name
RequestMappingTemplate: |
{
"version": "2017-02-28",
"operation": "PutItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($ctx.args.id),
},
"attributeValues": {
"hogetext": $util.dynamodb.toDynamoDBJson($ctx.args.hogetext)
}
}
ResponseMappingTemplate: |
{
"id": $util.toJson($ctx.args.id),
"hogetext": $util.toJson($ctx.args.hogetext)
}
AppSyncDynamoDBRole:
Type: AWS::IAM::Role
Properties:
RoleName: AppSyncDynamoDBRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- "appsync.amazonaws.com"
Action:
- "sts:AssumeRole"
Policies:
- PolicyName: AppSyncDynamoDBAccessPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- "dynamodb:BatchGetItem"
- "dynamodb:GetItem"
- "dynamodb:Query"
- "dynamodb:Scan"
- "dynamodb:BatchWriteItem"
- "dynamodb:PutItem"
- "dynamodb:UpdateItem"
- "dynamodb:DeleteItem"
Resource: !Sub
- "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${MyDynamoDBTable}"
- MyDynamoDBTable: !Ref MyDynamoDBTable
作っていて思ったのは、API Gateway とだいぶ似てるなって思いました。
API Gateway と AppSync の共通点として細かいコンポーネントが非常に多いです。
AppSync でいえば、基本的な構成だけで以下をそれぞれ別で定義して参照させる必要があります。(ApiKey あたりは認証方法によりそうだが)
- AWS::AppSync::GraphQLApi
- AWS::AppSync::ApiKey
- AWS::AppSync::DataSource
- AWS::AppSync::GraphQLSchema
- AWS::AppSync::Resolver
バラバラなので依存関係に注意が必要です。
特に、Resolver からスキーマのフィールド名を参照するのですが、リソース参照ではなくて名称で指定しているだけなので、Resolver に DependsOn をつけるとか気を配る必要がありました。
あと、CloudFormation だとやはり IAM はしっかり意識する必要がありますね。
こちらのテンプレートで次のように DynamoDB への項目の書き込みと読み込みが出来ます。
SAM
SAM の場合はAWS::Serverless::GraphQLApi
ひとつで定義出来ます。
定義出来るというか先程バラバラだったものを詰め込む感じではありますが。
AWSTemplateFormatVersion: 2010-09-09
Description: ---
Transform: AWS::Serverless-2016-10-31
Resources:
MyDynamoDBTable2:
Type: AWS::Serverless::SimpleTable
Properties:
TableName: MyDynamoDBTable2
PrimaryKey:
Name: id
Type: String
MyGraphQLAPI:
Type: AWS::Serverless::GraphQLApi
Properties:
Auth:
Type: API_KEY
ApiKeys:
MyApiKey:
Description: my api key
DataSources:
DynamoDb:
ItemsDataSource:
TableName: !Ref MyDynamoDBTable2
TableArn: !GetAtt MyDynamoDBTable2.Arn
SchemaInline: >
schema {
query: Query
mutation: Mutation
}
type Query {
getItem(id: ID!): Item
}
type Mutation {
addItem(id: ID!, hogetext: String!): Item
}
type Item {
id: ID!
hogetext: String
}
Functions:
addItem:
Runtime:
Name: APPSYNC_JS
Version: 1.0.0
DataSource: ItemsDataSource
InlineCode: >
import { util } from "@aws-appsync/utils";
export function request(ctx) {
const { id, hogetext } = ctx.arguments
return {
operation: "PutItem",
key: util.dynamodb.toMapValues({id: id}),
attributeValues: util.dynamodb.toMapValues({
hogetext: hogetext
})
};
}
export function response(ctx) {
return ctx.result;
}
getItem:
Runtime:
Name: APPSYNC_JS
Version: "1.0.0"
DataSource: ItemsDataSource
InlineCode: >
import { util } from "@aws-appsync/utils";
export function request(ctx) {
return dynamoDBGetItemRequest({ id: ctx.args.id });
}
export function response(ctx) {
return ctx.result;
}
function dynamoDBGetItemRequest(key) {
return {
operation: "GetItem",
key: util.dynamodb.toMapValues(key),
};
}
Resolvers:
Mutation:
addItem:
Runtime:
Name: APPSYNC_JS
Version: "1.0.0"
Pipeline:
- addItem
Query:
getItem:
Runtime:
Name: APPSYNC_JS
Version: "1.0.0"
Pipeline:
- getItem
スキーマ定義は CloudFormation のものをそのまま使うことが出来ました。
また、AppSync から DynamoDB へアクセスするためのサービスロールについては次のように自動構成されました。
データソースのプロパティで Connector を使って独自に定義も出来ますが、デフォルトで自動設定してくれるのは便利ですね。
カスタマイズしたい場合は次を参考にしてください。
DynamoDb - AWS Serverless Application Model
CloudFormation と大きく違う点というか、作成していて最初に戸惑ったのはリゾルバーですね。
SAM では JavaScript パイプラインリゾルバーのみサポートされている
SAM の場合はパイプラインリゾルバーのみがサポートされています。
AWS SAM supports JavaScript pipeline resolvers.
AWS::Serverless::GraphQLApi - AWS Serverless Application Model より
以前は Unit Resolver というリゾルバータイプのみが AppSync ではサポートされていて、以前の AppSync では VTL でマッピングテンプレートを定義していることが多いのではないでしょうか。
2022 年 11 月のアップデートでリゾルバーを JavaScript で定義出来るようになりました。
今回作成したテンプレートでは CloudFormation で使っていた VTL をベースに JavaScript パイプラインリゾルバーに変更してみました。
マネジメントコンソール上でも「推奨」という表記があったので、VTL よりもこちらを使いましょうという方針のようですね。
さいごに
本日は AWS AppSync が SAM でデプロイ出来るようになったので、使ってみました。
バラバラで依存関係を気にしなければならなかった問題などは気にしなくてよくなりそうです。
また、権限周りがすっきりするのはやはり SAM のひとつのメリットかなと思いました。カスタム定義したい場合も Connector で抽象化した定義が可能なので。
AppSync リソース自体は、AWS::Serverless::GraphQLApi
自体に明示的に指定する必要のある必須プロパティが多いので、CloudFormation と比べて記述量はあまり変わってないですね。
AppSync は理解しておかなければいけない概念が元々多いほうだと思いますが、それらが不要になって単純になったというわけではなさそうです。
JavaScript パイプラインリゾルバーの兼ね合いで CloudFormation から SAM へ、必ずしも単純に移行出来るわけではないという点は注意しておきたいですね。