Over the years I have seen many configuration files. Most of them were unusable. There are many reasons for unusable configuration files. What I've learned from looking at large configurations are those main points:
Often configuration files use the wrong values. Developers tend to use
true/false for switching options on and off.
track-users = true
The configuration is easier to understand when instead of
true/false one uses
on/off, it's more relevant to the domain
tracking-users = on
Sometimes there are double negations like
no-track-users = true
which leads easily to wrong configurations and is harder to understand. This also leads to inconsistencies like
track-users = on no-paypal = off creditcard = on
Do not use double negation, best stick with positive switches where possible.
2. Lists and Types
I'm a friend of typing data. This also goes for configuration values. Your configuration system should support simple types like Strings, Numbers and Lists.
backends = ['backend1', 'backend2', 'backend3'] port = 8080 name = "TestDB"
Then a configuration checker can check the values for typos, like
port = 808x0 backends = ['backend1' 'backend2', 'backend3'] // backends = backend1 backend2, backend3
Lists make configuration files easier to read and shorter. If you have no List support, configurations tend to look like this:
backend.1 = backend1 backend.2 = backend2 backend.3 = backend3 ... currency = dollar currency = euro
Compare this to the clean List code above.
Configuration options should be descriptive. Often they tend to use lots of true/false configuration switches.
database = true redis = false cassandra = false
If possible, its better to replace true/false switches with possible options:
// DATABASE, REDIS, CASSANDRA database = REDIS
4. Hierarchical configurations
Flat configurations are hard to read. To group configuration keys with flat configurations you need to repeat yourself with namespace values. I know many do not like mixing property style and XML style, but I've learned that this works really well for configurations (YAML is another hierarchical option).
<database> server = 127.0.0.1 port = 8080 name = "TestDB" </database>
is more readable and clear than
database.server = 127.0.0.1 database.port = 8080 datbase.name = "TestDB"
ordermanagement.database.server = 127.0.0.1 ordermanagement.database.port = 8080 ordermanagement.database.name = "TestDB"
5. Application configuration, server instances and environments
The configuration for one of your application instances is a combination of the environment the instance runs, the application configuration and the server instance.
// application code database-url = jdbc://$DATABASE.server//$DB callback-url = http://$HOST/callback
Both database-url and callback-url are application configuration values, $DB is depending on the environment (development, production, test) and $HOST is depending on the server instance. Environment solution can easily be done in different configuration files, e.g. put them into production, development and test directories. This enables you to put all configurations into your source control system.
6. Inversion of control and DRY
Your configuration framework should support inversion of control or dependency injection, I'm not sure what to call it. Do not define the database server IP in every client, define it in the database server configuration and inject/reference it into every client configuration.
// database server config host = $HOST
and the client
// client config database.server = $DATABASE.host
// client config database.server = 127.0.0.1
Client does not declare the database server, the client determines at startup the runtime config by getting the values from the database server. This can most easily solved with a configuration service, it's harder to solve with configuration files.
7. Write a configuration checker
Of utmost importance is to write a configuration checker. Many production problems arise from wrong configurations. Apache supports a config test before you restart the application by running
The checker should check typos, not resolved dependecies, not resolved variables, type checks (String, Number, List, IP, ....) and mandatory values.
Writing configuration in the wrong way leads to maintenance and production problems. Do not neglect the configuration of your application, you need to put thought into it or it will turn to an unmanageable mess.