

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

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