Search code examples
c#loggingdomain-driven-designdecorator

Decorator pattern with logging


I've recently been learning about the decorator pattern and want to use it in my code, but I see a challenge present itself when I want logging as well.

With an example from Zoran Horvat I have an interface IDiscount

public interface IDiscount
{
   IEnumerable<DiscountApplications> GetDiscount(Money money);
}

And I want to make decorators for it like the NoZeroDiscount

public class NoZeroDiscount(IDiscount other)
{
   public IEnumerable<DiscountApplications> GetDiscount(Money money)
   {
       return other.GetDiscount(money).Where(d => d != 0);
   }
}

But if I want to add logging to this decorator then I would have to accept an ILogger in the constructor, thus making this decorator is not as easy as I would like, especially if it accepts a generic ILogger.

I think this problem becomes quite prevalent if I have a decorator that depends on another decorator say a NoEmptyDiscount that also has logging uses the NoZeroDiscount.

This is also an issue I face with Domain models that have complex logic that I want logging in, when I instantiate the domain model, say a Book with Authors and a Publisher I don't also want to provide a logger. Having that requirement make a Book class hard to instantiate.

I've seen cases where dependencies are injected at the method level, which I think useful just not when it is a logger. And for cases like the IDiscount the interface would have to change to accept a logger...

public class Book
{
   public void ComplexStuff(ILogger<Book> logger, ...)
}

I suppose this issue is made worse if you want the generic loggers as you have to create one of the correct type, instead of just passing your non generic logger onto everything.

How do people handle logging in these situations for decorators and Domain models that you want to have logging, and still maintain the ease of use from before adding logging?


Solution

  • But if I want to add logging to this decorator then I would have to accept an ILogger in the constructor, thus making this decorator is not as easy as I would like, especially if it accepts a generic ILogger.

    Not necessarily; "setter injection" is also a thing, which has it's place. But yes: if you want a decorator implementation that also does other things, then you are going to need to implement the additional configuration somewhere.

    a Book with Authors and a Publisher I don't also want to provide a logger. Having that requirement make a Book class hard to instantiate.

    Yup. What makes sense in some cases is to initialize a class instance with a "null object" version of a logger, and also provide affordances that allow you to change the logger to one that is actually useful.

    Some good reading on this idea: The Doctrine of Useful Objects.

    You might also want to think more carefully about what the logging is for; because the different motivations for "pass information to a logger" may call for different design choices. See: Logging is also a feature.

    Broadly, though: there is no magic - doing logging well actually requires design.

    Design is what we do, when we want to get more of what we want than we'd get by "just doing it." -- Ruth Malan.