☃️

Amplify GraphQL Transformer v2 まとめ

2021/12/19に公開

AWS Amplify Advent Calendar 2021」 19日目の記事となります

記事の概要

2021/11/23に、Amplify GraphQL Transformer v2(以下v2と表記)が発表されました[1]。以前のGraphQL Transformer v1(以下v1と表記)から大幅なアップデートがいくつも入っており、Breaking Changeも少なからずあります。この記事では

  1. GraphQL Transformer v2の主な変更点
  2. GraphQL Transformer v1からのMigration時の注意事項

の2点についてまとめてみました!

想定読者

  • Amplify CLIでGraphQL APIを利用している人
  • これから利用を検討しているが、既存のブログ記事などがv1で記述されており差分が知りたい人

話さないこと(及び参考資料)

主な変更点

本セクションでは、以下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テーブルからとってくるために、

  1. 予めPostテーブルにblogIDフィールドを作成し@keyでGSIを貼っておく
  2. Blogモデル側で@connectionを貼る際に1.で貼ったインデックスを指定する

必要がありました。

v2では

  1. @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との違いをみながら理解を深めていきましょう。

schema.graphql
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では

  1. Query, Mutation, Subscription typeをスキーマに追加
  2. <project-root>/amplify/backend/api/<api-name>/resolvers/にマッピングテンプレート(VTLファイル)を<TypeName>.<FieldName>.<req/res>.vtlというファイル名で配置
  3. <project-root>/amplify/backend/api/<api-name>/stacks/CustomResource.jsonを編集し読み込めるようにする

というフローでCustom Resolverを作成していました。(詳細はこちら[11])

v2では

  1. Query, Mutation, Subscription typeをスキーマに追加
  2. $ amplify add custom でCDKを選択(CloudFormationも利用可)
  3. <project-root>/amplify/backend/custom/MyCustomResolvers/<TypeName>.<FieldName>.<req/res>.vtlを配置
  4. <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の形式で分割して書き直す必要があります。

具体的には、以下のような方法を取る必要があります。

  1. <project-root>/amplify/backend/api/<api-name>/build/resolvers/にv2 によって生成されたVTLファイルを確認し、どれを上書きする必要があるか調べます
  2. <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つのオペレーションを実施しています。

  1. コマンド実行前のschema.graphqlファイルのバックアップを取る
  2. schema.graphqlで使用されている @key @auth @connectionをv2の記法へ変換する
  3. <project-root>/amplify/cli.jsontransformerversion1から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
を実行することで元の状態に戻すことが可能です。具体的には以下のオペレーションを実施します。

  1. schema.graphqlファイルをバックアップを用いて、$ amplify migrate api実行前の状態に復元
  2. <project-root>/amplify/cli.jsontransformerversion2から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-legacyURLパス配下に引っ越ししました。
https://docs.amplify.aws/cli-legacy/graphql-transformer/overview/

脚注
  1. https://aws.amazon.com/blogs/mobile/aws-amplify-announces-the-new-graphql-transformer-v2-more-feature-rich-flexible-and-extensible/ ↩︎ ↩︎

  2. https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.SecondaryIndexes ↩︎

  3. https://docs.amplify.aws/cli-legacy/graphql-transformer/key/ ↩︎

  4. https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.PrimaryKey ↩︎ ↩︎

  5. https://docs.amplify.aws/cli/graphql/data-modeling/ ↩︎ ↩︎

  6. https://qiita.com/nagym/items/58b7847d171b57f0019f ↩︎

  7. https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/pipeline-resolvers.html ↩︎

  8. https://docs.amplify.aws/cli/graphql/custom-business-logic/#extend-amplify-generated-resolvers ↩︎

  9. https://docs.amplify.aws/cli-legacy/graphql-transformer/versioned/ ↩︎

  10. https://github.com/aws-amplify/amplify-cli/issues/9164 ↩︎

  11. https://docs.amplify.aws/cli-legacy/graphql-transformer/resolvers/#custom-resolvers ↩︎

  12. https://docs.amplify.aws/cli/graphql/custom-business-logic/#vtl-resolver ↩︎

  13. https://docs.aws.amazon.com/opensearch-service/latest/developerguide/managedomains-snapshots.html ↩︎

  14. https://github.com/aws-amplify/amplify-cli/tree/master/packages/amplify-graphql-transformer-migrator ↩︎

  15. https://docs.amplify.aws/cli/reference/feature-flags/#feature ↩︎

  16. https://docs.amplify.aws/cli/migration/transformer-migration/#changes-that-amplify-cli-will-auto-migrate-for-you ↩︎

Discussion