This example is loosely based on how the API looks for aggregates in Axon Framework.
We also base some of the code around examples of ValidationBehavior
found online.
The reason why I’ve implemented these examples is in order to showcase how you can cut down on some of the ceremony around using a generic mediator interface implementation together with fluent validation logic (pipeline behavior in MediatR parlance).
Note that I’m not making a value judgement around if you should use MediatR or not, if you should use AOP or not.
We assume that we want to use some validation API (here we use FluentValidation).
We assume that the domain have most of the business code in the entities.
We assume that we want to use MediatR (you could substitute MediatR with a message queue abstraction).
We assume that we use some sort of unit of work (if we use EF Core that would be DbContext, while it would be session if we use NHibernate).
First step is to create the classes that will implement the MediatR IRequestHandler:
FuncCreateCommandHandler
FuncMutateCommandHandler
You could implement these classes in the following way:
class FuncMutateCommandHandler<TEntity, TCommand, TResponse> : IRequestHandler<TCommand, TResponse>
where TCommand : ICommand<TResponse> where TEntity : IEntity
{
private readonly Func<TEntity, TCommand, IServiceProvider, TResponse> _func;
private readonly IServiceProvider _serviceProvider;
public FuncMutateCommandHandler(Func<TEntity, TCommand, IServiceProvider, TResponse> func, IServiceProvider serviceProvider)
{
_func = func;
_serviceProvider = serviceProvider;
}
public async Task<TResponse> Handle(TCommand cmd, CancellationToken cancellationToken)
{
var repository = _serviceProvider.GetRequiredService<IRepository<TEntity>>();
var keyValueFactory = _serviceProvider.GetRequiredService<IKeyValueFactory<TCommand>>();
var entity = await repository.FindAsync(keyValueFactory.Key(cmd));
var r = _func(entity, cmd, _serviceProvider);
return r;
}
}
Note that the signature of the delegate is:
Func<TEntity, TCommand, IServiceProvider, TResponse>
The base responsibility for these classes will be to retrieve and store the business entities as well as provide an opportunity to inject validation logic (since we implement a generic interface).
Since they take a func it’s trivial to create a fluent API in order to register how the entities :
Given_fluent_registration_of_handlers
The setup then becomes the description of how we tie together the business entities (Person).
services.RegisterHandlersFor<Person>()
.UpdateCommandOnEntity<EditPersonCommand,Person>((entity, cmd, svc) => entity.Handle(cmd, svc))
.CreateCommandOnEntity<CreatePersonCommand>(Person.Create)
Since we have the initial classes needed to write automatically created registrations based on attributes we can now create our first such implementation.
We note that what we need to achieve is to write code that registers the above handlers. Since we want to be efficient we write a Linq Expression that essentially does:
new FuncMutateCommandHandler<TEntity,TCommand,TResult>((entity, cmd, svcProvider) =>
entity.`MethodInfo`(cmd, svcProvider), outerSvdProvider)
This code is not too difficult to write. See the implementation.
We have a solution that deals with IServiceProvider
and a lambda. Since given a service provider we can always get a service, this essentially means that we have the needed building blocks to adjust the solution into taking multiple services.
Since we have the building blocks we need to find places where we have registrations that matches the pattern:
[CommandHandler]
public ... Handle(EditBookingCommand cmd, IAService services, ...) ...
In order to find such we can write some simple reflection based code (in the example we scan for instance methods that does not take service provider as a parameter).
Then the heavy lifting is to write an expression that does:
new FuncMutateCommandHandler<TEntity,TCommand, TResult>((entity, cmd, svcProvider) => entity.`MethodInfo`(cmd,
svcProvider.GetRequiredService<IAService>()
), outersvcProvider)
See the implementation.
I’ve talked to some coworkers that have expressed a feeling that there is too much magic going on with a decomposed solution that uses generic interfaces. This is similar to the criticism around AOP.
We have around 400 lines of infrastructure glue code to tie the code together. The main difficulty in the implementation is around writing Linq Expressions. The reflection code can be seen as similar to how you would write queries against a database, so might not be so far fetched. The Linq Expression code is somewhat abstract, that might be more difficult to understand.
If you want to write C# such that you find MediatR useful, then the complexity could be hidden as library or framework code. You cut down on a lot of copy paste and ceremony for implementing command handlers.
Do you want to send a comment or give me a hint about any issues with a blog post: Open up an issue on GitHub.
Do you want to fix an error or add a comment published on the blog? You can do a fork of this post and do a pull request on github.
Comments