O blog da AWS

CQRS na AWS: Sincronizando os Serviços de Command e Query com o Padrão Transactional Outbox, a Técnica Transaction Log Tailing e o Amazon DynamoDB Streams

Por Roberto Perillo, arquiteto de soluções enterprise da AWS Brasil.

Até agora, nesta série de blog posts, abordei diferentes formas de se implementar o padrão arquitetural CQRS com diferentes serviços da AWS, cada opção com seus prós e contras. Na primeira parte, comecei com a opção mais simples, em que usamos uma fila do Amazon SQS para transmitir dados do serviço de comandos para o serviço de consulta. Em seguida, exploramos diferentes opções de uso do padrão Transactional Outbox. Na segunda parte, exploramos o padrão Transactional Outbox e a técnica Polling Publisher, em que temos uma tabela outbox que contem registros que representam eventos de domínio a serem publicados e consumidos pelo serviço de consultas, e que é lida de tempos em tempos para que os eventos sejam publicados. Na terceira parte, continuamos utilizando o padrão Transactional Outbox, mas com a técnica de Transactional Log Tailing. Para a leitura da tabela outbox, utilizei o Debezium Connector for PostgreSQL. O caso apresentado na quarta parte foi o mesmo da terceira parte, mas utilizei o Amazon Database Migration Service para efetuar a leitura da tabela outbox. Em todos os quatro cenários apresentados até agora, tínhamos um banco de dados relacional como banco de dados do serviço de comandos e um banco de dados NoSQL como banco de dados do serviço de consultas.

Agora vamos ver como podemos fazer o oposto. Teremos uma tabela do Amazon DynamoDB para o serviço de comandos e um banco de dados relacional para o serviço de consultas. O DynamoDB é um dos bancos de dados da AWS de chave-valor que oferece latência de 1 dígito de milisegundo em qualquer escala. O caso de uso será praticamente o mesmo, mas agora a tabela do DynamoDB conterá dados relacionados a clientes, produtos e pedidos, e o banco de dados relacional (que será o Amazon Aurora for PostgreSQL-Compatible Edition) conterá dados resumidos relacionados a cada cliente e seu total correspondente em pedidos.

Introdução

A maioria das aplicações que criamos precisa de um banco de dados para armazenar dados. Armazenamos o estado de nossos objetos de domínio e os usamos para várias finalidades, como processar e gerar relatórios. Às vezes, nosso banco de dados principal pode não ser ideal para recuperação de dados, talvez devido a um modelo de domínio complexo. Para esses casos, podemos usar o padrão arquitetural CQRS, que sugere que, para um determinado bounded context em nosso domínio, podemos ter dois serviços, sendo um para receber comandos (ou seja, operações que mudam de estado) e consultas (que só recuperam dados). Dessa forma, cada serviço pode ter o banco de dados que melhor se encaixa. O desafio é como manter os dois bancos de dados sincronizados, o que pode ser feito com eventos publicados a partir do serviço de comando para serem consumidos pelo serviço de consulta.

A publicação confiável de eventos relacionados a coisas que já aconteceram em uma aplicação pode ser um desafio. Se estamos usando ou não o CQRS, dependendo de como o fazemos, se não tomarmos cuidado, podemos acabar publicando informações que ainda não foram confirmadas, conforme discutimos na primeira parte desta série, e as fontes de dados podem ficar fora de sincronia. Ao usar o DynamoDB, podemos usar o recurso de streams, fornecido pelo próprio serviço do DynamoDB. O Amazon DynamoDB Streams é o log dos itens que foram persistidos em uma tabela do DynamoDB nas últimas 24 horas. O recurso de streams também está disponível em outros bancos de dados NoSQL, como Amazon DocumentDB e Amazon Neptune.

Essa é uma forma de implementar a técnica Transaction Log Tailing. Diferentemente das opções apresentadas nos posts anteriores, não temos como ler o log de transações de uma tabela do DynamoDB, mas podemos obter o mesmo efeito com o DynamoDB Streams. É isso que vamos usar para implementar o CQRS, tendo uma tabela do DynamoDB como banco de dados do serviço de comandos.

Caso de uso: Amazon DynamoDB como banco de dados do serviço de comandos e Amazon Aurora for PostgreSQL-Compatible Edition como banco de dados do serviço de consultas, usando o padrão Transactional Outbox e a técnica Transaction Log Tailing com o Amazon DynamoDB Streams

Para implementar essa técnica, não precisamos instalar outros componentes para ler o log de transações de uma tabela do DynamoDB. Uma das opções é usar o DynamoDB Streams, que é um log que contém eventos que representam tudo o que aconteceu com uma tabela do DynamoDB nas últimas 24 horas. Há ainda outra opção para gerar eventos a partir de uma tabela do DynamoDB, que é conectar um stream de dados do Amazon Kinesis Data Streams à tabela do DynamoDB da qual queremos ler eventos. Para saber mais sobre essa técnica, consulte Use o Kinesis Data Streams para capturar alterações do DynamoDB. A escolha depende de cada caso de uso específico, pois há algumas diferenças entre as duas opções.

Com o Amazon DynamoDB Streams, as principais considerações são que 1) cada evento aparece apenas uma vez no stream; 2) o stream é escalado junto com a tabela do DynamoDB (enquanto uma tabela DynamoDB é escalada em partições, o stream é escalado em shards); 3) pode haver no máximo dois leitores consumindo cada shard do stream; 4) obtemos os eventos como eles aconteceram na tabela do DynamoDB em ordem; 5) o stream contém eventos que aconteceram nas últimas 24 horas; e 5) podemos consumir o stream com uma função AWS Lambda, um DynamoDB Streams Kinesis Adapter ou um pipe do Amazon EventBridge Pipes. Mais informações podem ser encontradas em Change Data Capture com o DynamoDB Streams e DynamoDB Streams como fonte de pipes do EventBridge.

Por outro lado, com um Amazon Kinesis Data Stream, as principais considerações são que 1) uma notificação de item pode aparecer mais de uma vez no stream; 2) até 2 MB/seg por shard (não importa quantos consumidores estejam consumindo dados do shard) sem enhanced fanout, ou 2 MB/seg por consumidor, com enhanced fanout; 3) eventos podem aparecer fora de ordem no stream (a ordenação pode ser feita com o campo ApproximateCreationDateTime, um metadado do evento no fluxo, e que também pode ser usado para identificar duplicatas); 4) por padrão, o stream contém eventos que aconteceram somente nas últimas 24 horas, mas isso pode ser aumentado em até 8760 horas; e 5) há mais opções disponíveis para consumo, incluindo um Amazon EventBridge Pipe. Mais informações podem ser encontradas em Use o Kinesis Data Streams para capturar alterações do DynamoDB.

Uma boa linha de raciocínio ao escolher entre as duas opções é o que estamos tentando alcançar. Se, por exemplo, quisermos também usar outros serviços de processamento de dados em streaming, como o Amazon Managed Service for Apache Flink ou o Amazon Data Firehose, para processar mais eventos, se quisermos ter mais de dois consumidores (por shard/partition) consumindo o stream ou se precisarmos dos dados por mais de 24 horas no stream de dados, o Amazon Kinesis Data Stream provavelmente será a escolha certa, mas provavelmente será uma solução mais cara. Se quisermos, por exemplo, simplesmente ler o stream e processá-lo com o mínimo de lógica (com no máximo 2 consumidores por shard), o DynamoDB Streams será a escolha certa, e provavelmente será uma solução mais econômica. Uma reflexão sobre essa escolha é feita também neste blog post.

Dado que, no exemplo que estamos explorando, queremos levar alterações que acontecem em uma tabela do DynamoDB para um banco de dados do Aurora, seja inserções, alterações ou deleções, e receber eventos fora de ordem não seria um problema pois eles são tratados de forma independente, poderíamos usar as duas opções, mas o DynamoDB Streams parece mais apropriado, pois é mais simples e econômico.

Para consumir eventos de um stream do DynamoDB, temos algumas opções. A primeira é fazer com que uma função Lambda o consuma diretamente. No nosso caso, nossa função Lambda precisaria consumir o stream, filtrar eventos e publicar eventos em um tópico do Amazon SNS, que por sua vez entregaria o evento em duas filas do Amazon SQS. A outra opção seria usar a Amazon Kinesis Client Library (KCL). Como estamos usando o DynamoDB Streams, precisaríamos usar especificamente as versões 1.X (a versão 2.X só poderia ser usada se tivéssemos optado pelo Amazon Kinesis Data Streams). Outra opção é usar um pipe do EventBridge, pois um DynamoDB Stream pode ser usado como fonte em um pipe do Amazon EventBridge.

Usar um pipe do EventBridge é a opção que vamos escolher, pois podemos ter a flexibilidade de enriquecer eventos, filtrar eventos e publicá-los em um tópico do SNS. Para efeitos de curiosidade, ao usar as Global Tables do DynamoDB, também usamos indiretamente o DynamoDB Streams para replicação entre regiões.

Visão Geral da Solução

Em nosso exemplo, uma chamada POST será feita para o endpoint /orders, com as informações de um pedido a ser feito por um cliente. Essa chamada será validada por uma Lambda Authorizer (para fins de simplificação, usaremos basic authentication) e será processada posteriormente pela função OrderReceiverLambda, que desempenhará a função de nosso serviço de comandos. Essa função Lambda inserirá dados relacionados ao pedido feito em nossa tabela do DynamoDB e também inserirá um item que representará o evento do pedido feito.

Poderíamos ter mais de uma tabela e emular a ideia de um banco de dados relacional, no qual inseriríamos dados em tabelas como Orders e OrderItems, envolvendo tudo em uma transação, mas nossa tabela do DynamoDB será modelada seguindo a ideia do Single-Table Design, que é a forma recomendada pela AWS de se trabalhar com tabelas do DynamoDB (mais sobre isso neste blog post). O domínio que estamos explorando pode ser representado pelo modelo a seguir, em uma abordagem Single-Table Design. O nome da tabela pode ser, por exemplo, ECommerceData. Como estamos armazenando tudo em uma única tabela, faz sentido os nomes das partition key e sort key serem genéricos, como “pk” e “sk”.

Imagem demonstrando a tabela do Amazon DynamoDB com dados do exemplo abordado, seguindo o padrão Single-Table Design. Além de emular um formato relacional, também é persistido um item que representa o evento do pedido efetuado, que será posteriormente lido a partir do stream da tabela e publicado em um pipe do Amazon EventBridge.

Figura 1. A tabela do Amazon DynamoDB com dados do exemplo abordado, seguindo o padrão Single-Table Design. Além de emular um formato relacional, também é persistido um item que representa o evento do pedido efetuado, que será posteriormente lido a partir do stream da tabela e publicado em um pipe do Amazon EventBridge.

Quando o DynamoDB Streams está ativado em uma tabela, quando uma ação em um item é persistida, um evento relacionado a ação é disponibilizado para consumo no stream como “imagens” dos itens. Podemos definir quais dados serão gerados no stream. Podemos gerar imagens dos principais atributos (chaves primárias e de classificação), novas imagens (os itens completos que foram inseridos ou atualizados), imagens antigas (os itens completos antes de serem atualizados) ou imagens novas e antigas. No nosso exemplo, escolhemos gerar novas imagens dos itens inseridos na tabela do DynamoDB. Um exemplo de uma imagem nova é mostrado na listagem abaixo.

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

Os itens que representam o pedido feito serão inseridos na tabela do DynamoDB, e cada acontecimento na tabela (seja inserção, atualização ou remoção) gerará um evento no stream atrelado à tabela. Haverá um item relacionado ao pedido, n itens relacionados aos itens do pedido e também um item que representa o evento de realização do pedido. Poderíamos envolver todas as inserções em uma transação, mas há considerações sobre essa abordagem. A primeira é que, como todos os itens serão persistidos na tabela do DynamoDB de forma atômica, os eventos no stream podem aparecer fora da ordem que esperamos. Outra consideração é que, quando estamos recuperando eventos em batches de um stream, os itens que foram inseridos em uma transação podem aparecer em batches diferentes.

Uma consideração final sobre o uso de transações no DynamoDB é que uma inserção em uma transação custa o dobro do custo de uma inserção feita sem uma transação (ou seja, 1 KB custará 2 Write Capacity Units, ou WCUs). Em nosso exemplo, mesmo se estivéssemos usando transações, receber eventos fora de ordem ou em lotes diferentes não seria um problema, já que estamos interessados apenas em eventos que representam o evento de realização do pedido. Não usaremos transações para inserir os itens relacionados ao pedido feito porque é muito improvável que ocorram problemas com as inserções.

Um evento que representa cada inserção feita na tabela do DynamoDB será gerado no stream, mas só estamos interessados em eventos que representem o resumo do pedido, com o nome do cliente, e-mail, total da compra e a hora atual. Com o DynamoDB Streams, não precisamos de uma tabela outbox separada, como fizemos quando lidamos com um banco de dados relacional, mas precisaremos de um item em nossa tabela do DynamoDB com esses dados, para que um evento possa ser gerado em nosso stream e possamos publicar facilmente um evento sobre o pedido feito sem realizar muito processamento. Essa é a única finalidade do item cuja sort key começa com ORDER_EVENT# em nossa tabela. Dessa forma, nosso stream conterá todos os eventos de todas as inserções (relacionados ao pedido, itens do pedido e resumo do pedido), mas lidaremos apenas com eventos que representem a realização do pedido.

Os itens que estamos armazenando no DynamoDB são referentes a clientes, pedidos e seus respectivos itens, e estamos também armazenando um item referente ao evento de realização de um pedido, cuja sort key começa com ORDER_EVENT#. Na prática, só precisamos desse item para fazer a transmissão do evento ocorrido no lado do serviço de comandos para o lado do serviço de consultas. Dessa forma, esse item tem uma natureza transiente. Para que esses ítens não fiquem ocupando espaço na tabela DynamoDB mais do que o necessário, podemos lançar mão do recurso de Time to Live, ou TTL, do DynamoDB, que é o recurso que permite que um item seja excluído de uma tabela após a expiração de um período indicado no próprio item.

Para fazer uso desse recurso, primeiro o habilitamos na tabela em que o utilizaremos e definimos o atributo que armazenará o valor que indicará quando o item deverá expirar. Com isso, poderemos indicar para cada item quando ele deverá expirar. O atributo deverá ser do tipo Number, e o valor deverá ser expresso em formato Unix Epoch Time. Assim que o valor indicado é atingido, o DynamoDB exclui o item sem consumir nenhuma WCU. Como o item que representa o evento da realização de um pedido tem uma natureza transiente, uma opção é habilitar o TTL na tabela e indicar no item do evento um tempo para a sua expiração.

Precisamos publicar um evento que represente o pedido feito pelo cliente para que o banco de dados do nosso serviço de consultas possa ser atualizado. Temos duas opções. A primeira é, no lado do serviço de comandos, recuperar todos os eventos de pedidos feitos pelo cliente, somar o total de todos os pedidos e publicar um evento (nesse caso, não poderemos lançar mão do TTL). Assim, nosso evento conteria o total gasto pelo cliente até aquele momento. A outra opção é publicar o evento apenas com o total do pedido feito (aproveitando o item de evento de realização do pedido) e fazer com que a função Lambda que atualizará o Aurora some o valor atual presente (que será o total gasto pelo cliente, sem considerar o pedido recém-feito) com o total do pedido recém-feito.

Dependendo de quantos pedidos o cliente já fez até aquele momento, recuperar todos os eventos de realização de pedidos da tabela do DynamoDB pode consumir muitos RCUs, enquanto recuperar um registro da réplica de leitura do Aurora para realizar a soma no lado do serviço de consultas exigirá menos processamento e será mais performático, então essa é a opção que vamos escolher.

Na prática, o que estamos fazendo é selecionar um valor, somá-lo com outro valor e atualizar uma tabela em nosso banco de dados Aurora. No nosso caso, essa é uma operação segura, pois não vamos enfrentar race conditions, porque o mesmo cliente não fará pedidos em intervalos nos quais as race conditions possam acontecer. Se fosse esse o caso, uma estratégia mais segura seria usar estratégias de bloqueio, como o Optmistic Locking.

