Rust para Mensageria: Kafka, RabbitMQ e NATS em 2026

Guia prático para usar Rust em sistemas orientados a eventos com Kafka, RabbitMQ e NATS: arquitetura, crates, observabilidade, testes e carreira.

Por que mensageria é um bom território para Rust

Sistemas modernos raramente são uma única API respondendo requisições HTTP. Um produto real costuma ter checkout, antifraude, emissão de notas, notificações, conciliação, analytics, cobrança recorrente, sincronização com CRM, ingestão de eventos e jobs assíncronos rodando em ritmos diferentes. Quando tudo isso fica acoplado em chamadas síncronas, qualquer latência ou falha vira efeito dominó. É nesse ponto que entram Kafka, RabbitMQ, NATS e outros brokers de mensagens.

Rust é uma linguagem especialmente interessante para essa camada porque mensageria exige três qualidades que combinam com o ecossistema: previsibilidade, concorrência segura e tratamento explícito de erro. Um consumer que processa milhares de eventos por minuto não pode vazar memória, travar silenciosamente ou transformar uma mensagem inválida em perda de dados. Um producer não pode publicar payload ambíguo e descobrir depois que vários serviços interpretaram o campo de formas diferentes.

Para quem está mirando vagas Rust em backend, plataforma, fintech, infraestrutura ou sistemas distribuídos, saber falar de filas, streams e eventos pesa mais do que decorar sintaxe. Empresas que usam Rust em produção normalmente estão resolvendo problemas de confiabilidade: baixa latência, alto volume, processamento paralelo, integração com sistemas legados, auditoria e custo operacional. Mensageria aparece exatamente nesse cruzamento.

Kafka, RabbitMQ e NATS: escolhas diferentes

Kafka é a escolha comum quando o problema parece mais com log de eventos do que com fila tradicional. Ele brilha em ingestão de alto volume, processamento por tópicos particionados, replay de eventos, pipelines de dados, event sourcing e integração entre times. Em Rust, a crate rdkafka é a opção mais conhecida para trabalhar com Kafka porque usa librdkafka por baixo e oferece APIs assíncronas maduras.

RabbitMQ é mais natural quando você precisa de roteamento flexível, filas de trabalho, confirmações, dead-letter queues e padrões clássicos de entrega. Ele é muito usado em backends que precisam desacoplar tarefas demoradas: enviar e-mail, gerar PDF, processar imagem, chamar API externa, enriquecer cadastro ou reprocessar algo que falhou. Em Rust, crates como lapin aparecem bastante para AMQP.

NATS costuma entrar quando a prioridade é simplicidade operacional, baixa latência e comunicação leve entre serviços. Ele funciona bem para pub/sub, request/reply, JetStream e arquiteturas que querem menos peso do que um cluster Kafka. Para times pequenos, NATS pode ser uma escolha pragmática: fácil de rodar localmente, rápido para testar e simples de explicar.

A decisão não deve começar pela moda. Comece pelo comportamento desejado. Você precisa de replay longo e particionamento por chave? Kafka tende a fazer sentido. Precisa de fila de trabalho com retry e dead letter previsível? RabbitMQ pode ser mais direto. Precisa de pub/sub leve e comunicação interna rápida? NATS merece avaliação. Rust funciona nos três cenários, mas a arquitetura muda bastante.

Arquitetura mínima de um serviço event-driven em Rust

Um serviço Rust orientado a eventos normalmente tem quatro camadas. A primeira é o contrato de mensagem: structs com Serde, validação e versionamento. A segunda é o adapter do broker: Kafka, RabbitMQ ou NATS isolado em um módulo pequeno. A terceira é a regra de negócio: código que recebe um comando ou evento tipado e decide o que fazer. A quarta é a camada operacional: logs, métricas, retry, idempotência, shutdown gracioso e testes.

Essa separação evita o erro comum de colocar tudo dentro do loop do consumer. Quando o handler mistura parsing, chamada ao banco, regra de negócio, confirmação da mensagem e log manual, qualquer mudança vira risco. Em vez disso, trate o broker como borda. O núcleo do serviço deve aceitar tipos Rust normais e retornar resultados explícitos.

Um esqueleto saudável fica assim:

src/
  main.rs
  config.rs
  messages.rs
  broker/
    kafka.rs
    rabbitmq.rs
    nats.rs
  handlers/
    pagamento_aprovado.rs
  observability.rs

Nem todo projeto precisa suportar três brokers; a estrutura acima só mostra o isolamento. Em produção, escolha um broker e esconda detalhes em um módulo. O resto da aplicação não deveria saber se a confirmação veio de offset Kafka, ack AMQP ou JetStream.

Contratos de mensagem: o ponto mais importante

Mensageria falha quando a mensagem parece simples demais. Um JSON como { "id": 123, "status": "ok" } funciona no primeiro dia, mas não carrega contexto suficiente para auditoria, replay, compatibilidade e diagnóstico. Prefira eventos nomeados, com versão, identificador único, timestamp, idempotency key, origem e payload bem tipado.

Um exemplo de contrato:

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Evento<T> {
    pub event_id: String,
    pub event_type: String,
    pub version: u16,
    pub occurred_at: String,
    pub producer: String,
    pub idempotency_key: String,
    pub payload: T,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PagamentoAprovado {
    pub pedido_id: String,
    pub cliente_id: String,
    pub valor_centavos: i64,
    pub moeda: String,
}

O detalhe importante é que Evento<PagamentoAprovado> representa uma intenção de domínio, não um pacote solto de campos. Isso facilita testes, documentação e evolução. Se no futuro você adicionar metodo_pagamento, pode manter version: 2 e decidir como consumidores antigos devem se comportar.

Também vale evitar payloads gigantes. Mensagens devem carregar o suficiente para processar a decisão imediata ou buscar dados por chave. Enfiar todo o estado do cliente em cada evento cria risco de privacidade, custo de rede e divergência. Para dados sensíveis, envie identificadores e deixe o consumer buscar o necessário com autorização explícita.

Idempotência, retry e dead letter

O maior choque para quem sai de APIs síncronas é aceitar que uma mensagem pode ser processada mais de uma vez. Brokers podem reentregar eventos depois de timeout, queda do consumer, rebalanceamento, falha de ack ou retry manual. Por isso, handlers precisam ser idempotentes: processar o mesmo evento duas vezes não pode cobrar duas vezes, mandar dois e-mails críticos ou duplicar saldo.

Em Rust, modele idempotência como parte da regra. Antes de executar uma ação irreversível, registre event_id ou idempotency_key em uma tabela com restrição única. Se já existe, retorne sucesso sem repetir a ação. SQLx ajuda nesse fluxo porque permite queries explícitas, transações e integração forte com PostgreSQL.

Retries também precisam ser classificados. Erro transitório, como timeout em API externa, pode tentar de novo com backoff. Erro permanente, como payload inválido ou cliente inexistente, deve ir para dead letter com contexto suficiente para análise. O erro mais caro é retry infinito de mensagem impossível, porque consome worker, polui logs e esconde incidentes reais.

Observabilidade desde o primeiro consumer

Um consumer sem observabilidade vira caixa-preta. Use Tracing desde o início para registrar event_id, tópico ou fila, partition/offset quando existir, tempo de processamento, resultado e classe de erro. Em vez de logs soltos como erro ao processar, prefira spans com campos estáveis. Isso permite buscar todas as tentativas de um evento específico durante um incidente.

Também acompanhe métricas simples: mensagens processadas por segundo, tempo médio e p95 do handler, quantidade de retries, tamanho da fila, lag por consumer group, dead letters por tipo de erro e falhas por dependência externa. Para Kafka, lag é sinal crítico. Para RabbitMQ, profundidade de fila e taxa de ack ajudam a enxergar gargalos. Para NATS JetStream, observe consumers atrasados e redeliveries.

Essa disciplina conversa com Rust e eBPF para observabilidade e com deploy Rust em VPS. Não basta o binário compilar; ele precisa explicar o que está fazendo quando o volume sobe ou quando uma dependência começa a falhar.

Testes que realmente protegem

Testar mensageria não significa subir um cluster completo em todo teste unitário. Separe níveis. Testes unitários devem validar parsing, versionamento, idempotência e regra de negócio sem broker. Testes de integração podem subir Kafka, RabbitMQ ou NATS via Docker Compose para cobrir publish, consume, ack, retry e shutdown gracioso. Testes de contrato garantem que o JSON produzido por um serviço continua aceito pelo consumidor.

Um bom conjunto mínimo inclui: mensagem válida processada com sucesso, mensagem duplicada ignorada, payload inválido rejeitado, erro transitório marcado para retry, erro permanente enviado para dead letter, consumer encerrando sem perder mensagem em andamento e handler respeitando timeout. Isso parece muito, mas cada caso evita uma classe de incidente comum.

Se o serviço usa Tokio, cuidado com testes que dependem de tempo real. Prefira timeouts curtos, funções puras no núcleo e simulações controladas para dependências externas. Quanto menos a regra de negócio souber sobre o broker, mais fácil será testar.

Projeto de portfólio para carreira Rust

Um projeto forte para currículo é um processador de eventos de pedidos em Rust. Publique uma API Axum que recebe pedidos, grava no PostgreSQL e publica PedidoCriado. Depois crie um worker que consome esse evento, simula antifraude, publica PagamentoAprovado ou PagamentoRecusado e registra auditoria. Adicione Docker Compose com PostgreSQL e um broker escolhido. Documente como rodar, como inspecionar filas, como reprocessar dead letters e como observar logs.

Esse projeto demonstra mais do que “sei Rust”. Ele mostra arquitetura, backend, banco, async, testes, idempotência, observabilidade e operação. Para empresas brasileiras listadas em empresas que usam Rust, esse conjunto é mais próximo do trabalho real do que um algoritmo isolado.

Também é uma boa ponte para quem já vem de Go. O ecossistema do Go Brasil é muito forte em microsserviços, filas e plataformas cloud native; comparar um worker Go com um worker Rust ajuda a entender trade-offs reais de produtividade, performance, tipos e operação.

Como escolher o primeiro caminho

Se você está aprendendo, não comece com todos os brokers. Escolha um problema pequeno e um broker. Para uma vaga backend comum, RabbitMQ com lapin é didático porque deixa claro o ciclo fila, ack, retry e dead letter. Para plataforma de dados, Kafka com rdkafka ensina partições, offsets, consumer groups e replay. Para sistemas internos leves, NATS mostra como pub/sub pode ser simples sem perder seriedade.

Depois, conecte esse aprendizado ao restante da stack Rust: Cargo para organizar o projeto, Serde para contratos, Tokio para concorrência, SQLx para persistência, Tracing para logs e Axum para expor endpoints administrativos. A força de Rust em mensageria não está em uma crate específica. Está em conseguir transformar comportamento distribuído em contratos explícitos, erros tratados e binários previsíveis.

Em 2026, esse é um diferencial profissional. Muitas empresas já têm filas e streams; poucas têm consumers bem observáveis, idempotentes e fáceis de operar. Rust oferece uma base excelente para construir essa camada com menos surpresa em produção.