Understanding the Brighter Pipeline

Brighter takes a distinct approach compared to many other frameworks by prioritizing explicitness in its request handling pipeline. Instead of relying on hidden conventions or complex configuration, you explicitly define the behavior of your pipeline using attributes, giving you full control over the execution order.

The Russian Doll Model

The Brighter pipeline is architectured around the Russian Doll (Matryoshka) Model. Imagine a set of nested dolls: each doll contains a smaller one inside it.

In Brighter, each middleware component (a “doll”) wraps the next one in the chain. A handler method is at the very center. When a request is handled, it passes through each middleware layer before reaching the handler, and then back out through each layer. This design perfectly implements the Pipe and Filter Pattern.

A key feature of this model is the ability to short-circuit the pipeline. Any middleware can stop the processing and return a result immediately, preventing the request from reaching the inner layers, including the handler itself.

This architecture allows any step in the pipeline to:

  1. Perform work before the request reaches the handler.
  2. Pass the request to the next step.
  3. Perform work after the inner handler returns.
  4. Short-circuit the pipeline by returning early (stopping the processing).

Note: While this guide uses synchronous handler examples, the same concepts apply to asynchronous handlers. Simply use the corresponding async attributes and handler base classes (e.g., RequestHandlerAsync).

Adding a Pipeline to Your Handler

In Brighter, adding middleware is done via C# Attributes. You must explicitly add the attribute to the Handle method of your Request Handler.

You control the order of execution using the step parameter. Lower numbers execute first (on the way in) and last (on the way out).

public class SampleHandler : RequestHandler<SomeMessage>
{
    // The step determines the order. 
    // Step 1 runs before Step 2.
    [RequestLogging(step: 0)]
    public virtual TRequest Handle(TRequest command)
    {
        ...
    }
}

Implementing “Global” Middleware

Brighter does not provide a fluent API (e.g., services.AddGlobalMiddleware(...)) to inject logic into every request globally. This is a deliberate design choice to keep the pipeline explicit at the handler level.

However, if you want “Global” middleware (logic that applies to all your handlers), you can achieve this through inheritance. By creating a Base Handler with the desired attributes, all inheriting handlers will adopt that pipeline.

Example: Creating a Base Handler with Global Policies

// 1. Create a base handler with common middleware
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)
    {
        // This call propagates the command down the pipeline to the actual handler.
        return base.Handle(command);
    }
}

// 2. Inherit from the base handler in your implementations
public class SampleHandler : MyGlobalPolicyHandler<SomeCommand>
{
    [UseResiliencePipeline("MyPolicy", step: 2)]
    public override SomeCommand Handle(SomeCommand command)
    {
        // Your core business logic here.
        Console.WriteLine("Handling SomeCommand");
        return base.Handle(command);
    }
}

The execution order for SampleHandler will be: RequestLogging -> FallbackPolicy -> UseResiliencePipeline -> SampleHandler.Handle.

Middleware Provided by Brighter

Brighter provides several useful middleware attributes out of the box:

  • RequestLoggingAttribute / RequestLoggingAsyncAttribute ->
    Logs the entry and exit of the handler pipeline, including timing and request details.
  • FallbackPolicyAttribute / FallbackPolicyAsyncAttribute -> Provides a fallback action if the handler execution fails. Calls a Fallback method on the handler.
  • TimeoutPolicyAttribute -> (Deprecated) Sets a maximum timeout for the handler. Prefer UseResiliencePipeline.
  • UsePolicyAttribute / UsePolicyAsyncAttribute -> (Deprecated) Wraps the handler in a Polly policy. Prefer UseResiliencePipeline.
  • UseResiliencePipelineAsyncAttribute / UseResiliencePipelineAttribute -> Wraps the handler in a Polly resilience pipeline for advanced retry, circuit-breaker, and timeout strategies.
  • MonitorAttribute / MonitorAsyncAttribute -> Allow you to monitor your handlers
  • FeatureSwitchAttribute / FeatureSwitchAsyncAttribute -> Dynamically enables or disables a handler based on a feature switch.

Implementing Your Own Middleware

To implement custom middleware, you generally need two classes per execution mode (sync or async):

  1. The Attribute: Used to decorate the handler method.
  2. The Handler: Contains the actual middleware logic.

Example Implementation:

// 1. The Attribute Class
[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()
    {
        // This tells Brighter which handler class to use for this attribute.
        return typeof(MyFeatureFlagHandler<>);
    }
}

// 2. The Handler Class
public class MyFeatureFlagHandler<TRequest> : RequestHandler<TRequest> where TRequest : class, IRequest
{
    private string _featureName;
    private IFeatureManager _featureManager; // Hypothetical feature manager

    public override void InitializeFromAttributeParams(params object[] initializerList)
    {
        // Receives the parameters from InitializerParams()
        _featureName = (string)initializerList[0];
        _featureManager = ...; // Would typically be injected via DI
    }

    public override TRequest Handle(TRequest command)
    {
        // Check if the feature is enabled
        if (!_featureManager.IsEnabled(_featureName))
        {
            return command;
        }

        // If it is enabled, continue to the next handler in the pipeline.
        return base.Handle(command);
    }
}

// 3. Using the Custom Middleware
public class DiscountHandler : RequestHandler<ApplyDiscountCommand>
{
    [MyFeatureFlag("AdvancedDiscounts", step: 1)]
    public override ApplyDiscountCommand Handle(ApplyDiscountCommand command)
    {
        // This will only execute if the "AdvancedDiscounts" feature is enabled.
        return base.Handle(command);
    }
}

Conclusion

Brighter’s explicit, attribute-based pipeline offers unparalleled clarity and control over your application’s cross-cutting concerns. By leveraging the Russian Doll model, you can build robust, well-ordered handler pipelines that are easy to reason about and maintain. Whether using built-in policies or creating your own custom middleware, the process remains consistent and transparent.

Reference

Official Documentation: Building a Pipeline of Request Handlers

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post

How Universal Shaft Machines are Redefining Shop Floor Measurement

Related Posts