Feature Branching or Trunk-Based Development

Feature Branching or Trunk-Based Development

Over the past year and a bit, I’ve thought deeply about the trade-offs between a feature branching model and trunk-based development. There were several reasons that provoked such thoughts, however two kept returning to the forefront of my mind; resistance from a team member in my own team for the gitflow-like approach we utilised and watching this ‘why pull requests are a bad idea’ video from Dave Farley. In Dave’s video he argues that trunk-based development with continuous deployment make for more productive and efficient engineering teams. While I wholeheartedly agree with this, I feel he is too emboldened in his message that teams should simply adopt this and become more productive. I believe the path to trunk-based development and that heightened productivity is more nuanced and a journey needs to be undertaken to get there to truly reap those productivity benefits. In this blog piece I explore the context around aspiring towards trunk-based development and while it may be the right choice eventually, I believe in certain scenarios, it may not be the right choice, right away.

First off let’s explore a little bit around what these two approaches entail. Gitflow is a branching model that centres on feature branches and multiple primary branches; a master branch that mirrors the code currently deployed in production and a develop branch that serves as the continuous integration (CI) branch. This branching model results in longer lived feature branches and larger commits as developers delay merging to the CI branch until the feature is complete. Traditionally, such branches require a greater level of collaboration to merge and introduce risk in that the longer lived they are the greater they can deviate from the CI branch. Code in feature branches typically gets merged to the CI branch via the raising of a pull request and developers in the same team reviewing said pull request and deciding when they are happy for that to be merged. Gitflow tends to work well for products that have scheduled release cycles and for teams that are embryonic and/or less experienced.

Trunk-based development is a lot more straightforward. This branching model involves developers committing changes to a single branch, typically named trunk. As developers tend to commit their changes to trunk multiple times a day it’s easier to satisfy the requirement of CI that all team members should commit changes to the main branch several times a day. One of the main productivity benefits of this approach is enablement of not only continuous integration but also continuous delivery. This approach tends to work best for experienced, mature teams that have no strict release cycle and instead release changes to production incrementally, often many times daily.

Quite recently I led a completely new team for around a year as we developed a new, greenfield application. Over that year the initial team reduced in size quite quickly and then increased in size more gradually. In that team we had some very senior engineers, some mid-level and one who was working towards mid-level in terms of experience and aptitude. During that year we adopted a gitflow like branching model that centred on pull requests. Why, might you ask given trunk-based development leads to heightened productivity. Not always would be my answer, context is key.

To begin touching on that context and why I think going for a trunk-based development approach from day zero in this project would have hampered us lets dig a little deeper. First off, we were a team of external consultants, the first such team to work in this particular organisation. The organisation was keen for us to be transparent about the code we were writing and how it was designed to ensure it met their own internal quality markers. With branches being a little longer lived and pull requests being raised frequently it allowed key technical stakeholders within the organisation to observe the code we were writing, raise feedback and crucially block any code that fell short of meeting such quality markers. Although this slowed us down in the short-term it allowed us to align ourselves technically with the client and allowed us to establish a degree of trust in that we were building something that met their own internal quality standards and something that they could also begin to understand incrementally. After all they would be the ones supporting and maintaining the application in the long-term. As time wore on our team and the client became more aligned and pull requests became less onerous and less time consuming, allowing us to go faster and ensure our code looked and felt like something the client was comfortable with. Had we not elected for a pull-request type branching model initially the code changes would not have been as visible to the client as they would have been smaller and more frequent and thus less contextual. Furthermore, the client wouldn’t have had the control of critiquing any changes pre-merge. In such a scenario addressing any feedback post-merge, once the code was in the CI branch would have been far costlier for us as a development team and overall would feel like the horse had already bolted from the client’s point of view.

As time wore on and our relationship with the client became more established, they became less involved with pull requests and our own development team began to align, we started to experiment with making pull requests far more light touch and less voluminous. The first time we tried this we quickly discovered in our retrospectives that we were trying to run before we could walk in this sense. Items concerning things we could do better were being raised by multiple team members in our retros pertaining to, features not working as they should which in turn led to re-work and some of the less experienced members of the team committing code that was hard to understand and maintain. Had we completely abandoned pull requests at this stage and went for trunk-based development the issues above would undoubtedly have been exacerbated and we would also have had to invest some effort in going back to our gitflow based branching model. In short, our team was not yet mature or aligned enough for a trunk-based development approach.

My experiences above led to me making some interesting observations around what can seem a binary debate most of the time when considering a feature branching model versus trunk-based development. The one thing that really resonated with me is that this debate is in fact very much non-binary and context is crucial in deciding what is the best approach for a development team. Key factors within that context started to become clear in my mind.

What level of review does your code require external to your own team? How frequent such reviews are? For how long will the code be reviewed externally? If the code requires no level of external review, then that’s a big indicator that trunk-based development may be of value. However, if the answer to any of these questions is no then it may not be.

How aligned is your team? In my mind whether a team is aligned is a far more important factor in team productivity than any technical approach we decide to take, branching models included. In every team, alignment comes with maturity and teams in their embryonic state are by default unaligned. How fast that team can reach the required maturity to become aligned is influenced by many factors; including but not limited to, experience levels of team members and the collective and individual behaviours of the team. For an immature, unaligned team trunk-based development in my opinion can be dangerous and lengthen the feedback loop in identifying technical debt and spotting future potential maintenance overheads. Pull requests for immature teams provide a transparency comfort blanket that allow design flaws to be spotted early, provide an opportunity for less senior members of the team to develop and align technically and for coding styles, standards, etc within the team to become established. Once the team reaches maturity and is aligned is the time to start thinking about trunk-based development and reaping the heightened productivity benefits it provides.

One may say that with an approach like the above or indeed any long-lived branching model that the key requirement of continuous integration is not being met. However, with advancements in technology I don’t believe you can still be as explicit about that. Continuous integration is essentially the notion of quick feedback that our changes work alongside any other changes that are being or have been made in the main branch. With automatic branch builds and ensuring we are merging the main branch into our feature branch at least once daily we can get this same level of feedback and meet that same requirement, albeit the process to glean that feedback is a little more onerous.

Overall, I believe applied in the correct conditions trunk-based development will lead to more productive, efficient engineering teams. It also goes further to enable even greater productivity gains like continuous integration and continuous delivery. However, I believe ignoring those conditions and considering it as some sort of panacea that always results in greater productivity is dangerous and can be counterproductive. In fact, I strongly believe that applied in the wrong conditions and circumstances it can have a detrimental effect on overall team productivity. Trunk-based development is something I think all engineering teams should aspire to but not before they are ready. For it to work and for those often-spoken productivity gains to be yielded teams must honestly appraise themselves on external technical stakeholders, alignment and maturity before making the call.