A simple dispatcher for .NET Core that uses Roslyn code generation to emit CQRS dispatcher code. Currently in extremely early stage, but I feel there's something to it.
I'm a great 'not-friend' of things like reflection, Activator and runtime type determination. When writing CQRS code, I would like to have my dispatcher code generated in compile time, with all optimalizations and other goodies that come with it.
To generate a dispatcher is as simple as this:
using DotnetDispatcher.Attributes;
using DotnetDispatcher.Core;
namespace ConsoleTester;
public record SampleQuery : IQuery<SampleQueryResponse>;
public record SampleQueryResponse(string Value);
[GenerateDispatcher(typeof(SampleQuery), typeof(SampleQueryHandler))]
public partial class MyAppDispatcher : DispatcherBase
{
public MyAppDispatcher(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
}
This will generate a dispatcher that looks like this:
namespace ConsoleTester
{
public partial interface IMyAppDispatcher
{
Task<SampleQueryResponse> Dispatch(SampleQuery unit, CancellationToken cancellationToken = default);
}
public partial class MyAppDispatcher : IMyAppDispatcher
{
public Task<SampleQueryResponse> Dispatch(SampleQuery unit, CancellationToken cancellationToken = default) =>
Get<IQueryHandler<SampleQuery, SampleQueryResponse>>().Query(unit, cancellationToken);
}
}
Aside from the Dispatch code, a matching interface is also generated. This allows for easy mocking of the dispatcher as well as using dependency injection. You can have multiple dispatchers in a project, if you want to. Alternatively, you can repeat the use of the GenerateDispatcher attribute with other types that implement IQuery<TResponse>
, ICommand<TResponse>
or ICommand
.
There are three nuget packages to install:
DotnetDispatcher.Core
- contains interfaces your commands and queries should implement, as well as the DispatcherBase
classDotnetDispatcher.Attributes
- contains the GenerateDispatcher
attributeDotnetDispatcher.Generator
- contains the code generatorAssuming you have an API project and a Domain project in your solution, where the generated dispatcher is part of the API project and the commands and queries are part of the Domain project:
DotnetDispatcher.Generator
and DotnetDispatcher.Attributes
packagesDotnetDispatcher.Core
packageThe actual implementation in the projects is as such:
public record SampleQuery : IQuery<SampleQueryResponse>;
public record SampleQueryResponse(string Value);
public class SampleQueryHandler : IQueryHandler<SampleQuery, SampleQueryResponse>
{
public Task
- The API project contains the dispatcher itself
```csharp
[GenerateDispatcher(typeof(SampleQuery), typeof(SampleQueryHandler))]
public partial class MyAppDispatcher : DispatcherBase
{
public MyAppDispatcher(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
}
This generates the dispatcher code as well as an IServiceCollection extension method that allows you to register the dispatcher in the DI container. You can use it in the API project like this:
var serviceCollection = new ServiceCollection();
serviceCollection.RegisterMyAppDispatcherAndHandlers();
var services = serviceCollection.BuildServiceProvider();
var appDispatcher = services.GetRequiredService<IMyAppDispatcher>();
var sampleQueryResponse = await appDispatcher.Dispatch(new SampleQuery(), CancellationToken.None);
Console.WriteLine(sampleQueryResponse.Value);