Faremos com que o DynamoDB Streams publique um evento de toda a imagem (nova imagem) do evento do pedido realizado, e esse stream será a origem de um pipe no EventBridge. Um dos parâmetros que podem ser definidos no source do nosso pipe é o tamanho do batch, que é a quantidade de eventos (batch size) que serão coletados e passados para a segunda etapa do nosso pipe. Podemos configurar esse parâmetro com o valor 10. Outro parâmetro é o tempo a ser esperado antes do próximo consumo do stream (batch window). Podemos deixar esse último parâmetro com o valor padrão.

A segunda etapa do pipe será uma função Lambda, na fase de enrichment, cujo objetivo é preparar um evento com menos dados do que o publicado pelo DynamoDB Streams e também filtrar eventos. Nosso pipe receberá todos os eventos da nossa tabela do DynamoDB, mas só estamos interessados em eventos que representem a realização do pedido, então filtraremos os eventos que não sejam o da realização do pedido. Para seguir com o processamento dos eventos na fase de enrichment, poderíamos criar uma lista e incluir nessa lista somente os eventos cuja sort key comece com “ORDER_EVENT#”, e retornar a lista na função Lambda.

Após a fase de enrichment, o evento é finalmente entregue a um tópico do SNS, que entrega o evento a duas filas do SQS, sendo uma para ser pesquisada por uma função Lambda que envia um email de notificação e outra para ser lida por uma função Lambda que atualiza o registro do cliente no Aurora.

Além do endpoint que recebe pedidos, teremos um segundo endpoint /clients/{clientId}, que recuperará informações relacionadas ao cliente. A função Lambda que fornece essas informações representa nosso serviço de consultas e as recupera da réplica de leitura do Aurora, que foi atualizada anteriormente. As informações retornadas contêm o nome do cliente, o email e o valor total que já foi comprado pelo cliente até aquele momento.

A função Lambda ClientRetrieverLambda recuperará informações relacionadas ao cliente da réplica de leitura do Aurora e, para poder se conectar, também recuperará as informações de conexão armazenadas no Amazon Secrets Manager. Podemos armazenar no secret somente o nome de usuário e senha do banco e colocar as outras informações, como host, nome do banco de dados e porta de conexão, como variáveis de ambiente na função Lambda, ou podemos armazenar todas as informações de conexão no nosso secret. Basta ter em mente que o ideal é recuperarmos as informações a partir do endpoint de leitura do Aurora (que é o ReaderInstanceEndpoint).

Imagem demonstrando a arquitetura proposta, tendo o DynamoDB como banco de dados do serviço de comandos, e o Aurora como banco de dados do serviço de consultas. A partir da inserção de um evento de inserção de um pedido na tabela do DynamoDB, o evento é disponibilizado no stream da tabela por meio do DynamoDB Streams, que é lido por um pipe do EventBridge que efetua a publicação desses eventos em um tópico do SNS. O SNS então entrega os eventos em duas filas, sendo que uma delas é lida por uma Lambda que atualiza o Redis, com os últimos dados do cliente relacionado a cada evento.

Figura 2. Arquitetura proposta, tendo o Amazon DynamoDB como banco de dados do serviço de comandos, e o Amazon Aurora como banco de dados do serviço de consultas.

A vantagem dessa solução é que não precisamos ter uma tabela outbox para que os eventos possam ser publicados de forma confiável. Para fazer isso, estamos usando um recurso fornecido pelo próprio serviço do DynamoDB, que é o DynamoDB Streams. Também não precisamos ter nenhum mecanismo para limpar nada, como tínhamos com a tabela outbox, seja uma Lambda que a limpa em algum momento ou uma extensão do Postgres combinada com outros serviços da AWS, como pg_partman e Amazon S3. No caso do DynamoDB, podemos lançar mão do recurso de TTL, e colocar no item que representa o evento de realização de pedido um atributo que indica o momento em que o item possa ser excluído.

A consideração aqui é que nem sempre podemos usar o DynamoDB. Há casos em que simplesmente não temos permissão para utilizá-lo, devido às regras internas da empresa para a qual trabalhamos. Ou talvez o DynamoDB não seja adequado para nossos padrões de acesso. É um equívoco tentar replicar a ideia de um banco de dados relacional para o DynamoDB, pois, em última instância, é um banco de dados de chave-valor, mas é possível emular essa ideia com o Single-Table Design. É importante avaliar os padrões de acesso e planejar o design da tabela antes de usar o DynamoDB, além de refletir se o DynamoDB é o banco de dados apropriado para ser usado em um determinado projeto.

Como optamos pelo DynamoDB Streams, só podemos ler os eventos que representam ações que ocorreram na tabela do DynamoDB nas últimas 24 horas, e isso não pode ser alterado. Se precisássemos de mais tempo, teríamos que optar pelo Amazon Kinesis Data Streams, que substituiria o DynamoDB Streams e seria a fonte do pipe do EventBridge. Por padrão, ele também mantém os dados no fluxo por 24 horas, mas esse valor pode ser aumentado em até 8760 horas, o equivalente a 365 dias.

Executando o Exemplo

Para executar o exemplo, os leitores deverão ter uma conta na AWS e um usuário com permissões de admin. Depois, basta executar o passo-a-passo fornecido no repositório de códigos desta série de blog posts sobre CQRS, no AWS Samples, hospedado no Github. Ao executar o passo-a-passo, os leitores terão a infraestrutura aqui apresentada nas suas próprias contas.

O exemplo contém dois endpoints, sendo um para receber informações relacionadas a pedidos (representando o nosso serviço de comandos) e outro para recuperar informações relacionadas a clientes (representando nosso serviço de consultas). Para verificar se tudo funcionou corretamente, vá até o Amazon API Gateway e, na lista de APIs, entre na API “OrdersAPI”, e depois em “Stages”. Haverá somente uma stage chamada “prod”. Recupere o valor do campo Invoke URL e acrescente “/orders”. Esse é o endpoint que recebe informações relacionadas a pedidos.

Vamos realizar uma requisição POST a esse endpoint. Podemos usar qualquer ferramenta para efetuar as requisições, como cURL ou Postman. Como esse endpoint está protegido, também precisamos adicionar basic authentication. Se você estiver usando o Postman, será necessário recuperar nome de usuário e senha gerados na construção da infraestrutura. No API Gateway, vá até “API Keys” e copie o valor da coluna “API key” de “admin_key”. Esse valor contém nome de usuário e senha separados pelo caracter “:”, porém está codificado em Base64. Decodifique o valor, utilizando alguma ferramenta online, ou o próprio comando “base64” do Linux. O nome de usuário está à esquerda do caracter “:”, e a senha está à direita. Adicione uma “Authorization” do tipo “Basic Auth” e preencha os campos “Username” e “Password” com os valores recuperados. Adicione também um header “Content-Type”, com o valor “application/json”.

Se você estiver usando, por exemplo, cURL, não será necessário decodificar o valor da API key. Basta adicionar um header “Authorization” com o valor “Basic <valor da api key copiado da coluna API key>”. Adicione também um header “Content-Type”, com o valor “application/json”.

payload para efetuar requisições para esse endpoint é o seguinte:

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

Isso representa um pedido que o cliente com o nome de usuário “bob” fez, contendo os produtos de nome “Computer” e “Phone”. O total desse pedido é de $3000. Todas essas informações serão armazenadas no DynamoDB. Ao efetuar essa requisição POST, se tudo funcionou conforme o esperado, você deverá ver o seguinte resultado:

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

Agora, vamos verificar se as informações relacionadas ao cliente foram enviadas ao Aurora. Ao endpoint do API Gateway, que foi recuperado anteriormente, acrescente “/clients/bob”. Esse é o endpoint que recupera informações relacionadas ao cliente. Vamos efetuar uma solicitação GET para esse endpoint. Assim como fizemos com o endpoint “/orders”, precisamos adicionar basic authentication. Siga as etapas explicadas anteriormente e efetue a requisição GET. Se tudo funcionou conforme o esperado, você verá uma saída semelhante à seguinte:

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

Isso significa que conseguimos alimentar com sucesso o Aurora com informações prontas para serem lidas, enquanto as mesmas informações estão no DynamoDB, em outro formato.

Limpando os Recursos

Para excluir a infraestrutura criada, em um terminal, no mesmo diretório em que a infraestrutura foi criada, basta executar o comando “cdk destroy” e confirmar. A deleção leva aproximadamente 15 minutos.

Conclusão

