Microsserviços RabbitMQ .NET MassTransit Mensageria

Microsserviços na prática: comunicação assíncrona com RabbitMQ e .NET

Como implementar comunicação assíncrona entre microsserviços .NET usando RabbitMQ e MassTransit: publishers, consumers, retries.

N
Neryx Digital Architects
25 de dezembro de 2025
15 min de leitura
250 profissionais leram
Categoria: Arquitetura Público: Times de engenharia e produto Etapa: Aprendizado

Um dos maiores desafios ao migrar de monolito para microsserviços é a comunicação entre serviços. Chamadas HTTP síncronas parecem simples no começo, mas criam acoplamento temporal: se o serviço de destino estiver fora do ar, a operação falha — e você precisa lidar com retry, timeout e circuit breaker em cada chamada.

Mensageria assíncrona com RabbitMQ resolve isso de forma elegante. O serviço publica uma mensagem e continua executando — não importa se o consumidor está disponível naquele momento. O RabbitMQ garante a entrega quando o consumidor voltar.

Neste guia você vai implementar um cenário real: Serviço de Pedidos publica um evento quando um pedido é confirmado, e o Serviço de Notificações consome esse evento para enviar e-mail.

Stack utilizada

RabbitMQ — message broker open source, battle-tested, com dashboard web incluído.
MassTransit — biblioteca .NET que abstrai o broker (funciona com RabbitMQ, Azure Service Bus, Amazon SQS). Cuida de serialização, retry, dead-letter e registro de consumers automaticamente.

Setup com Docker Compose

# docker-compose.yml
services:
  rabbitmq:
    image: rabbitmq:3.13-management-alpine
    ports:
      - "5672:5672"    # AMQP
      - "15672:15672"  # Dashboard web (login: guest/guest)
    environment:
      RABBITMQ_DEFAULT_VHOST: neryx
    healthcheck:
      test: rabbitmq-diagnostics -q ping
      interval: 10s
      timeout: 5s
      retries: 5

  pedidos-api:
    build: ./src/PedidosApi
    depends_on:
      rabbitmq:
        condition: service_healthy
    environment:
      RabbitMQ__Host: rabbitmq
      RabbitMQ__VirtualHost: neryx

  notificacoes-worker:
    build: ./src/NotificacoesWorker
    depends_on:
      rabbitmq:
        condition: service_healthy
    environment:
      RabbitMQ__Host: rabbitmq
      RabbitMQ__VirtualHost: neryx

Pacotes necessários

# Nos dois projetos (publisher e consumer)
dotnet add package MassTransit
dotnet add package MassTransit.RabbitMQ

Apenas no consumer (worker)

dotnet add package MassTransit.Extensions.DependencyInjection

Definindo os contratos de mensagem

Os contratos (interfaces ou records das mensagens) devem ficar em um pacote compartilhado entre os serviços. Isso garante que publisher e consumer falem a mesma "língua".

// Projeto: Neryx.Contracts (referenciado pelos dois serviços)
namespace Neryx.Contracts.Pedidos;

public record PedidoConfirmadoEvent { public Guid PedidoId { get; init; } public Guid ClienteId { get; init; } public string ClienteEmail { get; init; } = string.Empty; public string ClienteNome { get; init; } = string.Empty; public decimal Total { get; init; } public DateTime ConfirmadoEm { get; init; } public IReadOnlyList<ItemConfirmadoDto> Itens { get; init; } = []; }

public record ItemConfirmadoDto(string Produto, int Quantidade, decimal PrecoUnitario);

Publisher: Serviço de Pedidos

// Program.cs do PedidosApi
builder.Services.AddMassTransit(x =>
{
    x.UsingRabbitMq((ctx, cfg) =>
    {
        cfg.Host(builder.Configuration["RabbitMQ:Host"] ?? "localhost", "/neryx", h =>
        {
            h.Username("guest");
            h.Password("guest");
        });
    cfg.ConfigureEndpoints(ctx);
});

});

