-
Notifications
You must be signed in to change notification settings - Fork 3
request handlers
A Request Handler is any class implementing the IRequestHandler
interface below:
public interface IRequestHandler<in TRequest>
{
HandlerResult Dispatch(TRequest request);
}
As you can see, it's a pretty simple interface consisting of a single Dispatch
method that takes a request model as a parameter and returns a HandlerResult
.
Typically you wouldn't implement IRequestHandler
directly, but would instead use one of the more specific interfaces that inherit from this:
public interface IQueryHandler<in TRequest, out TResponse> : IRequestHandler<TRequest>
{
}
public interface IQueryHandler<out TResponse> : IRequestHandler<Unit>
{
}
public interface ICommandHandler<in TRequest> : IRequestHandler<TRequest>
{
}
None of the interfaces above adds any functionality to IRequestHandler
- they simply make the purpose of the handler that you're building more explicit. Command handlers do things (typically writing data) but don't generally return any data. Query handlers read data but don't generally have any side effects - so they don't normally alter data. That's the theory at least. In practice sometimes you have to break these rules. For example, if you're working with a database that generates an incremental primary key/id for you then you might sometimes return this id after performing an insert. In strict CQRS terms an insert is a command and commands shouldn't return any result... but the world is full of compromises.
In any case, the easiest way to see how this works is just to see an example request handler:
public class IgnoreBob : IQueryHandler<SayHelloRequest, string>
{
public HandlerResult Dispatch(SayHelloRequest request)
{
return request.Name == "bob"
? new Handled<string>($"I don't want to talk to you { request.Name }")
: new NotHandled();
}
}
Here we have a query handler that can handle any SayHelloRequest
. If it handles the request then it returns a new Handled<string>
result. If it can't handle the request then it returns a new NotHandled
result.
Alternatively, you may find it easier to descend from one of the abstract QueryHandler
or CommandHandler
base classes that are provided, since these provide a couple of helper methods that will make your code a bit more fluent. Here's an example of the handler above that descends from the QueryHandler
base class rather than implementing IQueryHandler
directly:
public class IgnoreBob : QueryHandler<SayHelloRequest, string>
{
public override HandlerResult Dispatch(SayHelloRequest request)
{
return request.Name == "bob"
? Handled($"I don't want to talk to you { request.Name }")
: Continue();
}
}
It's almost the same, however it makes use of two convenience methods that are provided by the base class: Handled
and Continue
(since if the command is not handled it continues to the next handler in the pipeline).
The QueryHandler
and CommandHandler
base classes also provide a File
helper method that you can use to return Stream
results as follows:
public class DownloadLogo : QueryHandler<Stream>
{
public override HandlerResult Dispatch(Unit request)
{
// Code to prepare the file stream here...
return File(stream, "image/png", "command-routing.png");
}
}