Skip to content

Commit

Permalink
Immutable refactor (#2)
Browse files Browse the repository at this point in the history
* PipelineBuilder<T> is now immutable

* completed refactor

* Decided against continuing when TOut matches default(T) as this will not work for non-nullable value types

* Added CancellationToken to handler interfaces for future proofing. Rest of pipeline will be trickier
  • Loading branch information
ben-foster-cko authored Mar 17, 2018
1 parent fd6fcbb commit ae0c8ec
Show file tree
Hide file tree
Showing 25 changed files with 711 additions and 939 deletions.
96 changes: 86 additions & 10 deletions src/Flo/Builder.cs
Original file line number Diff line number Diff line change
@@ -1,39 +1,115 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Flo
{
/// <summary>
/// Provides linking and building functionality for chain of responsibility pipelines
/// </summary>
public abstract class Builder<TInput, TOutput>
public abstract class Builder<TIn, TOut, TBuilder>
where TBuilder : Builder<TIn, TOut, TBuilder>
{
private readonly List<Func<Func<TInput, TOutput>, Func<TInput, TOutput>>> _handlers
= new List<Func<Func<TInput, TOutput>, Func<TInput, TOutput>>>();
private readonly TBuilder _builderInstance;
private readonly List<Func<TIn, Func<TIn, Task<TOut>>, Task<TOut>>> _handlers
= new List<Func<TIn, Func<TIn, Task<TOut>>, Task<TOut>>>();

protected Func<TInput, TOutput> InnerHandler { get; set; }
protected Func<Type, object> ServiceProvider { get; set; }
protected Func<TIn, Task<TOut>> InnerHandler { get; set; }

public Builder(Func<TInput, TOutput> innerHandler)
public Builder(Func<TIn, Task<TOut>> innerHandler, Func<Type, object> serviceProvider)
{
InnerHandler = innerHandler ?? throw new ArgumentNullException(nameof(innerHandler));
ServiceProvider = serviceProvider ?? DefaultServiceProvider;

_builderInstance = (TBuilder)this;
}

public TBuilder Fork(
Func<TIn, bool> predicate,
Action<TBuilder> configurePipeline)
{
return When(predicate, async (input, innerPipeline, next) =>
{
return await innerPipeline.Invoke(input);
},
configurePipeline);
}

public TBuilder When(
Func<TIn, bool> predicate,
Func<TIn, Func<TIn, Task<TOut>>, Func<TIn, Task<TOut>>, Task<TOut>> handler,
Action<TBuilder> configurePipeline)
{
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
if (handler == null) throw new ArgumentNullException(nameof(handler));
if (configurePipeline == null) throw new ArgumentNullException(nameof(configurePipeline));

return Add((input, next) =>
{
if (predicate.Invoke(input))
{
var builder = CreateBuilder();
configurePipeline(builder);
return handler.Invoke(input, builder.Build(), next);
}

return next.Invoke(input);
});
}

protected abstract TBuilder CreateBuilder();

public TBuilder Final(Func<TIn, Task<TOut>> handler)
{
if (handler == null) throw new ArgumentNullException(nameof(handler));
return Add((input, next) => handler.Invoke(input));
}

public TBuilder Add<THandler>() where THandler : class, IHandler<TIn, TOut>
{
Func<THandler> handlerFactory = () => ServiceProvider.Invoke(typeof(THandler)) as THandler;
return Add(handlerFactory);
}

protected virtual void AddHandler(Func<TInput, Func<TInput, TOutput>, TOutput> handler)
public TBuilder Add(Func<IHandler<TIn, TOut>> handlerFactory)
{
if (handlerFactory == null) throw new ArgumentNullException(nameof(handlerFactory));

return Add((input, next) =>
{
var handler = handlerFactory.Invoke();

if (handler != null)
{
return handler.HandleAsync(input, next, CancellationToken.None);
}

return next.Invoke(input);
});
}

public TBuilder Add(Func<TIn, Func<TIn, Task<TOut>>, Task<TOut>> handler)
{
if (handler == null) throw new ArgumentNullException(nameof(handler));
_handlers.Add(next => input => handler.Invoke(input, next));
_handlers.Add(handler);
return _builderInstance;
}

public virtual Func<TInput, TOutput> Build()
public virtual Func<TIn, Task<TOut>> Build()
{
Func<TInput, TOutput> pipeline = InnerHandler;
Func<TIn, Task<TOut>> pipeline = InnerHandler;
for (int i = _handlers.Count - 1; i >= 0; i--)
{
var handler = _handlers[i];
pipeline = handler.Invoke(pipeline);
var prev = pipeline;
pipeline = input => handler.Invoke(input, prev);
}

return pipeline;
}

private static object DefaultServiceProvider(Type type) => Activator.CreateInstance(type);
}
}
9 changes: 7 additions & 2 deletions src/Flo/IHandler.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Flo
{
public interface IHandler<TInput>
public interface IHandler<TIn, TOut>
{
Task<TOut> HandleAsync(TIn input, Func<TIn, Task<TOut>> next, CancellationToken token);
}

public interface IHandler<T> : IHandler<T, T>
{
Task HandleAsync(TInput input, Func<TInput, Task> next);
}
}
10 changes: 0 additions & 10 deletions src/Flo/IOutputHandler.cs

This file was deleted.

21 changes: 0 additions & 21 deletions src/Flo/IProjectingHandler.cs

This file was deleted.

21 changes: 0 additions & 21 deletions src/Flo/IProjectingOutputHandler.cs

This file was deleted.

36 changes: 36 additions & 0 deletions src/Flo/OutputPipelineBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Threading.Tasks;

namespace Flo
{
public class OutputPipelineBuilder<TIn, TOut>
: Builder<TIn, TOut, OutputPipelineBuilder<TIn, TOut>>
{
private static Func<TOut, bool> DefaultContinuation = output => output == null;

public OutputPipelineBuilder(Func<Type, object> serviceProvider = null)
: base(input => Task.FromResult(default(TOut)), serviceProvider)
{
}

public OutputPipelineBuilder<TIn, TOut> When(
Func<TIn, bool> predicate,
Action<OutputPipelineBuilder<TIn, TOut>> configurePipeline,
Func<TOut, bool> continueIf = null)
{
return When(predicate, async (input, innerPipeline, next) =>
{
var result = await innerPipeline.Invoke(input);

// Continue on to the parent pipeline if the continueIf output predicate matches
if ((continueIf ?? DefaultContinuation).Invoke(result))
return await next.Invoke(input);

return result;
},
configurePipeline);
}

protected override OutputPipelineBuilder<TIn, TOut> CreateBuilder() => new OutputPipelineBuilder<TIn, TOut>(ServiceProvider);
}
}
12 changes: 6 additions & 6 deletions src/Flo/Pipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ namespace Flo
{
public class Pipeline
{
public static Func<TInput, Task> Build<TInput>(
Action<PipelineBuilder<TInput>> configurePipeline,
public static Func<T, Task<T>> Build<T>(
Action<PipelineBuilder<T>> configurePipeline,
Func<Type, object> serviceProvider = null)
{
var pipelineBuilder = new PipelineBuilder<TInput>(serviceProvider);
var pipelineBuilder = new PipelineBuilder<T>(serviceProvider);
configurePipeline(pipelineBuilder);
return pipelineBuilder.Build();
}

public static Func<TInput, Task<TOutput>> Build<TInput, TOutput>(
Action<PipelineBuilder<TInput, TOutput>> configurePipeline,
public static Func<TIn, Task<TOut>> Build<TIn, TOut>(
Action<OutputPipelineBuilder<TIn, TOut>> configurePipeline,
Func<Type, object> serviceProvider = null)
{
var pipelineBuilder = new PipelineBuilder<TInput, TOutput>(serviceProvider);
var pipelineBuilder = new OutputPipelineBuilder<TIn, TOut>(serviceProvider);
configurePipeline(pipelineBuilder);
return pipelineBuilder.Build();
}
Expand Down
Loading

0 comments on commit ae0c8ec

Please sign in to comment.