Mixing Up The How And The What

Imagine a high-level business logic workflow containing the following code:

  TeamsClient.SendMessage("Calling ClientService to get client.");

  // ...

  TeamsClient.SendMessage("Calling ClientService to update client order count.");

The TeamsClient sends messages via the Microsoft Teams communication tool. Those messages have a diagnostic look about them. Yes—they are logging statements!

Let’s assume we want to replace the Teams logging mechanism with a different logging mechanism, say, Log4Net. We’d need to modify all the places in the code where we are logging with TeamsClient to logging with a corresponding Log4NetLogger. In a non-trivial system, there could be many such locations, thus requiring many changes. OK, so we make the changes and think nothing more of it.

After reading a blog, our tech lead mentions that we would be better off logging to Azure Table Storage. Once more, we replace one type of diagnostic logger with another. This time we’re replacing the Log4NetLogger with an AzureTableStorageLogger. And we must, yet again, do this in many different places throughout the codebase.

Do we want to make numerous changes in our code because we are changing how we log? No, we don’t. If possible, we’d like to make relatively few changes to change the logging mechanism. Ideally, we’d prefer to leave our existing code untouched and only write new code as per the Open-Closed Principle.

Let’s clearly distinguish between 

  • WHAT we are trying to accomplish, and 
  • HOW we are achieving it. 

 

WHAT: The system logs diagnostic messages.

=> This is POLICY

 

HOW: Diagnostic messages are logged via:

  • Microsoft Teams, or
  • Log4Net Logger, or
  • Azure Table Storage, or

=> These are MECHANISMS

 

With that in mind, let’s revisit how we can improve the logging in our example system:

We create an interface, say, ILogger containing one or more logging methods:

  public interface ILogger
  {
     void LogInfo(string message);
     // ...
  }

Now we can write ILogger implementations for the different logging mechanisms. Here is the TeamsLogger:

  public class TeamsLogger : ILogger
  {
     private ITeamsClient Client { get; }

     public TeamsLogger(ITeamsClient client)
     {
        Client = client;
     }

     public void LogInfo(string message)
     {
        Client.SendMessage(message);
     }

     // ... 
  }

Note that TeamsLogger still utilises the all-purpose TeamsClient.
The original high-level code can now use the ILogger abstraction rather than ITeamsClient:

  Logger.LogInfo("Calling ClientService to get client.");

  // ...

  Logger.LogInfo("Calling ClientService to update order count.");

Plugging in a different ILogger implementation—Log4NetLogger, AzureTableStorageLogger, etc.—is now a piece of cake. 

If that is not cool, I don’t know what is!

Please keep in mind that an essential part of being an accomplished Software Engineer is the capacity to distinguish between WHAT needs to be done (i.e. POLICY) from HOW it will be done (i.e. MECHANISM).

Master this skill, and many programming headaches will disappear.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply