07 Workflow Design How to Design a Reasonable Multi Developer Workflow

07 Workflow Design How to Design a Reasonable Multi-Developer Workflow #

Hello, I’m Kong Lingfei. Today, let’s talk about how to design a reasonable development model.

An enterprise-level project is completed by collaboration among multiple individuals. After different developers finish coding locally, they may submit their code to the same code repository. One developer may also work on multiple feature developments simultaneously. If these collaborative and parallel development features are not handled properly, it can lead to problems such as lost code, incorrect code merges, and code conflicts.

Therefore, before coding, we need to design a reasonable development model. Since most developers currently use Git for development, in this lesson, I will teach you how to design a reasonable development model based on Git.

So, how do we design a workflow? You can design your own workflow according to your needs, or you can adopt a well-established, well-designed, and popular workflow from the industry. On one hand, these workflows have been proven to be reasonable through long-term practice. On the other hand, using a well-known and industry-standard workflow can reduce the time needed for team coordination. In this lesson, I will introduce you to 4 popular workflows, from which you can choose one as your workflow design.

When using Git for development, there are 4 common workflows, also known as development models, which are centralized workflow, feature branch workflow, Git Flow workflow, and Forking workflow. Next, I will introduce these 4 workflows in chronological order of their development.

Centralized Workflow #

Let’s start by looking at the Centralized Workflow, which is the simplest way of development. The working mode of the Centralized Workflow is shown in the following diagram:

A, B, and C are three developers, each of whom has a local copy of the remote repository: the local repository. After A, B, and C finish developing the code on the local master branch, they commit the modified code to the remote repository. If there are conflicts, they resolve the conflicts locally before committing. After a period of development, the commit log of the remote repository’s master branch may look like the following diagram:

The Centralized Workflow is the simplest development pattern, but its disadvantages are also evident: the commit logs of different developers are mixed together, making it difficult to locate problems. If multiple features are developed simultaneously and merged into the master branch at the same time, the code may also interfere with each other, resulting in code conflicts.

Compared with other workflows, the code management of the Centralized Workflow is more chaotic and prone to problems. Therefore, it is suitable for use in small projects with few team members, infrequent development, and no need to maintain multiple versions simultaneously. When we want to develop multiple features in parallel, this workflow is not suitable. In such cases, we can look at the Feature Branch Workflow next.

Feature Branch Workflow #

The Feature Branch Workflow is based on the Centralized Workflow. When developing a new feature, a new branch is created based on the master branch, and the development is done on the feature branch instead of directly on the local master branch. After the development is completed, the changes are merged into the master branch, as shown in the diagram below:

Compared to the Centralized Workflow, this workflow allows different features to be developed on different branches and merged into the master branch only at the end. This not only avoids mutual interference between different features but also makes the commit history look more concise.

In addition, when merging into the master branch, a pull request (PR) needs to be submitted instead of directly merging the code into the master branch. The PR process not only allows the branch code to be reviewed by other developers in the team but also enables code discussions on the PR page. Through code review, we can ensure that the code merged into the master branch is robust. Through discussions on the PR page, developers can actively participate in code discussions, which helps improve code quality and provides a way to review the history of code changes.

So what is the specific development process of the Feature Branch Workflow? Let’s take a look together.

  1. Create a new feature branch based on the master branch. The feature branch can be given a meaningful name for better understanding, such as feature/rate-limiting.
$ git checkout -b feature/rate-limiting
  1. Develop the code on the feature branch and commit the changes to the feature branch after development is completed.
$ git add limit.go
$ git commit -m "add rate limiting"
  1. Push the local feature branch code to the remote repository.
$ git push origin feature/rate-limiting
  1. Create a pull request on the remote repository (e.g., GitHub).

Go to the project homepage on the GitHub platform and click Compare & pull request to submit a pull request, as shown in the screenshot below.

After clicking Compare & pull request, you will enter the PR page, where you can write comments as needed. Finally, click Create pull request to submit the PR.

  1. After receiving the PR, the code administrator can review the code. After the code review is approved, click Merge pull request to merge the PR into the master branch, as shown in the screenshot below.

