Granular config
Because we don't just have dev and prod.
@MrDanack
Press 'c' to toggle code style
Press 's' for speaker notes
I'm going to tell you a story. It's a story that most people should recognise at least parts of.
In the beginning, there was dev
$dev = [
'user.authentication_service' => 'spoof',
'logging.targets' => ['on_screen', 'local_file'],
'cdn.service' => false,
'payment.gateway' => 'spoof'
// 30 more lines here, but they
// don't fit on the screen.
];
In the beginning, there was just dev and we only needed one config.
This was the last time we were truly happy.
Oh, you want this code deployed somewhere
$live = [
'user.authentication_service' => 'standard',
'logging.targets' => ['sas_loggly'],
'cdn.service' => 'fastly',
'payment.gateway' => 'sagepay'
];
Well, then we had to put our code live. Which honestly probably wasn't a good idea, but we needed to do it to get paid.
So we duplicated the dev environment and changed a few things.
And all was good for a while.
Then after we broke stuff in production, we realised that maybe having a staging environment might be a good idea, and we needed a continuous integration environment for that.
Stop breaking live pls
$staging = [
'user.authentication_service' => 'standard',
'logging.targets' => ['local_file'],
'cdn.service' => false, // changed
'payment.gateway' => 'standard'
];
So we added a staging environment, by duplicating the live environment and tweaking it a bit.
And all was good for a while.
Then we realised that maybe, just maybe running unit-tests might be a good idea.
Unit tests are a thing
$ci = [
'user.authentication_service' => 'spoof',
'logging.targets' => ['null'], // changed
'cdn.service' => false,
'payment.gateway' => 'standard',
'user_data.service' => 'spoof',
'repository_type' => 'in_memory', // changed
];
So we duplicated the staging config settings and tweaked them a bit to add a CI environment.
And all was good for a while.
Users....
We found out that maybe our users found it difficult to use our site.
Honestly, not our fault - but apparently it's not acceptable to blame the stupid users.
UX people wanted to be able to change stuff
$ux_research = [
'user.authentication_service' => 'spoof',
'logging.targets' => ['on_screen', 'local_file'],
'cdn.service' => false,
'payment.gateway' => 'spoof',
'feature.signup.ui' => 'ui_2016_09', // changed
'user_data.service' => 'standard'
];
Then those asshole designers wanted to be able to try out new designs and get feedback from the gottamn users, without having a dev hard-code the changes in the code.
So we duplicated the dev config block, and changed a few bits.
And all was good for a while.
South west trains
South west trains, is a thing.
Remote workers can't access internal services
$remote_dev = [
'user.authentication_service' => 'spoof',
'logging.targets' => ['on_screen', 'local_file'],
'cdn.service' => false,
'payment.gateway' => 'spoof',
'feature.signup.ui' => 'standard',
'user_data.service' => 'spoof' // changed
];
And then we realised that if we ever want to allow people to work remotely, where they don't have access to all of the internal services, we need to have a new config block
So we duplicated the dev config block again.
And all was good for a while.
Server outage after cleaner unplugged 'the rack'
Deploying to different host
$live_aws = [
...
'logging.targets' => ['s3_logging'],
'cdn.service' => 'cloudfront',
'file_storage.service' => 's3',
...
];
And then we has a customer that didn't want us to run the software on hardware that was in a cupboard in the office, and instead they wanted us to deploy it to AWS
So we duplicated the live environment and modified a few bits of config.
?And all was good for a while?
Seven different configs. All unique.
dev + remote + ux_research + test + staging + live_cupboard + live_aws
\o/
So we've got seven different configs all of them different.
Is this actually good?
Seven different configs. All unique.
Ծ_Ծ
Problems
Need to change settings in multiple places.
No record of whether changes are deliberate or not.
Can't see easily how one environment is different to another.
Setting up a new environment is non-trivial.
Need to change settings in multiple places. This leads to people forgetting to edit stuff that should be changed.
If I show you these two config blocks, it's not possible to know whether this is a deliberate difference or whether it's a case of someone forgetting to update it.
Can't determine easily what an environment is trying to achieve, without comparing it to other environments.
In real apps where there are 20 or 30 things that need configuring, just setting up a new app environment becomes a burden, as you need to figure out what envs to copy.If something is difficult to do, people will avoid doing...."Do you really need a UX testing environment, can't you just hack the code to do what you want?"
Solution - granular config blocks
Shameless self-promotion alert
github.com/danack/configurator
The idea is simple - instead of having all the config stored in each environment, break it up into chunks.
Each chunk has a clear semantic meaning.
And then allow people to combine them however they want
My library is really quite shite - the idea is more important than the implmentation.
Default block gets applied to everything
$default = [
'user.authentication_service' => 'standard_auth_service',
'cdn.service' => 'fastly',
];
* always
* Shitty example in PHP - I use code for config....the syntax should be obvious,
* Default settings get used......by default
Each environment sets only the bits it cares about
$remote = [
'user.authentication_service' => 'spoof',
];
$dev = [
'cdn.service' => false,
];
$ux_research = [
'feature.signup.ui' => 'ui_2016_09',
];
Magic happens
Instead of just choosing "dev"
The configurator allows people to say they want the "dev,remote,ux_research" environment.
Which means apply the "dev", "remote", "ux_research" settings in that order.
* Solution granular controls. Instead of just having separate environments that are monolithic blocks, instead build the application config out of granular settings.
* Have "dev", "remote", "ux_research" settings, which means apply the "dev", "remote", "ux_research" settings in that order.
"dev,remote,ux_research"
$settings = array_merge([], $dev);
$settings = array_merge($settings, $remote);
$settings = array_merge($settings, $ux_research);
return $settings;
AKA the setting are combined in the order that the user chose.
Benefits
Most of the time, only need to edit a setting in one place, so less chance to forget.
Reading what is special for a particular environment becomes trivial.
Adding a new specific environment becomes trivial.
Can combine configs together for huge number of variation.
Adding a new specific environment becomes trivial. You only need to set the config bit that you want to tweak.
When something is set in a specific environment it is now obvious that this was a desired thing. Reading what an environment setting does becomes trivial. This is actually really useful when you just want to remind yourself what setting "uxtesting" actually changes.
Editing anything in default or in generic settings gets applied to all configs that use that config. So no need to go round editing the same thing in multiple places.
Setting up a new environment is always done as "I want it the same as this environment, but with this extra setting." You only need to set the one thing that needs changing and add the name you gave the env to your deploy tool.
Brucie bonus - storing config in code is awesome.
<?php
use AwesomeApp\Config;
$default[
// This is a class constant - very hard to miespeel
Config::SCRIPT_VERSION => date('jmyhis'),
// Oh, also, functions are a thing.
// Can generate absolute paths
Config::TEMPLATE_CACHE_DIR =>
realpath(__DIR__)."/var/cache/template",
];
Brucie bonus - storing config in code is awesome. Allows you to do things like this:
Class constants - autocomplete all the things. Any misspelling gets picked up as an error
Can run code. I deploy into a unique directory each time, and needed the absolute path. No need to pass it in or do "funny" config syntax,
<?php
// Using variables....
$siteName = 'awesomeapp.com';
//And string concantenation...ah yissss
$dev = [
Config::LIBRATO_STATS_NAME => 'dev.'.$siteName
];
$staging = [
Config::LIBRATO_STATS_NAME => 'staging.'.$siteName
];
$live = [
Config::LIBRATO_STATS_NAME => 'live.'.$siteName
];
Brucie bonus - storing config in code is awesome. Allows you to do things like this:
avoids duplication due to this amazing thing called variables.
Fin
github.com/danack/configurator
Granular config for apps.
Gone are the days where the only environments you had are dev and prod. These days you have many more environments and need a way of managing config that scales.
In this talk, I wibble on about how why you should build your config from granular parts, rather than monolithic blocks, so that you can scale your config to support environments for UX testing, people working remotely, unit-testing, etc etc without going insane.
“For years there has been a theory that millions of monkeys typing at random on millions of typewriters would reproduce the entire works of Shakespeare. The Internet has proven this theory to be untrue.”