DEV Community

Spyros Ponaris
Spyros Ponaris

Posted on

Chain of Responsibility pattern

Chain of Responsibility

About 8 years ago I came across the Chain of Responsibility pattern. I remember I was genuinely happy when I understood it, because all those long if / else if / else blocks suddenly felt unnecessary. Instead of one method trying to decide everything, you build a small chain of handlers. Each handler either handles the request, or passes it to the next one.

Wikipedia describes it as a source of requests and a series of processing objects. Each processing object contains logic that decides what it can handle, and forwards the rest to the next one. The nice part is that the chain can be rearranged and extended without rewriting one big method. (Wikipedia)

The classic example (your repo)

In your repo, the idea is very clear:

  • You have a base Handler with a successor
  • Employee, Supervisor, GeneralManager try to approve a PurchaseOrder
  • Each handler checks rules (your Specification<T>), and if it cannot approve, it forwards to the next handler

This is the object-oriented alternative to the “if ladder”, but with better structure and easier extension. (GitHub)

The base building blocks

This is the base handler that wires the chain:

public abstract class Handler
{
    protected Handler successor;

    public void SetSuccessor(Handler purchaseOrder)
    {
        this.successor = purchaseOrder;
    }

    public abstract void HandleRequest(PurchaseOrder purchase);
}
Enter fullscreen mode Exit fullscreen mode

And this is the small Specification<T> concept used to keep rules readable:

public interface ISpecification<T>
{
    bool IsSatisfiedBy(T o);
}

public class Specification<T> : ISpecification<T>
{
    private Func<T, bool> expression;

    public Specification(Func<T, bool> expression)
    {
        if (expression == null)
            throw new ArgumentNullException();
        else
            this.expression = expression;
    }

    public bool IsSatisfiedBy(T o)
    {
        return this.expression(o);
    }
}
Enter fullscreen mode Exit fullscreen mode

Example handler, Employee approves only small purchase orders. If it cannot approve, it forwards to the next handler:

public class Employee : Handler
{
    public override void HandleRequest(PurchaseOrder purchase)
    {
        ISpecification<PurchaseOrder> specification =
            new Specification<PurchaseOrder>(x => x.Price < 100);

        specification.And(new Specification<PurchaseOrder>(x => x.Amount == 1));

        if (specification.IsSatisfiedBy(purchase))
            Console.WriteLine("{0} approved purchase request #{1}",
                this.GetType().Name, purchase.RequestNumber);
        else if (successor != null)
            successor.HandleRequest(purchase);
    }
}
Enter fullscreen mode Exit fullscreen mode

A quick example, building the chain

This is the part that made the pattern “click” for me. You create the handlers, link them together, and then you send requests to the first handler:

var emp = new Employee();
var superVisor = new Supervisor();
var generalManager = new GeneralManager();

emp.SetSuccessor(superVisor);
superVisor.SetSuccessor(generalManager);

List<PurchaseOrder> purchaseOrders = new List<PurchaseOrder>
{
    new PurchaseOrder { RequestNumber = 1, Amount = 1, Price = 50, Name = "Test Room 1" },
    new PurchaseOrder { RequestNumber = 1, Amount = 1, Price = 150, Name = "Test Room 2" },
    new PurchaseOrder { RequestNumber = 1, Amount = 1, Price = 200, Name = "Test Room 3" },
};

purchaseOrders.ForEach(x => emp.HandleRequest(x));
Enter fullscreen mode Exit fullscreen mode

The caller doesn’t need to know who will approve. It only knows the start of the chain. Adding a new role later is just inserting one more handler, without rewriting the calling code.

How this shows up today with MediatR behaviors

What I like is that this pattern is not old school. You see the same idea a lot in MediatR pipeline behaviors.

A MediatR request goes through a chain of behaviors (validation, logging, performance, transaction, etc.). Each behavior can run logic before and after calling the next step in the pipeline, very similar to middleware. In practice, this is a Chain of Responsibility around your handler, used in a modern CQRS way. (CodeOpinion)

In my projects, the most common example is TransactionBehavior. I run a database transaction only for commands (write operations). Queries skip it. That keeps reads lightweight, while writes stay consistent. The chain idea is the same, each step has one job, and the next step continues the flow.

Typical chain steps in CQRS are:

  • Validation (FluentValidation), stop early if invalid
  • Logging (request, response, timing)
  • Authorization (permissions, policies)
  • Caching (usually queries only)
  • Transactions (usually commands only, begin, commit, rollback)
  • Retries / idempotency (depending on the system)

So the “chain” is not only about deciding who approves a purchase order. It’s also about running a request through cross-cutting concerns, without bloating controllers or handlers.

Why I still like it

  • You can add a new rule without editing a giant method
  • You can reorder behavior, or swap parts, without rewriting everything
  • It fits naturally with CQRS, because commands and queries are already separated by intent

References

  • Wikipedia, Chain-of-responsibility pattern (Wikipedia)
  • Repo example, ChainOfResponsibility- (GitHub)
  • MediatR behaviors overview (CodeOpinion)

If you want, in the next part we can break it down with a concrete CQRS flow, like CreatePurchaseOrderCommand with ValidationBehavior, TransactionBehavior, and an approval chain implemented as a pipeline (or inside the command handler, depending on what you prefer).

Top comments (0)