AWS AppSync
AWS AppSync 開発者ガイド

チュートリアル: パイプラインリゾルバー

AWS AppSync は、GraphQL フィールドをユニットリゾルバー経由で 1 のデータソースにつなぐシンプルな方法を提供します。ただし、1 つのオペレーションを実行するだけでは不十分な場合があります。パイプラインリゾルバーは、データソースに対してオペレーションを順番に実行する機能を提供します。API で関数を作成し、パイプラインリゾルバーにアタッチします。各関数の実行結果は、実行する関数がなくなるまで、次の結果にパイプされます。パイプラインリゾルバーを使用すると、AWS AppSync で直接、より複雑なワークフローを構築できます。このチュートリアルでは、友人によって投稿された写真を投稿したり表示したりできる、シンプルな写真表示アプリケーションを作成します。

ワンクリックでのセットアップ

設定したすべてのリゾルバーと必要な AWS リソースを使用して AWS AppSync に GraphQL エンドポイントが自動的に設定されるようにする場合は、以下の AWS CloudFormation テンプレートを使用できます。

このスタックはお客様のアカウントに以下のリソースを作成します。

  • AWS AppSync がお客様のアカウントのリソースにアクセスするための IAM ロール

  • 2 つの DynamoDB テーブル

  • 1 つの Amazon Cognito ユーザープール

  • 2 つの Amazon Cognito ユーザープールグループ

  • 3 つの Amazon Cognito ユーザープールユーザー

  • 1 つの AWS AppSync API

AWS CloudFormation スタック作成プロセスの最後に、作成された 3 つの Amazon Cognito ユーザーごとに 1 件の E メールを受信します。各 E メールには、Amazon Cognito ユーザーとして AWS AppSync コンソールにログインするために使用する一時パスワードが含まれています。これらのパスワードは、チュートリアルで後で使用するので保存しておきます。

手動セットアップ

AWS AppSync コンソールからステップバイステップのプロセスを手動で実行する場合は、以下の設定プロセスに従います。

AWS AppSync リソース以外のリソースの設定

API は、2 つの DynamoDB テーブル (写真を保存する pictures テーブルおよびユーザー間の関係を保存する friends テーブル) とやり取りします。API は、認証タイプとして Amazon Cognito ユーザープールを使用するように設定されています。以下の AWS CloudFormation スタックはアカウント内にこれらのリソースを設定します。

AWS CloudFormation スタック作成プロセスの最後に、作成された 3 つの Amazon Cognito ユーザーごとに 1 件の E メールを受信します。各 E メールには、Amazon Cognito ユーザーとして AWS AppSync コンソールにログインするために使用する一時パスワードが含まれています。これらのパスワードは、チュートリアルで後で使用するので保存しておきます。

GraphQL API の作成

AWS AppSync で GraphQL API を作成するには、以下の手順に従います。

  1. AWS AppSync コンソールを開き、[Build From Scratch (最初から構築)]、[Start (開始)] の順に選択します。

  2. API の名前を AppSyncTutorial-PicturesViewer に設定します。

  3. [Create (作成)] を選択します。

AWS AppSync コンソールによって、API キー認証モードを使用して新しい GraphQL API が作成されます。このコンソールを使用して、残りの GraphQL API をセットアップでき、このチュートリアルの残りの部分でクエリを実行できます。

GraphQL API の設定

先ほど作成した Amazon Cognito ユーザープールを使用して AWS AppSync API を設定する必要があります。

  1. [Settings (設定)] タブを選択します。

  2. [Authorization Type (承認タイプ)] セクションで、Amazon Cognito ユーザープールを選択します。

  3. [User Pool Configuration (ユーザープール設定)] の [AWS リージョン] で、[US-WEST-2] を選択します。

  4. AppSyncTutorial-UserPool ユーザープールを選択します。

  5. [デフォルトのアクション] で、[拒否] を選択します。

  6. [AppId client regex (AppId クライアント正規表現)] フィールドは空白のままにします。

  7. [Save] を選択します。

これで、承認タイプとして Amazon Cognito ユーザープールを使用するように API が設定されました。

DynamoDB テーブル用のデータソースの設定

DynamoDB テーブルを作成した後、コンソールで AWS AppSync GraphQL API に移動し、[データソース] タブを選択します。次は、先ほど作成した DynamoDB テーブルごとに、AWS AppSync でデータソースを作成します。

  1. [Data source (データソース)] タブを選択します。

  2. [New (新規)] を選択して、新しいデータソースを作成します。

  3. データソース名に、PicturesDynamoDBTable を入力します。

  4. データソースのタイプとして [Amazon DynamoDB Table (Amazon DynamoDB テーブル)] を選択します。

  5. リージョンとして [US-WEST-2 (米国西部 (オレゴン))] を選択します。

  6. テーブルのリストから AppSyncTutorial-Pictures DynamoDB テーブルを選択します。

  7. Create or use an existing role (作成または既存のロールの使用)」セクションで [Existing role (既存のロール)] を選択します。

  8. CloudFormation テンプレートから先ほど作成したロールを選択します。ResourceNamePrefix を変更しなかった場合、ロールの名前は AppSyncTutorial-DynamoDBRole になっています。

  9. [Create (作成)] を選択します。

friends テーブルに対して同じプロセスを繰り返します。CloudFormation スタックの作成時に ResourceNamePrefix パラメータを変更しなかった場合は、DynamoDB テーブルの名前は AppSyncTutorial-Friends になっています。

GraphQL スキーマの作成

データソースが DynamoDB テーブルに接続されたところで、GraphQL スキーマを作成しましょう。AWS AppSync コンソールのスキーマエディタで、スキーマが以下のスキーマと一致することを確認します。

schema { query: Query mutation: Mutation } type Mutation { createPicture(input: CreatePictureInput!): Picture! @aws_auth(cognito_groups: ["Admins"]) createFriendship(id: ID!, target: ID!): Boolean @aws_auth(cognito_groups: ["Admins"]) } type Query { getPicturesByOwner(id: ID!): [Picture] @aws_auth(cognito_groups: ["Admins", "Viewers"]) } type Picture { id: ID! owner: ID! src: String } input CreatePictureInput { owner: ID! src: String! }

スキーマを保存するには、[Save Schema (スキーマの保存)] を選択します。

いくつかのスキーマフィールドには @aws_auth ディレクティブで注釈が付けられています。API のデフォルトのアクション設定は [拒否] に設定されているため、API は @aws_auth ディレクティブ内で指定されているグループのメンバーではないすべてのユーザーを拒否します。API の保護方法の詳細については、「セキュリティ」ページを参照してください。この場合、管理者ユーザーのみが Mutation.createPicture および Mutation.createFriendship フィールドにアクセスできますが、Admins または Viewers グループのメンバーであるユーザーは Query.getPicturesByOwner フィールドにアクセスできます。他のすべてのユーザーはアクセスできません。

リゾルバーの設定

有効な GraphQL スキーマと 2 つのデータソースを用意できたところで、スキーマでリゾルバーを GraphQL フィールドにアタッチできます。API は以下の機能を提供します。

  • Mutation.createPicture フィールドで写真を作成する

  • Mutation.createFriendship フィールドで友人関係を作成する

  • Query.getPicture フィールドで写真を取得する

Mutation.createPicture

AWS AppSync コンソールのスキーマエディタで、右側から createPicture(input: CreatePictureInput!): Picture! に対して、[Attach Resolver (リゾルバーをアタッチ)] を選択します。DynamoDB PicturesDynamoDBTable データソースを選択します。[request mapping template (リクエストマッピングテンプレート)] セクションで、以下のテンプレートを追加します。

#set($id = $util.autoId()) { "version" : "2018-05-29", "operation" : "PutItem", "key" : { "id" : { "S" : "$id" }, "owner": { "S": "$ctx.args.input.owner"} }, "attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.input) }

[response mapping template (レスポンスマッピングテンプレート)] セクションで、以下のテンプレートを追加します。

#if($ctx.error) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)

写真の作成機能が実行されます。ランダムに生成された UUID を写真の ID として使用し、Cognito のユーザー名を写真の所有者として使用して、Pictures テーブルに写真を保存します。

Mutation.createFriendship

AWS AppSync コンソールのスキーマエディタで、右側から createFriendship(id: ID!, target: ID!): Boolean に対して、[Attach Resolver (リゾルバーをアタッチ)] を選択します。DynamoDB FriendsDynamoDBTable データソースを選択します。[request mapping template (リクエストマッピングテンプレート)] セクションで、以下のテンプレートを追加します。

#set($userToFriendFriendship = { "userId" : "$ctx.args.id", "friendId": "$ctx.args.target" }) #set($friendToUserFriendship = { "userId" : "$ctx.args.target", "friendId": "$ctx.args.id" }) #set($friendsItems = [$util.dynamodb.toMapValues($userToFriendFriendship), $util.dynamodb.toMapValues($friendToUserFriendship)]) { "version" : "2018-05-29", "operation" : "BatchPutItem", "tables" : { ## Replace 'AppSyncTutorial-' default below with the ResourceNamePrefix you provided in the CloudFormation template "AppSyncTutorial-Friends": $util.toJson($friendsItems) } }

重要: BatchPutItem リクエストテンプレートには、DynamoDB テーブルの正確な名前が指定されている必要があります。デフォルトのテーブル名は AppSyncTutorial-Friends です。間違ったテーブル名を使用している場合、指定したロールを AppSync が引き受けようとするとエラーが発生します。

このチュートリアルではシンプルになるように、友人関係のリクエストが承認されたかのように処理を進め、友人関係のエントリを AppSyncTutorialFriends * テーブルに直接保存します。実際、友人関係は双方向であるため、関係ごとに 2 つの項目を保存します。多対多の関係を表すための Amazon DynamoDB のベストプラクティスの詳細については、「DynamoDB のベストプラクティス」を参照してください。

[response mapping template (レスポンスマッピングテンプレート)] セクションで、以下のテンプレートを追加します。

#if($ctx.error) $util.error($ctx.error.message, $ctx.error.type) #end true

注意: リクエストテンプレートに正しいテーブル名が含まれていることを確認してください。デフォルト名は AppSyncTutorial-Friends ですが、CloudFormation の ResourceNamePrefix パラメータを変更した場合、そのテーブル名が異なることがあります。

Query.getPicturesByOwner

友人関係と写真を用意できたところで、ユーザーが自分の友人の写真を表示できるようにする必要があります。この要件を満たすには、まずリクエスト者が所有者と友人であることを確認し、最後に写真のクエリを実行する必要があります。

この機能には 2 つのデータソースオペレーションが必要であるため、2 つの関数を作成します。最初の関数 isFriend は、リクエスト者と所有者が友人であるかどうかを確認します。2 番目の関数 getPicturesByOwner は、所有者 ID を指定してリクエストされた写真を取得します。Query.getPicturesByOwner フィールドで提案されたリゾルバーに対する以下の実行フローを見てみましょう。

  1. Before マッピングテンプレート: コンテキストとフィールドの入力引数を準備します。

  2. isFriend 関数: リクエスト者が写真の所有者かどうかを確認します。そうでない場合は、friends テーブルに対して DynamoDB GetItem オペレーションを実行して、リスクエスト者と所有者が友人であるかどうかを確認します。

  3. getPicturesByOwner 関数: owner-index グローバルセカンダリインデックスに対する DynamoDB クエリオペレーションを使用して、Pictures テーブルから写真を取得します。

  4. After マッピングテンプレート: DynamoDB 属性が、想定される GraphQL タイプのフィールドに正しくマッピングされるように、写真の結果をマッピングします。

まず、関数を作成しましょう。

isFriend 関数
  1. [関数] タブをクリックします。

  2. [関数の作成] を選択して、関数を作成します。

  3. データソース名に、FriendsDynamoDBTable を入力します。

  4. 関数名として、「isFriend」と入力します。

  5. リクエストマッピングテンプレートのテキスト領域内に、以下のテンプレートを貼り付けます。

    #set($ownerId = $ctx.prev.result.owner) #set($callerId = $ctx.prev.result.callerId) ## if the owner is the caller, no need to make the check #if($ownerId == $callerId) #return($ctx.prev.result) #end { "version" : "2018-05-29", "operation" : "GetItem", "key" : { "userId" : { "S" : "$callerId" }, "friendId" : { "S" : "$ownerId" } } }
  6. レスポンスマッピングテンプレートのテキスト領域内に、以下のテンプレートを貼り付けます。

    #if($ctx.error) $util.error("Unable to retrieve friend mapping message: ${ctx.error.message}", $ctx.error.type) #end ## if the users aren't friends #if(!$ctx.result) $util.unauthorized() #end $util.toJson($ctx.prev.result)
  7. [関数の作成] を選択します。

結果: isFriend 関数を作成しました。

getPicturesByOwner 関数
  1. [関数] タブをクリックします。

  2. [関数の作成] を選択して、関数を作成します。

  3. データソース名に、PicturesDynamoDBTable を入力します。

  4. 関数名として、「getPicturesByOwner」と入力します。

  5. リクエストマッピングテンプレートのテキスト領域内に、以下のテンプレートを貼り付けます。

    { "version" : "2018-05-29", "operation" : "Query", "query" : { "expression": "#owner = :owner", "expressionNames": { "#owner" : "owner" }, "expressionValues" : { ":owner" : { "S" : "$ctx.prev.result.owner" } } }, "index": "owner-index" }
  6. レスポンスマッピングテンプレートのテキスト領域内に、以下のテンプレートを貼り付けます。

    #if($ctx.error) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)
  7. [関数の作成] を選択します。

結果: getPicturesByOwner 関数を作成しました。関数が作成されたところで、パイプラインリゾルバーを Query.getPicturesByOwner フィールドにアタッチします。

AWS AppSync コンソールのスキーマエディタで、右側から Query.getPicturesByOwner(id: ID!): [Picture] に対して、[Attach Resolver (リゾルバーをアタッチ)] を選択します。以下のページで、データソースのドロップダウンリストの下に表示される [Convert to pipeline resolver (パイプラインリゾルバーに変換)] リンクを選択します。Before マッピングテンプレートに、以下のものを使用します。

#set($result = { "owner": $ctx.args.id, "callerId": $ctx.identity.username }) $util.toJson($result)

After マッピングテンプレートのセクションに、以下のものを使用します。

#foreach($picture in $ctx.result.items) ## prepend "src://" to picture.src property #set($picture['src'] = "src://${picture['src']}") #end $util.toJson($ctx.result.items)

[Create Resolver (リゾルバー作成)] を選択します。最初のパイプラインリゾルバーが正常にアタッチされました。同じページで、前に作成した 2 つの関数を追加します。関数セクションで、[Add A Function (関数の追加)] を選択してから、最初の関数の名前として [isFriend] を選択または入力します。getPicturesByOwner 関数のものと同じプロセスに従って、2 番目の関数を追加します。isFriend 関数がリストの最初に表示され、続いて getPicturesByOwner 関数が表示されていることを確認します。上下の矢印を使用して、パイプライン内の関数の実行順序に並べ替えることができます。

パイプラインリゾルバーが作成され、関数がアタッチされたところで、新しく作成した GraphQL API をテストしましょう。

GraphQL API をテストする

まず、作成した管理者ユーザーを使用していくつかのミューテーションを実行することで、写真と友人関係を入力する必要があります。AWS AppSync コンソールの左側で、[クエリ] タブを選択します。

createPicture ミューテーション

  1. AWS AppSync コンソールで、[クエリ] タブを選択します。

  2. [Login With User Pools (ユーザープールでログイン)] を選択します。

  3. モーダルで、CloudFormation スタックによって作成された Cognito Sample Client ID (37solo6mmhh7k4v63cqdfgdg5d など) を入力します。

  4. CloudFormation スタックにパラメータとして渡したユーザー名を入力します。デフォルトは nadia です。

  5. E メールに送信されて CloudFormation スタックへのパラメータとして渡した一時パスワード (UserPoolUserEmail など) を使用します。

  6. [ログイン] を選択します。これで、ボタンの名前が Logout nadia に変更されているか、CloudFormation スタックの作成時に選択したユーザー名 (つまり UserPoolUsername) に変更されています。

pictures テーブルに入力するいくつかの createPicture ミューテーションを送信しましょう。コンソール内で以下の GraphQL クエリを実行します。

mutation { createPicture(input:{ owner: "nadia" src: "nadia.jpg" }) { id owner src } }

レスポンスは以下のようになります。

{ "data": { "createPicture": { "id": "c6fedbbe-57ad-4da3-860a-ffe8d039882a", "owner": "nadia", "src": "nadia.jpg" } } }

さらに写真をいくつか追加しましょう。

mutation { createPicture(input:{ owner: "shaggy" src: "shaggy.jpg" }) { id owner src } }
mutation { createPicture(input:{ owner: "rex" src: "rex.jpg" }) { id owner src } }

管理者ユーザーとして nadia を使用して 3 つの写真を追加しました。

createFriendship ミューテーション

友人関係エントリを追加しましょう。コンソールで以下のミューテーションを実行します。

注意: 管理者ユーザーとしてログインしている必要があります (デフォルトの管理者ユーザーは nadia です)。

mutation { createFriendship(id: "nadia", target: "shaggy") }

レスポンスは以下のようになります。

{ "data": { "createFriendship": true } }

nadiashaggy は友人です。rex はだれとも友人ではありません。

getPicturesByOwner クエリ

この手順では、Cognito ユーザープールと共に、このチュートリアルの始めに設定した認証情報を使用して、nadia ユーザーとしてログインします。nadia として、shaggy が所有する写真を取得します。

query { getPicturesByOwner(id: "shaggy") { id owner src } }

nadiashaggy は友人であるため、クエリからは対応する写真が返されます。

{ "data": { "getPicturesByOwner": [ { "id": "05a16fba-cc29-41ee-a8d5-4e791f4f1079", "owner": "shaggy", "src": "src://shaggy.jpg" } ] } }

同様に、nadia が自分の写真を取得しようとした場合も、クエリは成功します。その場合、isFriend GetItem オペレーションを実行しないように、パイプラインリゾルバーは最適化されています。以下のクエリを試します。

query { getPicturesByOwner(id: "nadia") { id owner src } }

API のログ記録を有効にしている場合 ([Settings (設定)] ペインで)、デバッグレベルを [すべて] に設定し、同じクエリをもう一度実行すると、フィールド実行のログが返されます。ログを見ることで、Request Mapping Template 段階で isFriend 関数から先ほど戻ったかどうかを判断できます。

{ "errors": [], "mappingTemplateType": "Request Mapping", "path": "[getPicturesByOwner]", "resolverArn": "arn:aws:appsync:us-west-2:XXXX:apis/XXXX/types/Query/fields/getPicturesByOwner", "functionArn": "arn:aws:appsync:us-west-2:XXXX:apis/XXXX/functions/o2f42p2jrfdl3dw7s6xub2csdfs", "functionName": "isFriend", "earlyReturnedValue": { "owner": "nadia", "callerId": "nadia" }, "context": { "arguments": { "id": "nadia" }, "prev": { "result": { "owner": "nadia", "callerId": "nadia" } }, "stash": {}, "outErrors": [] }, "fieldInError": false }

earlyReturnedValue キーは、#return ディレクティブによって返されたデータを表します。

最後に、rexViewers Cognito ユーザープールグループのメンバーです。rex はだれとも友人ではないため、shaggy または nadia によって所有されている写真にアクセスすることはできません。コンソールに rex としてログインし、以下のクエリを実行したとします。

query { getPicturesByOwner(id: "nadia") { id owner src } }

以下の未承認エラーが発生します。

{ "data": { "getPicturesByOwner": null }, "errors": [ { "path": [ "getPicturesByOwner" ], "data": null, "errorType": "Unauthorized", "errorInfo": null, "locations": [ { "line": 2, "column": 9, "sourceName": null } ], "message": "Not Authorized to access getPicturesByOwner on type Query" } ] }

パイプラインリゾルバーを使用した複雑な承認が正常に実装されました。