Introduction
The present – the fleeting moment that everyone inevitably lives. But how many such moments happen “without us”, because we’re on a parallel thread, reliving the past, or planning the future? A tricky answer to give, certainly. How many of us would like to live more in the present moment? We could speculate that many, based on the growing popularity of the mindfulness phenomenon or meditation, give just a couple of examples.
An unexpected analogy
No, we aren’t going to talk about meditation or mindfulness in this article. But let’s ask ourselves a question: As software developers, is there an area of activity where we are exposed to the risk of being disconnected from the reality “in the field”?
Let’s assume that our team is working on a new, complex feature, within an application. What is the strategy for integrating this feature? Most likely, we’ll aim for work in isolation, on a different branch than the main one. This way, we’ll avoid interfering with the product that’s already in use by our customers. When the new feature is ready to be delivered to the customer, this is simply integrated back into the main branch and everyone’s happy. Did you smile?
The reality is that the paragraph above translates to weeks or months when the actual feature development is the smallest of problems. The integration though, is oftentimes a headache. In the equation there are other teams that work on their own features, on their own branches, and the process is repeated x times.
All the teams that work in parallel on different branches are disconnected. Not only from the main branch, but also disconnected between themselves.
The present moment is something that merely happens for most of the time, and is not often determined by contributions from all the active members on a project.
A popular integration model
Let’s give an overview of probably the most popular model of feature integration: git flow. A very organized model that sits at the base of many products.
The main approach of this methodology are the feature branches. Any new functionality is developed on a parallel thread and integrated into the main one when it’s ready. On top of feature branches, we are also talking of release branches, hot fix branches, main branch, develop branch. Keyword: branch.
The problem with this model is that each parallel thread in the image below represents a different reality, disconnected from all the others. Obviously, integrating these realities is not a child’s play. On top of the frustration brought to the developers, of of the biggest drawbacks of this approach are the rare release cycles.
CI/CD
The CI/CD concept is a modern approach to software development. Its main characteristics are the rapid integration of changes and the frequent delivery to the customers. This way, a continuous flow of features is ensured, as well as fixes for the possible defects.
A paradigm change
Four years ago, our team started work on a large project, where there were already other teams present, working intensely on transforming a product used already by majors customers. With an extremely rich roadmap and an interaction between the product team and customers at an enviable level, we wondered how it was possible for the development team to match this dynamic on the ground.
The recipe is complex, and it has been a continuous process of improvement during these four years. One ingredient has not changed though: fast and effortless change integration.
Meet trunk-based development (TBD), a branchless integration methodology, where everyone is connected to the present reality and nobody is wasting time with integrating others’ changes.
In TBD, there only exists a single branch, the trunk. The engineers integrate small changes often into this trunk, streamlining the review process and reducing risk.
Let us relieve a bit the initial shock of the word branchless. Committing directly on the only existing branch, which is also the source for release candidates (RC), sounds a bit extreme, doesn’t it? The TBD flavor that always worked for us was the use of short-lived feature branches. These only have the role of facilitating the review process though, and not of isolating the changes for long periods of time.
TBD in practice
Everything starts locally, where the developer commits the changes directly into the trunk. Next is the execution of a script that abstracts the creation of a branch, pushing this to the origin and opening the pull-request. The local branch is then deleted, and the context moved back to the trunk, which already contains the change.
Pushing the ephemeral branch on the origin and opening the pull request triggers the execution of one or more pipelines, which will automatically verify the change, by running unit tests, building the code, and others. The integration is blocked until this step is completed. In parallel with the automation, or after this, the peer review process can take place. When the two validations are completed, the change can become part of the trunk on the origin, bringing the local and the origin back in sync.
Of course, the integration in the trunk also has to trigger an automation suite, which will validate and prepare the current version of the source to be transformed into a deployable artifact, followed by deploying this to an initial environment. These stages usually consist of end-to-end tests, containerisation, promotion to various RC states, smoke tests, and others.
A culture of responsibility
An important aspect that is facilitated by the continuous integration is that the artifacts produced as a result of the pipeline execution on the trunk are (or rather should be) ready to reach production at any given moment.
Of course, we’ve spoken about all of the automation systems which come to complement TBD, but how do we deal with isolation? We want to live in the present, but we don’t want the customer to live in the exact same present. We don’t want the customer to access half-ready features, or insufficiently tested functionalities. How do we reconcile this? Meet the feature flag concept.
Feature flags are simply programmatic switches that block or allow access to certain functionalities. It falls under the developer’s responsibility to use feature flags for bigger features, facilitating this way small and often changes, easy to review and test, while keeping the product in a releasable state at any moment. Another advantage of using feature flags is that the product team can decide on parameters such as the exact date when the functionality will be available in production, early access customers, and others. This way, a separation between the concepts of deployment and release occurs. The feature flags systems can vary in complexity from simple boolean values in the code, to third party tools such as Launch Darkly or Harness.
Conclusions
With a CI/CD system that has at its base TBD, our team, as well as other teams that work on their parts of the application, have visibility on all the initiatives in progress, and the integration with these is straight-forward.
The production deployments on multiple services happen twice-a-week cadence, but they can be done anytime a hot-fix may be required.
Through rooting into the truly present state of the source code, the developers’ anxiety produced by integrating unknown features is significantly reduced, leaving them to focus on their own mission, which is creating robust and well tested functionalities.