DEV Community

Cover image for Entendendo o Pipeline do Brighter
Rafael Andrade
Rafael Andrade

Posted on

Entendendo o Pipeline do Brighter

O Brighter adota uma abordagem distinta em comparação a muitos outros frameworks, priorizando a clareza explícita em seu pipeline de tratamento de requisições. Em vez de depender de convenções ocultas ou configurações complexas, você define explicitamente o comportamento do pipeline usando atributos, garantindo controle total sobre a ordem de execução.

O Modelo da Boneca Russa (Matryoshka)

O pipeline do Brighter é arquitetado com base no Modelo da Boneca Russa. Imagine um conjunto de bonecas encaixadas: cada boneca contém uma menor em seu interior.

No Brighter, cada componente de middleware (uma "boneca") envolve o próximo na cadeia. Um método handler fica no centro. Quando uma requisição é processada, ela atravessa cada camada de middleware antes de chegar ao handler, e depois retorna passando novamente por cada camada na ordem inversa. Esse design implementa perfeitamente o Padrão Pipe and Filter.

Um recurso essencial desse modelo é a capacidade de interromper o pipeline (short-circuit). Qualquer middleware pode parar o processamento e retornar um resultado imediatamente, impedindo que a requisição chegue às camadas internas (incluindo o próprio handler).

Essa arquitetura permite que qualquer etapa do pipeline:

  1. Execute ações antes da requisição chegar ao handler.
  2. Encaminhe a requisição para a próxima etapa.
  3. Execute ações após o retorno do handler interno.
  4. Interrompa o pipeline retornando antecipadamente (cancelando o processamento).

Observação: Embora este guia use exemplos de handlers síncronos, os mesmos conceitos se aplicam a handlers assíncronos. Basta usar os atributos e classes base correspondentes (ex: RequestHandlerAsync<T>).

Adicionando um Pipeline ao Seu Handler

No Brighter, middlewares são adicionados via Atributos do C#. Você deve decorar explicitamente o método Handle do seu Request Handler.

A ordem de execução é controlada pelo parâmetro step:

  • Números menores executam primeiro na entrada e por último na saída.
public class SampleHandler : RequestHandler<SomeMessage>
{
    // O 'step' define a ordem. 
    // Step 1 executa antes do Step 2.
    [RequestLogging(step: 0)]
    public virtual TRequest Handle(TRequest command)
    {
        ...
    }
}
Enter fullscreen mode Exit fullscreen mode

Implementando Middleware "Global"

O Brighter não oferece uma API fluente (ex: services.AddGlobalMiddleware(...)) para injetar lógica globalmente em todas as requisições. Essa é uma escolha deliberada de design para manter o pipeline explícito no nível do handler.

Porém, para aplicar lógica a todos os handlers ("middleware global"), use herança: crie um Handler Base com os atributos desejados. Todos os handlers que herdarem dessa classe adotarão o pipeline definido.

Exemplo: Criando um Handler Base com Políticas Globais

// 1. Crie um handler base com middleware comum
public abstract class MyGlobalPolicyHandler<T> : RequestHandler<T> where T : class, IRequest
{
    [RequestLogging(step: 0)]
    [FallbackPolicy(backstop: true, circuitBreaker: false, step: 1)]
    public override T Handle(T command)
    {
        // Este método propaga o comando pelo pipeline até o handler real.
        return base.Handle(command);
    }
}

// 2. Herde do handler base nas suas implementações
public class SampleHandler : MyGlobalPolicyHandler<SomeCommand>
{
    [UseResiliencePipeline("MyPolicy", step: 2)]
    public override SomeCommand Handle(SomeCommand command)
    {
        // Sua lógica de negócio principal aqui.
        Console.WriteLine("Handling SomeCommand");
        return base.Handle(command);
    }
}
Enter fullscreen mode Exit fullscreen mode

Ordem de execução para SampleHandler:

RequestLoggingFallbackPolicyUseResiliencePipelineSampleHandler.Handle.

Middleware Oferecido pelo Brighter

O Brighter inclui middlewares prontos para uso:

  • RequestLoggingAttribute / RequestLoggingAsyncAttribute → Registra a entrada/saída do pipeline, incluindo tempo de execução e detalhes da requisição.
  • FallbackPolicyAttribute / FallbackPolicyAsyncAttribute → Define uma ação de fallback se a execução falhar. Chama o método Fallback do handler.
  • TimeoutPolicyAttribute → (Obsoleto) Define tempo máximo de execução. Prefira UseResiliencePipeline.
  • UsePolicyAttribute / UsePolicyAsyncAttribute → (Obsoleto) Usa políticas do Polly. Prefira UseResiliencePipeline.
  • UseResiliencePipelineAsyncAttribute / UseResiliencePipelineAttribute → Aplica pipelines de resiliência do Polly (retry, circuit-breaker, timeouts).
  • MonitorAttribute / MonitorAsyncAttribute → Permite monitoramento de handlers.
  • FeatureSwitchAttribute / FeatureSwitchAsyncAttribute → Habilita/desabilita dinamicamente um handler com base em um feature flag.

Implementando Seu Próprio Middleware

Para criar middleware personalizado, você precisa de duas classes por modo de execução (síncrono/assíncrono):

  1. O Atributo: Decora o método do handler.
  2. O Handler: Contém a lógica do middleware.

Exemplo de Implementação:

// 1. Classe do Atributo
[AttributeUsage(AttributeTargets.Method)]
public class MyFeatureFlagAttribute : RequestHandlerAttribute
{
    public string FeatureName { get; }

    public MyFeatureFlagAttribute(string featureName, int step) : base(step, HandlerTiming.Before)
    {
        FeatureName = featureName;
    }

    public override object[] InitializerParams()
    {
        return new object[] { FeatureName };
    }

    public override Type GetHandlerType()
    {
        // Indica qual classe handler usar para este atributo.
        return typeof(MyFeatureFlagHandler<>);
    }
}

// 2. Classe do Handler
public class MyFeatureFlagHandler<TRequest> : RequestHandler<TRequest> where TRequest : class, IRequest
{
    private string _featureName;
    private IFeatureManager _featureManager; // Gerenciador de features hipotético

    public override void InitializeFromAttributeParams(params object[] initializerList)
    {
        // Recebe parâmetros do método InitializerParams()
        _featureName = (string)initializerList[0];
        _featureManager = ...; // Normalmente injetado via DI (Injeção de Dependência)
    }

    public override TRequest Handle(TRequest command)
    {
        // Verifica se a funcionalidade está habilitada
        if (!_featureManager.IsEnabled(_featureName))
        {
            return command;
        }

        // Se habilitada, continua para o próximo handler no pipeline.
        return base.Handle(command);
    }
}

// 3. Usando o Middleware Personalizado
public class DiscountHandler : RequestHandler<ApplyDiscountCommand>
{
    [MyFeatureFlag("AdvancedDiscounts", step: 1)]
    public override ApplyDiscountCommand Handle(ApplyDiscountCommand command)
    {
        // Este código só executa se a funcionalidade "AdvancedDiscounts" estiver habilitada.
        return base.Handle(command);
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusão

O pipeline explícito e baseado em atributos do Brighter oferece clareza e controle incomparáveis sobre as preocupações transversais da sua aplicação. Ao utilizar o Modelo da Boneca Russa, você constrói pipelines de handlers robustos, bem ordenados e fáceis de manter. Seja com políticas embutidas ou middlewares personalizados, o processo permanece consistente e transparente.

Referência

Documentação Oficial: Building a Pipeline of Request Handlers

Top comments (0)