Amazon Web Services ブログ

AWS AppSync Merged API の紹介

AWS AppSync は、GraphQL API を簡単に作成、管理、監視、セキュリティ保護できるサーバーレス GraphQL サービスです。開発者は AppSync API 内で、Amazon DynamoDBAWS Lambda、HTTP API を含む複数の異なるデータソースに対して横断的にアクセスすることができます。サービスの普及が進むにつれ、お客様は組織内の複数のチームや AWS アカウントに跨がるチームコラボレーションに関連する課題に直面しています。各 AppSync API には、単一の GraphQL スキーマと構成されたデータソース、リゾルバ、および関数があります。

単一の GraphQL API エンドポイントを通じて複数のマイクロサービスを公開する場合、API の一部を所有する複数のチームは、同じ API で同時に作業する必要があります。適切な分離とガードレールなしに同じ API で作業する複数の開発者チームは、偶然に変更を壊してしまう可能性があります。
例えば、チーム A の開発者が以前にチーム B が実装した機能を壊す可能性のある API の変更をプッシュしたり、その逆が起こったりすることがあります。もうひとつの課題はサポートとメンテナンスです。単一の GraphQL API を使用している場合、グラフの一部分に問題があるかどうかをトラブルシューティングすることは困難です。また、同じグラフに複数のビジネスドメインを持つ企業では、一人の人間がグラフ全体を理解することはできず、メンテナンスは困難になります。

2023 年 5 月 23 日に AWS AppSync の Merged API の一般提供を開始します。Merged API により、チームは複数の Source AppSync API から型、データソース、関数、リゾルバなどの リソースを単一の統一された AppSync エンドポイントにマージすることができます。最大 10 個の Source AppSync API を1つの Merged API にマージすることができます。フロントエンドでは、クライアントは単一のエンドポイントとやりとりするだけで、複数の Source API に跨がるデータを取得することができます。バックエンドでは、開発チームは CI/CD パイプラインの一部として、独立した Source API を作成、更新、テスト、デプロイすることができます。変更が承認されると、他の Source API からの変更をブロックすることなく、クライアントが利用できるようにするために、変更を Merged API エンドポイントにマージすることができます。Merged API 上での Query、Mutation、Subscription の実行は、AppSync によって処理され、 Source AppSync API と同じモニタリングとパフォーマンス体験を提供します。

下図は、異なるチームに跨がる複数の Source API で Merged API が構成される例を示しています。

図1:複数のSource APIにまたがって構成されるMerged APIの例

スキーマディレクティブ

Source スキーマの定義間で競合がある場合、新しい GraphQL ディレクティブを使用することで、競合を解決する柔軟性を提供することができます。

  • @canonical:2 つ以上の Source API が同じ型またはフィールドを持つ場合、API の 1 つがその型またはフィールドを canonical として注釈することができ、スキーマをマージするときに優先されます。他の Source API でこのディレクティブがない競合する型は、マージされるときに無視されます。
  • @hidden:内部クライアントのみが特定の型データにアクセスできるように、ターゲット API で特定の型や操作を削除または非表示にしたい場合があります。このディレクティブが付加されている場合、型やフィールドはマージ API ターゲットにマージされません。
  • @renamed: 異なる API が同じ型やフィールド名を持っているユースケースがあります。しかし、それらはすべてマージされたスキーマで利用可能である必要があります。これらの型を Merged API ターゲットで使用する簡単な方法は、名前の衝突を処理するためにそれらのうちの 1 つの名前を変更することです。

自動マージ

AppSync は Source API からMerged API に変更をマージする複数の方法を提供します。デフォルトでは、これらのマージ操作は手動で行われます。ただし、自動マージモードを有効にすると、 Source API が更新されるたびに、 Merged API にマージ操作を送信します。この仕組みは、自動マージを有効にした Source API への変更を Merged API がサブスクライブすることです。AppSync は変更があると Merged API上で構成された Merged API 実行ロールを引き受け、Merged API 所有者に代わって StartSchemaMergeを実行し、変更をマージします。 Source API の型、データソース、リゾルバ、または関数を変更すると、自動マージによって対応する Merged API への更新がトリガーされます。

他のアプローチとの比較

GraphQL コミュニティには、GraphQL スキーマを結合し、共有グラフを通じてチームコラボレーションを可能にするための多くのソリューションとパターンがあります。Merged API はスキーマ合成に「ビルドタイム」アプローチを採用し、 Source API を個別の Merged API に結合します。別のアプローチとして、複数の Source API * やサブグラフに跨がる「*ランタイム」ルーターを重ねる方法があります。このアプローチでは、ルーターはリクエストを受け取り、メタデータとして保持する結合スキーマを参照し、リクエストプランを構築し、その下にあるサブグラフ/サーバーにリクエスト要素を分散させます。

Merged API は、AppSync Source API にのみ関連付けることができます。AppSync と非 AppSync のサブグラフ間でスキーマ合成のサポートが必要な場合、1つ以上の AppSync GraphQL API および/または Merged API をルーターベースのソリューションに接続できます。

シナリオ:書籍のレビューとレコメンデーションのための Merged API

次の例では、Goodreads に似た、書籍のレビューとレコメンデーションの Web サイトのバックエンドを動かす Merged API を作成します。このシナリオでは、以下のような別々のチームが所有し開発する複数の Source API を用意します。

  1. Books Source API
    このチームは、サイトのカタログにある各書籍に関連するデータを保存する責任を負っています。このチームは、書籍のメタデータを照会し、カタログから書籍を追加、更新、削除する機能を提供する Source API を管理します。
  2. Authors Source API
    サイトカタログの書籍の著者に関するデータの保存を担当するチームです。この Source API は、名前と電子メールを含む著者に関連する情報を照会する機能と、カタログの著者を追加、更新、削除するための Mutation を提供します。
  3. Reviews Source API
    サイトのカタログにある書籍のレビューに関連するデータを格納するチームです。サイトのユーザーは、コメントや評価など、書籍のレビューを追加することができます。この Source API は、書籍や著者ごとにレビューを照会したり、レビューを追加、更新、削除したりする機能を提供する予定です。
  4. Users Source API
    サイトのユーザーに関するデータの取り扱いを担当するチームです。サイトのユーザーは、書籍のレビューだけでなく、リーディングリストの作成も可能です。この Source API は、指定したユーザーに関連する情報や、サイト上の指定したユーザーのリーディングリストを照会する機能を提供する予定です。ユーザーエントリーは、この Source API 内で追加、更新、削除することができます。

Source API の作成

書評サイトの Merged API を構成する Source API を作成します。本シナリオでは、データソースとして DynamoDB を使用します。あなたのユースケースに基づいて、AWS AppSync がサポートする他のデータソースを使用することもできます。

Books Source API の作成

  • AppSync コンソールに移動し、Create API をクリックします。API 作成ウィザードの Step1では、GraphQL APIsDesign from scratch を選択して Next をクリックします。

  • ウィザードの Step2では、Merged API の所有者が必要に応じて問題を調査する際に Books チームに連絡できるように、Books API のメタデータを追加します。

:ユースケースに必要であれば、 Source API を作成する際に Private API も使用できます。

  • ウィザードの Step3 では、Book のメタデータを格納するために、DynamoDB テーブルをバックエンドとして使用する Book 型を生成します。
  • このモデルでは、各書籍に必要な authorId フィールドを含んでいます。このフィールドは、Authors Source API から著者に関連するメタデータを取得するために使用されるキーです。 Source スキーマを構築する場合、異なる Source API 間でデータを結合しやすくするために、ID フィールドなどの一意のキーで型を識別することが推奨されます。
  • DynamoDB テーブルでは、特定の著者または特定の出版社の書籍を照会できるようにするためのインデックスを追加します。

  • ウィザードの Step4 で、API の詳細を確認し、Create API をクリックします。API と DynamoDB テーブルの作成が開始されます。

Authors Source API の作成

  • AppSync コンソールに移動し、Create API をクリックします。API 作成ウィザードの Step1では、GraphQL APIsDesign from scratch を選択して Next をクリックします。
  • ウィザードの Step2 では、Merged API の所有者が必要に応じて問題を調査する際に Authors チームに連絡できるように、Authors API のメタデータを追加します。

  • ウィザードの Step3 では、DynamoDB テーブルに紐付けられた Author 型を生成します。このテーブルのモデルには、著者の id、name、bio、contactEmail、および nationality が含まれます。

* ウィザードの Step4 で、API の詳細を確認し、Create API をクリックします。API と DynamoDB テーブルの作成が開始されます。

Reviews Source API の作成

  • AppSync コンソールに移動し、Create API をクリックします。API 作成ウィザードの Step1では、GraphQL APIsDesign from scratch を選択して Next をクリックします。
  • ウィザードの Step2 では、Merged API の所有者が必要に応じて問題を調査する際に Reviews チームに連絡できるように、Reviews API のメタデータを追加します。

  • ウィザードの Step3では、DynamoDB テーブルに紐付けられた Review 型を生成します。Review 型には、レビューを書いたユーザーを参照する reviewerId、レビューされた書籍の著者を参照する authorId、レビューされた書籍を参照する bookId のほか、idcommentratingcreatedAtreedatedAt が含まれます。DynamoDB テーブルにインデックスを追加して、ある書籍のレビューとあるレビュアーのレビューを照会できるようにします。

Users Source API の作成

  • AppSync コンソールに移動し、Create API をクリックします。API 作成ウィザードの Step1では、GraphQL APIsDesign from scratch を選択して Next をクリックします。
  • ウィザードの Step2 では、Merged API の所有者が必要に応じて問題を調査する際に Users チームに連絡できるように、Users API のメタデータを追加します。

  • ウィザードの Step3 では、idnameemail フィールドを持つ生成された DynamoDB テーブルに紐付けられた User 型を追加します。

  • ウィザードの Step4 で、API の詳細を確認し、Create APIをクリックします。APIDynamoDB テーブルの作成が開始されます。

Merged AppSync API の作成

4 つの Source API すべてが作成できたので、次は Merged API を作成します。

  • AppSync コンソールに移動し、Create API をクリックします。API 作成ウィザードの Step1で、Merged APIs を選択し、Next をクリックします。

  • 先ほどと同様に、 Step2 で API 名と連絡先を追加します。
  • このシナリオでは、Merged API 用に新しいサービスロールを作成します。
    • Merged API 実行ロールは、新しい IAMアクション appsync:SourceGraphQL を使用して、 Query および Mutation 中に Source API に安全にアクセスします。
    • AppSync は Source API からマージされた設定されたリゾルバを持つ各トップレベルフィールドのリクエスト内のトップレベルフィールド ARN にこの権限を要求します。appsync:GraphQL 権限は AppSyncで IAM 認可モードがどのように機能するかと同様に、特定のトップレベルフィールドを許可または拒否するように選択できます。トップレベル以外のフィールドについては、AppSync は Source API ARN 自体の権限を必要とします。
    • 例えば、以下は Merged API 実行ロールのポリシーです。
{
            "Effect": "Allow",
            "Action": [
                "appsync:SourceGraphQL"
            ],
            "Resource": [
                "arn:aws:appsync:us-east-1:123456789012:apis/<Source API Id>",
                "arn:aws:appsync:us-east-1:123456789012:apis/<Source API Id/*>"
            ]
        }
    • 特定の GraphQL 操作のみにアクセスを制限したい場合は、ルートの Query、Mutation、Subscription フィールドに対してこれを行うことができます。
{
            "Effect": "Allow",
            "Action": [
                "appsync:SourceGraphQL"
            ],
            "Resource": [
                "arn:aws:appsync:us-east-1:123456789012:apis/<Source API Id>/types/Query/fields/<Field-1>",
                "arn:aws:appsync:us-east-1:123456789012:apis/<Source API Id/types/Mutation/fields/<Field-1>"
                "arn:aws:appsync:us-east-1:123456789012:apis/<Source API Id/types/Subscription/fields/<Field-1>"
            ]
        }
  • Merged API の IAM 認可については、ドキュメントを参照してください。

  •  Step3 で Merged API に Source API の関連付けを追加します。AppSync は自分のアカウントからの Source APIと AWS Resource Access Manager(AWS RAM)を介して共有されている他のアカウントからの Source API の追加をサポートしています。

:ユースケースに必要であれば、Merged API を作成する際に Private API 機能を使用可能です。

  • 今回は先程作成した Source API を追加することにします。次のブログでは、AWS RAM の別のアカウントから Source API を共有する方法について説明します。
  • Add Source APIs をクリックし、Users APIAuthors APIReviews APIBooks API の Source を確認します。最後に、Add Source APIs をクリックして確認します。最後に、Next をクリックして Step3 を終了します。
  • : Source API を関連付けるには、関連付けの両方のリソースに対する権限が必要です。この操作には、 Merged API に appsync:AssociateSourceGraphqlApi アクション、 Source API に appsync:AssociateMergedGraphqlApi が必要です。一方、関連付けを解除する操作では、関連付けのリソースの 1 つに対する権限のみが必要です。例えば、DisassociateSourceGraphqlApi は、Merged API から Source API を削除するために、Merged API に対する権限を必要とします。DisassociateMergedGraphqlApi は、 Source API をその Merged API から関連付けを解除するために使用でき、この操作は Source API に対して権限を与ます。

  • ウィザードの Step4 では、認証モードを選択します。本シナリオでは全ての Source API のプライマリ認証モードと一致するように、デフォルトの API キーのプライマリ認証モードを使用することにします。
    • 関連付けに互換性を持たせるために、Merged API は各 Source API のプライマリ認証モードを、その構成にプライマリ認証モードまたは追加認証モードとして含める必要があります。

  • ウィザードの Step 5で、構成を確認し、Create API をクリックします。
    • Merged API の作成により、構成されていた全ての Source API が関連付けられ、 Source API が統一エンドポイントにマージされます。ページ上部のフラッシュバーは、これらの関連付けのいずれかが競合により失敗したかどうかを示すために使用されます。
    • 各 Source API の関連付けのステータスは、Merged API のメインページで確認できます。ここでは、現在のマージステータス、最後に成功したマージ、所有者の連絡先、および関連付けられた Source API へのリンクに関する情報を確認できます。

:マージステータスカラムにカーソルを合わせてクリックすると、個々の Source API のマージの成功または失敗に関連する詳細なメッセージを表示することができます。

  • Merged API が作成されたので、スキーマを表示して、Source API からの全ての型が Merged API エンドポイントで利用可能であることを確認できます。

