avatar Artículo

Construyendo aplicaciones en tiempo real con AWS AppSync y Subscriptions

Aprende cómo construir aplicaciones en tiempo real con AWS AppSync y Subscriptions.

AWS AppSync

Aprende AWS AppSync para APIs GraphQL con suscripciones en tiempo real, integración de fuentes de datos y capacidades offline.

2 articles

Complete

Como expliqué en el primer artículo de AppSync, las capacidades en tiempo real se están volviendo cada vez más cruciales. Los usuarios ahora esperan actualizaciones inmediatas sin necesidad de actualización manual o sondeo constante. Aquí es donde entran en juego AWS AppSync y sus poderosas suscripciones GraphQL, permitiendo a los desarrolladores construir aplicaciones en tiempo real sin esfuerzo.

Usando suscripciones con AWS AppSync, configurarás capacidades en tiempo real en tus aplicaciones.

1. ¿Qué son las suscripciones GraphQL?

Las suscripciones GraphQL son una característica poderosa que permite a los clientes recibir actualizaciones en tiempo real del servidor. A diferencia de las consultas y mutaciones, que son operaciones de una sola vez, las suscripciones permiten un flujo continuo de datos entre el cliente y el servidor. Esto es particularmente útil para aplicaciones que requieren notificaciones o actualizaciones en tiempo real.

2. Beneficios clave de usar suscripciones de AWS AppSync

  • Actualizaciones en Tiempo Real: Las suscripciones envían datos a los clientes tan pronto como ocurren cambios, asegurando que los usuarios siempre tengan la información más reciente.
  • Eficiencia: Reduce la necesidad de sondeo constante, ahorrando ancho de banda y mejorando el rendimiento.
  • Integración Fluida: Se integra fácilmente con otros servicios de AWS, como DynamoDB, Lambda y más.

3. Demo: Configurando suscripciones en tiempo real con AWS AppSync

Profundicemos en los aspectos prácticos de configurar suscripciones en tiempo real en AWS AppSync.

Aquí encontrarás el código de GitHub con el ejemplo de la aplicación en tiempo real: https://github.com/alazaroc/appsync-website-subscription

Esta es la arquitectura de la solución que construiremos:

Arquitectura en tiempo real AWS v1

3.1. Crear la Tabla DynamoDB

Usaremos una tabla DynamoDB para almacenar datos de mensajes. Este es el schema de la tabla que crearemos:

1
2
3
4
type Messages {
  id: String!
  message: String
}

Para crearla, puedes usar el código Terraform proporcionado en este repositorio de GitHub para crear tu tabla DynamoDB: https://github.com/alazaroc/appsync-website-subscription/tree/main/infrastructure.

Si eres nuevo con Terraform, puedes revisar este otro artículo

Ejecutaremos estos comandos dentro de la carpeta infrastructure (donde se encuentran los archivos .tf):

1
2
3
4
5
6
# Inicializaremos la configuración de terraform
terraform init
# Verificaremos qué se creará
terraform plan
# Lo desplegaremos
terraform apply --auto-approve

Comando terraform apply

Con esta ejecución, también hemos creado un primer elemento en la tabla “Messages”. Entraremos a la Consola de AWS para verificarlo:

Tabla DynamoDB creada con un elemento

3.2. Crear y configurar la API de AWS AppSync

3.2.1. Creando suscripciones

Crearemos una nueva API AppSync que estará conectada con la tabla DynamoDB. En este caso, no lo haremos con Terraform, porque estamos aquí para aprender cómo funciona AppSync para la configuración en tiempo real. Lo haremos manualmente.

Es muy sencillo:

  1. Accede al servicio AWS AppSync
  2. Crear API
  3. En GraphQL API Data Source selecciona Start with a DynamoDB table
  4. Elige un nombre de API, y en Import from DynamoDB table, selecciona el nombre de la tabla DynamoDB creada previamente: Messages.
  5. Finalmente, en Configure model information, agrega un nuevo campo message de tipo String. Opcionalmente, puedes cambiar el tipo del campo id a ID en lugar de String.
    • Si configuras el tipo como ID, cuando crees un valor usando el editor, el tipo ID asegurará que el valor sea único y se trate como un identificador. Esto significa que las comparaciones se basarán en el identificador único en lugar de un simple valor de cadena, proporcionando una forma más eficiente y confiable de referenciar elementos en tu base de datos.
  6. Eso es todo

Ahora, puedes explorar la API creada.

Revisemos el Schema, y específicamente la configuración de suscripción:

Esto se creó automáticamente:

1
2
3
4
5
6
7
8
type Subscription {
  onCreateMessages(id: ID, message: String): Messages
    @aws_subscribe(mutations: ["createMessages"])
  onUpdateMessages(id: ID, message: String): Messages
    @aws_subscribe(mutations: ["updateMessages"])
  onDeleteMessages(id: ID, message: String): Messages
    @aws_subscribe(mutations: ["deleteMessages"])
}

Aquí, se crearon 3 suscripciones, una para cada operación de mutación: crear, actualizar y eliminar. Significa que puedes suscribirte a una de estas operaciones y luego recibirás notificaciones cuando se ejecuten.

Quiero repetirlo porque así es como funciona.

Al suscribirte a una operación de mutación, recibirás una notificación cada vez que se ejecute esa operación. Esta suscripción establece una conexión WebSocket que se activa específicamente por la operación de mutación, no directamente por la base de datos en sí. Por lo tanto, si modificas la base de datos fuera del servicio AWS AppSync, no recibirás ninguna notificación.

3.2.2. Recibir notificaciones si la base de datos es modificada

Para ser notificado si el cambio se realiza directamente en la base de datos sin el uso de la operación de mutación de AWS AppSync, tendrás que usar una configuración adicional. En el caso de que estuvieras usando DynamoDB, tendrás que habilitar DynamoDB Streams y tendrás que configurar una función Lambda para notificar a tu AWS AppSync. Puedes verlo en el siguiente diagrama:

Arquitectura en tiempo real AWS v2

3.2.3. Suscripción alternativa: canal pub/sub

También puedes crear directamente una API en tiempo real, creando una API pub/sub simple impulsada por WebSockets serverless

Si usas la consola de AWS para crear una API WebSocket en tiempo real donde los clientes se suscriben a canales, el contenido completo del schema será el siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Channel {
  name: String!
  data: AWSJSON!
}

type Mutation {
  publish(name: String!, data: AWSJSON!): Channel
}

type Query {
  getChannel: Channel
}

type Subscription {
  subscribe(name: String!): Channel
    @aws_subscribe(mutations: ["publish"])
}

Y luego puedes usar la operación de mutación de publish para publicar los datos, y la suscripción de subscribe para recibirlos en tiempo real. Eso es todo.

3.3. Implementar aplicación en tiempo real

Continuaremos con nuestro ejemplo de aplicación en tiempo real de mensajes almacenados en la tabla DynamoDB de Messages que habíamos creado antes.

Usaremos este código HTML y JavaScript para implementar actualizaciones de datos en tiempo real en tu aplicación y al mismo tiempo, persistirlos en la base de datos. Este ejemplo usa la suscripción para escuchar nuevas entradas de mensajes.

Expliquemos cómo usar la conexión con la operación de suscripción.

  1. Tenemos que obtener la URL completa del WebSocket
    1. Tenemos que usar el nuevo endpoint en tiempo real: wss://${APPSYNC_ENDPOINT_ID}.appsync-realtime-api.${AWS_REGION}.amazonaws.com/graphql
    2. En la solicitud, tenemos que incluir como datos de parámetro en el encabezado la siguiente información ?header=${header}&payload=${payload}, por lo que la URL completa será algo como wss://${APPSYNC_ENDPOINT_ID}.appsync-realtime-api.${AWS_REGION}.amazonaws.com/graphql?header=${header}&payload=${payload}
    3. El encabezado debe contener la versión codificada de los encabezados host y x-api-key, de la siguiente manera:
    1
    2
    3
    4
    5
    6
    7
    
      function encodeAppSyncCredentials() {
        const creds = {
          host: `${APPSYNC_ENDPOINT_ID}.appsync-api.${AWS_REGION}.amazonaws.com`,
          "x-api-key": APPSYNC_API_KEY,
        };
        return window.btoa(JSON.stringify(creds));
      }
    
    1. El payload debe estar vacío, por lo que podemos usar lo siguiente:
    1
    
     payload = window.btoa(JSON.stringify({}))
    
  2. Crearemos la conexión WebSocket usando la URL del paso anterior e inicializarla:

    1
    2
    3
    4
    5
    6
    7
    8
    
     const websocket = new WebSocket(url, ["graphql-ws"]);
     websocket.addEventListener("open", () => {
       websocket.send(
         JSON.stringify({
           type: "connection_init",
         })
       );
     });
    
  3. Después de eso, manejar los mensajes WebSocket según su tipo:

    • connection_ack: Indica que la conexión WebSocket se estableció exitosamente.
    • start_ack: Reconoce que la solicitud de suscripción ha sido recibida y aceptada.
    • error: Indica que ocurrió un error durante el proceso.
    • data: Contiene las actualizaciones en tiempo real del servidor basadas en la suscripción.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
     websocket.addEventListener("message", (event) => {
       const message = JSON.parse(event.data);
       console.log(message);
       switch (message.type) {
         case "connection_ack":
           startSubscription(websocket);
           break;
         case "start_ack":
           console.log("start_ack");
           break;
         case "error":
           console.error(message);
           break;
         case "data":
           handleNotification(message.payload.data.onCreateMessages);
           break;
       }
     });
    
  4. Para crear la conexión por primera vez, enviaremos una solicitud de suscripción una vez que se reconozca la conexión:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  function startSubscription(websocket) {
    const subscribeMessage = {
      id: window.crypto.randomUUID(),
      type: "start",
      payload: {
        data: JSON.stringify({
          query: `subscription onCreateMessages {
                    onCreateMessages {
                      id
                      message
                    }
                }`,
        }),
        extensions: {
          authorization: {
            "x-api-key": APPSYNC_API_KEY,
            host: `${APPSYNC_ENDPOINT_ID}.appsync-api.${AWS_REGION}.amazonaws.com`,
          },
        },
      },
    };
    websocket.send(JSON.stringify(subscribeMessage));
  }
  1. Ejecutando la aplicación. Solo tienes que hacer dos cosas:

  2. Actualiza tus credenciales en el código

1
2
3
  const AWS_REGION = "your-region"
  const APPSYNC_ID = "your-id-included-in-the-host";
  const APPSYNC_API_KEY = "your-api-key";
  1. Este es un sitio web HTML estático. Solo abre el archivo html en tu navegador

app ejecutándose

3.4. Probando la aplicación

Para probar la aplicación en tiempo real tenemos que crear un mensaje usando la operación de mutación de createMessages.

La forma más fácil de hacerlo es usando la sección Queries en la consola de AWS. En los siguientes mensajes, verás cómo funciona. En el lado izquierdo de la imagen, verás la consola de AWS y la generación de mensajes, y en el lado derecho verás nuestra aplicación HTML y los mensajes apareciendo después de la creación:

Paso 1, preparando la operación crear mensaje 1

Paso 2, ejecutando la operación y creando el mensaje actualizará inmediatamente el mensaje en nuestra aplicación en tiempo real: crear mensaje 2

4. Conclusión

Con AWS AppSync y suscripciones, construir aplicaciones en tiempo real se convierte en un proceso sencillo. Siguiendo esta guía, has configurado un sistema de mensajería que notifica a los usuarios en tiempo real cada vez que se envían nuevos mensajes. Este enfoque puede extenderse a varios casos de uso, haciendo que tus aplicaciones sean más interactivas y responsivas.

Siéntete libre de experimentar y construir más características en tu aplicación.

Como dijo Werner Vogels:

Nunca ha habido un mejor momento para ser un constructor.

¡AHORA VE Y CONSTRUYE!

Este artículo está licenciado bajo CC BY 4.0 por el autor.