AWS AppSync
AWS AppSync 開発者ガイド

チュートリアル : DynamoDB リゾルバー

このチュートリアルでは、Amazon DynamoDB テーブルを AWS AppSync に移行してそれを GraphQL API に接続する方法について説明します。

AWS AppSync がユーザーに代わって DynamoDB リソースをプロビジョニングするようにできます。または、必要であれば、データソースとリゾルバーを作成することで、既存のテーブルを GraphQL スキーマに接続できます。いずれの場合も、GraphQL ステートメントを使用して DynamoDB データベースへの読み取りと書き込みを行うことができ、リアルタイムデータをサブスクライブできます。

GraphQL ステートメントが DynamoDB オペレーションに変換され、レスポンスが GraphQL に変換されるように、特定の設定ステップを完了しておく必要があります。このチュートリアルでは、いくつかの実際のシナリオおよびデータアクセスパターンを使用して、その設定手順の概要を示します。

DynamoDB テーブルのセットアップ

このチュートリアルを開始するには、まず、次の AWS CloudFormation テンプレートを使用して AWS リソースをプロビジョニングする必要があります。

aws cloudformation create-stack \ --stack-name AWSAppSyncTutorialForAmazonDynamoDB \ --template-url https://s3-us-west-2.amazonaws.com/awsappsync/resources/dynamodb/AmazonDynamoDBCFTemplate.yaml \ --capabilities CAPABILITY_NAMED_IAM

このボタンをクリックすると、ユーザーの AWS アカウントで、米国西部 (オレゴン) (us-west-2) リージョンでこの AWS CloudFormation スタックを起動できます。

これにより、以下の項目が作成されます。

  • Post データを保持する AppSyncTutorial-Post と呼ばれる DynamoDB テーブル。

  • AWS AppSync で Post テーブルとやり取りできるようにするための IAM ロールとそれに関連付けられている IAM 管理ポリシー。

スタックおよび作成されたリソースに関する詳細を確認するには、次の CLI コマンドを実行します。

aws cloudformation describe-stacks \ --stack-name AWSAppSyncTutorialForAmazonDynamoDB

そのリソースを削除するには、次のコマンドを実行します。

aws cloudformation delete-stack \ --stack-name AWSAppSyncTutorialForAmazonDynamoDB

GraphQL API の作成

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

  • AWS AppSync コンソールを開き、[Create API (API の作成)] を選択します。

  • API の名前を AWSAppSyncTutorial に設定します。

  • カスタムスキーマを選択します。

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

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

基本的な Post API の定義

ここで、AWS AppSync の GraphQL API をセットアップして、ポストデータの基本的な作成、取得、削除を許可する基本スキーマをセットアップできます。

AWS AppSync コンソールで、[Schema (スキーマ)] タブを選択します。[Schema (スキーマ)] ペインで、ファイルの現在の内容を次のコードに置き換えて、[Save (保存)] を選択します。

schema { query: Query mutation: Mutation } type Query { getPost(id: ID): Post } type Mutation { addPost( id: ID! author: String! title: String! content: String! url: String! ): Post! } type Post { id: ID! author: String title: String content: String url: String ups: Int! downs: Int! version: Int! }

このスキーマでは、Post 型と、Post オブジェクトを追加および取得するオペレーションを定義しています。

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

次に、スキーマで定義されているクエリとミューテーションを AppSyncTutorial-PostDynamoDB テーブルにリンクします。

まず、AWS AppSync にユーザーのテーブルを認識させる必要があります。これを行うには AWS AppSync にデータソースをセットアップします。

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

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

  • データソース名に、PostDynamoDBTable を入力します。

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

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

  • テーブルのリストから [AppSyncTutorial-Post] DynamoDB テーブルを選択します。

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

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

addPost リゾルバー (DynamoDB PutItem) のセットアップ

AWS AppSync が DynamoDB テーブルを認識したら、リゾルバーを定義することで、そのテーブルを個々のクエリおよびミューテーションにリンクできます。最初に作成するリゾルバーは addPost リゾルバーです。このリゾルバーによって、ユーザーが AppSyncTutorial-PostDynamoDB テーブルにポストを作成できるようになります。

リゾルバーには以下のコンポーネントがあります。

  • リゾルバーをアタッチする、GraphQL スキーマ内の場所。この例では、addPost 型の Mutation フィールドにリゾルバーをセットアップしています。このリゾルバーは、呼び出し元が mutation { addPost(...){...} } を呼び出したときに呼び出されます。

  • このリゾルバーで使用するデータソース。この例では、PostDynamoDBTable DynamoDB テーブルにエントリを追加できるように、前に定義した AppSyncTutorial-Post データソースを使用します。

  • リクエストマッピングテンプレート。リクエストマッピングテンプレートの目的は、呼び出し元からの受信リクエストを取り込み、それを DynamoDB に対して実行するための AWS AppSync 用のインストラクションに変換することです。

  • レスポンスマッピングテンプレート。レスポンスマッピングテンプレートの目的は、DynamoDB からのレスポンスを取り込み、それを GraphQL で想定されているものに変換し直すことです。これは、DynamoDB でのデータのシェイプが GraphQL での Post 型と異なる場合に便利です。ただし、この例では、両方のシェイプが同じであるため、データをそのまま渡します。

リゾルバーをセットアップするには、以下の手順に従います。

  • [Schema (スキーマ)] タブを選択します。

  • 右側の [Data types (データ型)] ペインで、ミューテーション型の [addPost] フィールドを検索し、[Attach (アタッチ)] を選択します。

  • [Data source name (データソース名)] で、[PostDynamoDBTable] を選択します。

  • 次のコードを [Configure the request mapping template (リクエストマッピングテンプレートの設定)] に貼り付けます。

    { "version" : "2017-02-28", "operation" : "PutItem", "key" : { "id" : { "S" : "${context.arguments.id}" } }, "attributeValues" : { "author": { "S" : "${context.arguments.author}" }, "title": { "S" : "${context.arguments.title}" }, "content": { "S" : "${context.arguments.content}" }, "url": { "S" : "${context.arguments.url}" }, "ups" : { "N" : 1 }, "downs" : { "N" : 0 }, "version" : { "N" : 1 } } }

    注 : 「型」はすべてのキーと属性値で指定されています。たとえば、author フィールドを { "S" : "${context.arguments.author}" } に設定します。S パートによって、その値が文字列値であることが AWS AppSync と DynamoDB に指示されます。実際の値は author 引数から入力されます。同様に、version フィールドは、型として N が使用されているため数値フィールドです。最後に、upsdowns および version フィールドの初期化も行っています。

    このチュートリアルでは、DynamoDB に挿入される新しい項目をインデックス付けする GraphQL ID! 型が、クライアントの引数の一部として渡されることを指定しています。AWS AppSync には、$utils.autoId() という ID 自動生成用のユーティリティが付属していて、"id" : { "S" : "${$utils.autoId()}" } の形式で使用することもできます。そのため、id: ID!addPost() のスキーマ定義から除外するだけで、自動的に挿入されます。このチュートリアルではこの手法を使用しませんが、DynamoDB テーブルに書き込む場合はこの手法を検討することをお勧めします。

    マッピングテンプレートの詳細については、「リゾルバーのマッピングテンプレートの概要」リファレンスドキュメントを参照してください。GetItem リクエストマッピングの詳細については、「GetItem」リファレンスドキュメントを参照してください。型の詳細については、「型システム (リクエストマッピング)」リファレンスドキュメントを参照してください。

  • 次のコードを [Configure the response mapping template (レスポンスマッピングテンプレートの設定)] に貼り付けます。

    $utils.toJson($context.result)

    注 : AppSyncTutorial-Post テーブルでのデータのシェイプは GraphQL での Post 型のシェイプと厳密に一致しているため、このレスポンスマッピングテンプレートは結果をそのまま渡すだけです。また、このチュートリアルのすべての例では、これと同じレスポンスマッピングテンプレートだけを使用しているため、作成するファイルはこの 1 つだけです。

  • [Save] を選択します。

ポストを追加する API の呼び出し