Source API を跨いだデータの結合

4 つの異なる Source API からのスキーマのマージに成功したので、それぞれの Source API から独立してデータにアクセスすることができるようになりました。しかし、Merged API の真の力は、異なるチーム間でデータを結合する能力です。本シナリオでは、クライアントにもっと便利な体験を提供するためにデータを結合する機会が多くあります。例えば、Merged API エンドポイントに問い合わせる際、著者に関する基本的な情報を取得しながら、その著者が書いたカタログの書籍のページ付きリストを返したいと考えるかもしれない。さらに一歩進んで、リスト内の各書籍のレビューのリストも取得したいと思うかもしれません。

異なる Source API 間でデータを結合するためには、結合する各型に、別の Source API でこのリレーションを参照するために使用できる、よく知られた主キーフィールドまたはフィールドがあることを確認する必要があります。この例で行っているように、ID フィールドを使用して各型を識別するのが一般的です。Books Source API の各 Book 型に対して、Authors Source API から Author に関するデータを結合するために使用できる authorId フィールドを格納します。authorId は、著者に関するデータを取得するための Key として機能します。次に、これらの Key フィールドを使用して、データを結合する方法を説明します。

フィールドリゾルバを使用した書籍データと著者データの結合

  • Books 型と Authors 型の間でデータを結合するための最初のステップは、Authors Source API 内で Book 型を定義することです。この Book 型は Book 定義のサブセットとして機能し、Authors Source API で関連するフィールドのみを含みます。
  • Merged APIテーブルのリンクから Authors Source API に移動し、スキーマに以下の定義を追加します。右上の Save Schema をクリックします。
type Book {
    authorId: ID!
    author: Author
}
  • 次に、Book.author フィールドにリゾルバを追加し、与えられた書籍の著者を返すようにします。Book サービスでは、著者 ID を authorId フィールドとして格納しています。リゾルバは、このauthorId フィールドを context.source オブジェクトからアクセスすることになりますが、これは Book Source API が所有する親リゾルバが取得することになります。
  • Authors Source API で GetAuthorByParentAuthorId という関数を作成します。functions に移動して右上の Create function をクリックします。この関数は、DynamoDB Authors Table をデータソースとして使用します。
  • この関数は context.source オブジェクトから authorId を使用して、DynamoDB テーブルの GetItem を呼び出します。この関数のコードを JavaScript で書くと、次のようになります。

export function request(ctx) {
    return {
        operation: 'GetItem',
        key: {
            id: {
                'S': ctx.source.authorId
            }
        }
    }
}

export function response(ctx) {
    if (ctx.error) {
        util.error(ctx.error.message, ctx.error.type, ctx.result)
    }
    
    return ctx.result
}
  • 右上の Create をクリックし、関数を作成します。
  • 関数が作成されたので、Authors Source API の Book.author フィールドにアタッチされた Pipeline リゾルバでこの関数を使用することにします。Resolvers セクションの Book.author フィールドに移動し、Attach をクリックします。

  • Pipeline リゾルバにはデフォルトのリクエストとレスポンスコードを使用し、先ほど作成した関数を追加します。右上の Create をクリックし、リゾルバを作成します。

  • このリゾルバを作成したことで、これらの Source API 間で適切にデータをリンクさせることができるようになりました。このリゾルバを Merged API で利用できるようにするためには、このアップデートをマージする必要があります。これを行うには、Authors Source API の設定ページに移動し、Merge Now をクリックします。

  • リゾルバが正常にマージされたので、 Merged API の Book 型に author のフィールドが含まれ、その authorフィールドにパイプラインリゾルバが定義されていることを確認します。
type Book {
    id: ID!
    title: String!
    authorId: ID!
    publisherId: ID
    author: Author
    genre: String
    publicationYear: Int
}

フィールドリゾルバを使用したレビューデータと著者データの結合

  • 次に、レビューデータと著者データを結合するためのリゾルバを追加します。
  • Authors Source API で、 Source API スキーマに別の型を追加します。
type Review {
   authorId: ID!
   author: Author
}
  • この型は、与えられた Review の著者を一回の Query で取得する機能を追加するために使用されます。
  • Authors Source API の Review.author フィールドにパイプラインリゾルバを作成します。パイプラインリゾルバは、デフォルトのリクエストリゾルバとレスポンスリゾルバのコードを使用する必要があります。Review 型は authorId フィールドを使って著者も参照するので、このリゾルバにも上記で使用した GetAuthorByParentAuthorIdas という同じ関数を追加することにします。
  • パイプラインリゾルバが追加されたら、設定ページの Merge Now をクリックし、再び Merged API に更新をマージします。

フィールドリゾルバを使用した書籍データとレビューデータの結合

  • 次に、レビューとサイトカタログの書籍を結合するためのリゾルバを追加します。
  • Reviews Source API に移動します。
    • 各レビュー型には、どの書籍についてのレビューかを示す bookId があるので、Reviews Source API に参照用の Book 型を追加して、与えられた書籍のレビューのページ分割リストを次のように追加することができます。
type Book {
    id: ID!
    reviews: ReviewConnection
}
  • ReviewsTable をデータソースとする関数を作成し、書籍ID でレビューテーブルを照会して、各書籍のレビューのページ付きリストを返します。書籍 ID は、Book 型の id フィールドでアクセスできます。この id フィールドには、ctx.source オブジェクトを通してアクセスします。以下のコードで関数の JavaScript コードを作成し、 GetReviewsByBook と名付けることにします。
export function request(ctx) {
    return {
        operation: 'Query',
        query: {
            expression: '#bookId = :bookId',
            expressionNames: {
                '#bookId': 'bookId'
            },
            expressionValues: {
                ':bookId': util.dynamodb.toDynamoDB(ctx.source.id)
            }
        },
        index: 'reviews-by-book-index',
        scanIndexForward: true,
        select: 'ALL_ATTRIBUTES'
    }
}

export function response(ctx) {
    if (ctx.error) {
        util.error(ctx.error.message, ctx.error.type, ctx.result)
    }
    
    return ctx.result
}
  • デフォルトのリクエストとレスポンスのリゾルバコードを持つパイプラインリゾルバに、この関数を追加します。今回は、追加した Author.reviews フィールドにアタッチします。Reviews source API の設定ページで Merge Now をクリックし、更新を Merged API にマージします。

フィールドリゾルバを使用した著者データと書籍データの結合

  • 書籍データを結合することで、指定した著者の書籍を取得する機能を追加します。
  • Books Source API に移動して、新しい参照 Author 型を作成します。この型には、主キーである id フィールドと、指定した著者が書いた書籍のページ付きリストを返す新しいフィールドが含まれます。
type Author {
   id: ID!
   books: BookConnection
}
  • 著者 ID は、このリゾルバーの ctx.source オブジェクトでアクセスできるます。この id を使用して、authorId が一致する書籍の全エントリを検索することになります。
  • Books Source API に GetBooksForAuthor という新しい関数を作成し、データソースとして Books テーブルを使用します。
export function request(ctx) {
    return {
        operation: 'Query',
        query: {
            expression: '#authorId = :authorId',
            expressionNames: {
                '#authorId': 'authorId'
            },
            expressionValues: {
                ':authorId': util.dynamodb.toDynamoDB(ctx.source.id)
            }
        },
        index: 'author-index',
        scanIndexForward: true,
        select: 'ALL_ATTRIBUTES'
    }
}

export function response(ctx) {
    if (ctx.error) {
        util.error(ctx.error.message, ctx.error.type, ctx.result)
    }
    
    return ctx.result
}
  • Author.books フィールドのデフォルトのパイプラインリゾルバに関数を追加し、パイプラインリゾルバを保存します。設定ページの Merge Now をクリックして、更新を Merged API にマージします。

フィールドリゾルバを使用したレビューデータと書籍データの結合

  • 最後に、与えられたレビューの書籍情報を取得するリゾルバを追加して、書籍データとレビューデータを結合します。Reviews Source API の各 Review 型には bookId フィールドがあり、この結合を行う際に使用できます。
  • Books Source API のまま、新しい参照型を作成して、次のように Review 型に Book フィールドを追加します。
type Review {
   bookId: ID!
   book: Book
}
  • ctx.source オブジェクトで見つかった bookId を使用し、Books テーブルの GetItem を呼び出してこのブックに関連するデータを取得する関数 GetBookForReview を作成します。
export function request(ctx) {
    return {
       operation: 'GetItem',
       key: {
          id: {
               'S': ctx.source.bookId
           }
       }
    }
}

export function response(ctx) {
    if (ctx.error) {
        util.error(ctx.error.message, ctx.error.type, ctx.result)
    }
    
    return ctx.result
}
  • Review.book フィールドのデフォルトパイプラインリゾルバーに関数を追加し、パイプラインリゾルバーを作成します。Merge Now をクリックして更新を Merged API にマージします。

チャレンジ: Users Source API に独自のリゾルバを追加し、Review 型に User フィールドを追加して、指定した書籍をレビューしたユーザのデータを取得します。ユーザーデータを結合するためのキーとして、reviewerId を使用することができます。このデータを追加できるように、リゾルバを Merged API にマージしてください。

自動マージの有効化

Source API で自動マージを有効にするには、 Source API 設定に移動して、Edit をクリックします。次に、Automatic Merging を選択し、Save をクリックします。

Merged API のテスト

データソースに入力するためのテストデータを追加してみましょう。Merged API (Books Reviews Merged Api) に移動し、Query エディタをクリックします。以下の Mutation を実行して、以降の Query / Mutation で使用する ID をメモします。

mutation CreateAuthor {
    createAuthor(input: {
        name: "Jane Austen",
        bio: "English novelist known for her witty and insightful works set in the English countryside during the Regency era.",
        nationality: "English",
        contactEmail: "janeausten@example.com"
    }) {
        id,
        name,
        bio,
        nationality,
        contactEmail
    }
}

mutation CreateAuthor2 {
   createAuthor(input: {
    name: "Ernest Hemingway",
    bio: "American writer known for his concise and minimalist writing style. His works often explore themes of war, masculinity, and existentialism.",
    nationality: "American",
    contactEmail: "hemingway@example.com"
  }) {
        id,
        name,
        bio,
        nationality,
        contactEmail
  }
}
  
mutation CreateAuthor3 {
    createAuthor(input: {
        name: "J.K. Rowling",
        bio: "British author best known for creating the immensely popular 'Harry Potter' series.",
        nationality: "British",
        contactEmail: "jkrowling@example.com"
    }) {
        id,
        name,
        bio,
        nationality,
        contactEmail
    }
}

mutation CreateAuthor4 {
    createAuthor(input: {
        name: "William Shakespeare",
        bio: "English playwright and poet.",
        nationality: "English",
        contactEmail: "shakespeare@example.com"
    }) {
        id,
        name,
        bio,
        nationality,
        contactEmail
    }
}

mutation CreateBookMutation {
  createBook(input: {
    authorId: "<Jane austen の author id>",
    publisherId: "2b345cde-6789-01fg-hijk-lmnopqrstuv",
    title: "Emma",
    publicationYear: 1815,
    genre: "Classic"}) {
    id
    author {
      bio
      contactEmail
      name
      nationality
    }
    genre
    publicationYear
    title
  }
}

mutation CreateBookMutation1 {
  createBook(input: {
    authorId: "<J.K. Rowling の author id>",,
    publisherId: "2b345cde-1111-01fg-hijk-lmnopqrabcd",
    title: "Harry Potter and the Chamber of Secrets",
    publicationYear: 1998,
    genre: "Fantasy"}) {
    id
    author {
      bio
      contactEmail
      name
      nationality
    }
    genre
    publicationYear
    title
  }
}

mutation CreateBookMutation2 {
  createBook(input: {
    authorId: "<Jane austen の author id>",,
    publisherId: "2b345cde-6789-01fg-hijk-lmnopqrstuv",
    title: "Sense and Sensibility",
    publicationYear: 1814,
    genre: "Fantasy"}) {
    id
    author {
      bio
      contactEmail
      name
      nationality
    }
    genre
    publicationYear
    title
  }
}

mutation CreateReview1 {
    createReview(input: {
        authorId: "<JK Rowling の author id>",
        bookId: "<book id of Harry Potter and the Chamber of Secrets>",
        comment: "This book was captivating!",
        createdAt: "2023-05-08T12:00:00Z",
        rating: 9,
        reviewerId: "c52309fa-4a5b-42fc-b942-94c77e96ac3d",
        updatedAt: "2023-05-08T12:00:00Z"
        }) {
        id,
        comment,
        rating
    }
}
  

mutation CreateReview2 {
    createReview(input: {
        authorId: "<Jane Austen の author id>",
        bookId: "<book id of Sense and Sensibility>",
        comment: "A thought-provoking read!",
        createdAt: "2023-05-08T12:00:00Z",
        rating: 4,
        reviewerId: "74afce67-6b41-4fe6-aab0-fde839356231",
        updatedAt: "2023-05-08T12:00:00Z"}) {
            id,
            comment,
            rating
    }
}

mutation CreateReview3 {
    createReview(input: {
        authorId: "<Jane Austen の author id>",
        bookId: "<book id of Emma>",
        comment: "Couldn't put this book down!",
        createdAt: "2023-05-08T12:00:00Z",
        rating: 5,
        reviewerId: "71cd9ec4-3b56-407d-8144-095e0ba1a534",
        updatedAt: "2023-05-08T12:00:00Z"}) {
            id,
            comment,
            rating
    }
}

これで、複数の Source API に跨がる Query に対するレスポンスをテストできるようになりました。

以下は、著者とレビュー情報とともに書籍をリストアップする例です。

query ListBooksAuthorsAndReviews {
  listBooks {
    items {
      id,
      title
      authorId
      genre
      publicationYear
      publisherId
      author {
        id,
        name,
        bio,
        nationality
      },
      reviews {
        items {
          id,
          createdAt,
          updatedAt,
          rating,
          comment
        }
      }
    },
  }
}

Response:

{
  "data": {
    "listBooks": {
      "items": [
        {
          "id": "85783c75-94c1-4eb4-bd73-aad50cd1481d",
          "title": "Emma",
          "authorId": "9af707d9-30ff-4841-84bf-7881cfce802c",
          "genre": "Classic",
          "publicationYear": 1815,
          "publisherId": "2b345cde-6789-01fg-hijk-lmnopqrstuv",
          "author": {
            "id": "9af707d9-30ff-4841-84bf-7881cfce802c",
            "name": "Jane Austen",
            "bio": "English novelist known for her witty and insightful works set in the English countryside during the Regency era.",
            "nationality": "English"
          },
          "reviews": {
            "items": [
              {
                "id": "3531083c-0535-4e19-9d14-71cba00fad76",
                "createdAt": "2023-05-08T12:00:00Z",
                "updatedAt": "2023-05-08T12:00:00Z",
                "rating": 5,
                "comment": "Couldn't put this book down!"
              }
            ]
          }
        },
        {
          "id": "b7d7ae3e-8d13-4fe4-8a87-970c79ebf884",
          "title": "Sense and Sensibility",
          "authorId": "9af707d9-30ff-4841-84bf-7881cfce802c",
          "genre": "Fantasy",
          "publicationYear": 1814,
          "publisherId": "2b345cde-6789-01fg-hijk-lmnopqrstuv",
          "author": {
            "id": "9af707d9-30ff-4841-84bf-7881cfce802c",
            "name": "Jane Austen",
            "bio": "English novelist known for her witty and insightful works set in the English countryside during the Regency era.",
            "nationality": "English"
          },
          "reviews": {
            "items": [
              {
                "id": "73535cca-b3ae-4193-ade7-ea5e77f13748",
                "createdAt": "2023-05-08T12:00:00Z",
                "updatedAt": "2023-05-08T12:00:00Z",
                "rating": 4,
                "comment": "A thought-provoking read!"
              }
            ]
          }
        },
        {
          "id": "3a43cc15-1510-4a24-b68b-317cf01a0040",
          "title": "Harry Potter and the Chamber of Secrets",
          "authorId": "2a1a80a7-773d-48c4-a685-a9619700febe",
          "genre": "Fantasy",
          "publicationYear": 1998,
          "publisherId": "2b345cde-1111-01fg-hijk-lmnopqrabcd",
          "author": {
            "id": "2a1a80a7-773d-48c4-a685-a9619700febe",
            "name": "J.K. Rowling",
            "bio": "British author best known for creating the immensely popular 'Harry Potter' series.",
            "nationality": "British"
          },
          "reviews": {
            "items": [
              {
                "id": "fd8071d4-7460-4b73-a28d-31b3a5675b58",
                "createdAt": "2023-05-08T12:00:00Z",
                "updatedAt": "2023-05-08T12:00:00Z",
                "rating": 9,
                "comment": "This book was captivating!"
              }
            ]
          }
        }
      ]
    }
  }
}

他の Query も試してみましょう。

query ListAuthorsBooksAndReviews {
  listAuthors {
    items {
      id
      name
      bio
      nationality
      books {
        items {
            id
            title
        }
      }
      reviews {
        items {
          id
          createdAt
          updatedAt
          rating
        }
      }
    }
  }
}

query ListAuthorsBooksAndReviews {
  listReviews {
    items {
      id
      createdAt
      updatedAt
      rating
      comment
      book {
        id,
        title
      }
      author {
        id,
        name,
        bio,
        nationality
      }
    }
  }
}

Source API のテスト

今までの手順では、異なる Source API 間のデータを結合するために、id フィールドキーを使用しました。これらのキーは Source API のインターフェースを形成し、Merged API 自体をセットアップすることなく、 Source API を独立してテストすることができます。私たちが Books Source API 開発チームのメンバーであるとします。我々は、Author.booksReview.book フィールドで Books データを他の Source API にリンクするための 2 つのリゾルバを作成しました。

  • Books Source API の Author.books リゾルバをテストするために、AppSync テストエンドポイントを使用して、GetBooksForAuthor 関数用の JavaScript コードが正しいリクエストおよびレスポンス関数評価を生成することを確認できます。AppSync のリゾルバのテストについては、こちらで詳しく説明しています。
  • コンソールや EvaluateMappingTemplateEvaluateCode 操作によるテストでは、特定の関数やリゾルバに設定されているデータソースを実際に呼び出すことはありません。
  • Source API をエンドツーエンドでテストするには、@hidden ディレクティブを使用して、著者 ID キーをモックするモックリゾルバを作成することができます。これを行うには、Books Source API の Query 型に getAuthort というフィールドを追加し、非表示にします。このフィールドを追加した後、スキーマを保存してください。
type Query {
   ... all existing fields
   getAuthor(id: ID!): Author @hidden 
}
  • Books source API のデータソースメニューに移動し、Create data source をクリックします。このモックリゾルバに使用する MockAuthorDatasource という新しい None データソースを作成します。
  • 作成した None データソースを使用する MockGetAuthor という新しい関数を作成します。この関数には、次のコードを使用します。
export function request(ctx) {
    return {
        payload: {
            id: ctx.args.id
        }
    };
}

export function response(ctx) {
    return ctx.result;
}
  • Query.getAuthor に関連付けられたデフォルトのパイプラインリゾルバに MockGetAuthorfunction を追加します。Author.books リゾルバへの入力をモックできるようにするために、パイプラインリゾルバを保存します。
  • Source API リゾルバそのものをテストしてみましょう。まず、Mutation を使用して Books のモックデータをいくつか追加します。