Este é o último blog que mostra possíveis soluções que abordam como implementar o padrão arquitetural CQRS com os serviços da AWS. Ele mostra como implementá-lo com o Amazon DynamoDB, especificamente com o Amazon DynamoDB Streams, que é uma forma de aplicar o padrão Transactional Outbox, para que possamos levar alterações que aconteceram no serviço de comandos para o serviço de consultas. Ao usar o DynamoDB, seu stream pode desempenhar o papel de um mecanismo de outbox. Também é importante observar que o recurso de streams também está disponível em outros serviços da AWS, como Amazon DocumentDB e Amazon Neptune. No momento em que este artigo foi escrito, no entanto, somente um stream do DynamoDB pode ser a fonte de um pipe do EventBridge e, portanto, os streams dos outros bancos de dados precisarão ser consumidos por outros meios.

Com o DynamoDB Streams, não precisamos ter uma tabela separada para representar eventos, como acontece quando usamos um banco de dados relacional para o serviço de comandos. Esse stream é o log que contém todos os eventos que aconteceram em nossa tabela nas últimas 24 horas. Nessa solução, o stream é a fonte de um pipe do EventBridge, o que nos dá a flexibilidade de enriquecer eventos, filtrar eventos e enviá-los para outros serviços, como um tópico do SNS, que foi o que fizemos. Nós inserimos todos os dados relacionados ao pedido e também um item que representa o evento de recebimento do pedido, que é o que publicamos como um evento. Todos os outros eventos são filtrados pelo Lambda na fase de enrichment.

Outra possibilidade seria usar um Amazon Kinesis Data Stream, mas essa é uma solução mais apropriada em outros cenários, como quando queremos enviar nossos eventos para outros serviços de processamento de dados em streaming, como o Amazon Data Firehose, ou precisamos manter nossos eventos por mais de 24 horas. No exemplo que exploramos, o DynamoDB Streams foi uma solução mais apropriada, pois é mais simples e não usamos outros serviços como o Amazon Data Firehose.

Nesta série de blog posts, explorei cinco maneiras diferentes de aplicar o padrão arquitetural CQRS com diferentes serviços da AWS. Mas como podemos escolher qual solução usar em um determinado cenário? O que eu gostaria de fazer agora é refletir sobre as diferentes possibilidades, para que os leitores possam escolher a solução que melhor atenda às suas necessidades em um determinado cenário. E esse será o último post dessa série, e será apresentado a seguir!

Sobre o Autor

Roberto Perillo Roberto Perillo é arquiteto de soluções enterprise da AWS Brasil especialista em serverless, atendendo a clientes da indústria financeira, e atua na indústria de software desde 2001. Atuou por quase 20 anos como arquiteto de software e desenvolvedor Java antes de ingressar na AWS, em 2022. Possui graduação em Ciência da Computação, especialização em Engenharia de Software e mestrado também em Ciência da Computação. Um eterno aprendiz. Nas horas vagas, gosta de estudar, tocar guitarra, e também jogar boliche e futebol de botão com seu filho, Lorenzo!

Sobre o Colaborador

Luiz Santos Luiz Santos trabalha atualmente como Technical Account Manager (TAM) na AWS e é um entusiasta de tecnologia, sempre busca novos conhecimentos e possui maior profundidade sobre desenvolvimento de Software, Data Analytics, Segurança, Serverless e DevOps. Anteriormente, teve experiência como Arquiteto de Soluções AWS e SDE.

Sobre os Revisores

Erika Nagamine Erika Nagamine é arquiteta de soluções especialista em Dados na AWS. Tem formação acadêmica sólida, graduada em Sistemas de Informação, com pós graduação em Administração de Banco de Dados, Engenharia de Dados e especialização em mineração de Dados Complexos pela Unicamp. Atua com clientes de diversos segmentos e já participou de mais de 200 projetos no Brasil e no mundo. Atualmente múltiplas certificações em dados, computação em nuvem, todas as certificações AWS, e ama compartilhar conhecimento em comunidades e é palestrante em eventos técnicos de destaque em todo o Brasil e no mundo.
Ciro Santos Ciro Santos é arquiteto de soluções sênior na AWS, atuando a +20 anos com arquitetura e tecnologia. Tendo como especialidades arquiteturas cloud native e de software. Focando em temas como microserviços, containers, serverless, DevOps e mobile.