Skip to content
This repository has been archived by the owner on Jul 13, 2024. It is now read-only.
Eduardo Cabral edited this page Sep 25, 2023 · 26 revisions

PipelineRD is a library that implements the chain of responsability pattern and brings to you many features to make your code more resilient, readable and easier to maintain. Supports netstandard2.0 and netstandard2.1.

This library is an improved version of the PipelineR, refactored and better to use it.

Setup

Install the package via NuGet first: Install-Package PipelineRD

PipelineRD has some dependencies such as:

  • Polly: used to implement the retry policy to the steps.
  • Serilog: to log a possible pipeline error.

PipelineRD has only one dependency:

  • Fluent Validation: to add a validator to the pipeline and use it to implement the fail fast principle.

You will need to configure the pipeline using the extension method IServiceCollection.UsePipelineRD. It's action methods allow you to register the type of cache that you will use, the dependency injection of all pipeline services and the documentation that will be generated after each run.

Initial configuration

public void ConfigureServices(IServiceCollection services) 
{
  services.AddMvc();

  services.UsePipelineRD(x => 
  {
      // ...
  });
}

Inner cache configuration

You can use two types of cache, in memory and redis. Both cache settings contains a TTLInMinutes property that will hold the value to how many minutes the cache will get hold in place and a KeyPreffix that will be used to preffix the cache data key. The redis settings contains a connection string property that will be used to connect.

For now, the usage of cache is obrigatory, because the pipeline uses it to go back to a previous state based on a hash that is generated by the json serialized request concatenated with the name of the pipeline.

services.UsePipelineRD(x => 
{
    x.SetupCache(new PipelineRDCacheSettings()
    {
        KeyPreffix = "pipelinerdsample",
        TTLInMinutes = 5
    });
});

Inner pipeline services configuration

If you prefer, the library contains a built in automatic service injection to the .NET dependency container. You can choose individually which service type to inject. Of course, you can choose to inject all of them at the same time.

All services will be injected with the lifetime scoped, but the validators are singleton.

services.UsePipelineRD(x => 
{
    x.AddPipelineServices(x =>
    {
        x.InjectContexts();
        x.InjectHandlers();
        x.InjectPipelines();
        // Or all
        x.InjectAll();
    });
});

Usage

Pipeline Initialization

You can either use by dependency injection IPipeline or directly instanciation Pipeline. The way you build the pipeline is using fluent methods.

The available methods are:

WithHandler<Handler> or WithHandler(Handler)

Method that will add a new handler in to the pipeline. It will handle a step that implements the inherits the class Handler<,>.

When(x: BaseContext => {})

Method that proceeds the method WithHandler. It will receive a lambda that handles the context object of the pipeline and you can create conditions to execute the handler defined previously.

Execute(model)

Method that will start the execution of the pipeline. It receives a object that can be of any type and will be used by the pipeline step as the main model.

Examples

Simple one step pipeline initialization

var result = await Pipeline
    .WithHandler<CustomHandler>()
    .Execute(model);

Multiple steps pipeline initialization

var result = await Pipeline
    .WithHandler<CustomOneHandler>()
    .WithHandler<CustomTwoHandler>()
    .Execute(model);

Multiple conditional steps pipeline initialization

var result = await Pipeline
    .WithHandler<CustomOneHandler>()
    .WithHandler<CustomTwoHandler>()
      .When(b => b.Id == "testOne")
    .WithHandler<CustomTwoHandler>()
      .When(b => b.Id == "testTwo")
    .Execute(model);

Context

It is a shared model between the pipeline steps. You can use to share variables. It needs to implement the abstract class BaseContext.

Steps

You can define a handler by inheriting the classe Handler<TContext, TRequest>.

The available base methods are:

Proceed()

Method that handles the advance of the pipeline. It will be used when you want to proceed to the next handler.

Abort(HandlerError)

Method that handles the abortion of the pipeline. It will be used when you want to abort the execution. It is used together with the return keyword and receives the object HandlerError.

Finish(result, statusCode)

Method that handles the ending of the pipeline. It will be used when you want to end the execution and return a result. It is used together with the return keyword.

Examples

Simple sync request step

public class SimpleCustomHandler : Handler<CustomContext, RequestModel>
{
    public override Task<HandlerResult> Handle(RequestModel request)
    {
        Console.WriteLine("SimpleCustomHandler ");
        // ...
        return Proceed();
    }
}

Request step with abort

public class SimpleCustomHandler : Handler<CustomContext, RequestModel>
{
    public override Task<HandlerResult> Handle(RequestModel request)
    {
        Console.WriteLine("SimpleCustomHandler ");
        // ...

        if(model.type == "inactive") 
        {
            var error = new HandlerError("Inactive");
            return Abort(error, HttpStatusCode.BadRequest);
        }

        return Proceed();
    }
}

Request step with finish

public class SimpleCustomHandler :  : Handler<CustomContext, RequestModel>
{
    public override Task<HandlerResult> Handle(RequestModel request)
    {
        Console.WriteLine("SimpleCustomHandler");
        // ...

        if (model.type == "inactive") 
        {
            var error = new HandlerError("Inactive");
            return Abort(error, HttpStatusCode.BadRequest);
        }
        
        var result = new CustomResultModel();
        return Finish(result, HttpStatusCode.OK);
    }
}