DEV Community

Ryan Teh Hoon Meng
Ryan Teh Hoon Meng

Posted on • Edited on

Using the Decorator Pattern with Autofac for Selective Service Registration

The other day, while working on a ticket at my job, I was tasked with adding a validation step to the checkout process for a specific platform. Initially, I thought this would be a simple change. I added the validation logic to the shared CheckoutProcessCommandHandler class, which looked like this:

class CheckoutProcessCommand : ICommandHandler
{
    public void Handle()
    {
        if (platform == 'specific-platform' && !Validate())
        {
            throw new InvalidOperationException();
        }

        // Other checkout logic...
    }
}
Enter fullscreen mode Exit fullscreen mode

After creating a pull request (PR), I assumed the ticket was complete. However, a reviewer suggested using a decorator pattern and registering it in the platform's Autofac module instead. The challenge? There were multiple ICommandHandler implementations registered in the Autofac module, and I needed a way to apply the decorator to a single class only.

What is the Decorator Pattern?

The decorator pattern is a structural design pattern that allows you to dynamically add new functionality to an object by wrapping it in one or more decorator classes. Importantly, it does this without altering the object's original structure.

Why Use the Decorator Pattern?

  • Reusability: You can create reusable components to extend functionality.

  • Maintainability: Keeps the original codebase clean and focused on core logic.

  • Flexibility: Allows dynamic composition of behaviors at runtime.

Registering a Decorator in Autofac

Here’s a simple example of how to register a decorator in Autofac. In this example, we have two classes implementing ICommandHandler: CheckoutProcessCommand and PaymentProcessCommandHandler. Additionally, there’s a decorator class, CommandHandlerDecorator, which adds functionality by printing an additional message.

Interface Definition

ICommandHandler.cs:

interface ICommandHandler
{
    void Handle();
}
Enter fullscreen mode Exit fullscreen mode

Command Handler Implementations

CheckoutProcessCommand.cs:

class CheckoutProcessCommand : ICommandHandler
{
    public void Handle()
    {
        Console.WriteLine($"{nameof(CheckoutProcessCommand)}.{nameof(Handle)}");
    }
}
Enter fullscreen mode Exit fullscreen mode

PaymentProcessCommandHandler.cs:

class PaymentProcessCommandHandler : ICommandHandler
{
    public void Handle()
    {
        Console.WriteLine($"{nameof(PaymentProcessCommandHandler)}.{nameof(Handle)}");
    }
}
Enter fullscreen mode Exit fullscreen mode

The Decorator

CommandHandlerDecorator.cs:

class CommandHandlerDecorator(ICommandHandler innerHandler) : ICommandHandler
{
    public void Handle()
    {
        Console.WriteLine($"{nameof(CommandHandlerDecorator)}.{nameof(Handle)}");
        innerHandler.Handle();
    }
}
Enter fullscreen mode Exit fullscreen mode

Autofac Registration

Here’s how we register the command handlers and the decorator:

var builder = new ContainerBuilder();
builder.RegisterType<CheckoutProcessCommand>().As<ICommandHandler>();
builder.RegisterType<PaymentProcessCommandHandler>().As<ICommandHandler>();
builder.RegisterDecorator<CommandHandlerDecorator, ICommandHandler>();
var container = builder.Build();
foreach (var handler in container.Resolve<IEnumerable<ICommandHandler>>())
{
    handler.Handle();
}
Enter fullscreen mode Exit fullscreen mode

Output

When you run the application, the output will look like this:

CommandHandlerDecorator.Handle
CheckoutProcessCommand.Handle
CommandHandlerDecorator.Handle
PaymentProcessCommandHandler.Handle
Enter fullscreen mode Exit fullscreen mode

Notice that CommandHandlerDecorator.Handle is executed before each command handler’s message. This happens because the decorator is applied to all instances implementing ICommandHandler. However, this isn’t the behavior we want—we need to target a specific implementation.

The Solution: Keyed Services in Autofac

After consulting the Autofac documentation on Keyed Service, I found a solution to apply the decorator to a specific implementation. Here’s the updated code:

var builder = new ContainerBuilder();
builder.RegisterType<CheckoutProcessCommand>().As<ICommandHandler>();
builder.RegisterType<PaymentProcessCommandHandler>().Keyed<ICommandHandler>(nameof(PaymentProcessCommandHandler));
builder.RegisterDecorator<ICommandHandler>(innerInstance => new CommandHandlerDecorator(innerInstance), nameof(PaymentProcessCommandHandler));

var container = builder.Build();
foreach (var handler in container.Resolve<IEnumerable<ICommandHandler>>())
{
    handler.Handle();
}
Enter fullscreen mode Exit fullscreen mode

Updated Output

CommandHandlerDecorator.Handle
CheckoutProcessCommand.Handle
CommandHandlerDecorator.Handle
PaymentProcessCommandHandler.Handle
Enter fullscreen mode Exit fullscreen mode

The decorator is now only applied to PaymentProcessCommandHandler, as desired.

Conclusion

Using Autofac’s keyed services allows for precise control over which classes to apply decorators to. This approach helped me implement the required functionality without affecting other parts of the system. My PR was approved, and I was finally able to close the ticket!

The original post could be found here

Note: this blog post was edited by ChatGPT for a better reading experience.

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay