Skip to main content

Dish washing and the chain of responsibility

In our house, cleaning out the dishwasher is a shared chore. My son starts the unloading process by removing a dish or utensil from the dishwasher. If he can put it away, then he does. If the proper location for the dish is out of his reach, then he passes it to his mother. She then goes through the same process; put the dish away if she can, or pass it off to the next person in line, which is me. When I get handed a dish I will put it away and, since I’m 6’4" (1.92m) tall, I can reach all of our cupboard space, which means that the process ends with me.

In our kitchen, the handing off of work can be thought of as an implementation of the chain of responsibility pattern. Each person in the family is a link in that chain. The chain of responsibility starts with my son removing a dish from the dishwasher and ends execution with me. The process of putting away dishes isn’t all that different from the handling of messages in a message-based architecture. The system gets messages from a queue or stream and feeds them into the next transformation or piece of business logic.

Each person in our kitchen has a clearly defined set of questions that they ask themselves to determine if they should put the dish away or hand it off to the next process. With these clearly defined questions, each person, or link, has a well-encapsulated set of rules that they apply as they deal with each dish.

A chain of responsibility applies to more than just message-based architectures and emptying dishwashers. You’ll find different variations of the pattern in frameworks and middleware like OWIN, FubuMVC, Express.js, and more. They usually share a common approach to the chain of responsibility implementation: nesting functions inside functions.

Here is a simple way to grasp the concept of the chain of responsibility.

TL;DR

  • The chain is essentially a list of links. To process a message, links are picked from the list one by one and executed recursively until the end of the list is reached.
  • A functional programming equivalent of the chain of responsibility pattern is called functional composition.

🔗Unloading as a chain of responsibility

The process of unloading our dishwasher is, at its heart, a chain of responsibility. At our house, we have three steps in the chain and our output is an empty dishwasher. With this in mind, let’s explore the code representation of our dishwasher-unloading chain so that we can see the chain and its links. A single person, a link in the chain, can be represented as just a method.

static void Person(Action next)
{
    // Implementation
    next();
}

The method Person above has a single parameter called next of type Action. The Action type is a delegate that can point to any method returning void and accepting zero parameters. Passing in the delegate allows us to compose multiple individual elements together into a chain of responsibility. As you can see in the sample below, the ManualDishwasherUnloading method contains the chain of the individual links. You can also see how we’ve represented each person, or a link in the chain, as a method that matches the signature for an Action.

public void ManualDishwasherUnloading()
{
    Son(() => Wife(() => Husband(() => Done())));
}

static void Son(Action next)
{
    // son can reach? return; else:
    Console.WriteLine("Son can't reach");
    next();
}

static void Wife(Action next)
{
    // wife can reach? return; else:
    Console.WriteLine("Wife can't reach");
    next();
}

static void Husband(Action next)
{
    Console.WriteLine("Husband put dish away");
    next();
}

static void Done()
{
    Console.WriteLine("Dish put away!");
}    

If I, as the husband, am the only person who can reach to put the dish away, the output of this code is:

Son can't reach
Wife can't reach
Husband put dish away
Dish put away!

Visualized, the code above looks like this:

Chain of Responsibility

Of course, complex method-chaining like we’ve shown in ManualDishwasherUnloading isn’t something we’d enjoy manually writing repeatedly. By itself, the code isn’t complex. As the method chain grows, visualizing and understanding it becomes more and more difficult. Changing or adding to a large chaining sequence becomes a process that can easily introduce errors or create unintended side effects.

Luckily, there’s an even more flexible and maintainable way of building a chain of responsibility.

🔗A better chain of responsibility

The purpose of the chain of responsibility is to create a composition in which the links work together and are executed in a predefined order. In the examples above, we did it by writing it out method call by method call. A simpler and more generic approach is to create a list of actions. All actions are then picked from that list and executed one by one until the end of the list is reached:

public void MoreFlexibleDishwasherUnloading()
{
    var elements = new List<Action<Action>>
    {
        Son,
        Wife,
        Husband,
        next => Done()
    };

    Invoke(elements);
}

static void Invoke(List<Action<Action>> elements, int currentIndex = 0)
{
    if(currentIndex == elements.Count)
        return;

    var element = elements[currentIndex];
    element(() => Invoke(elements, currentIndex + 1));
}

It might be confusing at first that we declare a List<Action<Action>>. Why not just List<Action>? It’s because the signature of methods stored in the list is void LinkInTheChain(Action next). We want the ability to execute them in a generic way. The Invoke method takes an Action<Action> from the list, then invokes it by recursively passing itself as the next function parameter. The process terminates when the end of the list is reached.

The output of this code would be:

Son can't reach
Wife can't reach
Husband put dish away
Dish put away!

This type of generic approach is probably overkill if all we want to do is compose four methods together. But as you introduce more links in the chain, the generic approach quickly begins to shine. For example, say you want to surround link with a wrapper that filters out exceptions or performs some other cross-cutting behavior:

static void IgnoreDishStillWetException(Action next))
{
    try {
        next();
    }
    catch(DishStillWetException) { }
}    

It’s easy to add IgnoreDishStillWetException before any step in the chain of responsibility when it is needed. As long as the cross-cutting behavior implements the same pattern as Action, you can create and add new links to the chain of responsibility.

🔗Message handling as a chain of responsibility

Handling a message from a queue can also be implemented as a chain of responsibility.

public void MessageHandlingChain()
{
    var elements = new List<Action<Action>>
    {
        RetryMultipleTimesOnFailure,
        PickMessageFromTransport,
        DeserializeMessage,
        DetermineCodeToBeExecuted,
    };

    Invoke(elements);
}

Each operation, such as picking up the message from the transport of choice, can be thought of as a link in the message-handling chain. Because the chain is stored in a List, links can be added, removed, or reordered. You can make those changes at design time, or at runtime. There is a lot of flexibility in this chain of responsibility.

🔗Finishing the chain

With the analogy of my family and me emptying the dishwasher, we saw how to conceptualize the chain of responsibility and its links as a simple chain of function calls wrapped in each other in a nested fashion. We have also seen that a message handling pipeline is, in itself, a chain of responsibility.

But while these chains are flexible, they are also very linear. As I showed in my previous post, Async/Await: It’s time!, the domain of message handling is primarily focused on IO-intensive work, and the answer to IO-heavy workloads is a task-based API combined with the async/await keywords.

So, what would happen if we used async/await on each of the links in the chain of responsibility? You’ll just have to await for me to cover that next() time… ;)

🔗Additional readings

Share on Twitter
Don't miss a thing. Sign up today and we'll send you an email when new posts come out.
Thank you for subscribing. We'll be in touch soon.
 
We collect and use this information in accordance with our privacy policy.