Previous version is here. Sources are available on GitHub.I am trying to define a plug and play CQRS 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 codeLet's have something primitive implemented for the sake of simplicity - a query to define some sequence of integers:class IntRange : Query<int>{    public IntRange(int start, int count)    {        Start = start;        Count = count;    }    public int Start { get; }    public int Count { get; }}We also have some services to be instantiated and invoked by inversion of control container. One to validate the query before execution:class RangeValidator : IValidator<IntRange>{    public Task ValidateAsync(IntRange subject) =>        subject.Count < 0 || subject.Count > 10 ?            Task.FromException(new ArgumentOutOfRangeException()) :            Task.CompletedTask;}And one more for actual sequence materialization:class IntSequence : IReader<IntRange, int>{    public Task<IEnumerable<int>> ReadAsync(IntRange subject) =>        Task.FromResult(            Enumerable.Range(subject.Start, subject.Count));}Logging:class RangeLogger : ILogger<IntRange>{    public Task LogAsync(IntRange e) =>        Console.Out.WriteLineAsync($Time taken {e.Taken});        }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);        }Queries could be model-binded in ASP.NET MVC (No way to make it shorter! :)public async Task<IEnumerable<int>> GetSequence(IntRange query) =>     await query;Autofac configuration used for the demo code above:        var builder = new ContainerBuilder();        builder.RegisterType<IntSequence>().AsImplementedInterfaces();        builder.RegisterType<RangeValidator>().AsImplementedInterfaces();        builder.RegisterType<RangeLogger>().AsImplementedInterfaces();        var container = builder.Build();        var csl = new AutofacServiceLocator(container);        ServiceLocator.SetLocatorProvider(() => csl);Here are the library interfaces and classes:public abstract class Event{    public Exception Error { get; protected set; }    public TimeSpan Taken { get; protected set; }}  Request aka external event:public abstract class Request : Event{}And the Query:public abstract class Query<TResult> : Request{    protected Query()    {        Data = new Lazy<Task<IEnumerable<TResult>>>(ExecuteAsync);    }    public TaskAwaiter<IEnumerable<TResult>> GetAwaiter() =>        Data.Value.GetAwaiter();    Lazy<Task<IEnumerable<TResult>>> Data { get; }    protected virtual async Task<IEnumerable<TResult>> ExecuteAsync()    {        var sw = Stopwatch.StartNew();        try        {            await this.ValidateAsync();            return await this.ReadAsync();        }        catch(Exception ex)        {            Error = ex;            throw;        }        finally        {            Taken = sw.Elapsed;            await this.LogAsync();        }    }}Which uses the following services and helpers:public interface IValidator<in TRequest>    where TRequest : Request{    Task ValidateAsync(TRequest request);}With helper:static class Validator{    // Invoke all matching registered validators    public static Task ValidateAsync(this Request request) =>        Task.WhenAll(            ServiceLocator.Current                .GetAllInstances(typeof(IValidator<>)                    .MakeGenericType(request.GetType()))                .Select(validator =>                    (Task)validator                        .GetType()                        .GetMethod(ValidateAsync, new[] { request.GetType() })                        .Invoke(validator, new[] { request })));}Readers supposed to implement:public interface IReader<in TQuery, TResult>    where TQuery : Query<TResult>{    Task<IEnumerable<TResult>> ReadAsync(TQuery query);}Where helper is:static class Reader{    // Invoke all matching registered readers and concatenate resultsets    public async static Task<IEnumerable<TResult>> ReadAsync<TResult>(this Query<TResult> query) =>        (await Task.WhenAll(            ServiceLocator.Current                .GetAllInstances(typeof(IReader<,>)                    .MakeGenericType(query.GetType(), typeof(TResult)))                .Select(reader =>                    (Task<IEnumerable<TResult>>)reader                        .GetType()                        .GetMethod(ReadAsync, new[] { query.GetType() })                        .Invoke(reader, new[] { query }))))        .SelectMany(rr => rr)        .ToArray();}And the last one for logging:public interface ILogger<in TEvent>    where TEvent : Event{    Task LogAsync(TEvent e);}With a helper:static class Logger{    // Invoke all matching registered loggers    public static Task LogAsync(this Event e) =>        Task.WhenAll(            ServiceLocator.Current                .GetAllInstances(typeof(ILogger<>)                    .MakeGenericType(e.GetType()))                .Select(logger =>                     (Task)logger                        .GetType()                        .GetMethod(LogAsync, new[] { e.GetType() })                        .Invoke(logger, new[] { e })));}PROS:Reusability (minimum amount of code for query invocation)Extensibility (easy to add more handling services)Traceability (ExecuteAsync defines a central point to add logging)Query objects are serializable/model-bindable DTORequired dependencies are injectable to handling services by an IoC container.Testable through LocatorService mocking (empty locator provides a reasonable default behavior).CONS:It is a ServiceLocator.
Injecting stereotypical role implementations by IoC container - follow-up
c#;design patterns
After studying the code for a while... I am not sure if that is genial or absolutely over engineered ;)Probably because I can't imagine real-world use cases for that framework. So, I'll try to solve a more real-world problem to see how it works:Problem:Loading lookup values form a database:There are lots of lookup values in the database (Table: LookupValues, Columns: Type (string), Key (int), Value (string))All lookup values with the same Type belongs togetherEach lookup value is a key value pairThe number of loaded lockup values should be loggedAfter loading the lookup values, it should be validated that the value properties are not empty.Solution:First of all, lets assume that the database access is abstracted by the following interface:public interface IDatabase{    Task<KeyValuePair<int, string>[]> LoadLookupValues(string lookupType);}Lets start with the query:class LookupValueQuery : Query<KeyValuePair<int, string>>{    public LookupValueQuery(string lookupType)    {        LookupType = lookupType;    }    public string LookupType { get; }}and the reader:class LookupValueReader : IReader<LookupValueQuery, KeyValuePair<int, string>>{    private readonly IDatabase myDatabase;    public LookupValueReader(IDatabase database)    {        myDatabase = database;    }    public Task<IEnumerable<KeyValuePair<int, string>>> ReadAsync(LookupValueQuery subject)         => myDatabase.LoadLookupValues(subject.LookupType);}The usage for the query would be:foreach (var lookupValue in await new LookupValueQuery(MyFavoriteLookupValues))    Console.WriteLine($Key: {pair.Key}; Value: {pair.Value});That looks quite simple so far.Ok, lets add the loggerclass LookupValueLogger : ILogger<LookupValueQuery>{    public Task LogAsync(LookupValueQuery e) =>        Console.Out.WriteLineAsync($Loaded lookup values of type '{e.LookupType}' in {e.Taken.TotalMilliseconds} ms);}It is not possible to log information about the result.Lets continue with the validator:class LookupValueValidator : IValidator<LookupValueQuery>{    public Task ValidateAsync(LookupValueQuery subject) =>        String.IsNullOrEmpty(subject.LookupType) ?            Task.FromException(new ArgumentException(LookupType must not be null or empty)) :            Task.CompletedTask;}It is only possible to validate the query, not the result.Of course, it is possible to overwrite the query's ExecuteAsync method, and extend the API, but that is far away from being a simple usage.Pros:I like, that the usage of the query is independent of the query execution logic. Just create a query object and it will be 'magically' executed. Same for the implementation. The query knows only its parameters but nothing related to the execution (at least from the view of the query implementer)Cons:While IMHO it is reasonable to splitting the functionality of the reader and the query in different classes, I don't see the benefit of spreading the rest of the functionality (logging and validation) in multiple classes in general. Logging can be done by the reader implementation if required.The query is responsible for validating it's arguments - there is no separate class required for that.It is NOT easy to extend if you have specific use cases!Like @Nikita B, I don't like service locators because they hide dependencies. Otherwise, I can not imagine another way to execute a query that way...There is a dependency to a concrete DI framework.ConclusionI think, at the end of the day, I would prefer a conventional DI approach to your nice query API.Instead offoreach (var element in new Query(/*parameters*/)    // process elementI would just useIReader<Query> reader = // injected by DI (with ANY DI framework and without ServiceLocator)foreach (var element in reader.Execute(new Query(/*parameters*/))    // process element   