I am working on a product that has been developed by multiple developers over the years.
The product is built on
- PHP
- jQuery
- Angular
- Bootstrap
The product has different modules separated by directories in the server. These modules are individual repositories in my BitBucket as well.
Fortunately, all the modules use the same database. In a code level, the configurations such as Database name, URLs (to access APIs, assets that point to the other folders in the server) are hard-coded.
We have 4 instances (dev, production, abc, abcdev). The hard-coded configurations are different for each instance. I have implemented Git very recently and have pushed all the code into the server.
The scope is to maintain a single repository and have different branches for the different instances. Since the values are hard-coded at so many places, merging between the branches would be very difficult
The way I feel I should be handling it is, to create another module named config
, add presumably a json
file that would have the URLs, database related information in it.
This file would obviously be different for each branch and this file would not be changed in the branches. Though this is all in theory, how do I go about implement it? Or are there any other better ways to handle this scenario? Any feedback would be appreciated! Thanks!
First, move all your config options into a configuration file (or files). Use a language agnostic format such as JSON or YAML.
Put the config file/directory at the top of the source tree where it's easy to find.
The scope is to maintain a single repository and have different branches for the different instances.
A single repo is good. Different branches are not. Managing many long-lived branches rapidly gets complex.
Instead have a single long-lived branch, probably master
. Use feature branches to isolate development and have them QA'd before they're allowed to merge into master
. That way master
is always ready to go. Deploy directly from master
.
Here's what that might look like. Each letter is a commit. Each [branch]
is a branch head.
I - J L - M - N [feature1]
/ \ /
A - B - C ----- F - G ----- K [master]
\ / \
D - E O - P [feature2]
This shows two completed features, D - E
and I - J
, have already passed QA and been merged into master
. Since QA has already been done on the feature branches, master
is deployed to production. There are two open feature branches, each of which periodically runs git rebase master
so they are up to date with the latest fully tested code.
Notice there are no direct commits to master
, it only changes via a feature branch merge. This means master
is always tested, reliable, and ready to deploy. Individual features in progress don't interfere with each other and can rely on a stable master
branch; if something breaks they know it's because of their work, not because someone broke the dev branch.
But what about the different configs?
This file would obviously be different for each branch and this file would not be changed in the branches.
Having the same config file be different in different branches is a great way to get conflicts and people accidentally overwriting the config with test and development info.
It's simpler to have one set of config files which has configurations for all the different contexts, plus a default for them to share.
For example, your database config might look like the following in YAML.
default: &default
adapter: postgresql
encoding: unicode
username: postgres
development:
<<: *default
database: myapp_development
host: localhost
test:
<<: *default
database: myapp_test
host: localhost
production: &production
<<: *default
host: our.production.server
username: production_db_user
client1:
<<: *production
database: client1
client2:
<<: *production
database: client2
This uses YAML anchors and aliases to do the overlays, <<: *default
says to include the node marked with &default
. This eliminates a lot of redundancy between the various contexts.
Have your app choose the environment from an environment variable, or guess it from context.
This is how Rails does it, multiple configuration files, each with configuration sections for each context.
The next step is to take production secrets out of the source code and put them into environment variables. Secrets are things like passwords and API keys. Then when your production systems are started, they're started with those environment variables set. This is how cloud hosting sites such as Heroku do it.
Alternatively you can hard code the production secrets in your config files, but encrypted. Then supply the decryption key via an environment variable. This means you only need one environment variable for production, which might be simpler if you're migrating an old system. This is how TravisCI does it.
It might be a bunch of work, depending on how your existing system and workflow are currently set up, but this is a very successful pattern.
12 factor apps is a opinionated guideline for building modern, maintainable and scalable applications.
Related to configuration it tells you to keep it on the environment. The recommended approach is to use environment variables.
The twelve-factor app stores config in environment variables (often shortened to env vars or env). Env vars are easy to change between deploys without changing any code; unlike config files, there is little chance of them being checked into the code repo accidentally; and unlike custom config files, or other config mechanisms such as Java System Properties, they are a language- and OS-agnostic standard.