Continuous integration is a software development practice where team members continuously integrate their work into a single code base. Each developer must submit their changes to the shared repository at least once a day (preferably every few hours).
The merge with the work of other team members should be trivial, since the shared code should be at most only a few hours late.
Working with a centralized repository brings a number of advantages but also many potential problems. Again, automated testing can help a lot.
We need to feel comfrotable including or changing new code, and also pulling the changes made by the other team members at all times. For this to be possible, everyone must write automated code (unit tests, integration, and end-to-end) for all functionality, and never version code that contains failing test. The versioned code must always have all the unit and regression tests passing. If any test fails it is our obligation to give priority and correct them.
In the old waterfall approach, we had a ceremony that needed to be held to validate whether a new version of the product could be published in production. With the evolution of the adoption of agile development and practices of eXtreme Programming, today we can automate much (if not all) of this regression and validation testing process. This significantly shortens the time interval between developing and publishing a new functionality in production or, in other words, significantly reducing our lead time.
When we combine automated testing practices with continuous integration, we arrive at a situation where we always have in our shared repository a version of the product that can be deployed in production at any time. This would be totally unfeasible if there were no regression tests that ensured that the new codes are not causing unwanted side effects.
Configuration management
Ideally the changes should be developed and deployed in production gradually. Any new functionality that requires many changes or new features should be broken into minor modifications that can be deployed individually. This way the system evolves gradually.
But there are times that for technical reasons or business strategies, we are obliged to release changes with a broader scope, making the previous suggestion unfeasible. That is, we need to work on a certain functionality for more than one day, and can even weeks. In these situations we have two possible approaches: using feature branches or adopting feature toggles (also known as feature flags).
Feature branches
The idea behind so-called feature branches is simple: when changing or starting the development of a feature, a branch to work exclusively on this part of the application is created. when the work is done, a merge (or pull request) is made to the shared central repository.
In this approach, multiple branches are kept with active development in parallel, so each developer's code can become quite out of sync with the other versions. The consequence is that there is an great effort and a high risk when it comes to synchronizing the branches, making these merges painful. But it's the only way to integrate everyone's code.
Feature branches (or any other type of branch) are strongly inadvisable when working with the concepts of continuous integration. We prefer to adopt other solutions, such as features toggles.
Feature toggles
"If it hurts, do it more often" — Martin Fowler
In eXtreme Programming when a practice is necessary but at the same time painful we choose to practice it as often as possible. The shorter the time interval between merges, the less pain. Although it is not possible to eliminate it completely at least it will be distributed throughout development, making it a little more bearable.
Rather than having a painful and risky operation at the end of the project, we prefer to take appropriate precautions over time to avoid further risks in the future.
When we work in an integrated way, situations can occur where we have in our shared repository some code that is not yet ready to be delivered in the production environment. In these situations they can choose to use feature toggles. It is a simple pattern that allows changing our applications dynamically. We can turn a system feature on or off whenever we want. This enables the same code base (the same build) to have different behaviors depending on the situation. We can then, for example, have a certain functionality released in the development environment but unavailable in the production environment.
In practice it is quite simple to achieve this goal and there are several ways to implement this pattern.
How to implement feature toggles
Let's assume that we are working in a banking system and face the following problem: the way we calculate the current account balance is too slow. We need to optimize this part of the system. For this a team had a great idea, but it will take a few weeks before it is implemented and validated.
Instead of working on a branch, we can choose to create a toggle feature called OPTIMIZED_BALANCE_CALCULATION. This way the calculateBalance method could be refactored as follows:
public Amount calculateBalance() {
if (Features.OPTIMIZED_BALANCE_CALCULATION.isActive()) {
return calculateOptimizedBalance();
} else {
return calculateBalanceWithoutOptimization();
}
}
The idea is that the isActive method returns true only in the development environment or only on the workstation of the developer responsible for implementing this functionality. This way the optimized balance calculation code, even if incomplete, can be distributed in production, since it will never actually run in this environment. In this example I chose to use a simple if, but we could use the factory pattern or some dependency injection framework.
In practice we can see the feature toggle as a kind of branch, but it is maintained on the same code base, which significantly simplifies software configuration management.
While it's fairly easy to implement all the logic of feature toggles manually, I recommend that you use a library for this purpose. Some options are: togglez (Java), fflip (JavaScript), NFeature (.NET), among many others.
Different types of toggles
In this article I use the term feature toogle in a purposely generic way, with the objective of only explaining the fundamentals of this concept. Some authors adopt more specific terms according to context. Depending on the scenario we call them release toggles, experiment toggles, ops toggles, permissioning toggles, ...
At its core the implementations are very similar, although the purpose is different in each case. In this article I use the term feature toggle to group all these types of toggles.
Branches and feature toggles should be your last choice
"Release toggles are a useful technique and lots of teams use them. However they should be your last choice when you're dealing with putting features into production. Your first choice should be to break the feature down so you can safely introduce parts of the feature into the product." — Martin Fowler
Although there are several situations where it is tempting to use feature toggles or even branches, we should avoid these features as much as possible. Ideally, always try to break functionality into smaller pieces that can be deployed in production independently. This is the most appropriate way to think about continuous integration.
Being able to break a large functionality which initially seemed to be indivisible into small parts that can be completed in a matter of a few hours is a difficult task and requires some effort. But there are rewards!
By breaking into smaller deliveries, the end user can take advantage of the improvements more quickly, and it is not necessary to wait until the completion of all parts so that they can begin to benefit from the work being developed.
Another positive consequence when you can anticipate part of the delivery is that we have faster feedback. Sometimes we have doubts as to whether the path we're taking is the best. Reducing this time interval and being able to validate our solution and evolution directly with end users is always very important, since we can change our path if necessary.
When we put new functionality into production, in addition to direct user feedback, we also have feedback on the metrics collected by our monitoring systems. This allows us to know, for example, what was the impact on the performance of each small change. There are situations where a small change already brings benefits beyond what is expected, but we can have scenarios where unwanted side effects occur and our changes have a behavior inverse to what is expected! It is important to be able to identify these situations and correct them as quickly as possible.
Conclusion
Continuous integration is one of the eXtreme Programming practices that in theory is very easy to apply: just integrate in a single central repository all the source code of our system every few hours. But in the real world it's more complicated than that. For this practice to be feasible, it is necessary that every team member is willing to implement automated tests on all functionalities.
Feature toggles can assist us in certain situations, but should be avoided. Whenever possible, we prefer to break the larger features so that their smaller parts can be deployed to production gradually.
References
- Extreme Programming Explained: Embrace Change, Kent Beck, Cynthia Andres.
- Continuous Integration: Improving Software Quality and Reducing Risk, Paul Duvall, Steve Matyas, Andrew Glover.
- Frequency reduces difficulty, blog post by Martin Fowler.
- Feature Toggles (aka Feature Flags), blog post by Pete Hodgson.
Top comments (0)