In software development, the name of the game is to develop reliable systems in a fast-paced manner. As development shops have evolved to increase the speed of delivery, many organizations have embraced the Agile development practices of continuous integration and continuous deployment (CI/CD). But, the very nature of fast-paced development introduces challenges related to the quality and reliability of the software being developed. The development process that defines how developers interact with source control and integrate new features and other modifications on a day-to-day basis is often omitted from the discussion of software quality.
Today, there are several popular methods for defining this process. Below, I will focus upon one method in particular: trunk-based development. I will discuss what trunk-based development is, how it’s leveraged for the management of a CI/CD pipeline, as well as the benefits of trunk-based development in terms of developing reliable software in a fast-paced development environment.
Trunk-based development and its advantages in a CI/CD world
Trunk-based development (TBD) refers to a process for managing a project within source control where all developers working on the project commit their code changes directly to the trunk (master) branch.
With trunk-based development, developers steer clear of utilizing long-running feature branches that would potentially allow them to fall drastically out of sync with other team members collaborating on the same project. Short-running branches can be used where necessary, keeping them small to prevent the developer from straying too far from the state of the trunk’s source code.
The trunk-based method is made possible by enforcing a smaller scope for application changes that allows for the gradual integration of features within the codebase (sometimes done with the help of feature flags). Leveraging this method can lead to an array of benefits that will make the life of developers and IT teams significantly better.
Staying in-sync means less frustrating merge scenarios
A key principle of trunk-based development, mentioned above, states that developers are prevented from falling out-of-sync by eliminating the practice of creating and working in long-running feature branches. This allows all developers to remain, more or less, in-sync with those working around them – leading to more reliable services and better incident management processes. And, this provides one very noticeable benefit: merging in code becomes simpler and faster and no longer holds the anxiety-inducing prospects it once did.
Consider the following when looking at trunk-based development:
1) Trunk-based development dictates that the scope of the changes being made to the codebase for a particular task are limited, to prevent the creation of long-running branches.
2) With these limitations being the case, all developers commit their code to trunk on a regular basis as tasks are completed (with some even committing multiple times per day) – leading to efficient release management and more frequent deployments.
3) Since no developer is waiting extended periods of time to integrate their changes with trunk, the complexity of a potential merge is kept to a minimum. Conflicts are, therefore, fewer in number and much less tricky to resolve than when trying to integrate a long-running branch with master on the heels of other recent merges containing significant changes.
4) With lowered merge complexity comes a lower likelihood of mistakes being made within a merge (i.e. leaving in code that shouldn’t be there, removing code that should remain, etc.).
Reducing the likelihood of build and integration issues
Another significant benefit of trunk-based development is a reduction in build issues due to a higher-level of responsibility being taken by all team members to keep it functioning properly. With all developers committing continuously to the same branch, maintaining a functioning build is of great importance.
The notion that the build must be kept in working order at all times helps developers buy into on-call responsibilities and turns resilience into the credo of every developer on the project. In addition, with developers merging their changes into trunk with such great frequency, they learn to code with the build in mind.
Furthermore, with trunk-based development, developers have an increased ability to validate their code changes against those of their colleagues. When working in long-running feature branches with fewer commits to the shared branch, this opportunity doesn’t arise as often. And, when these branches are finally integrated, the changesets are bigger, more complex and have been concealed from all other team members for a long period of time. In contrast, here’s how this validation process works when doing trunk-based development:
Trunk-based development leads to actionable insights
With greater visibility comes greater insight. And insight is exactly what’s provided by the continual process of merging small sets of code changes into an application and making minor continuous improvements. Prior to checking in, developers merge all recently committed changes from other team members into their local copy of the source code.
Developers can then proceed to build and test on their local machine. This provides the developer with all they need to ensure local changes work in conjunction with all recent commits. In this manner, trunk-based development inherently ensures a base-level of quality within the application, simply through increased visibility into the work of other team members.
Trunk-based development is built for CI/CD
Trunk-based development is, in many ways, tailor-made for the concepts of CI/CD. As you surely know by now, in TBD, all developers merge changes frequently into the master branch. This could be as little as one commit every 1-2 days, to several commits per day for each team member. With each commit, a build can be triggered with full-unit and integration testing, followed by deployments to testing environments.
A continuous stream of commits containing small changesets causes the fully up-to-date and integrated trunk branch to build and validate frequently, leading to an application that becomes increasingly stable as it evolves. And, as an added benefit of the oft-run CI process, the application is always in a state that’s ready for deployment with the latest and greatest features present.
So how does delivery work in trunk-based development? It’s actually quite simple. When it’s time for a release, a new branch is created off of trunk. This release branch is not to be tampered with by developers outside of extreme circumstances.
If a production bug exists in the release and a patch is necessary, the fix should be made in trunk and the code fixing the bug should be patched into the release branch to be deployed. Release branches are later deleted when it’s certain they will no longer be needed, keeping the repository clean and maintaining a process where source code moves in one direction – from trunk forward.
When employed properly, the principles governing trunk-based development allow for the rapid development of high-quality, reliable applications. In a world that craves fast-paced application development, this can be very valuable. In order to implement this process properly, the scope of application changes must be kept small and code must be integrated as often as possible with trunk. With each merge from each developer triggering a full application build – complete with integration testing – issues within the codebase are easy to spot and likely to be fixed in a swift manner by those who introduced them.