Main Line
When you use an agile software development process, you plan incrementally and iteratively. You want your codeline process to support your approach to delivery. This pattern describes the high-level structure — a Main Line — that makes it easier to deploy code quickly to reveal business value while maintaining stability and traceability by providing a central integration point for code.
Challenges of Balancing Speed and Stability
Agile software development is based on frequent iteration and feedback as measured by inspecting working software. But stability and speed can appear to be at odds, especially in a shared codebase where many people contribute.
Some teams try to reduce the risk of error by slow, disciplined steps when integrating work into a delivery code line. These teams may have:
- More manual testing
- More peer reviews
- Intricate integration testing
- Strict approval gates to prevent ‘accidental’ integration
- One or more staging branches
The team integrates into a single shared code line only once it is “certain” that the code works. For example, in a GitFlow model {Atlassian #13833}, work in progress is integrated into a “Develop” branch, which is considered a working branch. The changes are merged to the shared mainline only after they have been approved for release.
There are variations, all of which work on the belief that isolation and moving slowly is safer. Keeping work on isolated branches preserves the stability of the eventual target branch in the short term but defers the problem: the target branch receives changes slowly, leading to process and business risk:
- Process risk: The longer work stays isolated, the greater the risk of merge conflicts and divergent design decisions, as well as more work-streams to maintain.
- Business risk: Slower code delivery means that features take longer to be released, which can lead to opportunity costs and longer feedback cycles based on larger features.
Since agile software development is about adapting to uncertainty in the project space, it is valuable to evaluate the current state of the code sooner.
Moving slowly can lead to a self-fulfilling dynamic:
- A slow integration process leads to a temptation to integrate larger, more complex units of work.
- The longer you keep your changes isolated, the harder the integration will be, both for your work and work started after your work stream started.
- The more overhead for a merge, such as more testing due to change set size, the greater the temptation to introduce more work before you merge.
Faster integration means you could miss an error, but slow integration doesn’t guarantee perfect software. Regardless of the size of the unit of work or how quickly you integrate it, merging code that breaks the shared integration codeline will slow down the entire team.
Frequent integration is more productive: the more frequently you integrate, the simpler each integration will be because the change is smaller and the work started with more recent code. You want a solution that balances stability and speed, allowing you to deliver code rapidly,
Create a Stable Baseline
** Work on a Main Line, where all work is integrated. Use mechanisms to allow work to be integrated frequently while maintaining stability so that the Main Line is potentially deployable.**
A Main Line is a code line that:
- contains the “record” of the latest work
- tracks the current state of working code and is the starting point for any new work
- is the source of all releases (with the rare exception of emergency patch releases).
The Main Line is never deleted. It lives throughout the entire project, and the entire team contributes to it.
Work in progress, before it is merged to the Main Line could be:
- on another branch (as this pattern language describes) or
- In a developer workspace with no independent tracking branch. In either case, the prerequisites are the same. This pattern language describes how to use short-lived branches, which is consistent with what Accelerate {Forsgren et al., 2018 #92987} describes:
Following our principle of working in small batches and building quality in, high- performing teams keep branches short-lived (less than one day’s work) and integrate them into trunk/master frequently.
The goal of Agile Software development is to manage uncertainty. As Mike Cohn wrote in Agile Estimating and Planning {Cohn, 2006 #222473}:
The best way of dealing with uncertainty is to iterate. To reduce uncertainty about what the product should be, work in short iterations, and show (or, ideally, give) working software to users every few weeks…
Being biased toward quicker integration into a shared Main Line, rather than reducing error, can help you manage uncertainty by showing you an accurate current state. Errors will still happen; being able to recover when they do is more valuable than slowing down in an attempt to avoid them all. As Gary Klein writes in Seeing What Others Don’t {Klein, 2015 #156450}:
“When we put too much energy into eliminating mistakes, we’re less likely to gain insights. Having insights is a different matter from preventing mistakes.”
A Main Line model makes you more responsive, and the tradeoff between stability and speed can be reconciled with a bias toward speed {Forsgren et al., 2018 #92987}:
This huge increase in responsiveness does not come at a cost in stability, since these organizations find their updates cause failures at a fraction of the rate of their less-performing peers, and these failures are usually fixed within the hour. Their evidence refutes the bimodal IT notion that you have to choose between speed and stability—instead, speed depends on stability, so good IT practices give you both.
A goal of an agile project is to gain insights into the state of the software by frequently inspecting the latest code in a running application.
Migrating to a Mainline
The end state of the Main Line model is continuous deployment once code is merged to main. While many teams embrace “Continuous Integration” to the Main Line, continuous deployment from the main line to production can take a few step to get there:
- It can present operational risks in terms of setting customer expectations.
- Your automated testing process might not give you the confidence to deploy to production without human intervention.
A reasonable migration path is:
- Work towards using short lived Task Branches that start from the Main Line and merge to the Main Line
- Develop automated Continuous Deployment mechanisms, starting with deploying to a pre-production environment with each merge to the Main Line.
- Over time, reduce the time between the CD deployment and the production deployment.
Example
A Main Line development flow will look like the following, though each team can decide what the correct length of time is:
- Checkout the HEAD of the Main Line into a development workspace
- Code, backed by a Task Branch
- Within a day, merge the code after an appropriate feedback process.
Cautions
Maintaining an active, healthy, Main Line takes discipline. An occasional error is inevitable, so while you might feel comfortable eliminating intermediate branches and getting code to the Main Line quickly, you may be tempted to add extra gates between “merged to main” and “released.” While this might be a reasonable starting place, you want to work to get to a point where the merge to the_ Main Line_ is quick, automated, and gives you high confidence.
Next Steps
While the Main Line model’s simplicity, with fewer codelines, has advantages, you need some mechanisms to allow frequent integration to happen safely and reliably. You can’t avoid all errors, but you can avoid major ones and reduce the impact of any that slip through.
To help ensure a healthy Main Line you need to:
- Define the rules for integrating to the Main Line and when to use other codelines: Codeline Policy.
- Provide a place to reliably do development with the correct dependencies and tools: Developer Workspace
- Allow for delivery of critical fixes to released code: Release Line
- Enable Parallel, Independent Work that can be integrated into the Main Line quickly and reliably: Task Branch
- Get feedback on work before it’s integrated into the Main Line: Code Review_
- Build and Test automatically: Integration Build
- Get Feedback on design and implementation: Pull Request
- Create a Retrospective Culture that is robust in the face of the inevitability that things will break despite best efforts and has a continuous improvement mindset.
References
- Atlassian. “Gitflow Workflow | Atlassian Git Tutorial.” Atlassian |
- Cohn, Mike. 2006. Agile Estimating and Planning. Prentice-Hall PTR.
- Forsgren, Nicole, Jez Humble, and Gene Kim. 2018. Accelerate: The Science Behind Devops: Building and Scaling High Performing Technology Organizations. Portland, Oregon: IT Revolution.
- Klein, Gary. 2015. Seeing What Others Don’T: The Remarkable Ways We Gain Insights. New York: Public Affairs.