PMQ.Mediator
977 downloads · repository
A mediator for .NET with a behavior pipeline, validation integration, streaming and notification publishing. 37 files, 2,154 lines.
The problem
In a service with many use cases, the controller becomes a dispatcher: it injects five dependencies, validates, calls a service, handles errors. The mediator takes that out of the way: the controller hands over a command and receives a result, and everything that happens in between is composition.
The decision that ties the suite together
Validation, logging and transactions are not the handler’s responsibility: they are stages that wrap any handler. What makes this pipeline different is what it does when validation fails:
// Try to use the custom failure handler if registered
var failureHandler = serviceProvider.GetService<IValidationFailureHandler<TResponse>>();
if (failureHandler is not null)
return failureHandler.HandleFailure(failures);
// If no custom handler, throw a ValidationException
throw new ValidationException(failures);
If there is a registered handler, the validation failure becomes a return value; if there is not, it falls back to an exception. It is the point where PMQ.Notifications fits in: whoever adopts the whole suite registers the handler and the violated rule never becomes an exception; whoever uses only the mediator keeps the idiomatic .NET behavior.
That is how the weakness accepted in PMQ.Notifications, in which nothing forces the caller to check, ended up solved structurally. The pipeline checks on everyone’s behalf, before the handler exists.
Other decisions
Validator resolved per request, not at startup. If there is no validator registered for that command, the pipeline goes straight through. It costs one container lookup and avoids forcing every command to have a validator just so the pipeline does not break.
Validators run in parallel with Task.WhenAll, and the errors are concatenated. Serial would
return the first set of errors; parallel returns all of them, which is the same reason the suite
exists.
Streaming as a separate abstraction, with IStreamRequest and IStreamPipelineBehavior,
because a single response and a sequence have different lifecycles, and forcing both into the same
interface produces a type that lies about half the cases.
Origin
I wrote a mediator before this one, to decouple modules of a corporate service, and it worked. This one is what I would do knowing what that one taught me: same idea, better implementation.