Messy Configuration code

Everyone’s been in the situation where they’re reading through a bunch of code and most of it’s just reading config values and parsing them off in to their correct type, this normally makes the code harder to understand the business logic.

For example take our DepositCalculator it is loading the deposit percentage, minimum deposit and max deposit from config, while this class isn’t that hard to follow you can understand in a bigger and more complex class, loading config values directly within the method could get completely out of hand.

// DepositCalculator.cs
public class DepositCalculator
{
	public decimal CalculateDeposit(decimal basketTotal)
	{
		var deposit = basketTotal / 100 * decimal.Parse(ConfigurationManager.AppSettings["DepositPercentage"]);

		deposit = Math.Max(deposit, decimal.Parse(ConfigurationManager.AppSettings["MinDeposit"]));

		deposit = Math.Min(deposit, decimal.Parse(ConfigurationManager.AppSettings["MaxDeposit"]));

		return deposit;
	}
}

Abstraction

Ideally we would want to abstract these settings away in to a separate class so that the DepositCalculator isn’t concerning itself with how it’s getting these settings, so let’s introduce an IDepositSettings interface and a AppSettings class that implements it.

// IDepositSettings.cs
public interface IDepositSettings
{
    float DepositPercentage { get; }

    decimal MinDeposit { get; }
    
    decimal MaxDeposit { get; }
}
// AppSettings.cs
public class AppSettings : IDepositSettings
{
    public decimal DepositPercentage
    {
        get
        {
            return decimal.Parse(ConfigurationManager.AppSettings["DepositPercentage"]);
        }
    }

    public decimal MinDeposit
    {
        get
        {
            return decimal.Parse(ConfigurationManager.AppSettings["MinDeposit"]);
        }
    }

    public decimal MaxDeposit
    {
        get
        {
            return decimal.Parse(ConfigurationManager.AppSettings["MaxDeposit"]);
        }
    }
}
// DepositCalculator.cs
public class DepositCalculator
{
    public DepositCalculator(IDepositSettings depositSettings)
    {
        _depositSettings = depositSettings;
    }

    public decimal CalculateDeposit(decimal basketTotal)
    {
        var deposit = basketTotal / 100 * _depositSettings.DepositPercentage;

        deposit = Math.Max(deposit, _depositSettings.MinDeposit);

        deposit = Math.Min(deposit, _depositSettings.MaxDeposit);

        return deposit;
    }

    private readonly IDepositSettings _depositSettings;
}

Now we’ve got our configuration separated from our logic, you can easily see how this is much more unit testable as we can mock out our deposit settings without concerning our self with our actual settings.

Default values?

Default values I hear you say? Well now our configuration is separated we can easily just change our AppSettings implementation to set our default values.

// AppSettings.cs
public class AppSettings : IDepositSettings
{
    public decimal DepositPercentage
    {
        get
        {
            return decimal.Parse(ConfigurationManager.AppSettings["DepositPercentage"]);
        }
    }

    public decimal MinDeposit
    {
        get
        {
            decimal minDeposit;
            if (!decimal.TryParse(ConfigurationManager.AppSettings["MinDeposit"], out minDeposit))
            {
                minDeposit = 0;
            }

            return minDeposit;
        }
    }

    public decimal MaxDeposit
    {
        get
        {
            decimal maxDeposit;
            if (!decimal.TryParse(ConfigurationManager.AppSettings["MaxDeposit"], out maxDeposit))
            {
                maxDeposit = decimal.MaxValue;
            }

            return maxDeposit;
        }
    }
}		

As you can see if we added the parsing logic in to our DepositCalculator method for calculating the deposit amount, it would be far more complex and on edge of maintainable!

Crashing apps!

You’ve just done a merge or checked out someone else’s code and it’s taken you 20 minutes to get it to building state. You spin up the application, flip though a few pages then “Server Error” - the yellow screen of death! You do a bit of debugging and realise its missing of one them AppSettings within the app.config file, how annoying!

Wouldn’t it be nice if our app just pre-loaded and parsed our AppSettings when the application started? Then we would know upfront if someone missed out one of the AppSettings.

So seeing as our Settings are using a common interface we can create a PreloadedSettings class for example.

// PreloadedSettings.cs
public class PreloadedSettings : IDepositSettings
{
	public PreloadedSettings(IDepositSettings depositSettings)
	{
		_depositPercentage = depositSettings.DepositPercentage;
		_minDeposit = depositSettings.MinDeposit;
		_maxDeposit = depositSettings.MaxDeposit;
	}

	public decimal DepositPercentage
	{
		get { return _depositPercentage; }
	}

	public decimal MinDeposit
	{
		get { return _minDeposit; }
	}

