One of the things which has bitten me more than once as a developer is when pushing code from development to QA / Staging / Production and suddenly something doesn’t work the same way. I then spend then next 2 hours trying to figure out what went wrong.
This can be caused by a number of different factors, a couple of which are
- Platforms between dev/qa/staging/live are all configured differently
- Code needs to behave different depending on the environment
Luckily 1 is pretty much sorted these days, using a configuration management tool like Chef or Puppet keeping environments in sync is much easier than it used to be.
I personally prefer Puppet over Chef but thats most likely because that is what I have been exposed to the most in my current role but I am sure Chef is equally as awesome once you get to understand its syntax and learn its gotcha’s.
For this blogpost I want to concentrate on item number 2 which is something which continues to bite me every now and again when working on legacy code.
Whilst all of the new code written in the last year or so has been environment agnostic there is still some legacy around which has some wonderful checks in it which looks a bit like
if (Environment::isQA() || Environment::isStaging())
{
// Do something different here such as
// get different DB connection details or enable caching
}
The “something different” could be anything from switching database credentials to enabling caching and these methods are usually checking something like the servers hostname to see if it matches a known pattern. If you then happen to give your staging server a rebuild and name it differently then all hell can break loose.
This can be solved by pushing out configuration files via Puppet which are configured for each environments requirements and can contain everything from database connection details to what types of caching should be enabled. These configuration files are completely separate from your application so you could if the need arises change all of the database login credentials without needed to redeploy all of your applications, all that would be needed would be to push out the new configuration files via Puppet and the application will just trust what it has been given.
An example configuration file might look something like
[db]
host = localhost
username = dbuser
password = dbpassword
dbname = dbname
[cache]
fullpage_enabled = 0
memcache_enabled = 0
[memcache]
server[] = localhost:11211
server[] = localhost:11212
server[] = localhost:11213
By modifying this configuration file you could completely simulate “live” on “dev” with no messing around with code / application changes which might then get committed by accident. Your application can just check what should be enabled using something like
if (Environment::useMemcache())
{
// If configuration file says memcache_enabled = 1 then use memcache
}
$db_details = Environment::getDatabaseDetails();
Using this method switches your code from having any knowledge of what should be done in a particular environment to just following the configuration it has been given.
I am sure there are many other ways to do this and I would love to hear how others have solved similar problems.