As a continuation of the "plug-and-play architecture" series we need to prepare a small Mediator pattern-based CQRS architecture.
First we need to wrap any result that a command or a query can return
namespace PlugAndPlayExample.Services.Infrastructure
{
public class Response
{
public Response()
{
Exceptions = new List<Exception>();
}
public List<Exception> Exceptions { get; set; }
}
public class Response<T> : Response
{
public T Result { get; set; }
public static implicit operator Response<T>(T result)
{
return new Response<T> { Result = result };
}
}
}
Second, we need to create the command and query handler interfaces
namespace PlugAndPlayExample.Services.Infrastructure
{
public interface ICommand
{
}
public interface ICommandHandler<TCommand, TResult>
where TCommand: ICommand
where TResult : Response
{
TResult Handle(TCommand command);
}
public interface IQuery
{
}
public interface IQueryHandler<TQuery, TResult>
where TQuery : IQuery
where TResult : Response
{
TResult Handle(TQuery query);
}
}
We need a mediator that will dispatch these requests to their appropriate handlers
namespace PlugAndPlayExample.Services.Infrastructure
{
public class Mediator
{
private readonly IServiceProvider serviceProvider;
public Mediator(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public TResult Dispatch<TCommand, TResult>(TCommand command)
where TCommand : ICommand
where TResult : Response
{
var commandHandler = serviceProvider.GetService(typeof(ICommandHandler<TCommand, TResult>)) as ICommandHandler<TCommand, TResult>;
return commandHandler.Handle(command);
}
public TResult Get<TQuery, TResult>(TQuery query)
where TQuery : IQuery
where TResult : Response
{
var queryHandler = serviceProvider.GetService(typeof(IQueryHandler<TQuery, TResult>)) as IQueryHandler<TQuery, TResult>;
return queryHandler.Handle(query);
}
}
}
And register everything in the startup
namespace PlugAndPlayExample.Configuration
{
public static class RegisterServicesExtension
{
public static IServiceCollection RegisterServices(this IServiceCollection services)
{
services.AddSingleton<Mediator>();
var queryHandlerType = typeof(IQueryHandler<,>);
var commandHandlerType = typeof(ICommandHandler<,>);
RegisterOfType(services, queryHandlerType);
RegisterOfType(services, commandHandlerType);
return services;
}
private static void RegisterOfType(IServiceCollection services, Type type)
{
var exportedTypes = typeof(Mediator).Assembly.GetExportedTypes();
var result = exportedTypes.Where(x => x
.GetInterfaces()
.Any(i => i.IsGenericType
&& i.GetGenericTypeDefinition() == type)
&& x.IsClass
&& !x.IsAbstract)
.ToList();
result.ForEach(handler =>
{
var handlerType = handler;
var serviceType = handler
.GetInterfaces()
.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == type);
services.AddTransient(serviceType, handlerType);
});
}
}
}
namespace PlugAndPlayExample
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.RegisterServices();
}
// Configure code ...
}
}
With there few infrastructure preparations we are ready to start implementing the plug-and-play architecture.
Until next time...
Happy coding,
DotNetGuru