A few months back I wrote a (somewhat theoretical) post on composable interfaces. A major point of that post was that Role Interfaces  with a single Command method (i.e. a method that returns no value) is a very versatile category of abstraction.

Some of my readers asked for examples, so in this post I will provide a few. Consider this interface that fits the above description:

public interface IMessageConsumer<T>
{
    void Consume(T message);
}

This is a very common type of interface you will tend to encounter a lot in distributed, message-based architectures such as CQRS or Udi Dahan's view of SOA. Some people would call it a message subscriber instead…

In the rest of this post I will examine how we can create compositions out of the IMessageConsumer<T> interface using (in order of significance) Decorator, Null Object, Composite, and other well-known programming constructs.

Decorator

Can we create a meaningful Decorator around the IMessageConsumer<T> interface? Yes, that's easy - I've earlier provided various detailed examples of Decorators, so I'm not going to repeat them here.

I've yet to come up with an example of an interface that prevents us from applying a Decorator, so it's a valid falsifiable claim that we can always Decorate an interface. However, I have yet to prove that this is true, so until now we'll have to call it a conjecture.

However, since it's so easy to apply a Decorator to an interface, it's not a particularly valuable trait when evaluating the composability of an interface.

Null Object

It can be difficult to implement the Null Object pattern when the method(s) in question return a value, but for Commands it's easy:

public class NullMessageConsumer<T> : IMessageConsumer<T>
{
    public void Consume(T message)
    {
    }
}

The implementation simply ignores the input and does nothing.

Once again my lack of formal CS education prevents me from putting forth a formal proof, but I strongly suspect that it's always possibly to apply the Null Object pattern to a Command (keep in mind that out parameters count as output, so any interface with one or more of these are not Commands).

It's often valuable to be able to use a Null Object, but the real benefit comes when we can compose various implementations together.

Composite

To be truly composable, an interface should make it possible to create various concrete implementations that each adhere to the Single Responsibility Principle and then compose those together in a complex implementation. A Composite is a general-purpose implementation of this concept, and it's easy to create a Composite out of a Command:

public class CompositeMessageConsumer<T> : 
    IMessageConsumer<T>
{
    private readonly IEnumerable<IMessageConsumer<T>> 
        consumers;
 
    public CompositeMessageConsumer(
        params IMessageConsumer<T>[] consumers)
    {
        if (consumers == null)
        {
            throw new ArgumentNullException("consumers");
        }
 
        this.consumers = consumers;
    }
 
    public IEnumerable<IMessageConsumer<T>> Consumers
    {
        get { return this.consumers; }
    }
 
    #region IMessageConsumer<T> Members
 
    public void Consume(T message)
    {
        foreach (var consumer in this.Consumers)
        {
            consumer.Consume(message);
        }
    }
 
    #endregion
}

The implementation of the Consume method simply loops over each composed IMessageConsumer<T> and invokes its Consume method.

I can, for example, implement a sequence of actions that will take place by composing the individual concrete implementations. First we have a guard that protects against invalid messages, followed by a consumer that writes the message to a persistent store, completed by a consumer that raises a Domain Event that the reservation request was accepted.

var c = new CompositeMessageConsumer<MakeReservationCommand>(
    guard, 
    writer, 
    acceptRaiser);

Consumers following the Liskov Substitution Principle will not notice the difference, as all they will see is an implementation of IMessageConsumer<MakeReservationCommand>.

More advanced programming constructs

The Composite pattern only describes a single, general way to compose implementations, but with a Command interface we can do more. As Domain-Driven Design explains, a successful interface is often characterized by making it possible to apply well-known arithmetic or logical operators. As an example, in the case of the IMessageConsumer<T> interface, we can easily mimic the well-known ?! ternary operator from C#:

public class ConditionalMessageConsumer<T> : 
    IMessageConsumer<T>
{
    private Func<T, bool> condition;
    private IMessageConsumer<T> first;
    private IMessageConsumer<T> second;
 
    public ConditionalMessageConsumer(
        Func<T, bool> condition, 
        IMessageConsumer<T> first, 
        IMessageConsumer<T> second)
    {
        if (condition == null)
        {
            throw new ArgumentNullException("condition");
        }
        if (first == null)
        {
            throw new ArgumentNullException("first");
        }
        if (second == null)
        {
            throw new ArgumentNullException("second");
        }
 
        this.condition = condition;
        this.first = first;
        this.second = second;
    }
 
    public void Consume(T message)
    {
        (this.condition(message) ? this.first : this.second)
            .Consume(message);
    }
}

This is more verbose than the ?! operator because C# doesn't allow us to define operator overloads for interfaces, but apart from that, it does exactly the same thing. Notice particularly that the Consume method uses the ?! operator to select among the two alternatives, and then subsequently invokes the Consume method on the selected consumer.

We can use the ConditionalMessageConsumer to define branches in the consumption of messages. As an example, we can encapsulate the previous CompositeMessageConsumer<MakeReservationCommand> into a conditional branch like this:

var consumer = 
    new ConditionalMessageConsumer<MakeReservationCommand>(
        guard.HasCapacity, c, rejectRaiser);

Notice that I use the method group syntax to supply the condition delegate. If the HasCapacity method returns true, the previous composite (c) is being invoked, but if the result is false we instead use a consumer that raises the Domain Event that the reservation request was rejected.

Concluding thoughts

Apart from the direct purpose of providing examples of the immensely powerful composition options a Command interface provides I want to point out a couple of things:

  • Each of the design pattern implementations (Null Object, Composite - even Conditional) are generic. This is a strong testament to the power of this particular abstraction.
  • The dynamic mocks I'm familiar with (Moq, Rhino Mocks) will by default try to create Null Object implementations for interfaces without explicit setups. Since it's trivial to implement a Null Command, they just emit them by default. If you use an Auto-mocking Container with your unit tests, you can refactor to your heart's content, adding and removing dependencies as long as they are Command interfaces, and your testing infrastructure will just take care of things for you. It'll just work.
  • Did you notice that even with these few building blocks we have implemented a large part of a sequential workflow engine? We can execute consumers in sequence as well as branch between different sequences. Obviously, more building blocks are needed to make a full-blown workflow engine, but not that many. I'll leave the rest as an exercise to the reader :)

As I originally sketched, a Command interface is the ultimate in composability. To illustrate, the application from where I took the above examples is a small application with 48 types (classes and interfaces) in the production code base (that is, excluding unit tests). Of these, 9 are implementations of the IMessageConsumer<T> interface. If we also count the interface itself, it accounts for more than 20 percent of the code base. According to the Reused Abstractions Principle (RAP) I consider this a very successful abstraction.



Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or Google Plus, or somewhere else with a permalink. Ping me with the link, and I may add it as a comment.

Published

Tuesday, 22 March 2011 13:09:25 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!