As mentioned in the previous post on TBD, one can be as lax as needed when adapting this practice to a team’s workflow.
The day-to-day reality of each project (team, enterprise…) can be vastly different. Ideally when trying out a different paradigm, the team would have some “wiggle room” to adapt the new workflow to their needs and environment.
Follow the principles, not the rules
Branch wisely
Ideally, other than the occasional release branch (if needed), you would work with only a main “Trunk” branch. In most cases, this is not necessarily viable.
Start by ensuring no branch lives for more than a day, the shorter the life span the better. Crucially, remove all long-lived branches that run in parallel to the main one.
A good rule of thumb to begin with is to never have more than three branches at a time, none for more than a day.
No Gatekeeping
While you work your way to a “branch-less” workflow, PRs are still going to happen.
You can use them in a “TBD-ish” workflow, with some important restrictions:
- Reviews are optional: Reviewers shouldn’t prevent code from reaching production, and reviews should be requested by the submitting dev if needed (and should be done synchronously if possible).
- Use PRs as tools, not rituals: You might feel more confident blocking all pushes to the main branch and having the pipeline run on merge. This is little more than “implementation detail”, and is fine as long as it doesn’t interfere with the other principles.
- Keep them small: The easier they are to revert, the better. Prefer multiple small PRs over one big PR per user story.
In general, try to see PRs as little more than “a thing that happens” more or less automatically before merging your commits.
Stay in sync
The less time you spend away from your main branch, the better.
Constantly ask yourself: Could this be merged? Doesn’t matter if the feature (or bug fix) is done. If the answer is yes (as in “the tests pass, your code builds and doesn’t break prod”), do it.
Merge with Master, open a new branch and keep going. Make this a normal part of your workflow.
Deploy constantly
The more, the merrier. Keep your code deployable while you work, don’t break the build, keep your tests green.
Test yourself and your team by deploying at least once a day. See if your code really is “always in a releasable state”.
This will allow you to reap part of the benefits of TBD (fast feedback loop with the end users) while getting there.
Must-haves
Here are a couple of requirements to consider for an effective TBD workflow and some tips on how to bring it all together.
Pipeline
You need a solid, cared for, efficient and stable pipeline. This should be a primary focus of the team: Issues with the setup should be resolved immediately.
It should be fast and efficient especially in regard to testing, implementing caching layers if needed to avoid processing unchanged code. Ideally it would take care of building, testing, performing code analysis and even deploying to production.
Fast builds and tests
You need to have a comprehensive and meaningful suite of automated tests, mostly unit tests with a more selective approach to e2e and integration tests.
These need to be fast and reliable and should give the team enough confidence to consider the code deployable as soon as it passes them.
Ideally, building the project and running the tests shouldn’t take more than a few minutes from start to finish.
Locally reproducible
When doing TBD, breaking the build in the pipeline can be costly and often slows down the rest of the team.
Ensure the system can be quickly built and tested locally. This should be done on a regular basis, before a task is considered “done”. Possibly include these tasks in a git hook (a pre-push for example).
Very rarely should a commit build and pass the tests on the dev’s machine while failing to do so in the pipeline, and there should be a very good reason for this to happen.
Fine-grained deploys
Ideally, one wouldn’t need to re-deploy the whole application. Rather, deploys should only involve the parts of the system that have been updated.
This is easy enough when working with microservices (if done right), but can be challenging with monolithic applications.
Introduce deploy labels, fragment your code in a way that allows for deploys to be as small as possible.
Work in small steps
Commit code frequently, multiple times per hour. Doesn’t matter if the code is not perfect or if it’s a “Work In Progress”.
As stated before, if it builds and passes the test suite go ahead and commit it, push it to the main branch. Work in such a way that allows you to keep your code releasable.
Reverts are easy when working in small increments, trust your VCS!
Tips and tricks
When coming from a branch heavy workflow, these things sound scary, and it is likely unclear how exactly to apply them without breaking things. There are multiple tricks you can use while doing TBD to protect the system from your code:
Feature Flags
Want to see how your code behaves in production, but still keep the old behavior for real end users?
Create a feature flag and hide the new code under it, make sure your test user has that flag enabled and presto! Only your test user has access to the new feature.
This can be something as simple as:
This allows you to easily “turn your code off and on” for one or more users and handle Betas easily. It also allows your code to live in production without affecting the application in the slightest.
Crucially (especially for your PO/business team), this easily paves the way for A/B testing. This can be enough of a reason to implement feature flags on its own.
As you can imagine, there is much more to feature flags than this and implementing a stable and reliable flagging system is complex enough to be offered as third party solutions. Learn more here.
Branch by abstraction
What if you need to change code that other devs depend on?
Abstract it away: wrap the code to keep its original API, while preserving the freedom to change stuff around under the hood.
It’s basically a form of Parallel Change, only in this case instead of allowing you to keep the tests passing, it allows other team members to keep working uninterrupted.
Dark releases
Dark launches simply refer to releasing software updates only for a subset of users. This is usually done with a predefined group of trusted users (or a small percentage of the total user base), with the help of feature flags.
By doing so, you can see a feature in action, gather feedback, evaluate how it performs and decide if a full-scale release makes sense or more work needs to be done.