// Handler do command ConfirmarPedido (usando CQRS + MediatR do artigo anterior) public class ConfirmarPedidoHandler : IRequestHandler<ConfirmarPedidoCommand, ConfirmarPedidoResult> { private readonly IPedidoRepository _repository; private readonly IUnitOfWork _unitOfWork; private readonly IPublishEndpoint _publishEndpoint; // MassTransit

public ConfirmarPedidoHandler(
    IPedidoRepository repository,
    IUnitOfWork unitOfWork,
    IPublishEndpoint publishEndpoint)
{
    _repository = repository;
    _unitOfWork = unitOfWork;
    _publishEndpoint = publishEndpoint;
}

public async Task&lt;ConfirmarPedidoResult&gt; Handle(
    ConfirmarPedidoCommand request,
    CancellationToken ct)
{
    var pedido = await _repository.GetByIdAsync(request.PedidoId, ct)
        ?? throw new NotFoundException("Pedido não encontrado.");

    var cliente = await _clienteRepository.GetByIdAsync(pedido.ClienteId, ct);

    pedido.Confirmar();
    await _unitOfWork.SaveChangesAsync(ct);

    // Publica o evento — sem saber quem vai consumir
    await _publishEndpoint.Publish(new PedidoConfirmadoEvent
    {
        PedidoId = pedido.Id,
        ClienteId = pedido.ClienteId,
        ClienteEmail = cliente.Email,
        ClienteNome = cliente.Nome,
        Total = pedido.CalcularTotal(),
        ConfirmadoEm = DateTime.UtcNow,
        Itens = pedido.Itens.Select(i => new ItemConfirmadoDto(i.Produto.Nome, i.Quantidade, i.PrecoUnitario)).ToList()
    }, ct);

    return new ConfirmarPedidoResult(pedido.Id, pedido.Status.ToString(), pedido.CalcularTotal());
}

}

Consumer: Serviço de Notificações

// NotificacoesWorker — um Worker Service (.NET)
// Program.cs
builder.Services.AddMassTransit(x =>
{
    x.AddConsumer<PedidoConfirmadoConsumer>();
x.UsingRabbitMq((ctx, cfg) =>
{
    cfg.Host(builder.Configuration["RabbitMQ:Host"] ?? "localhost", "/neryx", h =>
    {
        h.Username("guest");
        h.Password("guest");
    });

    cfg.ReceiveEndpoint("notificacoes-pedido-confirmado", e =>
    {
        // Retry automático: 3 tentativas com backoff exponencial
        e.UseMessageRetry(r => r.Exponential(3,
            TimeSpan.FromSeconds(1),
            TimeSpan.FromSeconds(30),
            TimeSpan.FromSeconds(5)));

        e.ConfigureConsumer&lt;PedidoConfirmadoConsumer&gt;(ctx);
    });
});

});

// Consumer public class PedidoConfirmadoConsumer : IConsumer<PedidoConfirmadoEvent> { private readonly IEmailService _emailService; private readonly ILogger<PedidoConfirmadoConsumer> _logger;

public PedidoConfirmadoConsumer(IEmailService emailService, ILogger&lt;PedidoConfirmadoConsumer&gt; logger)
{
    _emailService = emailService;
    _logger = logger;
}

public async Task Consume(ConsumeContext&lt;PedidoConfirmadoEvent&gt; context)
{
    var evento = context.Message;
    _logger.LogInformation("Processando confirmação do pedido {PedidoId}", evento.PedidoId);

    await _emailService.EnviarConfirmacaoPedido(new EmailConfirmacaoDto(
        Para: evento.ClienteEmail,
        NomeCliente: evento.ClienteNome,
        NumeroPedido: evento.PedidoId.ToString()[..8].ToUpper(),
        Total: evento.Total,
        Itens: evento.Itens,
        DataConfirmacao: evento.ConfirmadoEm
    ));

    _logger.LogInformation("E-mail de confirmação enviado para {Email}", evento.ClienteEmail);
}

}

Dead-Letter Queue: o que fazer com mensagens que falham

