Blog de Amazon Web Services (AWS)

CQRS en AWS: ¿Qué Solución Elegir?

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

¡Esta es la última parte de la serie que cubre las diferentes formas de implementar el estándar arquitectural CQRS en AWS! Durante la serie, traté de analizar qué es el estándar y las diferentes técnicas que demuestran cómo se pueden publicar eventos desde el servicio de comandos para utilizarlos en el servicio de consultas. En cada entrada de blog, el aspecto más importante es, en realidad, el mecanismo que permite emitir y transmitir eventos desde el servicio de comandos para que los consuma el servicio de consultas, de modo que puedas tener cualquier base de datos en cada uno de los servicios.

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 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 tiempos en tiempos para que los eventos se publiquen. En la tercera parte, seguimos utilizando el estándar Transactional Outbox, pero con la técnica de Transactional Log Tailing. Para leer la bandeja de salida, usé el conector Debezium para PostgreSQL. El caso presentado en la cuarta parte era el mismo que en la tercera, pero usé el Amazon Database Migration Service (Amazon DMS) para leer la bandeja de salida. Por último, en la quinta parte demostré cómo es posible emitir y consumir eventos cuando se tiene una tabla de Amazon DynamoDB en el servicio de comandos, a través de DynamoDB Streams.

En las publicaciones también se analizan las formas serverless de implementar el estándar, con las API expuestas por Amazon API Gateway, que delegan las solicitudes a las funciones de AWS Lambda, pero se puede usar cualquier forma de computación para implementar el estándar.

Hubo 5 publicaciones de blog que demostraron diferentes mecanismos. Una pregunta que los lectores pueden hacerse es: ante diferentes técnicas, diferentes posibilidades y diferentes servicios, ¿qué opción deberían elegir? Esa es la pregunta que esta entrada de blog busca abordar. La idea es ofrecer a los lectores un modelo mental para que puedan tomar una decisión consciente frente al escenario actual.

Introducción

El estándar CQRS resuelve el problema de las diferentes necesidades computacionales para escribir y leer. Por lo tanto, la norma propone dos servicios para estas necesidades. Es posible que el servicio de escritura esté implementado como un contenedor en Amazon EKS y que el servicio de lectura sea una función de AWS Lambda, lo que permite acceder a diferentes bases de datos y que cada componente tenga necesidades computacionales diferentes. Sin embargo, no es necesario que usemos bases de datos o modelos de datos diferentes. Por ejemplo, la base de datos puede ser una Amazon Aurora que tenga diferentes réplicas de lectura. El servicio de escritura puede gestionar el cluster endpoint, mientras que el servicio de lectura puede gestionar el reader endpoint que puede abstraer hasta 15 instancias de diferentes tamaños.

Como vimos en la primera parte, la idea del patrón es permitir que las diferentes necesidades se representen de diferentes maneras. En el caso de la Aurora mencionada anteriormente, tendremos el modelo relacional en ambos servicios. Y dado que Aurora tiene las características de una base HTAP (procesamiento transaccional/analítico híbrido), hay varios casos en los que puede tener sentido usarla tanto en el lado del servicio de comandos como en el del servicio de consultas. O un modelo de datos concreto que sea complejo o que, por alguna razón, no sea fácil de leer, se puede representar en otro formato, que se puede leer con una latencia aceptable para el usuario. Por lo tanto, un modelo de datos que se encuentre en un determinado formato o base de datos que sea apropiado para su escritura pero que no permita una recuperación fácil puede representarse de otra manera, a fin de facilitar la lectura.

El patrón le permite representar aspectos de un modelo de datos de diferentes maneras. En el caso que analicé en la serie, tenemos un modelo de datos simple que hace referencia a un comercio electrónico desde el punto de vista del servicio de comandos. En la base de datos, tendremos registros que representan a los clientes, los productos, los pedidos y sus respectivos artículos.

Imagen que muestra el modelo de dominio utilizado para demostrar las soluciones presentadas en las publicaciones del blog

Figura 1. El modelo de dominio utilizado para demostrar las soluciones presentadas en las publicaciones del blog.

El aspecto que queremos representar aquí, para facilitar las consultas, es la cantidad total comprada por cada cliente. Otro ejemplo sería una serie de registros en los que necesitaríamos realizar diferentes cálculos sobre un valor de una columna determinada para obtener un resultado. Una forma sería leer todos estos registros y realizar cálculos en la memoria. Dependiendo de la cantidad de registros, esto podría resultar costoso, además de requerir más recursos computacionales. En este caso, podríamos tener el modelo de datos como una serie de registros en el lado del servicio de comandos y otro que proporcione el resultado de los cálculos precalculados en el lado del servicio de consultas. De este modo, tendríamos más rendimiento y menos demanda de recursos informáticos.

Quiero dejar constancia de dos observaciones: la primera es que, en AWS, la seguridad es un tema que nos tomamos muy en serio. En las entradas del blog, en cada subsección de “Ejecutando el Ejemplo”, indiqué la creación de un usuario con acceso de administrador. La idea era facilitar la vida del lector y acelerar el proceso de creación del entorno. Pero, de hecho, tener un usuario administrador para crear el entorno escapa al principio del mínimo privilegio. Por lo tanto, una posibilidad es crear el usuario con acceso de administrador, crear el entorno y eliminar al usuario inmediatamente después de crear el entorno. O bien, a modo de ejercicio, los lectores pueden encontrar las políticas de Amazon IAM más adecuadas (para encontrar las políticas más estrictas para cada ejemplo, puede utilizar el Amazon IAM Access Analyzer) y utilizarlas para crear usuarios con un acceso más restringido, para crear entornos.

La segunda es que, cuando empecé a escribir la serie, y Luiz y yo ya habíamos empezado a trabajar en el código CDK que se encuentra en el repositorio de código de esta serie de entradas de blog, Redis Inc. anunció que Redis ya no era de código abierto. Eso nos tomó por sorpresa. Si lo hubiera sabido antes de empezar las implementaciones, habría elegido otra base de datos como ejemplo para la base de datos del servicio de consultas (probablemente DynamoDB), pero las técnicas que he presentado funcionan para cualquier base de datos. Solo en la primera entrada del blog, muestro un esquema de idempotencia con Redis, que los lectores pueden reemplazar con una implementación que utilice, por ejemplo, DynamoDB con escritura condicional, para lograr el mismo efecto de lock Redis, y TTL, de modo que el elemento que controla la idempotencia caduque después de algún tiempo en la tabla de DynamoDB.

Opciones Cuando la Base de Datos del Servicio de Comandos es Relacional

Al implementar el estándar CQRS en AWS mediante un modelo de datos relacional, la forma más sencilla es usar una base de datos Aurora o RDS y usar réplicas de lectura, con el servicio de escritura gestionando el cluster endpoint y el servicio de lectura gestionando el reader endpoint. En este caso, la base de datos y el modelo de datos serían los mismos en el servicio de comando y lectura, pero podríamos tener la base de escritura en una máquina y las réplicas de las lecturas en diferentes máquinas. En ese caso, solo abordaríamos diferentes necesidades computacionales.

Si necesitamos diferentes modelos de datos, lo primero que debemos hacer es comprobar si la base de datos que se está utilizando tiene un mecanismo nativo de stream, la facilidad de uso de ese mecanismo, si este mecanismo requiere algún componente periférico y el costo de la solución. En el caso de Aurora, como mencioné en el primer post, la opción nativa es Database Activity Streams, lo que implica el uso obligatorio de Amazon Kinesis Data Streams. Si la usáramos, podríamos tener un mecanismo similar al presentado en la publicación que aborde el uso de DMS cuando un pipe de Amazon EventBridge proviene de Amazon Kinesis Data Streams, y el resto de la solución sería la misma. El detalle es que esta funcionalidad de Aurora está destinada más a la auditoría y, por lo tanto, los registros enviados a Amazon Kinesis Data Streams tienen un formato que no es muy fácil de procesar. Luego, tendríamos que procesar los registros, ya sea al publicar el evento en el servicio de redacción o al procesar el evento en el servicio de consulta.

La opción más rentable entre las soluciones presentadas es la que se discutió en la primera publicación. En él, se crea un evento (representado por un documento JSON) en tiempo de ejecución para representar la situación que ocurrió y se publicó en una cola de SQS. Si se utiliza una cola de SQS estándar, hay que tener cuidado de tratar los eventos de forma idempotente en la función de Lambda que recupera los mensajes. La primera publicación muestra una forma idempotente de gestionar los mensajes con Amazon Elasticache for Redis. El motivo por el que las colas estándar tienen al menos una semántica de entrega se explica en Amazon SQS At-Least Once Delivery.

Esta solución funcionará bien en la gran mayoría de las situaciones. Sin embargo, puede ocurrir que se recupere un evento del lado del servicio de consultas y que el componente que lee la cola principal y actualiza la base de servicios (en los ejemplos de la serie, una función de Lambda) no pueda, por algún motivo, actualizarlo. En este caso, para evitar perder el evento, habrá que redirigirlo a una dead-letter queue, pero la base de datos del servicio de consultas estará desactualizada y su actualización dependerá del tratamiento que se dé a una parte del evento de la dead-letter queue. Esta solución tampoco permite volver a procesar los eventos. Tampoco requiere que se limpie la base de servicios de comandos. Por lo tanto, si el lector busca una solución sencilla, un posible retraso en el procesamiento de eventos no es un problema y no hay necesidad de volver a procesar los eventos, esta es la solución que recomendaría.

En 2018, tan pronto como se publicó, leí un libro increíble llamado Microservices Patterns, de Chris Richardson, que aborda varios patrones fundamentales para utilizar el estilo arquitectural de los microservicios de la mejor manera posible. Concretamente, en el capítulo 3, Chris Richardson aborda la idea de la comunicación asincrónica entre microservicios y, en las páginas 97 y 98, en la subsección de Transactional Messaging, presenta el estándar Transactional Outbox. Este estándar se puede usar en cualquier situación que implique la publicación de eventos y, dado que el CQRS requiere que los eventos se publiquen desde el servicio de comandos para que los consuma el servicio de consultas, pensé que tendría sentido combinar los dos estándares y crear las soluciones que presenté en las publicaciones del blog. Por lo tanto, la inspiración para crear la mayoría de las soluciones que presenté en esta serie provino del libro Microservices Patterns.

Empecé a abordar el estándar Transactional Outbox desde la segunda publicación. La idea del estándar es que los registros que representan eventos relacionados con eventos de nuestros aggregates se conserven junto con los propios aggregates. Por ejemplo, supongamos que el estado de pago de un pedido aggregates ha pasado de “PagoPendiente” a “PagoConfirmado”. En la operación en la que se conserven los datos aggregates, también conservaremos un registro en una tabla que representará el evento ocurrido. Luego, de alguna manera, recuperaremos ese registro y lo publicaremos en un message broker. La tabla en la que se conserva el registro que representa el evento se denomina outbox table (en español, algo así como “bandeja de salida”).

En el siguiente ejemplo, en las tablas que hacen referencia al Pedido aggregate los detalles de un pedido realizado junto con los datos que representan el evento ocurrido en una tabla “PedidoEvent”.

El estándar Transactional Outbox. En este ejemplo, un aggregate de pedidos se compone de la clase Pedido, que a su vez contiene una lista de ArticuloPedido. Al realizar un pedido, el aggregate se confirma junto con un registro que representa el evento de creación del pedido.

Figura 2. El estándar Transactional Outbox. En este ejemplo, un aggregate de pedidos se compone de la clase Pedido, que a su vez contiene una lista de ArticuloPedido. Al realizar un pedido, el aggregate se confirma junto con un registro que representa el evento de creación del pedido.

En el ejemplo anterior, la tabla PedidoEvent representa únicamente los eventos de creación de pedidos. Si quisiéramos almacenar más eventos en esta tabla, podríamos tener una columna que represente el tipo de evento. En este caso, sería ideal que los datos del evento en sí se almacenaran en una columna cuyo tipo fuera JSON (de modo que no fuera necesario crear n columnas y tener valores nulos en los registros), o también podríamos crear una tabla para cada tipo de evento que ocurriera en la aplicación.

Aunque la técnica se refiere a bases de datos relacionales, también funciona con bases de datos NoSQL. Por ejemplo, suponiendo que usáramos Amazon DocumentDB, podríamos conservar en el documento que representa el aggregate una entrada que representaría un evento que se va a publicar. Si la base que estamos utilizando no tiene mecanismos de streaming, podríamos aplicar el mismo mecanismo que en la segunda entrada del blog en la que se utiliza la técnica de Polling Publisher o, si los tiene, usar el mecanismo de streaming de la base de datos para publicar los eventos, como hice en la quinta entrada del blog.

La ventaja de este estándar es que se garantiza que los eventos se emitirán y procesarán en algún momento, por lo que no hay pérdida de eventos. El estándar solo se refiere a la persistencia de los eventos en las bandejas de salida. Para publicar los eventos, es necesario utilizar algún mecanismo que recupere los eventos de la bandeja de salida de alguna manera y los publique.

Imagen que muestra el mecanismo de publicación de eventos del estándar Transactional Outbox

Figura 3. En el estándar Transactional Outbox, los registros que representan eventos se conservan de forma atómica junto con los datos que representan el aggregate que se conserva. Para que se publiquen eventos, existe un mecanismo que de alguna manera lee los eventos y los publica en un intermediario de mensajes.

Al usar este estándar, hay que tener cuidado de procesar siempre los eventos de manera idempotente y tener algún mecanismo para mantener la bandeja de salida lo más limpia posible, de modo que no crezca inadvertidamente. Para publicar eventos desde las bandejas de salida, existen dos técnicas: Polling Publisher y Transaction Log Tailing. En la segunda publicación, hablé de la primera técnica. Por ejemplo, usé el mecanismo programador de Amazon EventBridge para invocar la función Lambda que recupera los eventos de la bandeja de salida cada minuto. En las publicaciones tercera y cuarta, hablé de la segunda técnica. En la tercera entrada, para leer el log de transacciones de la base de datos Aurora, usé el Debezium Connector for PostgreSQL instalado en un cluster de Amazon Managed Streaming for Kafka (Amazon MSK). En la cuarta publicación, usé DMS con replicación continua.

La ventaja de la solución presentada en la segunda publicación y que utiliza la técnica Polling Publisher es que, aunque es un poco más compleja que la presentada en la primera publicación, sigue siendo relativamente sencilla. Tampoco requiere la instalación de componentes adicionales (como la solución presentada en la tercera publicación), no requiere mecanismos de base de datos específicos (como la parametrización para permitir la lectura del log de transacciones) y funciona con cualquier base de datos. La desventaja es el tiempo de actualización entre las bases. Otra consideración es que, en principio, el objetivo principal de esta técnica no es el reprocesamiento de eventos. Tampoco permite obtener los metadatos de las transacciones en sí mismas, ya que recuperamos los eventos de la bandeja de salida para su publicación, en lugar del log de transacciones de la tabla.

Esta técnica implica el uso de un mecanismo que consulta la bandeja de salida de tiempos en tiempos, y es posible que no haya datos en n consultas a la tabla, lo que desperdicia el procesamiento (si utiliza Aurora, le recomiendo encarecidamente que utilice réplicas de lectura y recupere los eventos del punto final del lector o que cree un punto final personalizado solo para recuperar los eventos). Por el contrario, es posible que hayan pasado muchas cosas en la tabla de salida entre las consultas. Por lo tanto, si no puede haber pérdida de eventos, no es necesario volver a procesarlos, no es necesario obtener metadatos de las transacciones y no hay problema si las bases no están sincronizadas durante mucho tiempo (y “mucho tiempo” es una medida que el lector tendrá que definir caso por caso), esta es la solución que recomendaría.

La otra forma de tratar las bandejas de salida es la que se presenta en las publicaciones tercera y cuarta, que es la técnica denominada Transaction Log Tailing (en español, algo así como “leer la cola o el final del log de transacciones”). Con ella, tendremos un componente que acompañará al final del log de transacciones de una tabla determinada y facilitará la publicación de eventos. Además, dado que publicamos los eventos en transmisiones (en la tercera publicación usé Amazon MSK y en la cuarta, Amazon Kinesis Data Streams), podemos volver a leer los eventos y volver a procesarlos.

En la tercera publicación, utilizamos el Debezium Connector for PostgreSQL. Es un componente que se usa junto con Kafka y, de este modo, lee el final del log de transacciones de una tabla y publica los cambios (inserciones, actualizaciones, eliminaciones) en un tema de Kafka. La técnica consiste en conservar, en la misma transacción, un registro en una tabla de salida que es leído por Debezium. Por lo tanto, los registros enviados al tema de Kafka representan un evento que debe publicar el servicio de comandos para el servicio de consultas que ya se ha realizado, por lo que no hay posibilidad de que surja ningún problema entre la publicación del evento y la transacción de la base de datos.

En la cuarta publicación, presenté exactamente la misma técnica, pero utilizando DMS, que además de realizar migraciones de bases de datos, también permite hacer change data capture mediante la lectura del log de transacciones bancarias relacionales. La diferencia entre Debezium y DMS es que, por un lado, Debezium es más completo que DMS. Por lo tanto, admite más bases de datos y también cuenta con el apoyo de la comunidad. Varios clientes de AWS han informado de que admite un alto rendimiento, por lo que los datos de una tabla que recibe miles de inserciones y actualizaciones por segundo aparecen en milisegundos en el tema de Kafka. La desventaja es la configuración. Varios clientes también informaron que lograr la instalación y la configuración correctas, combinadas con el modelo de seguridad de AWS, puede ser un desafío. Pero una vez que todo está configurado correctamente, funciona a la perfección.

Por otro lado, la configuración del DMS es más fácil de usar. En una tarea de migración de bases de datos, se indican los puntos finales de origen (o desde dónde se leerán los datos) y de destino (adonde van a ir los datos). En Debezium, esto equivale a los conectores de sincronización y fuente. La desventaja es que DMS no tiene el alcance de Debezium, por lo que no admite la recuperación de datos de tantas bases de datos como Debezium. Además, si una tabla ya tiene registros antes de la primera lectura del DMS, habrá una penalización por la lectura inicial. El DMS realiza un escaneo completo de la tabla en la primera lectura y, a continuación, comienza a leer el log de transacciones.

Es importante tener en cuenta que para que el DMS funcione correctamente, es necesario elegir el tamaño correcto de la máquina que realizará la tarea de migración, que es la instancia de replicación, para que la tarea no presente problemas y sea necesario reiniciarla y realizarla más de una vez. Encontrará información sobre cómo realizar la elección en Cómo Elegir la Instancia Adecuada para la Migración. Otro consejo es tener una tarea de migración por tabla siempre que sea posible. Si esto no es posible (por motivos de coste, por ejemplo), lo ideal es mantener las tablas en las tareas lo más parecidas posible (por ejemplo, el tamaño y los patrones de acceso). En el caso presentado en las entradas del blog, solo tenemos la tabla de salida que lee el DMS y se replica en una transmisión en Amazon Kinesis Data Streams, pero cuando es necesario replicar más de una tabla, lo ideal es no mantener todas las tablas en la misma tarea de migración, ya que si es necesario reiniciar la tarea, tendrá que volver a leer todas las tablas desde el primer registro.

Si la base de datos del servicio de comandos es específicamente un servidor SQL, es necesario seguir algunos pasos para que todo funcione correctamente y que el DMS pueda leer el log de transacciones sin perder el rendimiento. Algunos clientes informaron de que, debido a que la configuración del punto final de origen no se realizó correctamente, el DMS sobrecargó el procesamiento de la máquina de SQL Server en aproximadamente un 30%. Si trabaja con SQL Server, es importante comprobar que en la sección Captura de Datos de Cambios Para la Replicación Continua en SQL Server se abordan todos los requisitos previos.

En las publicaciones tercera y cuarta presenté dos ejemplos de cómo leer el log de transacciones desde la tabla de salida. Por lo tanto, si la base de datos del servicio de comandos solo es compatible con Debezium, o si la bandeja de salida que se va a replicar ya tiene registros del orden de decenas o cientos de miles, o el equipo de desarrollo sabe cómo configurar Debezium, o incluso si es un requisito utilizar código abierto, la solución que recomendaría es la que se presenta en la tercera entrada del blog. Si el equipo de desarrollo sabe cómo hacer la elección correcta de la instancia de replicación, o si la bandeja de salida no tiene registros, o tiene registros del orden de cientos o unos pocos miles, o si tiene más de decenas de miles, es posible que haya tiempo de inactividad para analizar la tabla completa de la lectura inicial y, además, sabe cómo configurar el DMS correctamente, la solución que recomendaría es la que se presenta en la cuarta entrada del blog.

Algunos lectores pueden preguntarse si no sería válido utilizar la idea de Two-Phase Commit. Por lo tanto, en la misma transacción, podríamos tener la persistencia de los datos en la base de comandos y en la base de lectura. El detalle es que, en este caso, tendríamos un único punto de falla, que es el administrador de transacciones que coordina las transacciones. Además, nos limitaríamos a las bases de datos que implementan el estándar XA y limitaríamos considerablemente nuestras soluciones. Por ejemplo, en este caso, no podríamos usar DynamoDB ni Amazon Neptune.

Opciones Cuando la Base de Datos del Servicio de Comandos es NoSQL

Al igual que con las bases de datos relacionales, la solución de la primera publicación también funcionará cuando tengamos una base de datos NoSQL en el servicio de comandos, con las mismas consideraciones. Al realizar una operación, se publica un evento para que lo consuma un componente que actualiza la base de datos del servicio de consultas.

Y, al igual que ocurre con las base de datos relacionales, si las desventajas de la primera publicación no son aceptables, hay que comprobar que la base que se utiliza cuenta con mecanismos para hacer que los cambios estén disponibles mediante streaming. Por ejemplo, el caso que se muestra en la quinta entrada, en el que se utiliza una tabla de Amazon DynamoDB en el servicio de comandos, aborda el uso de DynamoDB Streams. Para lograr el mismo efecto que obtuvimos con una bandeja de salida, insertamos en la tabla, además de los datos que normalmente se almacenan, un elemento que representa el evento que persistiría en la bandeja de salida y, a continuación, lo filtramos y lo publicamos en nuestro pipe de Amazon EventBridge. Además de la entrada de blog que publiqué, otra entrada de blog (en portugués), que también aborda el uso de DynamoDB Streams para CQRS, es la que publicó mi amigo Roberto “El Robertón” Silva, que muestra cómo BTG Pactual llevó a cabo la implementación.

Otras bases de datos NoSQL de AWS también tienen mecanismos similares. Por ejemplo, Amazon DocumentDB y Amazon Neptune también tienen formas de proporcionar operaciones que ya se han realizado mediante streaming. El detalle es que, en el momento de escribir esta entrada de blog, estos mecanismos no pueden ser fuentes de un pipe en EventBridge, como hicimos con DynamoDB Streams y, por lo tanto, el consumo de estas transmisiones debe realizarse de otras formas.

Hacer la Elección de la Solución

Para facilitar a los lectores la elección de la solución, he creado el siguiente árbol de decisiones. La idea es guiar a los lectores a través de cada escenario, de modo que sea posible elegir la solución más adecuada para un escenario determinado.

Imagen que muestra el árbol de decisiones para elegir implementar el estándar CQRS con servicios de AWS

Figura 4. Árbol de decisión para elegir implementar el estándar CQRS en AWS.

Conclusión

Esta es la última entrada de blog de la serie que escribí sobre las diferentes formas de implementar el estándar CQRS en AWS. En este artículo, traté de hacer algunas consideraciones sobre el estándar y sobre cada una de las soluciones, así como ofrecer una guía sobre cómo los lectores pueden elegir la implementación más adecuada para cada situación.

La norma resuelve el problema de las diferentes necesidades computacionales para escribir y leer datos. Por lo tanto, la norma propone tener un servicio que se ocupe únicamente de la escritura y otro que se ocupe únicamente de la lectura de datos, y cada servicio puede tener los recursos computacionales más apropiados. Además, cada servicio también puede tener la base de datos que más facilite las operaciones de escritura y lectura. El desafío consiste en cómo replicar los datos del lado del servicio de comandos al servicio de consultas.

Durante la serie, presenté diferentes formas de realizar la replicación mediante diferentes técnicas, cada una de las cuales era más apropiada para cada escenario. También presenté un árbol de decisiones, que permitirá a los lectores tomar la decisión más adecuada.

¡Eso es todo para esta serie! Espero que las entradas del blog ayuden a los usuarios y clientes de AWS a tomar la mejor decisión a la hora de implementar CQRS en cada situación y, si tienen preguntas o consideraciones, ¡los lectores pueden encontrarme en LinkedIn! Y mi más sincero agradecimiento a los amigos que contribuyeron con el material que presenté en esta serie: Luiz Santos, Érika “Erikinha” Nagamine, Gabriela “Gabi” Guimarães, Karine “Kah” Ferrari, Maria “Mah” Lucio, Maria “Mavi” Mendes, Ciro Santos, Daniel ABIB, Gerson Itiro, Gonzalo Vásques, Luiz Yanai, Peterson Larentis y Ricardo Tasso. ¡Muchísimas gracias a todos! ¡Sin vosotros, esta serie no sería posible!

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.
Luiz Yanai Luiz Yanai es arquitecto de análisis senior en AWS y trabaja con clientes nativos de la nube y empresas financieras en su camino hacia el uso de datos. Tiene casi 20 años de experiencia en arquitectura y desarrollo de soluciones que involucran sistemas comerciales y de misión crítica, los últimos 5 años de los cuales han estado enfocados en la nube de AWS.
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.