Blog de Amazon Web Services (AWS)

CQRS en AWS: Sincronizando los Servicios de Command y Query con el Estándar Transactional Outbox, la Técnica Transaction Log Tailing y el Amazon DynamoDB Streams

Por Roberto Perillo, arquitecto de soluciones empresariales en AWS Brasil.

Hasta ahora, en esta serie de blog posts, he abordado diferentes formas de implementar el estándar arquitectural CQRS con diferentes servicios de AWS, cada opción con sus ventajas y desventajas. En la primera parte, empecé con la opción más sencilla, en la que utilizábamos una cola de Amazon SQS para transmitir datos del servicio de comandos al servicio de consultas. A continuación, exploramos diferentes opciones para usar el estándar Transactional Outbox. En la segunda parte, analizamos el estándar Transactional Outbox y la técnica Polling Publisher, en la que tenemos una tabla de bandeja de salida que contiene registros que representan los eventos de dominio que va a publicar y consumir el servicio de consultas, y que se lee de vez en cuando para que los eventos se publiquen. En la tercera parte, seguimos utilizando el estándar Transactional Outbox, pero con la técnica de seguimiento de registros transaccionales. Para leer la tabla de bandejas de salida, usé el Debezium Connector for PostgreSQL. El caso presentado en la cuarta parte era el mismo que en la tercera, pero usé el Amazon Database Migration Service para leer la tabla de la bandeja de salida. En los cuatro escenarios presentados hasta ahora, teníamos una base de datos relacional como base de datos del servicio de comandos y una base de datos NoSQL como base de datos del servicio de consultas.

Ahora veamos cómo podemos hacer lo contrario. Tendremos una tabla de Amazon DynamoDB para el servicio de comandos y una base de datos relacional para el servicio de consultas. DynamoDB es una de las bases de datos de llave valor de AWS que ofrece una latencia de milisegundos de 1 dígito a cualquier escala. El caso práctico será prácticamente el mismo, pero ahora la tabla de DynamoDB contendrá datos relacionados con los clientes, los productos y los pedidos, y la base de datos relacional (que será el Amazon Aurora for PostgreSQL-Compatible Edition) contendrá los datos resumidos relacionados con cada cliente y su correspondiente total de pedidos.

Introducción

La mayoría de las aplicaciones que creamos requieren una base de datos para almacenar los datos. Almacenamos el estado de nuestros objetos de dominio y los utilizamos para diversos fines, como procesar y generar informes. En ocasiones, es posible que nuestra base de datos no sea ideal para la recuperación de datos, ya sea por su naturaleza o debido a un modelo de dominio complejo, por ejemplo. Para estos casos, podemos usar el estándar arquitectural CQRS, que sugiere que, para un determinado bounded context (en español, algo así como “contexto delimitado”) de nuestro dominio, podemos tener dos servicios, uno para recibir comandos (es decir, operaciones que cambian de estado) y otro para consultas (que solo recuperan datos). De este modo, cada servicio puede tener la base de datos que mejor se adapte. El desafío consiste en cómo mantener sincronizadas las dos bases de datos, lo que se puede hacer con los eventos publicados desde el servicio de comandos para que los consuma el servicio de consultas.

La publicación confiable de eventos relacionados con cosas que ya han sucedido en una aplicación puede ser un desafío. Tanto si utilizamos el estándar CQRS como si no, si no tenemos cuidado, es posible que acabemos publicando información que aún no existe o que no la publiquemos en cualquier momento, como ya comentamos en la primera parte de esta serie, y las fuentes de datos pueden perder la sincronía. Al usar DynamoDB, podemos usar la función de transmisiones, que proporciona el propio servicio DynamoDB. Amazon DynamoDB Streams es el registro de los elementos que han permanecido en una tabla de DynamoDB durante las últimas 24 horas. La función de transmisiones también está disponible en otras bases de datos NoSQL, como Amazon DocumentDB y Amazon Neptune.

Esta es una forma de implementar la técnica Transaction Log Tailing. A diferencia de las opciones presentadas en publicaciones anteriores, no podemos leer el log de transacciones de una tabla de DynamoDB, pero podemos lograr el mismo efecto con DynamoDB Streams. Eso es lo que vamos a usar para implementar CQRS, con una tabla de DynamoDB como base de datos del servicio de comandos.

Caso de uso: Amazon DynamoDB como base de datos del servicio de comandos y Amazon Aurora for PostgreSQL-Compatible Edition como base de datos del servicio de consultas, utilizando el estándar Transactional Outbox y la técnica Transaction Log Tailing con el Amazon DynamoDB Streams

Para implementar esta técnica, no necesitamos instalar otros componentes para leer el log de transacciones de una tabla de DynamoDB. Una de las opciones es usar DynamoDB Streams, que es un registro que contiene los eventos que representan todo lo que ha ocurrido en una tabla de DynamoDB en las últimas 24 horas. También hay otra opción para generar eventos a partir de una tabla de DynamoDB, que consiste en conectar una stream de datos de Amazon Kinesis Data Streams a la tabla de DynamoDB desde la que queremos leer los eventos. Para obtener más información sobre esta técnica, consulte Uso de Kinesis Data Streams para capturar cambios en DynamoDB. La elección depende de cada caso de uso específico, ya que hay algunas diferencias entre las dos opciones.

Con Amazon DynamoDB Streams, las principales consideraciones son: 1) cada evento aparece solo una vez en la transmisión; 2) la transmisión se escala junto con la tabla de DynamoDB (si bien una tabla de DynamoDB se escala en particiones, la transmisión se escala en fragmentos); 3) puede haber un máximo de dos lectores que consuman cada fragmento de la transmisión; 4) obtenemos los eventos tal como ocurrieron en la tabla de DynamoDB en orden; 5) la transmisión contiene eventos que ocurrieron en las últimas 24 horas; y 5) podemos consumir la transmisión con una función de AWS Lambda, un adaptador Kinesis de DynamoDB Streams o un pipe de Amazon EventBridge. Puede encontrar más información en Change Data Capture with DynamoDB Streams y Transmisión de Amazon DynamoDB como fuente de Pipes EventBridge.

Por otro lado, con una transmisión de datos de Amazon Kinesis, las principales consideraciones son que 1) una notificación de artículo puede aparecer más de una vez en lo stream; 2) hasta 2 MB/s por stream (independientemente del número de consumidores que consuman datos fragmentados) sin una distribución en abanico mejorada, o 2 MB/s por consumidor, con una distribución en abanico mejorada; 3) los eventos pueden aparecer desordenados en la transmisión (el pedido se puede realizar con ApproximateCreationDateTime, un metadato del evento de la transmisión y que también se puede usar para identificar duplicados según el estándar, la transmisión contiene eventos que ocurrieron solo las últimas 24 horas, pero este número se puede aumentar hasta 8760 horas; y 5) hay más opciones disponibles para el consumo, incluido un pipe de EventBridge. Puede encontrar más información en Use Kinesis Data Streams para capturar los cambios en DynamoDB.

Lo que estamos intentando conseguir es una buena línea de razonamiento a la hora de elegir entre las dos opciones. Si, por ejemplo, también queremos usar otros servicios de procesamiento de datos en streaming, como Amazon Managed Service for Apache Flink o Amazon Data Firehose, para procesar más eventos, si queremos que más de dos consumidores (por stream o partición) consuman el stream o si necesitamos los datos durante más de 24 horas en la transmisión de datos, Amazon Kinesis Data Stream probablemente sea la opción correcta, pero probablemente sea una solución más cara. Si queremos, por ejemplo, simplemente leer lo stream y procesarla con un mínimo de lógica (con un máximo de 2 consumidores por stream), DynamoDB Streams será la elección correcta y probablemente sea una solución más económica. En esta publicación también se reflexiona sobre esta elección.

Dado que, en el ejemplo que estamos estudiando, queremos trasladar los cambios que se producen en una tabla de DynamoDB a una base de datos Aurora, ya sean inserciones, cambios o eliminaciones, y recibir los eventos desordenados no sería un problema, ya que se tratan de forma independiente, podríamos utilizar ambas opciones, pero DynamoDB Streams parece más apropiado, ya que es más sencillo y económico.

Para consumir eventos de un stream de DynamoDB, tenemos varias opciones. La primera es hacer que una función de Lambda la consuma directamente. En nuestro caso, nuestra función Lambda necesitaría consumir la transmisión, filtrar los eventos y publicarlos en un tema de Amazon SNS, lo que a su vez distribuiría el evento en dos colas de Amazon SQS. La otra opción sería usar la Amazon Kinesis Client Library (KCL). Dado que utilizamos DynamoDB Streams, necesitaríamos usar específicamente las versiones 1.X (la versión 2.X solo podría usarse si hubiéramos optado por Amazon Kinesis Data Streams). Otra opción es usar un pipe de EventBridge, ya que una transmisión de DynamoDB se puede usar como fuente en un pipe de Amazon EventBridge.

La opción que vamos a elegir es usar un pipe de EventBridge, ya que tenemos la flexibilidad de enriquecer los eventos, filtrarlos y publicarlos en un tema de SNS. Como dato curioso, cuando utilizamos las tablas globales de DynamoDB, también utilizamos indirectamente DynamoDB Streams para la replicación entre regiones.

Resumen de la Solución

En nuestro ejemplo, se realizará una llamada POST al endpoint /orders, con la información de un pedido que realizará un cliente. Esta llamada la validará un autorizador de Lambda (por motivos de simplificación, utilizaremos la autenticación básica) y la procesará más adelante mediante la función OrderReceiverLambda, que realizará la función de nuestro servicio de comandos. Esta función de Lambda introducirá los datos relacionados con el pedido realizado en nuestra tabla de DynamoDB y también insertará un elemento que representará el evento en el que se realizó el pedido.

Podríamos tener más de una tabla y emular la idea de una base de datos relacional, en la que insertaríamos datos en tablas como Orders y OrderItems, todas ellas relacionadas con una transacción, pero nuestra tabla de DynamoDB se modelará siguiendo la idea del Single-Table Design, que es la forma recomendada por AWS para trabajar con tablas de DynamoDB (más información sobre esto en esta publicación). El dominio que estamos explorando se puede representar mediante el siguiente modelo, en un enfoque de diseño de tabla única. El nombre de la tabla puede ser, por ejemplo, ECommerceData. Como almacenamos todo en una sola tabla, tiene sentido que la clave de partición y los nombres de las claves de clasificación sean genéricos, como “pk” y “sk”.

Imagen que muestra la tabla de Amazon DynamoDB con datos del ejemplo analizado, siguiendo el estándar Single-Table Design. Además de emular un formato relacional, también se persiste un elemento que representa el evento de la solicitud realizada, que luego se leerá del stream de la tabla y se publicará en un pipe de Amazon EventBridge.

Figura 1. La tabla de Amazon DynamoDB con datos del ejemplo analizado, siguiendo el estándar Single-Table Design. Además de emular un formato relacional, también se persiste un elemento que representa el evento de la solicitud realizada, que luego se leerá del stream de la tabla y se publicará en un pipe de Amazon EventBridge.

Cuando DynamoDB Streams está habilitado en una tabla, cuando una acción sobre un elemento persiste, un evento relacionado con la acción está disponible para su consumo en el stream como “imágenes” de los elementos. Podemos definir qué datos se generarán en el stream. Podemos generar imágenes de los atributos principales (claves principales y de clasificación), imágenes nuevas (los elementos completos que se insertaron o actualizaron), imágenes antiguas (los elementos completos antes de actualizarse) o imágenes nuevas y antiguas. En nuestro ejemplo, optamos por generar nuevas imágenes de los elementos insertados en la tabla de DynamoDB. En la lista siguiente se muestra un ejemplo de una imagen nueva.

{
    'last_purchase': {
	    'N': '1728900480'
    }, 'total': {
	    'N': '3000'
    }, 'name': {
        'S': 'bob'
    }, 'sk': {
        'S': 'ORDER_EVENT#01JA57ZF8M301N0B0F7HMZDG0V'
    }, 'pk': {
        'S': 'USER#bob'
    }, 'email': {
        'S': 'bob@anemailprovider.com'
    }
}

Los elementos que representan el pedido realizado se insertarán en la tabla de DynamoDB y cada evento de la tabla (ya sea de inserción, actualización o eliminación) generará un evento en el stream vinculada a la tabla. Habrá un artículo relacionado con el pedido, n artículos relacionados con los artículos del pedido y también un artículo que represente el evento del pedido. Podríamos incluir todos las inserciones de una transacción, pero hay que tener consideraciones adicionales en cuenta en este enfoque. La primera es que, dado que todos los elementos permanecerán en la tabla de DynamoDB de forma atómica, es posible que los eventos del stream aparezcan fuera del orden esperado. Otra consideración es que, cuando recuperamos eventos por batches de un stream, los elementos que se insertaron en una transacción pueden aparecer en batches diferentes.

Una última consideración sobre el uso de transacciones en DynamoDB es que una inserción en una transacción cuesta el doble que una inserción realizada sin una transacción (es decir, 1 KB cuesta 2 Write Capacity Units, o WCU). En nuestro ejemplo, aunque usáramos transacciones, recibir eventos fuera de orden o en lotes diferentes no sería un problema, ya que solo nos interesan los eventos que representan el evento de tramitación del pedido. No utilizaremos las transacciones para introducir los artículos relacionados con el pedido realizado, ya que es muy poco probable que surjan problemas con las inserciones.

En el stream se generará un evento que representa cada inserción realizada en la tabla de DynamoDB, pero solo nos interesan los eventos que representan el resumen del pedido, con el nombre del cliente, el correo electrónico, el total de la compra y la hora actual. Con DynamoDB Streams, no necesitamos una bandeja de salida independiente, como hacíamos con una base de datos relacional, pero necesitaremos un elemento de nuestra tabla de DynamoDB con esos datos, de modo que se pueda generar un evento en nuestro stream y podamos publicar fácilmente un evento sobre el pedido realizado sin mucho procesamiento. Este es el único propósito del elemento cuya sort key comienza por ORDER_EVENT# en nuestra tabla. De esta forma, nuestra secuencia contendrá todos los eventos de todas las inserciones (relacionados con el pedido, los artículos del pedido y el resumen del pedido), pero solo trataremos los eventos que representen la tramitación del pedido.

Los artículos que almacenamos en DynamoDB hacen referencia a clientes, pedidos y sus respectivos artículos, y también almacenamos un artículo relacionado con el evento de gestión logística del pedido, cuya sort key comienza por ORDER_EVENT#. En la práctica, solo necesitamos este elemento para transmitir el evento que se produjo en el lado del servicio de comandos al lado del servicio de consultas. Por lo tanto, este elemento tiene un carácter transitorio. Para que estos elementos no ocupen más espacio del necesario en la tabla de DynamoDB, podemos utilizar la función Time to Live (TTL) de DynamoDB, que es la función que permite eliminar un elemento de una tabla una vez transcurrido el período indicado en el propio elemento.

Para hacer uso de esta función, primero la habilitamos en la tabla donde la usaremos y definimos el atributo que almacenará el valor que indicará cuándo debe caducar el artículo. Con esto, podemos indicar para cada artículo cuándo debe caducar. El atributo debe ser del tipo Number y el valor debe expresarse en formato Unix Epoch Time. Cuando se alcanza el valor indicado, DynamoDB elimina el elemento sin consumir ninguna WCU. Dado que el artículo que representa el evento de gestión logística de un pedido es de naturaleza transitoria, una opción es habilitar el TTL en la tabla e indicar la fecha de caducidad en el artículo del caso.

Necesitamos publicar un evento que represente el pedido realizado por el cliente para poder actualizar nuestra base de datos del servicio de consultas. Tenemos dos opciones. La primera es, desde el punto de vista del servicio de comandos, recuperar todos los eventos de los pedidos realizados por el cliente, sumar el total de todos los pedidos y publicar un evento (en este caso, no podremos usar el TTL). Por lo tanto, nuestro evento contendría el importe total gastado por el cliente hasta ese momento. La otra opción es publicar el evento solo con el total del pedido realizado (aprovechando el artículo del evento de gestión logística del pedido) y hacer que la función Lambda que actualizará Aurora sume el importe actual (que será el total gastado por el cliente, independientemente del pedido que se acabe de realizar) al total del pedido que se acaba de realizar.

Según el número de pedidos que el cliente haya realizado hasta ese momento, la recuperación de todos los eventos de gestión logística de pedidos de la tabla de DynamoDB puede consumir muchas RCU, mientras que recuperar un registro de la réplica de lectura de Aurora para realizar la suma en el servicio de consultas requerirá menos procesamiento y tendrá más rendimiento, por lo que esa es la opción que vamos a elegir.

En la práctica, lo que hacemos es seleccionar un valor, agregarlo con otro valor y actualizar una tabla en nuestra base de datos de Aurora. En nuestro caso, se trata de una operación segura, ya que no nos enfrentaremos a race conditions, ya que un mismo cliente no realizará pedidos en intervalos en los que puedan darse las condiciones de la carrera. Si ese fuera el caso, una estrategia más segura sería utilizar estrategias de bloqueo, como Optmistic Locking.

Haremos que DynamoDB Streams publique un evento con la imagen completa (imagen nueva) del evento del pedido realizado, y ese stream será la fuente de un pipe en EventBridge. Uno de los parámetros que se pueden definir en la fuente de nuestro pipe es el tamaño del batch, que es la cantidad de eventos (batch size) que se recopilarán y pasarán a la segunda etapa de nuestro pipe. Podemos configurar este parámetro con el valor 10. Otro parámetro es el tiempo de espera antes del próximo consumo de la stream (batch window). Podemos dejar ese último parámetro en su valor predeterminado.

La segunda etapa del pipe será una función Lambda, en fase de enriquecimiento, cuyo propósito es preparar un evento con menos datos que los publicados por DynamoDB Streams y también filtrar los eventos. Nuestro pipe recibirá todos los eventos de nuestra tabla de DynamoDB, pero solo nos interesan los eventos que representan la tramitación del pedido, por lo que filtraremos los eventos distintos de la tramitación del pedido. Para seguir procesando los eventos en la fase de enriquecimiento, podríamos crear una lista e incluir en ella solo los eventos cuya sort key comience por “ORDER_EVENT#” y devolver la lista en la función Lambda.

Tras la fase de enriquecimiento, el evento finalmente se entrega a un tema de SNS, que lo envía a dos colas de SQS: una para buscar una función de Lambda que envíe un correo electrónico de notificación y la otra para que la lea una función de Lambda que actualice el registro del cliente en Aurora.

Además del endpoint que recibe las solicitudes, tendremos un segundo endpoint /clients/ {clientId}, que recuperará la información relacionada con el cliente. La función de Lambda que proporciona esta información representa nuestro servicio de consultas y la recupera de la réplica de lectura de Aurora, que se actualizó anteriormente. La información devuelta contiene el nombre del cliente, su correo electrónico y el importe total que el cliente ya había comprado hasta ese momento.

La función Lambda ClientRetrieverLambda recuperará la información relacionada con el cliente de la réplica de lectura de Aurora y, para conectarse, también recuperará la información de conexión almacenada en Amazon Secrets Manager. Solo podemos almacenar en secret el nombre de usuario y la contraseña de la base de datos e introducir el resto de la información, como el host, el nombre de la base de datos y el puerto de conexión, como variables de entorno en la función Lambda, o podemos almacenar toda la información de conexión en nuestro secret. Tenga en cuenta que lo ideal es recuperar la información del endpoint de lectura de Aurora (que es el ReaderInstanceEndpoint).

Imagen que muestra la arquitectura propuesta, con DynamoDB como base de datos del servicio de comandos y Aurora como base de datos del servicio de consultas. Tras la inserción de un evento de inserción de pedido en la tabla de DynamoDB, el evento está disponible en la secuencia de la tabla a través de DynamoDB Streams, que es leído por un pipe de EventBridge que publica estos eventos en un tema de SNS. Luego, SNS entrega los eventos a dos colas, una de las cuales es leída por un Lambda que actualiza Redis con los datos más recientes del cliente relacionados con cada evento.

Figura 2. Arquitectura propuesta, con Amazon DynamoDB como base de datos del servicio de comandos y Amazon Aurora como base de datos del servicio de consultas.

La ventaja de esta solución es que no necesitamos tener uma bandeja de salida para que los eventos se puedan publicar de forma fiable. Para ello, utilizamos una función que proporciona el propio servicio de DynamoDB, que es DynamoDB Streams. Tampoco necesitamos ningún mecanismo para limpiar nada, como el que teníamos con la bandeja de salida, ya sea una Lambda que la limpia en algún momento o una extensión de Postgres combinada con otros servicios de AWS, como pg_partman y Amazon S3. En el caso de DynamoDB, podemos utilizar la función TTL y colocar un atributo en el artículo que represente el evento de gestión logística del pedido e indique el momento en el que se puede eliminar el artículo.

La consideración aquí es que no siempre podemos usar DynamoDB. Hay casos en los que simplemente no se nos permite usarlo, debido a las normas internas de la empresa para la que trabajamos. O puede que DynamoDB no se ajuste a nuestros estándares de acceso. Intentar replicar la idea de una base de datos relacional en DynamoDB es un error, ya que, en última instancia, se trata de una base de datos de valores clave, pero es posible emular esta idea con el Single-Table Design. Es importante evaluar los patrones de acceso y planificar el diseño de las tablas antes de usar DynamoDB, así como reflexionar sobre si DynamoDB es la base de datos adecuada para usarse en un proyecto determinado.

Como hemos optado por DynamoDB Streams, solo podemos leer los eventos que representan acciones que se han producido en la tabla de DynamoDB en las últimas 24 horas y no se pueden cambiar. Si necesitáramos más tiempo, tendríamos que optar por Amazon Kinesis Data Streams, que sustituiría a DynamoDB Streams y sería el origen del pipe EventBridge. De forma predeterminada, también conserva los datos en la transmisión durante 24 horas, pero esta cantidad se puede aumentar hasta 8760 horas, lo que equivale a 365 días.

Ejecutando el Ejemplo

Para ejecutar el ejemplo, los lectores deben tener una cuenta de AWS y un usuario con permisos de administrador. A continuación, basta con ejecutar el paso a paso que se proporciona en el repositorio de código para esta serie de entradas de blog sobre CQRS, en AWS Samples, alojado en Github. Al realizar el proceso paso a paso, los lectores dispondrán de la infraestructura que se presenta aquí en sus propias cuentas.

El ejemplo contiene dos endpoints, uno para recibir información relacionada con los pedidos (que representa nuestro servicio de comando) y el otro para recuperar información relacionada con los clientes (que representa nuestro servicio de consultas). Para comprobar que todo ha funcionado correctamente, ve a la Amazon API Gateway y, en la lista de API, introduce la API “OrdersAPI” y, a continuación, “Stages”. Solo habrá una stage llamada “prod”. Recupere el valor del campo “Invoke URL” y añada “/orders”. Este es el endpoint que recibe la información relacionada con los pedidos.

Hagamos una solicitud POST a ese endpoint. Podemos usar cualquier herramienta para realizar solicitudes, como cURL o Postman. Como este endpoint está protegido, también necesitamos añadir basic authentication. Si utilizas Postman, tendrás que recuperar el nombre de usuario y la contraseña generados al crear la infraestructura. En el API Gateway, vaya a “API Keys” y copie el valor de la columna “API Key” de “admin_key”. Este valor contiene el nombre de usuario y la contraseña separados por el carácter “:”, pero está codificado en Base64. Decodifique el valor con una herramienta en línea o con el comando “base64” de Linux. El nombre de usuario está a la izquierda del carácter “:” y la contraseña, a la derecha. Añada una “Authorization” del tipo “Basic Auth” y rellene los campos “Username” y “Password” con los valores recuperados. Añade también un header “Content-Type”, con el valor “application/json”.

Si está utilizando, por ejemplo, cURL, no será necesario decodificar el valor de la clave API. Simplemente agregue un header “Authorization” con el valor “Basic <valor de la API key copiado de la columna API key>”. También añade un header “Content-Type” con el valor “application/json”.

El payload para realizar solicitudes a este endpoint es el siguiente:

{
    "client": "bob",
    "products": [{
        "product": "Computer",
        "quantity": 1
    }, {
        "product": "Phone",
        "quantity": 3
    }]
}

Esto representa un pedido realizado por el cliente con el nombre de usuario “bob”, que contiene los productos denominados “Computer” y “Phone”. El total de ese pedido es de $3000. Toda esta información se almacenará en DynamoDB. Al realizar esta solicitud POST, si todo ha funcionado según lo previsto, debería ver el siguiente resultado:

{
    "statusCode": 200,
    "body": "Order created successfully!"
}

Ahora, verifiquemos que la información relacionada con el cliente se haya enviado a Aurora. Al endpoint de API Gateway, que se recuperó anteriormente, agregue “/clients/bob”. Este es el endpoint que recupera la información relacionada con el cliente. Hagamos una solicitud GET para ese endpoint . Al igual que hicimos con el punto final “/orders”, necesitamos añadir basic authentication. Siga los pasos explicados anteriormente y realice la solicitud GET. Si todo ha funcionado según lo esperado, verás un resultado similar al siguiente:

{
    "name": "Bob",
    "email": "bob@anemailprovider.com",
    "total": 3000.0,
    "last_purchase": 1700836837
}

Esto significa que hemos podido alimentar correctamente a Aurora con información lista para ser leída, mientras la misma información está en DynamoDB, en otro formato.

Limpiando los Recursos

Para eliminar la infraestructura creada, en una terminal, en el mismo directorio donde se creó la infraestructura, simplemente ejecute el comando “cdk destroy” y confirme. La eliminación tarda aproximadamente 15 minutos.

Conclusión

Este es el último blog que muestra posibles soluciones que abordan cómo implementar el estándar arquitectural CQRS con los servicios de AWS. En él se muestra cómo implementarlo con Amazon DynamoDB, específicamente con Amazon DynamoDB Streams, que es una forma de aplicar el estándar Transactional Outbox, de forma que podamos trasladar al servicio de consultas los cambios ocurridos en el servicio de comandos. Al usar DynamoDB, su stream puede desempeñar la función de una bandeja de salida. También es importante tener en cuenta que la función de streams también está disponible en otros servicios de AWS, como Amazon DocumentDB y Amazon Neptune. Sin embargo, en el momento de escribir este artículo, solo un stream de DynamoDB puede ser el origen de un pipe de EventBridge, por lo que los streams de otras bases de datos deberán consumirse por otros medios.

Con DynamoDB Streams, no necesitamos tener una tabla independiente para representar los eventos, como ocurre cuando utilizamos una base de datos relacional para el servicio de comandos. Este stream es el log que contiene todos los eventos que se han producido en nuestra tabla en las últimas 24 horas. En esta solución, el stream es el origen de un pipe de EventBridge, lo que nos brinda la flexibilidad de enriquecer los eventos, filtrarlos y enviarlos a otros servicios, como un tema de SNS, que es lo que hicimos. Introdujimos todos los datos relacionados con el pedido y también un elemento que representa el evento de recepción del pedido, que es lo que publicamos como evento. Lambda filtra todos los demás eventos en la fase de enriquecimiento.

Otra posibilidad sería utilizar una transmisión de datos de Amazon Kinesis, pero esta es una solución más adecuada en otros casos, como cuando queremos enviar nuestros eventos a otros servicios de procesamiento de datos en streaming, como Amazon Data Firehose, o necesitamos conservar nuestros eventos durante más de 24 horas. En el ejemplo que analizamos, DynamoDB Streams era una solución más adecuada, ya que es más sencilla y no utilizamos otros servicios como Amazon Data Firehose.

En esta serie de publicaciones, analicé cinco maneras diferentes de aplicar el estándar arquitectural CQRS con diferentes servicios de AWS. Pero, ¿cómo podemos elegir qué solución usar en un escenario determinado? Lo que me gustaría hacer ahora es reflexionar sobre las diferentes posibilidades, para que los lectores puedan elegir la solución que mejor se adapte a sus necesidades en un escenario determinado. Y este será el último post de esta serie, ¡y se presentará a continuación!

Este contenido és una traduccíon del blog original en Portugués (enlace acá).

Acerca del Autor

Roberto Perillo Roberto Perillo es un arquitecto de soluciones empresariales en AWS Brasil, especializado en sistemas serverless, que presta servicios a clientes del sector financiero y ha estado en la industria del software desde 2001. Trabajó durante casi 20 años como arquitecto de software y desarrollador Java antes de unirse a AWS en 2022. Es licenciado en Ciencia de la Computación, tiene una especialización en Ingeniería de Software y un máster también en Ciencia de la Computación. Un aprendiz eterno. En su tiempo libre, le gusta estudiar, tocar la guitarra y también ¡jugar a los bolos y al fútbol de mesa con su hijo, Lorenzo!

Acerca del Colaborador

Luiz Santos Luiz Santos trabaja actualmente como Technical Account Manager (TAM) en AWS y es un entusiasta de la tecnología, siempre busca nuevos conocimientos y tiene una mayor experiencia en el desarrollo de software, el análisis de datos, la seguridad, la tecnología serverless y DevOps. Anteriormente, tenía experiencia como arquitecto de soluciones de AWS y SDE.

Acerca de las Traductoras

Gabriela Guimarães Gabriela Guimarães es actualmente una de las 10 jóvenes que participan en el primer Tech Apprentice Program – Black Women Edition en AWS. Licenciado en Análisis y Desarrollo de Sistemas, estudia el mundo de la nube con enfoque en backend.
Maria Lucio Maria Lucio es Aprendiz de Tecnología, formando parte del primer Tech Apprentice Program – Black Women Edition en AWS. Licenciada en Técnico en Computación y Licenciada en Sistemas de Información, le apasiona la programación y orienta su trabajo como aprendiz hacia proyectos y estudios que le permitan evolucionar en el área del desarrollo de software.

Acerca de los Revisores

Erika Nagamine Erika Nagamine es arquitecta de soluciones de datos en AWS. Tiene una sólida formación académica, con un título en Sistemas de Información, un posgrado en Administración de Bases de Datos, Ingeniería de Datos y una especialización en Minería de Datos Complejos de la Unicamp. Trabaja con clientes de varios segmentos y ha participado en más de 200 proyectos en Brasil y en todo el mundo. Actualmente cuenta con múltiples certificaciones en datos y computación en la nube, todas las certificaciones de AWS, y le encanta compartir conocimientos en las comunidades y dar conferencias en eventos técnicos destacados en Brasil y el mundo.
Ciro Santos Ciro Santos es arquitecto senior de soluciones en AWS y trabaja con arquitectura y tecnología durante más de 20 años. Especializado en arquitecturas de software y nativas de la nube. Enfocándose en temas como microservicios, contenedores, serverless, DevOps y mobile.
Gonzalo Vásquez Gonzalo Vásquez es Senior Solutions Architect de AWS Chile para clientes de los segmentos Independent software vendor (ISV) y Digital Native Business (DNB) de Argentina, Paraguay y Uruguay. Antes de sumarse a AWS, se desempeñó como desarrollador de software, arquitecto de sistemas, gerente de investigación y desarrollo y CTO en compañías basadas en Chile.