The Command Handler contravariant functor by Mark Seemann
An introduction to the Command Handler contravariant functor for object-oriented programmers.
This article is an instalment in an article series about contravariant functors. It assumes that you've read the introduction.
Asynchronous software architectures, such as those described in Enterprise Integration Patterns, often make good use of a pattern where Commands are (preferably immutable) Data Transfer Objects (DTOs) that are often placed on a persistent queue and later handled by a background process.
Even if you don't use asynchronous processing, separating command data from command handling can be beneficial for your software's granular architecture. In perhaps his most remarkable contribution to our book, Steven van Deursen describes how this pattern can greatly simplify how you deal with cross-cutting concerns.
Interface #
In DIPPP the interface is called ICommandService
, but in this article I'll instead call it ICommandHandler
. It's a generic interface with a single method:
public interface ICommandHandler<TCommand> { void Execute(TCommand command); }
The book explains how this interface enables you to gracefully handle cross-cutting concerns without any reflection magic. You can also peruse its example code base on GitHub. In this article, however, I'm using a fork of that code because I wanted to make the properties of contravariant functors stand out more clearly.
In the sample code base, an ASP.NET Controller delegates work to an injected ICommandHandler<AdjustInventory>
called inventoryAdjuster
.
[Route("inventory/adjustinventory")] public ActionResult AdjustInventory(AdjustInventoryViewModel viewModel) { if (!this.ModelState.IsValid) { return this.View(nameof(Index), this.Populate(viewModel)); } AdjustInventory command = viewModel.Command; this.inventoryAdjuster.Execute(command); this.TempData["SuccessMessage"] = "Inventory successfully adjusted."; return this.RedirectToAction(nameof(HomeController.Index), "Home"); }
There's a single implementation of ICommandHandler<AdjustInventory>
, which is a class called AdjustInventoryService
:
public class AdjustInventoryService : ICommandHandler<AdjustInventory> { private readonly IInventoryRepository repository; public AdjustInventoryService(IInventoryRepository repository) { if (repository == null) throw new ArgumentNullException(nameof(repository)); this.repository = repository; } public void Execute(AdjustInventory command) { var productInventory = this.repository.GetByIdOrNull(command.ProductId) ?? new ProductInventory(command.ProductId); int quantityAdjustment = command.Quantity * (command.Decrease ? -1 : 1); productInventory = productInventory.AdjustQuantity(quantityAdjustment); if (productInventory.Quantity < 0) throw new InvalidOperationException("Can't decrease below 0."); this.repository.Save(productInventory); } }
The Execute
method first loads the inventory data from the database, calculates how to adjust it, and saves it. This is all fine and good object-oriented design, and my intent with the present article isn't to point fingers at it. My intent is only to demonstrate how the ICommandHandler
interface gives rise to a contravariant functor.
I'm using this particular code base because it provides a good setting for a realistic example.
Towards Domain-Driven Design #
Consider these two lines of code from AdjustInventoryService
:
int quantityAdjustment = command.Quantity * (command.Decrease ? -1 : 1); productInventory = productInventory.AdjustQuantity(quantityAdjustment);
Doesn't that look like a case of Feature Envy? Doesn't this calculation belong better on another class? Which one? The AdjustInventory
Command? That's one option, but in this style of architecture Commands are supposed to be dumb DTOs, so that may not be the best fit. ProductInventory
? That may be more promising.
Before making that change, however, let's consider the current state of the class.
One of the changes I made in my fork of the code was to turn the ProductInventory
class into an immutable Value Object, as recommended in DDD:
public sealed class ProductInventory { public ProductInventory(Guid id) : this(id, 0) { } public ProductInventory(Guid id, int quantity) { Id = id; Quantity = quantity; } public Guid Id { get; } public int Quantity { get; } public ProductInventory WithQuantity(int newQuantity) { return new ProductInventory(Id, newQuantity); } public ProductInventory AdjustQuantity(int adjustment) { return WithQuantity(Quantity + adjustment); } public override bool Equals(object obj) { return obj is ProductInventory inventory && Id.Equals(inventory.Id) && Quantity == inventory.Quantity; } public override int GetHashCode() { return HashCode.Combine(Id, Quantity); } }
That looks like a lot of code, but keep in mind that typing isn't the bottleneck - and besides, most of that code was written by various Visual Studio Quick Actions.
Let's try to add a Handle
method to ProductInventory
:
public ProductInventory Handle(AdjustInventory command) { var adjustment = command.Quantity * (command.Decrease ? -1 : 1); return AdjustQuantity(adjustment); }
While AdjustInventoryService
isn't too difficult to unit test, it still does require setting up and configuring some Test Doubles. The new method, on the other hand, is actually a pure function, which means that it's trivial to unit test:
[Theory] [InlineData(0, false, 0, 0)] [InlineData(0, true, 0, 0)] [InlineData(0, false, 1, 1)] [InlineData(0, false, 2, 2)] [InlineData(1, false, 1, 2)] [InlineData(2, false, 3, 5)] [InlineData(5, true, 2, 3)] [InlineData(5, true, 5, 0)] public void Handle( int initial, bool decrease, int adjustment, int expected) { var sut = new ProductInventory(Guid.NewGuid(), initial); var command = new AdjustInventory { ProductId = sut.Id, Decrease = decrease, Quantity = adjustment }; var actual = sut.Handle(command); Assert.Equal(sut.WithQuantity(expected), actual); }
Now that the new function is available on ProductInventory
, you can use it in AdjustInventoryService
:
public void Execute(AdjustInventory command) { var productInventory = this.repository.GetByIdOrNull(command.ProductId) ?? new ProductInventory(command.ProductId); productInventory = productInventory.Handle(command); if (productInventory.Quantity < 0) throw new InvalidOperationException("Can't decrease below 0."); this.repository.Save(productInventory); }
The Execute
method now delegates its central logic to ProductInventory.Handle
.
Encapsulation #
If you consider the Execute
method in its current incarnation, you may wonder why it checks whether the Quantity
is negative. Shouldn't that be the responsibility of ProductInventory
? Why do we even allow ProductInventory
to enter an invalid state?
This breaks encapsulation. Encapsulation is one of the most misunderstood concepts in programming, but as I explain in my PluralSight course, as a minimum requirement, an object should not allow itself to be put into an invalid state.
How to better encapsulate ProductInventory
? Add a Guard Clause to the constructor:
public ProductInventory(Guid id, int quantity) { if (quantity < 0) throw new ArgumentOutOfRangeException( nameof(quantity), "Negative quantity not allowed."); Id = id; Quantity = quantity; }
Again, such behaviour is trivial to drive with a unit test:
[Theory] [InlineData( -1)] [InlineData( -2)] [InlineData(-19)] public void SetNegativeQuantity(int negative) { var id = Guid.NewGuid(); Action action = () => new ProductInventory(id, negative); Assert.Throws<ArgumentOutOfRangeException>(action); }
With those changes in place, AdjustInventoryService
becomes even simpler:
public void Execute(AdjustInventory command) { var productInventory = this.repository.GetByIdOrNull(command.ProductId) ?? new ProductInventory(command.ProductId); productInventory = productInventory.Handle(command); this.repository.Save(productInventory); }
Perhaps even so simple that the class begins to seem unwarranted.
Sandwich #
It's just a database Query, a single pure function call, and another database Command. In fact, it looks a lot like an impureim sandwich:
public void Execute(AdjustInventory command) { var productInventory = this.repository.GetByIdOrNull(command.ProductId) ?? new ProductInventory(command.ProductId); productInventory = productInventory.Handle(command); this.repository.Save(productInventory); }
In fact, it'd probably be more appropriate to move the null-handling closer to the other referentially transparent code:
public void Execute(AdjustInventory command) { var productInventory = this.repository.GetByIdOrNull(command.ProductId); productInventory = (productInventory ?? new ProductInventory(command.ProductId)).Handle(command); this.repository.Save(productInventory); }
Why do we need the AdjustInventoryService
class, again?
Can't we move those three lines of code to the Controller? We could, but that might make testing the above AdjustInventory
Controller action more difficult. After all, at the moment, the Controller has an injected ICommandHandler<AdjustInventory>
, which is easy to replace with a Test Double.
If only we could somehow compose an ICommandHandler<AdjustInventory>
from the above sandwich without having to define a class...
Contravariant functor #
Fortunately, an interface like ICommandHandler<T>
gives rise to a contravariant functor. This will enable you to compose an ICommandHandler<AdjustInventory>
object from the above constituent parts.
In order to enable contravariant mapping, you must add a ContraMap
method:
public static ICommandHandler<T1> ContraMap<T, T1>( this ICommandHandler<T> source, Func<T1, T> selector) { Action<T1> action = x => source.Execute(selector(x)); return new DelegatingCommandHandler<T1>(action); }
Notice that, as explained in the overview article, in order to map from an ICommandHandler<T>
to an ICommandHandler<T1>
, the selector
has to go the other way: from T1
to T
. How this is possible will become more apparent with an example, which will follow later in the article.
The ContraMap
method uses a DelegatingCommandHandler
that wraps any Action<T>
:
public class DelegatingCommandHandler<T> : ICommandHandler<T> { private readonly Action<T> action; public DelegatingCommandHandler(Action<T> action) { this.action = action; } public void Execute(T command) { action(command); } }
If you're now wondering whether Action<T>
itself gives rise to a contravariant functor, then yes it does.
Identity law #
A ContraMap
method with the right signature isn't enough to be a contravariant functor. It must also obey the contravariant functor laws. As usual, it's proper computer-science work to actually prove this, but you can write some tests to demonstrate the identity law for the ICommandHandler<T>
interface. In this article, you'll see parametrised tests written with xUnit.net. First, the identity law:
[Theory] [InlineData("foo")] [InlineData("bar")] [InlineData("baz")] [InlineData("qux")] [InlineData("quux")] [InlineData("quuz")] [InlineData("corge")] [InlineData("grault")] [InlineData("garply")] public void IdentityLaw(string input) { var observations = new List<string>(); ICommandHandler<string> sut = new DelegatingCommandHandler<string>(observations.Add); T id<T>(T x) => x; ICommandHandler<string> projection = sut.ContraMap<string, string>(id); // Run both handlers sut.Execute(input); projection.Execute(input); Assert.Equal(2, observations.Count); Assert.Single(observations.Distinct()); }
In order to observe that the two handlers have identical behaviours, the test has to Execute
both of them to verify that both observations are the same.
All test cases pass.
Composition law #
Like the above example, you can also write a parametrised test that demonstrates that ContraMap
obeys the composition law for contravariant functors:
[Theory] [InlineData("foo")] [InlineData("bar")] [InlineData("baz")] [InlineData("qux")] [InlineData("quux")] [InlineData("quuz")] [InlineData("corge")] [InlineData("grault")] [InlineData("garply")] public void CompositionLaw(string input) { var observations = new List<TimeSpan>(); ICommandHandler<TimeSpan> sut = new DelegatingCommandHandler<TimeSpan>(observations.Add); Func<string, int> f = s => s.Length; Func<int, TimeSpan> g = i => TimeSpan.FromDays(i); ICommandHandler<string> projection1 = sut.ContraMap((string s) => g(f(s))); ICommandHandler<string> projection2 = sut.ContraMap(g).ContraMap(f); // Run both handlers projection1.Execute(input); projection2.Execute(input); Assert.Equal(2, observations.Count); Assert.Single(observations.Distinct()); }
This test defines two local functions, f
and g
. Once more, you can't directly compare methods for equality, so instead you have to Execute
them to verify that they produce the same observable effect.
They do.
Composed inventory adjustment handler #
We can now return to the inventory adjustment example. You may recall that the Controller would Execute
a command
on an injected ICommandHandler<AdjustInventory>
:
this.inventoryAdjuster.Execute(command);
As a first step, we can attempt to compose inventoryAdjuster
on the fly:
ICommandHandler<AdjustInventory> inventoryAdjuster = new DelegatingCommandHandler<ProductInventory>(repository.Save) .ContraMap((ProductInventory inv) => (inv ?? new ProductInventory(command.ProductId)).Handle(command)) .ContraMap((AdjustInventory cmd) => repository.GetByIdOrNull(cmd.ProductId)); inventoryAdjuster.Execute(command);
Contra-mapping is hard to get one's head around, and to make matters worse, you have to read it from the bottom towards the top to understand what it does. It really is contrarian.
How do you arrive at something like this?
You start by looking at what you have. The Controller may already have an injected repository
with various methods. repository.Save
, for example, has this signature:
void Save(ProductInventory productInventory);
Since it has a void
return type, you can treat repository.Save
as an Action<ProductInventory>
. Wrap it in a DelegatingCommandHandler
and you have an ICommandHandler<ProductInventory>
:
ICommandHandler<ProductInventory> inventoryAdjuster = new DelegatingCommandHandler<ProductInventory>(repository.Save);
That's not what you need, though. You need an ICommandHandler<AdjustInventory>
. How do you get closer to that?
You already know from the AdjustInventoryService
class that you can use a pure function as the core of the impureim sandwich. Try that and see what it gives you:
ICommandHandler<ProductInventory> inventoryAdjuster = new DelegatingCommandHandler<ProductInventory>(repository.Save) .ContraMap((ProductInventory inv) => (inv ?? new ProductInventory(command.ProductId)).Handle(command));
That doesn't change the type of the handler, but implements the desired functionality.
You have an ICommandHandler<ProductInventory>
that you need to somehow map to an ICommandHandler<AdjustInventory>
. How do you do that?
By supplying a function that goes the other way: from AdjustInventory
to ProductInventory
. Does such a method exist? Yes, it does, on the repository:
ProductInventory GetByIdOrNull(Guid id);
Or, close enough. While AdjustInventory
is not a Guid
, it comes with a Guid
:
ICommandHandler<AdjustInventory> inventoryAdjuster = new DelegatingCommandHandler<ProductInventory>(repository.Save) .ContraMap((ProductInventory inv) => (inv ?? new ProductInventory(command.ProductId)).Handle(command)) .ContraMap((AdjustInventory cmd) => repository.GetByIdOrNull(cmd.ProductId));
That's cool, but unfortunately, this composition cheats. It closes over command
, which is a run-time variable only available inside the AdjustInventory
Controller action.
If we're allowed to compose the Command Handler inside the AdjustInventory
method, we might as well just have written:
var inv = repository.GetByIdOrNull(command.ProductId); inv = (inv ?? new ProductInventory(command.ProductId)).Handle(command); repository.Save(inv);
This is clearly much simpler, so why don't we do that?
In this particular example, that's probably a better idea overall, but I'm trying to explain what is possible with contravariant functors. The goal here is to decouple the caller (the Controller) from the handler. We want to be able to define the handler outside of the Controller action.
That's what the AdjustInventory
class does, but can we leverage the contravariant functor to compose an ICommandHandler<AdjustInventory>
without adding a new class?
Composition without closures #
The use of a closure in the above composition is what disqualifies it. Is it possible to compose an ICommandHandler<AdjustInventory>
when the command
object is unavailable to close over?
Yes, but it isn't pretty:
ICommandHandler<AdjustInventory> inventoryAdjuster = new DelegatingCommandHandler<ProductInventory>(repository.Save) .ContraMap((ValueTuple<AdjustInventory, ProductInventory> t) => (t.Item2 ?? new ProductInventory(t.Item1.ProductId)).Handle(t.Item1)) .ContraMap((AdjustInventory cmd) => (cmd, repository.GetByIdOrNull(cmd.ProductId)));
You can let the composing function return a tuple of the original input value and the projected value. That's what the lowest ContraMap
does. This means that the upper ContraMap
receives this tuple to map. Not pretty, but possible.
I never said that this was the best way to address some of the concerns I've hinted at in this article. The purpose of the article was mainly to give you a sense of what a contravariant functor can do.
Action as a contravariant functor #
Wrapping an Action<T>
in a DelegatingCommandHandler
isn't necessary in order to form the contravariant functor. I only used the ICommandHandler
interface as an object-oriented-friendly introduction to the example. In fact, any Action<T>
gives rise to a contravariant functor with this ContraMap
function:
public static Action<T1> ContraMap<T, T1>(this Action<T> source, Func<T1, T> selector) { return x => source(selector(x)); }
As you can tell, the function being returned is similar to the lambda expression used to implement ContraMap
for ICommandHandler<T>
.
This turns out to make little difference in the context of the examples shown here, so I'm not going to tire you with more example code.
Conclusion #
Any generic polymorphic interface or abstract method with a void
return type gives rise to a contravariant functor. This includes the ICommandHandler<T>
(originally ICommandService<T>
) interface, but also another interface discussed in DIPPP: IEventHandler<TEvent>
.
The utility of this insight may not be immediately apparent. Contrary to its built-in support for functors, C# doesn't have any language features that light up if you implement a ContraMap
function. Even in Haskell where the Contravariant functor is available in the base library, I can't recall having ever used it.
Still, even if not a practical insight, the ubiquitous presence of contravariant functors in everyday programming 'building blocks' tells us something profound about the fabric of programming abstraction and polymorphism.