Friday, April 1, 2016

Branching Strategy for Agile team desiring continuous integration that must work with a restriction of a slow release cycle

Solution Requirements:

  1. Continuous Integration - Frequent integration of code changes to limit big scary merges. Avoid long periods without merging back for others to see.
  2. Changes to UAT - Allows for a release to stabilize at a slow pace than development.
  3. What is approved in UAT is what gets released to production.
  4. Hot fixes to Production - Allow for emergency changes to a production release
  5. Parallel UAT and Development - Development should be considered working, but not ready for production. UAT would a candidate for release to production pending user approval.
  6. Code Reviews - each feature should be code reviewed. Ideally any merging that is done by everyone is be done at the feature level, but the developer making the changes can commit changes more often. Thus once the feature is done, it can be bundled up, code reviewed, and merged and ultimately released.
Deliver software with a high level of confidence. Confidence to be derived from unit tests, integration tests, BDD tests (End to End tests), and UAT user approval. This implies that what was approved in UAT is what should be moved to production.

A compromised Solution:
The solution we weretempted to implement is best described in the post A successful Git branching model by Vincent Driessen, but we are using TFS, not Git. It is basically says, each feature is done in a branch, though I would add that changes should be pulled from the develop branch DAILY. Ideally, we should be able to look at the develop branch and see features branches being merged in, not individual commits. The individual commits would have been done to the feature branch itself.This makes it easy to see what is in the develop branch and also when they are merged to the release branch and the main branch it is again at the feature level.

Once a feature passes all regression tests (unit tests, integration tests, BDD tests) and has been code reviewed it can then be merged into the develop branch. Ideally a feature branch would be only a few days. If it is longer than a few days the feature should be merged into the develop branch to avoid a big scary merge. If a feature is not finished by the end of a sprint and part of the feature has been merged to the develop branch, but part is unfinished a feature toggle (See Feature toggles note below on how these can be implemented) should be added so that it is not accessible to the user.

A feature should be merged into the release branch by the develop responsible for it once they are confident (passed unit tests, integration tests, BDD tests, and exploratory testing) it is ready for release (pending UAT approval). Unless a specific need comes up, we will only have one release branch and label each release in it when it is merged into master (main). A separate versioned release branch can be created if needed though.

If a change (bug fix, enhancement, etc) is need to the release branch because of something that needs to be changed in UAT the change should be made on the release branch and then merged to the develop branch. 

Similarly, if an emergency change (hotfix) is needed on the production code, the change should be made on the master (main) branch. It should then be merged into the develop branch and eventually to the release branch (as part of the usual merging into release branch process).

A label should be created automatically or explicitly when merging from development to release and then to master (main).

In the end this may not work so well when trying to continuously integration. It can work if the branches are short lived enough. The real issue is the unknown time it takes to integration when the effort for the integration of branches is large and the unknown time needed to stabilize after the integration and before release. This tends to be needed at least once before a release. Smaller more frequent integrations has proven to be a more stable amount of effort required and the code base tends to keep the code base stable when using feature toggles (feature hiding).

Refactoring is an exception to this strategy and the changes should be merged immediately to minimize difficult merges. This make this strategy not as useful unfortunately.

NOTE: This strategy could probably work by merging branches (both to and from the develop branch) at least once a day. The question is what is the benefit of doing a branch then? Branching is for isolation of development and successful continuous integration demands not being isolated. A compromise may work as well such that the branching on the develop branch is removed and instead everyone develops on the develop branch. A branch should not live more than 3 days to be considered short lived.

A Better Approach: 
Have one mainline (no other branches unless doing a bugfix for UAT or production). The key is that the mainline is ALWAYS deployable. Use feature toggles for every feature and enable or disable as needed to make current release stable or that what is enabled is deployable. Remove old feature toggles after not needed anymore. Use labels for each release or deployment. It has the advantage of always integrating, and only create a branch if needed in the case of an emergency hotfix or something. Alternatively, the mainline could be released for the hotfix if all previously unreleased feature toggles are still turned off, but that is a judgement call based on the situation. Note, the release label would be on the new branch in this case and then merged back into the mainline immediately. It is simple to manage and could work very well for open source or fast moving projects that can release as desired. It may be a bit difficult to use for processes where there is a lengthy UAT approval cycle by end users. This difficulty can be mitigated by making the change to the mainline, disabling the appropriate feature toggles and putting in UAT again. Could work well for a continuous deployment model too I would think. This entire model (as does any CI/CD model) relies on having automated regression tests.

Some guidelines:

  • Avoid branches - Branches should only rarely used. For example, only use branches for things like releases or spikes. In general things that will not be used again. 
  • No long-lived branches - Long-lived branches are opposed to successful continuous integration and should be avoided at all costs. This includes things like refactoring, long term development, etc. Instead use feature toggles (feature hiding).
  • Integration Daily - Get the latest from the mainline and Commit to the mainline at least once a day
  • Pass tests - all tests should still work BEFORE (and after) checking in your changes. This helps keep the mainline deployable.
  • Incremental changes - It may take a little longer to do a bigger change in smaller incremental changes, but the effort and time spent is worth the effort because the mainline is always deployable.
  • Label or Branch for releases - A branch or label can be created just before a release. Once created testing and validation of the release is done from this branch. New development is performed on the mainline. i.e. Only critical bug fixes are done on the release branch and immediately merged back to mainline. Branches are always off the mainline, not existing release branches.
A successful  Git branching model - what we are implementing here
Feature Toggles - enable / disable features using compile or runtime flags to make a feature visible to the end user. Pete Hodgson's perspective on how to implement / issues is here. Martin Fowler says this.
Branching and Merging: Ten Pretty-Good Practices
Continuous Delivery by Jez Humble and David Farley. See chapters on Chapter 13: Managing Components and Dependencies, and Chapter 14: Advanced Version Control.
Some different version control implementations - worth looking at since they are bit simpler than the successful git branching model.

No comments: