Padrões de integração entre sistemas heterogêneos com mensageria

1. Fundamentos da integração heterogênea com mensageria

Sistemas heterogêneos são aqueles construídos com diferentes linguagens de programação, protocolos de comunicação, formatos de dados e arquiteturas. O desafio central da integração heterogênea é estabelecer comunicação confiável entre esses sistemas sem criar acoplamento rígido. Um sistema legado em COBOL precisa trocar dados com uma aplicação moderna em Node.js; um microsserviço em Go precisa enviar eventos para um consumidor em Python.

O middleware de mensageria atua como uma camada de desacoplamento, permitindo que sistemas heterogêneos se comuniquem de forma assíncrona. Em vez de integração ponto-a-ponto — onde cada par de sistemas precisa conhecer os detalhes de implementação um do outro —, utiliza-se um barramento de mensagens que gerencia roteamento, transformação e entrega. A integração ponto-a-ponto cria um emaranhado de conexões (n*(n-1)/2 conexões para n sistemas), enquanto o barramento reduz para n conexões.

Exemplo de configuração de fila em RabbitMQ:

# Declaração de exchange e fila via AMQP
channel.exchange_declare(exchange='pedidos', exchange_type='topic')
channel.queue_declare(queue='pedidos_novos', durable=True)
channel.queue_bind(exchange='pedidos', queue='pedidos_novos', routing_key='pedido.criado')

2. Padrões de roteamento e filtragem de mensagens

Content-Based Router examina o conteúdo da mensagem para decidir o destino. Se uma mensagem contém tipo: "urgente", roteia para fila prioritária; se tipo: "normal", para fila padrão.

# Exemplo de roteamento baseado em conteúdo com Apache Camel
from("jms:queue:entrada")
    .choice()
        .when(jsonpath("$.tipo == 'urgente'"))
            .to("jms:queue:prioritaria")
        .otherwise()
            .to("jms:queue:normal");

Message Filter elimina mensagens irrelevantes. Um consumidor de logística pode filtrar apenas mensagens com departamento: "transporte".

Recipient List envia uma mensagem para múltiplos destinos de forma seletiva. Uma única notificação de pedido pode ir para o sistema de faturamento, estoque e CRM.

# Recipient List com ActiveMQ
Message message = session.createTextMessage("Pedido 123 criado");
String[] recipients = {"queue:faturamento", "queue:estoque", "topic:notificacoes"};
for (String recipient : recipients) {
    MessageProducer producer = session.createProducer(session.createQueue(recipient));
    producer.send(message);
}

3. Padrões de transformação e enriquecimento de dados

Message Translator converte entre formatos. Um sistema legado envia XML, enquanto um microsserviço moderno espera JSON. O tradutor faz a conversão:

# Tradução de XML para JSON
{
  "origem": "<pedido><id>1</id><valor>100</valor></pedido>",
  "destino": "{\"id\": 1, \"valor\": 100}",
  "transformacao": "XSLT ou JOLT"
}

Enricher adiciona dados contextuais. Uma mensagem de pedido chega apenas com ID do cliente; o enricher consulta o banco de dados para adicionar nome e endereço.

Normalizer unifica múltiplos formatos de entrada em um formato canônico. Vários sistemas enviam pedidos em formatos diferentes (CSV, JSON, XML); o normalizer converte todos para um schema Avro padronizado.

# Normalizador com schema Avro
{
  "schema": "PedidoCanonico",
  "fields": ["id", "cliente_id", "valor_total", "data_criacao"],
  "formatos_entrada": ["csv", "json_v1", "xml_legado"]
}

4. Padrões de garantia de entrega e consistência

Guaranteed Delivery usa persistência em disco e confirmação de recebimento (ACK/NACK). O broker só remove a mensagem após confirmação do consumidor.

# Consumidor com confirmação manual
channel.basicConsume(queue, false, (consumerTag, message) -> {
    try {
        processarMensagem(message);
        channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
    } catch (Exception e) {
        channel.basicNack(message.getEnvelope().getDeliveryTag(), false, true);
    }
});

Transactional Client integra operações de banco de dados e mensageria em transações distribuídas (XA) ou usa Outbox Pattern: a mensagem é salva no mesmo banco de dados da operação de negócio, e um processo separado a publica.

Idempotent Receiver garante que mensagens duplicadas não causem efeitos colaterais. Cada mensagem carrega um ID único; o consumidor mantém um cache de IDs processados.

# Verificação de idempotência
if (cacheDeIds.contains(mensagem.getId())) {
    log.info("Mensagem duplicada ignorada: " + mensagem.getId());
    return;
}
cacheDeIds.add(mensagem.getId());
processarMensagem(mensagem);

5. Padrões de escalabilidade e paralelismo

Competing Consumers distribui mensagens entre múltiplos workers concorrentes. Com RabbitMQ, várias instâncias consomem da mesma fila, e o broker faz o balanceamento round-robin.

# Três consumidores competindo pela mesma fila
worker-1: consumindo da fila "pedidos"
worker-2: consumindo da fila "pedidos"
worker-3: consumindo da fila "pedidos"

Message Dispatcher distribui mensagens para filas de prioridade. Mensagens críticas vão para fila com prioridade alta, processadas antes das demais.

Dead Letter Channel trata mensagens que falharam repetidamente. Após 3 tentativas sem sucesso, a mensagem vai para uma fila de "cartas mortas" para análise manual.

# Configuração de dead letter no RabbitMQ
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", "dead.letter.exchange");
args.put("x-dead-letter-routing-key", "falhas");
args.put("x-max-delivery-attempts", 3);
channel.queueDeclare("fila.principal", true, false, false, args);

6. Padrões de orquestração e coreografia

Scatter-Gather envia uma requisição para múltiplos serviços em paralelo e agrega as respostas. Um sistema de cotação envia para três fornecedores e combina os resultados.

Aggregator combina múltiplas mensagens relacionadas em uma única saída. Mensagens parciais de um pedido (itens, pagamento, frete) são agregadas até que todas cheguem.

# Agregador com correlation ID
Aggregator ag = new Aggregator();
ag.setCorrelationExpression(header("pedidoId"));
ag.setCompletionPredicate(mensagens -> mensagens.size() >= 3);

Process Manager implementa máquina de estados para coreografia de fluxos longos (Saga Pattern). Cada etapa do processo é um estado; falhas disparam compensações.

# Saga para reserva de viagem
estado: "reserva_voo" -> sucesso -> "reserva_hotel"
estado: "reserva_voo" -> falha -> "cancelar_tudo"
estado: "reserva_hotel" -> sucesso -> "confirmar_pagamento"

7. Padrões de monitoramento e rastreamento

Message Store persiste todas as mensagens para auditoria e replay. Em caso de falha, é possível reprocessar mensagens a partir do store.

Wire Tap intercepta mensagens sem afetar o fluxo principal. Útil para logging, métricas e depuração.

# Wire Tap no Apache Camel
from("jms:queue:entrada")
    .wireTap("jms:queue:auditoria")
    .to("jms:queue:processamento");

Correlation Identifier rastreia fluxos entre sistemas. Cada mensagem carrega um ID único que persiste por toda a cadeia de processamento, permitindo rastrear o caminho completo.

# Correlation ID propagado
{
  "correlationId": "abc-123-def",
  "payload": { ... },
  "origem": "sistema-a",
  "timestamp": "2025-01-15T10:30:00Z"
}

8. Considerações finais e boas práticas

A escolha entre brokers tradicionais (RabbitMQ, ActiveMQ) e modernos (Kafka, Redpanda) depende dos requisitos. RabbitMQ é excelente para roteamento complexo e garantia de entrega; Kafka é superior para streaming de eventos, replay e alta vazão.

Estratégias de versionamento de esquemas com Schema Registry (Avro, Protobuf) permitem evolução segura: consumidores antigos continuam funcionando com schemas compatíveis.

Anti-padrões a evitar:
- Acoplamento excessivo: usar mensagens com formato específico de um sistema
- Polling ineficiente: consumidores que ficam checando filas sem necessidade
- Mensagens monolíticas: mensagens gigantes que carregam dados desnecessários

A integração heterogênea com mensageria exige planejamento cuidadoso de padrões, mas oferece desacoplamento, escalabilidade e resiliência que integrações ponto-a-ponto não conseguem proporcionar.

Referências