

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

# Tutoriales de resolución de VTL para AWS AppSync
<a name="tutorials"></a>

**nota**  
Ahora admitimos de forma básica el tiempo de ejecución APPSYNC\$1JS y su documentación. Considere la opción de utilizar el tiempo de ejecución APPSYNC\$1JS y sus guías [aquí](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html).

Las fuentes de datos y los resolutores se utilizan AWS AppSync para traducir las solicitudes de GraphQL y obtener información de sus recursos. AWS AWS AppSync admite el aprovisionamiento automático y las conexiones con determinados tipos de fuentes de datos. AWS AppSync también admite Amazon DynamoDB AWS Lambda, bases de datos relacionales (Amazon Aurora Serverless), OpenSearch Amazon Service y puntos de enlace HTTP como fuentes de datos. Puedes usar una API de GraphQL con tus AWS recursos existentes o crear fuentes de datos y resolutores desde cero. Las siguientes secciones están destinadas a explicar algunos de los casos de uso más comunes de GraphQL en forma de tutoriales.

AWS AppSync utiliza *plantillas de mapeo* escritas en el lenguaje de plantillas Apache Velocity (VTL) para los resolutores. Para obtener más información sobre el uso de plantillas de mapeo, consulte [Resolver mapping template reference](resolver-mapping-template-reference.md#aws-appsync-resolver-mapping-template-reference). Encontrará más información sobre cómo trabajar con VTL en la [Resolver mapping template programming guide](resolver-mapping-template-reference-programming-guide.md#aws-appsync-resolver-mapping-template-reference-programming-guide).

AWS AppSync admite el aprovisionamiento automático de tablas de DynamoDB desde un esquema de GraphQL, tal y como se describe en Aprovisionar desde un esquema (opcional) y Lanzar un esquema de ejemplo. También puede importar desde una tabla de DynamoDB existente, lo que creará solucionadores de esquema y de conexión. Esto se detalla en la sección sobre importación desde Amazon DynamoDB (opcional).

**Topics**
+ [Creación de una aplicación de publicación sencilla con los solucionadores de DynamoDB](tutorial-dynamodb-resolvers.md)
+ [Uso de resolutores AWS Lambda](tutorial-lambda-resolvers.md)
+ [Uso de solucionadores OpenSearch de servicios](tutorial-elasticsearch-resolvers.md)
+ [Uso de solucionadores locales](tutorial-local-resolvers.md)
+ [Combinación de solucionadores de GraphQL](tutorial-combining-graphql-resolvers.md)
+ [Uso de operaciones por lotes de DynamoDB](tutorial-dynamodb-batch.md)
+ [Ejecución de transacciones de DynamoDB](tutorial-dynamodb-transact.md)
+ [Uso de solucionadores de HTTP](tutorial-http-resolvers.md)
+ [Uso de solucionadores de Aurora Serverless v2](tutorial-rds-resolvers.md)
+ [Uso de solucionadores de canalización](tutorial-pipeline-resolvers.md)
+ [Uso de operaciones de Delta Sync en orígenes de datos con control de versiones](tutorial-delta-sync.md)

# Creación de una aplicación de publicación sencilla con los solucionadores de DynamoDB
<a name="tutorial-dynamodb-resolvers"></a>

**nota**  
Ahora admitimos de forma básica el tiempo de ejecución APPSYNC\$1JS y su documentación. Considere la opción de utilizar el tiempo de ejecución APPSYNC\$1JS y sus guías [aquí](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html).

En este tutorial, se muestra cómo puede incorporar sus propias tablas de Amazon DynamoDB y conectarlas AWS AppSync a una API de GraphQL.

Puede permitir el AWS AppSync aprovisionamiento de recursos de DynamoDB en su nombre. O bien, si lo prefiere, puede conectar las tablas existentes a un esquema de GraphQL creando un origen de datos y un solucionador. En cualquier caso, podrá leer y escribir en la base de datos de DynamoDB a través de instrucciones de GraphQL y suscribirse a datos en tiempo real.

Hay que realizar una serie de pasos de configuración específicos para que las instrucciones de GraphQL se traduzcan a operaciones de DynamoDB y que, a su vez, las respuestas se traduzcan de nuevo a GraphQL. En este tutorial se describe el proceso de configuración a través de varios escenarios y patrones de acceso a datos del mundo real.

## Configuración de las tablas de DynamoDB
<a name="setting-up-your-ddb-tables"></a>

Para comenzar este tutorial, primero debe seguir los pasos que se indican a continuación para AWS aprovisionar los recursos.

1.  AWS Aprovisione recursos mediante la siguiente AWS CloudFormation plantilla de la CLI:

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

   Como alternativa, puedes lanzar la siguiente CloudFormation pila en la región US-West 2 (Oregón) de tu AWS cuenta.

   [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)

   Así se crea lo siguiente:
   + Una tabla de DynamoDB llamada `AppSyncTutorial-Post` que contendrá datos de `Post`.
   + Un rol de IAM y una política gestionada de IAM asociada para poder interactuar con AWS AppSync la tabla. `Post`

1. Para ver más información acerca de la pila y de los recursos creados, ejecute el siguiente comando de la CLI:

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

1. Para eliminar los recursos más adelante, puede ejecutar lo siguiente:

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

## Creación de la API de GraphQL
<a name="creating-your-graphql-api"></a>

Para crear la API GraphQL en: AWS AppSync

1. Inicia sesión en la [AppSync consola Consola de administración de AWS](https://console.aws.amazon.com/appsync/) y ábrela.

   1. En el **APIs panel**, selecciona **Crear API**.

1. En la ventana **Personalice su API o impórtela desde Amazon DynamoDB**, seleccione **Crear desde cero**.

   1. Seleccione **Comenzar** a la derecha de la misma ventana.

1. En el campo **Nombre de la API**, establezca el nombre de la API en `AWSAppSyncTutorial`.

1. Seleccione **Crear**.

La AWS AppSync consola crea una nueva API de GraphQL para usted mediante el modo de autenticación con clave de API. Puede utilizar la consola para configurar el resto de la API de GraphQL y ejecutar consultas en ella durante el resto de este tutorial.

## Definición de una API de publicación básica
<a name="defining-a-basic-post-api"></a>

Ahora que ha creado una API de AWS AppSync GraphQL, puede configurar un esquema básico que permita la creación, recuperación y eliminación básicas de los datos posteriores.

1. [Inicia sesión en la consola Consola de administración de AWS y ábrelaAppSync .](https://console.aws.amazon.com/appsync/)

   1. En el **APIs panel de control**, elige la API que acabas de crear.

1. En la **barra lateral**, seleccione **Esquema**.

   1. En la página **Esquema**, sustituya el contenido por el código siguiente:

     ```
     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. Seleccione **Save**.

Este esquema define un tipo `Post` y las operaciones para agregar y obtener objetos `Post`.

## Configuración del origen de datos para las tablas de DynamoDB
<a name="configuring-the-data-source-for-the-ddb-tables"></a>

A continuación, vincule las consultas y las mutaciones definidas en el esquema a la tabla `AppSyncTutorial-Post` de DynamoDB.

En primer lugar, AWS AppSync debe conocer sus tablas. Para ello, configure un origen de datos en AWS AppSync:

1. Inicie sesión en la [AppSync consola Consola de administración de AWS y ábrala](https://console.aws.amazon.com/appsync/).

   1. En el **APIs panel**, elige tu API de GraphQL.

   1. En la **barra lateral**, seleccione **Origen de datos**.

1. Elija **Crear origen de datos**.

   1. En **Nombre de origen de datos**, escriba `PostDynamoDBTable`. 

   1. En **Tipo de origen de datos**, elija **Tabla de Amazon DynamoDB**.

   1. En **Región**, elija **US-WEST-2**.

   1. **En Nombre de tabla**, elija la tabla **AppSyncTutorial-Post DynamoDB**.

   1. Cree un nuevo rol de IAM (recomendado) o elija un rol existente que tenga el permiso de IAM `lambda:invokeFunction`. Los roles existentes necesitan una política de confianza, tal y como se explica en la sección [Asociar un origen de datos](attaching-a-data-source.md). 

      El siguiente es un ejemplo de política de IAM que tiene los permisos necesarios para llevar a cabo operaciones en el recurso:

------
#### [ 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. Seleccione **Crear**.

## Configuración del solucionador AddPost (DynamoDB) PutItem
<a name="setting-up-the-addpost-resolver-dynamodb-putitem"></a>

**Una vez AWS AppSync que conozca la tabla de DynamoDB, podrá vincularla a consultas y mutaciones individuales definiendo Resolvers.** El primer solucionador que va a crear es `addPost`, que le permite crear una publicación en la tabla `AppSyncTutorial-Post` de DynamoDB.

Un solucionador tiene los siguientes componentes:
+ La ubicación en el esquema de GraphQL donde se asocia el solucionador. En este caso configuramos en el campo `addPost` un solucionador de tipo `Mutation`. El solucionador se invocará cuando el intermediario llame a `mutation { addPost(...){...} }`.
+ El origen de datos que se va a utilizar para el solucionador. En este caso, queremos utilizar el origen de datos `PostDynamoDBTable` definido anteriormente para poder agregar entradas en la tabla `AppSyncTutorial-Post` de DynamoDB.
+ La plantilla de mapeo de solicitudes. El propósito de la plantilla de mapeo de solicitudes es tomar la solicitud entrante de la persona que llama y traducirla en instrucciones AWS AppSync para su ejecución en DynamoDB.
+ La plantilla de mapeo de respuestas. El cometido de la plantilla de mapeo de respuesta es tomar la respuesta de DynamoDB y traducirla de nuevo a algo que GraphQL espera. Esto resulta útil si la forma de los datos en DynamoDB es diferente del tipo `Post` en GraphQL. Como este caso sí tienen la misma forma, solo hay que transmitir los datos.

Para configurar el solucionador de :

1. [Inicie sesión en la consola Consola de administración de AWS y ábrala. AppSync ](https://console.aws.amazon.com/appsync/)

   1. En el **APIs panel**, elige tu API de GraphQL.

   1. En la **barra lateral**, seleccione **Origen de datos**.

1. Elija **Crear origen de datos**.

   1. En **Nombre de origen de datos**, escriba `PostDynamoDBTable`. 

   1. En **Tipo de origen de datos**, elija **Tabla de Amazon DynamoDB**.

   1. En **Región**, elija **US-WEST-2**.

   1. **En Nombre de tabla**, elija la tabla **AppSyncTutorial-Post DynamoDB**.

   1. Cree un nuevo rol de IAM (recomendado) o elija un rol existente que tenga el permiso de IAM `lambda:invokeFunction`. Los roles existentes necesitan una política de confianza, tal y como se explica en la sección [Asociar un origen de datos](attaching-a-data-source.md). 

      El siguiente es un ejemplo de política de IAM que tiene los permisos necesarios para llevar a cabo operaciones en el recurso:

------
#### [ 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. Seleccione **Crear**.

1. Elija la pestaña **Schema (Esquema)**.

1. En el panel **Data types (Tipos de datos)** de la derecha, busque el campo **addPost** en el tipo **Mutation** y, a continuación, seleccione **Attach (Asociar)**.

1. En el **menú Acción**, seleccione **Actualizar tiempo de ejecución** y, a continuación, elija **Solucionador de unidades (solo VTL)**.

1. En **Data source name (Nombre del origen de datos)**, elija **PostDynamoDBTable**.

1. En **Configure the request mapping template (Configurar la plantilla de mapeo de solicitud)**, pegue lo siguiente:

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

   **Nota**: se especifica un *tipo* para todas las claves y valores de atributos. Por ejemplo, puede establecer el campo `author` como `{ "S" : "${context.arguments.author}" }`. La `S` parte indica a DynamoDB AWS AppSync y a DynamoDB que el valor será un valor de cadena. El valor real se rellena con el argumento `author`. Del mismo modo, el campo `version` es numérico, ya que utiliza `N` para el tipo. Por último, también inicializamos los campos `ups`, `downs` y `version`.

   En este tutorial, especificó que el `ID!` tipo GraphQL, que indexa el nuevo elemento que se inserta en DynamoDB, forma parte de los argumentos del cliente. AWS AppSync viene con una utilidad para la generación automática de identificadores llamada `$utils.autoId()` que también podrías haber utilizado en forma de. `"id" : { "S" : "${$utils.autoId()}" }` Así bastaría con excluir `id: ID!` de la definición del esquema de `addPost()` y se insertaría de forma automática. No utilizará esta técnica en este tutorial, pero debe considerarla, ya que se trata de una práctica recomendada a la hora de escribir en tablas de DynamoDB.

   Para obtener más información acerca de las plantillas de mapeo, consulte la sección [Información general de plantillas de mapeo de solucionador](resolver-mapping-template-reference-overview.md#aws-appsync-resolver-mapping-template-reference-overview). Para obtener más información sobre el mapeo de GetItem solicitudes, consulte la documentación de [GetItem](aws-appsync-resolver-mapping-template-reference-dynamodb-getitem.md)referencia. Para obtener más información sobre los tipos, consulte la documentación de referencia del [Sistema de tipos (mapeos de solicitud)](aws-appsync-resolver-mapping-template-reference-dynamodb-typed-values-request.md).

1. En **Configure the response mapping template (Configurar la plantilla de mapeo de respuesta)**, pegue lo siguiente:

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

    **Nota**: Debido a que la forma de los datos de la tabla `AppSyncTutorial-Post` coincide exactamente con la forma del tipo `Post` de GraphQL, la plantilla de mapeo de respuesta se limita a pasar directamente los resultados. Observe que todos los ejemplos de este tutorial utilizan la misma plantilla de mapeo de respuesta, de manera que solo tiene que crear un archivo.

1. Seleccione **Save**.

### Llame a la API para añadir una publicación
<a name="call-the-api-to-add-a-post"></a>

Ahora que el solucionador está configurado, AWS AppSync puede convertir una `addPost` mutación entrante en una operación de PutItem DynamoDB. Ahora puede ejecutar una mutación para incluir datos en la tabla.
+ Seleccione la pestaña **Queries (Consultas)**.
+ En el panel **Queries (Consultas)**, pegue la mutación siguiente:

  ```
  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
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ Los resultados de la publicación que acaba de crear deben aparecer en el panel de resultados a la derecha del panel de consultas. Debería parecerse a lo que sigue:

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

Esto es lo que ha ocurrido:
+ AWS AppSync recibió una solicitud de `addPost` mutación.
+ AWS AppSync tomó la solicitud y la plantilla de mapeo de la solicitud y generó un documento de mapeo de la solicitud. Esto habrá tenido el siguiente aspecto:

  ```
  {
      "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 utilizó el documento de mapeo de solicitudes para generar y ejecutar una solicitud de `PutItem` DynamoDB.
+ AWS AppSync tomó los resultados de la `PutItem` solicitud y los volvió a convertir a tipos 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
  }
  ```
+ Después los ha pasado a través del documento de mapeo de respuesta, que se ha limitado a transferirlos sin cambios.
+ Por fin ha devuelto el objeto recién creado en la respuesta de GraphQL.

## Configuración del GetPost Resolver (DynamoDB) GetItem
<a name="setting-up-the-getpost-resolver-ddb-getitem"></a>

Ahora que puede añadir datos a la tabla de DynamoDB `AppSyncTutorial-Post`, tiene que configurar la consulta `getPost` para poder recuperar los datos de la tabla `AppSyncTutorial-Post`. Para ello, tiene que configurar otro solucionador.
+ Elija la pestaña **Schema (Esquema)**.
+ En el panel **Data types (Tipos de datos)** de la derecha, busque el campo **getPost** en el tipo **Query** y, a continuación, seleccione **Attach (Asociar)**.
+ En el **menú Acción**, seleccione **Actualizar tiempo de ejecución** y, a continuación, elija **Solucionador de unidades (solo VTL)**.
+ En **Data source name (Nombre del origen de datos)**, elija **PostDynamoDBTable**.
+ En **Configure the request mapping template (Configurar la plantilla de mapeo de solicitud)**, pegue lo siguiente:

  ```
  {
      "version" : "2017-02-28",
      "operation" : "GetItem",
      "key" : {
          "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
      }
  }
  ```
+ En **Configure the response mapping template (Configurar la plantilla de mapeo de respuesta)**, pegue lo siguiente:

  ```
  $utils.toJson($context.result)
  ```
+ Seleccione **Save**.

### Llame a la API para obtener una publicación
<a name="call-the-api-to-get-a-post"></a>

Ahora que el solucionador está configurado, AWS AppSync sabe cómo convertir una `getPost` consulta entrante en una operación de `GetItem` DynamoDB. Ahora puede ejecutar una consulta para recuperar la publicación que ha creado anteriormente.
+ Seleccione la pestaña **Queries (Consultas)**.
+ En el panel **Queries (Consultas)**, pegue lo siguiente:

  ```
  query getPost {
    getPost(id:123) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ La publicación obtenida de DynamoDB debe aparecer en el panel de resultados a la derecha del panel de consultas. Debería parecerse a lo que sigue:

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

Esto es lo que ha ocurrido:
+ AWS AppSync recibió una `getPost` solicitud de consulta.
+ AWS AppSync tomó la solicitud y la plantilla de mapeo de solicitudes y generó un documento de mapeo de solicitudes. Esto habrá tenido el siguiente aspecto:

  ```
  {
      "version" : "2017-02-28",
      "operation" : "GetItem",
      "key" : {
          "id" : { "S" : "123" }
      }
  }
  ```
+ AWS AppSync utilizó el documento de mapeo de solicitudes para generar y ejecutar una solicitud de GetItem DynamoDB.
+ AWS AppSync tomó los resultados de la `GetItem` solicitud y los volvió a convertir a tipos 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
  }
  ```
+ Después los ha pasado a través del documento de mapeo de respuesta, que se ha limitado a transferirlos sin cambios.
+ Entonces ha devuelto el objeto recuperado en la respuesta.

Como alternativa, vamos a tomar el siguiente ejemplo:

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

Si la consulta `getPost` solo necesita las letras `id`, `author` y `title`, puede cambiar la plantilla de mapeo de solicitudes para utilizar expresiones de proyección que especifiquen únicamente los atributos que desee de la tabla de DynamoDB y evitar la transferencia innecesaria de datos de DynamoDB a AWS AppSync. Por ejemplo, la plantilla de mapeo de solicitudes puede tener un aspecto similar al siguiente fragmento:

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

## Crear una mutación en UpdatePost (DynamoDB) UpdateItem
<a name="create-an-updatepost-mutation-ddb-updateitem"></a>

De momento ya sabe crear y recuperar objetos `Post` en DynamoDB. A continuación va a configurar una nueva mutación que permite actualizar objetos. Para ello, utilice la operación UpdateItem DynamoDB.
+ Elija la pestaña **Schema (Esquema)**.
+ En el panel **Schema (Esquema)** modifique el tipo `Mutation` para agregar una nueva mutación `updatePost` de este modo:

  ```
  type Mutation {
      updatePost(
          id: ID!,
          author: String!,
          title: String!,
          content: String!,
          url: String!
      ): Post
      addPost(
          author: String!
          title: String!
          content: String!
          url: String!
      ): Post!
  }
  ```
+ Seleccione **Save**.
+ En el panel **Data types (Tipos de datos)** de la derecha, busque el campo **updatePost** recién creado en el tipo **Mutation** y, a continuación, elija **Attach (Asociar)**.
+ En el **menú Acción**, seleccione **Actualizar tiempo de ejecución** y, a continuación, elija **Solucionador de unidades (solo VTL)**.
+ En **Data source name (Nombre del origen de datos)**, elija **PostDynamoDBTable**.
+ En **Configure the request mapping template (Configurar la plantilla de mapeo de solicitud)**, pegue lo siguiente:

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

   **Nota:** Este solucionador utiliza UpdateItem DynamoDB, que es muy diferente de la operación. PutItem En lugar de escribir el elemento completo, solo pide a DynamoDB que actualice determinados atributos. Esto se consigue mediante expresiones de actualización de DynamoDB. La expresión en sí se especifica en el campo `expression` de la sección `update`. Indica que se deben establecer los atributos `author`, `title`, `content` y URL, y a continuación incrementar el campo `version`. Los valores empleados no aparecen en la expresión en sí, que contiene marcadores de posición con nombres que comienzan con dos puntos y que luego se definen en el campo `expressionValues`. Por último, DynamoDB tiene palabras reservadas que no pueden aparecer en la `expression`. Por ejemplo, `url` es una palabra reservada, por lo que para actualizar el campo `url`, puede utilizar marcadores de posición de nombre y definirlos en el campo `expressionNames`.

  Para obtener más información sobre el mapeo de `UpdateItem` solicitudes, consulte la documentación de [UpdateItem](aws-appsync-resolver-mapping-template-reference-dynamodb-updateitem.md)referencia. Para obtener más información sobre cómo escribir expresiones de actualización, consulte la documentación de [ UpdateExpressions DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html).
+ En **Configure the response mapping template (Configurar la plantilla de mapeo de respuesta)**, pegue lo siguiente:

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

### Llame a la API para actualizar una publicación
<a name="call-the-api-to-update-a-post"></a>

Ahora que el solucionador está configurado, AWS AppSync sabe cómo convertir una `update` mutación entrante en una operación de `Update` DynamoDB. Ahora puede ejecutar una mutación para actualizar el elemento que escribió anteriormente.
+ Seleccione la pestaña **Queries (Consultas)**.
+ En el panel **Queries (Consultas)**, pegue la mutación siguiente. También tendrá que actualizar el argumento `id` con el valor que anotó anteriormente.

  ```
  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
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ La publicación actualizada en DynamoDB debería aparecer en el panel de resultados a la derecha del panel de consultas. Debería parecerse a lo que sigue:

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

En este ejemplo, los `downs` campos `ups` y no se modificaron porque la plantilla de mapeo de solicitudes no pedía AWS AppSync a DynamoDB que hiciera nada con esos campos. Además, el `version` campo se incrementó en 1 porque pidió AWS AppSync a DynamoDB que añadiera 1 al campo. `version`

## Modificación del UpdatePost Resolver (DynamoDB) UpdateItem
<a name="modifying-the-updatepost-resolver-dynamodb-updateitem"></a>

Se trata de un buen comienzo para la mutación `updatePost`, pero tiene dos inconvenientes:
+ Aunque solo desee actualizar un campo, tiene que actualizar todos.
+ Si dos personas modifican el objeto a la vez información podría perderse información.

Para solucionar estos problemas, va a modificar la mutación `updatePost` de manera que únicamente modifique los argumentos que se han especificado en la solicitud, y también va a agregar una condición a la operación `UpdateItem`.

1. Elija la pestaña **Schema (Esquema)**.

1. En el panel **Esquema**, modifique el campo `updatePost` del tipo `Mutation` para eliminar los signos de admiración de los argumentos `author`, `title`, `content` y `url` asegurándose de dejar sin cambios el campo `id`. Esto los convertirá en argumentos opcionales. Además, añada un nuevo argumento `expectedVersion` obligatorio.

   ```
   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. Seleccione **Save**.

1. En el panel **Data types (Tipos de datos)** de la derecha, busque el campo **updatePost** en el tipo **Mutation**.

1. Elija abrir el **PostDynamoDBTable**solucionador existente.

1. En **Configure the request mapping template (Configurar la plantilla de mapeo de solicitud)**, modifique la plantilla de mapeo de solicitud de este modo:

   ```
   {
       "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. Seleccione **Save**.

Esta plantilla es uno de los ejemplos más complejos. Demuestra la eficacia y flexibilidad de las plantillas de mapeo. Itera por todos los argumentos, saltando `id` y `expectedVersion`. Si el argumento tiene un valor determinado, pide AWS AppSync a DynamoDB que actualice ese atributo en el objeto de DynamoDB. Si el atributo se establece en null, pide AWS AppSync a DynamoDB que elimine ese atributo del objeto de publicación. Si no se ha especificado algún argumento, lo dejará como está. También incrementa el campo `version`.

También hay una nueva sección `condition`. Una expresión de condición permite indicar AWS AppSync a DynamoDB si la solicitud debe realizarse correctamente o no en función del estado del objeto que ya se encuentra en DynamoDB antes de realizar la operación. En este caso, solo quiere que la solicitud `UpdateItem` se realice si el campo `version` del elemento que hay en DynamoDB coincide exactamente con el argumento `expectedVersion`.

Si desea más información sobre las expresiones de condición, consulte la documentación de referencia [Expresiones de condición](aws-appsync-resolver-mapping-template-reference-dynamodb-condition-expressions.md).

### Llame a la API para actualizar una publicación
<a name="id1"></a>

Vamos a intentar actualizar el objeto `Post` con el nuevo solucionador:
+ Seleccione la pestaña **Queries (Consultas)**.
+ En el panel **Queries (Consultas)**, pegue la mutación siguiente. También tendrá que actualizar el argumento `id` con el valor que anotó anteriormente.

  ```
  mutation updatePost {
    updatePost(
      id:123
      title: "An empty story"
      content: null
      expectedVersion: 2
    ) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ La publicación actualizada en DynamoDB debería aparecer en el panel de resultados a la derecha del panel de consultas. Debería parecerse a lo que sigue:

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

En esta solicitud, ha pedido a DynamoDB que actualice únicamente `title` el `content` campo AWS AppSync and. Los demás campos se quedan como estaban (salvo por el incremento del campo `version`). Hemos establecido un nuevo valor en el atributo `title` y hemos eliminado el atributo `content` de la publicación. Los campos `author`, `url`, `ups` y `downs` no se han modificado.

Intente ejecutar la solicitud de mutación de nuevo, dejándola exactamente como está. Verá una respuesta parecida a la siguiente:

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

La solicitud falla porque la expresión de condición se evalúa como false:
+ La primera vez que ejecutamos la solicitud, el valor del campo `version` de la publicación en DynamoDB era `2`, que coincidía con el argumento `expectedVersion`. La solicitud se realizó correctamente, lo que significa que el campo `version` se incrementó en DynamoDB a `3`.
+ La segunda vez que ejecutamos la solicitud, el valor del campo `version` de la publicación en DynamoDB era `3`, que no coincide con el argumento `expectedVersion`.

Este método suele denominarse *"bloqueo optimista"*.

Una característica de un solucionador de AWS AppSync DynamoDB es que devuelve el valor actual del objeto post en DynamoDB. Puede encontrarlo en el campo `data`, en la sección `errors` de la respuesta de GraphQL. La aplicación puede utilizar esta información para decidir cómo debe proceder. En este caso, podemos ver que el campo `version` del objeto en DynamoDB tiene el valor `3`, por lo que solo tendríamos que actualizar el argumento `expectedVersion` a `3` para que la solicitud se ejecutase de nuevo.

Para obtener más información sobre cómo gestionar los casos en que no se cumpla la condición, consulte [Expresiones de condición](aws-appsync-resolver-mapping-template-reference-dynamodb-condition-expressions.md) en la documentación de referencia de las plantillas de mapeo.

## Crear mutaciones en UpVotePost y DownVotePost (DynamoDB) UpdateItem
<a name="create-upvotepost-and-downvotepost-mutations-ddb-updateitem"></a>

El tipo `Post` tiene campos `ups` y `downs` para registrar los votos a favor y en contra, pero hasta el momento la API no nos deja hacer nada con ellos. Vamos a agregar algunas mutaciones para votar a favor o en contra de las publicaciones.
+ Elija la pestaña **Schema (Esquema)**.
+ En el panel **Schema (Schema)**, modifique el tipo `Mutation` para agregar las nuevas mutaciones `upvotePost` y `downvotePost`, de este modo:

  ```
  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!
  }
  ```
+ Seleccione **Save**.
+ En el panel **Data types (Tipos de datos)** de la derecha, busque el campo **upvotePost** recién creado en el tipo **Mutation** y, a continuación, elija **Attach (Asociar)**.
+ En el **menú Acción**, seleccione **Actualizar tiempo de ejecución** y, a continuación, elija **Solucionador de unidades (solo VTL)**.
+ En **Data source name (Nombre del origen de datos)**, elija **PostDynamoDBTable**.
+ En **Configure the request mapping template (Configurar la plantilla de mapeo de solicitud)**, pegue lo siguiente:

  ```
  {
      "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 }
          }
      }
  }
  ```
+ En **Configure the response mapping template (Configurar la plantilla de mapeo de respuesta)**, pegue lo siguiente:

  ```
  $utils.toJson($context.result)
  ```
+ Seleccione **Save**.
+ En el panel **Data types (Tipos de datos)** de la derecha, busque el campo `downvotePost` recién creado en el tipo **Mutation** y, a continuación, elija **Attach (Asociar)**.
+ En **Data source name (Nombre del origen de datos)**, elija **PostDynamoDBTable**.
+ En **Configure the request mapping template (Configurar la plantilla de mapeo de solicitud)**, pegue lo siguiente:

  ```
  {
      "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 }
          }
      }
  }
  ```
+ En **Configure the response mapping template (Configurar la plantilla de mapeo de respuesta)**, pegue lo siguiente:

  ```
  $utils.toJson($context.result)
  ```
+ Seleccione **Save**.

### Llame a la API para votar a favor o en contra de una publicación
<a name="call-the-api-to-upvote-and-downvote-a-post"></a>

Una vez configurados los nuevos resolutores, AWS AppSync sabe cómo traducir una operación entrante `upvotePost` o de `downvote` mutación a DynamoDB UpdateItem . Ahora puede ejecutar mutaciones para votar a favor o en contra de la publicación que ha creado anteriormente.
+ Seleccione la pestaña **Queries (Consultas)**.
+ En el panel **Queries (Consultas)**, pegue la mutación siguiente. También tendrá que actualizar el argumento `id` con el valor que anotó anteriormente.

  ```
  mutation votePost {
    upvotePost(id:123) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ La publicación se actualiza en DynamoDB y debe aparecer en el panel de resultados a la derecha del panel de consultas. Debería parecerse a lo que sigue:

  ```
  {
    "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
      }
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** algunas veces más. Debería ver cómo se incrementan en 1 los campos `ups` y `version` cada vez que ejecuta la consulta.
+ Modifique la consulta para llamar a la mutación `downvotePost` de este modo:

  ```
  mutation votePost {
    downvotePost(id:123) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja). En esta ocasión, debería ver cómo se incrementan en 1 los campos `downs` y `version` cada vez que ejecuta la consulta.

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

## Configuración del solucionador DeletePost (DynamoDB) DeleteItem
<a name="setting-up-the-deletepost-resolver-ddb-deletepost"></a>

La próxima mutación que desea configurar es la eliminación de una publicación. Para ello, utilizará la operación `DeleteItem` de DynamoDB.
+ Elija la pestaña **Schema (Esquema)**.
+ En el panel **Schema (Esquema)** modifique el tipo `Mutation` para agregar una nueva mutación `deletePost` de este modo:

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

  Esta vez hemos declarado el campo `expectedVersion` como opcional, lo que se explica más adelante al añadir la plantilla de mapeo de solicitud.
+ Seleccione **Save**.
+ En el panel **Data types (Tipos de datos)** de la derecha, busque el campo **delete** recién creado en el tipo **Mutation** y, a continuación, elija **Attach (Asociar)**.
+ En el **menú Acción**, seleccione **Actualizar tiempo de ejecución** y, a continuación, elija **Solucionador de unidades (solo VTL)**.
+ En **Data source name (Nombre del origen de datos)**, elija **PostDynamoDBTable**.
+ En **Configure the request mapping template (Configurar la plantilla de mapeo de solicitud)**, pegue lo siguiente:

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

   **Nota**: el argumento `expectedVersion` es opcional. Si el intermediario ha establecido un argumento `expectedVersion` en la solicitud, entonces la plantilla añade una condición que solo permitirá que la solicitud `DeleteItem` se atienda cuando el elemento se haya eliminado o cuando el atributo `version` de la publicación en DynamoDB coincida exactamente con `expectedVersion`. Si se omite, no se especificará ninguna expresión de condición para la solicitud `DeleteItem`. Se realizará correctamente independientemente del valor de `version` o de si el elemento existe o no en DynamoDB.
+ En **Configure the response mapping template (Configurar la plantilla de mapeo de respuesta)**, pegue lo siguiente:

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

   **Note:** Aunque se trate de eliminar un elemento, puede devolver el elemento eliminado, siempre que no se haya eliminado previamente.
+ Seleccione **Save**.

Para obtener más información sobre el mapeo de `DeleteItem` solicitudes, consulte la documentación de referencia. [DeleteItem](aws-appsync-resolver-mapping-template-reference-dynamodb-deleteitem.md)

### Llame a la API para eliminar una publicación
<a name="call-the-api-to-delete-a-post"></a>

Ahora que el solucionador está configurado, AWS AppSync sabe cómo convertir una `delete` mutación entrante en una operación de `DeleteItem` DynamoDB. Ahora ya podemos ejecutar una mutación para eliminar datos de la tabla.
+ Seleccione la pestaña **Queries (Consultas)**.
+ En el panel **Queries (Consultas)**, pegue la mutación siguiente. También tendrá que actualizar el argumento `id` con el valor que anotó anteriormente.

  ```
  mutation deletePost {
    deletePost(id:123) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ La publicación se elimina de DynamoDB. Tenga en cuenta que AWS AppSync devuelve el valor del elemento que se eliminó de DynamoDB, que debería aparecer en el panel de resultados a la derecha del panel de consulta. Debería parecerse a lo que sigue:

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

El valor solo se devuelve si esta llamada a `deletePost` es realmente la que lo elimina de DynamoDB.
+ Elija **Execute query (Ejecutar consulta)** de nuevo.
+ La llamada se sigue ejecutando con éxito, pero no se devuelve ningún valor.

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

Ahora vamos a probar a eliminar una publicación, pero esta vez especificando `expectedValue`. Primero tiene que crear una nueva, porque acaba de eliminar la publicación con la que ha estado trabajando hasta ahora.
+ En el panel **Queries (Consultas)**, pegue la mutación siguiente:

  ```
  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
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ Los resultados de la publicación que acaba de crear deben aparecer en el panel de resultados a la derecha del panel de consultas. Anote el `id` del objeto recién creado, pues lo necesitaremos en breve. Debería parecerse a lo que sigue:

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

Ahora intentaremos eliminar esta publicación, pero especificaremos un valor erróneo en `expectedVersion`:
+ En el panel **Queries (Consultas)**, pegue la mutación siguiente. También tendrá que actualizar el argumento `id` con el valor que anotó anteriormente.

  ```
  mutation deletePost {
    deletePost(
      id:123
      expectedVersion: 9999
    ) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).

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

  La solicitud falla, porque la expresión de condición se evalúa como false: el valor de `version` de la publicación en DynamoDB no coincide con el `expectedValue` especificado en los argumentos. El valor actual del objeto se devuelve en el campo `data` de la sección `errors` de la respuesta de GraphQL.
+ Vuelva a intentar la solicitud, pero corrija `expectedVersion`:

  ```
  mutation deletePost {
    deletePost(
      id:123
      expectedVersion: 1
    ) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ Esta vez la solicitud se realiza correctamente y se devuelve el valor eliminado en 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
      }
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** de nuevo.
+ La llamada sigue realizándose, pero esta vez no se devuelve ningún valor, ya que la publicación ya se había eliminado en DynamoDB.

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

## Configuración del solucionador allPost (Scan en DynamoDB)
<a name="setting-up-the-allpost-resolver-dynamodb-scan"></a>

Hasta ahora, la API solo es útil si conoce el `id` de cada publicación que desea ver. Añada un nuevo solucionador que devuelva todas las publicaciones en la tabla.
+ Elija la pestaña **Schema (Esquema)**.
+ En el panel **Schema (Esquema)** modifique el tipo `Query` para agregar una nueva consulta `allPost`, de este modo:

  ```
  type Query {
      allPost(count: Int, nextToken: String): PaginatedPosts!
      getPost(id: ID): Post
  }
  ```
+ Añada un nuevo tipo `PaginationPosts`:

  ```
  type PaginatedPosts {
      posts: [Post!]!
      nextToken: String
  }
  ```
+ Seleccione **Save**.
+ En el panel **Data types (Tipos de datos)** de la derecha, busque el campo **allPost** recién creado en el tipo **Query** y, a continuación, elija **Attach (Asociar)**.
+ En el **menú Acción**, seleccione **Actualizar tiempo de ejecución** y, a continuación, elija **Solucionador de unidades (solo VTL)**.
+ En **Data source name (Nombre del origen de datos)**, elija **PostDynamoDBTable**.
+ En **Configure the request mapping template (Configurar la plantilla de mapeo de solicitud)**, pegue lo siguiente:

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

  Este solucionador tiene dos argumentos opcionales: `count`, que especifica el número máximo de elementos que se devolverán en una sola llamada, y `nextToken`, que se puede utilizar para recuperar el siguiente conjunto de resultados (más adelante mostrará de dónde procede el valor de `nextToken`).
+ En **Configure the response mapping template (Configurar la plantilla de mapeo de respuesta)**, pegue lo siguiente:

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

   **Nota:** esta plantilla de mapeo de respuesta es diferente de todas las que hemos visto hasta ahora. El resultado de la consulta `allPost` es del tipo `PaginatedPosts`, que contiene una lista de publicaciones y un token de paginación. La forma de este objeto es diferente a la que devuelve el AWS AppSync DynamoDB Resolver: `items` en los resultados del DynamoDB Resolver se consulta la lista de publicaciones, AWS AppSync pero se invoca. `posts` `PaginatedPosts`
+ Seleccione **Save**.

Si desea más información sobre el mapeo de solicitud `Scan`, consulte la documentación de referencia de [Scan](aws-appsync-resolver-mapping-template-reference-dynamodb-scan.md).

### Llame a la API para escanear todas las publicaciones
<a name="call-the-api-to-scan-all-posts"></a>

Ahora que el solucionador está configurado, AWS AppSync sabe cómo convertir una `allPost` consulta entrante en una operación de `Scan` DynamoDB. Ahora puede recorrer la tabla para obtener todas las publicaciones.

Sin embargo, antes de probarlo, debe rellenar la tabla datos, ya que hemos eliminado las publicaciones con las que hemos estado trabajado hasta ahora.
+ Seleccione la pestaña **Queries (Consultas)**.
+ En el panel **Queries (Consultas)**, pegue la mutación siguiente:

  ```
  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 }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).

Ahora recorreremos la tabla obteniendo cinco resultados cada vez.
+ En el panel **Queries (Consultas)**, pegue la siguiente consulta:

  ```
  query allPost {
    allPost(count: 5) {
      posts {
        id
        title
      }
      nextToken
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ Las cinco primeras publicaciones deben aparecer en el panel de resultados a la derecha del panel de consultas. Debería parecerse a lo que sigue:

  ```
  {
    "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="
      }
    }
  }
  ```

Hemos obtenido cinco resultados y el valor `nextToken`, que nos permite obtener el siguiente conjunto de resultados.
+ Actualice la consulta `allPost` para incluir el valor de `nextToken` del conjunto de resultados anterior:

  ```
  query allPost {
    allPost(
      count: 5
      nextToken: "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnRlluNktJRWl6V0ZlR3hJOVJkaStrZUFBQUI1akNDQWVJR0NTcUdTSWIzRFFFSEJxQ0NBZE13Z2dIUEFnRUFNSUlCeUFZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF5cW8yUGFSZThnalFpemRCTUNBUkNBZ2dHWk1JODhUNzhIOFVUZGtpdFM2ZFluSWRyVDg4c2lkN1RjZzB2d1k3VGJTTWpSQ2U3WjY3TkUvU2I1dWNETUdDMmdmMHErSGJSL0pteGRzYzVEYnE1K3BmWEtBdU5jSENJdWNIUkJ0UHBPWVdWdCtsS2U5L1pNcWdocXhrem1RaXI1YnIvQkt6dU5hZmJCdE93NmtoM2Jna1BKM0RjWWhpMFBGbmhMVGg4TUVGSjBCcXg3RTlHR1V5N0tUS0JLZlV3RjFQZ0JRREdrNzFYQnFMK2R1S2IrVGtZZzVYMjFrc3NyQmFVTmNXZmhTeXE0ZUJHSWhqZWQ5c3VKWjBSSTc2ZnVQdlZkR3FLNENjQmxHYXhpekZnK2pKK1FneEU1SXduRTNYYU5TR0I4QUpmamR2bU1wbUk1SEdvWjlMUUswclczbG14RDRtMlBsaTNLaEVlcm9pem5zcmdINFpvcXIrN2ltRDN3QkJNd3BLbGQzNjV5Nnc4ZnMrK2FnbTFVOUlKOFFrOGd2bEgySHFROHZrZXBrMWlLdWRIQ25LaS9USnBlMk9JeEVPazVnRFlzRTRUU09HUlVJTkxYY2MvdW1WVEpBMUthV2hWTlAvdjNlSnlZQUszbWV6N2h5WHVXZ1BkTVBNWERQdTdjVnVRa3EwK3NhbGZOd2wvSUx4bHNyNDVwTEhuVFpyRWZvVlV1bXZ5S2VKY1RUU1lET05hM1NwWEd2UT09In0="
    ) {
      posts {
        id
        author
      }
      nextToken
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ Las cuatro publicaciones restantes deben aparecer en el panel de resultados a la derecha del panel de consultas. No hay ningún `nextToken` en este conjunto, porque ya ha recorrido las nueve publicaciones y no quedan más. Debería parecerse a lo que sigue:

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

## Configuración del allPostsBy Author Resolver (consulta de DynamoDB)
<a name="setting-up-the-allpostsbyauthor-resolver-ddb-query"></a>

Además de escanear DynamoDB para obtener todas las publicaciones, también puede consultar DynamoDB para obtener las publicaciones creadas por un autor determinado. La tabla de DynamoDB que creó anteriormente ya tiene un `GlobalSecondaryIndex` llamado `author-index` que puede utilizar con una operación `Query` de DynamoDB para recuperar todas las publicaciones creadas por un autor determinado.
+ Elija la pestaña **Schema (Esquema)**.
+ En el panel **Schema (Esquema)** modifique el tipo `Query` para agregar una nueva consulta `allPostsByAuthor`, de este modo:

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

   **Nota:** aquí vuelve a usarse el tipo `PaginatedPosts` que empleamos en la consulta `allPost`.
+ Seleccione **Save**.
+ **En el panel de **tipos de datos** de la derecha, busque el campo **allPostsByAutor** recién creado en el tipo de **consulta** y, a continuación, seleccione Adjuntar.**
+ En el **menú Acción**, seleccione **Actualizar tiempo de ejecución** y, a continuación, elija **Solucionador de unidades (solo VTL)**.
+ En **Data source name (Nombre del origen de datos)**, elija **PostDynamoDBTable**.
+ En **Configure the request mapping template (Configurar la plantilla de mapeo de solicitud)**, pegue lo siguiente:

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

  Al igual que el solucionador `allPost`, este solucionador tiene dos argumentos opcionales: `count`, que especifica el número máximo de elementos que se devolverá en una sola llamada, y `nextToken`, que se puede utilizar para recuperar el siguiente conjunto de resultados (el valor para `nextToken` se puede obtener en una llamada anterior).
+ En **Configure the response mapping template (Configurar la plantilla de mapeo de respuesta)**, pegue lo siguiente:

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

   **Nota:** esta es la misma plantilla de mapeo de respuesta que usamos en el solucionador `allPost`.
+ Seleccione **Save**.

Si desea más información sobre el mapeo de solicitud `Query`, consulte la documentación de referencia de [Query](aws-appsync-resolver-mapping-template-reference-dynamodb-query.md).

### Llame a la API para consultar todas las publicaciones de un autor
<a name="call-the-api-to-query-all-posts-by-an-author"></a>

Ahora que el solucionador está configurado, AWS AppSync sabe cómo traducir una `allPostsByAuthor` mutación entrante en una operación de `Query` DynamoDB contra el índice. `author-index` Ahora puede consultar la tabla para recuperar todas las publicaciones de un autor determinado.

Sin embargo, antes de hacerlo, debemos rellenar la tabla con algunas publicaciones más, ya que por el momento todas son del mismo autor.
+ Seleccione la pestaña **Queries (Consultas)**.
+ En el panel **Queries (Consultas)**, pegue la mutación siguiente:

  ```
  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 }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).

Ahora consultaremos la tabla para que devuelva todas las publicaciones creadas por `Nadia`.
+ En el panel **Queries (Consultas)**, pegue la siguiente consulta:

  ```
  query allPostsByAuthor {
    allPostsByAuthor(author: "Nadia") {
      posts {
        id
        title
      }
      nextToken
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ Todas las publicaciones creadas por `Nadia` deben aparecer en el panel de resultados a la derecha del panel de consultas. Debería parecerse a lo que sigue:

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

La paginación funciona con `Query` de igual modo que con `Scan`. Por ejemplo, vamos a buscar todas las publicaciones de `AUTHORNAME` y obtener cinco cada vez.
+ En el panel **Queries (Consultas)**, pegue la siguiente consulta:

  ```
  query allPostsByAuthor {
    allPostsByAuthor(
      author: "AUTHORNAME"
      count: 5
    ) {
      posts {
        id
        title
      }
      nextToken
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ Todas las publicaciones creadas por `AUTHORNAME` deben aparecer en el panel de resultados a la derecha del panel de consultas. Debería parecerse a lo que sigue:

  ```
  {
    "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=="
      }
    }
  }
  ```
+ Actualice el argumento `nextToken` con el valor devuelto en la consulta anterior, de este modo:

  ```
  query allPostsByAuthor {
    allPostsByAuthor(
      author: "AUTHORNAME"
      count: 5
      nextToken: "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnSExqRnVhVUR3ZUhEZ2QzNGJ2QlFuY0FBQUNqekNDQW9zR0NTcUdTSWIzRFFFSEJxQ0NBbnd3Z2dKNEFnRUFNSUlDY1FZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF5Qkg4Yk1obW9LVEFTZHM3SUNBUkNBZ2dKQ3dISzZKNlJuN3pyYUVKY1pWNWxhSkNtZW1KZ0F5N1dhZkc2UEdTNHpNQzJycTkwZHFJTFV6Z25wck9Gd3pMS3VOQ2JvUXc3VDI5eCtnVExIbGg4S3BqbzB1YjZHQ3FwcDhvNDVmMG9JbDlmdS9JdjNXcFNNSXFKTXZ1MEVGVWs1VzJQaW5jZGlUaVRtZFdYWlU1bkV2NkgyRFBRQWZYYlNnSmlHSHFLbmJZTUZZM0FTdmRIL0hQaVZBb1RCMk1YZkg0eGJOVTdEbjZtRFNhb2QwbzdHZHJEWDNtODQ1UXBQUVNyUFhHemY0WDkyajhIdlBCSWE4Smcrb0RxbHozUVQ5N2FXUXdYWWU2S0h4emI1ejRITXdEdXEyRDRkYzhoMi9CbW10MzRMelVGUVIyaExSZGRaZ0xkdzF5cHJZdFZwY3dEc1d4UURBTzdOcjV2ZEp4VVR2TVhmODBRSnp1REhXREpTVlJLdDJwWmlpaXhXeGRwRmNod1BzQ3d2aVBqMGwrcWFFWU1jMXNQbENkVkFGem43VXJrSThWbS8wWHlwR2xZb3BSL2FkV0xVekgrbGMrYno1ZEM2SnVLVXdtY1EyRXlZeDZiS0Izbi9YdUViWGdFeU5PMWZTdE1rRlhyWmpvMVpzdlYyUFRjMzMrdEs0ZDhkNkZrdjh5VVR6WHhJRkxIaVNsOUx6VVdtT3BCaWhrTFBCT09jcXkyOHh1UmkzOEM3UFRqMmN6c3RkOUo1VUY0azBJdUdEbVZzM2xjdWg1SEJjYThIeXM2aEpvOG1HbFpMNWN6R2s5bi8vRE1EbDY3RlJraG5QNFNhSDBpZGI5VFEvMERLeFRBTUdhcWpPaEl5ekVqd2ZDQVJleFdlbldyOGlPVkhScDhGM25WZVdvbFRGK002N0xpdi9XNGJXdDk0VEg3b0laUU5lYmZYKzVOKy9Td25Hb1dyMTlWK0pEb2lIRVFLZ1cwMWVuYjZKUXo5Slh2Tm95ZzF3RnJPVmxGc2xwNlRHa1BlN2Rnd2IrWT0ifQ=="
    ) {
      posts {
        id
        title
      }
      nextToken
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ Las demás publicaciones creadas por `AUTHORNAME` deben aparecer en el panel de resultados a la derecha del panel de consultas. Debería parecerse a lo que sigue:

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

## Uso de conjuntos
<a name="using-sets"></a>

Hasta este punto, el `Post` tipo era un objeto plano key/value . También puede modelar objetos complejos con el AWS AppSyncDynamo solucionador de bases de datos, como conjuntos, listas y mapas.

Vamos a actualizar el tipo `Post` para incluir etiquetas. Una publicación puede tener cero o más etiquetas, que se almacenan en DynamoDB como un conjunto de cadenas. También configuraremos algunas mutaciones para añadir y eliminar etiquetas, y una nueva consulta para buscar las publicaciones con una etiqueta concreta.
+ Elija la pestaña **Schema (Esquema)**.
+ En el panel **Schema (Esquema)** modifique el tipo `Post` para agregar un nuevo campo `tags`, de este modo:

  ```
  type Post {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int!
    downs: Int!
    version: Int!
    tags: [String!]
  }
  ```
+ En el panel **Schema (Esquema)** modifique el tipo `Query` para agregar una nueva consulta `allPostsByTag`, de este modo:

  ```
  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
  }
  ```
+ En el panel **Schema (Schema)**, modifique el tipo `Mutation` para agregar las nuevas mutaciones `addTag` y `removeTag`, de este modo:

  ```
  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!
  }
  ```
+ Seleccione **Save**.
+ En el panel de **tipos de datos** de la derecha, busque el campo **allPostsByEtiqueta** recién creado en el tipo de **consulta** y, a continuación, elija **Adjuntar**.
+ En **Data source name (Nombre del origen de datos)**, elija **PostDynamoDBTable**.
+ En **Configure the request mapping template (Configurar la plantilla de mapeo de solicitud)**, pegue lo siguiente:

  ```
  {
      "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
  }
  ```
+ En **Configure the response mapping template (Configurar la plantilla de mapeo de respuesta)**, pegue lo siguiente:

  ```
  {
      "posts": $utils.toJson($context.result.items)
      #if( ${context.result.nextToken} )
          ,"nextToken": $util.toJson($context.result.nextToken)
      #end
  }
  ```
+ Seleccione **Save**.
+ En el panel **Data types (Tipos de datos)** de la derecha, busque el campo **addTag** recién creado en el tipo **Mutation** y, a continuación, elija **Attach (Asociar)**.
+ En **Data source name (Nombre del origen de datos)**, elija **PostDynamoDBTable**.
+ En **Configure the request mapping template (Configurar la plantilla de mapeo de solicitud)**, pegue lo siguiente:

  ```
  {
      "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 }
          }
      }
  }
  ```
+ En **Configure the response mapping template (Configurar la plantilla de mapeo de respuesta)**, pegue lo siguiente:

  ```
  $utils.toJson($context.result)
  ```
+ Seleccione **Save**.
+ En el panel **Data types (Tipos de datos)** de la derecha, busque el campo **removeTag** recién creado en el tipo **Mutation** y, a continuación, elija **Attach (Asociar)**.
+ En **Data source name (Nombre del origen de datos)**, elija **PostDynamoDBTable**.
+ En **Configure the request mapping template (Configurar la plantilla de mapeo de solicitud)**, pegue lo siguiente:

  ```
  {
      "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 }
          }
      }
  }
  ```
+ En **Configure the response mapping template (Configurar la plantilla de mapeo de respuesta)**, pegue lo siguiente:

  ```
  $utils.toJson($context.result)
  ```
+ Seleccione **Save**.

### Llame a la API para trabajar con etiquetas
<a name="call-the-api-to-work-with-tags"></a>

Ahora que ha configurado los resolutores, AWS AppSync sabe cómo traducir las solicitudes entrantes `addTag` y las `allPostsByTag` solicitudes a `UpdateItem` DynamoDB `Scan` y a las operaciones. `removeTag`

Para probarlo, vamos a seleccionar una de las publicaciones que ha creado anteriormente. Por ejemplo, tomemos una publicación creada por `Nadia`.
+ Seleccione la pestaña **Queries (Consultas)**.
+ En el panel **Queries (Consultas)**, pegue la siguiente consulta:

  ```
  query allPostsByAuthor {
    allPostsByAuthor(
      author: "Nadia"
    ) {
      posts {
        id
        title
      }
      nextToken
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ Todas las publicaciones creadas por Nadia deben aparecer en el panel de resultados a la derecha del panel de consultas. Debería parecerse a lo que sigue:

  ```
  {
    "data": {
      "allPostsByAuthor": {
        "posts": [
          {
            "id": "10",
            "title": "The cutest dog in the world"
          },
          {
            "id": "11",
            "title": "Did you known...?"
          }
        ],
        "nextToken": null
      }
    }
  }
  ```
+ Usemos el que tiene el título `"The cutest dog in the world"`. Anote su `id`, ya que vamos a utilizarlo más adelante.

Ahora probemos a añadirle la etiqueta `dog`.
+ En el panel **Queries (Consultas)**, pegue la mutación siguiente. También tendrá que actualizar el argumento `id` con el valor que anotó anteriormente.

  ```
  mutation addTag {
    addTag(id:10 tag: "dog") {
      id
      title
      tags
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ La publicación se actualiza con la nueva etiqueta.

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

Puede agregar más etiquetas, de este modo:
+ Actualice la mutación para cambiar el argumento `tag` a `puppy`.

  ```
  mutation addTag {
    addTag(id:10 tag: "puppy") {
      id
      title
      tags
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ La publicación se actualiza con la nueva etiqueta.

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

También puede eliminar etiquetas:
+ En el panel **Queries (Consultas)**, pegue la mutación siguiente. También tendrá que actualizar el argumento `id` con el valor que anotó anteriormente.

  ```
  mutation removeTag {
    removeTag(id:10 tag: "puppy") {
      id
      title
      tags
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ La publicación se actualiza y la etiqueta `puppy` se elimina.

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

También puede buscar todas las publicaciones que tengan una etiqueta:
+ En el panel **Queries (Consultas)**, pegue la siguiente consulta:

  ```
  query allPostsByTag {
    allPostsByTag(tag: "dog") {
      posts {
        id
        title
        tags
      }
      nextToken
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ Se devolverán todas las publicaciones que tenga la etiqueta `dog`, de este modo:

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

## Uso de listas y mapas
<a name="using-lists-and-maps"></a>

Además de usar conjuntos de DynamoDB, también puede usar las listas y mapas de DynamoDB para modelar datos complejos con un único objeto.

Vamos a añadir la capacidad de agregar comentarios a las publicaciones. Esto se modelará como una lista de objetos de mapa para el objeto `Post` en DynamoDB.

 **Nota:** en una aplicación real, los comentarios se incluirían en su propia tabla. Para este tutorial, nos limitaremos a añadirlos a la tabla `Post`.
+ Elija la pestaña **Schema (Esquema)**.
+ En el panel **Schema (Esquema)**, agregue un nuevo tipo `Comment` de este modo:

  ```
  type Comment {
      author: String!
      comment: String!
  }
  ```
+ En el panel **Schema (Esquema)** modifique el tipo `Post` para agregar un nuevo campo `comments`, de este modo:

  ```
  type Post {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int!
    downs: Int!
    version: Int!
    tags: [String!]
    comments: [Comment!]
  }
  ```
+ En el panel **Schema (Esquema)** modifique el tipo `Mutation` para agregar una nueva mutación `addComment` de este modo:

  ```
  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!
  }
  ```
+ Seleccione **Save**.
+ En el panel **Data types (Tipos de datos)** de la derecha, busque el campo **addComment** recién creado en el tipo **Mutation** y, a continuación, elija **Attach (Asociar)**.
+ En **Data source name (Nombre del origen de datos)**, elija **PostDynamoDBTable**.
+ En **Configure the request mapping template (Configurar la plantilla de mapeo de solicitud)**, pegue lo siguiente:

  ```
  {
    "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)
      }
    }
  }
  ```

  Esta expresión de actualización agregará una lista con nuestro nuevo comentario a la lista `comments` existente. Si la lista no existe todavía, se creará.
+ En **Configure the response mapping template (Configurar la plantilla de mapeo de respuesta)**, pegue lo siguiente:

  ```
  $utils.toJson($context.result)
  ```
+ Seleccione **Save**.

### Llame a la API para añadir un comentario
<a name="call-the-api-to-add-a-comment"></a>

Ahora que ha configurado los resolutores, AWS AppSync sabe cómo convertir las `addComment` solicitudes entrantes en operaciones de DynamoDB`UpdateItem`.

Vamos a probarlo añadiendo un comentario a la misma publicación a la que añadimos las etiquetas.
+ Seleccione la pestaña **Queries (Consultas)**.
+ En el panel **Queries (Consultas)**, pegue la siguiente consulta:

  ```
  mutation addComment {
    addComment(
      id:10
      author: "Steve"
      comment: "Such a cute dog."
    ) {
      id
      comments {
        author
        comment
      }
    }
  }
  ```
+ Elija **Execute query (Ejecutar consulta)** (el botón de reproducción naranja).
+ Todas las publicaciones creadas por Nadia deben aparecer en el panel de resultados a la derecha del panel de consultas. Debería parecerse a lo que sigue:

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

Si ejecuta la solicitud varias veces, se agregarán varios comentarios a la lista.

## Conclusión
<a name="conclusion"></a>

En este tutorial, ha creado una API que nos permite manipular objetos Post en DynamoDB mediante GraphQL AWS AppSync . Para obtener más información, consulte la [Referencia de plantillas de mapeo de solucionador para Elasticsearch](resolver-mapping-template-reference.md#aws-appsync-resolver-mapping-template-reference).

Para limpiar, puedes eliminar la API de AppSync GraphQL de la consola.

Para eliminar la tabla de DynamoDB y el rol de IAM que creó para este tutorial, puede ejecutar lo siguiente para eliminar la pila o visitar `AWSAppSyncTutorialForAmazonDynamoDB` la consola y eliminar CloudFormation la pila:

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

# Uso de AWS Lambda resolutores en AWS AppSync
<a name="tutorial-lambda-resolvers"></a>

**nota**  
Ahora admitimos de forma básica el tiempo de ejecución APPSYNC\$1JS y su documentación. Considere la opción de utilizar el tiempo de ejecución APPSYNC\$1JS y sus guías [aquí](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html).

Puedes usar AWS Lambda with AWS AppSync para resolver cualquier campo de GraphQL. Por ejemplo, una consulta de GraphQL podría enviar una llamada a una instancia de Amazon Relational Database Service (Amazon RDS), y una mutación de GraphQL podría escribir en un flujo de Amazon Kinesis. En esta sección, veremos cómo puede escribir una función de lambda que ejecute la lógica de negocio en función de la invocación de una operación de campo de GraphQL.

## Crear una función de Lambda
<a name="create-a-lam-function"></a>

En el siguiente ejemplo se muestra una función de Lambda escrita en `Node.js` que realiza distintas operaciones con publicaciones de blogs como parte de una aplicación de publicaciones de blog.

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

Esta función de Lambda recupera una publicación por identificador, añade una publicación, recupera una lista de publicaciones y recupera publicaciones relacionadas para una publicación determinada.

 **Nota:** La función de Lambda utiliza la instrucción `switch` en `event.field` para determinar qué campo se está resolviendo en ese momento.

Cree esta función Lambda mediante la consola de AWS administración o una AWS CloudFormation pila. Para crear la función a partir de una CloudFormation pila, puede utilizar el siguiente comando 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
```

También puedes lanzar la CloudFormation pila en la AWS región EE.UU. Oeste (Oregón) de tu AWS cuenta desde aquí:

[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)

## Configure un origen de datos para Lambda
<a name="configure-data-source-for-lamlong"></a>

Una vez creada la función de Lambda, vaya a la API de GraphQL en la consola de AWS AppSync y elija la pestaña **Orígenes de datos**.

Elija **Crear origen de datos**, introduzca un **Nombre de origen de datos** fácil de recordar (por ejemplo, **Lambda**) y, a continuación, en **Tipo de origen de datos**, elija **Función de AWS Lambda .** En **Región**, elija la misma región que en su función. (Si has creado la función a partir de la CloudFormation pila proporcionada, es probable que la función esté en **US-WEST-2**). En **ARN de función**, elija el nombre de recurso de Amazon (ARN) para la función de Lambda.

Tras elegir la función Lambda, puede crear una nueva función (de IAM) AWS Identity and Access Management (para la que se AWS AppSync asignen los permisos adecuados) o elegir una función existente que tenga la siguiente política en línea:

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

****  

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

------

También debe establecer una relación de confianza con el rol de IAM de AWS AppSync la siguiente manera:

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

****  

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

------

## Cree un esquema de GraphQL
<a name="creating-a-graphql-schema"></a>

Ahora que el origen de datos está conectado a la función de Lambda, cree un esquema de GraphQL.

En el editor de esquemas de la AWS AppSync consola, asegúrese de que el esquema coincide con el siguiente esquema:

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

## Configure solucionadores
<a name="configuring-resolvers"></a>

Ahora que ha registrado un origen de datos de Lambda y un esquema de GraphQL válido, puede conectar sus campos de GraphQL al origen de datos de Lambda utilizando solucionadores.

Para crear un solucionador, necesitará plantillas de mapeo. Para obtener más información sobre las plantillas de mapeo, consulte [Resolver Mapping Template Overview](resolver-mapping-template-reference-overview.md#aws-appsync-resolver-mapping-template-reference-overview).

Para obtener más información sobre las plantillas de mapeo de Lambda, consulte [Resolver mapping template reference for Lambda](resolver-mapping-template-reference-lambda.md#aws-appsync-resolver-mapping-template-reference-lambda).

En este paso, debe asociar un solucionador a la función de Lambda para los siguientes campos: `getPost(id:ID!): Post`, `allPosts: [Post]`, `addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!` y `Post.relatedPosts: [Post]`.

En el editor de esquemas de la AWS AppSync consola, en la parte derecha, selecciona **Attach Resolver** for`getPost(id:ID!): Post`.

Luego, en el **menú Acción**, seleccione **Actualizar tiempo de ejecución** y, a continuación, elija **Solucionador de unidades (solo VTL)**.

Después, elija el origen de datos de Lambda. En la sección de la **plantilla de mapeo de solicitud**, elija **Invoke And Forward Argumentos (Invocar y reenviar argumentos)**.

Modifique el objeto `payload` para agregar el nombre de campo. La plantilla debe tener el aspecto siguiente:

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

En la sección **Plantilla de mapeo de respuestas**, elija **Devolver resultado Lambda**.

En este caso utilice la plantilla base tal y como está. Debe parecerse a lo siguiente:

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

Seleccione **Save**. Acaba de asociar su primer solucionador. Repita esta operación para el resto de los campos como se indica a continuación:

Para la plantilla de mapeo de solicitudes `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)
    }
}
```

Para la plantilla de mapeo de respuestas `addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!`:

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

Para la plantilla de mapeo de solicitudes `allPosts: [Post]`:

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

Para la plantilla de mapeo de respuestas `allPosts: [Post]`:

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

Para la plantilla de mapeo de solicitudes `Post.relatedPosts: [Post]`:

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

Para la plantilla de mapeo de respuestas `Post.relatedPosts: [Post]`:

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

## Pruebe la API de GraphQL
<a name="testing-your-graphql-api"></a>

Ahora que la función de Lambda está conectada a los solucionadores de GraphQL, puede ejecutar algunas mutaciones y consultas con la consola o una aplicación cliente.

En la parte izquierda de la AWS AppSync consola, selecciona **Consultas** y, a continuación, pega el siguiente código:

### Mutación 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
    }
}
```

### Consulta getPost
<a name="getpost-query"></a>

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

### Consulta allPosts
<a name="allposts-query"></a>

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

## Devolución de errores
<a name="returning-errors"></a>

Cualquier resolución de campo dada puede producir un error. Con AWS AppSync, puede generar errores de las siguientes fuentes:
+ Plantilla de mapeo de solicitudes o de respuestas
+ Función de Lambda

### Desde la plantilla de mapeo
<a name="from-the-mapping-template"></a>

Para generar errores intencionados, puede utilizar el método de ayuda `$utils.error` de la plantilla de Velocity Template Language (VTL). Indique como argumentos un mensaje en `errorMessage`, un tipo en `errorType` y un valor opcional en `data`. El argumento `data` es útil para devolver datos adicionales al cliente cuando se produce un error. El objeto `data` se añade a `errors` en la respuesta final de GraphQL.

En el siguiente ejemplo se muestra cómo usarlo en la plantilla de mapeo de respuestas `Post.relatedPosts: [Post]`:

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

Así se obtiene una respuesta de GraphQL similar a la siguiente:

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

Donde `allPosts[0].relatedPosts` es *null* debido al error y `errorMessage`, `errorType` y `data` se incluyen en el objeto `data.errors[0]`.

### Desde la función de Lambda
<a name="from-the-lam-function"></a>

AWS AppSync también entiende los errores que arroja la función Lambda. El modelo de programación de Lambda permite generar errores *gestionados*. Si la función Lambda arroja un error, AWS AppSync no resuelve el campo actual. La respuesta solo incluirá el mensaje de error que devuelva Lambda. Actualmente no es posible devolver datos adicionales al cliente generando un error desde la función de Lambda.

 **Nota**: Si la función Lambda genera un error *no controlado*, AWS AppSync utiliza el mensaje de error establecido por Lambda.

La siguiente función de Lambda genera un error:

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

Así se obtiene una respuesta de GraphQL similar a la siguiente:

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

## Caso de uso avanzado: agrupación en lotes
<a name="advanced-use-case-batching"></a>

La función de Lambda de este ejemplo tiene un campo `relatedPosts` que devuelve una lista de publicaciones relacionadas para una publicación determinada. En las consultas del ejemplo, la invocación al campo `allPosts` desde la función de Lambda devuelve cinco publicaciones. Dado que hemos especificado que también queremos resolver `relatedPosts` para cada publicación obtenida, la operación del campo `relatedPosts` se invoca cinco veces.

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

Aunque no parezca mucho en este ejemplo concreto, esta sobrecarga compuesta puede perjudicar rápidamente a la aplicación.

Si quisiéramos obtener `relatedPosts` otra vez para todos los elementos de `Posts` en la misma consulta, el número de invocaciones aumentaría exponencialmente.

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

En esta consulta relativamente simple, AWS AppSync invocaría la función Lambda 1 \$1 5 \$1 25 = 31 veces.

Se trata de una situación bastante habitual que a menudo se denomina "problema N\$11", (en nuestro caso, N = 5) y puede causar un aumento de la latencia y del costo de la aplicación.

Una forma de solucionarlo es agrupar por lotes las solicitudes de solucionador de campo similares. En este ejemplo, en lugar de hacer que la función de Lambda obtenga una lista de publicaciones relacionadas con una publicación individual determinada, hacemos que obtenga una lista de publicaciones relacionadas con un lote de publicaciones dado.

Para ilustrarlo modificaremos el solucionador `Post.relatedPosts: [Post]` de modo que utilice lotes.

En el lado derecho de la AWS AppSync consola, elija la resolución existente`Post.relatedPosts: [Post]`. Cambie la plantilla de mapeo de solicitud por lo siguiente:

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

Observe que solo cambia el campo `operation` de `Invoke` a `BatchInvoke`. El campo de carga es ahora una matriz que contiene lo que especifique la plantilla. En este ejemplo, la función de Lambda recibe lo siguiente como entrada:

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

Cuando se especifica `BatchInvoke` en la plantilla de mapeo de solicitudes, la función de Lambda recibe una lista de solicitudes y devuelve una lista de resultados.

En concreto, la lista de resultados debe coincidir con el tamaño y el orden de las entradas de carga útil de la solicitud para que AWS AppSync los resultados coincidan en consecuencia.

En este ejemplo de agrupación en lotes, la función de Lambda devuelve un lote de resultados de este modo:

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

La función de Lambda siguiente escrita en Node.js ilustra esta funcionalidad de agrupación en lotes para el campo `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;
    }
};
```

### Devolución de errores individuales
<a name="returning-individual-errors"></a>

Los ejemplos anteriores muestran cómo devolver un único error desde la función de Lambda o generar un error desde las plantillas de mapeo. En las invocaciones en lotes, la generación de un error desde la función de Lambda marca como fallido todo el lote. Esto puede ser adecuado en situaciones concretas donde se haya producido un error irrecuperable, como, por ejemplo, un error de conexión a un almacén de datos. Sin embargo, en los casos en los que algunos elementos del lote se ejecutan correctamente y otros fallan, es posible devolver tanto los errores como los datos válidos. Dado AWS AppSync que la respuesta del lote requiere que se enumeren los elementos que coincidan con el tamaño original del lote, debe definir una estructura de datos que pueda diferenciar los datos válidos de los errores.

Por ejemplo, si se espera que la función de Lambda devuelva un lote de publicaciones relacionadas, podría optar por devolver una lista de objetos `Response` en la que cada objeto tenga los campos opcionales *data*, *errorMessage* y *errorType*. Si el campo *errorMessage* está presente, significa que se ha producido un error.

El código siguiente muestra cómo podría actualizar la función de 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;
    }
};
```

En este ejemplo, la plantilla de mapeo de respuestas siguiente analiza cada elemento de la función de Lambda y genera los errores que se produzcan:

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

Este ejemplo devuelve una respuesta de GraphQL similar a la siguiente:

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

### Configuración del tamaño máximo de agrupación en lotes
<a name="configure-max-batch-size"></a>

De forma predeterminada, cuando se usa`BatchInvoke`, AWS AppSync envía solicitudes a la función Lambda en lotes de hasta cinco elementos. Puede configurar el tamaño máximo de lote de sus solucionadores de Lambda.

Para configurar el tamaño máximo de procesamiento por lotes en una resolución, utilice el siguiente comando en (): 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
```

**nota**  
Al proporcionar una plantilla de mapeo de solicitudes, debe usar la operación `BatchInvoke` para usar la agrupación en lotes.

También puede utilizar el siguiente comando para habilitar y configurar la agrupación en lotes en solucionadores de Direct Lambda:

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

### Configuración del tamaño máximo de agrupación en lotes con plantillas de VTL
<a name="configure-max-batch-size-vtl"></a>

Para los solucionadores de Lambda que tienen plantillas en solicitudes de VTL, el tamaño máximo del lote no tendrá ningún efecto a menos que lo hayan especificado directamente como una operación `BatchInvoke` en VTL. Del mismo modo, si realiza una mutación de nivel superior, no se realiza la agrupación en lotes de las mutaciones porque la especificación GraphQL requiere que las mutaciones paralelas se ejecuten secuencialmente.

Por ejemplo, tomemos las mutaciones siguientes:

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

Con la primera mutación, podemos crear 10 `Items`, como se muestra en el siguiente fragmento:

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

En este ejemplo, los `Items` no se agruparán en un grupo de 10 aunque el tamaño máximo del lote esté establecido en 10 en el solucionador de Lambda. En su lugar, se ejecutarán secuencialmente de acuerdo con la especificación GraphQL.

Para realizar una mutación por lotes real, puede seguir el siguiente ejemplo utilizando la segunda mutación:

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

Para obtener más información sobre el uso de agrupaciones en lotes con solucionadores de Direct Lambda, consulte [Solucionadores de Lambda directos](resolver-mapping-template-reference-lambda.md#direct-lambda-resolvers).

# Uso de resolutores OpenSearch de Amazon Service en AWS AppSync
<a name="tutorial-elasticsearch-resolvers"></a>

**nota**  
Ahora admitimos de forma básica el tiempo de ejecución APPSYNC\$1JS y su documentación. Considere la opción de utilizar el tiempo de ejecución APPSYNC\$1JS y sus guías [aquí](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html).

AWS AppSync admite el uso de Amazon OpenSearch Service desde dominios que hayas aprovisionado en tu propia AWS cuenta, siempre que no existan dentro de una VPC. Después de aprovisionar los dominios, puede conectarse a ellos con un origen de datos. En ese momento, puede configurar un solucionador en el esquema para que realice operaciones de GraphQL como consultas, mutaciones y suscripciones. Este tutorial le guiará a lo largo de algunos ejemplos comunes.

Para obtener más información, consulte la [referencia de plantillas de mapeo de Resolver](resolver-mapping-template-reference-elasticsearch.md#aws-appsync-resolver-mapping-template-reference-elasticsearch) para. OpenSearch

## Configuración en un clic
<a name="one-click-setup"></a>

Para configurar automáticamente un punto final de GraphQL con AWS AppSync Amazon OpenSearch Service configurado, puedes usar esta AWS CloudFormation plantilla:

[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)

Una vez completada la AWS CloudFormation implementación, puedes pasar directamente a [ejecutar consultas y mutaciones de GraphQL](#tutorial-elasticsearch-resolvers-perform-queries-mutations).

## Cree un nuevo dominio OpenSearch de servicio
<a name="create-a-new-es-domain"></a>

Para empezar con este tutorial, necesitas un dominio de OpenSearch servicio existente. Si todavía no tiene uno, puede utilizar la siguiente muestra. Tenga en cuenta que la creación de un dominio de OpenSearch servicio puede tardar hasta 15 minutos antes de que pueda pasar a integrarlo con una fuente de AWS AppSync datos.

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

Puedes lanzar la siguiente AWS CloudFormation pila en la región US West 2 (Oregón) de tu AWS cuenta:

 [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)

## Configure la fuente de datos para el OpenSearch servicio
<a name="configure-data-source-for-es"></a>

Una vez creado el dominio de OpenSearch servicio, dirígete a tu API de AWS AppSync GraphQL y selecciona la pestaña **Fuentes de datos**. Elija **Nuevo** y escriba un nombre fácil de recordar para el origen de datos, por ejemplo “oss”. A continuación, elige el ** OpenSearch dominio de Amazon** como **tipo de fuente de datos**, elige la región correspondiente y verás tu dominio de OpenSearch servicio en la lista. Después de seleccionarlo, puede crear un nuevo rol y AWS AppSync asignarle los permisos correspondientes, o puede elegir un rol existente, que tenga la siguiente política en línea:

------
#### [ 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/*"
            ]
        }
    ]
}
```

------

También tendrás que establecer una relación de confianza AWS AppSync para ese rol:

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

****  

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

------

Además, el dominio del OpenSearch servicio tiene su propia **política de acceso** que puedes modificar a través de la consola de Amazon OpenSearch Service. Deberás añadir una política similar a la siguiente, con las acciones y los recursos adecuados para el dominio del OpenSearch servicio. Tenga en cuenta que la función **principal** será la de fuente de AppSync datos y, si deja que la consola la cree, podrá encontrarla en la consola de 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/*"
        }
    ]
}
```

------

## Conexión de un solucionador
<a name="connecting-a-resolver"></a>

Ahora que la fuente de datos está conectada a tu dominio de OpenSearch servicio, puedes conectarla a tu esquema de GraphQL con un solucionador, como se muestra en el siguiente ejemplo:

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

Observe que hay un tipo `Post` definido por el usuario con un campo `id`. En los siguientes ejemplos, suponemos que hay un proceso (que se puede automatizar) para colocar este tipo en tu dominio de OpenSearch servicio, que se asignaría a una raíz de ruta de`/post/_doc`, donde `post` está el índice. A partir de esta ruta raíz, puede realizar búsquedas de documentos individuales, búsquedas de comodín con `/id/post*` o búsquedas en varios documentos con la ruta `/post/_search`. Por ejemplo, si tiene otro tipo llamado `User`, puede indexar documentos bajo un nuevo índice llamado `user`, y luego realizar búsquedas con una **ruta** de `/user/_search`. 

En el editor de esquemas de la AWS AppSync consola, modifique el `Posts` esquema anterior para incluir una `searchPosts` consulta:

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

Guarde el esquema. En la parte derecha, en `searchPosts`, elija **Attach resolver (Asociar solucionador)**. En el **menú Acción**, seleccione **Actualizar tiempo de ejecución** y, a continuación, elija **Solucionador de unidades (solo VTL)**. A continuación, elija la fuente OpenSearch de datos del servicio. En la sección de **plantilla de mapeo de solicitud**, seleccione el menú desplegable de **Query posts (Consultar publicaciones)** para obtener una plantilla base. Modifique la `path` para que sea `/post/_search`. Debe parecerse a lo siguiente:

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

Esto supone que el esquema anterior contiene documentos que se han indexado en OpenSearch Service en el `post` campo. Si estructura los datos de manera diferente, tendrá que realizar una actualización como corresponda.

En la sección de **plantillas de mapeo de respuestas**, debe especificar el `_source` filtro apropiado si desea recuperar los resultados de datos de una consulta de OpenSearch servicio y traducirlos a GraphQL. Utilice la plantilla siguiente:

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

## Modificación de las búsquedas
<a name="modifying-your-searches"></a>

La plantilla de mapeo de solicitud anterior realiza una consulta sencilla de todos los registros. Supongamos que desea buscar por un autor específico. Además, supongamos que desea que ese autor sea un argumento definido en la consulta de GraphQL. En el editor de esquemas de la consola de AWS AppSync, añada una consulta `allPostsByAuthor`:

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

Ahora elige **Attach resolver** y selecciona la fuente de datos del OpenSearch servicio, pero usa el siguiente ejemplo en la **plantilla de mapeo de respuestas**:

```
{
    "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)
                }
            }
        }
    }
}
```

Observe que `body` se llena con una consulta de término para el campo `author`, que se pasa desde el cliente como un argumento. Si lo desea, puede tener información previamente rellenada, como texto estándar, o incluso utilizar otras [utilidades](resolver-context-reference.md#aws-appsync-resolver-mapping-template-context-reference).

Si va a utilizar este solucionador, rellene la **plantilla de mapeo de respuesta** con la misma información que en el ejemplo anterior.

## Añadir datos al OpenSearch servicio
<a name="adding-data-to-es"></a>

Es posible que desee añadir datos a su dominio de OpenSearch servicio como resultado de una mutación de GraphQL. Se trata de un eficaz mecanismo para realizar búsquedas y para otros fines. Como puedes usar las suscripciones de GraphQL para [crear tus datos en tiempo real](aws-appsync-real-time-data.md), sirve como mecanismo para notificar a los clientes las actualizaciones de los datos en tu OpenSearch dominio de servicio.

Vuelve a la página de **esquemas** de la AWS AppSync consola y selecciona **Adjuntar resolución** para la `addPost()` mutación. Vuelva a seleccionar la fuente de datos del OpenSearch servicio y utilice la siguiente **plantilla de mapeo de respuestas** para el `Posts` esquema:

```
{
    "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)
        }
    }
}
```

Como antes, este es un ejemplo de cómo pueden estar estructurados los datos. Si los nombres de campos o índices son distintos, debe actualizar la `path` y `body` como corresponda. Este ejemplo también muestra cómo usar `$context.arguments` para rellenar la plantilla a partir de los argumentos de mutación de GraphQL.

Antes de continuar, utilice la siguiente plantilla de mapeo de respuestas, que devolverá el resultado de la operación de mutación o la información del error como salida:

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

## Recuperación de un solo documento
<a name="retrieving-a-single-document"></a>

Por último, si desea utilizar la consulta `getPost(id:ID)` en su esquema para obtener un documento individual, encuentre esta consulta en el editor de esquemas de la consola de AWS AppSync y seleccione **Attach resolver (Asociar solucionador)**. Vuelva a seleccionar la fuente de datos del OpenSearch servicio y utilice la siguiente plantilla de mapeo:

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

Dado que el valor de `path` anterior utiliza el argumento `id` con un cuerpo vacío, esto devuelve el documento individual. Sin embargo, debe utilizar la siguiente plantilla de mapeo de respuesta, ya que ahora va a obtener un único elemento y no una lista:

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

## Ejecución de consultas y mutaciones
<a name="tutorial-elasticsearch-resolvers-perform-queries-mutations"></a>

Ahora deberías poder realizar operaciones de GraphQL en tu dominio de OpenSearch servicio. Ve a la pestaña **Consultas** de la AWS AppSync consola y agrega un registro nuevo:

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

Verá el resultado de la mutación a la derecha. Del mismo modo, ahora puedes ejecutar una `searchPosts` consulta en tu dominio OpenSearch de servicio:

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

## Prácticas recomendadas
<a name="best-practices"></a>
+ OpenSearch El servicio debe ser para consultar datos, no como su base de datos principal. Es posible que desee utilizar el OpenSearch Servicio junto con Amazon DynamoDB, tal y como se describe en [Combinación](tutorial-combining-graphql-resolvers.md#aws-appsync-tutorial-combining-graphql-resolvers) de Resolvers de GraphQL.
+ Conceda acceso a su dominio únicamente si permite que el rol de AWS AppSync servicio acceda al clúster.
+ Puede comenzar con un pequeño desarrollo, con el clúster de menor costo y, a continuación, pasar a un clúster de mayor tamaño con alta disponibilidad (HA) al pasar a producción.

# Uso de solucionadores locales en AWS AppSync
<a name="tutorial-local-resolvers"></a>

**nota**  
Ahora admitimos de forma básica el tiempo de ejecución APPSYNC\$1JS y su documentación. Considere la opción de utilizar el tiempo de ejecución APPSYNC\$1JS y sus guías [aquí](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html).

AWS AppSync le permite utilizar orígenes de datos compatibles (AWS Lambda, Amazon DynamoDB o Amazon OpenSearch Service) para realizar diversas operaciones. Sin embargo, en determinados casos, es posible que no sea necesario realizar una llamada a un origen de datos admitido.

Aquí es donde un solucionador local es útil. En lugar de llamar a un origen de datos remoto, el solucionador local simplemente **reenviará** los resultados de la plantilla de mapeo de solicitudes a la plantilla de mapeo de respuestas. La resolución de campo no sale de AWS AppSync.

Los solucionadores locales son útiles para varios casos de uso. El caso de uso más habitual consiste en publicar notificaciones sin activar una llamada de origen de datos. Para demostrar este caso de uso vamos a crear una aplicación buscapersonas con la que los usuarios puedan enviarse notificaciones. Este ejemplo utiliza *suscripciones*, de modo que si no está familiarizado con las *suscripciones*, puede seguir el tutorial de [datos en tiempo real](aws-appsync-real-time-data.md).

## Creación de la aplicación buscapersonas
<a name="create-the-paging-application"></a>

En nuestra aplicación buscapersonas, los clientes pueden suscribirse a una bandeja de entrada y enviar notificaciones a otros clientes. Cada notificación contiene un mensaje. El esquema es el siguiente:

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

Asociemos un solucionador al campo `Mutation.page`. En el panel **Schema (Esquema)**, haga clic en *Attach Resolver (Asociar solucionador)* junto a la definición de campo en el panel derecho. Cree un nuevo origen de datos de tipo *None (Ninguno)* y asígnele el nombre *PageDataSource*.

Para la plantilla de mapeo de solicitud, escriba:

```
{
  "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()"
  }
}
```

Y para la plantilla de mapeo de respuesta, seleccione la opción predeterminada *Forward the result (Reenviar el resultado)*. Guarde el solucionador. Su aplicación ya está lista, ya puede ejecutarla.

## Envío y suscripción a notificaciones
<a name="send-and-subscribe-to-pages"></a>

Para que los clientes reciban notificaciones, primero deben estar suscritos a una bandeja de entrada.

En el panel **Queries (Consultas)** vamos a ejecutar la suscripción `inbox`:

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

 *Nadia* recibirá notificaciones siempre que se invoque la mutación `Mutation.page`. Vamos a invocar la mutación ejecutándola:

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

Acabamos de demostrar el uso de solucionadores locales enviando una notificación y recibiéndola sin salir de AWS AppSync.

# Combinación de resolutores de GraphQL en AWS AppSync
<a name="tutorial-combining-graphql-resolvers"></a>

**nota**  
Ahora admitimos de forma básica el tiempo de ejecución APPSYNC\$1JS y su documentación. Considere la opción de utilizar el tiempo de ejecución APPSYNC\$1JS y sus guías [aquí](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html).

Los solucionadores y los campos de un esquema de GraphQL mantienen una relación 1:1 con un alto grado de flexibilidad. Puesto que se configura un origen de datos para cada solucionador independientemente de un esquema, tiene la posibilidad de resolver o manipular los tipos de GraphQL mediante orígenes de datos diferentes, combinándolos en el esquema que mejor se adapte a sus necesidades.

Los siguientes escenarios de ejemplo muestran cómo mezclar y hacer corresponder los orígenes de datos en su esquema. Antes de empezar, le recomendamos que se familiarice con la configuración de fuentes de datos y resolutores para AWS Lambda Amazon DynamoDB OpenSearch y Amazon Service, tal y como se describe en los tutoriales anteriores.

## Esquema de ejemplo
<a name="example-schema"></a>

El siguiente esquema tiene un tipo `Post` con 3 operaciones `Query` y 3 operaciones `Mutation` definidas:

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

En este ejemplo tendría que asociar un total de 6 solucionadores. Una opción sería hacer que todas procedieran de una tabla de Amazon DynamoDB, denominada `Posts`, donde `AllPosts` ejecuta un análisis y `searchPosts` ejecuta una consulta, tal y como se describe en [DynamoDB Resolver Mapping Template Reference](resolver-mapping-template-reference-dynamodb.md#aws-appsync-resolver-mapping-template-reference-dynamodb). Sin embargo, existen alternativas para satisfacer las necesidades de su empresa, como hacer que estas consultas de GraphQL se resuelvan desde Lambda o Service. OpenSearch 

## Modificación de datos mediante solucionadores
<a name="alter-data-through-resolvers"></a>

Puede que sea necesario enviar a los clientes resultados de una base de datos como DynamoDB (o Amazon Aurora) con algunos de los atributos modificados. Esto puede deberse al formato de los tipos de datos, por ejemplo diferencias de marcas de tiempo en los clientes, o para gestionar problemas de compatibilidad con versiones anteriores. A título ilustrativo, en el siguiente ejemplo mostramos una función AWS Lambda que manipula las votaciones a favor y en contra de las publicaciones del blog asignándoles números aleatorios cada vez que se invoca el solucionador de GraphQL:

```
'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);
    });
};
```

Se trata de una función de Lambda perfectamente válida y puede asociarse al campo `AllPosts` del esquema de GraphQL para que cualquier consulta que devuelva todos los resultados obtenga números aleatorios para los votos a favor y en contra.

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

Para algunas aplicaciones, puede realizar mutaciones o consultas de búsqueda sencillas en DynamoDB y hacer que un proceso en segundo plano transfiera los documentos a Service. OpenSearch A continuación, solo tiene que adjuntar el `searchPosts` Resolver a la fuente de datos del OpenSearch servicio y devolver los resultados de la búsqueda (a partir de datos que se originaron en DynamoDB) mediante una consulta de GraphQL. Esto puede ser extremadamente útil al añadir operaciones de búsqueda avanzada a sus aplicaciones, como palabras clave, coincidencias parciales o incluso búsquedas geoespaciales. La transferencia de datos desde DynamoDB puede realizarse mediante un proceso ETL o, alternativamente, puede transmitir desde DynamoDB mediante Lambda. Puede lanzar un ejemplo completo de esto con la siguiente AWS CloudFormation pila en la región US West 2 (Oregón) de su cuenta: AWS 

[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) 

El esquema de este ejemplo le permite añadir publicaciones mediante un solucionador de DynamoDB, de este modo:

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

Esto escribe los datos en DynamoDB, que luego los transmite a través de Lambda a OpenSearch Amazon Service, donde puede buscar todas las publicaciones por diferentes campos. Por ejemplo, dado que los datos están en Amazon OpenSearch Service, puedes buscar en el autor o en los campos de contenido con texto de formato libre, incluso con espacios, de la siguiente manera:

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

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

Como los datos se escriben directamente en DynamoDB, aún puede realizar operaciones eficientes de búsqueda de lista o de elemento en la tabla con las consultas `allPosts{...}` y `singlePost{...}`. Esta pila utiliza el siguiente código de ejemplo para los flujos de DynamoDB:

 **Nota:** este código es solo un ejemplo.

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

A continuación, puede usar las transmisiones de DynamoDB para adjuntarlas a una tabla de DynamoDB con una clave principal de, y cualquier cambio en la fuente `id` de DynamoDB se transmitirá a su dominio de servicio. OpenSearch Para obtener más información sobre cómo configurarlo, consulte la [documentación de flujos de DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.Lambda.html).

# Uso de operaciones por lotes de DynamoDB en AWS AppSync
<a name="tutorial-dynamodb-batch"></a>

**nota**  
Ahora admitimos de forma básica el tiempo de ejecución APPSYNC\$1JS y su documentación. Considere la opción de utilizar el tiempo de ejecución APPSYNC\$1JS y sus guías [aquí](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html).

AWS AppSync admite el uso de operaciones por lotes de Amazon DynamoDB en una o más tablas de una sola región. Las operaciones admitidas son `BatchGetItem`, `BatchPutItem` y `BatchDeleteItem`. Estas características de AWS AppSync le puede realizar tareas como las siguientes:
+ Transferir una lista de claves en una sola consulta y obtener los resultados desde una tabla
+ Leer registros de una o varias tablas en una única consulta
+ Escribir registros de forma masiva en una o varias tablas
+ Escribir o eliminar condicionalmente registros en varias tablas que pueden tener una relación

El uso de operaciones por lotes con AWS AppSync DynamoDB es una técnica avanzada que requiere un poco más de reflexión y conocimiento de las operaciones de backend y las estructuras de las tablas. Además, las operaciones por lotes AWS AppSync presentan dos diferencias clave con respecto a las operaciones sin lotes:
+ El rol del origen de datos debe tener permisos para todas las tablas a las que el solucionador obtiene acceso.
+ La especificación de tabla de un solucionador forma parte de la plantilla de mapeo.

## Permisos
<a name="permissions"></a>

Al igual que otros solucionadores, debe crear una fuente de datos AWS AppSync y crear un rol o usar uno existente. Dado que las operaciones por lotes requieren diferentes permisos para las tablas de DynamoDB, debe conceder los permisos de rol configurados para las acciones de lectura o escritura:

------
#### [ 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/*"
            ]
        }
    ]
}
```

------

 **Nota**: Los roles están vinculados a las fuentes de datos en AWS AppSync una fuente de datos y los resolutores de los campos se invocan en función de una fuente de datos. Los orígenes de datos configurados para recuperar información de DynamoDB solo tienen especificada una tabla a fin de que la configuración siga siendo sencilla. Por lo tanto, al realizar una operación por lotes en varias tablas con un único solucionador, que es una tarea más avanzada, debe conceder al rol de ese origen de datos acceso a cualquier tabla con la que el solucionador vaya a interactuar. Esto se hace en el campo **Resource (Recurso)** de la política de IAM anterior. La configuración de las tablas en las que se realizan llamadas por lotes se lleva a cabo en la plantilla de solucionador, que se describe a continuación.

## Origen de datos
<a name="data-source"></a>

En aras de la simplicidad, vamos a utilizar el mismo origen de datos para todos los solucionadores que se utilizan en este tutorial. En la pestaña **Fuentes de datos**, cree una nueva fuente de datos de DynamoDB y asígnele un nombre. **BatchTutorial** El nombre de la tabla pueden ser uno cualquiera, ya que los nombre de las tablas se especifican como parte de la plantilla de mapeo de solicitud para las operaciones por lotes. Llamaremos a la tabla `empty`.

En este tutorial funcionará cualquier rol con la siguiente política insertada:

## Lotes en una única tabla
<a name="single-table-batch"></a>

**aviso**  
`BatchPutItem` y `BatchDeleteItem` no se admiten cuando se utilizan con la detección y resolución de conflictos. Esta configuración debe estar deshabilitada para evitar posibles errores.

En este ejemplo, supongamos que tiene una sola tabla denominada **Posts** en la que desea añadir y eliminar elementos mediante operaciones por lotes. Utilice el siguiente esquema y tenga en cuenta que, para la consulta, pasaremos una lista de: IDs

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

Asocie un solucionador al campo `batchAdd()` con la siguiente **plantilla de mapeo de solicitud**. Esto toma automáticamente cada elemento con el tipo de GraphQL `input PostInput` y crea un mapa, lo que es necesario para la operación `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)
    }
}
```

En este caso, la **plantilla de mapeo de respuesta** simplemente traslada la información, pero el nombre de la tabla se anexa como `..data.Posts` al objeto contexto de este modo:

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

Ahora vaya a la página **Queries (Consultas)** de la consola de AWS AppSync y ejecute la siguiente mutación **batchAdd**:

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

Los resultados deben aparecer en la pantalla y puede validarlos de manera independiente en la consola de DynamoDB que ambos valores han escrito en la tabla **Posts**.

A continuación, asocie un solucionador al campo `batchGet()` con la siguiente **plantilla de mapeo de solicitud**. Esto toma automáticamente cada elemento del tipo de GraphQL `ids:[]` y crea un mapa que es necesario para la operación `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"}
                }
        }
    }
}
```

De nuevo, la **plantilla de mapeo de respuesta** se limita a transferir la información, pero añadiendo otra vez el nombre de la tabla como `..data.Posts` al objeto contexto:

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

Vuelva ahora a la página **Queries (Consultas)** de la consola de AWS AppSync y ejecute consulta **batchGet** siguiente:

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

Esto debe devolver los resultados para los dos valores `id` que ha añadido anteriormente. Observe que se devuelve un valor `null` para el `id` con valor `3`. Esto se debe a que aún no hay ningún registro en la tabla **Posts** con ese valor. Tenga en cuenta también que AWS AppSync devuelve los resultados en el mismo orden que las claves transferidas a la consulta, lo cual es una función adicional que AWS AppSync funciona en su nombre. Por lo tanto, si cambia a `batchGet(ids:[1,3,2)`, verá que el orden cambia. También sabrá por qué `id` devuelve un valor `null`.

Por último, asocie un solucionador al campo `batchDelete()` con la siguiente **plantilla de mapeo de solicitud**. Esto toma automáticamente cada elemento del tipo de GraphQL `ids:[]` y crea un mapa que es necesario para la operación `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)
    }
}
```

De nuevo, la **plantilla de mapeo de respuesta** se limita a transferir la información, pero añadiendo otra vez el nombre de la tabla como `..data.Posts` al objeto contexto:

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

Ahora vuelva a la página **Queries (Consultas)** de la consola de AWS AppSync y ejecute la siguiente mutación **batchDelete**:

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

Ahora se eliminarán los registros con `id` `1` y `2`. Si vuelve a ejecutar la consulta `batchGet()` anterior, devolverá `null`.

## Lotes en varias tablas
<a name="multi-table-batch"></a>

**aviso**  
`BatchPutItem` y `BatchDeleteItem` no se admiten cuando se utilizan con la detección y resolución de conflictos. Esta configuración debe estar deshabilitada para evitar posibles errores.

AWS AppSync también le permite realizar operaciones por lotes en todas las tablas. Vamos a crear una aplicación más compleja. Imagine que queremos crear una aplicación de salud de mascotas, con sensores que comunican la ubicación y temperatura corporal de la mascota. Los sensores funcionan con pilas e intentan conectarse a la red cada pocos minutos. Cuando un sensor establece una conexión, envía sus lecturas a nuestra AWS AppSync API. A continuación, los disparadores analizan los datos para presentar un panel al propietario de la mascota. Concentrémonos en representar las interacciones entre el sensor y el almacén de datos de backend.

Como requisito previo, vamos a crear primero dos tablas de DynamoDB: **locationReadings** almacenará las lecturas de ubicación de los sensores y **temperatureReadings** almacenará las lecturas de temperatura de los sensores. Ambas tablas comparten la misma estructura de clave principal: `sensorId (String)` es la clave de partición y `timestamp (String)` la clave de ordenación.

Vamos a utilizar el siguiente esquema de 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 - Grabación de las lecturas del sensor
<a name="batchputitem-recording-sensor-readings"></a>

Nuestros sensores tiene que poder enviar sus lecturas cuando se conectan a Internet. El campo de GraphQL `Mutation.recordReadings` es la API que utilizarán para hacerlo. Vamos a asociar un solucionador para activar nuestra API.

Seleccione **Attach (Asociar)** junto al campo `Mutation.recordReadings`. En la pantalla siguiente, elija el origen de datos `BatchTutorial` creado al inicio del tutorial.

Vamos a añadir la siguiente plantilla de mapeo de solicitud:

 **Plantilla de mapeo de solicitud** 

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

Como puede ver, la operación `BatchPutItem` nos permite especificar varias tablas.

Vamos a usar la siguiente plantilla de mapeo de respuesta.

 **Plantilla de mapeo de respuesta** 

```
## 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)
```

Con operaciones por lotes, la invocación puede devolver tanto errores como resultados. Por lo tanto, podemos realizar algún control de errores adicional.

 **Nota**: el uso de `$utils.appendError()` es similar al de `$util.error()`, siendo la principal diferencia que no interrumpe la evaluación de la plantilla de mapeo. En su lugar, indica que hubo un error con el campo, pero permite evaluar la plantilla y, por tanto, devolver los datos al intermediario. Recomendamos que utilice `$utils.appendError()` cuando la aplicación tenga que devolver resultados parciales.

Guarde el solucionador y vaya a la página de **consultas** de la AWS AppSync consola. Enviemos ahora algunas lecturas de los sensores.

Ejecute la mutación siguiente:

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

Hemos enviado en una mutación 10 lecturas de sensor repartidas en dos tablas. Utilice la consola de DynamoDB para validar que los datos aparecen en las tablas **locationReadings** y **temperatureReadings**.

### BatchDeleteItem - Eliminar las lecturas del sensor
<a name="batchdeleteitem-deleting-sensor-readings"></a>

Del mismo modo, también es necesario eliminar lotes de lecturas de sensor. Vamos a utilizar el campo de GraphQL `Mutation.deleteReadings` para este fin. Seleccione **Attach (Asociar)** junto al campo `Mutation.recordReadings`. En la pantalla siguiente, elija el origen de datos `BatchTutorial` creado al inicio del tutorial.

Vamos a usar la siguiente plantilla de mapeo de solicitud.

 **Plantilla de mapeo de solicitud** 

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

La plantilla de mapeo de respuesta es la misma que la que hemos usado para `Mutation.recordReadings`.

 **Plantilla de mapeo de respuesta** 

```
## 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)
```

Guarde el solucionador y vaya a la página de **consultas** de la AWS AppSync consola. Ahora vamos a eliminar un par de lecturas de sensores.

Ejecute la mutación siguiente:

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

Valide a través de la consola de DynamoDB que estas dos lecturas se han eliminado de las tablas **locationReadings** y **temperatureReadings**.

### BatchGetItem - Recupera las lecturas
<a name="batchgetitem-retrieve-readings"></a>

Otra operación habitual en nuestra aplicación de salud de mascotas es obtener las lecturas de un sensor en un momento determinado. Vamos a asociar un solucionador al campo de GraphQL `Query.getReadings` en nuestro esquema. Seleccione **Attach (Asociar)** y, en la siguiente pantalla, elija el origen de datos `BatchTutorial` creado al inicio del tutorial.

Vamos a añadir la siguiente plantilla de mapeo de solicitud.

 **Plantilla de mapeo de solicitud** 

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

Tenga en cuenta que ahora estamos usando la **BatchGetItem**operación.

Nuestra plantilla de mapeo de respuesta será un poco diferente, ya que hemos decidido devolver una lista `SensorReading`. Demos ahora la forma deseada al resultado de la invocación.

 **Plantilla de mapeo de respuesta** 

```
## 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)
```

Guarde el solucionador y vaya a la página de **consultas** de la AWS AppSync consola. Ahora vamos a recuperar lecturas de sensores.

Ejecute la siguiente consulta:

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

Hemos demostrado con éxito el uso de las operaciones por lotes de DynamoDB mediante. AWS AppSync

## Gestión de errores
<a name="error-handling"></a>

En AWS AppSync, las operaciones de fuentes de datos a veces pueden arrojar resultados parciales. "Resultados parciales" es el término que utilizaremos para indicar que el resultado de una operación se compone de algunos datos y un error. Dado que la gestión de errores es inherentemente específica de la aplicación, AWS AppSync le da la oportunidad de gestionar los errores en la plantilla de mapeo de respuesta. El error de invocación del solucionador, si lo hay, está disponible en el contexto como `$ctx.error`. Los errores de invocación siempre incluyen un mensaje y un tipo a los que se tiene acceso como propiedades `$ctx.error.message` y `$ctx.error.type`. Durante la invocación de la plantilla de mapeo de respuestas, puede gestionar los resultados parciales de tres formas:

1. pasar por alto el error de la invocación y limitarse a devolver los datos

1. generar un error (con `$util.error(...)`) y detener la evaluación de la plantilla de mapeo de respuesta, con lo que no se devuelven datos.

1. agregar un error (con `$util.appendError(...)`) y devolver los datos

Vamos a demostrar cada una de estas posibilidades con operaciones por lotes de DynamoDB.

### Operaciones por lotes de DynamoDB
<a name="dynamodb-batch-operations"></a>

Con las operaciones por lotes de DynamoDB, es posible que un lote se complete parcialmente. Es decir, es posible que algunos de los elementos o claves solicitados se queden sin procesar. Si no AWS AppSync se puede completar un lote, se configurarán en el contexto los elementos sin procesar y un error de invocación.

Vamos a implementar la gestión de errores utilizando la configuración del campo `Query.getReadings` de la operación `BatchGetItem` de la sección anterior de este tutorial. Esta vez, supongamos que mientras se ejecutaba el campo `Query.getReadings`, la tabla de DynamoDB `temperatureReadings` agotó el desempeño aprovisionado. DynamoDB lanzó **ProvisionedThroughputExceededException**una en el segundo intento para procesar AWS AppSync los elementos restantes del lote.

El siguiente código JSON representa el contexto serializado después de la invocación por lotes de DynamoDB, pero antes de la evaluación de la plantilla de mapeo de respuesta.

```
{
  "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": []
}
```

Cabe tener en cuenta algunos aspectos del contexto:
+ el error de invocación se ha establecido en el contexto de `$ctx.error` by AWS AppSync y el tipo de error se ha establecido en **DynamoDB**:. ProvisionedThroughputExceededException
+ los resultados se mapean para cada tabla en `$ctx.result.data`, aunque haya un error
+ las claves que quedaron sin procesar están disponibles en `$ctx.result.data.unprocessedKeys`. En este caso, no AWS AppSync se pudo recuperar el elemento con la clave (SensorID:1, TimeStamp:2018-02-01T 17:21:05.000 \$1 08:00) debido a un rendimiento insuficiente de la tabla.

 **Nota**: para `BatchPutItem`, es `$ctx.result.data.unprocessedItems`. Para `BatchDeleteItem`, es `$ctx.result.data.unprocessedKeys`.

Vamos a gestionar este error de tres formas diferentes.

#### 1. Pasar por alto el error de invocación
<a name="swallowing-the-invocation-error"></a>

Si se devuelven los datos sin gestionar el error de invocación se pasa por alto el error, lo que hace que el resultado para el campo de GraphQL indicado siempre tenga éxito.

La plantilla de mapeo de respuesta que escribimos ya es conocida y solo se centra en los datos de resultado.

Plantilla de mapeo de respuestas:

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

Respuesta de 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
      }
    ]
  }
}
```

No se añadirán errores a la respuesta, ya que solo se actúa en los datos.

#### 2. Generar un error para anular la ejecución de la plantilla
<a name="raising-an-error-to-abort-the-template-execution"></a>

Cuando los errores parciales se deban tratar como errores completos desde la perspectiva del cliente, puede anular la ejecución de la plantilla para evitar la devolución de datos. El método de utilidad `$util.error(...)` consigue exactamente ese comportamiento.

Plantilla de mapeo de respuestas:

```
## 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)
```

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

Aunque podrían haberse devuelto algunos resultados de la operación por lotes de DynamoDB, decidimos generar un error que hace que el campo de GraphQL `getReadings` sea nulo y se añada el error al bloque de *errors* de la respuesta de GraphQL.

#### 3. Añadir el error para devolver tanto los datos como los errores
<a name="appending-an-error-to-return-both-data-and-errors"></a>

En algunos casos, para proporcionar una mejor experiencia al usuario, las aplicaciones pueden devolver resultados parciales e informar a sus clientes de los elementos que no se han procesado. Los clientes pueden decidir probar de nuevo o trasladar el error al usuario final. El método `$util.appendError(...)` es la utilidad que permite este comportamiento al permitir que el creador de la aplicación agregue errores al contexto sin interferir con la evaluación de la plantilla. Tras evaluar la plantilla, AWS AppSync procesará los errores de contexto añadiéndolos al bloque de errores de la respuesta de GraphQL.

Plantilla de mapeo de respuestas:

```
#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)
```

Hemos reenviado el error de invocación y el elemento unprocessedKeys dentro del bloque de errores de la respuesta de GraphQL. El campo `getReadings` también devuelve datos parciales de la tabla **locationReadings**, como puede ver en la respuesta a continuación.

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

# Realizar transacciones de DynamoDB en AWS AppSync
<a name="tutorial-dynamodb-transact"></a>

**nota**  
Ahora admitimos de forma básica el tiempo de ejecución APPSYNC\$1JS y su documentación. Considere la opción de utilizar el tiempo de ejecución APPSYNC\$1JS y sus guías [aquí](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html).

AWS AppSync admite el uso de operaciones de transacciones de Amazon DynamoDB en una o más tablas de una sola región. Las operaciones admitidas son `TransactGetItems` y `TransactWriteItems`. Al utilizar estas funciones en AWS AppSync, puede realizar tareas como:
+ Transferir una lista de claves en una sola consulta y obtener los resultados desde una tabla
+ Leer registros de una o varias tablas en una única consulta
+ Escriba los registros de transacciones en una o más tablas de all-or-nothing alguna manera
+ Ejecutar transacciones cuando se cumplan algunas condiciones

## Permisos
<a name="permissions"></a>

Al igual que otros solucionadores, debe crear una fuente de datos AWS AppSync y crear un rol o usar uno existente. Dado que las operaciones de transacciones requieren diferentes permisos para las tablas de DynamoDB, debe conceder los permisos de rol configurados para las acciones de lectura o escritura:

------
#### [ 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/*"
            ]
        }
    ]
}
```

------

 **Nota**: Los roles están vinculados a las fuentes de datos y los resolutores de los campos se invocan en función de una fuente de datos. AWS AppSync Los orígenes de datos configurados para recuperar información de DynamoDB solo tienen especificada una tabla a fin de que la configuración siga siendo sencilla. Por lo tanto, al realizar una operación de transacciones en varias tablas con un único solucionador, que es una tarea más avanzada, debe conceder al rol de ese origen de datos acceso a cualquier tabla con la que el solucionador vaya a interactuar. Esto se hace en el campo **Resource (Recurso)** de la política de IAM anterior. La configuración de las tablas en las que se realizan llamadas de transacciones se lleva a cabo en la plantilla de solucionador, que se describe a continuación.

## Origen de datos
<a name="data-source"></a>

En aras de la simplicidad, vamos a utilizar el mismo origen de datos para todos los solucionadores que se utilizan en este tutorial. En la pestaña **Fuentes de datos**, cree una nueva fuente de datos de DynamoDB y asígnele un nombre. **TransactTutorial** El nombre de la tabla pueden ser uno cualquiera, ya que los nombre de las tablas se especifican como parte de la plantilla de mapeo de solicitud para las operaciones de transacciones. Llamaremos a la tabla `empty`.

Tendremos dos tablas denominadas **savingAccounts** y **checkingAccounts**, ambas con la clave de partición `accountNumber`, y una tabla **transactionHistory** con la clave de partición `transactionId`.

En este tutorial funcionará cualquier rol con la siguiente política insertada. Sustituya `region` y `accountId` por su región y su identificador de cuenta.

------
#### [ 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/*"
            ]
        }
    ]
}
```

------

## Transacciones
<a name="transactions"></a>

Para este ejemplo, el contexto es una transacción bancaria clásica, en la que usaremos `TransactWriteItems` para:
+ Transferir dinero de cuentas de ahorro a cuentas corrientes
+ Generar nuevos registros de transacciones para cada transacción

Y, a continuación, usaremos `TransactGetItems` para recuperar los detalles de las cuentas de ahorro y las cuentas corrientes.

**aviso**  
`TransactWriteItems` no se admite cuando se utiliza con la detección y resolución de conflictos. Esta configuración debe estar deshabilitada para evitar posibles errores.

Definimos nuestro esquema GraphQL de la siguiente manera:

```
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 - Rellenar cuentas
<a name="transactwriteitems-populate-accounts"></a>

Para transferir dinero entre cuentas, tenemos que rellenar la tabla con los detalles. Para ello, utilizaremos la operación `Mutation.populateAccounts` de GraphQL.

En la sección Esquema, haga clic en **Asociar** junto a la operación `Mutation.populateAccounts`. Vaya a Solucionadores de unidades VTL y, a continuación, elija el mismo origen de datos `TransactTutorial`.

Ahora, use la siguiente plantilla de mapeo de solicitud.

 **Plantilla de mapeo de solicitud** 

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

Y la siguiente plantilla de mapeo de respuesta:

 **Plantilla de mapeo de respuesta** 

```
#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)
```

Guarde la resolución y vaya a la sección **Consultas** de la AWS AppSync consola para rellenar las cuentas.

Ejecute la mutación siguiente:

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

Hemos rellenado 3 cuentas de ahorro y 3 cuentas corrientes en una mutación.

Utilice la consola de DynamoDB para validar que los datos se muestren en las tablas **savingAccounts** y **checkingAccounts**.

### TransactWriteItems - Transfiere dinero
<a name="transactwriteitems-transfer-money"></a>

Asocie un solucionador a la mutación `transferMoney` con la siguiente **plantilla de mapeo de solicitud**. Tenga en cuenta que los valores de `amounts`, `savingAccountNumbers` y `checkingAccountNumbers` son los mismos.

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

Tendremos 3 transacciones bancarias en una sola operación de `TransactWriteItems`. Use la siguiente **plantilla de mapeo de respuesta**:

```
#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)
```

Ahora, vaya a la sección **Queries (Consultas)** de la consola de AWS AppSync y ejecute la mutación **transferMoney** de la siguiente manera:

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

Hemos enviado 2 transacciones bancarias en una mutación. Utilice la consola de DynamoDB para validar que los datos se muestren en las tablas **savingAccounts**, **checkingAccounts** y **transactionHistory**.

### TransactGetItems - Recuperar cuentas
<a name="transactgetitems-retrieve-accounts"></a>

Con el fin de recuperar los detalles de las cuentas de ahorro y cuentas corrientes en una sola solicitud transaccional, vamos a asociar un solucionador a la operación `Query.getAccounts` de GraphQL en nuestro esquema. Seleccione **Asociar**, vaya a Solucionadores de unidades VTL y, en la siguiente pantalla, elija el mismo origen de datos de `TransactTutorial` creado al inicio del tutorial. Configure las plantillas de la siguiente manera:

 **Plantilla de mapeo de solicitud** 

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

 **Plantilla de mapeo de respuesta** 

```
#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)
```

Guarde la resolución y vaya a las secciones de **consultas** de la AWS AppSync consola. Para recuperar las cuentas de ahorro y las cuentas corrientes, ejecute la siguiente consulta:

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

Hemos demostrado con éxito el uso de transacciones de DynamoDB mediante. AWS AppSync

# Uso de resolutores HTTP en AWS AppSync
<a name="tutorial-http-resolvers"></a>

**nota**  
Ahora admitimos de forma básica el tiempo de ejecución APPSYNC\$1JS y su documentación. Considere la opción de utilizar el tiempo de ejecución APPSYNC\$1JS y sus guías [aquí](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html).

AWS AppSync le permite utilizar fuentes de datos compatibles (es decir, Amazon DynamoDB AWS Lambda, Amazon Service o Amazon Aurora) para realizar diversas operaciones, además de cualquier punto de enlace HTTP arbitrario para resolver los campos de GraphQL. OpenSearch Una vez que los puntos de enlace HTTP están disponibles, puede conectar con ellos mediante un origen de datos. A continuación, puede configurar un solucionador en el esquema para realizar operaciones de GraphQL como consultas, mutaciones, y suscripciones. Este tutorial le guiará a lo largo de algunos ejemplos comunes.

En este tutorial, utilizará una API REST (creada con Amazon API Gateway y Lambda) con un punto final de GraphQL AWS AppSync .

## Configuración en un clic
<a name="one-click-setup"></a>

Si desea configurar automáticamente un punto de enlace de GraphQL AWS AppSync con un punto de enlace HTTP configurado (mediante Amazon API Gateway y Lambda), puede utilizar la siguiente plantilla: 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)

## Creación de una API de REST
<a name="creating-a-rest-api"></a>

Puedes usar la siguiente AWS CloudFormation plantilla para configurar un punto final REST que funcione para este tutorial:

[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)

La AWS CloudFormation pila realiza los siguientes pasos:

1. Configura una función Lambda que contiene la lógica de negocio del microservicio.

1. Configura una API REST de API Gateway con la siguiente combinación endpoint/method/content de tipos:


****  

| Ruta de recurso de la API | Método HTTP | Tipo de contenido admitido | 
| --- | --- | --- | 
|  /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  | 

## Creación de la API de GraphQL
<a name="creating-your-graphql-api"></a>

Para crear la API GraphQL en: AWS AppSync
+ Abre la AWS AppSync consola y selecciona **Crear API**.
+ En el nombre de la API, introduzca `UserData`.
+ Elija **Custom Schema (Esquema personalizado)**.
+ Seleccione **Crear**.

La AWS AppSync consola crea una nueva API de GraphQL para usted mediante el modo de autenticación con clave de API. Puede utilizar la consola para configurar el resto de la API de GraphQL y ejecutar consultas en ella durante el resto de este tutorial.

## Creación de un esquema de GraphQL
<a name="creating-a-graphql-schema"></a>

Ahora que tiene una API de GraphQL, vamos a crear un esquema de GraphQL. En el editor de esquemas de la AWS AppSync consola, asegúrate de que el esquema coincide con el siguiente esquema:

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

## Configure el origen de datos HTTP
<a name="configure-your-http-data-source"></a>

Para configurar el origen de datos HTTP, haga lo siguiente:
+ En la **DataSources**pestaña, elija **Nuevo** y, a continuación, escriba un nombre descriptivo para la fuente de datos (por ejemplo,`HTTP`).
+ En **Data source type (Tipo de origen de datos)**, elija **HTTP**.
+ Establezca como punto de conexión el punto de conexión de la puerta de enlace de la API que se ha creado. Asegúrese de no incluir el nombre de etapa como parte del punto de enlace.

 **Note (Nota):** En estos momentos, solo los puntos de enlace públicos son compatibles con AWS AppSync.

 **Nota:** Para obtener más información sobre las autoridades de certificación reconocidas por el AWS AppSync servicio, consulte [Autoridades de certificación (CA) reconocidas AWS AppSync por los puntos de enlace HTTPS.](http-cert-authorities.md#aws-appsync-http-certificate-authorities)

## Configuración de solucionadores
<a name="configuring-resolvers"></a>

En este paso conectará el origen de datos HTTP a la consulta **getUser**.

Para configurar el solucionador de :
+ Elija la pestaña **Schema (Esquema)**.
+ En el panel **Data types (Tipos de datos)** situado a la derecha, en el tipo **Query (Consulta)**, busque el campo **getUser** y seleccione **Attach (Asociar)**.
+ En **Data source name (Nombre del origen de datos)**, elija **HTTP**.
+ En **Configure the request mapping template (Configurar la plantilla de mapeo de solicitud)**, pegue el siguiente código:

```
{
    "version": "2018-05-29",
    "method": "GET",
    "params": {
        "headers": {
            "Content-Type": "application/json"
        }
    },
    "resourcePath": $util.toJson("/v1/users/${ctx.args.id}")
}
```
+ En **Configure the response mapping template (Configurar la plantilla de mapeo de respuesta)**, pegue el siguiente código:

```
## 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
```
+ Elija la pestaña **Query (Consulta)** y ejecute la consulta siguiente:

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

Debe obtener la siguiente respuesta:

```
{
    "data": {
        "getUser": {
            "id": "1",
            "username": "nadia"
        }
    }
}
```
+ Elija la pestaña **Schema (Esquema)**.
+ En el panel **Data types (Tipos de datos)** situado en la parte derecha, en **Mutation (Mutación)**, busque el campo **addUser** y seleccione **Attach (Asociar)**.
+ En **Data source name (Nombre del origen de datos)**, elija **HTTP**.
+ En **Configure the request mapping template (Configurar la plantilla de mapeo de solicitud)**, pegue el siguiente código:

```
{
    "version": "2018-05-29",
    "method": "POST",
    "resourcePath": "/v1/users",
    "params":{
      "headers":{
        "Content-Type": "application/json",
      },
      "body": $util.toJson($ctx.args.userInput)
    }
}
```
+ En **Configure the response mapping template (Configurar la plantilla de mapeo de respuesta)**, pegue el siguiente código:

```
## 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
```
+ Elija la pestaña **Query (Consulta)** y ejecute la consulta siguiente:

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

Debe obtener la siguiente respuesta:

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

## Servicios de invocación AWS
<a name="invoking-aws-services"></a>

Puedes usar resolutores HTTP para configurar una interfaz AWS API de GraphQL para los servicios. Las solicitudes HTTP AWS deben firmarse con el [proceso Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html) para AWS poder identificar quién las envió. AWS AppSync calcula la firma en su nombre al asociar una función de IAM a la fuente de datos HTTP.

Usted proporciona dos componentes adicionales para invocar AWS servicios con resolutores HTTP:
+ Un rol de IAM con permisos para llamar al servicio AWS APIs
+ Configuración de la firma en el origen de datos

Por ejemplo, si desea llamar a la [ListGraphqlApis operación](https://docs.aws.amazon.com/appsync/latest/APIReference/API_ListGraphqlApis.html) con resolutores HTTP, primero debe [crear un rol de IAM](attaching-a-data-source.md#aws-appsync-getting-started-build-a-schema-from-scratch) que AWS AppSync asuma la siguiente política adjunta:

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

****  

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

------

A continuación, cree la fuente de datos HTTP para. AWS AppSync En este ejemplo, debe realizar una llamada a AWS AppSync en la región EE. UU. Oeste (Oregón). Ajuste la siguiente configuración HTTP en un archivo denominado `http.json`, que incluye la región de la firma y el nombre del servicio:

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

A continuación, utilice AWS CLI para crear la fuente de datos con un rol asociado de la siguiente manera:

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

Cuando adjunte una resolución al campo del esquema, utilice la siguiente plantilla de mapeo de solicitudes para llamar AWS AppSync:

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

Cuando ejecutas una consulta GraphQL para esta fuente de datos, AWS AppSync firma la solicitud con el rol que has proporcionado e incluye la firma en la solicitud. La consulta devuelve una lista de AWS AppSync GraphQL APIs en tu cuenta en esa AWS región.

# Uso de Aurora Serverless v2 con AWS AppSync
<a name="tutorial-rds-resolvers"></a>

Connect su API GraphQL a las bases de datos Aurora Serverless mediante. AWS AppSync Esta integración le permite ejecutar instrucciones SQL a través de consultas, mutaciones y suscripciones de GraphQL, lo que le brinda una forma flexible de interactuar con sus datos relacionales.

**nota**  
En este tutorial se utiliza la región `US-EAST-1`.

**Ventajas**
+ Integración perfecta entre GraphQL y bases de datos relacionales
+ Capacidad para realizar operaciones SQL a través de interfaces de GraphQL
+ Escalabilidad sin servidor con Aurora Serverless v2
+ Acceso seguro a los datos a través de AWS Secrets Manager
+ Protección contra la inyección de código SQL mediante el saneamiento de entradas
+ Capacidades de consulta flexibles que incluyen operaciones de filtrado y rango

**Casos de uso común**
+ Creación de aplicaciones escalables con requisitos de datos relacionales
+ Crear APIs eso requiere flexibilidad de GraphQL y capacidades de bases de datos SQL
+ Administración de operaciones de datos mediante mutaciones y consultas de GraphQL
+ Implementación de patrones de acceso seguro a bases de datos

En este tutorial, aprenderá lo siguiente:
+ Configuración de un clúster de Aurora Serverless v2
+ Habilitación de la funcionalidad de la API de datos
+ Creación y configuración de estructuras de base de datos
+ Definición de esquemas de GraphQL para operaciones de bases de datos
+ Implementación de solucionadores para consultas y mutaciones
+ Protección del acceso a sus datos mediante el saneamiento adecuado de las entradas
+ Ejecución de varias operaciones de bases de datos a través de interfaces de GraphQL

**Topics**
+ [Configuración del clúster de base de datos](#create-cluster)
+ [Habilitar la API de datos](#enable-data-api)
+ [Creación de una base de datos y tabla](#create-database-and-table)
+ [Esquema de GraphQL](#graphql-schema)
+ [Conexión de su API a las operaciones de la base de datos](#configuring-resolvers)
+ [Modificación de los datos a través de la API](#run-mutations)
+ [Recuperación de los datos](#run-queries)
+ [Protección del acceso a los datos](#input-sanitization)

## Configuración del clúster de base de datos
<a name="create-cluster"></a>

Antes de añadir una fuente de datos de Amazon RDS AWS AppSync, primero debe habilitar una API de datos en un clúster de Aurora Serverless v2 y **configurar un secreto mediante**. *AWS Secrets Manager* Puede crear un clúster de Aurora Serverless v2 con la AWS CLI:

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

Esto devolverá un ARN para el clúster.

Tras crear el clúster, debe agregar una instancia de Aurora Serverless v2 mediante el siguiente comando.

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

**nota**  
Estos puntos de conexión tardan un tiempo en activarse. Puede comprobar su estado en la consola de Amazon RDS, en la pestaña **Conectividad y seguridad** del clúster. También puede comprobar el estado del clúster con el siguiente AWS CLI comando.   

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

Puede crear un *secreto* con la AWS Secrets Manager consola o AWS CLI con un archivo de entrada como el siguiente utilizando `USERNAME` y `COMPLEX_PASSWORD` desde el paso anterior.

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

Pasa esto como parámetro a AWS CLI:

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

Esto devolverá un ARN para el secreto.

 **Anote el ARN** del clúster Aurora Serverless y el secreto para usarlos posteriormente en la AppSync consola al crear una fuente de datos.

## Habilitar la API de datos
<a name="enable-data-api"></a>

Puede habilitar la API de datos en el clúster [siguiendo las instrucciones de la documentación de RDS](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html). La API de datos debe estar habilitada antes de añadirla como fuente de AppSync datos.

## Creación de una base de datos y tabla
<a name="create-database-and-table"></a>

Una vez que tenga habilitada su API de datos, puede asegurarse de que funciona con el comando `aws rds-data execute-statement` de la AWS CLI. Esto garantizará que el clúster Aurora Serverless esté configurado correctamente antes de añadirlo a la AppSync API. En primer lugar, cree una base de datos denominada *TESTDB* con el parámetro `--sql` del siguiente modo:

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

Si esto se ejecuta sin errores, añada una tabla con el comando de *creación de tablas*:

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

Si todo se ha ejecutado sin problemas, puede continuar y añadir el clúster como fuente de datos en su AppSync API.

## Esquema de GraphQL
<a name="graphql-schema"></a>

Ahora que su API de datos de Aurora Serverless está en marcha con una tabla, vamos a crear un esquema GraphQL y asociar los solucionadores para realizar mutaciones y suscripciones. Crea una nueva API en la AWS AppSync consola, ve a la página del **esquema** e introduce lo siguiente:

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

 **Guarde** su esquema y vaya a la página **Data Sources (Orígenes de datos)** y cree un nuevo origen de datos. Seleccione la **Relational database (Base de datos relacional)** para el tipo de origen de datos y proporcione un nombre fácil de recordar. Utilice el nombre de la base de datos que ha creado en el último paso, así como el **Cluster ARN (ARN del clúster)** en el que lo creó. Para el **rol**, puede AppSync crear un rol nuevo o uno con una política similar a la siguiente:

------
#### [ 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:*"
            ]
        }
    ]
}
```

------

Tenga en cuenta que hay dos **Statements (Instrucciones)** en esta política a las que está concediendo acceso de rol. El primer **recurso** es su clúster Aurora Serverless y el segundo es su AWS Secrets Manager ARN. **Deberá proporcionar **AMBOS** ARNs en la configuración de la fuente de AppSync datos antes de hacer clic en Crear.**

Pase esto como parámetro a AWS CLI.

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

Esto devolverá un ARN para el secreto. Al crear una fuente de datos en la consola, anote el ARN del clúster Aurora Serverless y el secreto para más adelante. AWS AppSync 

### Creación de la estructura de su base de datos
<a name="create-database-and-table"></a>

Una vez que tenga habilitada su API de datos, puede asegurarse de que funciona con el comando `aws rds-data execute-statement` de la AWS CLI. Esto garantizará que el clúster de Aurora Serverless v2 esté configurado correctamente antes de añadirlo a la AWS AppSync API. En primer lugar, cree una base de datos denominada *TESTDB* con el parámetro `--sql` del siguiente modo.

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

Si esto se ejecuta sin errores, añada una tabla con el siguiente comando de *creación de tablas*:

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

### Diseño de la interfaz de API
<a name="graphql-schema"></a>

Una vez que la API de datos de Aurora Serverless v2 está en marcha con una tabla, cree un esquema GraphQL y asocie los solucionadores para realizar mutaciones y suscripciones. Cree una nueva API en la AWS AppSync consola, vaya a la página del **esquema** de la consola e introduzca lo siguiente.

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

 **Guarde** su esquema y vaya a la página **Data Sources (Orígenes de datos)** y cree un nuevo origen de datos. Seleccione **Base de datos relacional** para el tipo de **origen de datos** y proporcione un nombre fácil de recordar. Utilice el nombre de la base de datos que ha creado en el último paso, así como el **Cluster ARN (ARN del clúster)** en el que lo creó. Para el **rol**, puede AWS AppSync crear un rol nuevo o uno con una política similar a la siguiente.

------
#### [ 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:*"
                ]
            }
        ]
    }
```

------

Tenga en cuenta que hay dos **Statements (Instrucciones)** en esta política a las que está concediendo acceso de rol. El primer **recurso** es su clúster Aurora Serverless v2 y el segundo es su AWS Secrets Manager ARN. **Deberá proporcionar **AMBOS** ARNs en la configuración de la fuente de AWS AppSync datos antes de hacer clic en Crear.**

## Conexión de su API a las operaciones de la base de datos
<a name="configuring-resolvers"></a>

Ahora que tenemos un esquema de GraphQL válido y un origen de datos de RDS, podemos asociar los solucionadores a los campos de GraphQL en el esquema. Nuestra API ofrecerá las siguientes capacidades:

1. crear una mascota con el campo *Mutation.createPet*

1. actualizar una mascota con el campo *Mutation.updatePet*

1. eliminar una mascota con el campo *Mutation.deletePet*

1. obtener una mascota única con el campo *Query.getPet*

1. enumerar todas las mascotas con el campo *Query.listPets*

1. publique las mascotas en un rango de precios mediante la *consulta. listPetsByPriceRange*campo

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

En el editor de esquemas de la AWS AppSync consola, en la parte derecha, selecciona **Attach Resolver** for`createPet(input: CreatePetInput!): Pet`. Elija el origen de los datos de RDS. En la sección **Plantilla de mapeo de solicitudes**, añada la siguiente plantilla:

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

El sistema ejecuta las instrucciones SQL de forma secuencial, en función del orden de la matriz **instrucciones**. Los resultados volverán en el mismo orden. Dado que se trata de una mutación, ejecute una instrucción *seleccionar* después de *insertar* para recuperar los valores confirmados con objeto de rellenar la plantilla de mapeo de respuesta de GraphQL.

En la sección **Plantilla de mapeo de respuestas**, añada la siguiente plantilla:

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])
```

Debido a que las *statements (instrucciones)* tienen dos consultas SQL, necesitamos especificar el segundo resultado en la matriz que viene de la base de datos con: `$utils.rds.toJsonString($ctx.result))[1][0])`.

### Mutation.updatePet
<a name="mutation-updatepet"></a>

En el editor de esquemas de la AWS AppSync consola, seleccione **Attach Resolver** for`updatePet(input: UpdatePetInput!): Pet`. Elija el **origen de los datos de RDS**. En la sección **Plantilla de mapeo de solicitudes**, añada la siguiente plantilla.

```
{
"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)
    }
}
```

En la sección **Plantilla de mapeo de respuestas**, añada la siguiente plantilla.

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])
```

### Mutation.deletePet
<a name="mutation-deletepet"></a>

En el editor de esquemas de la AWS AppSync consola, elija **Attach Resolver** for`deletePet(input: DeletePetInput!): Pet`. Elija el **origen de los datos de RDS**. En la sección **Plantilla de mapeo de solicitudes**, añada la siguiente plantilla.

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

En la sección **Plantilla de mapeo de respuestas**, añada la siguiente plantilla.

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])
```

### Query.getPet
<a name="query-getpet"></a>

Ahora que se han creado las mutaciones para su esquema, conecte las tres consultas para mostrar cómo obtener elementos individuales, listas y aplicar el filtrado de SQL. En el **editor de esquemas** de la AWS AppSync consola, elija **Attach Resolver** for`getPet(id: ID!): Pet`. Elija el **origen de los datos de RDS**. En la sección **Plantilla de mapeo de solicitudes**, añada la siguiente plantilla.

```
{
"version": "2018-05-29",
        "statements": [
            $util.toJson("select * from Pets WHERE id=:ID")
    ],
    "variableMap": {
        ":ID": "$ctx.args.id"
    }
}
```

En la sección **Plantilla de mapeo de respuestas**, añada la siguiente plantilla:

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])
```

### Query.listPets
<a name="query-listpets"></a>

En el editor de esquemas de la AWS AppSync consola, en la parte derecha, selecciona **Attach Resolver** for`getPet(id: ID!): Pet`. Elija el **origen de los datos de RDS**. En la sección **Plantilla de mapeo de solicitudes**, añada la siguiente plantilla.

```
{
    "version": "2018-05-29",
    "statements": [
        "select * from Pets"
    ]
}
```

En la sección **Plantilla de mapeo de respuestas**, añada la siguiente plantilla.

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])
```

### Consulta. listPetsByPriceRange
<a name="query-listpetsbypricerange"></a>

En el editor de esquemas de la AWS AppSync consola, en el lado derecho, selecciona **Attach Resolver** for`getPet(id: ID!): Pet`. Elija el **origen de los datos de RDS**. En la sección **Plantilla de mapeo de solicitudes**, añada la siguiente plantilla.

```
{
    "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)
    }
}
```

En la sección **Plantilla de mapeo de respuestas**, añada la siguiente plantilla:

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])
```

## Modificación de los datos a través de la API
<a name="run-mutations"></a>

Ahora que ha configurado todos sus solucionadores con instrucciones SQL y conectado API de GraphQL a su API de datos Aurora Serverless, puede comenzar a realizar mutaciones y consultas. En la AWS AppSync consola, selecciona la pestaña **Consultas** e introduce lo siguiente para crear una mascota:

```
mutation add {
    createPet(input : { type:fish, price:10.0 }){
        id
        type
        price
    }
}
```

La respuesta debe contener los valores de *id*, *type (tipo)* y *price (precio)* de la siguiente manera:

```
{
  "data": {
    "createPet": {
      "id": "c6fedbbe-57ad-4da3-860a-ffe8d039882a",
      "type": "fish",
      "price": "10.0"
    }
  }
}
```

Puede modificar este elemento mediante la ejecución de la mutación *updatePet*:

```
mutation update {
    updatePet(input : {
        id: ID_PLACEHOLDER,
        type:bird,
        price:50.0
    }){
        id
        type
        price
    }
}
```

Tenga en cuenta que hemos usado el *id* que ha devuelto la operación *createPet* anteriormente. Este será un valor único para su registro, ya que el solucionador obtuvo `$util.autoId()`. Podría eliminar un registro de un modo similar:

```
mutation delete {
    deletePet(input : {id:ID_PLACEHOLDER}){
        id
        type
        price
    }
}
```

Cree un par de registros con la primera mutación con valores diferentes de *price (precio)* y, a continuación, ejecute algunas consultas.

## Recuperación de los datos
<a name="run-queries"></a>

En la pestaña **Consultas** de la consola, utilice la siguiente instrucción para obtener una lista de todos los registros que haya creado.

```
query allpets {
    listPets {
        id
        type
        price
    }
}
```

Aproveche el predicado *WHERE* de SQL que había `where price > :MIN and price < :MAX` en nuestra plantilla de mapeo para *Query. listPetsByPriceRange*con la siguiente consulta de GraphQL:

```
query petsByPriceRange {
    listPetsByPriceRange(min:1, max:11) {
        id
        type
        price
    }
}
```

Solo debe ver registros con un *price (precio)* superior a 1 USD o inferior a 10 USD. Por último, puede realizar consultas para obtener registros individuales tal y como se indica a continuación:

```
query onePet {
    getPet(id:ID_PLACEHOLDER){
        id
        type
        price
    }
}
```

## Protección del acceso a los datos
<a name="input-sanitization"></a>

La inyección de código SQL es una vulnerabilidad de seguridad en las aplicaciones de bases de datos. Se produce cuando los atacantes insertan código SQL malicioso a través de los campos de entrada del usuario. Esto puede permitir el acceso no autorizado a los datos de la base de datos. Recomendamos que valide y sanee meticulosamente todas las entradas de los usuarios antes de procesarlas con `variableMap` como protección contra los ataques de inyección de código SQL. Si no se utilizan mapas de variables, usted es el responsable de sanear los argumentos de sus operaciones de GraphQL. Una forma de hacerlo es proporcionando los pasos de validación específicos de entrada en la plantilla de mapeo de solicitudes antes de la ejecución de una instrucción SQL con respecto a la API de datos. Veamos cómo podemos modificar la plantilla de mapeo de solicitudes del ejemplo de `listPetsByPriceRange`. En lugar de confiar solamente en la entrada del usuario puede hacer lo siguiente:

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

Otra forma de protegerse frente a la entrada no autorizada a la hora de ejecutar solucionadores con respecto a la API de datos es utilizar las instrucciones preparadas junto con el procedimiento almacenado y las entradas parametrizadas. Por ejemplo, en el solucionador para `listPets` defina el siguiente procedimiento que ejecuta *select (seleccionar)* como una instrucción preparada:

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

Cree esto en su instancia de 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"
```

El código de solucionador resultante para listPets se simplifica, ya que ahora simplemente llamamos al procedimiento almacenado: Como mínimo, toda entrada de cadena debe tener comillas simples [con caracteres de escape](#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("'", "''"))
    }
}
```

### Uso de cadenas de escape
<a name="escaped"></a>

Las comillas simples representan el inicio y el final de los literales de cadena en una instrucción SQL; por ejemplo, `'some string value'`. Para permitir el uso de valores de cadena con uno o más caracteres de comillas simples (`'`) dentro de una cadena, cada uno de ellos debe reemplazarse por dos comillas simples (`''`). Por ejemplo, si la cadena de entrada es `Nadia's dog`, para la instrucción SQL se aplicarían caracteres de escape de la siguiente forma:

```
update Pets set type='Nadia''s dog' WHERE id='1'
```

# Uso de solucionadores de canalizaciones en AWS AppSync
<a name="tutorial-pipeline-resolvers"></a>

**nota**  
Ahora admitimos de forma básica el tiempo de ejecución APPSYNC\$1JS y su documentación. Considere la opción de utilizar el tiempo de ejecución APPSYNC\$1JS y sus guías [aquí](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html).

AWS AppSync proporciona una forma sencilla de conectar un campo GraphQL a una única fuente de datos mediante resolutores unitarios. Sin embargo, ejecutar una sola operación podría no ser suficiente. Los solucionadores de canalización ofrecen la posibilidad de ejecutar operaciones en serie con respecto a los orígenes de datos. Cree funciones en su API y asócielas a un solucionador de canalización. Cada resultado de ejecución de la función se canaliza a la siguiente hasta que no quede ninguna función que ejecutar. Con los solucionadores de canalización ahora puede crear flujos de trabajo más complejos directamente en AWS AppSync. En este tutorial, se crea una aplicación sencilla de visualización de imágenes, en la que los usuarios pueden publicar y ver imágenes publicadas por sus amigos.

## Configuración en un clic
<a name="one-click-setup"></a>

Si quieres configurar automáticamente el punto final de GraphQL AWS AppSync con todos los resolutores configurados y los AWS recursos necesarios, puedes usar la siguiente plantilla: 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)

Esta pila crea los siguientes recursos en su cuenta:
+ Función de IAM para acceder AWS AppSync a los recursos de tu cuenta
+ 2 tablas de DynamoDB
+ 1 grupo de usuarios de Amazon Cognito
+ 2 grupos de grupos de usuarios de Amazon Cognito
+ 3 usuarios de grupos de usuarios de Amazon Cognito
+ 1 API AWS AppSync 

Al final del proceso de creación de la AWS CloudFormation pila, recibirá un correo electrónico para cada uno de los tres usuarios de Amazon Cognito que se crearon. Cada correo electrónico contiene una contraseña temporal que se utiliza para iniciar sesión en la consola como usuario de Amazon Cognito. AWS AppSync Guarde las contraseñas para el resto del tutorial.

## Configuración manual
<a name="manual-setup"></a>

Si prefiere realizar un step-by-step proceso manualmente a través de la AWS AppSync consola, siga el proceso de configuración que se indica a continuación.

### Configuración de sus AWS AppSync recursos no relacionados
<a name="setting-up-your-non-aws-appsync-resources"></a>

La API se comunica con dos tablas de DynamoDB: una tabla de **imágenes** que almacena imágenes y una tabla de **amigos** que almacena las relaciones entre los usuarios. La API está configurada para utilizar el grupo de usuarios de Amazon Cognito como el tipo de autenticación. La siguiente CloudFormation pila configura estos recursos en la cuenta.

[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)

Al final del proceso de creación de la AWS CloudFormation pila, recibirá un correo electrónico para cada uno de los tres usuarios de Amazon Cognito que se crearon. Cada correo electrónico contiene una contraseña temporal que se utiliza para iniciar sesión como un usuario de Amazon Cognito en la consola de AWS AppSync. Guarde las contraseñas para el resto del tutorial.

### Creación de la API de GraphQL
<a name="creating-your-graphql-api"></a>

Para crear la API GraphQL en: AWS AppSync

1. Abre la AWS AppSync consola, selecciona **Build From Scratch** y selecciona **Start**.

1. Establezca como nombre de la API `AppSyncTutorial-PicturesViewer`.

1. Seleccione **Crear**.

La AWS AppSync consola crea una nueva API de GraphQL para usted mediante el modo de autenticación con clave de API. Puede utilizar la consola para configurar el resto de la API de GraphQL y ejecutar consultas en ella durante el resto de este tutorial.

### Configuración de la API de GraphQL
<a name="configuring-the-graphql-api"></a>

Debe configurar la AWS AppSync API con el grupo de usuarios de Amazon Cognito que acaba de crear.

1. Elija la pestaña **Settings**.

1. En la sección **Authorization Type (Tipo de autorización)**, elija *Amazon Cognito User Pool (Grupo de usuarios de Amazon Cognito)*.

1. En **Configuración del grupo de usuarios**, elija **US-WEST-2** para la *Región de AWS *.

1. Elija el grupo **AppSyncTutorialde UserPool usuarios:**

1. Elija **DENY (DENEGAR)** como *Default Action (Acción predeterminada)*.

1. Deje en blanco el campo **de expresiones regulares del AppId cliente**.

1. Seleccione **Save**.

La API ahora está configurada para utilizar el grupo de usuarios de Amazon Cognito como el tipo de autenticación.

### Configuración de orígenes de datos para las tablas de DynamoDB
<a name="configuring-data-sources-for-the-ddb-tables"></a>

**Una vez creadas las tablas de DynamoDB, vaya a la API de AWS AppSync GraphQL en la consola y seleccione la pestaña Fuentes de datos.** Ahora, va a crear una fuente de datos AWS AppSync para cada una de las tablas de DynamoDB que acaba de crear.

1. Elija la pestaña **Data source (Origen de datos)**.

1. Elija **New (Nuevo)** para crear un nuevo origen de datos.

1. Escriba `PicturesDynamoDBTable` como nombre del origen de datos.

1. En Tipo de origen de datos, elija **Tabla de Amazon DynamoDB**.

1. Elija **US-WEST-2** como región.

1. En la lista de tablas, elija la tabla **AppSyncTutorial-Pictures DynamoDB**.

1. En la sección **Create or use an existing role (Crear o usar un rol existente)**, elija **Existing role (Rol existente)**.

1. Elija el rol que se acaba de crear a partir de la plantilla. CloudFormation Si no ha cambiado el *ResourceNamePrefix*, el nombre del rol debería ser **AppSyncTutorial-Dynamo DBRole**.

1. Seleccione **Crear**.

Repita el mismo proceso para la tabla de **amigos**, el nombre de la tabla de DynamoDB debería **AppSyncTutorialser** -Friends si no cambió *ResourceNamePrefix*el parámetro en el momento de crear la pila. CloudFormation 

### Creación del esquema de GraphQL
<a name="creating-the-graphql-schema"></a>

Ahora que los orígenes de datos están conectados a las tablas de DynamoDB, vamos a crear un esquema de GraphQL. En el editor de esquemas de la AWS AppSync consola, asegúrese de que el esquema coincide con el siguiente esquema:

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

Elija **Save Schema (Guardar esquema)** para guardar el esquema.

Algunos de los campos de esquema se han comentado con la directiva *@aws\$1auth*. Dado que la API de configuración de acción predeterminada está establecida en *DENY (DENEGAR)*, la API rechaza a todos los usuarios que no son miembros de los grupos mencionados dentro de la directiva *@aws\$1auth*. Para obtener más información acerca de cómo proteger la API, puede leer la página de [seguridad](security-authz.md#aws-appsync-security). **En este caso, solo los usuarios administradores tienen acceso a los campos *Mutation.createPicture y Mutation.createFriendship**, mientras que los usuarios que son miembros* de los grupos de administradores o de espectadores pueden acceder a la consulta.** * getPicturesByCampo propietario.* Todos los demás usuarios no tienen acceso.

### Configuración de solucionadores
<a name="configuring-resolvers"></a>

Ahora que tiene un esquema GraphQL válido y dos orígenes de datos, puede asociar los solucionadores a los campos de GraphQL en el esquema. La API ofrece las siguientes capacidades:
+ Crear una imagen a través del campo *Mutation.createPicture*
+ Crear una amistad a través del campo *Mutation.createFriendship*
+ Recuperar una imagen a través del campo *Query.getPicture*

#### Mutation.createPicture
<a name="mutation-createpicture"></a>

En el editor de esquemas de la AWS AppSync consola, en el lado derecho, selecciona **Attach Resolver** for`createPicture(input: CreatePictureInput!): Picture!`. Elija la fuente de datos de *PicturesDynamoDBTable*DynamoDB. En la sección **Plantilla de mapeo de solicitudes**, añada la siguiente plantilla:

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

En la sección **Plantilla de mapeo de respuestas**, añada la siguiente plantilla:

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

La funcionalidad de creación de imagen está lista. Va a guardar una imagen en la tabla **Pictures (Imágenes)** utilizando un UUID generado de forma aleatoria como id de la imagen y utilizando el nombre de usuario de Cognito como propietario de la imagen.

#### Mutation.createFriendship
<a name="mutation-createfriendship"></a>

En el editor de esquemas de la AWS AppSync consola, en la parte derecha, elija **Attach Resolver for**. `createFriendship(id: ID!, target: ID!): Boolean` Elija la fuente de datos de **FriendsDynamoDBTable**DynamoDB. En la sección **Plantilla de mapeo de solicitudes**, añada la siguiente plantilla:

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

Importante: En la plantilla de **BatchPutItem**solicitud, debe estar presente el nombre exacto de la tabla de DynamoDB. *El nombre predeterminado de la tabla es -FriendsAppSyncTutorial.* Si utilizas un nombre de tabla incorrecto, aparecerá un error cuando AppSync intentes asumir el rol proporcionado.

Para simplificar este tutorial, proceda como si la solicitud de amistad hubiera sido aprobada y guarde la entrada sobre la relación directamente en la **AppSyncTutorialFriends**tabla.

Efectivamente, está almacenando dos elementos para cada amistad, ya que la relación es bidireccional. Para obtener más información sobre las prácticas recomendadas de Amazon DynamoDB para many-to-many representar relaciones, consulte Prácticas recomendadas de [DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-adjacency-graphs.html).

En la sección **Plantilla de mapeo de respuestas**, añada la siguiente plantilla:

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

Nota: Asegúrese de que su plantilla de solicitud contiene el nombre de la tabla correcto. El nombre predeterminado es *AppSyncTutorial-Friends*, pero el nombre de la tabla podría ser diferente si cambiara el parámetro. CloudFormation **ResourceNamePrefix**

#### Consulta. getPicturesByDueño
<a name="query-getpicturesbyowner"></a>

Ahora que tiene amistades e imágenes, debe proporcionar la capacidad a los usuarios de ver las imágenes de sus amigos. Para cumplir este requisito, debe comprobar primero que el solicitante es amigo con el propietario y, por último, consultar las imágenes.

Dado que esta funcionalidad requiere dos operaciones de origen de datos, va a crear dos funciones. La primera función, **isFriend**, comprueba si el solicitante y el propietario son amigos. La segunda función, **getPicturesByPropietario**, recupera las imágenes solicitadas con un identificador de propietario. Veamos el siguiente flujo de ejecución para el solucionador propuesto en la *consulta. getPicturesByCampo propietario*:

1. Plantilla de mapeo Antes: Prepare los argumentos de entrada de contexto y campo.

1. Función isFriend: Comprueba si el solicitante es el propietario de la imagen. De lo contrario, comprueba si los usuarios solicitantes y propietarios son amigos realizando una operación de GetItem DynamoDB en la tabla de amigos.

1. getPicturesByFunción propietario: recupera imágenes de la tabla Pictures mediante una operación de consulta de DynamoDB en *el* índice secundario global owner-index.

1. Plantilla de mapeo Después: Mapea el resultado de la imagen para que los atributos de DynamoDB se mapeen correctamente en los campos de tipo GraphQL esperados.

Primero, vamos a crear las funciones.

##### Función isFriend
<a name="isfriend-function"></a>

1. Elija la pestaña **Functions (Funciones)**.

1. Elija **Create Function (Crear función)** para crear una función.

1. Escriba `FriendsDynamoDBTable` como nombre del origen de datos.

1. Para el nombre de la función, escriba *isFriend*.

1. Dentro del área de texto de la plantilla de mapeo de solicitudes, pegue la siguiente plantilla:

   ```
   #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. Dentro del área de texto de la plantilla de mapeo de respuestas, pegue la siguiente plantilla:

   ```
   #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. Elija **Crear función**.

Resultado: Ha creado la función **isFriend**.

##### getPicturesByFunción de propietario
<a name="getpicturesbyowner-function"></a>

1. Elija la pestaña **Functions (Funciones)**.

1. Elija **Create Function (Crear función)** para crear una función.

1. Escriba `PicturesDynamoDBTable` como nombre del origen de datos.

1. Para el nombre de la función, escriba `getPicturesByOwner`.

1. Dentro del área de texto de la plantilla de mapeo de solicitudes, pegue la siguiente plantilla:

   ```
   {
       "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. Dentro del área de texto de la plantilla de mapeo de respuestas, pegue la siguiente plantilla:

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

1. Elija **Crear función**.

Resultado: ha creado la función **getPicturesByPropietario**. Ahora que se han creado las funciones, adjunta un solucionador de canalizaciones a la *consulta. getPicturesByCampo propietario*.

En el editor de esquemas de la AWS AppSync consola, en el lado derecho, selecciona **Attach Resolver** for`Query.getPicturesByOwner(id: ID!): [Picture]`. En la página siguiente, elija el enlace **Convert to pipeline resolver (Convertir a solucionador de canalización)** que aparece debajo de la lista desplegable del origen de datos. Use lo siguiente para la plantilla de mapeo de antes:

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

En la sección **after mapping template (plantilla de mapeo de después)**, use lo siguiente:

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

Elija **Create Resolver (Crear solucionador)**. Acaba de asociar su primer solucionador de canalización. En la misma página, añada las dos funciones que ha creado anteriormente. En sección de funciones, seleccione **Add A Function (Añadir una función)** y, a continuación, elija o escriba el nombre de la primera función, **isFriend**. Añada la segunda función siguiendo el mismo proceso para la función **getPicturesByOwner**. Asegúrese de que la función **IsFriend** aparezca en primer lugar de la lista, seguida de la función **getPicturesByPropietario**. Puede utilizar las flechas hacia arriba y abajo para cambiar el orden de ejecución de las funciones de la canalización.

Ahora que el solucionador de canalización se ha creado y ha asociado las funciones, vamos a probar la nueva API de GraphQL.

## Prueba de la API de GraphQL
<a name="testing-your-graphql-api"></a>

En primer lugar, debe rellenar las imágenes y amistades ejecutando algunas mutaciones mediante el usuario administrador que ha creado. En la parte izquierda de la AWS AppSync consola, selecciona la pestaña **Consultas**.

### Mutación createPicture
<a name="createpicture-mutation"></a>

1. En AWS AppSync la consola, selecciona la pestaña **Consultas**.

1. Elija **Login With User Pools (Inicio de sesión con grupos de usuarios)**.

1. En el modal, introduzca el ID de cliente de muestra de Cognito que creó la CloudFormation pila (por ejemplo, 37solo6mmhh7k4v63cqdfgdg5d).

1. Introduzca el nombre de CloudFormation usuario que ha pasado como parámetro a la pila. El valor predeterminado es **nadia**.

1. Usa la contraseña temporal que se envió al correo electrónico que proporcionaste como parámetro de la CloudFormation pila (por ejemplo, *UserPoolUserEmail*).

1. Seleccione Iniciar sesión. Ahora deberías ver el botón con el nombre de **Logout nadia**, o el nombre de usuario que hayas elegido al crear la CloudFormation pila (es decir, *UserPoolUsername*).

Vamos a enviar unas mutaciones de *createPicture* para rellenar la tabla de imágenes. Ejecute la siguiente consulta de GraphQL dentro de la consola:

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

La respuesta debe tener un aspecto similar al siguiente:

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

Agreguemos algunas imágenes más:

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

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

Ha agregado tres imágenes usando a **nadia** como usuario administrador.

### Mutación createFriendship
<a name="createfriendship-mutation"></a>

Vamos a añadir una entrada de amistad. Ejecute las siguientes mutaciones en la consola.

Nota: Debe permanecer con la sesión iniciada como usuario administrador (el valor predeterminado es **nadia**).

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

La respuesta debe tener un aspecto similar al siguiente:

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

 **nadia** y **shaggy** son amigos. **rex** no es amigo de nadie.

### getPicturesByConsulta de propietario
<a name="getpicturesbyowner-query"></a>

En este paso, inicie sesión como el usuario **nadia** con los grupos de usuarios de Cognito, con las credenciales configuradas al principio de este tutorial. Como **nadia**, recupere las imágenes propiedad de **shaggy**.

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

Dado que **nadia** y **shaggy** son amigos, la consulta debe devolver la imagen correspondiente.

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

Del mismo modo, si **nadia** intenta recuperar sus propias imágenes, también se ejecuta satisfactoriamente. El solucionador de canalizaciones se ha optimizado para evitar ejecutar la GetItem operación **IsFriend** en ese caso. Pruebe la siguiente consulta:

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

Si habilita el registro en la API (en el panel **Settings (Configuración)**), configure el nivel de depuración a **ALL (TODO)** y ejecute la misma consulta de nuevo, devuelve los registros a la ejecución del campo. Observando los registros, puede determinar si la función **isFriend** se ha devuelto pronto en la etapa **Request Mapping Template (Plantilla de mapeo de solicitud)**:

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

La *earlyReturnedValue*clave representa los datos devueltos por la directiva *\$1return*.

**Por último, aunque **rex** es miembro del UserPool Grupo Cognito de los **espectadores** y, dado que **Rex** no es amigo de nadie, no podrá acceder a ninguna de las imágenes propiedad de **Shaggy** o Nadia.** Si iniciar sesión como **rex** en la consola y ejecuta la siguiente consulta:

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

Obtiene el siguiente error de no autorizado:

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

Ha implementado con éxito la autorización compleja con los solucionadores de canalización.

# Uso de operaciones de Delta Sync en fuentes de datos versionadas en AWS AppSync
<a name="tutorial-delta-sync"></a>

**nota**  
Ahora admitimos de forma básica el tiempo de ejecución APPSYNC\$1JS y su documentación. Considere la opción de utilizar el tiempo de ejecución APPSYNC\$1JS y sus guías [aquí](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html).

Las aplicaciones cliente AWS AppSync almacenan datos almacenando en caché las respuestas de GraphQL localmente en mobile/web el disco de una aplicación. Los orígenes de datos con control de versiones y las operaciones `Sync` ofrecen a los clientes la posibilidad de realizar el proceso de sincronización mediante un único solucionador. Esto permite a los clientes incorporar a su memoria caché local los resultados de una consulta de base que podría tener una gran cantidad de registros y, a continuación, recibir solo los datos alterados desde la última consulta (las *actualizaciones delta*). Al permitir que los clientes realicen la incorporación de la caché con una consulta inicial y las actualizaciones incrementales con otra, puede mover el cálculo desde la aplicación cliente al backend. Esto es sustancialmente más eficiente para las aplicaciones cliente que cambian con frecuencia entre estados en línea y sin conexión.

Para implementar Delta Sync, la consulta `Sync` utiliza la operación `Sync` en un origen de datos con control de versiones. Cuando una AWS AppSync mutación cambia un elemento de una fuente de datos versionada, también se almacenará un registro de ese cambio en la tabla *Delta*. Puede optar por utilizar diferentes tablas *Delta* (por ejemplo, una por tipo, una por área de dominio) para otras fuentes de datos versionadas o una sola tabla *Delta* para su API. AWS AppSync recomienda no utilizar una sola tabla *Delta* para varias, APIs a fin de evitar la colisión de las claves principales.

Además, los clientes Delta Sync también pueden recibir una suscripción como argumento y, a continuación, la suscripción de coordenadas del cliente vuelve a conectar y escribe entre transiciones de sin conexión y online. Delta Sync realiza esto reanudando automáticamente las suscripciones (incluido el retardo exponencial y el reintento con fluctuación a través de diferentes escenarios de error de red) y almacenando los eventos en una cola. Entonces la consulta delta o base adecuada se ejecuta antes de fusionar cualquier evento de la cola y, por último, procesar las suscripciones como normales.

La documentación sobre las opciones de configuración del cliente, incluida Amplify DataStore, está disponible en el sitio web de [Amplify](https://aws-amplify.github.io/) Framework. En esta documentación se describe cómo configurar las operaciones `Sync` y los orígenes de datos de DynamoDB con control de versiones para que funcionen con el cliente de Delta Sync y obtener un acceso óptimo a los datos.

## Configuración en un clic
<a name="one-click-setup"></a>

Para configurar automáticamente el punto final de GraphQL AWS AppSync con todos los resolutores configurados y los AWS recursos necesarios, usa esta plantilla: 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) 

Esta pila crea los siguientes recursos en su cuenta:
+ 2 tablas de DynamoDB (Base y Delta)
+ 1 AWS AppSync API con clave de API
+ 1 rol de IAM con política para tablas de DynamoDB

Se utilizan dos tablas para particionar las consultas de Sync en una segunda tabla que actúa como un diario de los eventos no atendidos cuando los clientes estaban sin conexión. Para mantener la eficacia de las consultas en la tabla delta, se utiliza [Amazon TTLs DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html) para organizar automáticamente los eventos según sea necesario. El tiempo TTL se puede configurar según sus necesidades en el origen de datos (puede configurarlo en 1 hora, 1 día, etc.).

## Esquema
<a name="schema"></a>

Para demostrar Delta Sync, la aplicación de ejemplo crea un esquema de *Posts* respaldado por una tabla *Base* y *Delta* en DynamoDB. AWS AppSync escribe automáticamente las mutaciones en ambas tablas. La consulta Sync extrae registros de la tabla *Base* o *Delta* según proceda y se define una sola suscripción para mostrar cómo los clientes pueden aprovechar esto en su lógica de reconexión.

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

El esquema GraphQL es estándar, pero vale la pena señalar un par de cosas antes de avanzar. Primero, todas las mutaciones se escriben automáticamente en la tabla *Base* y, luego, en la tabla *Delta*. La tabla de *Base* es el origen central de información de estado mientras que la tabla *Delta* es el diario. Si no se pasa el `lastSync: AWSTimestamp`, la consulta `syncPosts` se ejecuta con respecto a la tabla *Base* e hidrata la caché, además de ejecutarse de forma periódica como un *proceso de alcance global* para casos límite en los que los clientes están sin conexión más del tiempo TTL configurado en la tabla *Delta*. Si pasa el `lastSync: AWSTimestamp`, la consulta `syncPosts` se ejecuta con respecto a la tabla *Delta* y los clientes la utilizan para recuperar los eventos cambiados desde la última vez que estuvieron sin conexión. Los clientes de Amplify pasan automáticamente el valor `lastSync: AWSTimestamp` y lo conservan en disco debidamente.

El campo *\$1deleted* de *Post* se utiliza para las operaciones **DELETE**. Cuando los clientes están sin conexión y se eliminan registros de la tabla *Base*, este atributo notifica a los clientes que están realizando la sincronización que expulsen los elementos de su caché local. En los casos en que los clientes están offline durante períodos más largos y el elemento se ha eliminado antes de que el cliente pueda recuperar este valor con una consulta Delta Sync, el evento de alcance global de consulta base (configurable en el cliente) se ejecuta y elimina el elemento de la caché. Este campo se marca como opcional porque solo devuelve un valor al ejecutar una consulta Sync que ha eliminado los elementos presentes.

## Mutaciones
<a name="mutations"></a>

Para todas las mutaciones, AWS AppSync realiza una Create/Update/Delete operación estándar en la tabla *base* y también registra el cambio en la tabla *Delta* automáticamente. Puede reducir o ampliar el tiempo para conservar los registros modificando el valor `DeltaSyncTableTTL` en el origen de datos. Para las organizaciones con una alta velocidad de datos, se recomienda que sea breve. O bien, si sus clientes están sin conexión durante períodos más largos, puede ser prudente mantenerlo más tiempo.

## Consultas Sync
<a name="sync-queries"></a>

La *consulta base* es una operación de DynamoDB Sync sin un valor de `lastSync` especificado. Para muchas organizaciones, esto funciona porque la consulta de base solo se ejecuta durante el inicio y de forma periódica a partir de entonces.

La *consulta delta* es una operación de DynamoDB Sync con un valor de `lastSync` especificado. La *consulta delta* se ejecuta cada vez que el cliente vuelva a estar online de un estado sin conexión (siempre que el tiempo periódico de consulta de base no se haya activado para ejecutarse). Los clientes realizan automáticamente el seguimiento de la última vez que ejecutaron de forma correcta una consulta para sincronizar datos.

Cuando se ejecuta una consulta delta, el solucionador de la consulta utiliza `ds_pk` y `ds_sk` para consultar únicamente los registros que han cambiado desde la última vez que el cliente realizó una sincronización. El cliente almacena la respuesta GraphQL adecuada.

Para obtener más información sobre cómo ejecutar consultas Sync, consulte la [documentación de la operación de sincronización](aws-appsync-conflict-detection-and-sync-sync-operations.md).

## Ejemplo
<a name="example"></a>

Vamos a comenzar por llamar a una mutación `createPost` para crear un elemento:

```
mutation create {
  createPost(input: {author: "Nadia", title: "My First Post", content: "Hello World"}) {
    id
    author
    title
    content
    _version
    _lastChangedAt
    _deleted
  }
}
```

El valor de retorno de esta mutación tendrá el siguiente aspecto:

```
{
  "data": {
    "createPost": {
      "id": "81d36bbb-1579-4efe-92b8-2e3f679f628b",
      "author": "Nadia",
      "title": "My First Post",
      "content": "Hello World",
      "_version": 1,
      "_lastChangedAt": 1574469356331,
      "_deleted": null
    }
  }
}
```

Si examina el contenido de la tabla *Base*, verá un registro similar a este:

```
{
  "_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"
  }
}
```

Si examina el contenido de la tabla *Delta*, verá un registro que tiene el siguiente aspecto:

```
{
  "_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"
  }
}
```

Ahora podemos simular una consulta *Base* que un cliente ejecutará para hidratar su almacén de datos local usando una consulta `syncPosts` como esta:

```
query baseQuery {
  syncPosts(limit: 100, lastSync: null, nextToken: null) {
    items {
      id
      author
      title
      content
      _version
      _lastChangedAt
    }
    startedAt
    nextToken
  }
}
```

El valor devuelto de esta consulta *Base* tiene este aspecto:

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

Más adelante, vamos a guardar el valor de `startedAt` para simular una consulta *Delta*, pero antes tenemos que hacer un cambio en nuestra tabla. Vamos a utilizar la mutación `updatePost` para modificar nuestro Post existente:

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

El valor de retorno de esta mutación tendrá el siguiente aspecto:

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

Si examina el contenido de la tabla *Base* ahora, debería ver el elemento actualizado:

```
{
  "_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"
  }
}
```

Si examina el contenido de la tabla *Delta* ahora, debería ver dos registros:

1. Un registro de cuando se creó el elemento

1. Un registro de cuando se actualizó el elemento

El nuevo elemento tendrá este aspecto:

```
{
  "_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"
  }
}
```

Ahora podemos simular una consulta *Delta* para recuperar las modificaciones que ocurrieron mientras un cliente estaba sin conexión. Utilizaremos el valor `startedAt` devuelto por nuestra consulta *Base* para realizar la solicitud:

```
query delta {
  syncPosts(limit: 100, lastSync: 1574469602238, nextToken: null) {
    items {
      id
      author
      title
      content
      _version
    }
    startedAt
    nextToken
  }
}
```

El valor devuelto de esta consulta *Delta* tendrá este aspecto:

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