Cascading YAML config system?
The apps I’ve been writing lately have their own config in YAML files, which is sort of a natural evolution of the old global constants I had been using before. And just yesterday I was thinking about how I want to better organize the text in the files and I came up with a way to cascade my configurations. That is, a naming convention plus a deep merge utility for Hash objects.
The Problem
The problem is that I started to have bunches of YAML config files that started looking like:
awesome_app.production_host.customer_site.yml awesome_app.production_host.business_site.yml awesome_app.production_host.admin_site.yml awesome_app.test_host.customer_site.yml awesome_app.test_host.business_site.yml awesome_app.test_host.admin_site.yml awesome_app.development_host.customer_site.yml awesome_app.development_host.business_site.yml awesome_app.development_host.admin_site.yml
Let’s say I’m on my development host: I have 3 Rails apps that run as a suite, hence the 3 development_host files. Each of those files contains a configuration like:
application:
name: Awesome App Customer Site
email:
contact_addresses:
ads_contact: advertising@awesome_app.com
support_contact: support@awesome_app.com
logs:
base_directory: /home/awesome_user/logs
rotation_frequency: daily
What differs from app to app in this case is the application name. The business site might have instead:
application: name: AwesomeNet: The Awesome Business Portal ...
and the admin console might have:
application: name: Awesome App Administration ...
Thinking Ahead
Right now the configurations are relatively small so keeping 3 files up to date isn’t too bad. But I’m starting to see that cutting-and-pasting is not going to work long term. So I need a way to simplify the configurations but still allow for flexibility.
So what if we factored out the common bits into one YAML file and then just applied the deltas? Then I’d have 4 configuration files:
awesome_app.development_host.common_bits.yml awesome_app.development_host.customer_site.yml awesome_app.development_host.business_site.yml awesome_app.development_host.admin_site.yml
Thus the common_bits file will have:
email:
contact_addresses:
ads_contact: advertising@awesome_app.com
support_contact: support@awesome_app.com
logs:
base_directory: /home/awesome_user/logs
rotation_frequency: daily
And the other three files will have their names of their applications:
application: name: Awesome App Customer Site
application: name: AwesomeNet: The Awesome Business Portal
application: name: Awesome App Administration
Then using a utility like Steve Midgley’s Deep Merge gem (Download) I can use common_bits as the base configuration and deep merge the individual site configurations on top of that. e.g.
app_name = 'awesome_app' host_name = 'development_host' site_name = 'business_site' all_sites_config_file = app_name + '.' + host_name + '.common_bits.yml' site_config_file = app_name + '.' + host_name + '.' + site_name + '.yml' # Use all_sites_config_file to start, then apply site_config_file $GLOBAL_CONFIG = YAML.load_file(all_sites_config_file) $SITE_CONFIG = YAML.load_file(site_config_file) $GLOBAL_CONFIG.deep_merge!($SITE_CONFIG)
I was pretty happy with that, then I made an observation…
Naming Convention Implies Hierarchy
What if we used a hierarchy of dots? That is:
awesome_app.yml # Master config awesome_app.development_host.yml # Development host config awesome_app.development_host.customer_site.yml # Dev customers awesome_app.development_host.business_site.yml # Biz network awesome_app.development_host.admin_site.yml # Admin
Huh. So using periods as a kind of separator I can create a kind of cascading configuration.