patterncsharpMinor
Injecting stereotypical role implementations by IoC container
Viewed 0 times
injectingimplementationsioccontainerrolestereotypical
Problem
AN UPDATED VERSION with GitHub link is avalable HERE.
I am trying to define a plug and play architecture which leverages stereotypical role implementations (Validator, Reader, Writer, Logger, Query, etc) by automatically instantiating associated services through a very limited use of Service Locator.
Demo code: let's say we have a query object to define some sequence of integers:
We also have couple services to be instantiated and invoked by inversion of control container. One to validate the query before execution:
And one more for actual sequence materialization:
This code should magically print
The following snippet reports
Autofac configuration used for the demo code above:
Here are the library interfaces and classes:
```
public interface IHandler
where TTask : Task
{
TTask Handle
I am trying to define a plug and play architecture which leverages stereotypical role implementations (Validator, Reader, Writer, Logger, Query, etc) by automatically instantiating associated services through a very limited use of Service Locator.
Demo code: let's say we have a query object to define some sequence of integers:
class IntRange : Query
{
public IntRange(int start, int count)
{
Start = start;
Count = count;
}
public int Start { get; }
public int Count { get; }
}We also have couple services to be instantiated and invoked by inversion of control container. One to validate the query before execution:
class RangeValidator : IValidator
{
public Task HandleAsync(IntRange subject) =>
subject.Count 10 ?
Task.FromException(new ArgumentOutOfRangeException()) :
Task.CompletedTask;
}And one more for actual sequence materialization:
class IntSequence : IReader
{
public Task> HandleAsync(IntRange subject) =>
Task.FromResult(
Enumerable.Range(subject.Start, subject.Count));
}This code should magically print
10 11 12 13 14 after successful validation with matching service instantiation and execution:foreach (var i in await new IntRange(10,5))
Console.WriteLine(i);The following snippet reports
ArgumentOutOfRangeException:try
{
await new IntRange(10, 100);
}
catch(Exception ex)
{
Console.WriteLine(ex);
}Autofac configuration used for the demo code above:
var builder = new ContainerBuilder();
builder.RegisterType().AsImplementedInterfaces();
builder.RegisterType().AsImplementedInterfaces();
var container = builder.Build();
var csl = new AutofacServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => csl);Here are the library interfaces and classes:
```
public interface IHandler
where TTask : Task
{
TTask Handle
Solution
I'm going to do my usual complaining about basic issues, things that as a good programmer you should never get away with. (I'm looking at you Dmitry and t3chb0t. >.>)
Come on man, get those explicit access modifiers in there! Label that thing
That's just ugly. I get wanting to use expression-bodied members, they're really cool, but this just looks bad. Let's rewrite that as a non-expression-bodied method and create some intermediate variables, because I don't like seeing
Back to this:
I don't like the name
Now I'm going to complain about this idea in general.
I wrote a lot of data access API and I have never been validating the same request differently :) It supposed to be used in, let's say, ASP.NET web api Controller action:
Here's the issue I find with this structure: if I want to change how things validate for an
Lazy>> Data { get; }Come on man, get those explicit access modifiers in there! Label that thing
private!public class Reader : IReader
{
public async Task> HandleAsync(TQuery subject) =>
(await Task.WhenAll(
ServiceLocator.Current
.GetAllInstances>()
.Select(reader => reader.HandleAsync(subject))))
.SelectMany(results => results);
}That's just ugly. I get wanting to use expression-bodied members, they're really cool, but this just looks bad. Let's rewrite that as a non-expression-bodied method and create some intermediate variables, because I don't like seeing
)))) anywhere in my code.Back to this:
Lazy>> Data { get; }I don't like the name
Data, I think Result would be a little more informative. Data acts like it'll be a more simple type, but you have to call Data.Value which just doesn't look right.Now I'm going to complain about this idea in general.
I wrote a lot of data access API and I have never been validating the same request differently :) It supposed to be used in, let's say, ASP.NET web api Controller action:
[HttpGet] public async Task Sequence(IntRange query) => await query;Here's the issue I find with this structure: if I want to change how things validate for an
IntRange then I have to create different classes that work off different validators, add them to the configuration and hope for the best. I really fail to see where this would save any time/effort, especially if I want to validate something like a UserRange where you cannot select an Id larger than the max user ID, I'd have to either add it to the constructor (which means load from the database each call) or add it to the Validator (which means load from the database, and I have no idea how many times that will happen, I'd hope Autofac would only use one instance of the Validator, but who knows. Certainly not I) and hope for the best. This just seems like a lot of overhead for largely simple tasks. Oh, and I have to register my Validator and Sequence with Autofac.Code Snippets
Lazy<Task<IEnumerable<TResult>>> Data { get; }public class Reader<TQuery, TResult> : IReader<TQuery, TResult>
{
public async Task<IEnumerable<TResult>> HandleAsync(TQuery subject) =>
(await Task.WhenAll(
ServiceLocator.Current
.GetAllInstances<IReader<TQuery, TResult>>()
.Select(reader => reader.HandleAsync(subject))))
.SelectMany(results => results);
}Lazy<Task<IEnumerable<TResult>>> Data { get; }Context
StackExchange Code Review Q#150833, answer score: 7
Revisions (0)
No revisions yet.