We need to talk about configuration.

The vast majority of software that you build and use needs to be configured – feature toggles, file and directory paths, startup options. We build configurable software every single day – especially if you build line of business apps.

When we do this we somehow forget that all of the worst software experiences you have suffer from horrible installation and configuration processes.

Ever had to set up a SharePoint cluster?
Muddle through SQL Server replication configuration?
Nobody ever had a good time configuring an application.

Conversely, all the software that you love is defined by its slick user experience. Great software doesn’t require configuration, it just works.

Every time I watch a developer add “just another configuration value” or “just add something extra into the installation script”, I feel a pang of sadness, because I know all that’s really happening is they’re setting themselves a trap for later.

Configuration code takes time to build – you have to build parsers, or pick meaningful names for configuration settings.

You have to plumb those settings into your code somewhere.

For all this work? You’re rewarded with code that people can misconfigure.

A new point of failure in your application.

“But that’s ok!”

I hear you cry.

“I’ll write some documentation!”

I’ve got some bad news for you. That documentation gets out of date, or that person that uses your code doesn’t even know exists.

But lets pretend for a second, that this is all ok.

Lets pretend we correctly configure our software for the environment it’s deployed into, with correct database connection strings, paths to dependent services, and weird internal settings for physical paths on the local machine.  You start the application and it crashes. 

So you trawl through your error logs, and find an exception log that says something like

Application failed to start due to DirectoryNotFoundException – couldn’t open c:\program files (x86)\YourApp\UserData”.

You take a look, and sure enough, the directory doesn’t exist – so you create it and try again and the app starts.

 

Friends don’t let friends configure software

If you bought some software and endured that kind of user experience during installation and configuration, you’d probably give up, yet we don’t think twice about exposing our teams in dev and ops to this kind of complexity every single day.

To make things worse, in my experience the vast majority of configuration in setup that applications go through isn’t even required – it’s just in place to cover the cracks where we could’ve done better as developers.

Every time you’re tempted to add a new configuration setting, ask yourself carefully

“Is there any way my application can infer this configuration setting from it’s environment?”

And every time you’re tempted to add a custom installation step or an extra line to your setup guide, think

“Can my application verify it’s environment and do this task at startup?”

Convention and inference are the antidotes to configuration and complexity in setup, and I want to spend a bit of time explaining how you can infer and discover the vast majority of behaviours and settings you think that you need to configure.
We should aim for our software to be safe and configured by default.

 

Why do we configure?

To understand why we implement configuration, we need to look at the kind of things that get configured.

We configure software…

  • To adjust the behaviour of the application at install time
  • To adapt the application to multiple environments
  • To configure aspects of our applications functionality

Of the things we configure, you can split the types of configuration into a couple of categories

  • Configuration pointing to environment-specific instances of dependant services
  • Configuration that toggles application behaviour
  • Configuration that deals with the internal behaviour of the software being installed

 

Environment specific configuration is frequently unavoidable, often repetitive and badly factored.

Feature toggles and internal configuration are much more contentious, frequently defining functionality that shouldn’t be externally configured.

If you’re building software and you need to deploy it to multiple target environments, you’re probably going to end up with some kind of application specific configuration, defining environmental settings, database connection strings, and URIs that you depend on. There’s a certain amount of necessity to environmental connection strings, but we can make it as simple as possible.

Unfortunately, software that requires a lot of configuration and setup, is by definition more difficult to deploy, one of our key goals in building reliable, deployable software should be supporting simple automation.  Simplicity in usage and installation is mandatory, not optional.

Let’s talk about how we can make our software configuration simpler.

 

Always verify your environment

Your application should attempt to verify the existence of absolutely everything it depends on, creating things as it needs to.

This means that if you application requires certain data directories that don’t exist, or has expectations of other networked resources, it should detected, verify and create them at application startup.

Failure detect dependencies it cannot create should be a fatal error, while the lack of a dependency it can create should result in those dependencies being correctly created.

This includes

  • Creating queues
  • Registering topics on event busses
  • Creating directories
  • Setting permissions
  • Making exploratory calls to web-services

You application should refuse to startup in an invalid state, and ideally, should be able to create its entire execution environment from sensible defaults.

Keeping these checks in the codebase of the application, and executing them at startup rapidly reduces time to resolution of any issues. It’s simple to implement, there is no excuse.

 

Regularity helps

Unpredictable environments have a cost on the complexity of application configuration.

Do your best to ensure that all the target environments you deploy to follow regularly patterned names, and verify these strong conventions.

Use names like

  • service1.production-domain.com
  • service2.production-domain.com
  • service1.uat-domain.com
  • service2.uat-domain.com

This regularity in your environment makes mistakes trivial to spot, and reduces the need for verbose and error prone configuration.

Irregularly formed environments require a huge amount of configuration to automate, and that configuration will be brittle and get broken.

 

Discovery heuristics beat configuration

Anything that a human has to configure, a computer can probably do better. Consider having your software detect the environment it exists in, and configure itself appropriately.

You can lean on environmental variables, or even by detecting which services exist in the environment at startup either by connecting to them, or using a service registry of some kind.

This is an obvious technique to use if you have configuration for failover services – at runtime, your application can attempt to connect to both the primary and secondary service, and select which one to configure itself against. I’ve had good experiences using this technique to load balance across payment gateways that had a tendency to fail.

 

If you must configure, keep it dry and remove repetition

One of the absolute worst things I see regularly, is huge configuration files, and templated transformations, to repeatedly change a single portion of a templated string over and over.

We’ll end up with something like this

<appSettings>

<add key=”Server1” value=”http://someservice1.environment1.com” />

<add key=”Server2” value=”http://someservice2.environment1.com” />

<add key=”Server50” value=”http://someservice50.environment1.com” />

</appSettings>

and a transform file that looks like this…

<appSettings>

<add key=”Server1” value=”http://someservice1.environment2.com” />

<add key=”Server2” value=”http://someservice2.environment2.com” />

<add key=”Server50” value=”http://someservice50.environment2.com” />

</appSettings>

When a much simpler approach would’ve been to configure the application with a string template

<appSettings> <add key=”env” value=”environment1” />

<add key=”Server1” value=”http://someservice1.{env}.com” />

<add key=”Server2” value=”http://someservice2.{env}.com” />

<add key=”Server50” value=”http://someservice50.{env}.com” />

</appSettings>

and allow the application to splice the two strings together. This means that your environmental configuration files replace a single line of configuration – not 50. We can take this further and support overridden settings from environmental variables, all because the application now controls how it’s configured and doesn’t blindly read a text file and just go with it.

I’ve seen this remove literally hundreds of lines of configuration peppered through lengthy transformation files.

Never repeat yourself in a configuration file.

 

Allow important configuration to be overridden

Devise a general way to override any of your inferred or conventional configuration settings.

There will always be the odd environment or special circumstance that requires configuration, so you should make a point of it being the exception rather than the rule.

 

Configuration Is Hell

We’ve talked about how regulating deployment environments allows you to configure your software by convention, how predictability makes this easy and divergence makes it hard.

We’ve discussed how allowing your application to verify and create it’s local dependencies relieves the administrator from doing this mundane tasks.

We’ve also considered how detection heuristics can help you remove any notion of environmental configuration from your code, instead focusing on the services the app can discover when it starts up.

And finally, we’ve looked at how trivial configuration templating can remove much of the friction and duplication from the small amounts of required configuration.

It’s really easy to “just add more configuration” in situations where discovery heuristics and self-configuring applications are the correct answer, but configuring software is a user experience problem. 

You’d not accept it from boxed software you bought, so you should do your best to protect your teams from configuration, and the accidental bugs that occur when they get it wrong.

Leave a Reply