	public decimal MaxDeposit
	{
		get { return _maxDeposit; }
	}

	private readonly decimal _depositPercentage;
	private readonly decimal _minDeposit;
	private readonly decimal _maxDeposit;
}

We can now plug this in to our IoC container to create a singleton instance that gets created as soon as the container is built.

It’s actually quite amazing how common this is. I’ve been in situations where an application has been pushed live and it’s been half running for at least a week until someone raised a bug, it was then discovered after a lot of developer effort that a missing config value from a previous release was the problem.

Database

There comes a point where the application is running on multiple nodes and keeping all them config files up to date and allowing them to be changed easily without any human error becomes an issue. Such as our Deposit settings, we’d love to hand these off to the business owners so they can keep updating the deposit amounts on a weekly basis, they’d like that too as our competitor are always fluctuating their deposit rates.

So the only logical solution here is to move them in to a database and give our owners a nice pretty front end. That’s pretty easy to do now we’ve got our abstraction of IDepositSettings.

// DatabaseSettings.cs
public class DatabaseSettings : IDepositSettings
{
	public DatabaseSettings()
	{
		_settingsRepository = new MongoSettingsRepository();
	}
	
	public decimal DepositPercentage
	{
		get
		{
			return _settingsRepository.GetDepositPercentage();
		}
	}

	public decimal MinDeposit
	{
		get
		{
			return _settingsRepository.GetMinDeposit();
		}
	}

	public decimal MaxDeposit
	{
		get
		{
			return _settingsRepository.GetMaxDeposit();
		}
	}

	private readonly ISettingsRepository _settingsRepository;
}

Caching

Our DatabaseSettings isn’t going to scale very well in production. We could use our PreloadedSettings which we made earlier, it will cache the settings in process so that the settings are not fetched for the rest of the application life. This isn’t ideal though as when our business owners want to change the deposit amount from 5% to 10% we would have to tell them we need to restart all the applications running so that they can then fetch the data again from the database.

We need the ability to limit the load on our servers but also have the feel that it’s updating in real-time. Even caching strategies of 1 minute are worthwhile. Say you have 30,000 people within a 3 minute time frame hitting a page for the same setting (or any other resource), if we’d of cached that content on the server for 1 minute we’d of benefited by 10,000% as We’d have only hit the database 3 times. Also from our business owner’s point of view waiting a minute for the update to propagate is nothing, they’ll probably not even notice.

This also allows for us to continue running if our settings database server goes down for maintenance, if it can’t fetch up to date settings we can make it continue running with the old settings until the server is back up.

As you can see caching is always a win win! So let’s get on with the code.

// ICacheProvider.cs
public interface ICacheProvider
{
	T GetOrSet<T>(string key, Func<T> fetchFunction, int? ttlSeconds);
}
// CacheDepositSettingsDecorator.cs
public class CacheDepositSettingsDecorator : IDepositSettings
{
	public CacheDepositSettingsDecorator(IDepositSettings depositSettings, ICacheProvider cacheProvider)
	{
		_depositSettings = depositSettings;
		_cacheProvider = cacheProvider;
	}

	public decimal DepositPercentage
	{
		get
		{
			return _cacheProvider.GetOrSet("depositPercentage", () => _depositSettings.DepositPercentage, 60);
		}
	}

	public decimal MinDeposit
	{
		get
		{
			return _cacheProvider.GetOrSet("minDeposit", () => _depositSettings.MinDeposit, 60);
		}
	}

	public decimal MaxDeposit
	{
		get
		{
			return _cacheProvider.GetOrSet("maxDeposit", () => _depositSettings.MaxDeposit, 60);                
		}
	}

	private readonly IDepositSettings _depositSettings;

	private readonly ICacheProvider _cacheProvider;
}

Multiple interfaces

We can take this a stage further by using multiple interfaces for our AppSettings, this then adheres to the interface-segregation principle (ISP) as when we inject these settings in to the consuming class it only knows about the settings that it requires and doesn’t need to know anything about what these settings relate to.

public class AppSettings : IDepositSettings, IPaymentSettings, IUxSettings
{
	// Snips...
}

public class DatabaseSettings : IDepositSettings, IUxSettings
{
	// Snips...
}

public class CacheSettingsDecorator : IDepositSettings, IUxSettings
{
	// Snips...
}

Wrapping it up

Keeping our settings abstracted away from the main business logic of the code makes it much easier to maintain going forward. I’ve seen many solutions where the app.config files have over 100+ AppSettings and have resulted in an unmaintainable state with usage of them is scattered over the codebase. It would take a significant amount of time to move these out in to some other location such as a shared data store.


Kevin Smith

Developer, technology enthusiast and @dotnetsheff organiser.