Ao usar o Paramore.Brighter pela primeira vez, os desenvolvedores frequentemente entendem mal como funciona o tratamento de exceções, especialmente em relação ao reprocessamento de mensagens. Diferente de muitos sistemas, o Brighter confirma (ACK) uma mensagem após uma exceção não tratada por padrão. Este artigo esclarece esse comportamento e demonstra as maneiras corretas de implementar a lógica de repetição (retry) para mensagens com falha no Brighter V9 e V10.
Requisitos
- .NET 8 ou superior
- Podman ou docker
Para o Brighter, precisaremos destes pacotes NuGet:
- Paramore.Brighter.MessagingGateway.RocketMQ
- Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection
- Paramore.Brighter.ServiceActivator.Extensions.Hosting
- Serilog.AspNetCore
Para o Fluent Brighter:
Recapitulação do Brighter
Antes de mergulharmos no tratamento de erros, vamos revisar os blocos fundamentais de construção do Brighter.
Request (Comando/Evento)
Mensagens são classes simples que implementam IRequest
.
public class Greeting() : Event(Guid.NewGuid())
{
public string Name { get; set; } = string.Empty;
}
-
Comandos: Operações com um único destinatário (ex:
SendEmail
). -
Eventos: Notificações de broadcast (ex:
OrderShipped
).
Criando um Mapeador de Mensagem (Opcional)
Mapeadores traduzem entre seus objetos .NET e o formato Message do Brighter.
A partir do Brighter V10, esta etapa é opcional.
public class GreetingMapper : IAmAMessageMapper<Greeting>
{
public Message MapToMessage(Greeting request)
{
var header = new MessageHeader();
header.Id = request.Id;
header.TimeStamp = DateTime.UtcNow;
header.Topic = "greeting.topic"; // O tópico de destino para publicação
header.MessageType = MessageType.MT_EVENT;
var body = new MessageBody(JsonSerializer.Serialize(request));
return new Message(header, body);
}
public Greeting MapToRequest(Message message)
{
return JsonSerializer.Deserialize<Greeting>(message.Body.Bytes)!;
}
}
Implementando um Manipulador de Request (Handler)
Os Handlers contêm a lógica de negócio para processar mensagens recebidas.
public class GreetingHandler(ILogger<GreetingHandler> logger) : RequestHandler<Greeting>
{
public override Greeting Handle(Greeting command)
{
logger.LogInformation("Olá {Name}", command.Name);
return base.Handle(command);
}
}
Configurando o Fluent Brighter com RocketMQ
A API Fluent Brighter fornece uma maneira simplificada de configurar sua aplicação. O exemplo a seguir configura um publicador (publisher) e um assinante (subscriber) para o evento Greeting.
service
.AddHostedService<ServiceActivatorHostedService>()
.AddFluentBrighter(brighter => brighter
.UsingRocketMq(rocket => rocket
.SetConnection(conn => conn
.SetClient(c => c
.SetEndpoints("localhost:8081")
.EnableSsl(false)
.SetRequestTimeout(TimeSpan.FromSeconds(10))
.Build()))
.UsePublications(pub => pub
.AddPublication<GreetingEvent>(p => p
.SetTopic("greeting")))
.UseSubscriptions(sub => sub
.AddSubscription<GreetingEvent>(s => s
.SetSubscriptionName("greeting-sub-name")
.SetTopic("greeting")
.SetConsumerGroup("greeting-consumer-group")
.UseReactorMode()
))));
Para um mergulho mais profundo no Brighter e RocketMQ, veja este artigo detalhado.
Entendendo o Tratamento de Exceções do Brighter
Por padrão, o Brighter confirma (ACK) uma mensagem após uma exceção ser lançada em um handler. Isso ocorre porque o Brighter não pode distinguir entre um erro permanente de aplicação e um erro transitório e recuperável. O padrão seguro é assumir que o erro é permanente e não repetir, evitando que uma mensagem "venenosa" (poison-pill) bloqueie a fila.
Para solicitar explicitamente uma repetição, você deve lançar uma exceção do tipo DeferMessageAction
. Isso informa ao Brighter para negativamente confirmar (NACK) a mensagem, fazendo com que o broker a redelivery.
O Problema
Considere um handler que falha quando a propriedade Name
é "fail".
public class GreetingHandler : RequestHandler<Greeting>
{
public override Greeting Handle(Greeting @event)
{
if (@event.Name == "fail")
{
Console.WriteLine("===== falha ao processar");
throw new Exception("Algum erro");
}
Console.WriteLine("===== Olá, {0}", @event.Name);
return base.Handle(@event);
}
}
Neste cenário, a mensagem com Name = "fail"
é consumida uma vez e depois perdida porque o Brighter a confirma (ACK) após a Exception ser lançada.
Solução 1: Try/Catch Explícito com Defer
O método mais direto é envolver a lógica do seu handler em um bloco try-catch e lançar DeferMessageAction
para qualquer erro que você deseje repetir.
public class GreetingHandler : RequestHandler<Greeting>
{
public override Greeting Handle(Greeting @event)
{
try
{
if (@event.Name == "fail")
{
Console.WriteLine("===== falha ao processar");
throw new Exception("Algum erro");
}
Console.WriteLine("===== Olá, {0}", @event.Name);
return base.Handle(@event);
}
catch (Exception)
{
Console.Write("=== lançando DeferMessageAction");
throw new DeferMessageAction();
}
}
}
Solução 2: Usando o Middleware de Política de Fallback (Fallback Policy)
Uma abordagem mais elegante usa o modelo de middleware "Russian Doll" do Brighter. Aplicando o atributo [FallbackPolicy]
, você pode sobrescrever o método Fallback, que age como um "catch-all" quando o método principal Handle falha.
Isto é frequentemente preferível, pois mantém sua lógica de negócio principal limpa e separa a preocupação de repetição.
public class GreetingHandler : RequestHandler<Greeting>
{
[FallbackPolicy(true, false, 0)]
public override Greeting Handle(Greeting @event)
{
if (@event.Name == "fail")
{
Console.WriteLine("===== falha ao processar");
throw new Exception("Algum erro");
}
Console.WriteLine("===== Olá, {0}", @event.Name);
return base.Handle(@event);
}
private static int _counter = 0;
public override Greeting Fallback(Greeting command)
{
var res = Interlocked.Increment(ref _counter);
if (res % 3 == 0)
{
Console.Write("=== marcando como concluído");
return command;
}
Console.Write("=== lançando DeferMessageAction");
throw new DeferMessageAction();
}
}
Nota: Para handlers assíncronos, você deve usar o atributo FallbackPolicyAsync
e sobrescrever o método FallbackAsync
.
Conclusão
Tratar exceções de forma eficaz no Brighter requer a compreensão de sua política padrão de confirmação "assumir sucesso". Você não pode contar com exceções não tratadas para acionar repetições. Em vez disso, você deve controlar explicitamente o comportamento de repetição lançando DeferMessageAction
.
Você pode implementar isso diretamente dentro da lógica do seu handler usando um bloco try-catch ou, de maneira mais limpa, aproveitando o middleware [FallbackPolicy]
para manter sua lógica de negócio focada e seu tratamento de erro centralizado. A abordagem de middleware é geralmente recomendada devido à sua separação de preocupações e maior flexibilidade.
Veja o código completo em: https://github.com/lillo42/brighter-sample/tree/v10-error-handling
Top comments (0)