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 doesnt 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 thats really happening is theyre 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? Youre rewarded with code that people can misconfigure.
A new point of failure in your application.
But thats ok!
I hear you cry.
Ill write some documentation!
Ive got some bad news for you. That documentation gets out of date, or that person that uses your code doesnt even know exists.
But lets pretend for a second, that this is all ok.
Lets pretend we correctly configure our software for the environment its 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 couldnt open c:\program files (x86)\YourApp\UserData.
You take a look, and sure enough, the directory doesnt exist so you create it and try again and the app starts.
Friends dont let friends configure software
If you bought some software and endured that kind of user experience during installation and configuration, youd probably give up, yet we dont 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 isnt even required its just in place to cover the cracks where we couldve done better as developers.
Every time youre tempted to add a new configuration setting, ask yourself carefully
Is there any way my application can infer this configuration setting from its environment?
And every time youre tempted to add a custom installation step or an extra line to your setup guide, think
Can my application verify its 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 shouldnt be externally configured.
If youre building software and you need to deploy it to multiple target environments, youre probably going to end up with some kind of application specific configuration, defining environmental settings, database connection strings, and URIs that you depend on. Theres 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.
Lets 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 dont 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. Its 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. Ive 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.
Well 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 wouldve 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 its configured and doesnt blindly read a text file and just go with it.
Ive 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
Weve talked about how regulating deployment environments allows you to configure your software by convention, how predictability makes this easy and divergence makes it hard.
Weve discussed how allowing your application to verify and create its local dependencies relieves the administrator from doing this mundane tasks.
Weve 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, weve looked at how trivial configuration templating can remove much of the friction and duplication from the small amounts of required configuration.
Its 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.
Youd 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.