I’ll be posting some thoughts on how to configure a client side application. In a series of posts, I’ll be repeating myself, rewriting same things over and over, as this is a work in progress as I try to distill it to a widely understandable form.
Here are some requirements that a versatile configuration system should support:
With any of these tiers/channels, it should be possible and predictable to inspect what configuration the build has without starting the build. For example, which backend endpoint will the current build talk to?
With any of these tiers/channels, it should be possible to dynamically override cherry-picked configuration options with some type of environment variables (actual environment vars, CLI options, cookies, different domain names loading the same codebase, HTTP query parameters etc.) Key factor here is that the configuration should be overridden before the app code is loaded. For example, I want to see if my previous production release will work with the development backend database after I performed some migrations on it.
It should be ideally possible to have the same codebase shipped to different tiers/channels, platforms (browser, Electron, macOS, Windows, Linux etc.) and for it to act differently based on the configuration.
It should be possible to inspect all of the possible configuration options in a single place. For example, I want to see how many variations of backend endpoints (dev, staging, production) an app can possibly talk to. This is a horizontal slice over the configuration.
It should be possible, with a single command, to generate a config for a certain tier/channel and platform combination, and to inspect it in close to real-time fashion. For example, I want to see which backend endpoint the app within the beta tier will use. Real-time feedback is very important because it allows experimentation, extensibility, REPL-inspired workflow. This is a vertical slice over the configuration.
It should be possible to verify the configuration’s critical settings in an automated way. In other words — unit testing for configuration. For example, analytics tracking should be turned off for UI testing on CI/CD systems because it will pollute the tracking data. Production configuration should always point to a stable backend endpoint.
The file format of the configuration should be non-proprietary, widespread, human readable. JSON is perhaps ideal.
The configuration should be nestable in a declarative fashion. For example, beta apps should have the same options as the production apps, except a few deviations (feature flags, a different icon etc.)
Developers should be able to work (extend, verify) with the configuration on any OS.
Configuration should be widely accessible in the app codebase, without any glue code.
Configuration shouldn’t be parsed per-key in application code. For example, instead of
MyConfigModule.backendEndpoint it should be
MyConfigModule.valueForOption(“backendEndpoint”). This allows to have less glue code and simplifies extensibility.
If a key is missing in the previous example, the app should crash early and fast during the development stage.
All of the variations need to be moved to the configuration. This is so called configuration polymorphism. For example, instead of
if MyConfig.isDev then devEndpoint else prodEndpoint, use
MyConfig.endpoint and have two separate configs a for Dev and Prod environments. This encourages reuse, removes duplication, allows to inspect config without starting the app.
Configuration should be inspectable in the running application.
It should be easy to inspect which changes (commits) have been deployed to a particular tier/channel.