Configuration with OX
OX has no built-in configuration management system. Instead, the design of OX makes it easy to roll your own. Typically configuration will be a layer outside of your application, such that your configuration initializes your application.
This article describes one opinionated way to structure your application's configuration using Config::JFDI. There are many like it, but this one is mine.
Site-Specific Configuration
As an application grows out of its prototype and early design phases, it becomes more and more important that it adapt to each environment it runs in. For example, you might need to start running the production database on a different machine just to spread the load around. So you change the dsn
from localhost
to db.example.com
. Done deal!
Except now all the developers are testing their local changes against the production database. That's nuts!
What you really want is a configuration file for developers which specifies that their database lives on localhost
, and a production configuration file which points to db.example.com
. This approach scales to additional environments like a QA site, a continuous-integration machine, and so on. You might even have a second, unversioned config file where each developer can put config specific to their machine.
Implementation
MyApp::Config
The centerpiece of this opinionated design is an MyApp::Config
which is a class that, unsurprisingly, manages the configuration of this application. This class is responsible for deciding which config file to load (production? development?) and keeping track of the contents of that file. Here's what it might look like:
1: | package MyApp::Config; |
The filename
attribute looks at the MYAPP_CONFIG_SUFFIX
environment variable to decide which config file to use. The suffix corresponds to the environment the application is in. For developers the suffix might be dev
, and for production the suffix might be prod
. This means that Config::JFDI will try to load config/myapp.dev.yml
for developers and config/myapp.prod.yml
in production. (Config::JFDI supports loading many different file formats, but for config I prefer YAML.)
I like providing get
and as_hash
methods in my config class to hide the implementation details of Config::JFDI as much as possible. Otherwise, pulling a setting's value out would involve calling $config->config->config->{dsn}
, and that is an awful lot to ask of anybody. Instead, now $config->get("dsn")
suffices.
config/myapp.*.yml
The config file is simply YAML. The developer config might look like:
1: | --- |
The production config might look like:
1: | --- |
No problem!
app.psgi
We have a config file and a config loader, but these aren't actually being used anywhere. OX certainly won't intuit that these components exist, since explicit declarations are The OX Way. So how do we use our config?
The answer is probably what you'd expect: you load config to use when instantiating your application. You simply just pass the config variables to MyApp->new(...)
. Your PSGI script would then look like this:
1: | use MyApp; |
Since you're probably going to end up adding many different options, you'll want that to have as little friction as possible. After all, that kind of code is a breeding ground for merge conflicts. Instead, just pass everything the config specifies into MyApp->new
.
1: | MyApp->new( |
MyApp
The final piece is making MyApp
accept and use these new constructor parameters. Since MyApp
uses Bread::Board::Declare you can create services for each of these parameters with the familiar "has" in Moose syntax.
1: | has dsn => ( |
Finally, using the magic of Bread::Board you can wire these new services into your existing database
and view
attributes.
1: | has database => ( |
Developer-Specific Configuration
One of the many benefits of using a system like Config::JFDI is that it supports overlays. An individual developer can adjust specific settings in a file called config/myapp.dev_local.yml
which will be merged with the baseline config in config/myapp.dev.yml
. Say one of the developers runs their MySQL on a different machine. That developer can put this into a config/myapp.dev_local.yml
:
1: | --- |
Now, when DBI connects to the database on this developer's machine, it will get the correct dsn
. And it won't disrupt anyone else, because this config file is specific to that developer. Finally, since _local
configs inherit settings from the baseline config file, options like compile_templates
will trickle down from the ordinary developer config file.
This works especially well if you version config/myapp.dev.yml
and put config/myapp.dev_local.yml
into .gitignore
. That way if a developer adds new baseline config to config/myapp.dev.yml
, everyone gets it for free; no vigilance needed.