これで、リゾルバーが設定されたので、AWS AppSync は受信 addPost ミューテーションを DynamoDB の PutItem オペレーションに変換できるようになり、ユーザーはミューテーションを実行してテーブルに何かを入れることができるようになりました。

  • [Queries] タブを選択します。

  • 以下のミューテーションを [Queries (クエリ)] ペインに貼り付けます。

    mutation addPost { addPost( id: 123 author: "AUTHORNAME" title: "Our first post!" content: "This is our first post." url: "https://aws.amazon.com/appsync/" ) { id author title content url ups downs version } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • 新しく作成されたポストの結果が、クエリペインの右側にある結果ペインに表示されます。これは次のように表示されます。

    { "data": { "addPost": { "id": "123", "author": "AUTHORNAME", "title": "Our first post!", "content": "This is our first post.", "url": "https://aws.amazon.com/appsync/", "ups": 1, "downs": 0, "version": 1 } } }

何が起こったのかを以下に説明します。

  • AWS AppSync が addPost ミューテーションリクエストを受け取りました。

  • AWS AppSync が、リクエストとリクエストマッピングテンプレートを取り込んで、リクエストマッピングドキュメントを生成しました。そのドキュメントは次のようになっていました。

    { "version" : "2017-02-28", "operation" : "PutItem", "key" : { "id" : { "S" : "123" } }, "attributeValues" : { "author": { "S" : "AUTHORNAME" }, "title": { "S" : "Our first post!" }, "content": { "S" : "This is our first post." }, "url": { "S" : "https://aws.amazon.com/appsync/" }, "ups" : { "N" : 1 }, "downs" : { "N" : 0 }, "version" : { "N" : 1 } } }
  • AWS AppSync が、リクエストマッピングドキュメントを使用して、DynamoDB の PutItem リクエストを生成して実行しました。

  • AWS AppSync が、PutItem リクエストの結果を取り込んで、それを GraphQL 型に変換し直しました。

    { "id" : "123", "author": "AUTHORNAME", "title": "Our first post!", "content": "This is our first post.", "url": "https://aws.amazon.com/appsync/", "ups" : 1, "downs" : 0, "version" : 1 }
  • そのデータは、レスポンスマッピングドキュメントを介して変更されずに渡されました。

  • 新しく作成されたオブジェクトが GraphQL レスポンスで返されました。

getPost リゾルバー (DynamoDB GetItem) のセットアップ

これで、AppSyncTutorial-PostDynamoDB テーブルにデータを追加できるようになりました。次は、AppSyncTutorial-Post テーブルからデータを取得できるように、getPost クエリを設定する必要があります。そのためには、別のリゾルバーを設定します。

  • [Schema (スキーマ)] タブを選択します。

  • 右側の [Data types (データ型)] ペインで、クエリ型の [getPost] フィールドを検索し、[Attach (アタッチ)] を選択します。

  • [Data source name (データソース名)] で、[PostDynamoDBTable] を選択します。

  • 以下のコードを [Configure the request mapping template (リクエストマッピングテンプレートの設定)] に貼り付けます。

    { "version" : "2017-02-28", "operation" : "GetItem", "key" : { "id" : { "S" : "${context.arguments.id}" } } }
  • 以下のコードを [Configure the response mapping template (レスポンスマッピングテンプレートの設定)] に貼り付けます。

    $utils.toJson($context.result)
  • [Save] を選択します。

ポストを取得する API の呼び出し

これで、リゾルバーが設定されて、AWS AppSync には、受信 getPost クエリを DynamoDB の GetItem オペレーションに変換する方法が通知されるようになりました。次は、先ほど作成したポストを取得するクエリを実行できます。

  • [Queries] タブを選択します。

  • 以下のミューテーションを [Queries (クエリ)] ペインに貼り付けます。

    query getPost { getPost(id:123) { id author title content url ups downs version } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • DynamoDB から取得されたポストが、クエリペインの右側にある結果ペインに表示されます。これは次のように表示されます。

    { "data": { "getPost": { "id": "123", "author": "AUTHORNAME", "title": "Our first post!", "content": "This is our first post.", "url": "https://aws.amazon.com/appsync/", "ups": 1, "downs": 0, "version": 1 } } }

何が起こったのかを以下に説明します。

  • AWS AppSync が getPost クエリリクエストを受け取りました。

  • AWS AppSync が、リクエストとリクエストマッピングテンプレートを取り込んで、リクエストマッピングドキュメントを生成しました。そのドキュメントは次のようになっていました。

    { "version" : "2017-02-28", "operation" : "GetItem", "key" : { "id" : { "S" : "123" } } }
  • AWS AppSync が、リクエストマッピングドキュメントを使用して、DynamoDB の GetItem リクエストを生成して実行しました。

  • AWS AppSync が、GetItem リクエストの結果を取り込んで、それを GraphQL 型に変換し直しました。

    { "id" : "123", "author": "AUTHORNAME", "title": "Our first post!", "content": "This is our first post.", "url": "https://aws.amazon.com/appsync/", "ups" : 1, "downs" : 0, "version" : 1 }
  • そのデータは、レスポンスマッピングドキュメントを介して変更されずに渡されました。

  • 取得したオブジェクトがレスポンスで返されました。

updatePost ミューテーション (DynamoDB UpdateItem) の作成

これで、DynamoDB 内の Post オブジェクトを作成および取得できるようになりました。次は、オブジェクトを更新できるように、新しいミューテーションを設定します。そのためには、DynamoDB の UpdateItem オペレーションを使用します。

  • [Schema (スキーマ)] タブを選択します。

  • [Schema (スキーマ)] ペインの Mutation 型を次のように変更して、新しい updatePost ミューテーションを追加します。

    type Mutation { updatePost( id: ID!, author: String!, title: String!, content: String!, url: String! ): Post addPost( author: String! title: String! content: String! url: String! ): Post! }
  • [Save] を選択します。

  • 右側の [Data types (データ型)] ペインで、新しく作成したミューテーション型の [updatePost] フィールドを検索し、[Attach (アタッチ)] を選択します。

  • [Data source name (データソース名)] で、[PostDynamoDBTable] を選択します。

  • 次のコードを [Configure the request mapping template (リクエストマッピングテンプレートの設定)] に貼り付けます。

    { "version" : "2017-02-28", "operation" : "UpdateItem", "key" : { "id" : { "S" : "${context.arguments.id}" } }, "update" : { "expression" : "SET author = :author, title = :title, content = :content, #url = :url ADD version :one", "expressionNames": { "#url" : "url" }, "expressionValues": { ":author" : { "S": "${context.arguments.author}" }, ":title" : { "S": "${context.arguments.title}" }, ":content" : { "S": "${context.arguments.content}" }, ":url" : { "S": "${context.arguments.url}" }, ":one" : { "N": 1 } } } }

    注意: このリゾルバーでは DynamoDB の UpdateItem が使用されていて、そのオペレーションは PutItem オペレーションと大幅に異なります。DynamoDB では、項目全体が書き込まれるのではなく、特定の属性が更新されるようにします。これを行うには、DynamoDB の更新式を使用します。その式自体は、expression セクションの update フィールドで指定されています。その式では、authortitlecontent、url の各属性を設定し、version フィールドを増分しています。使用される値は式自体には記述されていません。この式には、名前がコロンで始まるプレースホルダーがあり、それぞれの値は expressionValues フィールドで定義されています。最後に、DynamoDB には、expression で使用できない予約語があります。たとえば、url は予約語であるため、url フィールドを更新するには、名前のプレースホルダーを expressionNames フィールドで定義できます。

    UpdateItem リクエストマッピングの詳細については、「UpdateItem」リファレンスドキュメントを参照してください。更新式の記述方法の詳細については、DynamoDB UpdateExpressions のドキュメントを参照してください。

  • 次のコードを [Configure the response mapping template (レスポンスマッピングテンプレートの設定)] に貼り付けます。

    $utils.toJson($context.result)

ポストを更新する API の呼び出し

これで、リゾルバーがセットアップされたので、AWS AppSync は受信 update ミューテーションを DynamoDB の Update オペレーションに変換する方法を知っていることになり、ユーザーは、前のステップで書き込んだ項目を更新するミューテーションを実行できるようになりました。

  • [Queries] タブを選択します。

  • 次のミューテーションを [Queries (クエリ)] ペインに貼り付けます。また、id 引数を、前にメモしておいた値に更新する必要があります。

    mutation updatePost { updatePost( id:"123" author: "A new author" title: "An updated author!" content: "Now with updated content!" url: "https://aws.amazon.com/appsync/" ) { id author title content url ups downs version } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • DynamoDB で更新されたポストが、クエリペインの右側にある結果ペインに表示されます。これは次のように表示されます。

    { "data": { "updatePost": { "id": "123", "author": "A new author", "title": "An updated author!", "content": "Now with updated content!", "url": "https://aws.amazon.com/appsync/", "ups": 1, "downs": 0, "version": 2 } } }

この例では、ups および downs フィールドは変更されていません。これは、リクエストマッピングテンプレートで、それらのフィールドで何かを行うように AWS AppSync と DynamoDB に指示していないためです。また、version フィールドが 1 ずつ増加します。これは、version フィールドに 1 を追加するように AWS AppSync と DynamoDB に指示しているためです。

updatePost リゾルバー (DynamoDB UpdateItem) の変更

updatePost ミューテーションの手始めとしてはこれで十分ですが、次の 2 つの主な問題があります。

  • 1 つのフィールドだけを更新する場合でも、すべてのフィールドを更新する必要があります。

  • 2 人のユーザーがこのオブジェクトを変更すると、情報が失われる可能性があります。

これらの問題に対処するために、リクエストで指定された引数のみを変更し、UpdateItem オペレーションに条件を追加するように、updatePost ミューテーションを修正します。

  • [Schema (スキーマ)] タブを選択します。

  • [Schema (スキーマ)] ペインで Mutation 型の updatePost フィールドを変更して、authortitlecontenturl の各引数から感嘆符を除去し、id フィールドが元のままになるようにします。これにより、それらの引数が省略可能になります。また、新しい必須の expectedVersion 引数を追加します。

    type Mutation { updatePost( id: ID!, author: String, title: String, content: String, url: String, expectedVersion: Int! ): Post addPost( author: String! title: String! content: String! url: String! ): Post! }
  • [Save] を選択します。

  • 右側の [Data types (データ型)] ペインで、ミューテーション型の updatePost フィールドを見つけます。

  • PostDynamoDBTable を選択し、既存リゾルバーを開きます。

  • [Configure the request mapping template (リクエストマッピングテンプレートの設定)] にあるリクエストマッピングテンプレートを次のように変更します。

    { "version" : "2017-02-28", "operation" : "UpdateItem", "key" : { "id" : { "S" : "${context.arguments.id}" } }, ## Set up some space to keep track of things you're updating ** #set( $expNames = {} ) #set( $expValues = {} ) #set( $expSet = {} ) #set( $expAdd = {} ) #set( $expRemove = [] ) ## Increment "version" by 1 ** $!{expAdd.put("version", ":one")} $!{expValues.put(":one", { "N" : 1 })} ## Iterate through each argument, skipping "id" and "expectedVersion" ** #foreach( $entry in $context.arguments.entrySet() ) #if( $entry.key != "id" && $entry.key != "expectedVersion" ) #if( (!$entry.value) && ("$!{entry.value}" == "") ) ## If the argument is set to "null", then remove that attribute from the item in DynamoDB ** #set( $discard = ${expRemove.add("#${entry.key}")} ) $!{expNames.put("#${entry.key}", "$entry.key")} #else ## Otherwise set (or update) the attribute on the item in DynamoDB ** $!{expSet.put("#${entry.key}", ":${entry.key}")} $!{expNames.put("#${entry.key}", "$entry.key")} $!{expValues.put(":${entry.key}", { "S" : "${entry.value}" })} #end #end #end ## Start building the update expression, starting with attributes you're going to SET ** #set( $expression = "" ) #if( !${expSet.isEmpty()} ) #set( $expression = "SET" ) #foreach( $entry in $expSet.entrySet() ) #set( $expression = "${expression} ${entry.key} = ${entry.value}" ) #if ( $foreach.hasNext ) #set( $expression = "${expression}," ) #end #end #end ## Continue building the update expression, adding attributes you're going to ADD ** #if( !${expAdd.isEmpty()} ) #set( $expression = "${expression} ADD" ) #foreach( $entry in $expAdd.entrySet() ) #set( $expression = "${expression} ${entry.key} ${entry.value}" ) #if ( $foreach.hasNext ) #set( $expression = "${expression}," ) #end #end #end ## Continue building the update expression, adding attributes you're going to REMOVE ** #if( !${expRemove.isEmpty()} ) #set( $expression = "${expression} REMOVE" ) #foreach( $entry in $expRemove ) #set( $expression = "${expression} ${entry}" ) #if ( $foreach.hasNext ) #set( $expression = "${expression}," ) #end #end #end ## Finally, write the update expression into the document, along with any expressionNames and expressionValues ** "update" : { "expression" : "${expression}" #if( !${expNames.isEmpty()} ) ,"expressionNames" : $utils.toJson($expNames) #end #if( !${expValues.isEmpty()} ) ,"expressionValues" : $utils.toJson($expValues) #end }, "condition" : { "expression" : "version = :expectedVersion", "expressionValues" : { ":expectedVersion" : { "N" : ${context.arguments.expectedVersion} } } } }
  • [Save] を選択します。

このテンプレートでは、より複雑な例を示します。マッピングテンプレートの力と柔軟性を示しています。すべての引数をループ処理し、idexpectedVersion をスキップしています。引数が何かの値に設定されている場合は、DynamoDB 内のオブジェクトでその属性を更新するように AWS AppSync と DynamoDB に指示します。属性が null に設定されている場合は、ポストオブジェクトからその属性を削除するように AWS AppSync と DynamoDB に指示します。引数が指定されていない場合、その属性は元のままになります。また、version フィールドが増分されます。

また、新しい condition セクションがあります。条件式により、オペレーションが実行される前に、DynamoDB 内の既存のオブジェクトの状態に基づいて、そのリクエストが成功するかどうかを AWS AppSync と DynamoDB に指示できます。この例では、DynamoDB に現在ある項目の version フィールドが expectedVersion 引数と厳密に一致する場合にのみ、UpdateItem リクエストが成功するように指示しています。

条件式の詳細については、「条件式」リファレンスドキュメントを参照してください。

ポストを更新する API の呼び出し

新しいリゾルバーで Post オブジェクトを更新してみましょう。

  • [Queries] タブを選択します。

  • 次のミューテーションを [Queries (クエリ)] ペインに貼り付けます。また、id 引数を、前にメモしておいた値に更新する必要があります。

    mutation updatePost { updatePost( id:123 title: "An empty story" content: null expectedVersion: 2 ) { id author title content url ups downs version } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • DynamoDB で更新されたポストが、クエリペインの右側にある結果ペインに表示されます。これは次のように表示されます。

    { "data": { "updatePost": { "id": "123", "author": "A new author", "title": "An empty story", "content": null, "url": "https://aws.amazon.com/appsync/", "ups": 1, "downs": 0, "version": 3 } } }

このリクエストでは、titlecontent フィールドのみを更新するように AWS AppSync と DynamoDB に指示しています。その他のフィールドは元のままです (増分する version フィールドは除く)。title 属性を新しい値に設定し、ポストから content 属性を削除しています。authorurlupsdowns の各フィールドは変更されません。

リクエストはまったく同じままで、このミューテーションをもう一度実行してみます。次のようなレスポンスが表示されます。

{ "data": { "updatePost": null }, "errors": [ { "path": [ "updatePost" ], "data": { "id": "123", "author": "A new author", "title": "An empty story", "content": null, "url": "https://aws.amazon.com/appsync/", "ups": 1, "downs": 0, "version": 3 }, "errorType": "DynamoDB:ConditionalCheckFailedException", "locations": [ { "line": 2, "column": 3 } ], "message": "The conditional request failed (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ConditionalCheckFailedException; Request ID: ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ)" } ] }

このリクエストは、条件式が false と評価されるため失敗します。

  • このリクエストを最初に実行したときに、DynamoDB 内のこのポストの version フィールドの値は 2 であり、expectedVersion 引数と一致していました。このリクエストは成功し、DynamoDB で version フィールドが 3 に増分されました。

  • このリクエストを 2 回目に実行したときに、DynamoDB 内のこのポストの version フィールドの値は 3 であり、expectedVersion 引数と一致していませんでした。

このパターンは通常、「オプティミスティックロック」と呼ばれます。

AWS AppSync の DynamoDB のリゾルバーの機能は、DynamoDB 内のポストオブジェクトの現在の値を返すことです。そのことは、GraphQL レスポンスの data セクションの errors フィールドで確認できます。アプリケーションでは、この情報を使用して、どのように続行するかを決めることができます。この例では、DynamoDB 内のオブジェクトの version フィールドが 3 に設定されていることを確認できるため、expectedVersion 引数を 3 に更新するだけで、このリクエストが再度成功するようになります。

条件チェックの失敗の処理の詳細については、マッピングテンプレートのリファレンスドキュメントの「条件式」を参照してください。

upvotePost と downvotePost ミューテーション (DynamoDB UpdateItem) の作成

Post 型には、賛成票と反対票を記録できる ups フィールドと downs フィールドがありますが、この API ではそのフィールドに対して何も行えません。ポストに賛成および反対するための、いくつかのミューテーションを追加してみましょう。

  • [Schema (スキーマ)] タブを選択します。

  • [Schema (スキーマ)] ペインの Mutation 型を変更して、新しい upvotePostdownvotePost ミューテーションを次のように追加します。

    type Mutation { upvotePost(id: ID!): Post downvotePost(id: ID!): Post updatePost( id: ID!, author: String, title: String, content: String, url: String, expectedVersion: Int! ): Post addPost( author: String!, title: String!, content: String!, url: String! ): Post! }
  • [Save] を選択します。

  • 右側の [Data types (データ型)] ペインで、新しく作成したミューテーション型の [upvotePost] フィールドを検索し、[Attach (アタッチ)] を選択します。

  • [Data source name (データソース名)] で、[PostDynamoDBTable] を選択します。

  • 以下のコードを [Configure the request mapping template (リクエストマッピングテンプレートの設定)] に貼り付けます。

    { "version" : "2017-02-28", "operation" : "UpdateItem", "key" : { "id" : { "S" : "${context.arguments.id}" } }, "update" : { "expression" : "ADD ups :plusOne, version :plusOne", "expressionValues" : { ":plusOne" : { "N" : 1 } } } }
  • 以下のコードを [Configure the response mapping template (レスポンスマッピングテンプレートの設定)] に貼り付けます。

    $utils.toJson($context.result)
  • [Save] を選択します。

  • 右側の [Data types (データ型)] ペインで、新しく作成したミューテーション型の [downvotePost] フィールドを検索し、[Attach (アタッチ)] を選択します。

  • [Data source name (データソース名)] で、[PostDynamoDBTable] を選択します。

  • 以下のコードを [Configure the request mapping template (リクエストマッピングテンプレートの設定)] に貼り付けます。

    { "version" : "2017-02-28", "operation" : "UpdateItem", "key" : { "id" : { "S" : "${context.arguments.id}" } }, "update" : { "expression" : "ADD downs :plusOne, version :plusOne", "expressionValues" : { ":plusOne" : { "N" : 1 } } } }
  • 以下のコードを [Configure the response mapping template (レスポンスマッピングテンプレートの設定)] に貼り付けます。

    $utils.toJson($context.result)
  • [Save] を選択します。

ポストに賛成および反対するための API の呼び出し

これで、新しいリゾルバーがセットアップされたので、AWS AppSync は受信 upvotePost または downvote ミューテーションを DynamoDB の UpdateItem オペレーションに変換する方法を知っていることになり、これで、先ほど作成したポストに賛成または反対するミューテーションを実行できるようになりました。

  • [Queries] タブを選択します。

  • 次のミューテーションを [Queries (クエリ)] ペインに貼り付けます。また、id 引数を、前にメモしておいた値に更新する必要があります。

    mutation votePost { upvotePost(id:123) { id author title content url ups downs version } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • このポストが DynamoDB で更新され、クエリペインの右側にある結果ペインに表示されます。これは次のように表示されます。

    { "data": { "upvotePost": { "id": "123", "author": "A new author", "title": "An empty story", "content": null, "url": "https://aws.amazon.com/appsync/", "ups": 6, "downs": 0, "version": 4 } } }
  • さらに数回、[Execute query (クエリを実行)] を選択します。このクエリを実行するたびに、ups フィールドと version フィールドが 1 つずつ増加することを確認できます。

  • 次のようにクエリを変更し、downvotePost ミューテーションを呼び出します。

    mutation votePost { downvotePost(id:123) { id author title content url ups downs version } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。今度は、このクエリを実行するたびに、downs フィールドと version フィールドが 1 つずつ増加することを確認できます。

    { "data": { "downvotePost": { "id": "123", "author": "A new author", "title": "An empty story", "content": null, "url": "https://aws.amazon.com/appsync/", "ups": 6, "downs": 4, "version": 12 } } }

deletePost リゾルバー (DynamoDB DeletePost) のセットアップ

次は、ポストを削除するミューテーションを設定します。そのためには、DynamoDB の DeleteItem オペレーションを使用します。

  • [Schema (スキーマ)] タブを選択します。

  • [Schema (スキーマ)] ペインの Mutation 型を次のように変更して、新しい deletePost ミューテーションを追加します。

    type Mutation { deletePost(id: ID!, expectedVersion: Int): Post upvotePost(id: ID!): Post downvotePost(id: ID!): Post updatePost( id: ID!, author: String, title: String, content: String, url: String, expectedVersion: Int! ): Post addPost( author: String!, title: String!, content: String!, url: String! ): Post! }

    今回は、expectedVersion フィールドを省略可能にしています。これについては、リクエストマッピングテンプレートを追加するときに説明します。

  • [Save] を選択します。

  • 右側の [Data types (データ型)] ペインで、新しく作成したミューテーション型の [delete (削除)] フィールドを検索し、[Attach (アタッチ)] を選択します。

  • [Data source name (データソース名)] で、[PostDynamoDBTable] を選択します。

  • 次のコードを [Configure the request mapping template (リクエストマッピングテンプレートの設定)] に貼り付けます。

    { "version" : "2017-02-28", "operation" : "DeleteItem", "key": { "id": { "S" : "${context.arguments.id}"} } #if( $context.arguments.containsKey("expectedVersion") ) ,"condition" : { "expression" : "attribute_not_exists(id) OR version = :expectedVersion", "expressionValues" : { ":expectedVersion" : { "N" : ${context.arguments.expectedVersion} } } } #end }

    注 : expectedVersion 引数は省略可能な引数です。呼び出し元がリクエストで expectedVersion 引数を設定していると、項目が既に削除されている場合、または DynamoDB 内のポストの version 属性が expectedVersion と完全に一致する場合にのみ、DeleteItem リクエストが成功することを許可する条件が、テンプレートによって追加されます。この引数が省略されている場合は、DeleteItem リクエストで条件式が指定されていません。version の値や項目が DynamoDB に存在するかどうかに関係なく、DeleteItem リクエストは成功します。

  • 以下のコードを [Configure the response mapping template (レスポンスマッピングテンプレートの設定)] に貼り付けます。

    $utils.toJson($context.result)

    注意: 項目を削除する場合でも、その項目がまだ削除されていなければ、削除された項目を返すことができます。

  • [Save] を選択します。

DeleteItem リクエストマッピングの詳細については、「DeleteItem」リファレンスドキュメントを参照してください。

ポストを削除する API の呼び出し

これで、リゾルバーがセットアップされたので、AWS AppSync は受信 delete ミューテーションを DynamoDB の DeleteItem オペレーションに変換する方法を知っていることになり、ユーザーはミューテーションを実行してテーブル内の何かを削除できるようになりました。

  • [Queries] タブを選択します。

  • 次のミューテーションを [Queries (クエリ)] ペインに貼り付けます。また、id 引数を、前にメモしておいた値に更新する必要があります。

    mutation deletePost { deletePost(id:123) { id author title content url ups downs version } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • このポストが DynamoDB から削除されます。AWS AppSync は、DynamoDB から削除された項目の値を返すことに注意してください。その値はクエリペインの右側にある結果ペインに表示されます。これは次のように表示されます。

    { "data": { "deletePost": { "id": "123", "author": "A new author", "title": "An empty story", "content": null, "url": "https://aws.amazon.com/appsync/", "ups": 6, "downs": 4, "version": 12 } } }

この値は、この deletePost 呼び出しによって実際に DynamoDB から項目が削除された場合にのみ返されます。

  • [Execute query (クエリを実行)] を再度選択します。

  • この呼び出しは成功しますが、値は返されません。

    { "data": { "deletePost": null } }

次は、expectedValue を指定して、ポストを削除してみましょう。ただし、これまで使用してきたポストは削除したため、まず新しいポストを作成する必要があります。

  • 以下のミューテーションを [Queries (クエリ)] ペインに貼り付けます。

    mutation addPost { addPost( id:123 author: "AUTHORNAME" title: "Our second post!" content: "A new post." url: "https://aws.amazon.com/appsync/" ) { id author title content url ups downs version } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • 新しく作成されたポストの結果が、クエリペインの右側にある結果ペインに表示されます。新しく作成されたオブジェクトの id を書き留めておきます。その値はすぐに必要になります。これは次のように表示されます。

    { "data": { "addPost": { "id": "123", "author": "AUTHORNAME", "title": "Our second post!", "content": "A new post.", "url": "https://aws.amazon.com/appsync/", "ups": 1, "downs": 0, "version": 1 } } }

次に、expectedVersion に間違った値を入れて、ポストを削除してみましょう。

  • 次のミューテーションを [Queries (クエリ)] ペインに貼り付けます。また、id 引数を、前にメモしておいた値に更新する必要があります。

    mutation deletePost { deletePost( id:123 expectedVersion: 9999 ) { id author title content url ups downs version } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

    { "data": { "deletePost": null }, "errors": [ { "path": [ "deletePost" ], "data": { "id": "123", "author": "AUTHORNAME", "title": "Our second post!", "content": "A new post.", "url": "https://aws.amazon.com/appsync/", "ups": 1, "downs": 0, "version": 1 }, "errorType": "DynamoDB:ConditionalCheckFailedException", "locations": [ { "line": 2, "column": 3 } ], "message": "The conditional request failed (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ConditionalCheckFailedException; Request ID: ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ)" } ] }

    条件式が false と評価されるため、このリクエストは失敗しました。DynamoDB でのそのポストの version の値が、引数で指定した expectedValue と一致していないためです。そのオブジェクトの現在の値が、GraphQL レスポンスの data セクションの errors フィールドで返されます。

  • expectedVersion を訂正して、このリクエストをもう一度試してみます。

    mutation deletePost { deletePost( id:123 expectedVersion: 1 ) { id author title content url ups downs version } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • 今回は、リクエストが成功し、DynamoDB から削除された値が返されています。

    { "data": { "deletePost": { "id": "123", "author": "AUTHORNAME", "title": "Our second post!", "content": "A new post.", "url": "https://aws.amazon.com/appsync/", "ups": 1, "downs": 0, "version": 1 } } }
  • [Execute query (クエリを実行)] を再度選択します。

  • この呼び出しは成功しますが、そのポストが DynamoDB で既に削除されているため、今回は値が返されません。

{ "data": { "deletePost": null } }

allPost リゾルバー (DynamoDB Scan) のセットアップ

ここまでで、API が役立つのは、確認する各ポストの :code:id がわかっている場合のみです。テーブル内のすべてのポストを返す新しいリゾルバーを追加してみましょう。

  • [Schema (スキーマ)] タブを選択します。

  • [Schema (スキーマ)] ペインの Query 型を次のように変更して、新しい allPost クエリを追加します。

    type Query { allPost(count: Int, nextToken: String): PaginatedPosts! getPost(id: ID): Post }
  • 新しい PaginationPosts 型を追加します。

    type PaginatedPosts { posts: [Post!]! nextToken: String }
  • [Save] を選択します。

  • 右側の [Data types (データ型)] ペインで、新しく作成したクエリ型の [allPost] フィールドを検索し、[Attach (アタッチ)] を選択します。

  • [Data source name (データソース名)] で、[PostDynamoDBTable] を選択します。

  • 以下のコードを [Configure the request mapping template (リクエストマッピングテンプレートの設定)] に貼り付けます。

    { "version" : "2017-02-28", "operation" : "Scan" #if( ${context.arguments.count} ) ,"limit": ${context.arguments.count} #end #if( ${context.arguments.nextToken} ) ,"nextToken": "${context.arguments.nextToken}" #end }

    このリゾルバーには、省略可能な 2 つの引数があります。count では、1 回の呼び出しで返される項目の最大数を指定し、nextToken は、次の結果セットを取得するために使用できます (nextToken の値がどのように生成されるかについては、後で説明します)。

  • 以下のコードを [Configure the response mapping template (レスポンスマッピングテンプレートの設定)] に貼り付けます。

    { "posts": $utils.toJson($context.result.items) #if( ${context.result.nextToken} ) ,"nextToken": "${context.result.nextToken}" #end }

    注 : このレスポンスマッピングテンプレートは、これまで他の例で使用したものとは異なります。allPost クエリの結果は PaginatedPosts であり、ポストのリストとページ分割トークンが含まれています。このオブジェクトのシェイプは、AWS AppSync DynamoDB のリゾルバーから返されるものと異なります。ポストのリストは、AWS AppSync DynamoDB のリゾルバーの結果では items という名前ですが、PaginatedPosts では posts という名前です。

  • [Save] を選択します。

Scan リクエストマッピングの詳細については、「Scan」リファレンスドキュメントを参照してください。

すべてのポストをスキャンする API の呼び出し

これで、リゾルバーがセットアップされたので、AWS AppSync は受信 allPost クエリを DynamoDB の Scan オペレーションに変換する方法を知っていることになり、ユーザーは、テーブルをスキャンしてすべてのポストを取得できるようになりました。

ただし、これまで使用してきたデータはすべて削除したため、これを試す前にテーブルにデータを入力しておく必要があります。

  • [Queries] タブを選択します。

  • 以下のミューテーションを [Queries (クエリ)] ペインに貼り付けます。

    mutation addPost { post1: addPost(id:1 author: "AUTHORNAME" title: "A series of posts, Volume 1" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } post2: addPost(id:2 author: "AUTHORNAME" title: "A series of posts, Volume 2" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } post3: addPost(id:3 author: "AUTHORNAME" title: "A series of posts, Volume 3" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } post4: addPost(id:4 author: "AUTHORNAME" title: "A series of posts, Volume 4" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } post5: addPost(id:5 author: "AUTHORNAME" title: "A series of posts, Volume 5" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } post6: addPost(id:6 author: "AUTHORNAME" title: "A series of posts, Volume 6" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } post7: addPost(id:7 author: "AUTHORNAME" title: "A series of posts, Volume 7" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } post8: addPost(id:8 author: "AUTHORNAME" title: "A series of posts, Volume 8" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } post9: addPost(id:9 author: "AUTHORNAME" title: "A series of posts, Volume 9" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

では、テーブルをスキャンして、一度に 5 個の結果を返しましょう。

  • 次のクエリを [Queries (クエリ)] ペインに貼り付けます。

    query allPost { allPost(count: 5) { posts { id title } nextToken } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • 最初の 5 個のポストが、クエリペインの右側にある結果ペインに表示されます。これは次のように表示されます。

    { "data": { "allPost": { "posts": [ { "id": "5", "title": "A series of posts, Volume 5" }, { "id": "1", "title": "A series of posts, Volume 1" }, { "id": "6", "title": "A series of posts, Volume 6" }, { "id": "9", "title": "A series of posts, Volume 9" }, { "id": "7", "title": "A series of posts, Volume 7" } ], "nextToken": "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnRkJEdXdUK09hcnovRGhNTGxLTGdMUEFBQUI1akNDQWVJR0NTcUdTSWIzRFFFSEJxQ0NBZE13Z2dIUEFnRUFNSUlCeUFZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF6ajFodkhKU1paT1pncTRaUUNBUkNBZ2dHWnJiR1dQWGxkMDB1N0xEdGY4Z2JsbktzRjRua1VCcks3TFJLcjZBTFRMeGFwVGJZMDRqOTdKVFQyYVRwSzdzbVdtNlhWWFVCTnFIOThZTzBWZHVkdDI2RlkxMHRqMDJ2QTlyNWJTUWpTbWh6NE5UclhUMG9KZWJSQ2JJbXBlaDRSVlg0Tis0WTVCN1IwNmJQWWQzOVhsbTlUTjBkZkFYMVErVCthaXZoNE5jMk50RitxVmU3SlJ5WmpzMEFkSGduM3FWd2VrOW5oeFVVd3JlK1loUks5QkRzemdiMDlmZmFPVXpzaFZ4cVJRbC93RURlOTcrRmVJdXZNby9NZ1F6dUdNbFRyalpNR3FuYzZBRnhwa0VlZTFtR0FwVDFISElUZlluakptYklmMGUzUmcxbVlnVHVSbDh4S0trNmR0QVoraEhLVDhuNUI3VnF4bHRtSnlNUXBrZGl6KzkyL3VzNDl4OWhrMnVxSW01ZFFwMjRLNnF0dm9ZK1BpdERuQTc5djhzb0grVytYT3VuQ2NVVDY4TVZ1Wk5KYkRuSEFSSEVlaTlVNVBTelU5RGZ6d2pPdmhqWDNJMWhwdWUrWi83MDVHVjlPQUxSTGlwZWZPeTFOZFhwZTdHRDZnQW00bUJUK2c1eC9Ec3ZDbWVnSDFDVXRTdHVuU1ZFa2JpZytQRC9oMUwyRTNqSHhVQldaa28yU256WUc0cG0vV1RSWkFVZHZuQT09In0=" } } }

5 つの結果と nextToken を取得しました。このトークンを使用して、次の結果セットを取得できます。

  • 前回の結果セットからの allPost を含めるように、nextToken クエリを更新します。

    query allPost { allPost( count: 5 nextToken: "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnRlluNktJRWl6V0ZlR3hJOVJkaStrZUFBQUI1akNDQWVJR0NTcUdTSWIzRFFFSEJxQ0NBZE13Z2dIUEFnRUFNSUlCeUFZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF5cW8yUGFSZThnalFpemRCTUNBUkNBZ2dHWk1JODhUNzhIOFVUZGtpdFM2ZFluSWRyVDg4c2lkN1RjZzB2d1k3VGJTTWpSQ2U3WjY3TkUvU2I1dWNETUdDMmdmMHErSGJSL0pteGRzYzVEYnE1K3BmWEtBdU5jSENJdWNIUkJ0UHBPWVdWdCtsS2U5L1pNcWdocXhrem1RaXI1YnIvQkt6dU5hZmJCdE93NmtoM2Jna1BKM0RjWWhpMFBGbmhMVGg4TUVGSjBCcXg3RTlHR1V5N0tUS0JLZlV3RjFQZ0JRREdrNzFYQnFMK2R1S2IrVGtZZzVYMjFrc3NyQmFVTmNXZmhTeXE0ZUJHSWhqZWQ5c3VKWjBSSTc2ZnVQdlZkR3FLNENjQmxHYXhpekZnK2pKK1FneEU1SXduRTNYYU5TR0I4QUpmamR2bU1wbUk1SEdvWjlMUUswclczbG14RDRtMlBsaTNLaEVlcm9pem5zcmdINFpvcXIrN2ltRDN3QkJNd3BLbGQzNjV5Nnc4ZnMrK2FnbTFVOUlKOFFrOGd2bEgySHFROHZrZXBrMWlLdWRIQ25LaS9USnBlMk9JeEVPazVnRFlzRTRUU09HUlVJTkxYY2MvdW1WVEpBMUthV2hWTlAvdjNlSnlZQUszbWV6N2h5WHVXZ1BkTVBNWERQdTdjVnVRa3EwK3NhbGZOd2wvSUx4bHNyNDVwTEhuVFpyRWZvVlV1bXZ5S2VKY1RUU1lET05hM1NwWEd2UT09In0=" ) { posts { id author } nextToken } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • 残りの 4 個のポストが、クエリペインの右側にある結果ペインに表示されます。9 個のポストのすべてをページ分割して、ポストは残っていないため、この結果セットに nextToken はありません。これは次のように表示されます。

    { "data": { "allPost": { "posts": [ { "id": "2", "title": "A series of posts, Volume 2" }, { "id": "3", "title": "A series of posts, Volume 3" }, { "id": "4", "title": "A series of posts, Volume 4" }, { "id": "8", "title": "A series of posts, Volume 8" } ], "nextToken": null } } }

allPostsByAuthor リゾルバー (DynamoDB Query) のセットアップ

DynamoDB ですべてのポストをスキャンだけでなく、特定の作成者が作成したポストを取得するクエリを DynamoDB に対して実行することもできます。前の手順で作成した DynamoDB テーブルには、既に author-index という GlobalSecondaryIndex があるため、DynamoDB の Query オペレーションでそれを使用して、特定の作成者が作成したすべてのポストを取得できます

  • [Schema (スキーマ)] タブを選択します。

  • [Schema (スキーマ)] ペインの Query 型を次のように変更して、新しい allPostsByAuthor クエリを追加します。

    type Query { allPostsByAuthor(author: String!, count: Int, nextToken: String): PaginatedPosts! allPost(count: Int, nextToken: String): PaginatedPosts! getPost(id: ID): Post }

    注 : このクエリでは、allPost クエリで使用したのと同じ PaginatedPosts 型を使用しています。

  • [Save] を選択します。

  • 右側の [Data types (データ型)] ペインで、新しく作成したクエリ型の [allPostsByAuthor] フィールドを検索し、[Attach (アタッチ)] を選択します。

  • [Data source name (データソース名)] で、[PostDynamoDBTable] を選択します。

  • 以下のコードを [Configure the request mapping template (リクエストマッピングテンプレートの設定)] に貼り付けます。

    { "version" : "2017-02-28", "operation" : "Query", "index" : "author-index", "query" : { "expression": "author = :author", "expressionValues" : { ":author" : { "S" : "${context.arguments.author}" } } } #if( ${context.arguments.count} ) ,"limit": ${context.arguments.count} #end #if( ${context.arguments.nextToken} ) ,"nextToken": "${context.arguments.nextToken}" #end }

    allPost リゾルバーと同様に、このリゾルバーには、省略可能な 2 つの引数があります。count では、1 回の呼び出しで返される項目の最大数を指定し、nextToken は、次の結果セットを取得するために使用できます (nextToken の値は前回の呼び出しから取得できます)。

  • 以下のコードを [Configure the response mapping template (レスポンスマッピングテンプレートの設定)] に貼り付けます。

    { "posts": $utils.toJson($context.result.items) #if( ${context.result.nextToken} ) ,"nextToken": "${context.result.nextToken}" #end }

    注 : これは、allPost リゾルバーで使用したのと同じレスポンスマッピングテンプレートです。

  • [Save] を選択します。

Query リクエストマッピングの詳細については、「クエリ」リファレンスドキュメントを参照してください。

特定の作成者によるすべてのポストをクエリする API の呼び出し

これで、リゾルバーがセットアップされたので、AWS AppSync は受信 allPostsByAuthor ミューテーションを、author-index インデックスに対する DynamoDB の Query オペレーションに変換する方法を知っていることになり、ユーザーは、テーブルをクエリして、特定の作成者によるポストをすべて取得できます。

ただし、これまで使用していたポストはすべて同じ作成者だったため、それを行う前に、テーブルにポストを追加しておきましょう。

  • [Queries] タブを選択します。

  • 以下のミューテーションを [Queries (クエリ)] ペインに貼り付けます。

    mutation addPost { post1: addPost(id:10 author: "Nadia" title: "The cutest dog in the world" content: "So cute. So very, very cute." url: "https://aws.amazon.com/appsync/" ) { author, title } post2: addPost(id:11 author: "Nadia" title: "Did you know...?" content: "AppSync works offline?" url: "https://aws.amazon.com/appsync/" ) { author, title } post3: addPost(id:12 author: "Steve" title: "I like GraphQL" content: "It's great" url: "https://aws.amazon.com/appsync/" ) { author, title } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

では、Nadia が作成したすべてのポストを返すクエリを実行しましょう。

  • 次のクエリを [Queries (クエリ)] ペインに貼り付けます。

    query allPostsByAuthor { allPostsByAuthor(author: "Nadia") { posts { id title } nextToken } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • Nadia が作成したすべてのポストが、クエリペインの右側にある結果ペインに表示されます。これは次のように表示されます。

    { "data": { "allPostsByAuthor": { "posts": [ { "id": "10", "title": "The cutest dog in the world" }, { "id": "11", "title": "Did you know...?" } ], "nextToken": null } } }

Query でのページ分割は Scan とまったく同じように動作します。たとえば、AUTHORNAME によるすべてのポストを検索して、一度に 5 個ずつ取得します。

  • 次のクエリを [Queries (クエリ)] ペインに貼り付けます。

    query allPostsByAuthor { allPostsByAuthor( author: "AUTHORNAME" count: 5 ) { posts { id title } nextToken } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • AUTHORNAME が作成したすべてのポストが、クエリペインの右側にある結果ペインに表示されます。これは次のように表示されます。

    { "data": { "allPostsByAuthor": { "posts": [ { "id": "6", "title": "A series of posts, Volume 6" }, { "id": "4", "title": "A series of posts, Volume 4" }, { "id": "2", "title": "A series of posts, Volume 2" }, { "id": "7", "title": "A series of posts, Volume 7" }, { "id": "1", "title": "A series of posts, Volume 1" } ], "nextToken": "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnSExqRnVhVUR3ZUhEZ2QzNGJ2QlFuY0FBQUNqekNDQW9zR0NTcUdTSWIzRFFFSEJxQ0NBbnd3Z2dKNEFnRUFNSUlDY1FZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF5Qkg4Yk1obW9LVEFTZHM3SUNBUkNBZ2dKQ3dISzZKNlJuN3pyYUVKY1pWNWxhSkNtZW1KZ0F5N1dhZkc2UEdTNHpNQzJycTkwZHFJTFV6Z25wck9Gd3pMS3VOQ2JvUXc3VDI5eCtnVExIbGg4S3BqbzB1YjZHQ3FwcDhvNDVmMG9JbDlmdS9JdjNXcFNNSXFKTXZ1MEVGVWs1VzJQaW5jZGlUaVRtZFdYWlU1bkV2NkgyRFBRQWZYYlNnSmlHSHFLbmJZTUZZM0FTdmRIL0hQaVZBb1RCMk1YZkg0eGJOVTdEbjZtRFNhb2QwbzdHZHJEWDNtODQ1UXBQUVNyUFhHemY0WDkyajhIdlBCSWE4Smcrb0RxbHozUVQ5N2FXUXdYWWU2S0h4emI1ejRITXdEdXEyRDRkYzhoMi9CbW10MzRMelVGUVIyaExSZGRaZ0xkdzF5cHJZdFZwY3dEc1d4UURBTzdOcjV2ZEp4VVR2TVhmODBRSnp1REhXREpTVlJLdDJwWmlpaXhXeGRwRmNod1BzQ3d2aVBqMGwrcWFFWU1jMXNQbENkVkFGem43VXJrSThWbS8wWHlwR2xZb3BSL2FkV0xVekgrbGMrYno1ZEM2SnVLVXdtY1EyRXlZeDZiS0Izbi9YdUViWGdFeU5PMWZTdE1rRlhyWmpvMVpzdlYyUFRjMzMrdEs0ZDhkNkZrdjh5VVR6WHhJRkxIaVNsOUx6VVdtT3BCaWhrTFBCT09jcXkyOHh1UmkzOEM3UFRqMmN6c3RkOUo1VUY0azBJdUdEbVZzM2xjdWg1SEJjYThIeXM2aEpvOG1HbFpMNWN6R2s5bi8vRE1EbDY3RlJraG5QNFNhSDBpZGI5VFEvMERLeFRBTUdhcWpPaEl5ekVqd2ZDQVJleFdlbldyOGlPVkhScDhGM25WZVdvbFRGK002N0xpdi9XNGJXdDk0VEg3b0laUU5lYmZYKzVOKy9Td25Hb1dyMTlWK0pEb2lIRVFLZ1cwMWVuYjZKUXo5Slh2Tm95ZzF3RnJPVmxGc2xwNlRHa1BlN2Rnd2IrWT0ifQ==" } } }
  • 次のように、nextToken 引数を、前回のクエリで返された値に更新します。

    query allPostsByAuthor { allPostsByAuthor( author: "AUTHORNAME" count: 5 nextToken: "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnSExqRnVhVUR3ZUhEZ2QzNGJ2QlFuY0FBQUNqekNDQW9zR0NTcUdTSWIzRFFFSEJxQ0NBbnd3Z2dKNEFnRUFNSUlDY1FZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF5Qkg4Yk1obW9LVEFTZHM3SUNBUkNBZ2dKQ3dISzZKNlJuN3pyYUVKY1pWNWxhSkNtZW1KZ0F5N1dhZkc2UEdTNHpNQzJycTkwZHFJTFV6Z25wck9Gd3pMS3VOQ2JvUXc3VDI5eCtnVExIbGg4S3BqbzB1YjZHQ3FwcDhvNDVmMG9JbDlmdS9JdjNXcFNNSXFKTXZ1MEVGVWs1VzJQaW5jZGlUaVRtZFdYWlU1bkV2NkgyRFBRQWZYYlNnSmlHSHFLbmJZTUZZM0FTdmRIL0hQaVZBb1RCMk1YZkg0eGJOVTdEbjZtRFNhb2QwbzdHZHJEWDNtODQ1UXBQUVNyUFhHemY0WDkyajhIdlBCSWE4Smcrb0RxbHozUVQ5N2FXUXdYWWU2S0h4emI1ejRITXdEdXEyRDRkYzhoMi9CbW10MzRMelVGUVIyaExSZGRaZ0xkdzF5cHJZdFZwY3dEc1d4UURBTzdOcjV2ZEp4VVR2TVhmODBRSnp1REhXREpTVlJLdDJwWmlpaXhXeGRwRmNod1BzQ3d2aVBqMGwrcWFFWU1jMXNQbENkVkFGem43VXJrSThWbS8wWHlwR2xZb3BSL2FkV0xVekgrbGMrYno1ZEM2SnVLVXdtY1EyRXlZeDZiS0Izbi9YdUViWGdFeU5PMWZTdE1rRlhyWmpvMVpzdlYyUFRjMzMrdEs0ZDhkNkZrdjh5VVR6WHhJRkxIaVNsOUx6VVdtT3BCaWhrTFBCT09jcXkyOHh1UmkzOEM3UFRqMmN6c3RkOUo1VUY0azBJdUdEbVZzM2xjdWg1SEJjYThIeXM2aEpvOG1HbFpMNWN6R2s5bi8vRE1EbDY3RlJraG5QNFNhSDBpZGI5VFEvMERLeFRBTUdhcWpPaEl5ekVqd2ZDQVJleFdlbldyOGlPVkhScDhGM25WZVdvbFRGK002N0xpdi9XNGJXdDk0VEg3b0laUU5lYmZYKzVOKy9Td25Hb1dyMTlWK0pEb2lIRVFLZ1cwMWVuYjZKUXo5Slh2Tm95ZzF3RnJPVmxGc2xwNlRHa1BlN2Rnd2IrWT0ifQ==" ) { posts { id title } nextToken } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • AUTHORNAME が作成した残りのポストが、クエリペインの右側にある結果ペインに表示されます。これは次のように表示されます。

    { "data": { "allPostsByAuthor": { "posts": [ { "id": "8", "title": "A series of posts, Volume 8" }, { "id": "5", "title": "A series of posts, Volume 5" }, { "id": "3", "title": "A series of posts, Volume 3" }, { "id": "9", "title": "A series of posts, Volume 9" } ], "nextToken": null } } }

セット型の使用

ここまでの Post 型は、フラットなキーと値のオブジェクトでした。AWS AppSync DynamoDB リゾルバーを使用して、セット型、リスト型、マップ型などの複雑なオブジェクトをモデル化することもできます。

Post 型を更新して、タグを含めましょう。1 つのポストには、DynamoDB に文字列として保存されているタグを 0 個以上付けることができます。タグを追加および削除するミューテーションと、特定のタグが付いているポストをスキャンする新しいクエリもセットアップします。

  • [Schema (スキーマ)] タブを選択します。

  • [Schema (スキーマ)] ペインの Post 型を次のように変更して、新しい tags フィールドを追加します。

    type Post { id: ID! author: String title: String content: String url: String ups: Int! downs: Int! version: Int! tags: [String!] }
  • [Schema (スキーマ)] ペインの Query 型を次のように変更して、新しい allPostsByTag クエリを追加します。

    type Query { allPostsByTag(tag: String!, count: Int, nextToken: String): PaginatedPosts! allPostsByAuthor(author: String!, count: Int, nextToken: String): PaginatedPosts! allPost(count: Int, nextToken: String): PaginatedPosts! getPost(id: ID): Post }
  • [Schema (スキーマ)] ペインの Mutation 型を変更して、新しい addTagremoveTag ミューテーションを次のように追加します。

    type Mutation { addTag(id: ID!, tag: String!): Post removeTag(id: ID!, tag: String!): Post deletePost(id: ID!, expectedVersion: Int): Post upvotePost(id: ID!): Post downvotePost(id: ID!): Post updatePost( id: ID!, author: String, title: String, content: String, url: String, expectedVersion: Int! ): Post addPost( author: String!, title: String!, content: String!, url: String! ): Post! }
  • [Save] を選択します。

  • 右側の [Data types (データ型)] ペインで、新しく作成したクエリ型の [allPostsByTag] フィールドを検索し、[Attach (アタッチ)] を選択します。

  • [Data source name (データソース名)] で、[PostDynamoDBTable] を選択します。

  • 以下のコードを [Configure the request mapping template (リクエストマッピングテンプレートの設定)] に貼り付けます。

    { "version" : "2017-02-28", "operation" : "Scan", "filter": { "expression": "contains (tags, :tag)", "expressionValues": { ":tag": { "S": "${context.arguments.tag}" } } } #if( ${context.arguments.count} ) ,"limit": ${context.arguments.count} #end #if( ${context.arguments.nextToken} ) ,"nextToken": "${context.arguments.nextToken}" #end }
  • 以下のコードを [Configure the response mapping template (レスポンスマッピングテンプレートの設定)] に貼り付けます。

    { "posts": $utils.toJson($context.result.items) #if( ${context.result.nextToken} ) ,"nextToken": "${context.result.nextToken}" #end }
  • [Save] を選択します。

  • 右側の [Data types (データ型)] ペインで、新しく作成したミューテーション型の [addTag] フィールドを検索し、[Attach (アタッチ)] を選択します。

  • [Data source name (データソース名)] で、[PostDynamoDBTable] を選択します。

  • 以下のコードを [Configure the request mapping template (リクエストマッピングテンプレートの設定)] に貼り付けます。

    { "version" : "2017-02-28", "operation" : "UpdateItem", "key" : { "id" : { "S" : "${context.arguments.id}" } }, "update" : { "expression" : "ADD tags :tags, version :plusOne", "expressionValues" : { ":tags" : { "SS": [ "${context.arguments.tag}" ] }, ":plusOne" : { "N" : 1 } } } }
  • 以下のコードを [Configure the response mapping template (レスポンスマッピングテンプレートの設定)] に貼り付けます。

    $utils.toJson($context.result)
  • [Save] を選択します。

  • 右側の [Data types (データ型)] ペインで、新しく作成したミューテーション型の [removeTag] フィールドを検索し、[Attach (アタッチ)] を選択します。

  • [Data source name (データソース名)] で、[PostDynamoDBTable] を選択します。

  • 以下のコードを [Configure the request mapping template (リクエストマッピングテンプレートの設定)] に貼り付けます。

    { "version" : "2017-02-28", "operation" : "UpdateItem", "key" : { "id" : { "S" : "${context.arguments.id}" } }, "update" : { "expression" : "DELETE tags :tags ADD version :plusOne", "expressionValues" : { ":tags" : { "SS": [ "${context.arguments.tag}" ] }, ":plusOne" : { "N" : 1 } } } }
  • 以下のコードを [Configure the response mapping template (レスポンスマッピングテンプレートの設定)] に貼り付けます。

    $utils.toJson($context.result)
  • [Save] を選択します。

タグを操作する API の呼び出し

これで、リゾルバーがセットアップされたので、AWS AppSync は受信 addTagremoveTag、および allPostsByTag リクエストを DynamoDB の UpdateItem および Scan オペレーションに変換する方法を知っていることになります。

それを試すには、前のステップで作成したポストのいずれかを選択します。たとえば、Nadia が作成したポストを使用しましょう。

  • [Queries] タブを選択します。

  • 以下のクエリを [Queries (クエリ)] ペインに貼り付けます。

    query allPostsByAuthor { allPostsByAuthor( author: "Nadia" ) { posts { id title } nextToken } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • Nadia のすべてのポストが、クエリペインの右側にある結果ペインに表示されます。これは次のように表示されます。

    { "data": { "allPostsByAuthor": { "posts": [ { "id": "10", "title": "The cutest dog in the world" }, { "id": "11", "title": "Did you known...?" } ], "nextToken": null } } }
  • タイトルが「"The cutest dog in the world"」のポストを使用しましょう。id は後で使用するため書き留めておきます。

では、dog タグを追加してみましょう。

  • 次のミューテーションを [Queries (クエリ)] ペインに貼り付けます。また、id 引数を、前にメモしておいた値に更新する必要があります。

    mutation addTag { addTag(id:10 tag: "dog") { id title tags } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • ポストが新しいタグで更新されています。

    { "data": { "addTag": { "id": "10", "title": "The cutest dog in the world", "tags": [ "dog" ] } } }

次のようにタグを追加できます。

  • puppy に変更するように、tag 引数を更新します。

    mutation addTag { addTag(id:10 tag: "puppy") { id title tags } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • ポストが新しいタグで更新されています。

    { "data": { "addTag": { "id": "10", "title": "The cutest dog in the world", "tags": [ "dog", "puppy" ] } } }

タグを削除することもできます。

  • 以下のミューテーションを [Queries (クエリ)] ペインに貼り付けます。また、id 引数を、前にメモしておいた値に更新する必要があります。

    mutation removeTag { removeTag(id:10 tag: "puppy") { id title tags } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • ポストが更新され、puppy タグが削除されています。

    { "data": { "addTag": { "id": "10", "title": "The cutest dog in the world", "tags": [ "dog" ] } } }

タグが付いているすべてのポストを検索することもできます。

  • 以下のクエリを [Queries (クエリ)] ペインに貼り付けます。

    query allPostsByTag { allPostsByTag(tag: "dog") { posts { id title tags } nextToken } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • 次のように dog タグが付いているすべての投稿が返されます。

    { "data": { "allPostsByTag": { "posts": [ { "id": "10", "title": "The cutest dog in the world", "tags": [ "dog", "puppy" ] } ], "nextToken": null } } }

リスト型とマップ型の使用

DynamoDB のセット型を使用するだけでなく、DynamoDB のリスト型やマップ型を使用して、複雑なデータを単一のオブジェクトにモデル化することもできます。

ポストにコメントを追加する機能を追加しましょう。これは、DynamoDB 内の Post オブジェクトで、マップ型オブジェクトのリスト型としてモデル化されます。

注 : 実際のアプリケーションでは、独自のテーブル内のコメントをモデル化します。このチュートリアルでは、Post テーブルにコメントを追加しているだけです。

  • [Schema (スキーマ)] タブを選択します。

  • [Schema (スキーマ)] ペインで、次のように新しい Comment 型を追加します。

    type Comment { author: String! comment: String! }
  • [Schema (スキーマ)] ペインの Post 型を次のように変更して、新しい comments フィールドを追加します。

    type Post { id: ID! author: String title: String content: String url: String ups: Int! downs: Int! version: Int! tags: [String!] comments: [Comment!] }
  • [Schema (スキーマ)] ペインの Mutation 型を次のように変更して、新しい addComment ミューテーションを追加します。

    type Mutation { addComment(id: ID!, author: String!, comment: String!): Post addTag(id: ID!, tag: String!): Post removeTag(id: ID!, tag: String!): Post deletePost(id: ID!, expectedVersion: Int): Post upvotePost(id: ID!): Post downvotePost(id: ID!): Post updatePost( id: ID!, author: String, title: String, content: String, url: String, expectedVersion: Int! ): Post addPost( author: String!, title: String!, content: String!, url: String! ): Post! }
  • [Save] を選択します。

  • 右側の [Data types (データ型)] ペインで、新しく作成したミューテーション型の [addComment] フィールドを検索し、[Attach (アタッチ)] を選択します。

  • [Data source name (データソース名)] で、[PostDynamoDBTable] を選択します。

  • 以下のコードを [Configure the request mapping template (リクエストマッピングテンプレートの設定)] に貼り付けます。

    { "version" : "2017-02-28", "operation" : "UpdateItem", "key" : { "id" : { "S" : "${context.arguments.id}" } }, "update" : { "expression" : "SET comments = list_append(if_not_exists(comments, :emptyList), :newComment) ADD version :plusOne", "expressionValues" : { ":emptyList": { "L" : [] }, ":newComment" : { "L" : [ { "M": { "author": { "S" : "${context.arguments.author}" }, "comment": { "S" : "${context.arguments.comment}" } }} ] }, ":plusOne" : { "N" : 1 } } } }

    この更新式によって、新しいコメントが含まれているリストが、既存の comments リストに追加されます。リストがまだ存在しない場合は作成されます。

  • 以下のコードを [Configure the response mapping template (レスポンスマッピングテンプレートの設定)] に貼り付けます。

    $utils.toJson($context.result)
  • [Save] を選択します。

コメントを追加する API の呼び出し

これで、リゾルバーがセットアップされたので、AWS AppSync は受信 addComment リクエストを DynamoDB の UpdateItem オペレーションに変換する方法を知っていることになります。

タグを追加したのと同じポストにコメントを追加して、試してみましょう。

  • [Queries] タブを選択します。

  • 以下のクエリを [Queries (クエリ)] ペインに貼り付けます。

    mutation addComment { addComment( id:10 author: "Steve" comment: "Such a cute dog." ) { id comments { author comment } } }
  • [Execute query (クエリを実行)] (オレンジ色の再生ボタン) を選択します。

  • Nadia のすべてのポストが、クエリペインの右側にある結果ペインに表示されます。これは次のように表示されます。

    { "data": { "addComment": { "id": "10", "comments": [ { "author": "Steve", "comment": "Such a cute dog." } ] } } }

このリクエストを複数回実行すると、複数のコメントがリストに追加されます。

まとめ

このチュートリアルでは、AWS AppSync と GraphQL を使用して DynamoDB 内のポストオブジェクトを操作できる API を構築しました。詳細については、「リゾルバーのマッピングテンプレートリファレンス」を参照してください。

クリーンアップするには、AppSync GraphQL API をコンソールから削除します。

このチュートリアルで作成した DynamoDB テーブルおよび IAM ロールを削除するには、次のコマンドを実行して、AWSAppSyncTutorialForAmazonDynamoDB スタックを削除するか、または AWS CloudFormation コンソールに移動して、スタックを削除します。

aws cloudformation delete-stack \ --stack-name AWSAppSyncTutorialForAmazonDynamoDB