Quando todas as tentativas de retry esgotam, o MassTransit move a mensagem para uma fila especial de erro — a Dead-Letter Queue (DLQ). Isso evita que mensagens problemáticas bloqueiem o processamento das demais.

// Configurar Dead-Letter Exchange no RabbitMQ via MassTransit
cfg.ReceiveEndpoint("notificacoes-pedido-confirmado", e =>
{
    e.UseMessageRetry(r => r.Exponential(3,
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(30),
        TimeSpan.FromSeconds(5)));
// Após todas as tentativas, vai para a fila de erro
e.UseDelayedRedelivery(r => r.Intervals(
    TimeSpan.FromMinutes(5),
    TimeSpan.FromMinutes(15),
    TimeSpan.FromMinutes(60)));

e.ConfigureConsumer&lt;PedidoConfirmadoConsumer&gt;(ctx);

}); // Mensagens na DLQ ficam visíveis no dashboard do RabbitMQ (porta 15672) // Você pode reprocessá-las manualmente quando o problema for corrigido

Idempotência: o consumidor pode receber a mesma mensagem duas vezes

Em sistemas distribuídos, "at-least-once delivery" é a garantia padrão — a mesma mensagem pode ser entregue mais de uma vez (em caso de falha antes do ACK). Seus consumers precisam ser idempotentes:

public class PedidoConfirmadoConsumer : IConsumer<PedidoConfirmadoEvent>
{
    private readonly IEmailService _emailService;
    private readonly INotificacaoRepository _repository;
public async Task Consume(ConsumeContext&lt;PedidoConfirmadoEvent&gt; context)
{
    var evento = context.Message;

    // Verificar se já processamos este evento
    var jaProcessado = await _repository.ExisteNotificacaoAsync(evento.PedidoId);
    if (jaProcessado)
    {
        _logger.LogInformation("Evento {PedidoId} já processado. Ignorando duplicata.", evento.PedidoId);
        return;
    }

    await _emailService.EnviarConfirmacaoPedido(...);

    // Registrar que processamos
    await _repository.SalvarNotificacaoAsync(new Notificacao(evento.PedidoId, evento.ClienteEmail, DateTime.UtcNow));
}

}

Monitorando filas no dashboard do RabbitMQ

Com o Docker Compose rodando, acesse http://localhost:15672 (login: guest/guest). Você pode ver em tempo real: mensagens na fila, taxa de publicação/consumo, mensagens em erro na DLQ, e estado dos consumers conectados. É a forma mais rápida de diagnosticar problemas de mensageria em desenvolvimento.

RabbitMQ vs Amazon SQS

RabbitMQ é ideal para ambiente on-premises, desenvolvimento local e controle total sobre a infraestrutura. Amazon SQS (integrado ao MassTransit com o pacote MassTransit.AmazonSqS) é melhor para quem já está na AWS e quer eliminar a operação do broker. A mudança entre os dois exige apenas trocar a configuração do UsingRabbitMq por UsingAmazonSqs — os consumers e publishers ficam idênticos.

Conclusão

Mensageria assíncrona com RabbitMQ e MassTransit desacopla seus microsserviços de forma robusta: o publisher não precisa saber quem consome, retry é automático, e Dead-Letter Queues evitam perda de mensagens. MassTransit adiciona uma camada de abstração que torna o código portável para outros brokers sem reescrita.

Os padrões mostrados aqui — contratos compartilhados, consumers idempotentes, retry com backoff exponencial — são os mesmos usados em sistemas de produção de alta escala.

Se você está desenhando a arquitetura de mensageria de um sistema ou migrando de chamadas HTTP síncronas para eventos, a Neryx pode ajudar com a estratégia e implementação. Consultoria inicial gratuita.

Leitura complementar:

Precisa desenhar a próxima fase com menos retrabalho?

Fazemos discovery técnico para mapear riscos, arquitetura-alvo e sequência de execução antes de investir pesado.

Solicitar Discovery

Newsletter

Receba artigos como este no seu e-mail

Conteúdo técnico sobre arquitetura de software, .NET, IA e gestão de produto. Sem spam.