Beyond String Configurations

Sometimes even retrieving simple configuration values can be tricky. 

Like the case where a particular configuration mechanism forces us to read a configuration value as a string, but we’d rather have an integer because an integer feels natural, while a string doesn’t. 

 

In a recent article on the benefits of separating configuration, the provided example featured an ApiClient class which read a pagination PageSize property from configuration. PageSize represented the maximum number of records ApiClient can retrieve in a single call to XYZ company’s API. 

So, if we are reading customer records and PageSize is set to 100, then we will receive up to 100 records per call to this API.

 

Unfortunately, the configuration values came from a mechanism which read and returned all key/value pairs as strings, like key: “PageSize” corresponded to value: “100”. 

Naturally, PageSize represents natural number values (e.g. 1, 2, 3, 4, …), so a string value of “100” does not work well. The integer data type, or similar, better match our expectations of a pagination PageSize.  

 

However, our definition of the original configuration interface, IApiConfiguration, used by ApiClient reflects what a single, specific configuration mechanism (i.e. string key/value pairs) offers, rather than what ApiClient prefers:

 

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

 

Notice the string data type for PageSize. It’s the expected—but not most natural—data type for a pagination page size. 

Let’s explore this example further: What if we went ahead and changed the configuration mechanism to one that read values from a JSON file? JSON is a moderately data-type-aware configuration format: 

  {
     "PageSize" : 10,
     "BaseUrl" : "https://api.xyz.com/customers"
  }

The PageSize value 10 is not a string; it’s a number. On the other hand, the value for BaseUrl is a string since it’s got surrounding quotes.

 

A JSON configuration implementer of IApiClientConfiguration, would—more than somewhat nonsensically—need to convert a helpful integer value to a much less useful string PageSize. I hope we feel suitably dirty for even considering such a move!

Furthermore, ApiClient will want to consume PageSize as an integer and turn it back into an integer! Not cool.

The interface definition is holding us back. We had defined it from our first implementation when the value for PageSize was only available as a string. Yet we might have been better off reflecting a bit on our data types before committing this mistake:

  • Should we return all configuration values exactly how they are stored?
  • Do all configuration mechanisms store values the same way?
  • What if we change the configuration mechanism that stores values in their native data type?
  • Most importantly: Why is it relevant to a consumer of configuration values, how configuration mechanisms store their values? 

And we can go one of two ways here:

  1. We could stick with our original, simplistic, all-values-are-strings configuration interface. This approach implies changing to a more sophisticated, data-type-aware configuration mechanism would mean downgrading to string values. So, PageSize would be “10”, even though we could have 10.
  2. Or, we could update our interface to return data types closer to how they are consumed. PageSize might be configured as “10” in a standard .config file, and we’d convert it to 10 in the AppSetting configuration class. Conversely, a JSON configuration holds the value already in numeric form, as 10, and no conversion would be required.

 

Which is the better way?

 

Maybe change the interface? But can we do so after we have created it and are using it in our application?

  

The question we were pondering was whether we should 

  • Keep the interface the same and have implementing configurations conform to a string PageSize even though it’s a bit awkward, or
  • Change the interface to an integer PageSize which intuitively makes more sense.

The answer is simple: We should change the interface. 

Having a string PageSize is bound to confuse readers. 

And at all times, we want to Avoid Confusion.

 

Avoiding Confusion – It’s the Golden Rule of Software Development.

 

OK, but is it safe to change this interface?

Yes, it will be, as long as ApiClient is the only consumer of IApiClientConfiguration. 

Let’s change property PageSize on IApiClientConfiguration:

  public interface IXyzApiClientConfiguration
  {
     int PageSize { get; }
     string BaseUrl { get; }
  }

 

Implementers, like ApiClientDatabaseConfiguration or ApiClientJsonConfiguration, may be required to perform string-to-integer conversions for PageSize. 

 

One last thing. How do these configuration classes convey to ApiClient that they could not retrieve a given configuration value for PageSize?

 

When such a failure occurs, it should return a null value. And that should be the case even for primitive types like booleans or integers. 

 

Why would we want to produce a nullable integer or a nullable boolean? In this instance, we cannot depend on the default integer value of 0 to convey to our ApiClient that the configuration ‘didn’t manage to retrieve an integer‘. The default integer value of 0 won’t do because 0 could be an acceptable value—a PageSize of 0 could conceptually denote ‘unlimited page size; retrieve all records at once‘. 

 

0 is a reasonable value in other configuration situations, too—such as 

  • NumberOfCacheInstances = 0 (i.e. not using any cache instances),
  • CacheSecondsToLive = 0 (i.e. don’t cache),
  • NumberOfSuperAdminsAllowed = 0 (i.e. no super-admins allowed)

Well, you get the idea. 

 

0 can be a reasonable integer value for a configuration setting. What we need is an unreasonable value—like null.

 

The interface finally becomes

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

In .NET, a ? after a primitive value type turns it into a nullable type. 

The string is already nullable, so it wasn’t needed when we started out. In Java, we could use the Integer reference type, which is nullable. 

Now we have a way of clearly signalling to the caller of our configuration that we failed to get a value. And this is consistent with retrieval from a database—a request for a non-existent single record ought to return null. 

Conclusion

Simple configuration values often come as strings. Yet a string may not always be the most natural representation for a configuration value.

In our example, we discovered that an integer for a pagination PageSize property feels more sensible than a string. In this way, if a consumer of a configuration value would naturally expect the value to be an integer, then we should offer it in integer form. And since configuration implementations ought to be separated from configuration value consumers by an abstraction, like an interface, those abstractions ought to offer the configuration values in a form ready for consumption—as, in our example, numeric PageSize should be a nullable integer.

 

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply