-
-
Save johneking/3198d6ed9b169add8bb4d53978a86bf5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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