

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

# の VTL リゾルバーチュートリアル AWS AppSync
<a name="tutorials"></a>

**注記**  
現在、主に APPSYNC\$1JS ランタイムとそのドキュメントをサポートしています。[こちら](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html) で APPSYNC\$1JS ランタイムとそのガイドの使用をご検討ください。

データソースとリゾルバーは AWS AppSync によって GraphQL リクエストを変換し、 AWS リソースから情報を取得するために使用されます。 AWS AppSync は、特定のデータソースタイプとの自動プロビジョニングと接続をサポートしています。 AWS AppSync は AWS Lambda、データソースとして Amazon DynamoDB、リレーショナルデータベース (Amazon Aurora Serverless)、Amazon OpenSearch Service、HTTP エンドポイントもサポートしています。既存の AWS リソースで GraphQL API を使用するか、データソースとリゾルバーをゼロから構築できます。以下のセクションは、より一般的な GraphQL のユースケースをチュートリアルの形式で説明することを目的としています。

AWS AppSync は、リゾルバーの Apache Velocity Template Language (VTL) で記述された*マッピング*テンプレートを使用します。 マッピングテンプレートの詳細については、「[リゾルバーのマッピングテンプレートリファレンス](resolver-mapping-template-reference.md#aws-appsync-resolver-mapping-template-reference)」を参照してください。VTL の操作の詳細については、「[リゾルバマッピングテンプレートプログラミングガイド](resolver-mapping-template-reference-programming-guide.md#aws-appsync-resolver-mapping-template-reference-programming-guide)」を参照してください。

AWS AppSync は、「スキーマからプロビジョニングする (オプション）」および「サンプルスキーマを起動する」で説明されているように、GraphQL スキーマからの DynamoDB テーブルの自動プロビジョニングをサポートしています。既存の DynamoDB テーブルからインポートして、スキーマを作成し、リゾルバーを接続することもできます。それについては、「(オプション) Amazon DynamoDB からのインポート」で説明しています。

**Topics**
+ [DynamoDB リゾルバーを使用したシンプルなポストアプリケーションの作成](tutorial-dynamodb-resolvers.md)
+ [AWS Lambda リゾルバーの使用](tutorial-lambda-resolvers.md)
+ [OpenSearch Service リゾルバーの使用](tutorial-elasticsearch-resolvers.md)
+ [ローカルリゾルバーの使用](tutorial-local-resolvers.md)
+ [GraphQL リゾルバーの組み合わせ](tutorial-combining-graphql-resolvers.md)
+ [DynamoDB バッチオペレーションの使用](tutorial-dynamodb-batch.md)
+ [DynamoDB トランザクションの実行](tutorial-dynamodb-transact.md)
+ [HTTP リゾルバーの使用](tutorial-http-resolvers.md)
+ [Aurora Serverless v2 リゾルバーの使用](tutorial-rds-resolvers.md)
+ [パイプラインリゾルバーの使用](tutorial-pipeline-resolvers.md)
+ [バージョニングされたデータソースでの Delta Sync オペレーションの使用](tutorial-delta-sync.md)

# DynamoDB リゾルバーを使用したシンプルなポストアプリケーションの作成
<a name="tutorial-dynamodb-resolvers"></a>

**注記**  
現在、主に APPSYNC\$1JS ランタイムとそのドキュメントをサポートしています。[こちら](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html) で APPSYNC\$1JS ランタイムとそのガイドの使用をご検討ください。

このチュートリアルでは、独自の Amazon DynamoDB テーブルを AWS AppSync に持ち込み、GraphQL API に接続する方法を示します。

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

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

## DynamoDB テーブルのセットアップ
<a name="setting-up-your-ddb-tables"></a>

このチュートリアルを開始するには、まず以下の手順に従って AWS リソースをプロビジョニングする必要があります。

1. CLI で次の 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
   ```

   または、アカウントの米国西部 2 (オレゴン) リージョンで次の CloudFormation スタックを起動することもできます AWS 。

   [https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/dynamodb/AmazonDynamoDBCFTemplate.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/dynamodb/AmazonDynamoDBCFTemplate.yaml)

   これにより、以下の項目が作成されます。
   + データを保持するDynamoDBテーブルが`AppSyncTutorial-Post`呼び出されます。`Post`
   +  AWS AppSync が`Post`テーブルとやり取りできるようにする IAM ロールおよび関連する IAM 管理ポリシー。

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

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

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

   ```
   aws cloudformation delete-stack --stack-name AWSAppSyncTutorialForAmazonDynamoDB
   ```

## GraphQL API の作成
<a name="creating-your-graphql-api"></a>

 AWS AppSync で GraphQL API を作成するには:

1. にサインイン AWS マネジメントコンソール し、[AppSync コンソール](https://console.aws.amazon.com/appsync/)を開きます。

   1. **API ダッシュボード**で、**[API の作成]** を選択します。

1. **[API をカスタマイズまたは Amazon DynamoDB からインポート]** ウィンドウで、**[一から構築]** を選択します。

   1. 同じウィンドウの右にある **[開始]** を選択します。

1. **[API 名]** フィールドで、API の名前を `AWSAppSyncTutorial` に設定します。

1. **[作成]** を選択します。

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

## 基本的な Post API の定義
<a name="defining-a-basic-post-api"></a>

 AWS AppSync GraphQL API を作成したので、ポストデータの基本作成、取得、削除を許可する基本スキーマを設定できます。

1. にサインイン AWS マネジメントコンソール し、[AppSync コンソール](https://console.aws.amazon.com/appsync/)を開きます。

   1. **API ダッシュボード**で、先ほど作成した API を選択します。

1. **サイドバー**で **[スキーマ]** を選択します。

   1. **[スキーマ]** ペインで、内容を次のコードに置き換えます。

     ```
     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!
     }
     ```

1. **[保存]** を選択します。

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

## DynamoDB テーブル用のデータソースの設定
<a name="configuring-the-data-source-for-the-ddb-tables"></a>

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

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

1. にサインイン AWS マネジメントコンソール し、[AppSync コンソール](https://console.aws.amazon.com/appsync/)を開きます。

   1. **API ダッシュボード**で、GraphQL API を選択します。

   1. **サイドバー** で **[データソース]** を選択します。

1. **[データソースを作成]** を選択します。

   1. **データソース名**には、`PostDynamoDBTable` を入力します。

   1. **[データソースタイプ]** として **[Amazon DynamoDB テーブル]** を選択します。

   1. **[リージョン]** として **[US-WEST-2]** を選択します。

   1. **[テーブル名]** には、**[AppSyncTutorial-Post]** DynamoDB テーブルを選択します。

   1. 次に、新しい IAM ロールを作成するか (推奨)、`lambda:invokeFunction` への IAM アクセス許可を持つ既存のロールを選択します。[データソースのアタッチ](attaching-a-data-source.md)セクションで説明しているように、既存のロールには信頼ポリシーが必要です。

      次に、リソースで操作を実行するために必要なアクセス許可を持つ IAM ポリシーの例を示します。

------
#### [ JSON ]

****  

      ```
      { 
           "Version":"2012-10-17",		 	 	  
           "Statement": [ 
               { 
                   "Effect": "Allow", 
                   "Action": [ "lambda:invokeFunction" ], 
                   "Resource": [ 
                       "arn:aws:lambda:us-east-1:111122223333:function:myFunction", 
                       "arn:aws:lambda:us-east-1:111122223333:function:myFunction:*" 
                   ] 
               } 
           ] 
       }
      ```

------

1. **[作成]** を選択します。

## addPost リゾルバー (DynamoDB PutItem) のセットアップ
<a name="setting-up-the-addpost-resolver-dynamodb-putitem"></a>

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

リゾルバーには以下のコンポーネントがあります。
+ リゾルバーをアタッチする、GraphQL スキーマ内の場所。この例では、`addPost` 型の `Mutation` フィールドにリゾルバーをセットアップしています。このリゾルバーは、呼び出し元が `mutation { addPost(...){...} }` を呼び出したときに呼び出されます。
+ このリゾルバーで使用するデータソース。この例では、`PostDynamoDBTable` DynamoDB テーブルにエントリを追加できるように、前に定義した `AppSyncTutorial-Post` データソースを使用します。
+ リクエストマッピングテンプレート。リクエストマッピングテンプレートの目的は、発信者からの受信リクエストを受け取り、それを AWS AppSync が DynamoDB に対して実行する手順に変換することです。
+ レスポンスマッピングテンプレート。レスポンスマッピングテンプレートの目的は、DynamoDB からのレスポンスを取り込み、それを GraphQL で想定されているものに変換し直すことです。これは、DynamoDB でのデータのシェイプが GraphQL での `Post` 型と異なる場合に便利です。ただし、この例では、両方のシェイプが同じであるため、データをそのまま渡します。

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

1. にサインイン AWS マネジメントコンソール し、[AppSync コンソール](https://console.aws.amazon.com/appsync/)を開きます。

   1. **API ダッシュボード**で、GraphQL API を選択します。

   1. **サイドバー** で **[データソース]** を選択します。

1. **[データソースを作成]** を選択します。

   1. **データソース名**には、`PostDynamoDBTable` を入力します。

   1. **[データソースタイプ]** として **[Amazon DynamoDB テーブル]** を選択します。

   1. **[リージョン]** として **[US-WEST-2]** を選択します。

   1. **[テーブル名]** には、**[AppSyncTutorial-Post]** DynamoDB テーブルを選択します。

   1. 次に、新しい IAM ロールを作成するか (推奨)、`lambda:invokeFunction` への IAM アクセス許可を持つ既存のロールを選択します。[データソースのアタッチ](attaching-a-data-source.md)セクションで説明しているように、既存のロールには信頼ポリシーが必要です。

      次に、リソースで操作を実行するために必要なアクセス許可を持つ IAM ポリシーの例を示します。

------
#### [ JSON ]

****  

      ```
      { 
           "Version":"2012-10-17",		 	 	  
           "Statement": [ 
               { 
                   "Effect": "Allow", 
                   "Action": [ "lambda:invokeFunction" ], 
                   "Resource": [ 
                       "arn:aws:lambda:us-west-2:123456789012:function:myFunction", 
                       "arn:aws:lambda:us-west-2:123456789012:function:myFunction:*" 
                   ] 
               } 
           ] 
       }
      ```

------

1. **[作成]** を選択します。

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

1. 右側の**データ型**ペインで、**ミューテーション**型の**addPost**フィールドを見つけて、**アタッチ**を選択します。

1. **[アクション]** メニューで **[ランタイムの更新]** を選択し、**[ユニットリゾルバー (VTL のみ)]** を選択します。

1. **[データソース名]** で、**[PostDynamoDBTable]** を選択します。

1. 以下のコードを **[リクエストマッピングテンプレートの設定]** に貼り付けます。

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

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

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

   マッピングテンプレートの詳細については、「[リゾルバーのマッピングテンプレートの概要](resolver-mapping-template-reference-overview.md#aws-appsync-resolver-mapping-template-reference-overview)」リファレンスドキュメントを参照してください。GetItem リクエストマッピングの詳細については、「[GetItem](aws-appsync-resolver-mapping-template-reference-dynamodb-getitem.md)」リファレンスドキュメントを参照してください。型の詳細については、「[型システム (リクエストマッピング)](aws-appsync-resolver-mapping-template-reference-dynamodb-typed-values-request.md)」リファレンスドキュメントを参照してください。

1. 以下のコードを **[レスポンスマッピングテンプレートの設定]** に貼り付けます。

   ```
   $utils.toJson($context.result)
   ```

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

1. **[保存]** を選択します。

### ポストを追加する API の呼び出し
<a name="call-the-api-to-add-a-post"></a>

これでリゾルバーがセットアップされたので、 AWS AppSync は着信`addPost`ミューテーションを DynamoDB PutItem オペレーションに変換できます。ユーザーはミューテーションを実行してテーブルに何かを入れることができるようになりました。
+ **[クエリ]** タブを選択します。
+ 以下のミューテーションを **[クエリ]** ペインに貼り付けます。

  ```
  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) のセットアップ
<a name="setting-up-the-getpost-resolver-ddb-getitem"></a>

これで、`AppSyncTutorial-Post` DynamoDB テーブルにデータを追加できるようになりました。次は、`AppSyncTutorial-Post` テーブルからデータを取得できるように、`getPost` クエリを設定する必要があります。そのためには、別のリゾルバーを設定します。
+ [**Schema (スキーマ)**] タブを選択します。
+ 右側の**データ型**ペインで、**Query**型の**getPost**フィールドを見つけて、[**アタッチ**] を選択します。
+ **[アクション]** メニューで **[ランタイムの更新]** を選択し、**[ユニットリゾルバー (VTL のみ)]** を選択します。
+ **[データソース名]** で、**[PostDynamoDBTable]** を選択します。
+ 以下のコードを **[リクエストマッピングテンプレートの設定]** に貼り付けます。

  ```
  {
      "version" : "2017-02-28",
      "operation" : "GetItem",
      "key" : {
          "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
      }
  }
  ```
+ 以下のコードを **[レスポンスマッピングテンプレートの設定]** に貼り付けます。

  ```
  $utils.toJson($context.result)
  ```
+ **[保存]** を選択します。

### ポストを取得する API の呼び出し
<a name="call-the-api-to-get-a-post"></a>

これでリゾルバーが設定され、 AWS AppSync は受信`getPost`クエリを DynamoDB`GetItem` オペレーションに変換する方法を知っています。次は、先ほど作成したポストを取得するクエリを実行します。
+ **[クエリ]** タブを選択します。
+ [**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
  }
  ```
+ そのデータは、レスポンスマッピングドキュメントを介して変更されずに渡されました。
+ 取得したオブジェクトがレスポンスで返されました。

別の方法として、次の例を指定します。

```
query getPost {
  getPost(id:123) {
    id
    author
    title
  }
}
```

`getPost` クエリに、`id`、`author` および `title` のみが必要な場合は、DynamoDB から AWS AppSyncへの不要なデータ転送を避けるため、投影式を使用して DynamoDB テーブルから必要な属性のみを指定するようにリクエストマッピングテンプレートを変更できます。例えば、リクエストマッピングテンプレートは以下のスニペットのようになります。

```
{
    "version" : "2017-02-28",
    "operation" : "GetItem",
    "key" : {
        "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
    },
    "projection" : {
     "expression" : "#author, id, title",
     "expressionNames" : { "#author" : "author"}
    }
}
```

## updatePost ミューテーション (DynamoDB UpdateItem) の作成
<a name="create-an-updatepost-mutation-ddb-updateitem"></a>

これで、DynamoDB 内の `Post` オブジェクトを作成および取得できるようになりました。次は、オブジェクトを更新できるように、新しいミューテーションを設定します。そのためには、DynamoDB の UpdateItem オペレーションを使用します。
+ [**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!
  }
  ```
+ **[保存]** を選択します。
+ 右側の**データ型**ペインで、**updatePost**フィールドに新しく作成された**ミューテーション**型を見つけて、**アタッチ**を選択します。
+ **[アクション]** メニューで **[ランタイムの更新]** を選択し、**[ユニットリゾルバー (VTL のみ)]** を選択します。
+ **[データソース名]** で、**[PostDynamoDBTable]** を選択します。
+ 以下のコードを **[リクエストマッピングテンプレートの設定]** に貼り付けます。

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

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

  `UpdateItem` リクエストマッピングの詳細については、「[UpdateItem](aws-appsync-resolver-mapping-template-reference-dynamodb-updateitem.md)」リファレンスドキュメントを参照してください。更新式の記述方法の詳細については、「[DynamoDB UpdateExpressions](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)」のドキュメントを参照してください。
+ 以下のコードを **[レスポンスマッピングテンプレートの設定]** に貼り付けます。

  ```
  $utils.toJson($context.result)
  ```

### ポストを更新する API の呼び出し
<a name="call-the-api-to-update-a-post"></a>

これでリゾルバーが設定され、 AWS AppSync は着信`update`ミューテーションを DynamoDB`Update` オペレーションに変換する方法を知っています。ユーザーは、前のステップで書き込んだ項目を更新するミューテーションを実行できるようになりました。
+ **[クエリ]** タブを選択します。
+ 次のミューテーションを [**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
      }
    }
  }
  ```

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

## updatePost リゾルバー (DynamoDB UpdateItem) の変更
<a name="modifying-the-updatepost-resolver-dynamodb-updateitem"></a>

`updatePost` ミューテーションの手始めとしてはこれで十分ですが、次の 2 つの主な問題があります。
+ 1 つのフィールドだけを更新する場合でも、すべてのフィールドを更新する必要があります。
+ 2 人のユーザーがこのオブジェクトを変更すると、情報が失われる可能性があります。

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

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

1. **[スキーマ]** ペインで `Mutation` 型の `updatePost` フィールドを変更して、`author`、`title`、`content`、`url` の各引数から感嘆符を削除し、`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!
   }
   ```

1. **[保存]** を選択します。

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

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

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

   ```
   {
       "version" : "2017-02-28",
       "operation" : "UpdateItem",
       "key" : {
           "id" : $util.dynamodb.toDynamoDBJson($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" : $util.dynamodb.toDynamoDBJson($context.arguments.expectedVersion)
           }
       }
   }
   ```

1. **[保存]** を選択します。

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

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

条件式の詳細については、「[条件式](aws-appsync-resolver-mapping-template-reference-dynamodb-condition-expressions.md)」リファレンスドキュメントを参照してください。

### ポストを更新する API の呼び出し
<a name="id1"></a>

新しいリゾルバーで `Post` オブジェクトを更新してみましょう。
+ **[クエリ]** タブを選択します。
+ 以下のミューテーションを **[クエリ]** ペインに貼り付けます。また、`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
      }
    }
  }
  ```

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

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

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

条件チェックの失敗の処理の詳細については、マッピングテンプレートのリファレンスドキュメントの「[条件式](aws-appsync-resolver-mapping-template-reference-dynamodb-condition-expressions.md)」を参照してください。

## upvotePost と downvotePost ミューテーション (DynamoDB UpdateItem) の作成
<a name="create-upvotepost-and-downvotepost-mutations-ddb-updateitem"></a>

`Post` 型には、賛成票と反対票を記録できる `ups` フィールドと `downs` フィールドがありますが、この API ではそのフィールドに対して何も行えません。ポストに賛成および反対するための、いくつかのミューテーションを追加してみましょう。
+ [**Schema (スキーマ)**] タブを選択します。
+ **[スキーマ]** ペインの `Mutation` タイプを変更して、新しい `upvotePost` と `downvotePost` ミューテーションを次のように追加します。

  ```
  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!
  }
  ```
+ **[保存]** を選択します。
+ 右側の**データ型**ペインで、**ミューテーション**型の新しく作成された **upvotePost** フィールドを見つけて、[**アタッチ**] を選択します。
+ **[アクション]** メニューで **[ランタイムの更新]** を選択し、**[ユニットリゾルバー (VTL のみ)]** を選択します。
+ **[データソース名]** で、**[PostDynamoDBTable]** を選択します。
+ 以下のコードを **[リクエストマッピングテンプレートの設定]** に貼り付けます。

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

  ```
  $utils.toJson($context.result)
  ```
+ **[保存]** を選択します。
+ 右側の**データ型**ペインで、**ミューテーション**型の新しく作成された `downvotePost` フィールドを見つけて、[**アタッチ**] を選択します。
+ **[データソース名]** で、**[PostDynamoDBTable]** を選択します。
+ 以下のコードを **[リクエストマッピングテンプレートの設定]** に貼り付けます。

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

  ```
  $utils.toJson($context.result)
  ```
+ **[保存]** を選択します。

### ポストに賛成および反対するための API の呼び出し
<a name="call-the-api-to-upvote-and-downvote-a-post"></a>

これで新しいリゾルバーが設定され、 AWS AppSync は受信`upvotePost`または`downvote`ミューテーションを DynamoDB UpdateItem オペレーションに変換する方法を知っています。これで、先ほど作成したポストに賛成または反対するミューテーションを実行できるようになりました。
+ **[クエリ]** タブを選択します。
+ 以下のミューテーションを **[クエリ]** ペインに貼り付けます。また、`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) のセットアップ
<a name="setting-up-the-deletepost-resolver-ddb-deletepost"></a>

次は、ポストを削除するミューテーションを設定します。そのためには、DynamoDB の `DeleteItem` オペレーションを使用します。
+ [**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` フィールドを省略可能にしています。これについては、リクエストマッピングテンプレートを追加するときに説明します。
+ **[保存]** を選択します。
+ 右側の**データ型**ペインで、**ミューテーション**型の新しく作成された **delete** フィールドを見つけて、[**アタッチ**] を選択します。
+ **[アクション]** メニューで **[ランタイムの更新]** を選択し、**[ユニットリゾルバー (VTL のみ)]** を選択します。
+ **[データソース名]** で、**[PostDynamoDBTable]** を選択します。
+ 以下のコードを **[リクエストマッピングテンプレートの設定]** に貼り付けます。

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

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

  ```
  $utils.toJson($context.result)
  ```

   **注意:** 項目を削除する場合でも、その項目がまだ削除されていなければ、削除された項目を返すことができます。
+ **[保存]** を選択します。

`DeleteItem` リクエストマッピングの詳細については、「[DeleteItem](aws-appsync-resolver-mapping-template-reference-dynamodb-deleteitem.md)」リファレンスドキュメントを参照してください。

### ポストを削除する API の呼び出し
<a name="call-the-api-to-delete-a-post"></a>

これでリゾルバーが設定され、 AWS AppSync は着信`delete`ミューテーションを DynamoDB`DeleteItem` オペレーションに変換する方法を知っています。ユーザーはミューテーションを実行してテーブル内の何かを削除できるようになりました。
+ **[クエリ]** タブを選択します。
+ 以下のミューテーションを **[クエリ]** ペインに貼り付けます。また、`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 から項目が削除された場合にのみ返されます。
+ **[クエリを実行]** を再度選択します。
+ この呼び出しは成功しますが、値は返されません。

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

次は、`expectedValue` を指定して、ポストを削除してみましょう。ただし、これまで使用してきたポストは削除したため、まず新しいポストを作成する必要があります。
+ 以下のミューテーションを **[クエリ]** ペインに貼り付けます。

  ```
  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` に間違った値を入れて、ポストを削除してみましょう。
+ 以下のミューテーションを **[クエリ]** ペインに貼り付けます。また、`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
      }
    }
  }
  ```
+ **[クエリを実行]** を再度選択します。
+ この呼び出しは成功しますが、そのポストが DynamoDB で既に削除されているため、今回は値が返されません。

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

## allPost リゾルバー (DynamoDB Scan) のセットアップ
<a name="setting-up-the-allpost-resolver-dynamodb-scan"></a>

これまでのところ、APIは、見たい各投稿の `id` がわかっている場合にのみ便利です。テーブル内のすべてのポストを返す新しいリゾルバーを追加してみましょう。
+ [**Schema (スキーマ)**] タブを選択します。
+ **[スキーマ]** ペインの `Query` タイプを次のように変更して、新しい `allPost` クエリを追加します。

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

  ```
  type PaginatedPosts {
      posts: [Post!]!
      nextToken: String
  }
  ```
+ **[保存]** を選択します。
+ 右側の**データ型**ペインで、**ミューテーション**型の新しく作成された **allPost** フィールドを見つけて、[**アタッチ**] を選択します。
+ **[アクション]** メニューで **[ランタイムの更新]** を選択し、**[ユニットリゾルバー (VTL のみ)]** を選択します。
+ **[データソース名]** で、**[PostDynamoDBTable]** を選択します。
+ 以下のコードを **[リクエストマッピングテンプレートの設定]** に貼り付けます。

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

  このリゾルバーには、省略可能な 2 つの引数があります。`count` では、1 回の呼び出しで返される項目の最大数を指定し、`nextToken` は、次の結果セットを取得するために使用できます (`nextToken` の値がどのように生成されるかについては、後で説明します)。
+ 以下のコードを **[レスポンスマッピングテンプレートの設定]** に貼り付けます。

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

   **注 :** このレスポンスマッピングテンプレートは、これまで他の例で使用したものとは異なります。`allPost` クエリの結果は `PaginatedPosts` であり、ポストのリストとページ分割トークンが含まれています。このオブジェクトの形状は、 AWS AppSync DynamoDB Resolver から返されるものと異なります。投稿のリストは AWS AppSync DynamoDB Resolver の結果`items`で呼び出されますが、 `posts`では呼び出されます`PaginatedPosts`。
+ **[保存]** を選択します。

`Scan` リクエストマッピングの詳細については、「[Scan](aws-appsync-resolver-mapping-template-reference-dynamodb-scan.md)」リファレンスドキュメントを参照してください。

### すべてのポストをスキャンする API の呼び出し
<a name="call-the-api-to-scan-all-posts"></a>

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

ただし、これまで使用してきたデータはすべて削除したため、これを試す前にテーブルにデータを入力しておく必要があります。
+ **[クエリ]** タブを選択します。
+ 以下のミューテーションを **[クエリ]** ペインに貼り付けます。

  ```
  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 個の結果を返しましょう。
+ 以下のクエリを **[クエリ]** ペインに貼り付けます。

  ```
  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 クエリ) のセットアップ
<a name="setting-up-the-allpostsbyauthor-resolver-ddb-query"></a>

DynamoDB ですべてのポストをスキャンだけでなく、特定の作成者が作成したポストを取得するクエリを DynamoDB に対して実行することもできます。前の手順で作成した DynamoDB テーブルには、既に `author-index` という `GlobalSecondaryIndex` があるため、DynamoDB の `Query` オペレーションでそれを使用して、特定の作成者が作成したすべてのポストを取得できます
+ [**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` 型を使用しています。
+ **[保存]** を選択します。
+ 右側の**データ型**ペインで、**ミューテーション**型の新しく作成された **allPostsByAuthor** フィールドを見つけて、[**アタッチ**] を選択します。
+ **[アクション]** メニューで **[ランタイムの更新]** を選択し、**[ユニットリゾルバー (VTL のみ)]** を選択します。
+ **[データソース名]** で、**[PostDynamoDBTable]** を選択します。
+ 以下のコードを **[リクエストマッピングテンプレートの設定]** に貼り付けます。

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

  `allPost` リゾルバーと同様に、このリゾルバーには、省略可能な 2 つの引数があります。`count` では、1 回の呼び出しで返される項目の最大数を指定し、`nextToken` は、次の結果セットを取得するために使用できます (`nextToken` の値は前回の呼び出しから取得できます)。
+ 以下のコードを **[レスポンスマッピングテンプレートの設定]** に貼り付けます。

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

   **注 :** これは、`allPost` リゾルバーで使用したのと同じレスポンスマッピングテンプレートです。
+ **[保存]** を選択します。

`Query` リクエストマッピングの詳細については、「[クエリ](aws-appsync-resolver-mapping-template-reference-dynamodb-query.md)」リファレンスドキュメントを参照してください。

### 特定の作成者によるすべてのポストをクエリする API の呼び出し
<a name="call-the-api-to-query-all-posts-by-an-author"></a>

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

ただし、これまで使用していたポストはすべて同じ作成者だったため、それを行う前に、テーブルにポストを追加しておきましょう。
+ **[クエリ]** タブを選択します。
+ 以下のミューテーションを **[クエリ]** ペインに貼り付けます。

  ```
  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` が作成したすべてのポストを返すクエリを実行しましょう。
+ 以下のクエリを **[クエリ]** ペインに貼り付けます。

  ```
  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 個ずつ取得します。
+ 以下のクエリを **[クエリ]** ペインに貼り付けます。

  ```
  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
      }
    }
  }
  ```

## セット型の使用
<a name="using-sets"></a>

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

`Post` 型を更新して、タグを含めましょう。1 つのポストには、DynamoDB に文字列として保存されているタグを 0 個以上付けることができます。タグを追加および削除するミューテーションと、特定のタグが付いているポストをスキャンする新しいクエリもセットアップします。
+ [**Schema (スキーマ)**] タブを選択します。
+ **[スキーマ]** ペインの `Post` タイプを次のように変更して、新しい `tags` フィールドを追加します。

  ```
  type Post {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int!
    downs: Int!
    version: Int!
    tags: [String!]
  }
  ```
+ **[スキーマ]** ペインの `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
  }
  ```
+ **[スキーマ]** ペインの `Mutation` タイプを変更して、新しい `addTag` と `removeTag` ミューテーションを次のように追加します。

  ```
  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!
  }
  ```
+ **[保存]** を選択します。
+ 右側の**データ型**ペインで、**ミューテーション**型の新しく作成された **allPostsByTag** フィールドを見つけて、[**アタッチ**] を選択します。
+ **[データソース名]** で、**[PostDynamoDBTable]** を選択します。
+ 以下のコードを **[リクエストマッピングテンプレートの設定]** に貼り付けます。

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

  ```
  {
      "posts": $utils.toJson($context.result.items)
      #if( ${context.result.nextToken} )
          ,"nextToken": $util.toJson($context.result.nextToken)
      #end
  }
  ```
+ **[保存]** を選択します。
+ 右側の**データ型**ペインで、**ミューテーション**型の新しく作成された **addTag** フィールドを見つけて、[**アタッチ**] を選択します。
+ **[データソース名]** で、**[PostDynamoDBTable]** を選択します。
+ 以下のコードを **[リクエストマッピングテンプレートの設定]** に貼り付けます。

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

  ```
  $utils.toJson($context.result)
  ```
+ **[保存]** を選択します。
+ 右側の**データ型**ペインで、**ミューテーション**型の新しく作成された **removeTag** フィールドを見つけて、[**アタッチ**] を選択します。
+ **[データソース名]** で、**[PostDynamoDBTable]** を選択します。
+ 以下のコードを **[リクエストマッピングテンプレートの設定]** に貼り付けます。

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

  ```
  $utils.toJson($context.result)
  ```
+ **[保存]** を選択します。

### タグを操作する API の呼び出し
<a name="call-the-api-to-work-with-tags"></a>

リゾルバーをセットアップしたので、 AWS AppSync は受信 `addTag`、、`removeTag`および `allPostsByTag`リクエストを DynamoDB`UpdateItem` および `Scan`オペレーションに変換する方法を理解しています。

それを試すには、前のステップで作成したポストのいずれかを選択します。例えば、`Nadia` が作成したポストを使用しましょう。
+ **[クエリ]** タブを選択します。
+ 以下のクエリを **[クエリ]** ペインに貼り付けます。

  ```
  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` タグを追加してみましょう。
+ 以下のミューテーションを **[クエリ]** ペインに貼り付けます。また、`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"
        ]
      }
    }
  }
  ```

タグを削除することもできます。
+ 以下のミューテーションを **[クエリ]** ペインに貼り付けます。また、`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"
        ]
      }
    }
  }
  ```

タグが付いているすべてのポストを検索することもできます。
+ 以下のクエリを **[クエリ]** ペインに貼り付けます。

  ```
  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
      }
    }
  }
  ```

## リスト型とマップ型の使用
<a name="using-lists-and-maps"></a>

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

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

 **注 :** 実際のアプリケーションでは、独自のテーブル内のコメントをモデル化します。このチュートリアルでは、`Post` テーブルにコメントを追加しているだけです。
+ [**Schema (スキーマ)**] タブを選択します。
+ [**Schema (スキーマ)**] ペインで、次のように新しい `Comment` 型を追加します。

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

  ```
  type Post {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int!
    downs: Int!
    version: Int!
    tags: [String!]
    comments: [Comment!]
  }
  ```
+ **[スキーマ]** ペインの `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!
  }
  ```
+ **[保存]** を選択します。
+ 右側の**データ型**ペインで、**ミューテーション**型の新しく作成された **addComment** フィールドを見つけて、[**アタッチ**] を選択します。
+ **[データソース名]** で、**[PostDynamoDBTable]** を選択します。
+ 以下のコードを **[リクエストマッピングテンプレートの設定]** に貼り付けます。

  ```
  {
    "version" : "2017-02-28",
    "operation" : "UpdateItem",
    "key" : {
      "id" : $util.dynamodb.toDynamoDBJson($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": $util.dynamodb.toDynamoDBJson($context.arguments.author),
            "comment": $util.dynamodb.toDynamoDBJson($context.arguments.comment)
            }
          }
        ] },
        ":plusOne" : $util.dynamodb.toDynamoDBJson(1)
      }
    }
  }
  ```

  この更新式によって、新しいコメントが含まれているリストが、既存の `comments` リストに追加されます。リストがまだ存在しない場合は作成されます。
+ 以下のコードを **[レスポンスマッピングテンプレートの設定]** に貼り付けます。

  ```
  $utils.toJson($context.result)
  ```
+ **[保存]** を選択します。

### コメントを追加する API の呼び出し
<a name="call-the-api-to-add-a-comment"></a>

リゾルバーをセットアップしたので、 AWS AppSync は受信`addComment`リクエストを DynamoDB`UpdateItem` オペレーションに変換する方法を理解しています。

タグを追加したのと同じポストにコメントを追加して、試してみましょう。
+ **[クエリ]** タブを選択します。
+ 以下のクエリを **[クエリ]** ペインに貼り付けます。

  ```
  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."
          }
        ]
      }
    }
  }
  ```

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

## 結論
<a name="conclusion"></a>

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

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

このチュートリアル用に作成した DynamoDB テーブルと IAM ロールを削除するには、以下を実行して`AWSAppSyncTutorialForAmazonDynamoDB`スタックを削除するか、 CloudFormation コンソールにアクセスしてスタックを削除します。

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

# での AWS Lambda リゾルバーの使用 AWS AppSync
<a name="tutorial-lambda-resolvers"></a>

**注記**  
現在、主に APPSYNC\$1JS ランタイムとそのドキュメントをサポートしています。[こちら](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html) で APPSYNC\$1JS ランタイムとそのガイドの使用をご検討ください。

 AWS Lambda with AWS AppSync を使用して、GraphQL フィールドを解決できます。例えば、GraphQL クエリが Amazon Relational Database Service (Amazon RDS) インスタンスを呼び出し、GraphQL のミューテーションを Amazon Kinesis ストリームに書き込むことができます。このセクションでは、GraphQL フィールド処理の呼び出しに応じてビジネスロジックを実行する Lambda 関数を記述する方法について説明します。

## Lambda 関数を作成する
<a name="create-a-lam-function"></a>

以下の例は、`Node.js` に記述された、ブログ投稿アプリケーションの一部としてブログ投稿に関するさまざまなオペレーションを実行する Lambda 関数を示しています。

```
exports.handler = (event, context, callback) => {
    console.log("Received event {}", JSON.stringify(event, 3));
    var posts = {
         "1": {"id": "1", "title": "First book", "author": "Author1", "url": "https://amazon.com/", "content": "SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1", "ups": "100", "downs": "10"},
         "2": {"id": "2", "title": "Second book", "author": "Author2", "url": "https://amazon.com", "content": "SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT", "ups": "100", "downs": "10"},
         "3": {"id": "3", "title": "Third book", "author": "Author3", "url": null, "content": null, "ups": null, "downs": null },
         "4": {"id": "4", "title": "Fourth book", "author": "Author4", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4", "ups": "1000", "downs": "0"},
         "5": {"id": "5", "title": "Fifth book", "author": "Author5", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT", "ups": "50", "downs": "0"} };

    var relatedPosts = {
        "1": [posts['4']],
        "2": [posts['3'], posts['5']],
        "3": [posts['2'], posts['1']],
        "4": [posts['2'], posts['1']],
        "5": []
    };

    console.log("Got an Invoke Request.");
    switch(event.field) {
        case "getPost":
            var id = event.arguments.id;
            callback(null, posts[id]);
            break;
        case "allPosts":
            var values = [];
            for(var d in posts){
                values.push(posts[d]);
            }
            callback(null, values);
            break;
        case "addPost":
            // return the arguments back
            callback(null, event.arguments);
            break;
        case "addPostErrorWithData":
            var id = event.arguments.id;
            var result = posts[id];
            // attached additional error information to the post
            result.errorMessage = 'Error with the mutation, data has changed';
            result.errorType = 'MUTATION_ERROR';
            callback(null, result);
            break;
        case "relatedPosts":
            var id = event.source.id;
            callback(null, relatedPosts[id]);
            break;
        default:
            callback("Unknown field, unable to resolve" + event.field, null);
            break;
    }
};
```

この Lambda 関数は、ID による投稿の取得、投稿の追加、投稿のリストの取得、および指定した投稿に関連する投稿の取得を行います。

 **注意 :** `event.field` の `switch` ステートメントにより、Lambda 関数は現在解決しているフィールドを確認することができます。

 AWS マネジメントコンソールまたは AWS CloudFormation スタックを使用して、この Lambda 関数を作成します。CloudFormation スタックから関数を作成するには、次の AWS Command Line Interface (AWS CLI) コマンドを使用できます。

```
aws cloudformation create-stack --stack-name AppSyncLambdaExample \
--template-url https://s3.us-west-2.amazonaws.com/awsappsync/resources/lambda/LambdaCFTemplate.yaml \
--capabilities CAPABILITY_NAMED_IAM
```

ここから AWS 、アカウントの米国西部 (オレゴン) AWS リージョンで CloudFormation スタックを起動することもできます。

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/lambda/LambdaCFTemplate.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/lambda/LambdaCFTemplate.yaml)

## Lambda のデータソースを設定する
<a name="configure-data-source-for-lamlong"></a>

Lambda 関数を作成した後、 AWS AppSync コンソールで GraphQL API に移動し、**[データソース]** タブを選択します。

**[データソースの作成]** を選択し、**[データソース名]**としてわかりやすい名前 (**Lambda** など) を入力してから、**[データソースタイプ]** に対して **[AWS Lambda 関数]**を選択します。**[リージョン]** で、関数と同じリージョンを選択します。(提供されている CloudFormation スタックから関数を作成した場合、その関数はおそらく **US-WEST-2** にあります)。**[関数 ARN]** で、Lambda 関数の Amazon リソースネーム (ARN)を選択します。

Lambda 関数を選択したら、新しい AWS Identity and Access Management (IAM) ロール ( AWS AppSync が適切なアクセス許可を割り当てるロール) を作成するか、次のインラインポリシーを持つ既存のロールを選択できます。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction"
            ],
            "Resource": "arn:aws:lambda:us-east-1:111122223333:function:LAMBDA_FUNCTION"
        }
    ]
}
```

------

また、次のように IAM ロールの AWS AppSync との信頼関係を設定する必要があります。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "appsync.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

------

## GraphQL スキーマを作成する
<a name="creating-a-graphql-schema"></a>

データソースが Lambda 関数に接続されたので、GraphQL スキーマを作成します。

 AWS AppSync コンソールのスキーマエディタから、スキーマが次のスキーマと一致していることを確認します。

```
schema {
    query: Query
    mutation: Mutation
}

type Query {
    getPost(id:ID!): Post
    allPosts: [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
    relatedPosts: [Post]
}
```

## リゾルバーを設定する
<a name="configuring-resolvers"></a>

Lambda のデータソースと有効な GraphQL スキーマが登録されたので、リゾルバーを使用して GraphQL フィールドを Lambda のデータソースに接続することができます。

リゾルバーを作成するには、マッピングテンプレートが必要です。マッピングテンプレートの詳細については、「[Resolver Mapping Template Overview](resolver-mapping-template-reference-overview.md#aws-appsync-resolver-mapping-template-reference-overview)」を参照してください。

Lambda マッピングテンプレートの詳細については、「[Resolver mapping template reference for Lambda](resolver-mapping-template-reference-lambda.md#aws-appsync-resolver-mapping-template-reference-lambda)」を参照してください。

このステップでは、`getPost(id:ID!): Post`、`allPosts: [Post]`、`addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!`、および `Post.relatedPosts: [Post]` の各フィールドで Lambda 関数にリゾルバーをアタッチします。

 AWS AppSync コンソールのスキーマエディタから、右側の「 **のリゾルバーをア**タッチする」を選択します`getPost(id:ID!): Post`。

次に、**[アクション]** メニューで **[ランタイムの更新]** を選択し、**[ユニットリゾルバー (VTL のみ)]** を選択します。

その後、Lambda データソースを選択します。[**request mapping template (リクエストマッピングテンプレート)**] セクションで、[**Invoke And Forward Arguments (呼び出しと引数の転送)**] を選択します。

`payload` オブジェクトを変更し、フィールド名を追加します。テンプレートは次のようになります。

```
{
    "version": "2017-02-28",
    "operation": "Invoke",
    "payload": {
        "field": "getPost",
        "arguments":  $utils.toJson($context.arguments)
    }
}
```

[**response mapping template (レスポンスマッピングテンプレート)**] セクションで、[**Return Lambda Result (Lambda 関数の結果を返す)**] を選択します。

この場合、ベーステンプレートをそのまま使用します。次のようになります。

```
$utils.toJson($context.result)
```

**[保存]** を選択します。最初のリゾルバーが正常に追加されました。以下のように、残りのフィールドについてこの操作を繰り返します。

`addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!` リクエストマッピングテンプレートについて

```
{
    "version": "2017-02-28",
    "operation": "Invoke",
    "payload": {
        "field": "addPost",
        "arguments":  $utils.toJson($context.arguments)
    }
}
```

`addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!` レスポンスマッピングテンプレートについて

```
$utils.toJson($context.result)
```

`allPosts: [Post]` リクエストマッピングテンプレートについて

```
{
    "version": "2017-02-28",
    "operation": "Invoke",
    "payload": {
        "field": "allPosts"
    }
}
```

`allPosts: [Post]` レスポンスマッピングテンプレートについて

```
$utils.toJson($context.result)
```

`Post.relatedPosts: [Post]` リクエストマッピングテンプレートについて

```
{
    "version": "2017-02-28",
    "operation": "Invoke",
    "payload": {
        "field": "relatedPosts",
        "source":  $utils.toJson($context.source)
    }
}
```

`Post.relatedPosts: [Post]` レスポンスマッピングテンプレートについて

```
$utils.toJson($context.result)
```

## GraphQL API をテストする
<a name="testing-your-graphql-api"></a>

これで Lambda 関数が GraphQL リゾルバーに接続されたので、コンソールまたはクライアントアプリケーションを使用してミューテーションまたはクエリが実行できます。

 AWS AppSync コンソールの左側でクエリを選択し****、次のコードを貼り付けます。

### addPost ミューテーション
<a name="addpost-mutation"></a>

```
mutation addPost {
    addPost(
        id: 6
        author: "Author6"
        title: "Sixth book"
        url: "https://www.amazon.com/"
        content: "This is the book is a tutorial for using GraphQL with AWS AppSync."
    ) {
        id
        author
        title
        content
        url
        ups
        downs
    }
}
```

### getPost クエリ
<a name="getpost-query"></a>

```
query getPost {
    getPost(id: "2") {
        id
        author
        title
        content
        url
        ups
        downs
    }
}
```

### allPosts クエリ
<a name="allposts-query"></a>

```
query allPosts {
    allPosts {
        id
        author
        title
        content
        url
        ups
        downs
        relatedPosts {
            id
            title
        }
    }
}
```

## エラーを返す
<a name="returning-errors"></a>

指定したフィールドの解決でエラーが発生する場合があります。 AWS AppSync を使用すると、次のソースからエラーを生成できます。
+ リクエストまたはレスポンスのマッピングテンプレート
+ Lambda function

### マッピングテンプレートからの場合
<a name="from-the-mapping-template"></a>

Velocity Template Language (VTL) テンプレートから `$utils.error` ヘルパーメソッドを使用して故意にエラーを発生させることができます。これは引数として、`errorMessage`、`errorType`、および必要に応じて `data` の各値を含みます。`data` は、エラーの発生時にクライアントに追加のデータを返す場合に利用できます。`data` オブジェクトは GraphQL の最終レスポンスで `errors` に追加されます。

次の例では、`Post.relatedPosts: [Post]` レスポンスマッピングテンプレートでそれを使用する方法を示しています。

```
$utils.error("Failed to fetch relatedPosts", "LambdaFailure", $context.result)
```

これにより、以下のような GraphQL レスポンスが生成されます。

```
{
    "data": {
        "allPosts": [
            {
                "id": "2",
                "title": "Second book",
                "relatedPosts": null
            },
            ...
        ]
    },
    "errors": [
        {
            "path": [
                "allPosts",
                0,
                "relatedPosts"
            ],
            "errorType": "LambdaFailure",
            "locations": [
                {
                    "line": 5,
                    "column": 5
                }
            ],
            "message": "Failed to fetch relatedPosts",
            "data": [
                {
                  "id": "2",
                  "title": "Second book"
                },
                {
                  "id": "1",
                  "title": "First book"
                }
            ]
        }
    ]
}
```

この場合、エラーおよび `errorMessage`、`errorType`、`data` が `data.errors[0]` オブジェクトに存在するため、`allPosts[0].relatedPosts` は *null* になります。

### Lambda 関数から
<a name="from-the-lam-function"></a>

AWS AppSync は、Lambda 関数がスローするエラーも理解します。Lambda プログラミングモデルを使用し、*処理される*エラーを発生させることができます。Lambda 関数がエラーをスローした場合、 AWS AppSync は現在のフィールドの解決に失敗します。Lambda から返されたエラーメッセージのみがレスポンスに設定されます。また現在のところ、Lambda 関数からエラーを発生させて、クライアントにエラー関連以外のデータを渡すことはできません。

 **注**: Lambda 関数が*未処理*のエラーを発生させた場合、 AWS AppSync は Lambda が設定したエラーメッセージを使用します。

以下の Lambda 関数はエラーを発生させます。

```
exports.handler = (event, context, callback) => {
    console.log("Received event {}", JSON.stringify(event, 3));
    callback("I fail. Always.");
};
```

これにより、以下のような GraphQL レスポンスが返されます。

```
{
    "data": {
        "allPosts": [
            {
                "id": "2",
                "title": "Second book",
                "relatedPosts": null
            },
            ...
        ]
    },
    "errors": [
        {
            "path": [
                "allPosts",
                0,
                "relatedPosts"
            ],
            "errorType": "Lambda:Handled",
            "locations": [
                {
                    "line": 5,
                    "column": 5
                }
            ],
            "message": "I fail. Always."
        }
    ]
}
```

## 高度なユースケース: バッチ処理
<a name="advanced-use-case-batching"></a>

この例の Lambda 関数には、指定した投稿に関連する投稿のリストを返す `relatedPosts` フィールドが含まれています。この例のクエリでは、Lambda 関数からの `allPosts` フィールドの呼び出しにより 5 件の投稿が返されます。返された各投稿に対して `relatedPosts` の解決も指定しているので、`relatedPosts` フィールドの処理が続けて 5 回呼び出されます。

```
query allPosts {
    allPosts {   // 1 Lambda invocation - yields 5 Posts
        id
        author
        title
        content
        url
        ups
        downs
        relatedPosts {   // 5 Lambda invocations - each yields 5 posts
            id
            title
        }
    }
}
```

この例では大して意味がないように思われますが、こうした余分な取得処理が積み重なることで、アプリケーションに急速に害をなす可能性があります。

同じクエリで返された関連する `Posts` について、再度 `relatedPosts` を取得すると、呼び出しの数は大幅に増加します。

```
query allPosts {
    allPosts {   // 1 Lambda invocation - yields 5 Posts
        id
        author
        title
        content
        url
        ups
        downs
        relatedPosts {   // 5 Lambda invocations - each yield 5 posts = 5 x 5 Posts
            id
            title
            relatedPosts {  // 5 x 5 Lambda invocations - each yield 5 posts = 25 x 5 Posts
                id
                title
                author
            }
        }
    }
}
```

この比較的単純なクエリでは、 AWS AppSync は Lambda 関数を 1 \$1 5 \$1 25 = 31 回呼び出します。

これはよくある課題であり、N\$11 問題と呼ばれます (この例では、N = 5)。これによりアプリケーションのレイテンシーとコストが増大します。

この問題を解決する 1 つの方法は、同様なフィールドのリゾルバーリクエストを同時にバッチ処理することです。この例では、Lambda 関数は指定された 1 つの投稿に関連する投稿のリストを解決するのではなく、指定された一連の投稿に関連する投稿のリストを解決できます。

これを示すため、`Post.relatedPosts: [Post]` リゾルバーをバッチ処理が有効なリゾルバーに切り替えます。

 AWS AppSync コンソールの右側で、既存の`Post.relatedPosts: [Post]`リゾルバーを選択します。リクエストマッピングテンプレートを次のように変更します。

```
{
    "version": "2017-02-28",
    "operation": "BatchInvoke",
    "payload": {
        "field": "relatedPosts",
        "source":  $utils.toJson($context.source)
    }
}
```

`operation` フィールドのみを `Invoke` から `BatchInvoke` に変更します。ペイロードフィールドはテンプレートで指定した内容の配列になっています。この例では、Lambda 関数は、入力として以下を受け取ります。

```
[
    {
        "field": "relatedPosts",
        "source": {
            "id": 1
        }
    },
    {
        "field": "relatedPosts",
        "source": {
            "id": 2
        }
    },
    ...
]
```

リクエストマッピングテンプレートで `BatchInvoke` を指定した場合、Lambda 関数はリクエストのリストを受け取り、結果のリストを返します。

具体的には、 AWS AppSync がそれに応じて結果を一致できるように、結果のリストがリクエストペイロードエントリのサイズと順序と一致する必要があります。

このバッチ処理の例では、Lambda 関数は次のように一連の結果を返します。

```
[
    [{"id":"2","title":"Second book"}, {"id":"3","title":"Third book"}],   // relatedPosts for id=1
    [{"id":"3","title":"Third book"}]                                                             // relatedPosts for id=2
]
```

次の Node.js の Lambda 関数は、`Post.relatedPosts` フィールドに対してこのバッチ処理機能を次のように実行します。

```
exports.handler = (event, context, callback) => {
    console.log("Received event {}", JSON.stringify(event, 3));
    var posts = {
         "1": {"id": "1", "title": "First book", "author": "Author1", "url": "https://amazon.com/", "content": "SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1", "ups": "100", "downs": "10"},
         "2": {"id": "2", "title": "Second book", "author": "Author2", "url": "https://amazon.com", "content": "SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT", "ups": "100", "downs": "10"},
         "3": {"id": "3", "title": "Third book", "author": "Author3", "url": null, "content": null, "ups": null, "downs": null },
         "4": {"id": "4", "title": "Fourth book", "author": "Author4", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4", "ups": "1000", "downs": "0"},
         "5": {"id": "5", "title": "Fifth book", "author": "Author5", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT", "ups": "50", "downs": "0"} };

    var relatedPosts = {
        "1": [posts['4']],
        "2": [posts['3'], posts['5']],
        "3": [posts['2'], posts['1']],
        "4": [posts['2'], posts['1']],
        "5": []
    };

    console.log("Got a BatchInvoke Request. The payload has %d items to resolve.", event.length);
    // event is now an array
    var field = event[0].field;
    switch(field) {
        case "relatedPosts":
            var results = [];
            // the response MUST contain the same number
            // of entries as the payload array
            for (var i=0; i< event.length; i++) {
                console.log("post {}", JSON.stringify(event[i].source));
                results.push(relatedPosts[event[i].source.id]);
            }
            console.log("results {}", JSON.stringify(results));
            callback(null, results);
            break;
        default:
            callback("Unknown field, unable to resolve" + field, null);
            break;
    }
};
```

### 個々のエラーを返す
<a name="returning-individual-errors"></a>

Lambda 関数から単一のエラーを返せることや、マッピングテンプレートから 1 つのエラーを生成できることは以前の例で説明しました。バッチ処理を呼び出した場合、Lambda 関数からエラーが発生すると、バッチ処理全体が失敗としてフラグ付けされます。これは、データストアとの接続が切れた場合など、回復不可能なエラーが発生するシナリオでは問題ないかもしれません。ここでは、バッチ処理の一部が成功し、他が失敗した場合、エラーと有効なデータの両方を返すことができます。 AWS AppSync はバッチの元のサイズに一致する要素を一覧表示するためにバッチレスポンスを必要とするため、有効なデータをエラーと区別できるデータ構造を定義する必要があります。

例えば、関連する一連の投稿が返されることを Lambda 関数が期待している場合には、`Response` オブジェクトのリストを返すように選択できます。この場合、各オブジェクトに任意の *data*、*errorMessage*、および *errorType* フィールドが含まれます。*errorMessage* フィールドが存在する場合は、エラーが発生したことを意味します。

次のコードは Lambda 関数の更新方法を示しています。

```
exports.handler = (event, context, callback) => {
    console.log("Received event {}", JSON.stringify(event, 3));
    var posts = {
         "1": {"id": "1", "title": "First book", "author": "Author1", "url": "https://amazon.com/", "content": "SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1", "ups": "100", "downs": "10"},
         "2": {"id": "2", "title": "Second book", "author": "Author2", "url": "https://amazon.com", "content": "SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT", "ups": "100", "downs": "10"},
         "3": {"id": "3", "title": "Third book", "author": "Author3", "url": null, "content": null, "ups": null, "downs": null },
         "4": {"id": "4", "title": "Fourth book", "author": "Author4", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4", "ups": "1000", "downs": "0"},
         "5": {"id": "5", "title": "Fifth book", "author": "Author5", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT", "ups": "50", "downs": "0"} };

    var relatedPosts = {
        "1": [posts['4']],
        "2": [posts['3'], posts['5']],
        "3": [posts['2'], posts['1']],
        "4": [posts['2'], posts['1']],
        "5": []
    };

    console.log("Got a BatchInvoke Request. The payload has %d items to resolve.", event.length);
    // event is now an array
    var field = event[0].field;
    switch(field) {
        case "relatedPosts":
            var results = [];
            results.push({ 'data': relatedPosts['1'] });
            results.push({ 'data': relatedPosts['2'] });
            results.push({ 'data': null, 'errorMessage': 'Error Happened', 'errorType': 'ERROR' });
            results.push(null);
            results.push({ 'data': relatedPosts['3'], 'errorMessage': 'Error Happened with last result', 'errorType': 'ERROR' });
            callback(null, results);
            break;
        default:
            callback("Unknown field, unable to resolve" + field, null);
            break;
    }
};
```

この例では、次のレスポンスマッピングテンプレートが Lambda 関数の各項目を解析し、発生するエラーがあればそれを生成します。

```
#if( $context.result && $context.result.errorMessage )
    $utils.error($context.result.errorMessage, $context.result.errorType, $context.result.data)
#else
    $utils.toJson($context.result.data)
#end
```

この例では、以下のような GraphQL レスポンスが返されます。

```
{
  "data": {
    "allPosts": [
      {
        "id": "1",
        "relatedPostsPartialErrors": [
          {
            "id": "4",
            "title": "Fourth book"
          }
        ]
      },
      {
        "id": "2",
        "relatedPostsPartialErrors": [
          {
            "id": "3",
            "title": "Third book"
          },
          {
            "id": "5",
            "title": "Fifth book"
          }
        ]
      },
      {
        "id": "3",
        "relatedPostsPartialErrors": null
      },
      {
        "id": "4",
        "relatedPostsPartialErrors": null
      },
      {
        "id": "5",
        "relatedPostsPartialErrors": null
      }
    ]
  },
  "errors": [
    {
      "path": [
        "allPosts",
        2,
        "relatedPostsPartialErrors"
      ],
      "errorType": "ERROR",
      "locations": [
        {
          "line": 4,
          "column": 9
        }
      ],
      "message": "Error Happened"
    },
    {
      "path": [
        "allPosts",
        4,
        "relatedPostsPartialErrors"
      ],
      "data": [
        {
          "id": "2",
          "title": "Second book"
        },
        {
          "id": "1",
          "title": "First book"
        }
      ],
      "errorType": "ERROR",
      "locations": [
        {
          "line": 4,
          "column": 9
        }
      ],
      "message": "Error Happened with last result"
    }
  ]
}
```

### 最大バッチサイズの設定
<a name="configure-max-batch-size"></a>

デフォルトでは、 を使用する場合`BatchInvoke`、 AWS AppSync は最大 5 つの項目のバッチで Lambda 関数にリクエストを送信します。Lambda リゾルバーの最大バッチサイズを設定できます。

リゾルバーで最大バッチサイズを設定するには、 AWS Command Line Interface () で次のコマンドを使用しますAWS CLI。

```
$ aws appsync create-resolver --api-id <api-id> --type-name Query --field-name relatedPosts \
 --request-mapping-template "<template>" --response-mapping-template "<template>" --data-source-name "<lambda-datasource>" \ 
 --max-batch-size X
```

**注記**  
リクエストマッピングテンプレートを提供するときは、バッチ処理を使用する `BatchInvoke` オペレーションを使用する必要があります。

次のコマンドを使用して、ダイレクト Lambda リゾルバーのバッチ処理を有効にして設定することもできます。

```
$ aws appsync create-resolver --api-id <api-id> --type-name Query --field-name relatedPosts \
 --data-source-name "<lambda-datasource>" \ 
 --max-batch-size X
```

### VTL テンプレートによる最大バッチサイズ設定
<a name="configure-max-batch-size-vtl"></a>

VTL のリクエスト内テンプレートがある Lambda リゾルバーの場合、最大バッチサイズは VTL の `BatchInvoke` オペレーションとして直接指定しない限り、効果がありません。同様に、トップレベルのミューテーションを実行する場合、GraphQL 仕様では並列ミューテーションを順次実行する必要があるため、ミューテーションのバッチ処理は行われません。

例として、次のミューテーションをとりあげます。

```
type Mutation {
    putItem(input: Item): Item
    putItems(inputs: [Item]): [Item]
}
```

最初のミューテーションを使うと、以下のスニペットに示すように 10 個の `Items` を作成できます。

```
mutation MyMutation {
    v1: putItem($someItem1) {
        id,
        name
    }
    v2: putItem($someItem2) {
        id,
        name
    }
    v3: putItem($someItem3) {
        id,
        name
    } 
    v4: putItem($someItem4) {
        id,
        name
    }
    v5: putItem($someItem5) {
        id,
        name
    }
    v6: putItem($someItem6) {
        id,
        name
    } 
    v7: putItem($someItem7) {
        id,
        name
    }
    v8: putItem($someItem8) {
        id,
        name
    }
    v9: putItem($someItem9) {
        id,
        name
    }
    v10: putItem($someItem10) {
        id,
        name
    }
}
```

この例では、Lambda Resolver の最大バッチサイズが 10 に設定されていても、`Items` は 10 の のグループでバッチ処理されません。代わりに、GraphQL の仕様に従って順番に実行されます。

実際にバッチミューテーションを実行するには、以下の例のように 2 つ目のミューテーションを使うといいでしょう。

```
mutation MyMutation {
    putItems([$someItem1, $someItem2, $someItem3,$someItem4, $someItem5, $someItem6, 
    $someItem7, $someItem8, $someItem9, $someItem10]) {
    id,
    name
    }
}
```

ダイレクト Lambda リゾルバーでのバッチ処理の使用方法については、[ダイレクトLambda リゾルバー](resolver-mapping-template-reference-lambda.md#direct-lambda-resolvers) を参照してください。

# AWS AppSync での Amazon OpenSearch Service リゾルバーの使用
<a name="tutorial-elasticsearch-resolvers"></a>

**注記**  
現在、主に APPSYNC\$1JS ランタイムとそのドキュメントをサポートしています。[こちら](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html) で APPSYNC\$1JS ランタイムとそのガイドの使用をご検討ください。

AWS AppSync は、VPC 内に存在しない場合、独自の AWS アカウントでプロビジョニングしたドメインからの Amazon OpenSearch Service の使用をサポートします。ドメインをプロビジョニングした後、データソースを使用してそれに接続できます。この時点で、スキーマにリゾルバーを設定し、クエリ、ミューテーション、サブスクリプションなどの GraphQL 処理を実行することができます。このチュートリアルでは、いくつかの一般的な例を説明します。

詳細については、「[OpenSearch 用リゾルバーのマッピングテンプレートリファレンス](resolver-mapping-template-reference-elasticsearch.md#aws-appsync-resolver-mapping-template-reference-elasticsearch)」を参照してください。

## ワンクリックでのセットアップ
<a name="one-click-setup"></a>

Amazon OpenSearch Service を設定して AWS AppSync で GraphQL エンドポイントを自動的にセットアップするには、この AWS CloudFormation テンプレートを使用できます。

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/elasticsearch/appsynces.yml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/elasticsearch/appsynces.yml)

 AWS CloudFormation デプロイが完了したら、[GraphQL クエリとミューテーションの実行](#tutorial-elasticsearch-resolvers-perform-queries-mutations)に直接スキップできます。

## OpenSearch Service ドメインを作成する
<a name="create-a-new-es-domain"></a>

このチュートリアルを開始するには、既存の OpenSearch Service ドメインが必要です。ドメインがない場合は、次のサンプルが使用できます。OpenSearch Service ドメインが作成されるまで、 AWS AppSync データソースとの統合に進むまでに最大 15 分かかる場合があります。

```
aws cloudformation create-stack --stack-name AppSyncOpenSearch \
--template-url https://s3.us-west-2.amazonaws.com/awsappsync/resources/elasticsearch/ESResolverCFTemplate.yaml \
--parameters ParameterKey=OSDomainName,ParameterValue=ddtestdomain ParameterKey=Tier,ParameterValue=development \
--capabilities CAPABILITY_NAMED_IAM
```

 AWS アカウントの米国西部 2 (オレゴン) リージョンで次の AWS CloudFormation スタックを起動できます。

 [https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/elasticsearch/ESResolverCFTemplate.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/elasticsearch/ESResolverCFTemplate.yaml)

## OpenSearch Service のデータソースの設定
<a name="configure-data-source-for-es"></a>

OpenSearch Service ドメインを作成したら、 AWS AppSync GraphQL API に移動し、**データソース**タブを選択します。[**New (新規)**] を選択して、データソースに「oss」などの分かりやすい名前を入力します。次に、**[データソースタイプ]** に **[Amazon Opensearch ドメイン]** を選択し、適切なリージョンを選択します。OpenSearch Service ドメインがリストに表示されます。選択したら、新しいロールを作成し、 AWS AppSync がロールに適切なアクセス許可を割り当てるか、次のインラインポリシーを持つ既存のロールを選択できます。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Sid": "Stmt1234234",
            "Effect": "Allow",
            "Action": [
                "es:ESHttpDelete",
                "es:ESHttpHead",
                "es:ESHttpGet",
                "es:ESHttpPost",
                "es:ESHttpPut"
            ],
            "Resource": [
                "arn:aws:es:us-east-1:111122223333:domain/democluster/*"
            ]
        }
    ]
}
```

------

また、そのロールに対して AWS AppSync との信頼関係を設定する必要があります。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "appsync.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

------

さらに、OpenSearch Service ドメインには独自の**アクセスポリシー**があります。これは Amazon OpenSearch Service コンソールを使用して変更できます。OpenSearch Service ドメインの適切なアクションとリソースを用いて、以下のように同じようなポリシーを追加する必要があります。**Principal** は AppSync のデータソースのロールです。これをコンソールに作成させる場合には、IAM コンソールに表示されるようにします。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::111122223333:role/service-role/APPSYNC_DATASOURCE_ROLE"
            },
            "Action": [
                "es:ESHttpDelete",
                "es:ESHttpHead",
                "es:ESHttpGet",
                "es:ESHttpPost",
                "es:ESHttpPut"
            ],
            "Resource": "arn:aws:es:us-east-1:111122223333:domain/DOMAIN_NAME/*"
        }
    ]
}
```

------

## リゾルバーを接続する
<a name="connecting-a-resolver"></a>

これで、データソースが OpenSearch Service ドメインに接続されたので、リゾルバーを使用して、以下の例のようにデータソースを GraphQL スキーマに接続することができます。

```
 schema {
   query: Query
   mutation: Mutation
 }

 type Query {
   getPost(id: ID!): Post
   allPosts: [Post]
 }

 type Mutation {
   addPost(id: ID!, author: String, title: String, url: String, ups: Int, downs: Int, content: String): AWSJSON
 }

type Post {
  id: ID!
  author: String
  title: String
  url: String
  ups: Int
  downs: Int
  content: String
}
...
```

`Post` フィールドを含むユーザー定義の `id` 型があることに注意してください。次の例では、この型を OpenSearch Service ドメインに配置するプロセス (自動化可能) があることを前提としています。このプロセスでは、`/post/_doc` へのルートパスをマッピングします。`post` はインデックスです。このルートパスから、個々のドキュメントの検索、`/id/post*` によるワイルドカード検索、`/post/_search` の パスを使用した複数ドキュメントの検索が行えます。例えば、同じインデックス `user` でインデックスが付けられた `User` という別の型がある場合は、`/user/_search` の **パス**を使用して複数ドキュメントが検索できます。

 AWS AppSync コンソールのスキーマエディタから、前述の`Posts`スキーマを変更して`searchPosts`クエリを含めます。

```
type Query {
  getPost(id: ID!): Post
  allPosts: [Post]
  searchPosts: [Post]
}
```

スキーマを保存します。右側の [`searchPosts`] で、[**Attach resolver (リゾルバーをアタッチ)**] を選択します。**[アクション]** メニューで **[ランタイムの更新]** を選択し、**[ユニットリゾルバー (VTL のみ)]** を選択します。次に、OpenSearch Service サービスのデータソースを選択します。[**request mapping template (リクエストマッピングテンプレート)**] セクションで、ドロップダウンから [**Query posts (投稿のクエリ)**] を選択し、基本テンプレートを取得します。`path` を `/post/_search` に変更します。次のようになります。

```
{
    "version":"2017-02-28",
    "operation":"GET",
    "path":"/post/_search",
    "params":{
        "headers":{},
        "queryString":{},
        "body":{
            "from":0,
            "size":50
        }
    }
}
```

これは、前述のスキーマの `post` フィールドの下に OpenSearch Service でインデックス化されたドキュメントがあることを前提としています。データ構造が異なる場合は、更新が必要です。

OpenSearch Service クエリからデータの結果を受信し、変換して GraphQL に渡す場合には、**[レスポンスマッピングテンプレート]** セクションで適切な `_source` フィルタを指定する必要があります。以下のテンプレートを使用してください。

```
[
    #foreach($entry in $context.result.hits.hits)
    #if( $velocityCount > 1 ) , #end
    $utils.toJson($entry.get("_source"))
    #end
]
```

## 検索を変更する
<a name="modifying-your-searches"></a>

前述のリクエストマッピングテンプレートは、すべてのレコードに簡単なクエリを実行します。今、特定の筆者で検索する場合を考えます。また、その筆者を、GraphQL クエリに定義した引数にするとします。 AWS AppSync コンソールのスキーマエディタで、`allPostsByAuthor`クエリを追加します。

```
type Query {
  getPost(id: ID!): Post
  allPosts: [Post]
  allPostsByAuthor(author: String!): [Post]
  searchPosts: [Post]
}
```

その後、**[リゾルバーをアタッチ]** を選択し、OpenSearch Service データソースを選択します。ただし、**[レスポンスマッピングテンプレート]** には次の例を使用します。

```
{
    "version":"2017-02-28",
    "operation":"GET",
    "path":"/post/_search",
    "params":{
        "headers":{},
        "queryString":{},
        "body":{
            "from":0,
            "size":50,
            "query":{
                "match" :{
                    "author": $util.toJson($context.arguments.author)
                }
            }
        }
    }
}
```

`body` には `author` フィールドを含む term クエリが含まれていることに注意してください。これは引数としてクライアントから渡されます。任意で、標準テキストなどの事前に入力された情報を追加したり、他の[ユーティリティ](resolver-context-reference.md#aws-appsync-resolver-mapping-template-context-reference)を使用したりすることもできます。

このリゾルバーを使用している場合、以前の例と同じ情報を**レスポンスマッピングテンプレート**に入力します。

## OpenSearch Service へのデータの追加
<a name="adding-data-to-es"></a>

GraphQL ミューテーションの結果、OpenSearch Service ドメインにデータの追加が必要になる場合があります。これは検索やその他の目的に非常に役立ちます。GraphQL サブスクリプションを使用して[データをリアルタイムで作成](aws-appsync-real-time-data.md)できるため、これは OpenSearch Service ドメインのデータの更新をクライアントに通知するメカニズムとして動作します。

 AWS AppSync コンソールの**スキーマ**ページに戻り、`addPost()`ミューテーションの**リゾルバーをア**タッチを選択します。再度 OpenSearch Service データソースを選択し、`Posts` スキーマに次の**レスポンスマッピングテンプレート**を使用します。

```
{
    "version":"2017-02-28",
    "operation":"PUT",
    "path": $util.toJson("/post/_doc/$context.arguments.id"),
    "params":{
        "headers":{},
        "queryString":{},
        "body":{
            "id": $util.toJson($context.arguments.id),
            "author": $util.toJson($context.arguments.author),
            "ups": $util.toJson($context.arguments.ups),
            "downs": $util.toJson($context.arguments.downs),
            "url": $util.toJson($context.arguments.url),
            "content": $util.toJson($context.arguments.content),
            "title": $util.toJson($context.arguments.title)
        }
    }
}
```

以前と同様、これはデータ構造の一例です。別のフィールド名またはインデックスがある場合は、必要に応じて `path` と `body` を更新します。この例では、GraphQL ミューテーションの引数からテンプレートを受け取るために `$context.arguments` を使用する方法も示されています。

次に進む前に、以下のレスポンスマッピングテンプレートを使用してください。これにより、ミューテーション操作の結果またはエラー情報が出力として返されます。

```
#if($context.error)
    $util.toJson($ctx.error)
#else
    $util.toJson($context.result)
#end
```

## 単一のドキュメントの取得
<a name="retrieving-a-single-document"></a>

最後に、スキーマで`getPost(id:ID)`クエリを使用して個々のドキュメントを返す場合は、 AWS AppSync コンソールのスキーマエディタでこのクエリを検索し、リ**ゾルバーをア**タッチを選択します。再度 OpenSearch Service データソースを選択し、次のマッピングテンプレートを使用します。

```
{
    "version":"2017-02-28",
    "operation":"GET",
    "path": $util.toJson("post/_doc/$context.arguments.id"),
    "params":{
        "headers":{},
        "queryString":{},
        "body":{}
    }
}
```

上記の `path` では `id` 引数の body が空であるため、単一のドキュメントが返ります。ただし、リストではなく単一の項目が返るため、次のレスポンスマッピングテンプレートを使用する必要があります。

```
$utils.toJson($context.result.get("_source"))
```

## クエリとミューテーションを実行する
<a name="tutorial-elasticsearch-resolvers-perform-queries-mutations"></a>

これで、OpenSearch Service ドメインに対して GraphQL 処理が実行できます。 AWS AppSync コンソールの**クエリ**タブに移動し、新しいレコードを追加します。

```
mutation addPost {
    addPost (
        id:"12345"
        author: "Fred"
        title: "My first book"
        content: "This will be fun to write!"
        url: "publisher website",
        ups: 100,
        downs:20 
       )
}
```

ミューテーションの結果が右側に表示されます。同様に、OpenSearch Service ドメインに対して `searchPosts` クエリが実行できます。

```
query searchPosts {
    searchPosts {
        id
        title
        author
        content
    }
}
```

## ベストプラクティス
<a name="best-practices"></a>
+ OpenSearch Service はプライマリデータベースとしてではなく、データのクエリ発行のために使用します。「[GraphQL リゾルバーを組み合わせる](tutorial-combining-graphql-resolvers.md#aws-appsync-tutorial-combining-graphql-resolvers)」で説明したように、Amazon DynamoDB と組み合わせて OpenSearch Service を使用する場合があります。
+ ドメインへのアクセスを許可するには、 AWS AppSync サービスロールにクラスターへのアクセスを許可します。
+ 開発中は、最小限のコストのクラスターを使用して小規模で開始し、その後、本稼働への移行時に高可用性 (HA) を備えた大規模なクラスターへと移行することができます。

# AWS AppSync でのローカルリゾルバーの使用
<a name="tutorial-local-resolvers"></a>

**注記**  
現在、主に APPSYNC\$1JS ランタイムとそのドキュメントをサポートしています。[こちら](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)で APPSYNC\$1JS ランタイムとそのガイドの使用をご検討ください。

AWS AppSync では、サポートされているデータソース (Amazon DynamoDB または Amazon OpenSearch Service) を使用してさまざまなオペレーションを実行できます。ただし、特定のシナリオでは、サポートされているデータソースに対する呼び出しの必要がないことがあります。

そのような場合は、ローカルリゾルバーが役立ちます。リモートのデータソースを呼び出すのではなく、ローカルリゾルバーはリクエストマッピングテンプレートの結果をレスポンスマッピングテンプレートに**転送**します。AWS AppSync ではフィールドの解決は行われません。

ローカルリゾルバーは、いくつかのユースケースで便利です。特によく使用されるのは、データソースの呼び出しをトリガーせずに通知を発行する場合です。そのユースケースの例を示すために、ユーザーが互いに呼び出すことができるページングアプリケーションを構築しましょう。この例では、*サブスクリプション*を活用します。*サブスクリプション*に慣れていない場合は、「[リアルタイムデータ](aws-appsync-real-time-data.md)」チュートリアルを参照してください。

## ページングアプリケーションの作成
<a name="create-the-paging-application"></a>

このページングアプリケーションでは、クライアントは受信箱をサブスクライブでき、他のクライアントにページを送信できます。各ページにはメッセージが含まれています。スキーマは次のとおりです。

```
schema {
    query: Query
    mutation: Mutation
    subscription: Subscription
}

type Subscription {
    inbox(to: String!): Page
    @aws_subscribe(mutations: ["page"])
}

type Mutation {
    page(body: String!, to: String!): Page!
}

type Page {
    from: String
    to: String!
    body: String!
    sentAt: String!
}

type Query {
    me: String
}
```

`Mutation.page` フィールドにリゾルバーをアタッチしましょう。[**スキーマ**] ペインで、右側のパネルのフィールド定義の横にある [*Attach Resolver (リゾルバーのアタッチ)*] をクリックします。*None* 型の新しいデータソースを作成して、*PageDataSource* という名前を付けます。

リクエストマッピングテンプレートで、次のように入力します。

```
{
  "version": "2017-02-28",
  "payload": {
    "body": $util.toJson($context.arguments.body),
    "from": $util.toJson($context.identity.username),
    "to":  $util.toJson($context.arguments.to),
    "sentAt": "$util.time.nowISO8601()"
  }
}
```

レスポンスマッピングテンプレートで、デフォルトの [*Forward the result (結果を転送)*] を選択します。リゾルバーを保存します。これで、アプリケーションの準備ができたので、ページングしましょう。

## ページの送信およびサブスクライブ
<a name="send-and-subscribe-to-pages"></a>

ページを受信するクライアントで、まず受信箱をサブスクライブする必要があります。

[**Queries (クエリ)**] ペインで `inbox` のサブスクリプションを実行しましょう。

```
subscription Inbox {
    inbox(to: "Nadia") {
        body
        to
        from
        sentAt
    }
}
```

 *Nadia* は、`Mutation.page` ミューテーションが呼び出されるたびにページを受け取ります。ミューテーションを実行することで、このミューテーションを呼び出しましょう。

```
mutation Page {
    page(to: "Nadia", body: "Hello, World!") {
        body
        to
        from
        sentAt
    }
}
```

AWS AppSync に任せずにページを送信してそれを受信することで、ローカルリゾルバーの使用方法の例を示しました。

# での GraphQL リゾルバーの組み合わせ AWS AppSync
<a name="tutorial-combining-graphql-resolvers"></a>

**注記**  
現在、主に APPSYNC\$1JS ランタイムとそのドキュメントをサポートしています。[こちら](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)にある APPSYNC\$1JS ランタイムとそのガイドの使用をご検討ください。

GraphQL スキーマのリゾルバーとフィールドには、非常に柔軟性の高い 1 対 1 の関係があります。データソースはスキーマとは独立にリゾルバーに設定されるため、ニーズに応じてスキーマ上で組み合わせやマッチングを行い、さまざまなデータソースを使用して GraphQL 型の解決や操作が行えます。

次のシナリオ例では、スキーマ内のデータソースを混在させて一致させる方法を示します。開始する前に、前のチュートリアルで説明したように AWS Lambda、、Amazon DynamoDB、Amazon OpenSearch Service のデータソースとリゾルバーの設定に精通しておくことをお勧めします。

## スキーマの例
<a name="example-schema"></a>

次のスキーマには、3 つの `Query` 処理と 3 つの `Mutation` 処理を含む `Post` という型が定義されています。

```
type Post {
    id: ID!
    author: String!
    title: String
    content: String
    url: String
    ups: Int
    downs: Int
    version: Int!
}

type Query {
    allPost: [Post]
    getPost(id: ID!): Post
    searchPosts: [Post]
}

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

この例では、合計 6 つのリゾルバーをアタッチします。考えられる方法の 1 つは、これらをすべて `Posts` という Amazon DynamoDB テーブルから取得することです。この場合、「[DynamoDB のリゾルバーのマッピングテンプレートリファレンス](resolver-mapping-template-reference-dynamodb.md#aws-appsync-resolver-mapping-template-reference-dynamodb)」で説明されているように、`AllPosts` はスキャンを実行し、`searchPosts` はクエリを実行します。ただし、Lambda または OpenSearch Service を使用してこれらの GraphQL クエリに解決させるなど、ビジネスニーズに応じて他の方法も使用できます。

## リゾルバーを使用してデータを変更する
<a name="alter-data-through-resolvers"></a>

DynamoDB (または Amazon Aurora) などのデータベースからクライアントに、一部の属性を変更した結果を返したい場合があります。クライアントでのタイムスタンプの相違などによるデータ型の成形が必要な場合や、後方互換性を確保するための処理が必要な場合などです。以下では、分かりやすい例として、GraphQL リゾルバーが呼び出されるたびに投稿にランダムな番号を割り当て、ブログの投稿に対する賛成または反対を操作する AWS Lambda 関数を示しています。

```
'use strict';
const doc = require('dynamodb-doc');
const dynamo = new doc.DynamoDB();

exports.handler = (event, context, callback) => {
    const payload = {
        TableName: 'Posts',
        Limit: 50,
        Select: 'ALL_ATTRIBUTES',
    };

    dynamo.scan(payload, (err, data) => {
        const result = { data: data.Items.map(item =>{
            item.ups = parseInt(Math.random() * (50 - 10) + 10, 10);
            item.downs = parseInt(Math.random() * (20 - 0) + 0, 10);
            return item;
        }) };
        callback(err, result.data);
    });
};
```

これは完全に有効な Lambda 関数であり、GraphQL スキーマの `AllPosts` フィールドにアタッチできるため、すべての結果を返すクエリが、賛成/反対に対するランダムな番号を受け取ります。

## DynamoDB と OpenSearch Service
<a name="ddb-and-es"></a>

一部のアプリケーションでは、DynamoDB に対してミューテーションまたは簡単な検索クエリを実行し、バックグラウンドプロセスでドキュメントを OpenSearch Service に転送する場合があります。その後、`searchPosts` リゾルバーを OpenSearch Service データソースに単純にアタッチし、GraphQL クエリを使用して、(DynamoDB のデータからの) 検索結果を返します。これは、キーワードやあいまいワードによる検索、地理空間検索などの高度な検索処理をアプリケーションに追加する場合に非常に役立ちます。DynamoDB からのデータ転送は、ETL プロセスを使用して、または Lambda を使用した DynamoDB からのストリームを使用して行うことができます。これの完全な例は、 AWS アカウントの米国西部 2 (オレゴン) リージョンで次の AWS CloudFormation スタックを使用して起動できます。

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/multipledatasource/appsyncesdbstream.yml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/multipledatasource/appsyncesdbstream.yml) 

この例では、スキーマは以下のような DynamoDB リゾルバーを使用して投稿を追加します。

```
mutation add {
    putPost(author:"Nadia"
        title:"My first post"
        content:"This is some test content"
        url:"https://aws.amazon.com/appsync/"
    ){
        id
        title
    }
}
```

これにより、データが DynamoDB に書き込まれます。その後、Lambda を介してデータが Amazon OpenSearch Service に転送されます。ここではすべての投稿がさまざまなフィールドについて検索できます。たとえば、データが Amazon OpenSearch Service にあるため、以下のように空白を含む任意のテキストを使用して、author フィールドまたは content フィールドを検索できます。

```
query searchName{
    searchAuthor(name:"   Nadia   "){
        id
        title
        content
    }
}

query searchContent{
    searchContent(text:"test"){
        id
        title
        content
    }
}
```

データは DynamoDB に直接書き込まれるため、`allPosts{...}` クエリと `singlePost{...}` クエリを使用して、テーブルに対するリストまたは項目の検索処理が効率的に実行できます。このスタックでは、DynamoDB ストリームに対して次のコード例を使用します。

 **注意 :** ここで示しているのは一例にすぎません。

```
var AWS = require('aws-sdk');
var path = require('path');
var stream = require('stream');

var esDomain = {
    endpoint: 'https://opensearch-domain-name.REGION.es.amazonaws.com',
    region: 'REGION',
    index: 'id',
    doctype: 'post'
};

var endpoint = new AWS.Endpoint(esDomain.endpoint)
var creds = new AWS.EnvironmentCredentials('AWS');

function postDocumentToES(doc, context) {
    var req = new AWS.HttpRequest(endpoint);

    req.method = 'POST';
    req.path = '/_bulk';
    req.region = esDomain.region;
    req.body = doc;
    req.headers['presigned-expires'] = false;
    req.headers['Host'] = endpoint.host;

    // Sign the request (Sigv4)
    var signer = new AWS.Signers.V4(req, 'es');
    signer.addAuthorization(creds, new Date());

    // Post document to ES
    var send = new AWS.NodeHttpClient();
    send.handleRequest(req, null, function (httpResp) {
        var body = '';
        httpResp.on('data', function (chunk) {
            body += chunk;
        });
        httpResp.on('end', function (chunk) {
            console.log('Successful', body);
            context.succeed();
        });
    }, function (err) {
        console.log('Error: ' + err);
        context.fail();
    });
}

exports.handler = (event, context, callback) => {
    console.log("event => " + JSON.stringify(event));
    var posts = '';

    for (var i = 0; i < event.Records.length; i++) {
        var eventName = event.Records[i].eventName;
        var actionType = '';
        var image;
        var noDoc = false;
        switch (eventName) {
            case 'INSERT':
                actionType = 'create';
                image = event.Records[i].dynamodb.NewImage;
                break;
            case 'MODIFY':
                actionType = 'update';
                image = event.Records[i].dynamodb.NewImage;
                break;
            case 'REMOVE':
            actionType = 'delete';
                image = event.Records[i].dynamodb.OldImage;
                noDoc = true;
                break;
        }

        if (typeof image !== "undefined") {
            var postData = {};
            for (var key in image) {
                if (image.hasOwnProperty(key)) {
                    if (key === 'postId') {
                        postData['id'] = image[key].S;
                    } else {
                        var val = image[key];
                        if (val.hasOwnProperty('S')) {
                            postData[key] = val.S;
                        } else if (val.hasOwnProperty('N')) {
                            postData[key] = val.N;
                        }
                    }
                }
            }

            var action = {};
            action[actionType] = {};
            action[actionType]._index = 'id';
            action[actionType]._type = 'post';
            action[actionType]._id = postData['id'];
            posts += [
                JSON.stringify(action),
            ].concat(noDoc?[]:[JSON.stringify(postData)]).join('\n') + '\n';
        }
    }
    console.log('posts:',posts);
    postDocumentToES(posts, context);
};
```

DynamoDB ストリームを使用し、`id` のプライマリキーを使用してこれを DynamoDB テーブルにアタッチできます。DynamoDB のソースへの変更は OpenSearch Service ドメインに転送されます。この設定の詳細については、[DynamoDB Streams のドキュメント](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.Lambda.html)を参照してください。

# AWS AppSync での DynamoDB バッチオペレーションの使用
<a name="tutorial-dynamodb-batch"></a>

**注記**  
現在、主に APPSYNC\$1JS ランタイムとそのドキュメントをサポートしています。[こちら](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html) で APPSYNC\$1JS ランタイムとそのガイドの使用をご検討ください。

AWS AppSync は、1 つのリージョン内の 1 つ以上のテーブルで Amazon DynamoDB バッチオペレーションの使用をサポートしています。サポートされている処理は、`BatchGetItem`、`BatchPutItem`、および `BatchDeleteItem` です。 AWS AppSync でこれらの機能を使用すると、次のようなタスクを実行できます。
+ 単一クエリでキーのリストを渡し、テーブルからの結果を返す
+ 単一クエリで 1 つ以上のテーブルからレコードを読み取る
+ 1 つ以上のテーブルに一連のレコードを書き込む
+ 関連のある複数のテーブルのレコードを状況に応じて書き込みまたは削除

 AWS AppSync で DynamoDB でバッチオペレーションを使用することは、バックエンドオペレーションとテーブル構造について、もう少し詳しく考え、知識を得るための高度な手法です。さらに、 AWS AppSync のバッチオペレーションには、バッチ処理されていないオペレーションと 2 つの主な違いがあります。
+ データソースのロールには、リゾルバーがアクセスするすべてのテーブルについてアクセス許可が必要です。
+ リゾルバーのテーブル仕様はマッピングテンプレートに含まれます。

## アクセス許可
<a name="permissions"></a>

他のリゾルバーと同様に、 AWS AppSync でデータソースを作成し、ロールを作成するか、既存のロールを使用する必要があります。バッチ処理では DynamoDB テーブルごとに異なるアクセス許可が必要であるため、読み取りまたは書き込みアクションのために設定されたアクセス許可がロールに必要です。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Action": [
                "dynamodb:BatchGetItem",
                "dynamodb:BatchWriteItem"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:dynamodb:us-east-1:111122223333:table/TABLENAME",
                "arn:aws:dynamodb:us-east-1:111122223333:table/TABLENAME/*"
            ]
        }
    ]
}
```

------

 **注**: ロールは AWS AppSync のデータソースに関連付けられ、フィールドのリゾルバーはデータソースに対して呼び出されます。DynamoDB に対して取得するよう設定されたデータソースは、設定を単純にするために 1 つのテーブルのみ指定されます。したがって、単一のリゾルバーで複数のテーブルに対してバッチ処理を実行する場合、これはより高度なタスクであり、リゾルバーが利用するすべてのテーブルへのアクセスをデータソースのロールに許可する必要があります。これは、上記の IAM ポリシーの **Resource** フィールドで行われます。テーブルに対してバッチを呼び出すためのテーブル設定は、リゾルバーのテンプレートで行います。これについては以下で説明します。

## データソース
<a name="data-source"></a>

分かりやすくするために、このチュートリアルではすべてのリゾルバーに同じデータソースを使用します。[**Data sources (データソース)**] タブで、新しい DynamoDB データソースを作成し、**BatchTutorial**という名前を付けます。テーブル名はバッチ処理のリクエストマッピングテンプレートで指定するので、何でも構いません。ここでは、テーブル名に `empty` を使用しています。

このチュートリアルでは、次のインラインポリシーを含むロールが使用できます。

## 1 つのテーブルのバッチ処理
<a name="single-table-batch"></a>

**警告**  
`BatchPutItem` および `BatchDeleteItem` は、競合の検出と解決とともに使用する場合はサポートされていません。これらの設定は、起こり得るエラーを防ぐために無効にする必要があります。

以下の例では、**Posts** という名前の単一のテーブルがあり、バッチ処理を使用してこれに項目を追加または削除するとします。次のスキーマを使用し、クエリには何もせずに ID のリストを渡します。

```
type Post {
    id: ID!
    title: String
}

input PostInput {
    id: ID!
    title: String
}

type Query {
    batchGet(ids: [ID]): [Post]
}

type Mutation {
    batchAdd(posts: [PostInput]): [Post]
    batchDelete(ids: [ID]): [Post]
}

schema {
    query: Query
    mutation: Mutation
}
```

次の**リクエストマッピングテンプレート**を使用して `batchAdd()` フィールドにリゾルバーをアタッチします。これは自動的に GraphQL の `input PostInput` 型に各項目を渡し、マップを作成します。このマップは `BatchPutItem` の処理に必要になります。

```
#set($postsdata = [])
#foreach($item in ${ctx.args.posts})
    $util.qr($postsdata.add($util.dynamodb.toMapValues($item)))
#end

{
    "version" : "2018-05-29",
    "operation" : "BatchPutItem",
    "tables" : {
        "Posts": $utils.toJson($postsdata)
    }
}
```

このケースでは、**レスポンスマッピングテンプレート**は単純に実行されますが、次のようにテーブル名が `..data.Posts` として context オブジェクトに追加されています。

```
$util.toJson($ctx.result.data.Posts)
```

次に、 AWS AppSync コンソールの**クエリ**ページに移動し、次の **batchAdd** ミューテーションを実行します。

```
mutation add {
    batchAdd(posts:[{
            id: 1 title: "Running in the Park"},{
            id: 2 title: "Playing fetch"
        }]){
            id
            title
    }
}
```

画面に結果が表示されるので、DynamoDB コンソールを使用して、両方の値が**Posts**テーブルに書き込まれたことを個別に確認できます。

次に、以下の**リクエストマッピングテーブル**を使用して `batchGet()` フィールドにリゾルバ―をアタッチします。これは自動的に GraphQL の `ids:[]` 型に各項目を渡し、マップを作成します。このマップは `BatchGetItem` の処理に必要になります。

```
#set($ids = [])
#foreach($id in ${ctx.args.ids})
    #set($map = {})
    $util.qr($map.put("id", $util.dynamodb.toString($id)))
    $util.qr($ids.add($map))
#end

{
    "version" : "2018-05-29",
    "operation" : "BatchGetItem",
    "tables" : {
        "Posts": {
            "keys": $util.toJson($ids),
            "consistentRead": true,
            "projection" : {
                "expression" : "#id, title",
                "expressionNames" : { "#id" : "id"}
                }
        }
    }
}
```

再度、**レスポンスマッピングテンプレート**が単純に実行されますが、ここでもテーブル名が `..data.Posts` として context オブジェクトに追加されます。

```
$util.toJson($ctx.result.data.Posts)
```

次に、 AWS AppSync コンソールの**クエリ**ページに戻り、次の **batchGet Query **を実行します。

```
query get {
    batchGet(ids:[1,2,3]){
        id
        title
    }
}
```

これは、以前に追加した 2 つの `id` 値の結果を返します。値が `null` の `id` に対して `3` 値が返っていることに注意してください。これは、その値に対応するレコードが **Posts** テーブルにまだないためです。また、 AWS AppSync はクエリに渡されたキーと同じ順序で結果を返します。これは、 AWS AppSync がユーザーに代わって実行する追加機能です。したがって、`batchGet(ids:[1,3,2)` に変更すると、順番が代わります。どの `id` で `null` 値が返されたのかもこれで分かります。

最後に、以下の**リクエストマッピングテンプレート**を使用して `batchDelete()` フィールドにリゾルバーをアタッチします。これは自動的に GraphQL の `ids:[]` 型に各項目を渡し、マップを作成します。このマップは `BatchGetItem` の処理に必要になります。

```
#set($ids = [])
#foreach($id in ${ctx.args.ids})
    #set($map = {})
    $util.qr($map.put("id", $util.dynamodb.toString($id)))
    $util.qr($ids.add($map))
#end

{
    "version" : "2018-05-29",
    "operation" : "BatchDeleteItem",
    "tables" : {
        "Posts": $util.toJson($ids)
    }
}
```

再度、**レスポンスマッピングテンプレート**が単純に実行されますが、ここでもテーブル名が `..data.Posts` として context オブジェクトに追加されます。

```
$util.toJson($ctx.result.data.Posts)
```

次に、 AWS AppSync コンソールの**クエリ**ページに戻り、次の **batchDelete** ミューテーションを実行します。

```
mutation delete {
    batchDelete(ids:[1,2]){ id }
}
```

`id` が `1` と `2` のレコードが削除されます。以前に使用した `batchGet()` クエリを再実行すると、これらは `null` を返します。

## 複数テーブルのバッチ処理
<a name="multi-table-batch"></a>

**警告**  
`BatchPutItem` および `BatchDeleteItem` は、競合の検出と解決とともに使用する場合はサポートされていません。これらの設定は、起こり得るエラーを防ぐために無効にする必要があります。

AWS AppSync では、テーブル間でバッチオペレーションを実行することもできます。より複雑なアプリケーションを作成しましょう。Pet Health アプリを作成するとします。これは、センサーによりペットの場所と体温を報告します。センサーは電池により動作し、数分ごとにネットワークへの接続を試みます。センサーが接続を確立すると、その読み取り値が AWS AppSync API に送信されます。その後、データの分析がトリガーされ、ペットの飼い主にダッシュボードが表示されます。センサーとバックエンドのデータストア間のやり取りの作成について考えてみましょう。

前提条件として、まず 2 つの DynamoDB テーブルを作成します。**locationReadings** はセンサーの温度の読み取り値を格納し、**temperatureReadings** はセンサーの位置の読み取り値を格納します。両方のテーブルで同じプライマリキー構造体を共有します。`sensorId (String)` はパーティションキーであり、`timestamp (String)` がソートキーです。

以下の GraphQL スキーマを使用しましょう。

```
type Mutation {
    # Register a batch of readings
    recordReadings(tempReadings: [TemperatureReadingInput], locReadings: [LocationReadingInput]): RecordResult
    # Delete a batch of readings
    deleteReadings(tempReadings: [TemperatureReadingInput], locReadings: [LocationReadingInput]): RecordResult
}

type Query {
    # Retrieve all possible readings recorded by a sensor at a specific time
    getReadings(sensorId: ID!, timestamp: String!): [SensorReading]
}

type RecordResult {
    temperatureReadings: [TemperatureReading]
    locationReadings: [LocationReading]
}

interface SensorReading {
    sensorId: ID!
    timestamp: String!
}

# Sensor reading representing the sensor temperature (in Fahrenheit)
type TemperatureReading implements SensorReading {
    sensorId: ID!
    timestamp: String!
    value: Float
}

# Sensor reading representing the sensor location (lat,long)
type LocationReading implements SensorReading {
    sensorId: ID!
    timestamp: String!
    lat: Float
    long: Float
}

input TemperatureReadingInput {
    sensorId: ID!
    timestamp: String
    value: Float
}

input LocationReadingInput {
    sensorId: ID!
    timestamp: String
    lat: Float
    long: Float
}
```

### BatchPutItem - センサーの読み取り値の記録
<a name="batchputitem-recording-sensor-readings"></a>

センサーは、インターネットに接続後、読み取り値を送信できる必要があります。GraphQL の `Mutation.recordReadings` フィールドは、このために使用される API です。リゾルバーをアタッチし、API を有効にしましょう。

[`Mutation.recordReadings`] フィールドの横にある [**アタッチ**] を選択します。次の画面で、チュートリアルの最初で作成したのと同じ `BatchTutorial` データソースを選択します。

次のリクエストマッピングテンプレートを追加しましょう。

 **リクエストマッピングテンプレート** 

```
## Convert tempReadings arguments to DynamoDB objects
#set($tempReadings = [])
#foreach($reading in ${ctx.args.tempReadings})
    $util.qr($tempReadings.add($util.dynamodb.toMapValues($reading)))
#end

## Convert locReadings arguments to DynamoDB objects
#set($locReadings = [])
#foreach($reading in ${ctx.args.locReadings})
    $util.qr($locReadings.add($util.dynamodb.toMapValues($reading)))
#end

{
    "version" : "2018-05-29",
    "operation" : "BatchPutItem",
    "tables" : {
        "locationReadings": $utils.toJson($locReadings),
        "temperatureReadings": $utils.toJson($tempReadings)
    }
}
```

ご覧のように、`BatchPutItem` 処理により複数のテーブルが指定できます。

次のレスポンスマッピングテンプレートを使用しましょう。

 **レスポンスマッピングテンプレート** 

```
## If there was an error with the invocation
## there might have been partial results
#if($ctx.error)
    ## Append a GraphQL error for that field in the GraphQL response
    $utils.appendError($ctx.error.message, $ctx.error.message)
#end
## Also returns data for the field in the GraphQL response
$utils.toJson($ctx.result.data)
```

バッチ処理では、呼び出しからエラーと結果の両方が返る可能性があります。その場合は、任意に追加のエラー処理を実行できます。

 **注意**: `$utils.appendError()` の使用法は `$util.error()` と同様ですが、マッピングテンプレートの評価を中断しないという違いがあります。代わりに、エラーが発生したことをフィールドで通知します。ただし、テンプレートを評価して、データを引き続き呼び出し元に返すことも可能です。アプリケーションで部分的な結果を返す必要がある場合は、`$utils.appendError()` を使用することを推奨します。

リゾルバーを保存し、 AWS AppSync コンソールの**クエリ**ページに移動します。センサーの読み取り値をいくつか送信してみましょう\$1

次のミューテーションを実行します。

```
mutation sendReadings {
  recordReadings(
    tempReadings: [
      {sensorId: 1, value: 85.5, timestamp: "2018-02-01T17:21:05.000+08:00"},
      {sensorId: 1, value: 85.7, timestamp: "2018-02-01T17:21:06.000+08:00"},
      {sensorId: 1, value: 85.8, timestamp: "2018-02-01T17:21:07.000+08:00"},
      {sensorId: 1, value: 84.2, timestamp: "2018-02-01T17:21:08.000+08:00"},
      {sensorId: 1, value: 81.5, timestamp: "2018-02-01T17:21:09.000+08:00"}
    ]
    locReadings: [
      {sensorId: 1, lat: 47.615063, long: -122.333551, timestamp: "2018-02-01T17:21:05.000+08:00"},
      {sensorId: 1, lat: 47.615163, long: -122.333552, timestamp: "2018-02-01T17:21:06.000+08:00"}
      {sensorId: 1, lat: 47.615263, long: -122.333553, timestamp: "2018-02-01T17:21:07.000+08:00"}
      {sensorId: 1, lat: 47.615363, long: -122.333554, timestamp: "2018-02-01T17:21:08.000+08:00"}
      {sensorId: 1, lat: 47.615463, long: -122.333555, timestamp: "2018-02-01T17:21:09.000+08:00"}
    ]) {
    locationReadings {
      sensorId
      timestamp
      lat
      long
    }
    temperatureReadings {
      sensorId
      timestamp
      value
    }
  }
}
```

1 つのミューテーションで 10 個のセンサー読み取り値を送信しました。これらは 2 つのテーブルに分けられています。DynamoDB コンソールを使用して、**locationReadings** と **temperatureReadings** の両方のテーブルにデータが表示されることを確認します。

### BatchDeleteItem - センサーの読み取り値の削除
<a name="batchdeleteitem-deleting-sensor-readings"></a>

同様に、センサーの読み取り値を一括して削除する必要もあります。これを行うために、`Mutation.deleteReadings` GraphQL フィールドを使用してみましょう。[`Mutation.recordReadings`] フィールドの横にある [**アタッチ**] を選択します。次の画面で、チュートリアルの最初で作成したのと同じ `BatchTutorial` データソースを選択します。

次のリクエストマッピングテンプレートを使用しましょう。

 **リクエストマッピングテンプレート** 

```
## Convert tempReadings arguments to DynamoDB primary keys
#set($tempReadings = [])
#foreach($reading in ${ctx.args.tempReadings})
    #set($pkey = {})
    $util.qr($pkey.put("sensorId", $reading.sensorId))
    $util.qr($pkey.put("timestamp", $reading.timestamp))
    $util.qr($tempReadings.add($util.dynamodb.toMapValues($pkey)))
#end

## Convert locReadings arguments to DynamoDB primary keys
#set($locReadings = [])
#foreach($reading in ${ctx.args.locReadings})
    #set($pkey = {})
    $util.qr($pkey.put("sensorId", $reading.sensorId))
    $util.qr($pkey.put("timestamp", $reading.timestamp))
    $util.qr($locReadings.add($util.dynamodb.toMapValues($pkey)))
#end

{
    "version" : "2018-05-29",
    "operation" : "BatchDeleteItem",
    "tables" : {
        "locationReadings": $utils.toJson($locReadings),
        "temperatureReadings": $utils.toJson($tempReadings)
    }
}
```

レスポンスマッピングテンプレートは `Mutation.recordReadings` に使用したものと同じです。

 **レスポンスマッピングテンプレート** 

```
## If there was an error with the invocation
## there might have been partial results
#if($ctx.error)
    ## Append a GraphQL error for that field in the GraphQL response
    $utils.appendError($ctx.error.message, $ctx.error.message)
#end
## Also return data for the field in the GraphQL response
$utils.toJson($ctx.result.data)
```

リゾルバーを保存し、 AWS AppSync コンソールの**クエリ**ページに移動します。では、いくつかのセンサーの読み取り値を削除しましょう\$1

次のミューテーションを実行します。

```
mutation deleteReadings {
  # Let's delete the first two readings we recorded
  deleteReadings(
    tempReadings: [{sensorId: 1, timestamp: "2018-02-01T17:21:05.000+08:00"}]
    locReadings: [{sensorId: 1, timestamp: "2018-02-01T17:21:05.000+08:00"}]) {
    locationReadings {
      sensorId
      timestamp
      lat
      long
    }
    temperatureReadings {
      sensorId
      timestamp
      value
    }
  }
}
```

DynamoDB コンソールを通じて **locationReadings** と **temperatureReadings** テーブルから削除されたこれら 2 つの読み取り値を検証します。

### BatchGetItem - 読み取り値の取得
<a name="batchgetitem-retrieve-readings"></a>

Pet Health アプリのもう 1 つの一般的な処理として、特定の時刻にセンサーの読み取り値を取得します。スキーマの `Query.getReadings` GraphQL フィールドにリゾルバーをアタッチしましょう。[**Attach (アタッチ)**] を選択し、次の画面で、このチュートリアルの最初で作成したのと同じ `BatchTutorial` データソースを選択します。

次のリクエストマッピングテンプレートを追加しましょう。

 **リクエストマッピングテンプレート** 

```
## Build a single DynamoDB primary key,
## as both locationReadings and tempReadings tables
## share the same primary key structure
#set($pkey = {})
$util.qr($pkey.put("sensorId", $ctx.args.sensorId))
$util.qr($pkey.put("timestamp", $ctx.args.timestamp))

{
    "version" : "2018-05-29",
    "operation" : "BatchGetItem",
    "tables" : {
        "locationReadings": {
            "keys": [$util.dynamodb.toMapValuesJson($pkey)],
            "consistentRead": true
        },
        "temperatureReadings": {
            "keys": [$util.dynamodb.toMapValuesJson($pkey)],
            "consistentRead": true
        }
    }
}
```

**BatchGetItem** 処理を現在使用していることに注意してください。

`SensorReading` リストが返されることを選択しているため、レスポンスマッピングテンプレートには多少の相違が発生します。呼び出しの結果を必要な形式にマッピングしましょう。

 **レスポンスマッピングテンプレート** 

```
## Merge locationReadings and temperatureReadings
## into a single list
## __typename needed as schema uses an interface
#set($sensorReadings = [])

#foreach($locReading in $ctx.result.data.locationReadings)
    $util.qr($locReading.put("__typename", "LocationReading"))
    $util.qr($sensorReadings.add($locReading))
#end

#foreach($tempReading in $ctx.result.data.temperatureReadings)
    $util.qr($tempReading.put("__typename", "TemperatureReading"))
    $util.qr($sensorReadings.add($tempReading))
#end

$util.toJson($sensorReadings)
```

リゾルバーを保存し、 AWS AppSync コンソールの**クエリ**ページに移動します。では、センサーの読み取り値を取得しましょう\$1

次のクエリを実行します。

```
query getReadingsForSensorAndTime {
  # Let's retrieve the very first two readings
  getReadings(sensorId: 1, timestamp: "2018-02-01T17:21:06.000+08:00") {
    sensorId
    timestamp
    ...on TemperatureReading {
      value
    }
    ...on LocationReading {
      lat
      long
    }
  }
}
```

以上で、 AWS AppSync を使用した DynamoDB のバッチ処理のデモンストレーションが完了しました。

## エラー処理
<a name="error-handling"></a>

In AWS AppSync では、データソースオペレーションが部分的な結果を返すことがあります。部分的な結果という用語は、処理の出力がいくつかのデータと 1 つのエラーで構成されていることを表す場合に使用します。エラー処理はアプリケーションで異なるため、 AWS AppSync は、エラーを処理する手段をレスポンスマッピングテンプレート内に用意しています。リゾルバーの呼び出しエラーがある場合、これは context から `$ctx.error` として読み出せます。呼び出しエラーには必ずメッセージと型が含まれています。これらは `$ctx.error.message` と `$ctx.error.type` というプロパティとしてアクセスできます。レスポンスマッピングテンプレートの呼び出し中に、以下の 3 つの方法で部分的な結果を処理することができます。

1. データを返すだけで、呼び出しエラーは通知しない

1. レスポンスマッピングテンプレートの評価を停止することでエラーを発生させる (`$util.error(...)` を使用)。データは返さない。

1. エラーを付加し (`$util.appendError(...)` を使用)、データも返す

DynamoDB のバッチ処理を使用して、上記の 3 つの方法をそれぞれ試してみましょう\$1

### DynamoDB のバッチ処理
<a name="dynamodb-batch-operations"></a>

DynamoDB のバッチ処理を使用すると、バッチを部分的に完了させることができます。つまり、リクエストされた項目またはキーの一部を未処理のままにすることができます。 AWS AppSync がバッチを完了できない場合、未処理の項目と呼び出しエラーがコンテキストに設定されます。

このチュートリアルの以前のセクションの `Query.getReadings` 処理で使用した `BatchGetItem` フィールドの設定を使用してエラー処理を実装します。ここでは、`Query.getReadings` フィールドの実行中に、`temperatureReadings` DynamoDB テーブルがプロビジョニングされたスループットを使い果たしたとします。DynamoDB は、 AWS AppSync がバッチ内の残りの要素を処理するために 2 回目の試行で **ProvisionedThroughputExceededException** を発生させました。

次の JSON は、DynamoDB のバッチ処理の呼び出し後、レスポンスマッピングテンプレートの評価前にシリアル化された context を表しています。

```
{
  "arguments": {
    "sensorId": "1",
    "timestamp": "2018-02-01T17:21:05.000+08:00"
  },
  "source": null,
  "result": {
    "data": {
      "temperatureReadings": [
        null
      ],
      "locationReadings": [
        {
          "lat": 47.615063,
          "long": -122.333551,
          "sensorId": "1",
          "timestamp": "2018-02-01T17:21:05.000+08:00"
        }
      ]
    },
    "unprocessedKeys": {
      "temperatureReadings": [
        {
          "sensorId": "1",
          "timestamp": "2018-02-01T17:21:05.000+08:00"
        }
      ],
      "locationReadings": []
    }
  },
  "error": {
    "type": "DynamoDB:ProvisionedThroughputExceededException",
    "message": "You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. (...)"
  },
  "outErrors": []
}
```

context に関する注意事項がいくつかあります。
+ by AWS AppSync `$ctx.error` のコンテキストで呼び出しエラーが設定され、エラータイプが **DynamoDB:ProvisionedThroughputExceededException** に設定されています。
+ エラーが存在していても、結果はテーブルごとに `$ctx.result.data` にマッピングされます。
+ 未処理のキーは `$ctx.result.data.unprocessedKeys` でアクセス可能です。ここでは、テーブルスループットが不十分なため、 AWS AppSync はキー (sensorId:1、timestamp:2018-02-01T17:21:05.000\$108:00) を持つ項目を取得できませんでした。

 **注意** : `BatchPutItem` の場合、これは `$ctx.result.data.unprocessedItems` です。`BatchDeleteItem` の場合、これは `$ctx.result.data.unprocessedKeys` です。

3 つの異なる方法でこのエラーを処理しましょう。

#### 1. 呼び出しエラーを通知しない
<a name="swallowing-the-invocation-error"></a>

呼び出しエラーを処理せずにデータを返して、エラーを実質的に通知しません。指定した GraphQL フィールドの結果は常に成功とします。

記述するレスポンスマッピングテンプレートは通常どおりであり、結果のデータのみを扱います。

レスポンスマッピングテンプレート

```
$util.toJson($ctx.result.data)
```

GraphQL レスポンス

```
{
  "data": {
    "getReadings": [
      {
        "sensorId": "1",
        "timestamp": "2018-02-01T17:21:05.000+08:00",
        "lat": 47.615063,
        "long": -122.333551
      },
      {
        "sensorId": "1",
        "timestamp": "2018-02-01T17:21:05.000+08:00",
        "value": 85.5
      }
    ]
  }
}
```

データのみが有効なため、エラーレスポンスにエラーは追加されません。

#### 2. エラーを発生させ、テンプレートの実行を中断する
<a name="raising-an-error-to-abort-the-template-execution"></a>

クライアントから見て、部分的な障害を完全な障害として扱う必要がある場合、テンプレートの実行を中断することでデータの返送を抑制することができます。この動作には、`$util.error(...)` ユーティリティメソッドを使用するのが最適です。

レスポンスマッピングテンプレート

```
## there was an error let's mark the entire field
## as failed and do not return any data back in the response
#if ($ctx.error)
    $util.error($ctx.error.message, $ctx.error.type, null, $ctx.result.data.unprocessedKeys)
#end

$util.toJson($ctx.result.data)
```

GraphQL レスポンス

```
{
  "data": {
    "getReadings": null
  },
  "errors": [
    {
      "path": [
        "getReadings"
      ],
      "data": null,
      "errorType": "DynamoDB:ProvisionedThroughputExceededException",
      "errorInfo": {
        "temperatureReadings": [
          {
            "sensorId": "1",
            "timestamp": "2018-02-01T17:21:05.000+08:00"
          }
        ],
        "locationReadings": []
      },
      "locations": [
        {
          "line": 58,
          "column": 3
        }
      ],
      "message": "You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. (...)"
    }
  ]
}
```

一部の結果が DynamoDB のバッチ処理から返されてもエラーが発生するように選択して、`getReadings` GraphQL フィールドが null になり、かつ GraphQL レスポンスの *errors* ブロックにエラーが追加されるようにします。

#### 3. エラーを付加してデータとエラーの両方を返す
<a name="appending-an-error-to-return-both-data-and-errors"></a>

場合によっては、より優れたユーザーエクスペリエンスを提供するために、アプリケーションから部分的な結果を返すとともに、クライアントに未処理の項目を通知することができます。クライアントでは、再試行を実装することも、エラーを変換してエンドユーザーに通知することもできます。`$util.appendError(...)` はこの動作を可能とするユーティリティメソッドであり、アプリケーションの設計者は、テンプレートの評価を妨げることなく、context にエラーを付加できます。テンプレートを評価した後、 AWS AppSync はコンテキストエラーを GraphQL レスポンスのエラーブロックに追加して処理します。

レスポンスマッピングテンプレート

```
#if ($ctx.error)
    ## pass the unprocessed keys back to the caller via the `errorInfo` field
    $util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.data.unprocessedKeys)
#end

$util.toJson($ctx.result.data)
```

ここでは、GraphQL レスポンスのエラーブロック内の呼び出しエラーと unprocessedKeys 要素の両方が転送されています。以下のレスポンスで分かるとおり、`getReadings` フィールドもまた **locationReadings** テーブルから部分的なデータを返します。

GraphQL レスポンス

```
{
  "data": {
    "getReadings": [
      null,
      {
        "sensorId": "1",
        "timestamp": "2018-02-01T17:21:05.000+08:00",
        "value": 85.5
      }
    ]
  },
  "errors": [
    {
      "path": [
        "getReadings"
      ],
      "data": null,
      "errorType": "DynamoDB:ProvisionedThroughputExceededException",
      "errorInfo": {
        "temperatureReadings": [
          {
            "sensorId": "1",
            "timestamp": "2018-02-01T17:21:05.000+08:00"
          }
        ],
        "locationReadings": []
      },
      "locations": [
        {
          "line": 58,
          "column": 3
        }
      ],
      "message": "You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. (...)"
    }
  ]
}
```

# AWS AppSync での DynamoDB トランザクションの実行
<a name="tutorial-dynamodb-transact"></a>

**注記**  
現在、主に APPSYNC\$1JS ランタイムとそのドキュメントをサポートしています。[こちら](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html) で APPSYNC\$1JS ランタイムとそのガイドの使用をご検討ください。

AWS AppSync は、1 つのリージョン内の 1 つ以上のテーブルで Amazon DynamoDB トランザクションオペレーションの使用をサポートしています。サポートされている処理は、`TransactGetItems` および `TransactWriteItems` です。 AWS AppSync でこれらの機能を使用すると、次のようなタスクを実行できます。
+ 単一クエリでキーのリストを渡し、テーブルからの結果を返す
+ 単一クエリで 1 つ以上のテーブルからレコードを読み取る
+ トランザクション内のレコードを 1 つまたは複数のテーブルにオールオアナッシング方式で書き込みます
+ いくつかの条件が満たされたときにトランザクションを実行します

## アクセス許可
<a name="permissions"></a>

他のリゾルバーと同様に、 AWS AppSync でデータソースを作成し、ロールを作成するか、既存のロールを使用する必要があります。トランザクションオペレーションでは DynamoDB テーブルごとに異なるアクセス許可が必要であるため、読み取りまたは書き込みアクションのために設定されたアクセス許可がロールに必要です。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Action": [
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:UpdateItem"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:dynamodb:us-east-1:111122223333:table/TABLENAME",
                "arn:aws:dynamodb:us-east-1:111122223333:table/TABLENAME/*"
            ]
        }
    ]
}
```

------

 **注**: ロールは AWS AppSync のデータソースに関連付けられ、フィールドのリゾルバーはデータソースに対して呼び出されます。DynamoDB に対して取得するよう設定されたデータソースは、設定を単純にするために 1 つのテーブルのみ指定されます。したがって、単一のリゾルバーで複数のテーブルに対してトランザクションオペレーションを実行する場合、これはより高度なタスクであり、リゾルバーが利用するすべてのテーブルへのアクセスをデータソースのロールに許可する必要があります。これは、上記の IAM ポリシーの **Resource** フィールドで行われます。テーブルに対するトランザクション呼び出しの設定は、リゾルバーのテンプレートで行います。これについては以下で説明します。

## データソース
<a name="data-source"></a>

分かりやすくするために、このチュートリアルではすべてのリゾルバーに同じデータソースを使用します。[**Data sources (データソース)**] タブで、新しい DynamoDB データソースを作成し、**TransactTutorial** という名前を付けます。テーブル名はトランザクションオペレーションのリクエストマッピングテンプレートで指定するので、何でも構いません。ここでは、テーブル名に `empty` を使用しています。

パーティションキーとして `accountNumber` を持つ **savingAccounts** および **checkingAccounts** という 2 つのテーブルと、パーティションキーとして `transactionId` を持つ **transactionHistory** テーブルができます。

このチュートリアルでは、次のインラインポリシーを含むロールが使用できます。`region` および `accountId` をお客様のリージョンとアカウント ID に置き換えます。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Action": [
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:UpdateItem"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:dynamodb:us-east-1:111122223333:table/savingAccounts",
                "arn:aws:dynamodb:us-east-1:111122223333:table/savingAccounts/*",
                "arn:aws:dynamodb:us-east-1:111122223333:table/checkingAccounts",
                "arn:aws:dynamodb:us-east-1:111122223333:table/checkingAccounts/*",
                "arn:aws:dynamodb:us-east-1:111122223333:table/transactionHistory",
                "arn:aws:dynamodb:us-east-1:111122223333:table/transactionHistory/*"
            ]
        }
    ]
}
```

------

## トランザクション
<a name="transactions"></a>

この例では、コンテキストは従来の銀行取引で、次の目的で `TransactWriteItems` を使用します。
+ 普通預金から当座預金への振替
+ 取引ごとの新しい取引レコードの生成

次に、`TransactGetItems` を使用して、普通預金と当座預金の詳細を取得します。

**警告**  
`TransactWriteItems` は、競合の検出と解決とともに使用する場合はサポートされていません。これらの設定は、起こり得るエラーを防ぐために無効にする必要があります。

次のように GraphQL スキーマを定義します。

```
type SavingAccount {
    accountNumber: String!
    username: String
    balance: Float
}

type CheckingAccount {
    accountNumber: String!
    username: String
    balance: Float
}

type TransactionHistory {
    transactionId: ID!
    from: String
    to: String
    amount: Float
}

type TransactionResult {
    savingAccounts: [SavingAccount]
    checkingAccounts: [CheckingAccount]
    transactionHistory: [TransactionHistory]
}

input SavingAccountInput {
    accountNumber: String!
    username: String
    balance: Float
}

input CheckingAccountInput {
    accountNumber: String!
    username: String
    balance: Float
}

input TransactionInput {
    savingAccountNumber: String!
    checkingAccountNumber: String!
    amount: Float!
}

type Query {
    getAccounts(savingAccountNumbers: [String], checkingAccountNumbers: [String]): TransactionResult
}

type Mutation {
    populateAccounts(savingAccounts: [SavingAccountInput], checkingAccounts: [CheckingAccountInput]): TransactionResult
    transferMoney(transactions: [TransactionInput]): TransactionResult
}

schema {
    query: Query
    mutation: Mutation
}
```

### TransactWriteItems - 口座の入力
<a name="transactwriteitems-populate-accounts"></a>

口座間で振替を行うには、テーブルに詳細を入力する必要があります。これを実行するには、GraphQL オペレーション `Mutation.populateAccounts` を使用します。

[スキーマ] セクションで、`Mutation.populateAccounts` オペレーションの横にある **[アタッチ]** をクリックします。VTL ユニットリゾルバーに移動し、同じ `TransactTutorial` データソースを選択してください。

ここで、次のリクエストマッピングテンプレートを使用します。

 **リクエストマッピングテンプレート** 

```
#set($savingAccountTransactPutItems = [])
#set($index = 0)
#foreach($savingAccount in ${ctx.args.savingAccounts})
    #set($keyMap = {})
    $util.qr($keyMap.put("accountNumber", $util.dynamodb.toString($savingAccount.accountNumber)))
    #set($attributeValues = {})
    $util.qr($attributeValues.put("username", $util.dynamodb.toString($savingAccount.username)))
    $util.qr($attributeValues.put("balance", $util.dynamodb.toNumber($savingAccount.balance)))
    #set($index = $index + 1)
    #set($savingAccountTransactPutItem = {"table": "savingAccounts",
        "operation": "PutItem",
        "key": $keyMap,
        "attributeValues": $attributeValues})
    $util.qr($savingAccountTransactPutItems.add($savingAccountTransactPutItem))
#end

#set($checkingAccountTransactPutItems = [])
#set($index = 0)
#foreach($checkingAccount in ${ctx.args.checkingAccounts})
    #set($keyMap = {})
    $util.qr($keyMap.put("accountNumber", $util.dynamodb.toString($checkingAccount.accountNumber)))
    #set($attributeValues = {})
    $util.qr($attributeValues.put("username", $util.dynamodb.toString($checkingAccount.username)))
    $util.qr($attributeValues.put("balance", $util.dynamodb.toNumber($checkingAccount.balance)))
    #set($index = $index + 1)
    #set($checkingAccountTransactPutItem = {"table": "checkingAccounts",
        "operation": "PutItem",
        "key": $keyMap,
        "attributeValues": $attributeValues})
    $util.qr($checkingAccountTransactPutItems.add($checkingAccountTransactPutItem))
#end

#set($transactItems = [])
$util.qr($transactItems.addAll($savingAccountTransactPutItems))
$util.qr($transactItems.addAll($checkingAccountTransactPutItems))

{
    "version" : "2018-05-29",
    "operation" : "TransactWriteItems",
    "transactItems" : $util.toJson($transactItems)
}
```

さらに、以下のレスポンスマッピングテンプレートがあるとします。

 **レスポンスマッピングテンプレート** 

```
#if ($ctx.error)
    $util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.cancellationReasons)
#end

#set($savingAccounts = [])
#foreach($index in [0..2])
    $util.qr($savingAccounts.add(${ctx.result.keys[$index]}))
#end

#set($checkingAccounts = [])
#foreach($index in [3..5])
    $util.qr($checkingAccounts.add(${ctx.result.keys[$index]}))
#end

#set($transactionResult = {})
$util.qr($transactionResult.put('savingAccounts', $savingAccounts))
$util.qr($transactionResult.put('checkingAccounts', $checkingAccounts))

$util.toJson($transactionResult)
```

リゾルバーを保存し、 AWS AppSync コンソールの**クエリ**セクションに移動してアカウントを入力します。

次のミューテーションを実行します。

```
mutation populateAccounts {
  populateAccounts (
    savingAccounts: [
      {accountNumber: "1", username: "Tom", balance: 100},
      {accountNumber: "2", username: "Amy", balance: 90},
      {accountNumber: "3", username: "Lily", balance: 80},
    ]
    checkingAccounts: [
      {accountNumber: "1", username: "Tom", balance: 70},
      {accountNumber: "2", username: "Amy", balance: 60},
      {accountNumber: "3", username: "Lily", balance: 50},
    ]) {
    savingAccounts {
      accountNumber
    }
    checkingAccounts {
      accountNumber
    }
  }
}
```

1 つのミューテーションで 3 つの普通預金と 3 つの当座預金に入力しました。

DynamoDB コンソールを使用して、**savingAccounts** テーブルと **checkingAccounts** テーブルの両方にデータが表示されることを確認します。

### TransactWriteItems - 振替
<a name="transactwriteitems-transfer-money"></a>

次の**リクエストマッピングテンプレート**を使用して `transferMoney` ミューテーションにリゾルバーをアタッチします。`amounts`、`savingAccountNumbers`、および `checkingAccountNumbers` の値は同じであることに注意してください。

```
#set($amounts = [])
#foreach($transaction in ${ctx.args.transactions})
    #set($attributeValueMap = {})
    $util.qr($attributeValueMap.put(":amount", $util.dynamodb.toNumber($transaction.amount)))
    $util.qr($amounts.add($attributeValueMap))
#end

#set($savingAccountTransactUpdateItems = [])
#set($index = 0)
#foreach($transaction in ${ctx.args.transactions})
    #set($keyMap = {})
    $util.qr($keyMap.put("accountNumber", $util.dynamodb.toString($transaction.savingAccountNumber)))
    #set($update = {})
    $util.qr($update.put("expression", "SET balance = balance - :amount"))
    $util.qr($update.put("expressionValues", $amounts[$index]))
    #set($index = $index + 1)
    #set($savingAccountTransactUpdateItem = {"table": "savingAccounts",
        "operation": "UpdateItem",
        "key": $keyMap,
        "update": $update})
    $util.qr($savingAccountTransactUpdateItems.add($savingAccountTransactUpdateItem))
#end

#set($checkingAccountTransactUpdateItems = [])
#set($index = 0)
#foreach($transaction in ${ctx.args.transactions})
    #set($keyMap = {})
    $util.qr($keyMap.put("accountNumber", $util.dynamodb.toString($transaction.checkingAccountNumber)))
    #set($update = {})
    $util.qr($update.put("expression", "SET balance = balance + :amount"))
    $util.qr($update.put("expressionValues", $amounts[$index]))
    #set($index = $index + 1)
    #set($checkingAccountTransactUpdateItem = {"table": "checkingAccounts",
        "operation": "UpdateItem",
        "key": $keyMap,
        "update": $update})
    $util.qr($checkingAccountTransactUpdateItems.add($checkingAccountTransactUpdateItem))
#end

#set($transactionHistoryTransactPutItems = [])
#foreach($transaction in ${ctx.args.transactions})
    #set($keyMap = {})
    $util.qr($keyMap.put("transactionId", $util.dynamodb.toString(${utils.autoId()})))
    #set($attributeValues = {})
    $util.qr($attributeValues.put("from", $util.dynamodb.toString($transaction.savingAccountNumber)))
    $util.qr($attributeValues.put("to", $util.dynamodb.toString($transaction.checkingAccountNumber)))
    $util.qr($attributeValues.put("amount", $util.dynamodb.toNumber($transaction.amount)))
    #set($transactionHistoryTransactPutItem = {"table": "transactionHistory",
        "operation": "PutItem",
        "key": $keyMap,
        "attributeValues": $attributeValues})
    $util.qr($transactionHistoryTransactPutItems.add($transactionHistoryTransactPutItem))
#end

#set($transactItems = [])
$util.qr($transactItems.addAll($savingAccountTransactUpdateItems))
$util.qr($transactItems.addAll($checkingAccountTransactUpdateItems))
$util.qr($transactItems.addAll($transactionHistoryTransactPutItems))

{
    "version" : "2018-05-29",
    "operation" : "TransactWriteItems",
    "transactItems" : $util.toJson($transactItems)
}
```

単一の `TransactWriteItems` オペレーションで 3 つの銀行取引を行います。以下の**レスポンスマッピングテンプレート**を使用します。

```
#if ($ctx.error)
    $util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.cancellationReasons)
#end

#set($savingAccounts = [])
#foreach($index in [0..2])
    $util.qr($savingAccounts.add(${ctx.result.keys[$index]}))
#end

#set($checkingAccounts = [])
#foreach($index in [3..5])
    $util.qr($checkingAccounts.add(${ctx.result.keys[$index]}))
#end

#set($transactionHistory = [])
#foreach($index in [6..8])
    $util.qr($transactionHistory.add(${ctx.result.keys[$index]}))
#end

#set($transactionResult = {})
$util.qr($transactionResult.put('savingAccounts', $savingAccounts))
$util.qr($transactionResult.put('checkingAccounts', $checkingAccounts))
$util.qr($transactionResult.put('transactionHistory', $transactionHistory))

$util.toJson($transactionResult)
```

次に AWS AppSync コンソールの**クエリ**セクションに移動して、以下のように **transferMoney** ミューテーションを実行します。

```
mutation write {
  transferMoney(
    transactions: [
      {savingAccountNumber: "1", checkingAccountNumber: "1", amount: 7.5},
      {savingAccountNumber: "2", checkingAccountNumber: "2", amount: 6.0},
      {savingAccountNumber: "3", checkingAccountNumber: "3", amount: 3.3}
    ]) {
    savingAccounts {
      accountNumber
    }
    checkingAccounts {
      accountNumber
    }
    transactionHistory {
      transactionId
    }
  }
}
```

1 つのミューテーションで 2 つの銀行取引を送信しました。DynamoDB コンソールを使用して、**savingAccounts**、**checkingAccounts**、および **transactionHistory** の各テーブルにデータが表示されることを検証します。

### TransactGetItems - 口座の取得
<a name="transactgetitems-retrieve-accounts"></a>

1 つの取引リクエストで普通預金口座と当座預金口座から詳細を取得するために、スキーマで `Query.getAccounts` GraphQL オペレーションにリゾルバーをアタッチします。**[アタッチ]** を選択し、次の画面で、このチュートリアルの最初で作成したのと同じ `TransactTutorial` データソースを選択します。テンプレートを次のように設定します。

 **リクエストマッピングテンプレート** 

```
#set($savingAccountsTransactGets = [])
#foreach($savingAccountNumber in ${ctx.args.savingAccountNumbers})
    #set($savingAccountKey = {})
    $util.qr($savingAccountKey.put("accountNumber", $util.dynamodb.toString($savingAccountNumber)))
    #set($savingAccountTransactGet = {"table": "savingAccounts", "key": $savingAccountKey})
    $util.qr($savingAccountsTransactGets.add($savingAccountTransactGet))
#end

#set($checkingAccountsTransactGets = [])
#foreach($checkingAccountNumber in ${ctx.args.checkingAccountNumbers})
    #set($checkingAccountKey = {})
    $util.qr($checkingAccountKey.put("accountNumber", $util.dynamodb.toString($checkingAccountNumber)))
    #set($checkingAccountTransactGet = {"table": "checkingAccounts", "key": $checkingAccountKey})
    $util.qr($checkingAccountsTransactGets.add($checkingAccountTransactGet))
#end

#set($transactItems = [])
$util.qr($transactItems.addAll($savingAccountsTransactGets))
$util.qr($transactItems.addAll($checkingAccountsTransactGets))

{
    "version" : "2018-05-29",
    "operation" : "TransactGetItems",
    "transactItems" : $util.toJson($transactItems)
}
```

 **レスポンスマッピングテンプレート** 

```
#if ($ctx.error)
    $util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.cancellationReasons)
#end

#set($savingAccounts = [])
#foreach($index in [0..2])
    $util.qr($savingAccounts.add(${ctx.result.items[$index]}))
#end

#set($checkingAccounts = [])
#foreach($index in [3..4])
    $util.qr($checkingAccounts.add($ctx.result.items[$index]))
#end

#set($transactionResult = {})
$util.qr($transactionResult.put('savingAccounts', $savingAccounts))
$util.qr($transactionResult.put('checkingAccounts', $checkingAccounts))

$util.toJson($transactionResult)
```

リゾルバーを保存し、 AWS AppSync コンソールの**クエリ**セクションに移動します。普通預金口座と当座預金口座を取得するには、次のクエリを実行します。

```
query getAccounts {
  getAccounts(
    savingAccountNumbers: ["1", "2", "3"],
    checkingAccountNumbers: ["1", "2"]
  ) {
    savingAccounts {
      accountNumber
      username
      balance
    }
    checkingAccounts {
      accountNumber
      username
      balance
    }
  }
}
```

以上で、 AWS AppSync を使用した DynamoDB のトランザクションのデモンストレーションが完了しました。

# AWS AppSync での HTTP リゾルバーの使用
<a name="tutorial-http-resolvers"></a>

**注記**  
現在、主に APPSYNC\$1JS ランタイムとそのドキュメントをサポートしています。[こちら](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html) で APPSYNC\$1JS ランタイムとそのガイドの使用をご検討ください。

AWS AppSync を使用すると、サポートされているデータソース (Amazon DynamoDB AWS Lambda、Amazon OpenSearch Service、または Amazon Aurora) を使用して、GraphQL フィールドを解決するための任意の HTTP エンドポイントに加えて、さまざまなオペレーションを実行できます。HTTP エンドポイントが利用可能になったら、データソースを使用してこれに接続できます。その後、クエリ、ミューテーション、およびサブスクリプションなどの GraphQL オペレーションを実行するために、スキーマ内のリゾルバーを設定できます。このチュートリアルでは、いくつかの一般的な例を説明します。

このチュートリアルでは、 AWS AppSync GraphQL エンドポイントで REST API (Amazon API Gateway と Lambda により作成) を使用します。

## ワンクリックでのセットアップ
<a name="one-click-setup"></a>

(Amazon API Gateway と Lambda を使用して) HTTP エンドポイントを設定して AWS AppSync で GraphQL エンドポイントを自動的にセットアップする場合は、次の AWS CloudFormation テンプレートを使用できます。

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/http/http-full.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/http/http-full.yaml)

## REST API を作成する
<a name="creating-a-rest-api"></a>

次の AWS CloudFormation テンプレートを使用して、このチュートリアルで機能する REST エンドポイントを設定できます。

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/http/http-api-gw.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/http/http-api-gw.yaml)

 AWS CloudFormation スタックは次のステップを実行します。

1. マイクロサービス用のビジネスロジックを含む Lambda 関数を設定します。

1. 以下のエンドポイント/メソッド/コンテンツタイプを組み合わせて、API Gateway REST API をセットアップします。


****  

| API リソースパス | HTTP メソッド | サポートされているコンテンツタイプ | 
| --- | --- | --- | 
|  /v1/users  |  POST  |  application/json  | 
|  /v1/users  |  GET  |  application/json  | 
|  /v1/users/1  |  GET  |  application/json  | 
|  /v1/users/1  |  PUT  |  application/json  | 
|  /v1/users/1  |  DELETE  |  application/json  | 

## GraphQL API の作成
<a name="creating-your-graphql-api"></a>

 AWS AppSync で GraphQL API を作成するには:
+  AWS AppSync コンソールを開き、**API の作成**を選択します。
+ API 名として「`UserData`」と入力します。
+ **カスタムスキーマ**を選択します。
+ **[作成]** を選択します。

 AWS AppSync コンソールは、API キー認証モードを使用して新しい GraphQL API を作成します。このコンソールを使用して、残りの GraphQL API を設定し、このチュートリアルの残りの部分でクエリを実行できます。

## GraphQL スキーマを作成する
<a name="creating-a-graphql-schema"></a>

GraphQL API を作成できたので、次に GraphQL スキーマを作成します。 AWS AppSync コンソールのスキーマエディタから、スキーマが次のスキーマと一致していることを確認します。

```
schema {
    query: Query
    mutation: Mutation
}

type Mutation {
    addUser(userInput: UserInput!): User
    deleteUser(id: ID!): User
}

type Query {
    getUser(id: ID): User
    listUser: [User!]!
}

type User {
    id: ID!
    username: String!
    firstname: String
    lastname: String
    phone: String
    email: String
}

input UserInput {
    id: ID!
    username: String!
    firstname: String
    lastname: String
    phone: String
    email: String
}
```

## HTTP データソースを設定する
<a name="configure-your-http-data-source"></a>

HTTP データソースを設定するには、以下の操作を行います。
+ [**DataSources (データソース)**] タブで [**New (新規)**] を選択し、データソース名としてわかりやすい名前 (`HTTP` など) を入力します。
+ [**Data source type (データソースタイプ)**] で「**HTTP**」を選択します。
+ 作成された API Gateway エンドポイントに、エンドポイントを設定します。エンドポイントの一部にステージ名が含まれていないことを確認します。

 **注意:** 現時点では、パブリックエンドポイントのみが AWS AppSync でサポートされています。

 **注:** AWS AppSync サービスによって認識される認証機関の詳細については、[「HTTPS エンドポイント AWS AppSync の によって認識される認証機関 (CA)](http-cert-authorities.md#aws-appsync-http-certificate-authorities)」を参照してください。

## リゾルバーの設定
<a name="configuring-resolvers"></a>

このステップでは、http データソースを **getUser** クエリに接続します。

リゾルバーをセットアップするには、以下の手順に従います。
+ [**Schema (スキーマ)**] タブを選択します。
+ **クエリ**タイプの右下の**データ型**ペインで、**getUser**フィールドを見つけて、**アタッチ**を選択します。
+ **[データソース名]** で、**[HTTP]** を選択します。
+ 以下のコードを **[リクエストマッピングテンプレートの設定]** に貼り付けます。

```
{
    "version": "2018-05-29",
    "method": "GET",
    "params": {
        "headers": {
            "Content-Type": "application/json"
        }
    },
    "resourcePath": $util.toJson("/v1/users/${ctx.args.id}")
}
```
+ 以下のコードを **[レスポンスマッピングテンプレートの設定]** に貼り付けます。

```
## return the body
#if($ctx.result.statusCode == 200)
    ##if response is 200
    $ctx.result.body
#else
    ##if response is not 200, append the response to error block.
    $utils.appendError($ctx.result.body, "$ctx.result.statusCode")
#end
```
+ [**Query (クエリ)**] タブを選択して、以下のクエリを実行します。

```
query GetUser{
    getUser(id:1){
        id
        username
    }
}
```

これは以下のレスポンスを返します。

```
{
    "data": {
        "getUser": {
            "id": "1",
            "username": "nadia"
        }
    }
}
```
+ [**Schema (スキーマ)**] タブを選択します。
+ **ミューテーション**の右下の**データ型**ペインで、**AddUser**フィールドを見つけて、**アタッチ**を選択します。
+ **[データソース名]** で、**[HTTP]** を選択します。
+ 以下のコードを **[リクエストマッピングテンプレートの設定]** に貼り付けます。

```
{
    "version": "2018-05-29",
    "method": "POST",
    "resourcePath": "/v1/users",
    "params":{
      "headers":{
        "Content-Type": "application/json",
      },
      "body": $util.toJson($ctx.args.userInput)
    }
}
```
+ 以下のコードを **[レスポンスマッピングテンプレートの設定]** に貼り付けます。

```
## Raise a GraphQL field error in case of a datasource invocation error
#if($ctx.error)
    $util.error($ctx.error.message, $ctx.error.type)
#end
## if the response status code is not 200, then return an error. Else return the body **
#if($ctx.result.statusCode == 200)
    ## If response is 200, return the body.
    $ctx.result.body
#else
    ## If response is not 200, append the response to error block.
    $utils.appendError($ctx.result.body, "$ctx.result.statusCode")
#end
```
+ [**Query (クエリ)**] タブを選択して、以下のクエリを実行します。

```
mutation addUser{
    addUser(userInput:{
        id:"2",
        username:"shaggy"
    }){
        id
        username
    }
}
```

これは以下のレスポンスを返します。

```
{
    "data": {
        "getUser": {
        "id": "2",
        "username": "shaggy"
        }
    }
}
```

## AWS サービスの呼び出し
<a name="invoking-aws-services"></a>

HTTP リゾルバーを使用して、 AWS サービスの GraphQL API インターフェイスを設定できます。への HTTP リクエストは、 AWS が送信者を識別できるように[、署名バージョン 4 プロセス](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)で署名 AWS する必要があります。 AWS AppSync は、IAM ロールを HTTP データソースに関連付けるときに、ユーザーに代わって署名を計算します。

HTTP リゾルバーで AWS サービスを呼び出すには、次の 2 つの追加コンポーネントを提供します。
+  AWS サービス APIs を呼び出すアクセス許可を持つ IAM ロール
+ データソースの署名設定

たとえば、HTTP リゾルバーを使用して [ListGraphqlApis オペレーション](https://docs.aws.amazon.com/appsync/latest/APIReference/API_ListGraphqlApis.html)を呼び出す場合は、まず AWS AppSync が引き受ける [IAM ロールを作成し](attaching-a-data-source.md#aws-appsync-getting-started-build-a-schema-from-scratch)、次のポリシーをアタッチします。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Action": [
                "appsync:ListGraphqlApis"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}
```

------

次に、 AWS AppSync の HTTP データソースを作成します。この例では、米国西部 (オレゴン) リージョンで AWS AppSync を呼び出します。`http.json` という名前のファイルで、署名リージョンとサービス名を含む以下の HTTP 設定を定義します。

```
{
    "endpoint": "https://appsync.us-west-2.amazonaws.com/",
    "authorizationConfig": {
        "authorizationType": "AWS_IAM",
        "awsIamConfig": {
            "signingRegion": "us-west-2",
            "signingServiceName": "appsync"
        }
    }
}
```

次に、 を使用して AWS CLI 、次のように関連付けられたロールを持つデータソースを作成します。

```
aws appsync create-data-source --api-id <API-ID> \
                               --name AWSAppSync \
                               --type HTTP \
                               --http-config file:///http.json \
                               --service-role-arn <ROLE-ARN>
```

リゾルバーをスキーマの フィールドにアタッチする場合は、次のリクエストマッピングテンプレートを使用して AWS AppSync を呼び出します。

```
{
    "version": "2018-05-29",
    "method": "GET",
    "resourcePath": "/v1/apis"
}
```

このデータソースに対して GraphQL クエリを実行すると、 AWS AppSync は指定したロールを使用してリクエストに署名し、リクエストに署名を含めます。クエリは、その AWS リージョンのアカウントの AWS AppSync GraphQL APIsのリストを返します。

# での Aurora Serverless v2 の使用 AWS AppSync
<a name="tutorial-rds-resolvers"></a>

を使用して GraphQL API を Aurora Serverless データベースに接続します AWS AppSync。この統合により、GraphQL クエリ、ミューテーション、サブスクリプションを通じて SQL ステートメントを実行できます。これにより、リレーショナルデータを柔軟に操作できます。

**注記**  
このチュートリアルでは、`US-EAST-1` リージョンを使用しています。

**利点**
+ GraphQL とリレーショナルデータベースのシームレスな統合
+ GraphQL インターフェイスを使用して SQL オペレーションを実行する機能
+ Aurora Serverless v2 によるサーバーレスのスケーラビリティ
+  AWS Secrets Manager によるデータアクセスの保護
+ 入力のサニタイズによる SQL インジェクションに対する保護
+ フィルタリングや範囲操作などの柔軟なクエリ機能

**一般的なユースケース**
+ リレーショナルデータ要件を使用したスケーラブルなアプリケーションの構築
+ GraphQL の柔軟性と SQL データベース機能の両方を必要とする API の作成
+ GraphQL ミューテーションとクエリによるデータオペレーションの管理
+ セキュアなデータベースアクセスパターンの実装

このチュートリアルでは、以下について説明します。
+ Aurora Serverless v2 クラスターを設定する
+ Data API 機能を有効にする
+ データベース構造の作成と設定
+ データベースオペレーションの GraphQL スキーマを定義する
+ クエリとミューテーションのリゾルバーを実装する
+ 適切な入力サニタイズによりデータアクセスを保護する
+ GraphQL インターフェイスを使用してさまざまなデータベースオペレーションを実行する

**Topics**
+ [データベースクラスターを設定する](#create-cluster)
+ [Data API を有効にする](#enable-data-api)
+ [データベースとテーブルを作成する](#create-database-and-table)
+ [GraphQL スキーマ](#graphql-schema)
+ [API をデータベースオペレーションに接続する](#configuring-resolvers)
+ [API を使用してデータを変更する](#run-mutations)
+ [データを取得する](#run-queries)
+ [セキュアなデータアクセス](#input-sanitization)

## データベースクラスターを設定する
<a name="create-cluster"></a>

Amazon RDS データソースを に追加する前に AWS AppSync、まず Aurora Serverless v2 クラスターで Data API を有効にし**、 を使用してシークレットを設定**する必要があります*AWS Secrets Manager*。 AWS CLIを使用して Aurora Serverless v2 クラスターを作成することができます。

```
aws rds create-db-cluster \
    --db-cluster-identifier appsync-tutorial \
    --engine aurora-mysql \
    --engine-version 8.0 \
    --serverless-v2-scaling-configuration MinCapacity=0,MaxCapacity=1 \
    --master-username USERNAME \
    --master-user-password COMPLEX_PASSWORD \
    --enable-http-endpoint
```

これにより、クラスターの ARN が返されます。

クラスターを作成したら、次のコマンドを使用して Aurora Serverless v2 インスタンスを追加する必要があります。

```
aws rds create-db-instance \
    --db-cluster-identifier appsync-tutorial \
    --db-instance-identifier appsync-tutorial-instance-1 \
    --db-instance-class db.serverless \
    --engine aurora-mysql
```

**注記**  
これらのエンドポイントのアクティブ化には時間がかかります。ステータスは、クラスターの **[接続とセキュリティ]** タブの Amazon RDS コンソールで確認できます。次の AWS CLI コマンドを使用して、クラスターのステータスを確認することもできます。  

```
aws rds describe-db-clusters \
    --db-cluster-identifier appsync-tutorial \
    --query "DBClusters[0].Status"
```

 AWS Secrets Manager コンソールまたは AWS CLI を使用してシー*クレット*を作成し、前のステップ`COMPLEX_PASSWORD`の `USERNAME`と を使用して次のような入力ファイルを作成できます。

```
{
    "username": "USERNAME",
    "password": "COMPLEX_PASSWORD"
}
```

これをパラメータとして に渡します AWS CLI。

```
aws secretsmanager create-secret --name HttpRDSSecret --secret-string file://creds.json --region us-east-1
```

これにより、シークレットの ARN が返されます。

 Aurora Serverless クラスターとシークレットの **ARN をメモ**しておいてください。これらの ARN は後でデータソースを作成するときに AppSync コンソールで使用します。

## Data API を有効にする
<a name="enable-data-api"></a>

[RDS のドキュメントの指示に従う](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html)ことで、クラスターで Data API を有効にできます。Data API は AppSync データソースとして追加する前に有効にする必要があります。

## データベースとテーブルを作成する
<a name="create-database-and-table"></a>

Data API を有効にしたら、 AWS CLIの `aws rds-data execute-statement` コマンドで使用できるようになります。これにより、Aurora Serverless クラスターが正しく設定されていることを AppSync API への追加前に確認できます。まず、`--sql` のようなパラメータで *TESTDB* というデータベースを作成します。

```
aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:123456789000:cluster:http-endpoint-test" \
--schema "mysql"  --secret-arn "arn:aws:secretsmanager:us-east-1:123456789000:secret:testHttp2-AmNvc1"  \
--region us-east-1 --sql "create DATABASE TESTDB"
```

これがエラーなしで実行されたら、*create table* コマンドを使用してテーブルを追加します。

```
aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:123456789000:cluster:http-endpoint-test" \
 --schema "mysql"  --secret-arn "arn:aws:secretsmanager:us-east-1:123456789000:secret:testHttp2-AmNvc1" \
 --region us-east-1 \
 --sql "create table Pets(id varchar(200), type varchar(200), price float)" --database "TESTDB"
```

すべてが問題なく実行されたら、AppSync API のデータソースとしてクラスターを追加する手順に進むことができます。

## GraphQL スキーマ
<a name="graphql-schema"></a>

Aurora Serverless Data API がテーブルで起動されて実行中になったところで、次は GraphQL スキーマを作成し、ミューテーションとサブスクリプションを実行するためのリゾルバーをアタッチします。 AWS AppSync コンソールで新しい API を作成し、**スキーマ**ページに移動して、次のように入力します。

```
type Mutation {
    createPet(input: CreatePetInput!): Pet
    updatePet(input: UpdatePetInput!): Pet
    deletePet(input: DeletePetInput!): Pet
}

input CreatePetInput {
    type: PetType
    price: Float!
}

input UpdatePetInput {
id: ID!
    type: PetType
    price: Float!
}

input DeletePetInput {
    id: ID!
}

type Pet {
    id: ID!
    type: PetType
    price: Float
}

enum PetType {
    dog
    cat
    fish
    bird
    gecko
}

type Query {
    getPet(id: ID!): Pet
    listPets: [Pet]
    listPetsByPriceRange(min: Float, max: Float): [Pet]
}

schema {
    query: Query
    mutation: Mutation
}
```

 スキーマを**保存**し、[**データソース**] ページに移動して、新しいデータソースを作成します。データソースタイプとして [**Relational database (リレーショナルデータベース)**] を選択し、データソース名としてわかりやすい名前を入力します。前回の手順で作成したデータベースの名前と、そのデータベースを作成したクラスターの**クラスター ARN** を使用します。[**Role (ロール)**] では、AppSync により新しいロールを作成するか、以下のようなポリシーによりロールを作成できます。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "rds-data:BatchExecuteStatement",
                "rds-data:BeginTransaction",
                "rds-data:CommitTransaction",
                "rds-data:ExecuteStatement",
                "rds-data:RollbackTransaction"
            ],
            "Resource": [
                "arn:aws:rds:us-east-1:111122223333:cluster:mydbcluster",
                "arn:aws:rds:us-east-1:111122223333:cluster:mydbcluster:*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetSecretValue"
            ],
            "Resource": [
            "arn:aws:secretsmanager:us-east-1:111122223333:secret:mysecret",
            "arn:aws:secretsmanager:us-east-1:111122223333:secret:mysecret:*"
            ]
        }
    ]
}
```

------

このポリシーには、ロールにアクセス許可を付与する 2 つの**ステートメント**があります。最初の**リソース**は Aurora Serverless クラスターで、2 番目のリソースは AWS Secrets Manager ARN です。**作成**をクリックする前に AppSync データソース構成内の**両方**の ARN を指定する必要があります。

これをパラメータとして に渡します AWS CLI。

```
aws secretsmanager create-secret \
  --name HttpRDSSecret \
  --secret-string file://creds.json \
  --region us-east-1
```

これにより、シークレットの ARN が返されます。 AWS AppSync コンソールでデータソースを作成するときは、Aurora Serverless クラスターの ARN と、後で のシークレットを書き留めます。

### データベース構造を構築する
<a name="create-database-and-table"></a>

Data API を有効にしたら、 AWS CLIの `aws rds-data execute-statement` コマンドで使用できるようになります。これにより、API に追加する前に Aurora Serverless v2 クラスターが正しく設定されます AWS AppSync 。まず、次のように `--sql` パラメータを使用して *TESTDB* というデータベースを作成します。

```
aws rds-data execute-statement \
                --resource-arn "arn:aws:rds:us-east-1:111122223333:cluster:appsync-tutorial" \
                --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333:secret:appsync-tutorial-rds-secret"  \
                --region us-east-1 \
                --sql "create DATABASE TESTDB"
```

これがエラーなしで実行されたら、次の *create table* コマンドを使用してテーブルを追加します。

```
aws rds-data execute-statement \
      --resource-arn "arn:aws:rds:us-east-1:111122223333:cluster:http-endpoint-test" \
      --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333:secret:testHttp2-AmNvc1" \
      --region us-east-1 \
      --sql "create table Pets(id varchar(200), type varchar(200), price float)" \
      --database "TESTDB"
```

### API インターフェイスを設計する
<a name="graphql-schema"></a>

Aurora Serverless v2 Data API がテーブルを使用して実行中になった後、GraphQL スキーマを作成し、ミューテーションとサブスクリプションを実行するためのリゾルバーをアタッチします。 AWS AppSync コンソールで新しい API を作成し、コンソールの**スキーマ**ページに移動して、次のように入力します。

```
type Mutation {
        createPet(input: CreatePetInput!): Pet
        updatePet(input: UpdatePetInput!): Pet
        deletePet(input: DeletePetInput!): Pet
    }
    
    input CreatePetInput {
        type: PetType
        price: Float!
    }
    
    input UpdatePetInput {
        id: ID!
        type: PetType
        price: Float!
    }
    
    input DeletePetInput {
        id: ID!
    }
    
    type Pet {
        id: ID!
        type: PetType
        price: Float
    }
    
    enum PetType {
        dog
        cat
        fish
        bird
        gecko
    }
    
    type Query {
        getPet(id: ID!): Pet
        listPets: [Pet]
        listPetsByPriceRange(min: Float, max: Float): [Pet]
    }
    
    schema {
        query: Query
        mutation: Mutation
    }
```

 スキーマを**保存**し、[**データソース**] ページに移動して、新しいデータソースを作成します。**[データソース]** タイプとして **[リレーショナルデータベース]** を選択し、わかりやすい名前を入力します。前回の手順で作成したデータベースの名前と、そのデータベースを作成したクラスターの**クラスター ARN** を使用します。**ロール**の場合、 に AWS AppSync 新しいロールを作成するか、次のようなポリシーで作成させることができます。

------
#### [ JSON ]

****  

```
{
        "Version":"2012-10-17",		 	 	 
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "rds-data:BatchExecuteStatement",
                    "rds-data:BeginTransaction",
                    "rds-data:CommitTransaction",
                    "rds-data:ExecuteStatement",
                    "rds-data:RollbackTransaction"
                ],
                "Resource": [
                    "arn:aws:rds:us-east-1:111122223333:cluster:mydbcluster",
                    "arn:aws:rds:us-east-1:111122223333:cluster:mydbcluster:*"
                ]
            },
            {
                "Effect": "Allow",
                "Action": [
                    "secretsmanager:GetSecretValue"
                ],
                "Resource": [
                "arn:aws:secretsmanager:us-east-1:111122223333:secret:mysecret",
                "arn:aws:secretsmanager:us-east-1:111122223333:secret:mysecret:*"
                ]
            }
        ]
    }
```

------

このポリシーには、ロールにアクセス許可を付与する 2 つの**ステートメント**があります。最初の**リソース**は Aurora Serverless v2 クラスターで、2 つ目は AWS Secrets Manager ARN です。Create をクリックする前に、 AWS AppSync データソース設定で**両方の** ARNs ****を指定する必要があります。

## API をデータベースオペレーションに接続する
<a name="configuring-resolvers"></a>

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

1. *Mutation.createPet* フィールドを使用してペットを作成する

1. *Mutation.updatePet* フィールドを使用してペットを更新する

1. *Mutation.deletePet* フィールドを使用してペットを削除する

1. *Query.getPet* フィールドを使用して 1 つのペットを取得する

1. *Query.listPets* フィールドを使用してすべてのペットを一覧表示する

1. *Query.listPetsByPriceRange* フィールドを使用してペットを価格帯別に一覧表示する

### Mutation.createPet
<a name="mutation-createpet"></a>

 AWS AppSync コンソールのスキーマエディタから、右側の「 **のリゾルバーをア**タッチする」を選択します`createPet(input: CreatePetInput!): Pet`。[RDS データソース] を選択します。**[リクエストマッピングテンプレート]** セクションで、以下のテンプレートを追加します。

```
#set($id=$utils.autoId())
{
"version": "2018-05-29",
    "statements": [
        "insert into Pets VALUES (:ID, :TYPE, :PRICE)",
        "select * from Pets WHERE id = :ID"
    ],
    "variableMap": {
        ":ID": "$ctx.args.input.id",
        ":TYPE": $util.toJson($ctx.args.input.type),
        ":PRICE": $util.toJson($ctx.args.input.price)
    }
}
```

SQL ステートメントは、**statements** 配列内での順序に基づいて順番に実行されます。結果はその同じ順序で返されます。これはミューテーションなので、*挿入*の後に*選択*ステートメントを実行して、GraphQL レスポンスマッピングテンプレートに入力するための、コミットされた値を取得します。

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

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])
```

*ステートメント*には 2 つの SQL クエリがあるため、データベースから返される行列の 2 番目の結果を `$utils.rds.toJsonString($ctx.result))[1][0])` で指定する必要があります。

### Mutation.updatePet
<a name="mutation-updatepet"></a>

 AWS AppSync コンソールのスキーマエディタから、 の**リゾルバーをア**タッチを選択します`updatePet(input: UpdatePetInput!): Pet`。**[RDS データソース]** を選択します。**[リクエストマッピングテンプレート]** セクションで、以下のテンプレートを追加します。

```
{
"version": "2018-05-29",
    "statements": [
        $util.toJson("update Pets set type=:TYPE, price=:PRICE WHERE id=:ID"),
        $util.toJson("select * from Pets WHERE id = :ID")
    ],
    "variableMap": {
        ":ID": "$ctx.args.input.id",
        ":TYPE": $util.toJson($ctx.args.input.type),
        ":PRICE": $util.toJson($ctx.args.input.price)
    }
}
```

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

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])
```

### Mutation.deletePet
<a name="mutation-deletepet"></a>

 AWS AppSync コンソールのスキーマエディタから、 の**リゾルバーをア**タッチを選択します`deletePet(input: DeletePetInput!): Pet`。**[RDS データソース]** を選択します。**[リクエストマッピングテンプレート]** セクションで、以下のテンプレートを追加します。

```
{
"version": "2018-05-29",
    "statements": [
        $util.toJson("select * from Pets WHERE id=:ID"),
        $util.toJson("delete from Pets WHERE id=:ID")
    ],
    "variableMap": {
        ":ID": "$ctx.args.input.id"
    }
}
```

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

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])
```

### Query.getPet
<a name="query-getpet"></a>

スキーマに対してミューテーションが作成されたところで、3 つのクエリを接続して、個々の項目、リストを取得し、SQL フィルタを適用する方法を紹介します。 AWS AppSync コンソールの**スキーマエディタ**から、 の**リゾルバーをア**タッチを選択します`getPet(id: ID!): Pet`。**[RDS データソース]** を選択します。**[リクエストマッピングテンプレート]** セクションで、以下のテンプレートを追加します。

```
{
"version": "2018-05-29",
        "statements": [
            $util.toJson("select * from Pets WHERE id=:ID")
    ],
    "variableMap": {
        ":ID": "$ctx.args.id"
    }
}
```

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

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])
```

### Query.listPets
<a name="query-listpets"></a>

 AWS AppSync コンソールのスキーマエディタから、右側の「 **のリゾルバーをア**タッチする」を選択します`getPet(id: ID!): Pet`。**[RDS データソース]** を選択します。**[リクエストマッピングテンプレート]** セクションで、以下のテンプレートを追加します。

```
{
    "version": "2018-05-29",
    "statements": [
        "select * from Pets"
    ]
}
```

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

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])
```

### Query.listPetsByPriceRange
<a name="query-listpetsbypricerange"></a>

 AWS AppSync コンソールのスキーマエディタから、右側の「 **のリゾルバーをア**タッチする」を選択します`getPet(id: ID!): Pet`。**[RDS データソース]** を選択します。**[リクエストマッピングテンプレート]** セクションで、以下のテンプレートを追加します。

```
{
    "version": "2018-05-29",
    "statements": [
            "select * from Pets where price > :MIN and price < :MAX"
    ],

    "variableMap": {
        ":MAX": $util.toJson($ctx.args.max),
        ":MIN": $util.toJson($ctx.args.min)
    }
}
```

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

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])
```

## API を使用してデータを変更する
<a name="run-mutations"></a>

すべてのリゾルバーを SQL ステートメントで設定し、GraphQL API を Serverless Aurora Data API に接続したところで、ミューテーションとクエリの実行を開始できます。 AWS AppSync コンソールで、**クエリ**タブを選択し、次のように入力してペットを作成します。

```
mutation add {
    createPet(input : { type:fish, price:10.0 }){
        id
        type
        price
    }
}
```

レスポンスには、*id*、*type*、*price* が含まれています。

```
{
  "data": {
    "createPet": {
      "id": "c6fedbbe-57ad-4da3-860a-ffe8d039882a",
      "type": "fish",
      "price": "10.0"
    }
  }
}
```

この項目は *updatePet* ミューテーションを実行することで変更できます。

```
mutation update {
    updatePet(input : {
        id: ID_PLACEHOLDER,
        type:bird,
        price:50.0
    }){
        id
        type
        price
    }
}
```

事前の*createPet*オペレーションから返された*id*を使用したことに注意してください。リゾルバーが `$util.autoId()` を利用したため、これがレコードに固有の値になります。同様の方法でレコードを削除できます。

```
mutation delete {
    deletePet(input : {id:ID_PLACEHOLDER}){
        id
        type
        price
    }
}
```

最初のミューテーションで *price* に異なる値を使用してレコードをいくつか作成したら、クエリをいくつか実行します。

## データを取得する
<a name="run-queries"></a>

引き続きコンソールの **[クエリ]** タブで、以下のステートメントを使用して、作成したすべてのレコードを一覧表示します。

```
query allpets {
    listPets {
        id
        type
        price
    }
}
```

次の GraphQL クエリを使用して、*Query.listPetsByPriceRange* のマッピングテンプレートに `where price > :MIN and price < :MAX` があった、SQL の *WHERE* 述語を活用します。

```
query petsByPriceRange {
    listPetsByPriceRange(min:1, max:11) {
        id
        type
        price
    }
}
```

*price* が 1 ドル以上、10 ドル未満のレコードのみが表示されます。最後に、以下のようにクエリを実行して個々のレコードを取得できます。

```
query onePet {
    getPet(id:ID_PLACEHOLDER){
        id
        type
        price
    }
}
```

## セキュアなデータアクセス
<a name="input-sanitization"></a>

SQL インジェクションは、データベースアプリケーションのセキュリティ脆弱性を利用して行われます。これは、攻撃者がユーザー入力フィールドを通じて悪意のある SQL コードを挿入した場合に発生します。これにより、データベースデータへの不正アクセスが可能になります。`variableMap` を使用して SQL インジェクション攻撃から保護する前に、すべてのユーザー入力を慎重に検証してサニタイズすることをお勧めします。変数マップを使用しない場合、GraphQL 操作の引数をサニタイズする責任があります。そのための 1 つの方法は、Data API に対して SQL ステートメントを実行する前に、リクエストマッピングテンプレートに入力固有の検証手順を提供することです。`listPetsByPriceRange` 例のリクエストマッピングテンプレートを変更する方法を見てみましょう。ユーザー入力だけに頼るのではなく、以下のことが可能です。

```
#set($validMaxPrice = $util.matches("\d{1,3}[,\\.]?(\\d{1,2})?",$ctx.args.maxPrice))

#set($validMinPrice = $util.matches("\d{1,3}[,\\.]?(\\d{1,2})?",$ctx.args.minPrice))


#if (!$validMaxPrice || !$validMinPrice)
    $util.error("Provided price input is not valid.")
