Publicado em: quinta-feira, 12 de fev de 2026
Oragon.RabbitMQ 1.6 – Welcome .NET 10

Desde a versão 1.1.0 (janeiro/2025), o Oragon.RabbitMQ passou por 6 releases com mais de 70 commits, resultando em +3.894 linhas adicionadas e -1.245 removidas ao longo de 112 arquivos.

A seguir, um resumo das principais novidades.

O que mudou de v1.1 para v1.6


Suporte a .NET 10

A partir da v1.6.0, o projeto passou a ter suporte nativo a três gerações do .NET simultaneamente:

  • .NET 8 (LTS)
  • .NET 9
  • .NET 10 (adicionado na v1.6.0) Isso garante que equipes em diferentes estágios de adoção possam utilizar a biblioteca sem restrições.

Novos Padrões de Mensageria: ReplyResult e ForwardResult (Estáveis)

Introduzidos como experimentais na v1.4.0 e promovidos a estáveis na v1.6.0, esses dois novos IAmqpResult ampliam significativamente os padrões de mensageria suportados:

ReplyResult – Padrão RPC (Request-Reply)

Permite que um handler responda diretamente ao remetente da mensagem, habilitando o padrão RPC de forma nativa:

app.MapQueue("rpc-queue", (MyRequest request) =>
  {
      var response = ProcessRequest(request);
      return AmqpResults.ReplyAndAck(response);
  });
  • Extrai automaticamente o ReplyTo da mensagem original
  • Preserva correlação via CorrelationId / MessageId
  • Cria um canal dedicado para a resposta, evitando race conditions

ForwardResult – Encaminhamento de Mensagens Permite redirecionar mensagens

processadas para outros exchanges/filas:

app.MapQueue("intake-queue", (Order order) =>
  {
      var enrichedOrder = Enrich(order);
      return AmqpResults.ForwardAndAck("orders-exchange", "processed", mandatory: true, enrichedOrder);
  });
  • Suporta envio para exchanges com routing key
  • Flag mandatory para garantia de entrega
  • Parâmetro opcional replyTo para encadeamento de replies

ComposableResult – Composição de Ações

Permite encadear múltiplas ações AMQP em um único retorno de handler:

app.MapQueue("my-queue", (MyMessage msg) =>
  {
      return AmqpResults.Compose(
          AmqpResults.Reply(response),
          AmqpResults.Forward("audit-exchange", "audit.key", false, msg),
          AmqpResults.Ack()
      );
  });

Métodos auxiliares como AmqpResults.ReplyAndAck() e AmqpResults.ForwardAndAck() simplificam os casos mais comuns.

Topology Initializer – Declaração Programática de Topologia

Adicionado na v1.5.0 (originalmente como ChannelInitializer, renomeado na v1.5.2 para maior clareza), permite declarar exchanges, filas e bindings antes do consumer começar a consumir:

  app.MapQueue("my-queue", handler)
     .WithTopology(async (channel, ct) =>
     {
         await channel.ExchangeDeclareAsync("my-exchange", "topic", cancellationToken: ct);
         await channel.QueueBindAsync("my-queue", "my-exchange", "routing.key", cancellationToken: ct);
     });

Isso elimina a necessidade de configuração externa de topologia e garante que a infraestrutura esteja pronta antes do consumo iniciar.

Otimizações de Performance: Expression Trees

A v1.6.0 trouxe duas otimizações significativas que eliminam o uso de Reflection no hot path:

Dispatcher com Expression-based Invoker

O Dispatcher substituiu DynamicInvoke (reflection) por delegates compilados via System.Linq.Expressions. O delegate é compilado uma única vez na inicialização e reutilizado para todos os dispatches de mensagem:

Antes: handler.DynamicInvoke(args) // reflection em cada mensagem
Depois: compiledInvoker(args) // delegate compilado, zero reflection

TaskOfAmqpResultResultHandler com Expression-based Extractor

A extração de Task.Result também foi migrada de PropertyInfo.GetValue para Expression Trees compiladas.

Resultado nos benchmarks: O overhead do Oragon sobre o consumer nativo do RabbitMQ.Client é de apenas ~1-5% em throughput, com ratio ~1.00 em cenários de I/O bound e ~1.02-1.07 em cenários CPU bound.

Eventos de Conexão: Shutdown, Blocked e Unblocked

Novos event handlers no QueueConsumer oferecem visibilidade sobre o estado da conexão:

  • ConnectionShutdownAsync (Critical): Loga quando a conexão é perdida, indicando que o consumer não se auto-recupera
  • ConnectionBlockedAsync (Warning): Detecta quando o RabbitMQ está sob pressão de recursos (memória/disco)
  • ConnectionUnblockedAsync (Information): Indica que a pressão de recursos foi resolvida.

Todos utilizam o padrão de alta performance LoggerMessage.Define com event IDs estruturados.

Health Checks Integrados

Integração nativa com o sistema de Health Checks do ASP.NET Core, especialmente útil com .NET Aspire:

builder.AddRabbitMQClient("rabbitmq"); // Health check registrado automaticamente
  • Registra health checks automaticamente ao configurar a conexão
  • Suporte a conexões keyed e non-keyed
  • Pode ser desabilitado via settings.DisableHealthChecks = true
  • Degradação graciosa: se a conexão falhar durante o registro, usa um FailedHealthCheck wrapper

IAsyncDisposable e Gerenciamento de Recursos

A gestão de ciclo de vida foi modernizada:

  • ConsumerServer: Removido IDisposable síncrono, mantendo apenas IAsyncDisposable
  • QueueConsumer: Dispose assíncrono completo com cancelamento de tokens, desregistro de event handlers e disposal do channel
  • Consumers são descartados em ordem reversa durante o shutdown
  • Proteção contra double-disposal com flag disposedValue

Melhorias de Thread-Safety e Async

  • Uso correto e consistente de ConfigureAwait em toda a stack
  • Campo isLocked no ConsumerDescriptor tornado volatile para thread-safety
  • Propagação adequada de CancellationToken em toda a cadeia assíncrona
  • Criação de conexões totalmente assíncrona

Logging de Alta Performance

  • Adoção do LoggerMessage.Define para alta performance
  • Logging estruturado com Event IDs em todos os caminhos críticos
  • Log para mensagens deserializadas como null (EventId 12)
  • Informações diagnósticas detalhadas em eventos de conexão

Cobertura de Testes Ampliada

Novos testes unitários adicionados para:

  • ConsumerDescriptor (+518 linhas)
  • QueueConsumer (+525 linhas)
  • ConsumerServer (+439 linhas)
  • ForwardResult (+196 linhas)
  • AsStringExtensions, NewReverseList, e mais

Nossas métricas no sonar estão bem interessantes também:

Benchmarks – A cereja do bolo

A v1.6.0 inclui um projeto de benchmarks (Oragon.RabbitMQ.Benchmarks) com cenários que medem:

  • Throughput: Comparação direta Native vs Oragon
  • Latência: Overhead por mensagem
  • Alocações: Memória alocada por operação
  • Escalabilidade de Concorrência: Comportamento com diferentes níveis de prefetch e dispatch concurrency
  • RPC: Performance do padrão request-reply Os resultados confirmam que o Oragon mantém performance virtualmente idêntica ao consumer nativo em cenários I/O bound (ratio ~1.00).

Cronologia

VersãoDataDestaques
1.1.0Jan/2025Baseline documentada no post anterior
1.2.x-betaAbr/2025Testes de ConsumerServer, renaming para ConsumerDescriptor
1.3.0-betaAbr/2025ReplyAndAck, ComposableResult
1.4.0Jun/2025ForwardResult (experimental), melhorias de locking
1.5.0Set/2025Topology Initializer, Health Checks, melhorias de conexão
1.5.1Set/2025Refactoring da inicialização do QueueConsumer
1.5.2Set/2025Rename para TopologyInitializer
1.6.0Fev/2026.NET 10, Expression Trees, eventos de conexão, ReplyResult/ForwardResult estáveis, benchmarks

O mais importante

O mais importante acompanhando projetos usando o Oragon.RabbitMQ é a capacidade de entregar um fluxo padronizado e uniforme, sem precisar tocar em tão baixo nível no AMQP/RabbitMQ.Client, permitindo consumir filas como se fossem minimal API’s.

Ao eliminar o boilerplate, adotando uma política rígida de ack, nack e reject que não suje nem mascare a realidade no dashboard e métricas do RabbitMQ, torna a vida mais fácil, mais simples.

Nem tudo são flores

Um erro grave no fluxo de inicialização dos consumidores, produzia um fire and forget no start do consumer, mascarando erros que fossem desse início da jornada. Cenário não tão comum, mas fácil de acontecer quando você está “mudando tudo” (exchanges, binds, filas etc) que era um dos cenários de cliente.

Novas demandas

Nesse momento, tenho uma demanda exótica que consiste em uma fila de controle usada para receber nomes de filas que precisam ser atendidas por uma janela limitada de tempo, como uma fila de atenção, uma implementação similar à que sistema operacional usa para que seu processador consiga atender mais processos e threads do que cores disponíveis.

Para o Oragon.RabbitMQ a demanda é permitir subir e parar dinamicamente o consumo de uma fila, trocar a fila alvo e continuar o ciclo.

https://github.com/luizcarlosfaria/Oragon.RabbitMQ

0 comentários

Enviar um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.

Lives

Fique de olho nas lives

Fique de olho nas lives no meu canal do Youtube, no Canal .NET e nos Grupos do Facebook e Instagram.

Aceleradores

Existem diversas formas de viabilizar o suporte ao teu projeto. Seja com os treinamentos, consultoria, mentorias em grupo.