Skip to content

Instantly share code, notes, and snippets.

@johneking
Created September 4, 2019 02:52
Show Gist options
  • Save johneking/3198d6ed9b169add8bb4d53978a86bf5 to your computer and use it in GitHub Desktop.
Save johneking/3198d6ed9b169add8bb4d53978a86bf5 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
using Xunit.Abstractions;
namespace Tests
{
public class PipelineTests
{
private readonly ITestOutputHelper _output;
public PipelineTests(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public void ResolveSingle()
{
var pipeline = PipelineExtensions.CreatePipeline<IService>(_output);
var service = pipeline.Resolve(new ResolutionRequest(typeof(string)), new ResolutionContext()).First();
_output.WriteLine($"Resolved service: {service.FinalInstance.Name}");
}
[Fact]
public void ResolveEnumerable()
{
var pipeline = PipelineExtensions.CreatePipeline<IService>(_output);
var services = pipeline.Resolve(new ResolutionRequest(typeof(string)), new ResolutionContext());
foreach (var resolutionResult in services)
{
_output.WriteLine($"Resolved service: {resolutionResult.FinalInstance.Name}");
}
}
[Fact]
public void ResolveEnumerableWithDecoration()
{
var pipeline = PipelineExtensions.CreatePipeline<IService>(_output);
pipeline = pipeline.AddDecoratorSection(s => new Decorator(s), _output);
var services = pipeline.Resolve(new ResolutionRequest(typeof(string)), new ResolutionContext());
foreach (var resolutionResult in services)
{
_output.WriteLine($"Resolved service: {resolutionResult.FinalInstance.Name}");
}
}
[Fact]
public void ResolveEnumerableWithComposition()
{
var pipeline = PipelineExtensions.CreatePipeline<IService>(_output);
pipeline = pipeline.AddCompositeSection(s => new Composite(s.ToList()), _output);
var services = pipeline.Resolve(new ResolutionRequest(typeof(string)), new ResolutionContext());
foreach (var resolutionResult in services)
{
_output.WriteLine($"Resolved service: {resolutionResult.FinalInstance.Name}");
}
}
[Fact]
public void ResolveEnumerableWithDecorationThenCompositionThenDecoration()
{
var pipeline = PipelineExtensions.CreatePipeline<IService>(_output);
pipeline = pipeline.AddDecoratorSection(s => new Decorator(s), _output);
pipeline = pipeline.AddCompositeSection(s => new Composite(s.ToList()), _output);
pipeline = pipeline.AddDecoratorSection(s => new Decorator(s), _output);
var services = pipeline.Resolve(new ResolutionRequest(typeof(string)), new ResolutionContext());
foreach (var resolutionResult in services)
{
_output.WriteLine($"Resolved service: {resolutionResult.FinalInstance.Name}");
}
}
[Fact]
public void ResolveCompositeWithFiltering()
{
var pipeline = PipelineExtensions.CreatePipeline<IService>(_output);
pipeline = pipeline.AddCompositeSection(s => new Composite(s.ToList()), _output, s=>!s.RootInstance.Name.Contains("b"));
var services = pipeline.Resolve(new ResolutionRequest(typeof(string)), new ResolutionContext());
foreach (var resolutionResult in services)
{
_output.WriteLine($"Resolved service: {resolutionResult.FinalInstance.Name}");
}
}
}
public static class PipelineExtensions
{
public static IResolutionPipelineSection<T> CreatePipeline<T>(ITestOutputHelper output) where T : ILogTest
{
return new ContainerPipelineSection<T>(output);
}
public static IResolutionPipelineSection<T> AddDecoratorSection<T>(
this IResolutionPipelineSection<T> innerSection,
Func<T, T> getDecoratorFunc,
ITestOutputHelper output,
Func<ResolutionResult<T>, bool> condition = null) where T : ILogTest
{
return new DecoratorPipelineSection<T>(innerSection, output, getDecoratorFunc, condition);
}
public static IResolutionPipelineSection<T> AddCompositeSection<T>(
this IResolutionPipelineSection<T> innerSection,
Func<IEnumerable<T>, T> getCompositeFunc,
ITestOutputHelper output,
Func<ResolutionResult<T>, bool> condition = null) where T : ILogTest
{
return new CompositePipelineSection<T>(innerSection, output, getCompositeFunc, condition);
}
}
public interface IResolutionPipelineSection<T> where T : ILogTest
{
IEnumerable<ResolutionResult<T>> Resolve(ResolutionRequest request, IResolutionContext<T> context);
}
// resolve from registrations, registration sources etc.
public class ContainerPipelineSection<T> : IResolutionPipelineSection<T> where T : ILogTest
{
private readonly ITestOutputHelper _output;
public ContainerPipelineSection(ITestOutputHelper output)
{
_output = output;
_output.WriteLine("Container pipeline section created");
}
public IEnumerable<ResolutionResult<T>> Resolve(ResolutionRequest request, IResolutionContext<T> context)
{
foreach (var resolutionResult in context.Resolve(request))
{
_output.WriteLine($"Resolved component {resolutionResult.FinalInstance.Name} from source");
yield return resolutionResult;
}
}
}
public class DecoratorPipelineSection<T> : IResolutionPipelineSection<T> where T : ILogTest
{
private readonly ITestOutputHelper _output;
private readonly IResolutionPipelineSection<T> _nextSection; // this is "next" structurally but gets resolved before
private readonly Func<ResolutionResult<T>, bool> _condition;
private readonly Func<T, T> _getDecoratorFunc;
public DecoratorPipelineSection(IResolutionPipelineSection<T> nextSection, ITestOutputHelper output, Func<T, T> getDecoratorFunc, Func<ResolutionResult<T>, bool> condition = null)
{
_nextSection = nextSection;
_output = output;
_getDecoratorFunc = getDecoratorFunc;
_condition = condition;
_output.WriteLine("Decorator pipeline section created");
}
public IEnumerable<ResolutionResult<T>> Resolve(ResolutionRequest request, IResolutionContext<T> context)
{
var inner = _nextSection.Resolve(request, context);
return inner.Select(r=> _condition==null || _condition(r) ? ApplyDecorator(r) : r);
}
private ResolutionResult<T> ApplyDecorator(ResolutionResult<T> innerResult)
{
var newInstance = _getDecoratorFunc(innerResult.FinalInstance);
_output.WriteLine($"Decorated '{innerResult.FinalInstance.Name}'");
innerResult.Update(newInstance);
return innerResult;
}
}
public class CompositePipelineSection<T> : IResolutionPipelineSection<T> where T : ILogTest
{
private readonly ITestOutputHelper _output;
private readonly IResolutionPipelineSection<T> _nextSection;
private readonly Func<ResolutionResult<T>, bool> _condition;
private readonly Func<IEnumerable<T>, T> _getCompositeFunc;
public CompositePipelineSection(IResolutionPipelineSection<T> nextSection, ITestOutputHelper output, Func<IEnumerable<T>, T> getCompositeFunc, Func<ResolutionResult<T>, bool> condition = null)
{
_nextSection = nextSection;
_output = output;
_getCompositeFunc = getCompositeFunc;
_condition = condition;
_output.WriteLine("Composite pipeline section created");
}
public IEnumerable<ResolutionResult<T>> Resolve(ResolutionRequest request, IResolutionContext<T> context)
{
return ApplyComposite(_nextSection.Resolve(request, context));
}
private IEnumerable<ResolutionResult<T>> ApplyComposite(IEnumerable<ResolutionResult<T>> innerResults)
{
// create composite and return, THEN return any additional registrations which were not included in the composite
var filtered = new List<ResolutionResult<T>>();
var composed = new List<T>();
foreach (var resolutionResult in innerResults)
{
if (_condition == null || _condition(resolutionResult))
composed.Add(resolutionResult.FinalInstance);
else
filtered.Add(resolutionResult);
}
var composite = _getCompositeFunc(composed);
_output.WriteLine($"Composed '{composite.Name}'");
yield return new ResolutionResult<T>(composite); // losing all history here for now although it's possible to compose that too
foreach (var resolutionResult in filtered)
{
_output.WriteLine($"Returning un-composed '{resolutionResult.FinalInstance.Name}'");
yield return resolutionResult;
}
}
}
public class ResolutionRequest
{
public ResolutionRequest(Type service)
{
Service = service;
}
public Type Service { get; }
}
public class Registration
{
public Registration(string name)
{
Name = name;
}
public string Name { get; }
}
public interface IResolutionContext<T>
{
IEnumerable<ResolutionResult<T>> Resolve(ResolutionRequest request);
}
public class ResolutionContext : IResolutionContext<IService> // a component registry + lifetime scope
{
public IEnumerable<ResolutionResult<IService>> Resolve(ResolutionRequest request)
{
var registrations = GetRegistrations(request);
foreach (var registration in registrations)
{
yield return ResolveComponent(registration);
}
}
private ResolutionResult<IService> ResolveComponent(Registration registration)
{
return new ResolutionResult<IService>( new ImplementorA("component-" + registration.Name));
}
private static readonly List<Registration> DummyStringRegistrations = new List<Registration>
{new Registration("a"), new Registration("b"), new Registration("c")};
private static readonly List<Registration> DummyIntRegistrations = new List<Registration>
{new Registration("1"), new Registration("2"), new Registration("3")};
private IEnumerable<Registration> GetRegistrations(ResolutionRequest request)
{
if (request.Service == typeof(string))
return DummyStringRegistrations;
if (request.Service == typeof(int))
return DummyIntRegistrations;
throw new ArgumentOutOfRangeException( nameof(request.Service),"Invalid request type");
}
}
public class ResolutionResult<T>
{
public ResolutionResult(T rootInstance)
{
RootInstance = rootInstance;
FinalInstance = rootInstance;
ResolutionHistory = new List<T> { rootInstance };
}
public T RootInstance { get; }
public T FinalInstance { get; private set; }
// running context
public List<T> ResolutionHistory { get; }
public void Update(T newResolutionResult)
{
FinalInstance = newResolutionResult;
ResolutionHistory.Add(newResolutionResult);
}
}
public interface ILogTest
{
string Name { get; }
}
public interface IService : ILogTest
{
IEnumerable<IService> InnerServices { get; }
}
public class Decorator : IService
{
private readonly IService _decorated;
public Decorator(IService decorated)
{
_decorated = decorated;
Name = $"Decorator for '{decorated.Name}'";
}
public IEnumerable<IService> InnerServices
{
get { yield return _decorated; }
}
public string Name { get; }
}
public class Composite : IService
{
private readonly List<IService> _composed;
public Composite(List<IService> composed)
{
_composed = composed;
Name = $"Composite for '{string.Join("|", composed.Select(c=>c.Name))}'";
}
public IEnumerable<IService> InnerServices => _composed;
public string Name { get; }
}
public class ImplementorA : IService
{
public ImplementorA(string name)
{
Name = name;
}
public string Name { get; }
public IEnumerable<IService> InnerServices => new List<IService>();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment