Swift/UnwrapDiscover about Swift, iOS and architecture patterns

Development workflows

September 27, 2022

A few weeks ago a few people (well... just one person πŸ˜„) asked me how I was handling release process.

To give a complete answer I actually described how my team is working together and thus our development workflow: how do we make branches, merge requests, and so on.

I thought it might be of interest to some people. So here it is!

GitFlow is dead πŸ’€

Like most people I guess I started using using GitFlow approach. While working with my colleagues I always found it cumbersome and error prone.

Then came a day where I worked a single iOS software engineer on a project. I quickly ditched some of Gitflow processes like creating release branches (as I was the only committer) then step by step... just throwing everything away.

Because I really appreciated this approach I tried to also apply it when working as a team. Disclaimer: it work πŸŽ‰.

I later found out I did not invent anything new πŸ₯². Some people already had the same idea and called it trunk based development

GitFlow example: 3 feature branches merged when completed into develop

Trunk based development

Trunk based development is described as a model where "developers collaborate on code in a single branch called β€˜trunk’ (or main/master), resist any pressure to create other long-lived development branches by employing documented techniques. They therefore avoid merge hell, do not break the build, and live happily ever after". Let's break it done.

One branch

Unlike GitFlow every developer commit its change to the main branch. Of course those changes are not made without prior approval so you still make merge requests. However here MR/branches are short-living (a few days) to be merged as quickly as possible on the main branch.

No long living branch

As branches are short living you need to divide features into small working branches, each one adding new functionalities. For instance:

  • πŸ—Ί One branch adding the domain model and webservices
  • 🎨 One creating the UI
  • πŸ”Œ Another one to plug it
  • ♻️ Another one refactoring some deprecated code, and so on...

The technique of MR containing unplugged code until another one plug everything is a good strategy to merge finished code but uncompleted feature.

This approach not only to merge quickly but also help having small MR meaning faster (and more accurate) review leading to MRs being merged faster πŸŽ‰.

This may not prevent you from delivering uncompleted feature though. To avoid exposing it to users you can rely on another technique: feature flags 🏳.

It can be challenging for people not used to this way of coding as it require approach development a different way. But it really pays off.

Same workflow example using master/main branch: branches are smaller and merged into main

QA Testing 🧐

Some might wonder how QA can test in such environment. Two possibilities:

  1. You merge your branch once validated by dev and QA testers
  2. You merge once validated by dev and deploy a build from main branch for QA to test

I prefer going with the latest approach for multiple reasons:

  1. Merge requests are merged more quickly
  2. Developers need to be more attentive as they know their work will be merged before QA tests
  3. I believe QA provide additional tests to find some edge cases but a team should be able to deliver confidently even with no QA

End result is that QA indeed find bugs but very few and very rarely blocking ones.

Release time βŒ›οΈ

Once ready how can you make a release? Very simply actually:

git tag -a v5.0.0 -m "Some release notes"

This command create a tag on Git repository flagging the commit as being the release commit. One created our CI detect it, run a build and upload it on Testflight. And... that's all πŸ€·β€β™‚οΈ. Easy, simple to remind process.

This has so much advantages compared to maintaining a not-so-useful branch like on Gitflow:

  • It's easy to create
  • It's visible in Git history
  • Tag description can be used as a changelog
  • Using git diff v4.0.0...5.0.0 you can quickly see which commits were made since previous release

Sometimes though process can be a little bit longer: last minute fix before release or event production hotfix. How to handle those cases? By fetching and starting back from the tag:

git checkout v5.0.0
git checkout -b release/5.0.1

In this kind of scenario yes we create a release branch. Only for the time of patching the release and sending it in production.

We even sometimes drop the release branch creation if our main branch was not modified since last release. But let's be honest it's very rare unless when spotting last minute bugs.

How do we make sure though that everything we send as hotfix also end up in main?

Same player merge again ‼️

Trunk based development is pretty clear on that:

  1. You first create a MR fixing main
  2. You then create a second one applying the patch on the target release

In theory it work well. In practice it is sometimes annoying as the patch might need some modifications between main and release.

Also once the fix is made on main we very often... forgot to apply the fix on the release branch πŸ™ˆ. So we tried the other way around but the problem remain as we also forget to apply the fix on main πŸ˜….

So for now we don't have any perfect solution to this (small) problem πŸ™ƒ.

That's a wrap

I cannot tell you how pleasant it is to work using Trunk based development. Processes are simple to follow/remember and things are going quick.

If you're still following Gitflow I would definitely advise you to give a try. It may require you some adoption time but it's definitely worth it.

Spend more time coding and less resolving merge conflicts! βš”οΈ