I know that there have been some blogs about this topic already, I still think that this topic is important enough to add another blog post to this – my own.
With Visual Studio Code and AL we now have a modern development environment, so we should also adopt modern development strategies. One of those strategies is Source Code Management (SCM). I don’t want to go into details on why it is needed or why we haven’t done that often in the past. Please just work with the premise that you will have to do it for various reasons. There are a lot of different ways to manage your source code, but this is for another day.
We will assume that we are using Git for SCM, just like the majority seems to use already. It is a great solution and it is nicely integrated into DevOps, which will provide you with a lot of other benefits, for instance, being able to automate your builds.
Today, I want to go more in detail on strategies, specifically branching.
What is Branching?
Branching is a feature available in most modern version control systems. Branching in other VCS’s can be an expensive operation in both time and disk space. In Git, branches are a part of your everyday development process. Git branches are effectively a pointer to a snapshot of your changes.Atlassian Git Tutorial
So, as you can see in this definition, a branch is a version of your code that is “maintained” in this specific state for as long as this branch exists. It allows multiple developers to make changes to the same product and keep those changes separate until they are done and tested and then combine them into a final product.
Instead of spending a lot of time explaining how a branch works, how you can create one, and how you work with them, I want to explain to you how we do branching – and also look at differences between actual product development and code customizations. If you want operational instructions on how to work with Git branches, there are a lot of good tutorials online.
Product Development vs. Code Customizations
There are a lot of differences between developing a product and developing a customization for a specific customer. In this part, I want to go in more details of what branches we use for product development.
Please also remember, while – traditionally – an ISV has done product development and a VAR has done code customizations for customers, this is changing or rather: this should be changing.
Everyone, ISVs and VARs, should look at any development going forward as “Product Development” and not anymore a “Code Customization”. While we – fortunately or unfortunately – can do AL code customizations again, it doesn’t mean that everyone should just jump on it. Code customizations are intended to be done by ISVs and VARs developing a product – a specific vertical solution. It is not intended to give custom development for a single customer a new life.
Anyone developing anything (or at least in the majority of cases) for Business Central going forward should be developing an extension. An extension technically can be considered a product and should be treated as such. The only difference might be that you are not planning on making it publicly available on AppSource or are not planning on selling it to different countries, etc. But the basics are still the same: Regardless, if you develop an in tenant app or a fully published AppSource available app, you still have to make sure that it works and that it continues to work in new versions.
The main repository
The default branch in Git is called “master“. This is the base repository where the current version of the software lives. It doesn’t mean that this is actually the latest released version, but this is the most current version that contains all bug fixes, all new features, everything that you need to release your app or an update to your app.
Any developer creating a new feature or fixing a bug will branch off of this master branch. So, basically, the developer will create a new branch – or copy of the code – that only this person is working in until it is completed. And then the changes are merged back into the master branch via a pull request.
What should the master branch contain?
So, here are a few different thoughts, I know that a lot of people do it slightly differently. But here is what we have found that works well. And it is also the way that you basically have to go, in case you want to be able to pursue Continuous Integration/Continuous Deployment in some way or another.
The master branch should contain everything for a specific product that is needed for the product to be able to be released. So, what does this mean?
- Only fully tested code: If a new feature is developed, it should be code reviewed and functionally tested, before it is considered to be completed and can be pushed into the master branch. I am going into this a bit more below.
- Testing: This should be an automated testing app and it could also include test scripts for consultants or other testers to ensure that it is clearly documented what needs to be tested.
- Documentation: If a new feature is developed, you need to document it. And that doesn’t mean code documentation. It means the functional documentation or online help or manual. This way it is easy to see what the new features do – since they’re already documented.
If you are working on new and complex features for a product, sometimes, you have more than one person developing on it. Therefore, it makes sense to create feature branches. Those branches are branched off of the master branch and are called “features/new-feature”. You can use anything to describe the new feature, such as the name, the ID from DevOps, etc. It is only important that this is uniquely identified. When developers work on the new features, they will branch off of this branch instead of the master.
Again, this branch should contain all code, testing instructions (automated and manual), and documentation for this new feature. Once all of this is done – and the code is tested completely – the feature branch will be merged into the master branch and then deleted.
Each developer working on code will create their own branch. They can branch off the master branch or a feature branch – or even other branches as described below. This user branch is basically compartmentalize the changes the user is making from the rest. The user branches are called “users/username/branch-identifier”. They exist for the time that the user is working on a change. Once the developer tested the changes, possibly made updates to documentation, etc., it is merged back into the branch it originated from.
This is a different type of branch, not for real code changes. We create a country branch for each country we want to publish an app in. For instance, if we want to publish an app in the USA and Canada, we have a branch “country/us” and “country/ca”.
Every time, some new feature is checked into the master branch, those changes will be pushed into the individual country branches. We have automated builds defined that run automatically after changes to the country branch. It will compile, publish, and install the extension and also the test app and run the tests. If there are any failures do to localizations, we create additional bugs and have those fixed as part of the normal development process.
Our country branches are running off of the current Business Central version, since we want to make sure that we can publish our products in Business Central whenever we want – technically.
We also have branches that run the next minor or major build that is coming out. We actually do both – once a monthly validation, if our apps will break in the next update – and also twice a year a validation, if our apps will break in the next major release (spring/fall). Those branches are called “future/minor/country” and “future/major/country” where “country” is the country code as above in the country branches.
We also have automated builds running here that – by the way – use docker containers to publish the different localized versions of Business Central and then test our apps.
When we are ready to create a new release, we create a new branch “releases/versionno” (for instance, “releases/220.127.116.11”. We create this release branch for two reasons: First, we want to be able to do one final testing and validation to make sure that everything works. Second, we also want to be able to have developers continue working on new releases while we are sending our apps to Microsoft’s technical validation. If any issues arise there, we fix those (basically creating user branches from this release branch) and once they are fixed, merge them back into the release branch as well as into the master branch.
It is also important to have the exact code that is deployed somewhere (and that also means the documentation, etc.). In case a customer finds an issue, we can replicate it in this codebase and then isolate it and possibly fix it. Or we can make sure that it’s fixed already and just push a new master version into a new release.
One additional reason we need those branches is that we also need to test, if we have any issues in updating from the current release to this new release or if the upgrade code that we developed works well.
Those release branches do not live forever. We keep them around at a maximum until a new release is sent to validation.
How does our branching looks like?
I am trying to get this illustrated so that all us visual learners can see this as well. So, here is what our branching looks like at a maximum:
master |- users |- user1/bugxyz |- user2/documentation123 |- features/feature 1 |- users |- user1/taska |- user2/taskb |- releases/18.104.22.168 |- country |- us |- ca |- future |- minor |- us |- ca |- major | -us | -ca
What do we do during pull requests?
When someone is done with some changes and it’s time to create a pull request to merge the code back into the master/feature/release branch, we do a few different things.
For once, we do a code review. This can be a fairly simple thing or it can take a while. We will check to make sure that the code looks “good” from a pattern perspective as well as that we don’t find anything sticking out to cause issues. If we can find code that is similar in different parts of the software, we also might want to have it refactored before releasing. If we find anything, it will go back to the developer to be corrected.
We also do a documentation review or rather a review, if everything is completed. So, does the pull request contain the code, the new tests (and are those tests only “mockups” or actual real tests), and also contain the documentation or key usage scenarios that are needed for validation.
Additionally, we have automated builds running that will compile, build, sign, publish, and install the new version of the app (with the changes included) as well as run the tests against it to make sure that we still have everything working.
Where to go from here?
Well, some things might be overkill for some of you, some might think that things should be done differently. This is all up to you. I want to give you food for thought and show what we are doing. If you have any questions – drop a comment below. If you have any suggestions – drop a comment below. I am certainly interested in hearing your experiences and maybe you have found some additional things that are helpful for you and want to share those.