Amplify GraphQL Transformer v2 まとめ
「AWS Amplify Advent Calendar 2021」 19日目の記事となります
記事の概要
2021/11/23に、Amplify GraphQL Transformer v2(以下v2と表記)が発表されました[1]。以前のGraphQL Transformer v1(以下v1と表記)から大幅なアップデートがいくつも入っており、Breaking Changeも少なからずあります。この記事では
- GraphQL Transformer v2の主な変更点
- GraphQL Transformer v1からのMigration時の注意事項
の2点についてまとめてみました!
想定読者
- Amplify CLIでGraphQL APIを利用している人
- これから利用を検討しているが、既存のブログ記事などがv1で記述されており差分が知りたい人
話さないこと(及び参考資料)
- Amplify 自体
- Amplify CLIのGraphQLカテゴリの概要
主な変更点
本セクションでは、以下5つの主要な変更を順に見ていきます。
-
@key
のリニューアル -
@connection
のリニューアル -
@auth
における Deny-by-default 原則の適用 -
@versioned
の廃止 - 自動生成されるResolverがPipeline Resolverに
@key
のリニューアル
v1では@key
を用いてAppSyncの背後にあるDynamoDB Tableに対し、Primariy KeyやLocal Sencodary Index(LSI)、Global Sencodary Index(GSI)[2]といったインデックスを貼っていました[3]。
v2では@key
が廃止され、代わりに@primaryKey
と@index
の2つのディレクティブが追加されました。
公式ブログ[1:1]に乗っている上図をもとに、v1とv2の違いを紐解いてみましょう
v1 | v2 | |
---|---|---|
Primary Keyの指定 (図中赤矢印) |
name フィールドがない@key を使用する |
@primaryKey を該当フィールドに付与 |
GSIの指定 (紫矢印) |
name フィールドを指定し、fields でPrimary Keyと異なるPartition Keyを指定した@key を使用する(name フィールドで指定した値がGSIの名前になる) |
@index を@primaryKey を付与していない(Primary KeyのPartition Key[4]でない)フィールドに付与 |
LSIの指定 (緑矢印) |
name フィールドを指定し、fields でPrimary KeyとおなじPartition Keyを指定した@key を使用する(name フィールドで指定した値がLSIの名前になる) |
@index を@primaryKey を付与している(Primary KeyのPartition Keyとなる)フィールドに付与 |
Partition KeyとSort Keyの指定 |
fields は最初の引数がPartition Key、残りがSort Key[4:1]
|
Partition Keyは @primaryKey or @index を付与したフィールド、Sort Keyは sortKeyFields で渡したフィールド |
@primaryKey
と@index
のどちらもフィールドに直接デコレートするため、直感的にどのフィールドにインデックスが貼られているのかわかりやすくなったと思います。また、ディレクティブの名前(@primaryKey
, @index
)やフィールド名(sortKeyFields
)も何を指定しているのかわかりやすい形になりました。
詳しくはこちら[5]のドキュメントを参照ください。
@connection
のリニューアル
@connection
の代わりに、以下の4つのDirectiveを使い分ける形になりました。
- @hasOne
- @hasMany
- @belongsTo
- @manyToMany
@key
同様、Directiveが何をしているのか、どういうデータ構造なのか。より明示的な記述ができるようになったと思います。リレーションを貼る先のテーブルにIDフィールドがある場合、引数が全て省略できるので、非常に簡潔ですね!
また、v1ではgetBlog
をコールした際に、紐づくPostをスキャンを回避して効率的にPostテーブルからとってくるために、
- 予めPostテーブルに
blogID
フィールドを作成し@key
でGSIを貼っておく - Blogモデル側で
@connection
を貼る際に1.で貼ったインデックスを指定する
必要がありました。
v2では
-
@hasMany
を付与する
だけで、Post側のGSIの作成まで自動で行ってくれます。
図の通りのスキーマのGraphQL APIをデプロイし、DynamoDBを確認してみると
-
BlogPostsId
フィールド -
BlogPostsId
フィールドを利用したGSI
の2つが自動的に追加されているのがわかります。
このGSIは、getBlog
のようなオペレーションに利用され、Blogに紐づくPostを効率的に取得するために利用されます。
詳しくはこちら[5:1]のドキュメントを参照ください。
@auth
における Deny-by-default 原則の適用
v2ではDeny-by-defaultの原則が適用され、v1のAllow-by-defaultとはおなじ@auth
の記述をした際の挙動が変わっています。
v1との違いをみながら理解を深めていきましょう。
type Employee @model @auth(rules: [
{allow: private, operations: [read]},
{allow: owner, operations: [create, read, update]}
]){
name: String
email: String
ssn: String
}
上記のようなスキーマが与えられたとき、v1とv2でどのような違いが現れるでしょうか?
図にしてみました。
図中で
- ✅はAPIコールの権限があることを、❌は権限がないことを示しています
- ownerはそのアイテムを作成したユーザーを示します
- otherはowner以外の認証済みユーザーを示します
- createを呼ぶ際はすべてのユーザーがそのアイテムのownerとなく、事実上otherがcreateを呼ぶことはないため、該当セルは
-
と表記しています
deleteに関する扱いが違うことがわかります。
v1の場合、
{allow: owner, operations: [create, read, update]}
のoperationsで指定されないdeleteは、Allow-by-defalutの原則 により、ownerでもotherでも権限を持つ
という挙動となっていました。
v2の場合、
{allow: owner, operations: [create, read, update]}
のoperationsで指定されないdeleteは、Deny-by-defaultの原則 により、ownerでもotherも権限を持たない
という、より直感的でセキュアな挙動となりました。
$ amplify status api -acm <YOUR_MODEL_NAME>
$ amplify status api -acm <YOUR_MODEL_NAME>
コマンドが追加され、各モデルのアクセス権限がわかりやすく表示できるようになっています
試しに記事中のスキーマを再現して実行してみました。
記事と同じアクセス権限の対応表が出力されることがわかります。
v1ではAmplify Mockingをしながら期待通りの振る舞いをするか検証するしかありませんでした。
v2ではこのコマンドの追加によってより気軽に確認できるようになったと思います。
自動生成されるResolverがPipeline Resolverに
Pipeline Resolverとは、複数のResolverを順番に実行して一つのクエリにレスポンスを返す機能です[7]。
v2では Amplify CLIはPipeline Resolverを用いて、ビジネスロジックを複数のVTLファイルに分割しています。
例えば v1では
Mutation.createTodo.req.vtl
のような1ファイルで表現されていたファイルが、
v2では以下の計7ファイルに分割されています。
Mutation.createTodo.init.1.req.vtl
Mutation.createTodo.preAuth.1.req.vtl
Mutation.createTodo.auth.1.req.vtl
Mutation.createTodo.postAuth.1.req.vtl
Mutation.createTodo.preDataLoad.1.req.vtl
Mutation.createTodo.postDataLoad.1.req.vtl
Mutation.createTodo.finish.1.req.vtl
これにより、Amplifyが生成したResolverを上書きする際に、一部分にだけ変更を施したり、特定のロジックの実行後に任意の処理を挟む、ということが容易になりました[8]。一方でv1でCustom Resolverを利用されている方は後述の通り、手動のマイグレーションが必要となります。
@versioned
の廃止
AppSync + DynamoDBの組み合わせでItemのバージョニングによって競合解決を行う @versioned
が廃止されました。v1のドキュメント[9]には
Note that @versioned is only supported in client code (statement and types) generated via AppSync codegen. @versioned is not supported by models generated via amplify codegen models. Use Amplify DataStore instead of @versioned to provide offline app data access with built-in conflict-resolution.
とあり、競合解決が必要なオフラインでのデータアクセスが想定されるアプリケーションではDataStoreを使うことが推奨されていました。今後は@versioned
が廃止されたため、DataStoreが基本的には唯一の選択肢となりそうです。
今回の変更では以下の記述しかなく、具体的なMigration方法も提示されていないのが現状です。
The @versioned directive is deprecated in favor of AppSync’s built-in conflict detection and resolution feature.
Amplify CLIのGitHubにIssueが上がっています[10]が、今の所解決策は提示されていません。
現状v1で@versioned
を利用している場合は、v2へのマイグレーションは待つのが良さそうです。
v1からのMigration
本セクションではGraphQL Transformer v1 to v2 migrationをもとに、
- 手動のMigrationを伴うケース
- Migrationの実施を見合わせたほうが良いケース
-
$ amplify migrate api
コマンドによるv2マイグレーション - Migrationを実施したくない場合のTips
についてまとめてみました。v1からのマイグレーションが発生しない場合は、このセクションはスキップしていただいて大丈夫です。
手動のMigrationを伴うケース
Custom Resolverを利用している
v1では
- Query, Mutation, Subscription typeをスキーマに追加
-
<project-root>/amplify/backend/api/<api-name>/resolvers/
にマッピングテンプレート(VTLファイル)を<TypeName>.<FieldName>.<req/res>.vtl
というファイル名で配置 -
<project-root>/amplify/backend/api/<api-name>/stacks/CustomResource.json
を編集し読み込めるようにする
というフローでCustom Resolverを作成していました。(詳細はこちら[11])
v2では
- Query, Mutation, Subscription typeをスキーマに追加
-
$ amplify add custom
でCDKを選択(CloudFormationも利用可) -
<project-root>/amplify/backend/custom/MyCustomResolvers/
に<TypeName>.<FieldName>.<req/res>.vtl
を配置 -
<project-root>/amplify/backend/custom/cdk-stack.ts
をCustom Resolverを利用できるよう編集
CDKを利用できるようになってはいますが、Custom Resolverを配置する位置や、読み込むプロセスが大幅に変わっています。
v2の手順[12]を参考にして、手動で作成し直す必要があります。
ちなみに、カスタムのビジネスロジックを書くための手法はCustom business logic (Lambda function, HTTP, and VTL resolvers)というドキュメントにまとめられるようになり、
- Lambda function resolver: use a custom Lambda function to handle query or mutation
- HTTP resolver: call an HTTP endpoint upon a query or mutation
- VTL resolver (most advanced): use VTL mapping templates to customize the query and mutation logic
VTLを利用する手法は"最も高度"という注意書きが入るようになりました。これからビジネスロジックを書く際は、Lambda Function Resolver (@function)で実現できないか、最初に検討すると良さそうです。(個人的にはVTL書きたくないというのもあり、以前から@functionを利用することが多いです)
Amplifyが生成したVTLファイルを上書きしていた
"自動生成されるResolverがPipeline Resolverに"の項目で紹介したとおり、Amplify CLIでは全面的にPipeline Resolverが採用されました。そのためv1の形式で1ファイルに纏められていたロジックを、v2の形式で分割して書き直す必要があります。
具体的には、以下のような方法を取る必要があります。
-
<project-root>/amplify/backend/api/<api-name>/build/resolvers/
にv2 によって生成されたVTLファイルを確認し、どれを上書きする必要があるか調べます -
<project-root>/amplify/backend/api/<api-name>/resolvers/
内に該当ファイルをコピーし、内容を書き換えます
@searchable
を利用している
When migrating to the new GraphQL Transformer, the OpenSearch domain will be replaced with OpenSearch version 7.10 which may result in data loss due to the breaking changes
ということで、
v2では @searchable
でセットアップされるAmazon OpenSearch Service(旧Amazon Elasticsearch Service)のドメインは自動的にversion 7.10に置き換わります。これによりOpenSearchのドメインに保存されたデータがロストする可能性があります。
マイグレーションの実施前に、スナップショットを作成、マイグレーション後にスナップショットを復元する必要があります。[13]
Migrationの実施を見合わせたほうが良いケース
@versioned
を利用している場合
前述の通り、現状v2に同等の機能がないため、マイグレーション方針がサービスチームから提示されるまではv2へのマイグレーションを見合わせるのが無難かと思います。
$ amplify migrate api
コマンドによるv2マイグレーション
v2の登場と同時に、v1のスキーマをv2にマイグレーションする$ amplify migrate api
コマンドが追加されています。@key
@auth
@connection
についてはこちらのコマンドで自動的なマイグレーションが可能です。
コマンドの中身
該当コマンドのコード[14]を見るに、$ amplify migrate api
コマンドは大きく3つのオペレーションを実施しています。
- コマンド実行前の
schema.graphql
ファイルのバックアップを取る -
schema.graphql
で使用されている@key
@auth
@connection
をv2の記法へ変換する -
<project-root>/amplify/cli.json
のtransformerversion
を1
から2
へ変える
ちなみに、3.で登場するcli.json
はAmplify CLIのFeature Flags機能の設定ファイルで、様々な機能の有効化・無効化などを制御しています[15]。
検証時の注意点
コマンド実行後に新旧スキーマの対応を確認する際の注意点として、一部v1/v2のディレクティブの対応が直感的でない部分があります。
例えばv1の@connection
を利用したmany to manyなリレーションは、v2では@manyToMany
ではなく@hasMany
と@belongsTo
を用いて表現されるたりします。
詳細なv1のディレクティブとv2のディレクティブのマッピングはドキュメント[16]をご参照ください。
マイグレーションを途中でやめたい場合
$ amplify migrate api
実行後に、うまく変換されていない、$ amplify api gql-compile
でエラーが出るなど、もとの状態に戻したくなる場合もあるかと思います。
その場合には
$ amplify migrate api --revert
を実行することで元の状態に戻すことが可能です。具体的には以下のオペレーションを実施します。
-
schema.graphql
ファイルをバックアップを用いて、$ amplify migrate api
実行前の状態に復元 -
<project-root>/amplify/cli.json
のtransformerversion
を2
から1
へ変える
まとめ
Amplify CLI v1とv2の変更点、v1からv2へのマイグレーション方法などについてまとめてみました。v2はより直感的にGraphQL APIが記述できるようアップデートされており、開発が捗りそうです。一方で、v1からのBreaking Changeも多く、既存ユーザーにとってはマイグレーションの意思決定が難しいかもしれません。本記事がマイグレーションの意思決定や、実施手順の考案の参考になれば幸いです。
おまけ
Q)Amplify CLI v7以前に作成したプロジェクトを使用しています。v2へのマイグレーションをするまでAmplify CLI v7にアップデートしないほうが良いのでしょうか?
Amplify CLIでは<project-root>/amplify/cli.json
で機能の有効化・無効化を管理しています。GraphQL Transfomerのバージョンもcli.json
で管理されており、手動で変更するか、$ amplify migrate api
コマンドを実行するまで自動的にv2になることはありません。
Q)公式ドキュメントを開くとv2のドキュメントになってしまっています。v1のドキュメントはどこでみれるのでしょうか?
公式ドキュメントでGraphQL APIセクションを開くと、左下にv1のドキュメントへのリンクが出てきます。
v1のドキュメントはcli-legacy
URLパス配下に引っ越ししました。
-
https://aws.amazon.com/blogs/mobile/aws-amplify-announces-the-new-graphql-transformer-v2-more-feature-rich-flexible-and-extensible/ ↩︎ ↩︎
-
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.SecondaryIndexes ↩︎
-
https://docs.amplify.aws/cli-legacy/graphql-transformer/key/ ↩︎
-
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.PrimaryKey ↩︎ ↩︎
-
https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/pipeline-resolvers.html ↩︎
-
https://docs.amplify.aws/cli/graphql/custom-business-logic/#extend-amplify-generated-resolvers ↩︎
-
https://docs.amplify.aws/cli-legacy/graphql-transformer/versioned/ ↩︎
-
https://docs.amplify.aws/cli-legacy/graphql-transformer/resolvers/#custom-resolvers ↩︎
-
https://docs.amplify.aws/cli/graphql/custom-business-logic/#vtl-resolver ↩︎
-
https://docs.aws.amazon.com/opensearch-service/latest/developerguide/managedomains-snapshots.html ↩︎
-
https://github.com/aws-amplify/amplify-cli/tree/master/packages/amplify-graphql-transformer-migrator ↩︎
-
https://docs.amplify.aws/cli/reference/feature-flags/#feature ↩︎
-
https://docs.amplify.aws/cli/migration/transformer-migration/#changes-that-amplify-cli-will-auto-migrate-for-you ↩︎
Discussion