Passage backend APIs are built with a microservice architecture with Go in a monorepo. Each microservice contains its own go.mod and go.sum file, as they are completely independent from one another. However, we found that we were often duplicating code across all of these different microservices, especially for things like helper functions, logging and error functionality. We wanted to create packages that could organize all of this shared code nicely while still keeping all of the code in a single repository.
In our initial microservice implementation each individual service contained all of these packages and our structure looked something like this:
This organization has some downsides for developers. Shared code is duplicated in each service, which makes it difficult to keep all of the services up to date. Making updates to these shared packages required us to make changes throughout all of the services.
We needed a better solution. Our main goals were to keep all of these shared packages inside of this repository, instead of creating multiple repos for them and to only have one version of each of the shared packages to reduce duplicated code and engineering work. After some research, we found a clean solution where each shared package becomes its own package in the monorepo with individual go.mod and go.sum files. The new structure looks like this:
Now we need to install these shared packages as dependencies in each of the services. To do this we use go mod edit -require and go mod edit -replace to place each of these dependencies in the go.mod files for each of the services that require the shared packages. Below is an example of the commands needed to do this for a single service:
After doing this, we are able to use any of the functions declared in any of these shared packages inside of the service-1 microservice the same way we would an external package. Now all of the code for the shared packages is in a single place which declutters the monorepo and makes maintainability much more seamless.