Separate Configuration. Here’s Why.

Consider the configuration code in the constructor for a hypothetical ApiClient, written in C#.NET[1].   

 

  public class ApiClient
  {
     private string PageSize { get; }  
     private string BaseUrl { get; }  

     public ApiClient()
     {
        // Configuration
        PageSize = ConfigurationManager.AppSettings["ApiPageSize"];
        BaseUrl = ConfigurationManager.AppSettings["ApiBaseUrl"];
     }

     // Methods doing the work of ApiClient 
     // and using PageSize & BaseUrl properties.
  }

The ConfigurationManager.AppSettings indexed property calls retrieve a value for the specified key from a configuration file.

Only One Responsibility

The part I want to concentrate on is the AppSettings configuration calls. The fact that a web client class retrieves the configuration settings is unimportant—it could just as well have been a Business Logic class.

 

The central issue here is the coupling of two responsibilities that are independent of one another and, therefore, ought to be kept separate:

  • Responsibility 1:  How the client interacts with the remote API, and
  • Responsibility 2:  How we get our configuration values. 

 

These concerns change for different reasons and do not belong together. 

 

What do we mean by ‘change for different reasons‘?

 

We are the developers who make changes to this ApiClient class. What if we decided to store and retrieve configuration values from a database or a custom configuration subsystem? Should a change to the configuration implementation affect the working of the client code? No, of course not. The client code should not care how it is configured; it should only care that it is configured. The inverse also applies: A change to the client code should not affect its configuration. 

 

How do we resolve this issue? 

 

By extracting the reading of configuration data into a separate class.

 

OK, let’s extract the configuration code from ApiClient.

First, we create an interface for the configuration values:

 

  public interface IApiClientConfiguration
  {
     string PageSize { get; }
     string BaseUrl { get; }
  }

Then we implement the interface:

 

  public class ApiClientConfiguration : IApiClientConfiguration
  {
     public string PageSize => ConfigurationManager.AppSettings["ApiPageSize"];
     public string BaseUrl => ConfigurationManager.AppSettings["ApiBaseUrl"];
  }

 

The calls to ConfigurationManager.AppSettings that were in ApiClient are now in ApiClientConfiguration. Nice.

All we need to do now to configure our ApiClient with an instance of the new ApiClientConfiguration class is to inject our ApiClientConfiguration via the ApiClient constructor:

 

  private IApiClientConfiguration Config { get; }

  public ApiClient(IApiClientConfiguration config)
  {
     Config = config;
  }

 

Once assigned to the Config property, we may retrieve the configuration values directly via Config.PageSize and Config.BaseUrl. A more elegant, more concise alternative would be to assign configuration values to read-only forwarding properties:

  private string PageSize => Config.PageSize;
  private string BaseUrl => Config.BaseUrl; 

Putting it all together, the ApiClient becomes:

 

  public class ApiClient
  {
     private IApiClientConfiguration Config { get; }

     private string PageSize => Config.PageSize;
     private string BaseUrl => Config.BaseUrl ;  

     public ApiClient(IApiClientConfiguration config)
     {
        Config = config;
     }

     // Methods doing the work of ApiClient 
     // and using PageSize & BaseUrl properties.
  }

Class ApiClient no longer contains the configuration implementation detail—we have extracted it into a separate class. 

How do we instantiate ApiClient with our configuration[2]? 

Like so:

 

  var config = new ApiClientConfiguration();
  var client = new ApiClient(config);

 

Configuration from the Database

If we wanted to retrieve the configuration values from the database, ApiClient would not need to be modified. That is a big win. All we would need to do is write a new configuration class implementing the IApiClientConfiguration interface:

 

  public class ApiClientDatabaseConfiguration : IApiClientConfiguration
  {
     public string PageSize => // Get PageSize from database
     public string BaseUrl => // Get BaseUrl from database
  }

 

Consequently, ApiClient would now be configured via an instance of the ApiClientDatabaseConfiguration:

 

  var config = new ApiClientDatabaseConfiguration();
  var client = new ApiClient(config);

 

Now we have a much neater separation of concerns. Configuration lives inside ApiClientConfiguration or ApiClientDatabaseConfiguration, and the client code is encapsulated inside ApiClient. Changes in one class do not need to affect the other.

 

Conclusion

Configuration retrieval is a concern that ought to be independent, and thus separated, from other logic. 

 

Footnotes:

[1] The point that we should separate configuration specifics from other concerns applies to all languages, not just C#. 

[2] In .NET, we normally let an Inversion of Control (IoC) framework like Ninject or AutoFac take care of instantiating the desired implementation of IApiClientConfiguration. Here we’ve done it the old-fashioned, manual way.

 

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply