Pipeline

Procesa datos a través de una secuencia ordenada de pasos (stages) que transforman su entrada en salida.

Contexto

Una request, un mensaje o un dato debe atravesar varias transformaciones: parsear → validar → enriquecer → guardar → notificar. Quieres que cada paso sea independiente y reordenable.

Problema
  • Una función monolítica con 8 responsabilidades es difícil de testear.
  • Reordenar pasos implica reescribir el flujo.
Solución

Define una interfaz común para cada stage y compón el pipeline conectándolos. Cada stage hace una sola cosa y pasa el resultado al siguiente.

#Estructura

  • IPipelineStep<TIn, TOut> — contrato genérico.
  • Pipeline<T> — encadena pasos compatibles.
  • Cada paso es testeable en aislamiento.

#Ejemplo en C# — pipeline genérico

public interface IPipelineStep<in TIn, out TOut>
{
    TOut Process(TIn input);
}

public class Pipeline<TIn, TOut>
{
    private readonly Func<TIn, TOut> _run;
    private Pipeline(Func<TIn, TOut> run) => _run = run;

    public static Pipeline<T, T> Start<T>() => new(x => x);

    public Pipeline<TIn, TNext> Then<TNext>(IPipelineStep<TOut, TNext> next) =>
        new(x => next.Process(_run(x)));

    public TOut Run(TIn input) => _run(input);
}

// Stages
public class Trim     : IPipelineStep<string, string>      { public string Process(string s)   => s.Trim(); }
public class ToUpper  : IPipelineStep<string, string>      { public string Process(string s)   => s.ToUpperInvariant(); }
public class Hash     : IPipelineStep<string, byte[]>      { public byte[] Process(string s)   => System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(s)); }

// Composición
var pipe = Pipeline<string, string>.Start<string>()
    .Then(new Trim())
    .Then(new ToUpper())
    .Then(new Hash());

byte[] digest = pipe.Run("  hola  ");

#Ejemplo en C# — pipeline asíncrono (estilo middleware)

public delegate Task PipelineDelegate<TContext>(TContext ctx, Func<Task> next);

public class AsyncPipeline<TContext>
{
    private readonly List<PipelineDelegate<TContext>> _steps = new();
    public AsyncPipeline<TContext> Use(PipelineDelegate<TContext> step) { _steps.Add(step); return this; }

    public Task RunAsync(TContext ctx)
    {
        Func<Task> next = () => Task.CompletedTask;
        for (int i = _steps.Count - 1; i >= 0; i--)
        {
            var step = _steps[i];
            var current = next;
            next = () => step(ctx, current);
        }
        return next();
    }
}

Es exactamente la idea del middleware de ASP.NET Core y de MediatR pipeline behaviors.

Cuándo NO aplicarlo
  • Cuando el flujo tiene mucho branching: Pipeline brilla cuando es lineal.
  • Cuando hay alto acoplamiento entre pasos (necesitan compartir estado interno).
Tradeoffs
Pro Contra
Cada paso es SRP y testeable Saltos en el código siguen el flujo
Reordenable y reutilizable Tipos genéricos pueden volverse complejos
Encaja con paralelismo / streaming Errores deben propagarse explícitamente

#Variantes

  • Pipes & Filters clásico (orientado a streams).
  • Middleware pipeline (con next() — estilo ASP.NET).
  • Channel-based pipeline (con System.Threading.Channels).

#extra #pipeline #data-flow