We recently had to make a relatively big change to one of the more fundamental parts of the Lifetime Value Company application stack. At The Lifetime Value Company, we have some complex systems in place to quickly give users what they’re looking for. With a large audience and steady traffic, it can sometimes be difficult to roll out changes without breaking production functionality inadvertently. We wanted a good way to confidently deploy this change when it was ready, while also using iterative approaches and incremental delivery. We also didn’t want to leave a bunch of environment-specific conditionals all over the code, and we wanted an easy way to clean up when done. With these parameters in mind, we came up with the Rollout strategy.
For the sake of our example, we’re going to use a Rails application because that is what we have in our application environment. We will also assume that we are building a new feature named, say, “My New Feature 1.” (We know, real original name, am I right?)
To start, we will first create a configuration YAML file and place it inside
config/rollout.yml and add the following contents to it:
development: my_new_feature_1: true test: my_new_feature_1: true production: my_new_feature_1: false
Next, we parse this config file for the current running environment during the runtime of the application. To do this, we will load it inside
ROLLOUT_OPTIONS = YAML.load_file(Rails.root.join("config", "rollout.yml"))[Rails.env]
We then make a simple helper method that can be used anywhere within our application’s codebase, where we need to add the conditional for the new feature. To do this, we create the file
lib/rollout.rb with the following content:
class Rollout def self.active?(key) ROLLOUT_OPTIONS[key] || false end end
Now we can use this to control flow for the new feature while preventing it from affecting the production environment until we are ready for it to go live.
# existing code ... ... if Rollout.active?('my_new_feature_1') # implement new feature code here end
The cool thing is that you can just write your tests assuming that the new feature is rolled out since it will be enabled in the test environment. However, until the flag is flipped on in the configuration, this conditional will not get triggered in the production environment
Once the feature is active in the production and verified as working properly, the last thing to do is code cleanup. The good thing here is that all you need to do is search for references to the feature key, as defined in the config file, and just delete that conditional. So, cleanup also becomes simple.
This strategy provides an option for iteratively making changes to a critical part of your codebase. This may not apply to all scenarios of development; in some cases, you may actually not want to do it iteratively (e.g., with migrations that break existing database tables). But in some cases, this strategy makes for a simple and easy way to build things rapidly and with confidence. For the feature we were developing, this simple strategy made it very easy for us to develop, test, QA, and eventually deploy to our production environment.
At The Lifetime Value Company, our engineering team utilizes many such strategies to solve engineering problems on a case-by-case basis. We don’t fear failure, so we try out new things all the time while still being aware of the implications of our coding patterns. Thankfully, we can bounce ideas off of a team of skilled developers, architects, and engineering managers. If this sounds like a place you may be interested in working at, have a look at our careers page. Reach out to us if you would like to be a part of our team!