APIs e Microsserviços

8 jun, 2026

Arquitetura de CQRS Multicloud: Desacoplando Domínios de Leitura e Gravação entre AWS e Azure

Publicidade

Bancos de dados relacionais monolíticos sofrem com a alta demanda de leitura de aplicações corporativas distribuídas globalmente. Quando milhares de clientes simultâneos consultam agregações complexas, o armazenamento transacional primário sofre severa contenção de CPU, comprometendo a integridade das operações de gravação. Tentar resolver isso aumentando a capacidade do banco de dados primário apenas adia o gargalo e acopla inextricavelmente o tráfego de leitura à disponibilidade do domínio de gravação. Se o banco de dados único apresentar problemas, toda a plataforma fica inoperante. A Segregação de Responsabilidades de Comando e Consulta (CQRS) resolve essa falha estrutural separando fisicamente o modelo de gravação do modelo de leitura. Ao orquestrar um pipeline de dados orientado a eventos entre o Amazon Managed Streaming for Apache Kafka (MSK) e o Microsoft Azure Event Hubs, as equipes de engenharia podem projetar visualizações materializadas assíncronas otimizadas estritamente para desempenho de leitura local. Essa topologia multicloud garante que padrões de consulta agressivos nunca impactem a taxa de transferência transacional, assegurando resiliência absoluta e acesso a dados com baixa latência em redes de fornecedores distintos.

Pré-requisitos

A implementação do CQRS entre nuvens exige domínio de event sourcing, processamento de fluxos de dados e engenharia de redes distribuídas. A camada de provisionamento de infraestrutura requer o Terraform versão 1.7.0 ou superior, utilizando o provedor AWS da HashiCorp versão 5.40.0 e o provedor AzureRM versão 3.90.0. Os workers de projeção exigem Python 3.12, complementado pelas confluent-kafkabibliotecas versão 2.3.0 e azure-cosmos4.5.1. Um plano de identidade unificado utilizando OpenID Connect (OIDC) é necessário para autenticação segura entre nuvens. O acesso administrativo para configurar o peering de redes virtuais ou túneis VPN IPsec dedicados entre a Nuvem Privada Virtual da AWS e a Rede Virtual do Azure é obrigatório para garantir a transmissão criptografada.

Implementação passo a passo

Arquitetando o Log de Gravação Imutável com o Amazon MSK

Estabelecer um modelo de escrita CQRS robusto requer um livro-razão imutável, de acréscimo único, para registrar com segurança cada transição de estado do domínio. Provisionamos o Amazon MSK para atuar como o sistema nervoso central do domínio de escrita. A justificativa arquitetônica reside na necessidade de ordenação rigorosa de eventos e recursos de retenção indefinida. Quando um serviço de domínio processa um comando, ele não modifica uma tabela de banco de dados tradicional. Em vez disso, publica um evento de domínio em um tópico específico do Kafka. O MSK garante que esses eventos sejam replicados em três Zonas de Disponibilidade distintas, proporcionando durabilidade excepcional. Ao isolar as operações de escrita para essa plataforma de streaming, o sistema atinge uma taxa de transferência de escrita massiva, totalmente livre de junções SQL complexas, recálculos de índice ou bloqueios de tabela.

Diagrama de sequência

resource "aws_msk_cluster" "enterprise_event_store" {
  cluster_name           = "enterprise-write-domain"
  kafka_version          = "3.5.1"
  number_of_broker_nodes = 3

  broker_node_group_info {
    instance_type = "kafka.m5.large"
    client_subnets = [
      aws_subnet.private_az1.id,
      aws_subnet.private_az2.id,
      aws_subnet.private_az3.id
    ]
    security_groups = [aws_security_group.msk_internal.id]
    storage_info {
      ebs_storage_info {
        volume_size = 1000
      }
    }
  }

  client_authentication {
    sasl {
      iam = true
    }
  }

  tags = {
    Architecture = "CQRS"
    Domain       = "WriteModel"
  }
}

Como replicar esses eventos altamente sensíveis e imutáveis ​​para um limite de nuvem externo sem expor os brokers MSK internos à latência imprevisível e às ameaças de segurança da internet pública?

Superando a barreira multicloud com o MirrorMaker 2

Superamos as barreiras de isolamento da rede implantando o Apache Kafka MirrorMaker 2 em um cluster Fargate do Amazon Elastic Container Service (ECS), enviando eventos de forma assíncrona para o Azure Event Hubs por meio de um túnel IPsec dedicado. A principal justificativa arquitetônica para esse componente é mitigar a dependência de um único fornecedor, ao mesmo tempo que se estabelece uma localidade de leitura secundária. O Event Hubs oferece suporte nativo ao protocolo Kafka, permitindo que o MirrorMaker 2 trate o destino do Azure exatamente como um cluster Kafka padrão. Ao executar esse processo de replicação de forma assíncrona, o domínio de gravação da AWS permanece completamente isolado de qualquer degradação de desempenho que ocorra na rede do Azure. A operação de gravação primária confirma o sucesso imediatamente após a persistência no MSK. Posteriormente, o MirrorMaker 2 atua como um consumidor autônomo, buscando lotes de eventos e gerenciando a entrega de consistência eventual para o ambiente do Azure de forma segura.

resource "azurerm_eventhub_namespace" "multicloud_replica" {
  name                = "enterprise-read-replica"
  location            = azurerm_resource_group.core.location
  resource_group_name = azurerm_resource_group.core.name
  sku                 = "Standard"
  capacity            = 4

  tags = {
    Architecture = "CQRS"
    Domain       = "ReadModel"
  }
}

resource "azurerm_eventhub" "transaction_events" {
  name                = "transactions"
  namespace_name      = azurerm_eventhub_namespace.multicloud_replica.name
  resource_group_name = azurerm_resource_group.core.name
  partition_count     = 32
  message_retention   = 7
}

Se o fluxo de eventos atravessar com sucesso a fronteira multicloud, como as unidades de computação do Azure processam esse influxo contínuo de dados brutos de eventos em modelos de leitura altamente otimizados e localizados, sem perder a velocidade de processamento?

Hidratação de Visões Materializadas com Consumidores Hexagonais

Construímos os modelos de leitura otimizados implantando consumidores Python horizontalmente escaláveis ​​que interpretam o fluxo de eventos e realizam upserts em estruturas de documentos no Azure Cosmos DB. O imperativo arquitetônico aqui é a estrita separação de responsabilidades utilizando a Arquitetura Hexagonal. O consumidor Python atua puramente como um adaptador de controle, consultando o Hub de Eventos via protocolo Kafka. Ele passa a carga útil bruta do evento para um serviço central de projeção de domínio. Este serviço de domínio computa o estado atual, pré-calculando os formatos de dados exatos exigidos pelas interfaces do cliente front-end e passa o resultado para uma porta de saída conectada ao Cosmos DB. Essa metodologia garante que o banco de dados de leitura contenha documentos JSON altamente desnormalizados e prontos para uso. Quando um cliente realiza uma operação de leitura, o Cosmos DB executa uma consulta de leitura pontual em milissegundos, evitando completamente a sobrecarga computacional em tempo de execução.

import json
import os
from confluent_kafka import Consumer, KafkaError
from azure.cosmos import CosmosClient
from typing import Dict, Any

EVENTHUB_CONN_STR = os.environ["EVENTHUB_KAFKA_CONN_STR"]
COSMOS_ENDPOINT = os.environ["COSMOS_ENDPOINT"]
COSMOS_KEY = os.environ["COSMOS_KEY"]

cosmos_client = CosmosClient(COSMOS_ENDPOINT, credential=COSMOS_KEY)
database = cosmos_client.get_database_client("ReadModels")
container = database.get_container_client("TransactionViews")

kafka_conf = {
    'bootstrap.servers': EVENTHUB_CONN_STR,
    'security.protocol': 'SASL_SSL',
    'sasl.mechanisms': 'PLAIN',
    'sasl.username': '$ConnectionString',
    'sasl.password': os.environ["EVENTHUB_SASL_KEY"],
    'group.id': 'projection-worker-group-v1',
    'auto.offset.reset': 'earliest'
}

def project_transaction_view(event_payload: Dict[str, Any]) -> None:
    view_id = event_payload.get("account_id")
    amount = float(event_payload.get("amount", 0))

    try:
        current_view = container.read_item(item=view_id, partition_key=view_id)
        current_view["total_balance"] += amount
        current_view["transaction_count"] += 1
        current_view["last_updated"] = event_payload.get("timestamp")
        container.upsert_item(body=current_view)
    except Exception:
        new_view = {
            "id": view_id,
            "account_id": view_id,
            "total_balance": amount,
            "transaction_count": 1,
            "last_updated": event_payload.get("timestamp")
        }
        container.create_item(body=new_view)