mutation CreateBookMutation {
  createBook(input: {
    authorId: "1873aeff-312c-4f39-87f3-6e7715d2a7c6",
    publisherId: "2b345cde-6789-01fg-hijk-lmnopqrstuv",
    title: "Harry Potter and the Chamber of Secrets",
    publicationYear: 1998,
    genre: "Fantasy"}) {
    id
    authorId
    genre
    publicationYear
    title
  }
}

mutation CreateBookMutation2 {
  createBook(input: {
    authorId: "1873aeff-312c-4f39-87f3-6e7715d2a7c6",,
    publisherId: "2b345cde-6789-01fg-hijk-lmnopqrstuv",
    title: "Harry Potter and the Prisoner of Azkaban",
    publicationYear: 1999,
    genre: "Fantasy"}) {
    id
    authorId
    genre
    publicationYear
    title
  }
}



mutation CreateBookMutation3 {
  createBook(input: {
    authorId: "c954e68e-2651-4dd7-a63d-30d7fffa05d2",
    publisherId: "1a234bcd-5678-90ef-ghij-klmnopqrstuv",
    title: "Pride and Prejudice",
    publicationYear: 1813,
    genre: "Romance"}) {
    id
    authorId
    genre
    publicationYear
    title
  }
}

次に、与えられたテスト著者 ID の書籍を検索します。

query testAuthorBooksResolver {
    getAuthor(id: "1873aeff-312c-4f39-87f3-6e7715d2a7c6") {
         id,
         books {
            items {
                id,
                title,
                genre,
                authorId,
                publisherId,
                publicationYear
            }
        }
    }
}

Response: 
{
  "data": {
    "getAuthor": {
      "id": "1873aeff-312c-4f39-87f3-6e7715d2a7c6",
      "books": {
        "items": [
          {
            "id": "948c9a0e-f646-46ea-87c6-e718de4dccbe",
            "title": "Harry Potter and the Prisoner of Azkaban",
            "genre": "Fantasy",
            "authorId": "1873aeff-312c-4f39-87f3-6e7715d2a7c6",
            "publisherId": "3c456def-7890-12gh-ijkl-mnopqrstuv",
            "publicationYear": 1999
          },
          {
            "id": "48ae5262-c94f-42da-a1af-17b9610c38da",
            "title": "Harry Potter and the Chamber of Secrets",
            "genre": "Fantasy",
            "authorId": "1873aeff-312c-4f39-87f3-6e7715d2a7c6",
            "publisherId": "2b345cde-6789-01fg-hijk-lmnopqrstuv",
            "publicationYear": 1998
          }
        ]
      }
    }
  }
}
  • モックした None リゾルバを使って、 Source API リゾルバが正常に動作していることを確認できました。

後片付け

  1. AppSync コンソールに移動し、Books Review Merged API を選択し、Delete をクリックし、API 名を確認し、Delete を再度クリックします。
  2. 全ての Source API について、 Step1 を同様に繰り返す。
  3. DynamoDB コンソールに移動し、 Source API に関連するテーブル(BooksTableAuthersTableReviewsTableUsersTable)を選択し、Delete をクリックし、確認を選択して Delete を再度クリックします。

知っておきたい大切なこと

2023 年 5 月 23 日時点では 1 つの Source API は 1 つの Merged API にのみ関連付けることができ、1 つの Merged API を他の Merged API に Source API として関連付けることはできません。1 つの Merged API に対する Source API の数は 10 個までです。また Merged API 固有のリゾルバや関数はサポートされていません。Merged API に固有のカスタムロジックを追加するには、 Source API に関数やリゾルバを追加し、その変更を Merged API にマージすることができます。

今日から始めましょう!

AWS AppSync Merged API は、AWS AppSync が利用可能な全てのリージョンで利用できます。AppSync が利用可能なリージョンは AWS リージョナルサービスリストで確認できます。Merged API の詳細については AppSync のドキュメント、AWS AppSync の一般的な情報については AppSync の製品ページを参照してください。私たちはあなたが何を構築するのかを見るのが待ち遠しいです!

著者について

Nicholas は過去 3 年間 AWS AppSync に携わってきたシニアソフトウェアエンジニアです。日中は GraphQL Query の実行性能向上に注力し、週末は愛犬のピッパとサンフランシスコを歩き回っています。

Venugopalan Vasudevan は、AWS の Frontend・Web・Mobile サービスを専門とするシニアスペシャリストソリューションアーキテクトです。AWS でのフロントエンドおよびモバイル戦略の構築を支援し、DevOps プラクティスの成熟と強化に取り組んでいます。

本記事は、Introducing Merged APIs on AWS AppSync を翻訳したものです。翻訳はソリューションアーキテクトの稲田大陸が担当しました。