DEV Community

Cover image for Tratamento de Exceções no Brighter
Rafael Andrade
Rafael Andrade

Posted on

Tratamento de Exceções no Brighter

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:

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;
}
Enter fullscreen mode Exit fullscreen mode
  • 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)!;
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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()
))));
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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)