Another instrumental element in our adventure to Continuous Deployment
Cet article est également disponible en français.
This is only possible because we moved away from the GitFlow branching model to Continuous Isolation.
If case you did not understand my previous sentence, I tried to explain all the necessary background in non-tech words:
GIT, GitFlow and Continuous Integration for Dummies
It doesn’t hurt to understand how developers work
Moving away from GitFlow and Continuous Integration
Using GitFlow and doing Continuous Integration is not the only possible way.
On our adventure toward Continuous Deployment, we decided to move away from GitFlow and to use another branching model.
Why stop GitFlow
In GitFlow, the developers add individual features into the develop branch and then, at some point in time, a release branch is made out of it and will be released to production and merged into master.
- You deliver features in batches
- Planning releases is a good idea
- New features must be implemented in an order compatible with the release plan
- Not following the release plan, that is adding a feature that was not planned in the next release, will lead to headaches (at the very least, you’ll have to update the release plan and the test plan, maybe even you’ll do the same work twice)
Obviously these are not absolute points. For instance nothing forbids you from delivering features one by one. But the GitFlow branching model will not make it easy.
Why we were not happy with these constraints
Here is some background:
We were in the middle of a massive refactoring. We did not really planned for it, but we had started down a slippery slope. We had no choice but to continue and finish it.
And then, some minor feature had to be delivered by some hard date, for the big launch of another product depending on our component.
That was very complicated because we were using GitFlow. The initial plan was to deliver the feature after/with the refactoring but we slipped and needed always more time — until the deadline for the minor feature came close.
The fact is, even if we finished the refactoring before the deadline, the raw amount of changes would mean an enormous risk of breaking anything for the big launch. This view made us very uneasy.
So instead of gambling on finishing the refactoring before the launch, we took the code of the minor feature and delivered it as a hot-fix, that is from the production branch (master) instead of develop. That way, we were able to deliver the feature on time and with no risk.
Doing that is complicated for the developers, as they need to extract code from the context of one branch to apply it to another branch.
In retrospect, it was not that hard but the risk of doing a mistake was real. More importantly, it planted the seeds of the new branching model: what if we could do that with any feature, on-demand, easily and without risk?
So we moved away from GitFlow.
Our new branching model
In the new branching model, the feature branches are kept alive until they are in production and merged into master. There is no develop branch anymore.
That way, the new features can be deployed in any given order.
Obviously, that also means that all the integration is done at the last minute. This change of branching model also invalidated how Continuous Integration worked — we’ll get back to it in a few moments.
However, the fact was that with GitFlow, when doing a release, we had to check extensively that nothing was broken in the release before actually releasing. Maybe our Continuous Integration did not have enough automated test to provide a satisfying coverage. In any case, we had to spend a lot of time to perform these checks.
Which led to another observation we made.
Release small and often
Another thing that we noticed was that when we made big changes we spent a lot of time on checking that anything was broken, and often we missed things and introduced regressions anyway. On the other hand, when we made small changes (mainly hot-fixes) we checked only the few important things, directly related to the changes, so we did not spend a lot of time, and we rarely broke anything.
So what if we could only do the smallest possible releases?
Doing so was a problem with GitFlow as it was a lot of overhead to each time create, manage and merge the release branches. That’s really OK when you’re releasing once in a while, but it’s a pain if you have to do it everyday.
On the other hand it was not a problem at all with our new branching model. Actually, it worked even better that way.
Indeed the problem with this new branching model is that you need to keep up to date all the feature branches. So to minimize this overhead, you must try to merge the branches as fast as possible. That is, to keep the features small and to release often. Exactly what we were looking for!
The Product Owner’s new super-powers
The most amazing fact about this new branching model was that it gave new super-powers to the Product Owner and to the business in general. The PO was able to prioritize freely the order in which the ‘done’ features were delivered into the users’ hands.
Maybe a new feature or fix popped up as urgent: it could be directly prioritized over the rest. And that was not a problem for the team or anyone!
There was no pipe of already on-going things that needed to be cleared before we could take care of the new, urgent stuff.
All this freedom given to the Product Owner is what I described in my previous article about the Release Planning Board. We simply prioritized in Sprint Planning which features should be put in production and in which order.
That would not have been possible, or not that easily, with a branching model like GitFlow.
The order in which features would be released would not have been up to the Product Owner; or at the very least not with this level of granularity.
Traditional continuous integration would be of little use with such a branching model. The whole concept of continuous integration is to integrate often small changes on a common branch; what we are doing is keeping the branches alive and merging them at the last possible moment. Once the branch is merged, it means it is in production: it is already too late to perform any integration check at this stage.
So instead we practice a variation of continuous integration: continuous isolation. Indeed the branches are kept isolated from each other for as long as possible. Still, we want to make sure that these branches are working on their own. Even better, we want to make sure that if we merged these branches, they would work — at least individually, or mixed all together.
In short: even if we are not doing continuous integration strictly speaking, we have some sort of software running that checks that everything is OK, whenever possible. And it works, as it gave us on several occasions interesting feedback. (that is, pointing out that the branch is broken before we start any manual testing)
As smooth are things now, we could obviously do better.
We are still way behind on the coverage of the product by automated tests. We made a lot of efforts but this is still not enough; ideally we would perform next to no regression testing at all. Maybe we would still perform manual testing, but that would be exploratory testing only: to learn things about our products that we did not know already, to find things it would be hard to plan for.