The “Merge pull request” button in the screenshot offers 3 merge methods:

  • Create a merge commit: GitHub performs git merge --no-ff under the hood. All commits on the feature branch will be added to the master branch and a merge commit will be generated. This method allows us to clearly see who made the commits and what commits were made, making it easier to trace the history.
  • Squash and merge: GitHub performs git merge --squash under the hood. “Squash and merge” will merge all the commits on this pull request into one commit and add it to the master branch, but the original commit history will be lost. If the commits made by developers on the feature branch are casual and not standardized, this method can be chosen to discard meaningless commits. However, in large projects, each developer should follow commit conventions, so I don’t recommend using Squash and merge in team development.
  • Rebase and merge: GitHub performs git rebase under the hood. This method will add all the commit history on the pull request to the top of the master branch in the original order. Because git rebase is risky, I do not recommend choosing this method when you are not fully familiar with Git workflows.

By analyzing the advantages and disadvantages of each method, in actual project development, I recommend using the Create a merge commit method.

From the specific development process we just discussed, we can see that the Feature Branch Workflow is relatively simple to get started with. It not only allows you to develop multiple features in parallel but also adds code review to ensure code quality. Of course, it also has disadvantages, such as not giving branches clear goals, which is not conducive to team collaboration. It is suitable for use in projects with a relatively fixed development team and a small scale. The next workflow we will discuss, Git Flow, is based on the Feature Branch Workflow and better addresses the above-mentioned problems.

Git Flow Workflow #

Git Flow workflow is a very mature solution and the most commonly used workflow in non-open source projects. It defines a strict branch model centered around project releases, making the iterative process of project development, release, and maintenance smoother by allocating independent branches for code development, release, and maintenance. It is more suitable for large projects or projects with a fast iteration speed. Next, I will explain how Git Flow works by introducing its 5 branches and workflow.

5 Branches in Git Flow #

Git Flow defines 5 branches, namely master, develop, feature, release, and hotfix. Among them, master and develop are permanent branches, while the others are non-permanent branches that are used in different development stages. The detailed descriptions of these 5 branches are shown in the table below.

Git Flow Development Workflow #

Let’s demonstrate the Git Flow development workflow using a practical example. The scenario is as follows:

a. The current version is: 0.9.0.

b. We need to develop a new feature that prints the “hello world” string to the standard output when the program is executed.

c. During the development stage, there is a bug in the live code that needs to be urgently fixed.

Assuming our Git project is called gitflow-demo, and there are 2 files in the project directory, README.md and main.go, with the following content.

package main

import "fmt"

func main() {
	fmt.Println("callmainfunction")
}

The specific development workflow consists of 12 steps. You can follow the steps below for practice.

  1. Create a permanent branch: develop.
$ git checkout -b develop master
  1. Create a feature branch based on the develop branch: feature/print-hello-world.
$ git checkout -b feature/print-hello-world develop
  1. In the feature/print-hello-world branch, add the line of code fmt.Println("Hello") to the main.go file. The modified code is as follows.
package main

import "fmt"

func main() {
	fmt.Println("callmainfunction")
	fmt.Println("Hello")
}
  1. Perform an urgent bug fix.

While we are in the middle of developing the new feature (only completed fmt.Println("Hello") instead of fmt.Println("Hello World")), suddenly a bug is found in the live code. We need to immediately stop the current work and fix the bug. The steps are as follows.

$ git stash # 1. If the development work is only half done and you don't want to commit yet, you can save the changes to the stash temporarily
$ git checkout -b hotfix/print-error master # 2. Create a hotfix branch from master
$ vi main.go # 3. Fix the bug, change callmainfunction to call main function
$ git commit -a -m 'fix print message error bug' # 4. Commit the fix
$ git checkout develop # 5. Switch to the develop branch
$ git merge --no-ff hotfix/print-error # 6. Merge the hotfix branch into the develop branch
$ git checkout master # 7. Switch to the master branch
$ git merge --no-ff hotfix/print-error # 8. Merge the hotfix branch into the master branch
$ git tag -a v0.9.1 -m "fix log bug" # 9. Create a tag on the master branch
$ go build -v . # 10. Compile the code and update the compiled binary file in the production environment
$ git branch -d hotfix/print-error # 11. After the bug is fixed, delete the hotfix/xxx branch
$ git checkout feature/print-hello-world # 12. Switch to the development branch
$ git merge --no-ff develop # 13. Since the develop branch has been updated, it is better to synchronize the updates here
$ git stash pop # 14. Restore to the state before the fix
  1. Continue development.

Add fmt.Println("Hello World") to main.go.

  1. Commit the code to the feature/print-hello-world branch.
$ git commit -a -m "print 'hello world'"
  1. Perform code review on the feature/print-hello-world branch.

Firstly, we need to push feature/print-hello-world to the code hosting platform, such as GitHub.

$ git push origin feature/print-hello-world

Then, on GitHub, create a pull request based on feature/print-hello-world, as shown in the following image.

After creating the pull request, you can designate reviewers for code review, as shown in the following image.

  1. After the code review is approved, the code repository maintainer merges the feature branch into the develop branch.
$ git checkout develop
$ git merge --no-ff feature/print-hello-world
  1. Based on the develop branch, create a release branch and test the code.
$ git checkout -b release/1.0.0 develop
$ go build -v . # After building, deploy the binary file and perform testing
  1. The test fails because we expect it to print “hello world,” but it prints “Hello World.” When fixing the bug,

We directly modify the code in the release/1.0.0 branch, submit the changes, and compile and deploy.

$ git commit -a -m "fix bug"
$ go build -v .
  1. After the test passes, merge the feature branch into the master and develop branches.
$ git checkout develop
$ git merge --no-ff release/1.0.0
$ git checkout master
$ git merge --no-ff release/1.0.0
$ git tag -a v1.0.0 -m "add print hello world" # Create a tag on the master branch
  1. Delete the feature/print-hello-world branch, and you can also selectively delete the release/1.0.0 branch.
$ git branch -d feature/print-hello-world

After personally going through these steps, you should have a better understanding of the advantages and disadvantages of this pattern. Its disadvantage, as you have experienced, is that it has a certain degree of learning curve. However, the Git Flow workflow still has many advantages: each branch in the Git Flow workflow has clear roles, which minimizes their mutual interference. Because multiple branches can be created, multiple features can be developed in parallel. In addition, like the feature branch workflow, code review can also be added to ensure code quality.

Therefore, the Git Flow workflow is more suitable for development teams with relatively fixed members and larger projects.

Forking Workflow #

The Git Flow described above is the most commonly used workflow for non-open source projects. In open source projects, the most commonly used workflow is the Forking Workflow, which is used by projects like Kubernetes and Docker. Let’s first understand the concept of forking.

Forking is the process of creating a copy of a target remote repository in your own remote repository. For example, when using GitHub, you can click the Fork button (located at the top right corner of the project page) to create a copy of the target remote repository. The workflow of the Forking Workflow is illustrated in the following diagram:

Let’s assume that Developer A owns a remote repository. If Developer B wants to contribute to Project A, they can fork a copy of A’s remote repository to their own GitHub account. Afterward, B can develop their own version of the project. Once the development is complete, B can submit a pull request (PR) to A. A will receive a notification and review the PR. If there are any issues, A can leave comments directly on the PR page. B will then make further modifications based on the comments. Finally, A will merge B’s PR request, incorporating B’s code into A’s repository. This completes the process of developing new features for Project A. If other developers want to contribute code to A, they would also follow the same process.

The detailed steps of the Forking Workflow in GitHub involve six steps (assuming the target repository is gitflow-demo). You can follow the steps below to practice:

  1. Fork the remote repository to your own account.

Visit https://github.com/marmotedu/gitflow-demo and click the Fork button. The forked repository address will be https://github.com/colin404fork/gitflow-demo.

  1. Clone the forked repository to your local machine.
$ git clone https://github.com/colin404fork/gitflow-demo
$ cd gitflow-demo
$ git remote add upstream https://github.com/marmotedu/gitflow-demo
$ git remote set-url --push upstream no_push # Never push to upstream master
$ git remote -v # Confirm that your remotes make sense
origin	https://github.com/colin404fork/gitflow-demo (fetch)
origin	https://github.com/colin404fork/gitflow-demo (push)
upstream	https://github.com/marmotedu/gitflow-demo (fetch)
upstream	https://github.com/marmotedu/gitflow-demo (push)
  1. Create a feature branch.

First, sync your local repository’s master branch with the latest changes from the upstream master branch.

$ git fetch upstream
$ git checkout master
$ git rebase upstream/master

Then, create a feature branch.

$ git checkout -b feature/add-function
  1. Commit your changes.

Develop the code on the feature/add-function branch and commit your changes when you’re done.

$ git fetch upstream # Sync feature with upstream/master before committing
$ git rebase upstream/master
$ git add <file>
$ git status
$ git commit

After completing the feature development, you may have multiple commits. However, when merging into the main branch, it is often recommended to have only one (or a few) commits. This can be achieved by combining and modifying the commits using git rebase. The following operations are performed:

$ git rebase -i origin/master

The usage of git rebase -i was explained in Lesson 5. If you have any questions, you can review that section. Here, we will not explain it again.

Another convenient method to merge commits is to first revert the last 5 commits and then create a new one:

$ git reset HEAD~5
$ git add .
$ git commit -am "Here's the bug fix that closes #28"
$ git push --force

The squash and fixup commands can also be used as command-line arguments to automatically merge commits.

$ git commit --fixup
$ git rebase -i --autosquash
  1. Push the feature branch to your remote repository.

After you have finished development and made commits, you need to push the feature branch to your personal remote repository.

$ git push -f origin feature/add-function
  1. Create a pull request on your remote repository page.

After pushing to the remote repository, you can create a pull request and request reviewers to review the code. Once the code is reviewed and approved, it can be merged into the master branch. It is important to note that when creating a pull request, the base branch is usually set to the master branch of the target remote repository.

We have covered the specific steps of the Forking Workflow. What do you think are its advantages and disadvantages?

Considering its characteristics, let’s look at its advantages: In the Forking Workflow, the project’s remote repository and the developer’s remote repository are completely independent. Developers contribute code to the remote repository by submitting pull requests, and project maintainers selectively accept any developer’s contributions. This allows developers to participate in the project without being granted direct access to the project’s remote repository, thereby improving the security of the remote repository. It also allows any developer to contribute to the project.

However, the Forking Workflow has limitations. It is not particularly beneficial for projects with well-defined roles and that are not intended for public contribution.

The Forking Workflow is suitable for three main scenarios: (1) open-source projects, (2) developers who have derived their own versions of the project, and (3) developers who are not fixed and can be any developer with access to the project.

Summary #

In this lecture, I introduced 4 development workflows based on Git. Let’s review them together.

  • Centralized Workflow: Developers directly work on the local master branch and push the completed code to the remote master branch.
  • Feature Branch Workflow: Developers create a new branch based on the master branch, work on the new branch, and then merge it into the remote master branch.
  • Git Flow Workflow: The Git Flow Workflow assigns specific roles to different branches and defines when and how the branches interact. It is more suitable for large-scale projects.
  • Forking Workflow: Developers first fork the project to their personal repositories, complete the development in their own repositories, submit a pull request to the target remote repository, and after a review, the pull request is merged into the master branch.

The Centralized Workflow is the earliest Git workflow, and the Feature Branch Workflow is based on the Centralized Workflow. The Git Flow Workflow is based on the Feature Branch Workflow, and the Forking Workflow decouples the personal remote repository from the project remote repository.

Each development workflow has its advantages and disadvantages, and is suitable for different scenarios. I summarized them in the table below:

In general, when choosing a workflow, my recommendation is as follows:

  • Use the Git Flow Workflow for non-open-source projects.
  • Use the Forking Workflow for open-source projects.

Because the hands-on project in this course is a relatively large non-open-source project, we have adopted the Git Flow Workflow.

Exercise #

  1. Please create a new project and follow the Git Flow development process. Perform each step yourself and observe the results of each operation.
  2. Please think about how to handle a temporary bug fix in the code repository within the Git Flow workflow.

Looking forward to seeing your thoughts and sharing in the comments section. See you in the next lesson!