def start_projection_worker():
    consumer = Consumer(kafka_conf)
    consumer.subscribe(['transactions'])

    print("Started Cosmos DB projection worker...")
    try:
        while True:
            msg = consumer.poll(1.0)
            if msg is None:
                continue
            if msg.error():
                if msg.error().code() != KafkaError._PARTITION_EOF:
                    print(f"Consumer error: {msg.error()}")
                continue

            payload = json.loads(msg.value().decode('utf-8'))
            project_transaction_view(payload)

    except KeyboardInterrupt:
        pass
    finally:
        consumer.close()

if __name__ == "__main__":
    start_projection_worker()

Que mecanismo de recuperação estrutural existe quando uma implementação de código defeituosa introduz um esquema de projeção malformado, corrompendo silenciosamente a visão materializada dentro do Cosmos DB por várias horas antes da detecção?

Reproduzindo o fluxo para evolução e recuperação de esquemas

Corrigimos a corrupção estrutural tratando a visão materializada como inteiramente efêmera e executando uma reprodução determinística de eventos a partir do log primário imutável. A principal vantagem do Event Sourcing é que o estado da aplicação deriva inteiramente do histórico de eventos. Quando ocorre corrupção de dados, ou quando um novo requisito de negócio exige uma estrutura de dados diferente, os desenvolvedores implementam uma versão corrigida da lógica do consumidor Python vinculada a uma nova group.idconfiguração. Esse novo grupo de consumidores ignora os marcadores de deslocamento corrompidos e começa a ler o log de eventos desde o início absoluto auto.offset.reset = 'earliest'. Ele projeta rapidamente todo o conjunto de dados histórico em um novo contêiner isolado do Cosmos DB, utilizando a lógica de esquema corrigida. Assim que a nova projeção alcança o cabeçalho do fluxo atual, a camada de roteamento transita o tráfego de leitura do cliente para o novo contêiner de forma transparente. O contêiner corrompido é então descartado, permitindo grandes mudanças estruturais e operações de recuperação sem tempo de inatividade.

Solução de problemas comuns

Ao estabelecer a replicação do Kafka entre nuvens, falhas de autenticação entre o MirrorMaker 2 e o Azure Event Hubs são frequentes. Se os logs do MirrorMaker exibirem a mensagem “falha de autenticação SASL authentication failedentre nuvens”, certifique-se de que a string de conexão fornecida à configuração de destino utilize o $ConnectionStringliteral `<username>` e a chave primária da Política de Acesso Compartilhado do Event Hubs como senha. O Event Hubs impõe uma validação rigorosa do protocolo Kafka e a utilização de mecanismos SASL/PLAIN mal configurados resultará em quedas silenciosas de conexão.

Outro problema crítico surge como um atraso extremo no consumo dentro dos workers de projeção em Python durante picos de ingestão de alto volume. Se o Cosmos DB começar a retornar TooManyRequestserros HTTP 429, o consumidor do Event Hub ficará bloqueado e tentará novamente continuamente, fazendo com que o atraso de offset aumente exponencialmente. Isso indica que a capacidade de Unidades de Requisição (RU) provisionada no contêiner do Cosmos DB é insuficiente para a velocidade de gravação do fluxo de eventos. Você deve dimensionar a taxa de transferência do contêiner dinamicamente usando as regras do Azure Auto Scaling ou implementar um processamento em lote agressivo no azure-cosmoscliente Python para confirmar várias visualizações materializadas em uma única operação de lote transacional, reduzindo significativamente a frequência de chamadas à API.

Conclusão

Orquestrar uma arquitetura CQRS entre nuvens usando o Amazon MSK e o Azure Event Hubs estabelece um ecossistema de dados altamente resiliente. Ao segregar as operações de gravação imutáveis ​​das demandas de leitura altamente concorrentes, as equipes de engenharia podem garantir a integridade transacional, ao mesmo tempo que fornecem desempenho de consulta localizado, com tempo de resposta inferior a um milissegundo, em redes de diferentes fornecedores. No futuro, as equipes que escalarem esse padrão devem implementar integrações com o Schema Registry na origem da AWS. Impor esquemas Protobuf ou Avro rigorosos antes que os eventos entrem nos tópicos do MSK garante que a evolução do esquema seja validada matematicamente, impedindo que payloads malformados se propaguem pela fronteira entre as nuvens.