Monorepo vs Polyrepo, Microservices with Monorepo in Go
Monorepo is a software-development strategy in which the code for several projects is stored in the same repository. It is controversial and I admitted that it doesn't fit into any spectrum like polyrepo. In this article, I told my experience while working in monorepo and polyrepo. Maybe you have another experience while working with monorepo. I'm glad to hear your review in the comment section below.
I discussed and debated with my friends about monorepo. From high-level to the low-level discussion. Research many articles on the internet. Of course, getting hands-on monorepo to feel the experience. In short, I can say that polyrepo fits all spectrums or all projects. Maybe you think the same because it is a default strategy for version control. But, there is an advantage in monorepo if it fits the project and team. That advantage is what we looking for. But, we will get into trouble if the monorepo doesn't fit, and it is not easy to escape.
Advantages
- Focus on a single repo. When we come into the project of microservices, we don't need to ask our team what repos we need to clone, because all sub-repos or sub-directories mean they are a part of the project.
- We can react to all public functions easily. Let's say we want to create a public function, package, helper, or library. Maybe the function we're looking for already exists in another service. We may adapt, enhance, or improve that function. So, our code is efficient.
- Let's say a feature needs to be released soon. That feature affects many services that need to be deployed at the same moment. Situations like this happen frequently. So, monorepo is the right choice.
Disadvantages
- Let's say we enter a project and we only need to work on one service or sub-project. We were forced to clone a large repo, including many services we didn't need. In this situation, monorepo is a bad choice.
- Let's say we work on a microservices project. There is a service that has bugs or domain changes. We need to see Git logs and it is hard to find the issue because we face a lot of other service logs that we need to filter. So, it is another reason to reject monorepo.
- On release or deployment day, there are often one or two services that only need to be deployed. So, I think monorepo is not the right choice.
We heard that many high-tech companies like Twitter, Meta, Microsoft, and even Google employ very large monorepos. They built microservices with thousands of developers working in the same repository, and even in the same branch called trunk (master/main). That development workflow is called Trunk-Based Development (TBD). Maybe I will make an article about it in the future. But TBD is also controversial, even though I agreed and worked with that workflow.
Microservices in monorepo are hot these days. But, there are things we need to prepare or consider before we set it up. Like setting some rules. There are many rules that we need to set in the team. One of the general rules is the naming convention. In many naming convention topics, I just want to talk about Git commit messages here to keep it short.
We can force our team to use the same Git commit message naming convention using Git hooks.
#!/bin/sh commit_regex='(chore:+|docs:+|feat:+|fix:+|Merge+|Initial commit)' error_message='Your commit message is invalid.' if ! grep -iqE "$commit_regex" "$1"; then echo "$error_message" >&2 exit 1 fi
The code above is just an example of a Git hook for a commit message. To enable the hook, you can save it to commit-msg
, put it on <your-project>/.git/hooks
, and set permission on it like chmod ug+x <your-project>/.git/hooks/commit-msg
. For more information you can visit, https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks.
In this article, I create an example of setting up a microservices project with monorepo in Go. Let's say we want to build a Blog. So, I created a file structure like this.
+-- article | +-- cmd | | +-- app | | | +-- main.go | +-- internal | | +-- internal.go | +-- pkg | | +-- pkg.go | +-- go.mod +-- image | +-- cmd | | +-- app | | | +-- main.go | +-- internal | | +-- internal.go | +-- pkg | | +-- pkg.go | +-- go.mod +-- pkg | +-- pkg.go +-- user | +-- cmd | | +-- app | | | +-- main.go | +-- internal | | +-- internal.go | +-- pkg | | +-- pkg.go | +-- go.mod +-- go.work
In Go, all packages in the internal
folder cannot be exported. In this example, we have three services; user
, article
, and image
. And we have one global package, pkg
. All services have public packages in the pkg
directory.
go 1.19 use ( ./article ./image ./user )
The go.work
file allows text editors to understand monorepo projects. For more information about go.work
files, you can visit https://go.dev/doc/tutorial/workspaces.
module github.com/aristorinjuang/microservices-go/article go 1.19 require github.com/aristorinjuang/microservices-go/pkg v0.0.0 replace github.com/aristorinjuang/microservices-go/pkg v0.0.0 => ../pkg
Just let the global package, pkg
, on the v0.0.0
and use the replace
syntax to import it to the sub-project or service.
When we are in a single project or polyrepo of Go, it is easy to build and test the project. Just use go build
and go test
. When it comes to monorepo, we need to build and test all or several services at once. I suggest you Bazel. It is fast and you can integrate it into your CI/CD. Very impressive.