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ão | Data | Destaques |
|---|---|---|
| 1.1.0 | Jan/2025 | Baseline documentada no post anterior |
| 1.2.x-beta | Abr/2025 | Testes de ConsumerServer, renaming para ConsumerDescriptor |
| 1.3.0-beta | Abr/2025 | ReplyAndAck, ComposableResult |
| 1.4.0 | Jun/2025 | ForwardResult (experimental), melhorias de locking |
| 1.5.0 | Set/2025 | Topology Initializer, Health Checks, melhorias de conexão |
| 1.5.1 | Set/2025 | Refactoring da inicialização do QueueConsumer |
| 1.5.2 | Set/2025 | Rename para TopologyInitializer |
| 1.6.0 | Fev/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.

0 comentários