#end
{
    "version": "2018-05-29",
    "statements": [
            "select * from Pets where price > :MIN and price < :MAX"
    ],

    "variableMap": {
        ":MAX": $util.toJson($ctx.args.maxPrice),
        ":MIN": $util.toJson($ctx.args.minPrice)
    }
}
```

Data API に対してリゾルバーを実行するときに不正な入力から保護するもう 1 つの方法は、プリペアードステートメントをストアドプロシージャおよびパラメータ化された入力と共に使用することです。例えば、`listPets` のリゾルバーで、*select* をプリペアードステートメントとして実行する以下のプロシージャを定義します。

```
CREATE PROCEDURE listPets (IN type_param VARCHAR(200))
  BEGIN
     PREPARE stmt FROM 'SELECT * FROM Pets where type=?';
     SET @type = type_param;
     EXECUTE stmt USING @type;
     DEALLOCATE PREPARE stmt;
  END
```

これは、Aurora Serverless v2 インスタンスで作成します。

```
aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:xxxxxxxxxxxx:cluster:http-endpoint-test" \
--schema "mysql"  --secret-arn "arn:aws:secretsmanager:us-east-1:xxxxxxxxxxxx:secret:httpendpoint-xxxxxx"  \
--region us-east-1  --database "DB_NAME" \
--sql "CREATE PROCEDURE listPets (IN type_param VARCHAR(200)) BEGIN PREPARE stmt FROM 'SELECT * FROM Pets where type=?'; SET @type = type_param; EXECUTE stmt USING @type; DEALLOCATE PREPARE stmt; END"
```

その結果、listPets のリゾルバーコードは、ストアドプロシージャを呼び出すシンプルなものになりました。少なくとも、文字列入力では一重引用符を[エスケープ](#escaped)する必要があります。

```
#set ($validType = $util.isString($ctx.args.type) && !$util.isNullOrBlank($ctx.args.type))
#if (!$validType)
    $util.error("Input for 'type' is not valid.", "ValidationError")
#end

{
    "version": "2018-05-29",
    "statements": [
        "CALL listPets(:type)"
    ]
    "variableMap": {
        ":type": $util.toJson($ctx.args.type.replace("'", "''"))
    }
}
```

### エスケープ文字列の使用
<a name="escaped"></a>

`'some string value'` のように、一重引用符を使用して、SQL ステートメントの文字列リテラルの開始と終了を表します。1 つ以上の一重引用符 (`'`) を含む文字列値を文字列内で使用するには、それぞれを 2 つの一重引用符 (`''`) に置き換える必要があります。例えば、入力文字列が `Nadia's dog` の場合、SQL ステートメントでは次のようにエスケープします。

```
update Pets set type='Nadia''s dog' WHERE id='1'
```

# AWS AppSync でのパイプラインリゾルバーの使用
<a name="tutorial-pipeline-resolvers"></a>

**注記**  
現在、主に APPSYNC\$1JS ランタイムとそのドキュメントをサポートしています。[こちら](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html) で APPSYNC\$1JS ランタイムとそのガイドの使用をご検討ください。

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

## ワンクリックでのセットアップ
<a name="one-click-setup"></a>

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

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/pipeline/pipeline-resolvers-full.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/pipeline/pipeline-resolvers-full.yaml)

このスタックはお客様のアカウントに以下のリソースを作成します。
+ アカウントのリソースにアクセスするための 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 メールには、 AWS AppSync コンソールへの Amazon Cognito ユーザーとしてログインするために使用する一時パスワードが含まれています。これらのパスワードを保存し、チュートリアルの残りで使用します。

## 手動セットアップ
<a name="manual-setup"></a>

 AWS AppSync コンソールを使用してstep-by-stepのプロセスを手動で実行する場合は、以下のセットアッププロセスに従います。

### 非 AWS AppSync リソースのセットアップ
<a name="setting-up-your-non-aws-appsync-resources"></a>

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

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/pipeline/pipeline-resolvers-resources-only.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/pipeline/pipeline-resolvers-resources-only.yaml)

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

### GraphQL API の作成
<a name="creating-your-graphql-api"></a>

 AWS AppSync で GraphQL API を作成するには:

1.  AWS AppSync コンソールを開き、**Build From Scratch** を選択し、**Start** を選択します。

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

1. **[作成]** を選択します。

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

### GraphQL API の設定
<a name="configuring-the-graphql-api"></a>

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

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

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

1. [**ユーザープールの設定**] で、*AWS リージョン*に**US-WEST-2**を選択します。

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

1. *デフォルトアクション*として**拒否**を選択します。

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

1. **[保存]** を選択します。

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

### DynamoDB テーブル用のデータソースの設定
<a name="configuring-data-sources-for-the-ddb-tables"></a>

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

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

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

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

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

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

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

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

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

1. **[作成]** を選択します。

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

### GraphQL スキーマの作成
<a name="creating-the-graphql-schema"></a>

データソースが 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\$1auth* ディレクティブで注釈が付けられています。API のデフォルトのアクション設定は [*拒否*] に設定されているため、API は *@aws\$1auth* ディレクティブ内で指定されているグループのメンバーではないすべてのユーザーを拒否します。API の保護方法の詳細については、「[セキュリティ](security-authz.md#aws-appsync-security)」ページを参照してください。この場合、管理者ユーザーのみが *Mutation.createPicture* および *Mutation.createFriendship* フィールドにアクセスできま、*Admins* または *Viewers* グループのメンバーのユーザーは *Query.getPicturesByOwner* フィールドにアクセスできます。他のすべてのユーザーはアクセスできません。

### リゾルバーの設定
<a name="configuring-resolvers"></a>

有効な GraphQL スキーマと 2 つのデータソースを用意できたところで、スキーマでリゾルバーを GraphQL フィールドにアタッチできます。API は以下の機能を提供します。
+ *Mutation.createPicture* フィールドで写真を作成する
+ *Mutation.createFriendship* フィールドで友人関係を作成する
+ *Query.getPicture* フィールドで写真を取得する

#### Mutation.createPicture
<a name="mutation-createpicture"></a>

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

```
#set($id = $util.autoId())

{
    "version" : "2018-05-29",

    "operation" : "PutItem",

    "key" : {
        "id" : $util.dynamodb.toDynamoDBJson($id),
        "owner": $util.dynamodb.toDynamoDBJson($ctx.args.input.owner)
    },

    "attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.input)
}
```

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

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

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

#### Mutation.createFriendship
<a name="mutation-createfriendship"></a>

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

```
#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 のベストプラクティス](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-adjacency-graphs.html)」を参照してください。

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

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

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

#### Query.getPicturesByOwner
<a name="query-getpicturesbyowner"></a>

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

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

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

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

1. GetPicturebyOwner 関数: *owner-index*グローバルセカンダリインデックスの DynamoDB クエリ操作を使用して、ピクチャテーブルから画像を取得します。

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

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

##### isFriend 関数
<a name="isfriend-function"></a>

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

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

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

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

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

   ```
   #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" : $util.dynamodb.toDynamoDBJson($callerId),
           "friendId" : $util.dynamodb.toDynamoDBJson($ownerId)
       }
   }
   ```

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

   ```
   #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)
   ```

1. [**関数の作成**] を選択します。

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

##### getPicturesByOwner 関数
<a name="getpicturesbyowner-function"></a>

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

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

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

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

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

   ```
   {
       "version" : "2018-05-29",
   
       "operation" : "Query",
   
       "query" : {
           "expression": "#owner = :owner",
           "expressionNames": {
               "#owner" : "owner"
           },
           "expressionValues" : {
               ":owner" : $util.dynamodb.toDynamoDBJson($ctx.prev.result.owner)
           }
       },
   
       "index": "owner-index"
   }
   ```

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

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

1. [**関数の作成**] を選択します。

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

 AWS AppSync コンソールのスキーマエディタから、右側の「 **のリゾルバーをア**タッチする」を選択します`Query.getPicturesByOwner(id: ID!): [Picture]`。以下のページで、データソースのドロップダウンリストの下に表示される [**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 をテストする
<a name="testing-your-graphql-api"></a>

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

### createPicture ミューテーション
<a name="createpicture-mutation"></a>

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

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

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

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

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

1. [**ログイン**] を選択します。これで、ボタンの名前が **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 ミューテーション
<a name="createfriendship-mutation"></a>

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

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

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

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

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

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

### getPicturesByOwner クエリ
<a name="getpicturesbyowner-query"></a>

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

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

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

```
{
  "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 (設定)**] ペインで)、デバッグレベルを [**すべて**] に設定し、同じクエリをもう一度実行すると、フィールド実行のログが返されます。ログを見ることで、**リクエストマッピングテンプレート**ステージで**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* キーは、*\$1return* ディレクティブによって返されたデータを表します。

最後に、**rex** は **Viewers** 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"
    }
  ]
}
```

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

# のバージョニングされたデータソースでの Delta Sync オペレーションの使用 AWS AppSync
<a name="tutorial-delta-sync"></a>

**注記**  
現在、主に APPSYNC\$1JS ランタイムとそのドキュメントをサポートしています。[こちら](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html) で APPSYNC\$1JS ランタイムとそのガイドの使用をご検討ください。

 AWS AppSync のクライアントアプリケーションは、GraphQL レスポンスをモバイル/ウェブアプリケーションのディスクにローカルにキャッシュすることでデータを保存します。バージョン管理されたデータソースと `Sync` オペレーションにより、お客様は単一のリゾルバーを使用して同期プロセスを実行することができます。これにより、クライアントは大量のレコードを含む可能性のある基本クエリでのローカルキャッシュをハイドレートし、最後のクエリ以降に変更されたデータのみを受信できます (*差分更新*)。クライアントが初期リクエストでキャッシュのベースハイドレートを実行し、別のリクエストでの増分更新を実行できるようにすることで、その計算をクライアントアプリケーションからバックエンドに移動できます。これは、頻繁にオンライン状態とオフラインの状態を切り替えるクライアントアプリケーションにとって効率が大きく向上します。

Delta Sync を実装するため、`Sync` クエリはバージョン管理されたデータソースで `Sync` オペレーションを使用します。 AWS AppSync ミューテーションがバージョニングされたデータソースの項目を変更すると、その変更の記録も *Delta* テーブルに保存されます。バージョン管理された他のデータソースに異なる *Delta* テーブル (タイプごとに 1 つ、ドメイン領域ごとに 1 つなど) を使用するか、API に 1 つの *Delta* テーブルを使用するかを選択できます。 AWS AppSync では、プライマリキーの衝突を避けるためAPIs に 1 つの *Delta* テーブルを使用することをお勧めします。

さらに、Delta Sync クライアントはサブスクリプションを引数として受け取ることもでき、オフラインからオンラインへの移行間でサブスクリプションの再接続と書き込みを調整します。そのために、Delta Sync はサブスクリプション (さまざまなネットワークエラーシナリオでの、エクスポネンシャルバックオフや、ジッターを伴う再試行など) を自動的に再開し、イベントをキューに保存します。その後、該当する差分クエリまたは基本クエリを実行してから、キュー内のイベントをマージし、最後に通常どおりにサブスクリプションを処理します。

Amplify DataStore などのクライアント設定オプションのドキュメントは、[Amplify Amplify Frameworkのウェブサイト](https://aws-amplify.github.io/)で入手できます。このドキュメントは、最適なデータアクセスのために Delta Sync クライアントと連携するように、バージョン管理された DynamoDB データソースと `Sync` オペレーションをセットアップする方法について概説しています。

## ワンクリックでのセットアップ
<a name="one-click-setup"></a>

すべてのリゾルバーと必要な AWS リソースが設定された状態で、 AWS AppSync で GraphQL エンドポイントを自動的にセットアップするには、次の AWS CloudFormation テンプレートを使用します。

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/deltasync/deltasync-v2-full.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/deltasync/deltasync-v2-full.yaml) 

このスタックはお客様のアカウントに以下のリソースを作成します。
+ 2 つの DynamoDB テーブル (基本と差分)
+ 1API キーを使用した AWS AppSync API
+ DynamoDB テーブルのポリシーを割り当てた 1 つの IAM ロール

2 つのテーブルは、クライアントがオフラインのときに取得できなかったイベントのジャーナルとして機能する 2 番目のテーブルに、同期クエリを分割するために使用されます。差分テーブルに対するクエリが以降も効率的になるように、必要に応じてイベントの自動整理に [Amazon DynamoDB TTL](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html) を使用します。TTL 時間は、データソースのニーズに合わせて設定できます (1 時間、1 日など)。

## Schema
<a name="schema"></a>

Delta Sync を実証するために、サンプルアプリケーションは DynamoDB の *Base* テーブルと *Delta* テーブルにバックアップされた *Posts* スキーマを作成します。 AWS AppSync はミューテーションを両方のテーブルに自動的に書き込みます。同期クエリは必要に応じて、*ベース*テーブルまたは*差分*テーブルからレコードをプルします。また、1 つのサブスクリプションは、クライアントが再接続ロジックでこのテーブルをどのように活用できるかを定義します。

```
input CreatePostInput {
    author: String!
    title: String!
    content: String!
    url: String
    ups: Int
    downs: Int
    _version: Int
}

interface Connection {
  nextToken: String
  startedAt: AWSTimestamp!
}

type Mutation {
    createPost(input: CreatePostInput!): Post
    updatePost(input: UpdatePostInput!): Post
    deletePost(input: DeletePostInput!): Post
}

type Post {
    id: ID!
    author: String!
    title: String!
    content: String!
    url: AWSURL
    ups: Int
    downs: Int
    _version: Int
    _deleted: Boolean
    _lastChangedAt: AWSTimestamp!
}

type PostConnection implements Connection {
    items: [Post!]!
    nextToken: String
    startedAt: AWSTimestamp!
}

type Query {
    getPost(id: ID!): Post
    syncPosts(limit: Int, nextToken: String, lastSync: AWSTimestamp): PostConnection!
}

type Subscription {
    onCreatePost: Post
        @aws_subscribe(mutations: ["createPost"])
    onUpdatePost: Post
        @aws_subscribe(mutations: ["updatePost"])
    onDeletePost: Post
        @aws_subscribe(mutations: ["deletePost"])
}

input DeletePostInput {
    id: ID!
    _version: Int!
}

input UpdatePostInput {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int
    downs: Int
    _version: Int!
}

schema {
    query: Query
    mutation: Mutation
    subscription: Subscription
}
```

GraphQL スキーマは標準的なものですが、先に進む前に注意することがいくつかあります。最初に、すべてのミューテーションは自動的に*ベース*テーブルに書き込み、次に*差分*テーブルに書き込みます。*基本*テーブルは、実際の一元的なソースであり、一方、*差分*テーブルはジャーナルです。`lastSync: AWSTimestamp` を渡さない場合、`syncPosts` クエリは*ベース*テーブルに対して実行され、キャッシュがハイドレートされるだけでなく、*グローバルキャッチアッププロセス*としても定期的に実行されます。これは、*デルタ*テーブルで設定された TTL 時間より長くクライアントがオフラインになっているエッジケースに当てはまります。`lastSync: AWSTimestamp` を渡す場合、`syncPosts` クエリは*差分*テーブルに対して実行されます。クライアントはこのクエリを使用して、最後のオフライン以降に変更されたイベントを取得します。Amplify クライアントは自動的に `lastSync: AWSTimestamp` 値を渡し、適切にディスクに保持します。

*POST* 上の *\$1deleted* フィールドは、**削除**に使用します。クライアントがオフラインになり、レコードが*基本*テーブルから削除されると、この属性は、同期中のクライアントにローカルキャッシュから項目をエビクションするように通知します。クライアントが長期間オフラインであり、クライアントが Delta Sync クエリでこの値を取得する前に項目が削除された場合は、基本クエリのグローバルキャッチアップイベント (クライアントで設定可能) が実行され、キャッシュからその項目が削除されます。このフィールドがオプションとしてマークされているのは、同期クエリの実行時に、削除された項目がある場合にのみ値を返すためです。

## ミューテーション
<a name="mutations"></a>

すべてのミューテーションについて、 AWS AppSync は*基本*テーブルで標準の Create/Update/Delete オペレーションを実行し、*Delta* テーブルに変更を自動的に記録します。データソースの `DeltaSyncTableTTL` 値を変更することで、レコードを保持する時間を短縮または延長できます。データの速度が速い組織では、この時間を短縮するのが妥当な場合があります。あるいは、クライアントが長期間オフラインになっている場合は、この時間を延長するのが賢明でしょう。

## 同期クエリ
<a name="sync-queries"></a>

*基本*クエリは、`lastSync` 値が指定されていない DynamoDB の同期オペレーションです。多くの組織にとって、基本クエリで十分です。基本クエリは起動時に実行され、以降は定期的に実行されるためです。

*差分クエリ*は、`lastSync` 値が指定された DynamoDB の同期オペレーションです。*差分クエリ*は、クライアントがオフライン状態からオンライン状態に戻るたびに実行されます (基本クエリが定期的にトリガーされていない場合)。クライアントは、データを同期するクエリを最後に正常に実行した時刻を自動的に追跡します。

差分クエリが実行されると、クエリのリゾルバーは `ds_pk` と `ds_sk` を使用して、クライアントが前回同期を実行してから変更されたレコードに対してのみクエリを実行します。クライアントは該当する GraphQL レスポンスを保存します。

同期クエリの実行の詳細については、[同期オペレーションのドキュメント](aws-appsync-conflict-detection-and-sync-sync-operations.md)を参照してください。

## 例
<a name="example"></a>

まず、項目を作成するために `createPost` ミューテーションを呼び出すことから始めましょう。

```
mutation create {
  createPost(input: {author: "Nadia", title: "My First Post", content: "Hello World"}) {
    id
    author
    title
    content
    _version
    _lastChangedAt
    _deleted
  }
}
```

このミューテーションの戻り値は次のようになります。

```
{
  "data": {
    "createPost": {
      "id": "81d36bbb-1579-4efe-92b8-2e3f679f628b",
      "author": "Nadia",
      "title": "My First Post",
      "content": "Hello World",
      "_version": 1,
      "_lastChangedAt": 1574469356331,
      "_deleted": null
    }
  }
}
```

*ベース*テーブルの内容を調べると、次のようなレコードがあります。

```
{
  "_lastChangedAt": {
    "N": "1574469356331"
  },
  "_version": {
    "N": "1"
  },
  "author": {
    "S": "Nadia"
  },
  "content": {
    "S": "Hello World"
  },
  "id": {
    "S": "81d36bbb-1579-4efe-92b8-2e3f679f628b"
  },
  "title": {
    "S": "My First Post"
  }
}
```

*差分*テーブルの内容を調べると、次のようなレコードがあります。

```
{
  "_lastChangedAt": {
    "N": "1574469356331"
  },
  "_ttl": {
    "N": "1574472956"
  },
  "_version": {
    "N": "1"
  },
  "author": {
    "S": "Nadia"
  },
  "content": {
    "S": "Hello World"
  },
  "ds_pk": {
    "S": "AppSync-delta-sync-post:2019-11-23"
  },
  "ds_sk": {
    "S": "00:35:56.331:81d36bbb-1579-4efe-92b8-2e3f679f628b:1"
  },
  "id": {
    "S": "81d36bbb-1579-4efe-92b8-2e3f679f628b"
  },
  "title": {
    "S": "My First Post"
  }
}
```

これで、クライアントが `syncPosts` クエリなどを実行してローカルデータストアをハイドレートするために実行する*基本*クエリをシミュレーションすることができます。

```
query baseQuery {
  syncPosts(limit: 100, lastSync: null, nextToken: null) {
    items {
      id
      author
      title
      content
      _version
      _lastChangedAt
    }
    startedAt
    nextToken
  }
}
```

この*基本*クエリの戻り値は次のようになります。

```
{
  "data": {
    "syncPosts": {
      "items": [
        {
          "id": "81d36bbb-1579-4efe-92b8-2e3f679f628b",
          "author": "Nadia",
          "title": "My First Post",
          "content": "Hello World",
          "_version": 1,
          "_lastChangedAt": 1574469356331
        }
      ],
      "startedAt": 1574469602238,
      "nextToken": null
    }
  }
}
```

*差分*クエリをシミュレーションするために後で `startedAt` 値を保存しますが、まずはテーブルを変更する必要があります。`updatePost` ミューテーションを使って既存の投稿を変更しましょう。

```
mutation updatePost {
  updatePost(input: {id: "81d36bbb-1579-4efe-92b8-2e3f679f628b", _version: 1, title: "Actually this is my Second Post"}) {
    id
    author
    title
    content
    _version
    _lastChangedAt
    _deleted
  }
}
```

このミューテーションの戻り値は次のようになります。

```
{
  "data": {
    "updatePost": {
      "id": "81d36bbb-1579-4efe-92b8-2e3f679f628b",
      "author": "Nadia",
      "title": "Actually this is my Second Post",
      "content": "Hello World",
      "_version": 2,
      "_lastChangedAt": 1574469851417,
      "_deleted": null
    }
  }
}
```

ここで*ベース*テーブルの内容を調べると、更新された項目が表示されます。

```
{
  "_lastChangedAt": {
    "N": "1574469851417"
  },
  "_version": {
    "N": "2"
  },
  "author": {
    "S": "Nadia"
  },
  "content": {
    "S": "Hello World"
  },
  "id": {
    "S": "81d36bbb-1579-4efe-92b8-2e3f679f628b"
  },
  "title": {
    "S": "Actually this is my Second Post"
  }
}
```

ここで*差分*テーブルの内容を調べると、次の 2 つのレコードがあります。

1. 項目が作成されたときのレコード

1. 項目が更新されたときのレコード

新しい項目は次のようになります。

```
{
  "_lastChangedAt": {
    "N": "1574469851417"
  },
  "_ttl": {
    "N": "1574473451"
  },
  "_version": {
    "N": "2"
  },
  "author": {
    "S": "Nadia"
  },
  "content": {
    "S": "Hello World"
  },
  "ds_pk": {
    "S": "AppSync-delta-sync-post:2019-11-23"
  },
  "ds_sk": {
    "S": "00:44:11.417:81d36bbb-1579-4efe-92b8-2e3f679f628b:2"
  },
  "id": {
    "S": "81d36bbb-1579-4efe-92b8-2e3f679f628b"
  },
  "title": {
    "S": "Actually this is my Second Post"
  }
}
```

*差分*クエリをシミュレートして、クライアントがオフラインのときに発生した変更を取得できるようになりました。リクエストを行うために、*基本*クエリから返された値 `startedAt` を使用します。

```
query delta {
  syncPosts(limit: 100, lastSync: 1574469602238, nextToken: null) {
    items {
      id
      author
      title
      content
      _version
    }
    startedAt
    nextToken
  }
}
```

この*差分*クエリの戻り値は次のようになります。

```
{
  "data": {
    "syncPosts": {
      "items": [
        {
          "id": "81d36bbb-1579-4efe-92b8-2e3f679f628b",
          "author": "Nadia",
          "title": "Actually this is my Second Post",
          "content": "Hello World",
          "_version": 2
        }
      ],
      "startedAt": 1574470400808,
      "nextToken": null
    }
  }
}
```