Golang Project Structure
I came to Go about a year ago. I write in it in my spare time and I like it. I think it’s a very good language for both beginners in IT and those who already have programming experience. To date, I use Go in my personal projects and in professional work.
After a year of using the language in production, I can confidently say that Go has only shown itself from the best side. Having mainly Javascript developers in the team, I had no difficulties in introducing them to the course of things. The readability and simplicity of the language allows you to quickly understand the code. But I wouldn’t say that Go is a simple language.
During this time, I have written several microservice projects in Go. They were all of different sizes and complexities, but in each of them, I tried to use similar approaches to the project structure. Gradually, I came to a structure that I like. I do not claim that this is the best structure for a microservice project, but it works for me. I hope you will like it too.
In addition, I want to note that I am not an expert in Go. I’m just sharing my experience. If you notice any mistakes or have suggestions for improvement, do not hold back and write to me about it.
Project Structure
I took the ideas from Clean Architecture and in symbiosis with basic recommendations, after several iterations, I got the following project structure:
.
├── deploy
├── pkg
│ ├── config
│ ├── grpc
│ ├── log
│ ├── mongodb
│ └── types
├── services
│ └── service1
│ ├── app
│ │ ├── grpc
│ │ └── http
│ ├── business
│ │ ├── core
│ │ ├── data
│ │ └── system
│ └── deploy
├── tools
└── web
├── deploy
├── public
├── node_modules
├── static
└── src
Now let’s take a closer look at what these folders are and what files they contain.
deploy
This folder contains files necessary for deploying the project. Depending on the size of the project and the required infrastructure, the deployment can be centralized or specific to each of the services. This is also where files for deploying common subsystems can be found. This is the most appropriate place for Kubernetes or Docker Compose files. In most cases, I use Docker Compose for local development and Docker Swarm for production deployment.
pkg
This folder contains all local packages used in the project. I do not use external packages inside the pkg
folder. All
external packages are stored in the vendor
folder. All packages inside the pkg
folder should be independent and
should not have dependencies on packages outside this folder. This allows for easy reuse of packages in other projects.
If a package can be moved to a separate repository, but it has not yet grown to that level and is only needed within
this project - you can safely put it here.
You can hide packages with the internal
folder, but I see no point in this as it only clutters the project.
In the project structure above, I provided several packages as examples that I use in my projects. Let’s quickly go over them.
- config - a package for working with configuration. I sometimes use a wrapper over viper or something small and custom depending on the project.
- grpc - a package for working with gRPC. I use grpc-go and grpc-gateway. In this package, I store all the necessary files for working with gRPC and gRPC-gateway.
- log - a package for working with logs. As a rule, I use zap from Uber. This is a very fast and convenient package for working with logs.
- mongodb - a package for working with MongoDB. I use mongo-go-driver.
- types - a package for storing common types. I use it to store common data types that are used in different parts of the project. As a rule, I try to redefine types from external packages in this package. This allows not to create dependencies on external packages within the project domain.
All the above packages inside the pkg
folder are defined as examples and can be changed depending on the project.
services
The most important directory in the project. This is where all the project’s services are stored. Each service has its
own folder, and inside it are all the necessary files. This folder also contains the main.go
file, which is the entry
point to the application. In this file, I try to keep the minimum necessary code for startup, including dependency
initialization and server startup.
Inside the service folder, there are three folders: app
, business
, and deploy
. Let’s go over each of them.
service/app
This folder contains files related to the external interface of the application. In my example, inside this folder,
there are two folders: grpc
and http
. They contain files related to gRPC and HTTP respectively. Inside these
folders, there may be files with request handlers, middleware files, files with validators of external data, etc.
Ultimately, this folder should only contain files related to the external interface of the application and not connected
with its business logic. This is more of a transport gateway that accepts requests, validates them, passes them to the
business logic, and returns a response.
service/business
This folder contains files related to the business logic of the application. In my example, inside this folder, there are three folders:
- core - a folder with files related to the core of the business logic. This folder contains files with entities that describe the business logic of the application. There should not be any auxiliary files here that are not related to business logic.
- data - a folder with files related to data processing. This folder contains files with repositories responsible for working with the database. There should not be any auxiliary files here that are not related to data processing. This folder can also contain files with services responsible for working with external services. For example, if a service aggregates data from several external services, then the files with access to these services should be in this folder, and the business logic should use them.
- system - a folder with files related to system things. I usually keep files with middleware here that are not related to business logic. For example, if you need to log all requests to the service, then the file with the middleware responsible for logging should be in this folder. Also, there can be files with auxiliary functions that are not related to business logic.
service/deploy
This folder contains files related to the deployment of this service. As a rule, I limit myself to the Dockerfile
. In
it, I describe all the necessary steps to start the service. Everything else I do through docker-compose
and
Makefile
at the project level.
tools
This folder contains files related to the project’s tools. These can be files with code generators, administrative applications, or log formatters. Anything that can help you in development but is not related to the code of the project itself should be in this folder.
web (optional)
Sometimes the project requirements include the need to create an administrative panel. In this case, I create a web
folder and create a helper web application in it. This can be anything at your discretion. As a rule, I use
vite with React under the hood.
The folder also contains deploy
and public
folders, which are similar to folders with the same names in the
services
folder.
All the above folders inside the web
folder are defined as examples and can be changed depending on the project.
Makefile
At the root of the project is a Makefile
that contains all the necessary commands for working with the project. All
commands in this file are divided into two groups: dev
and prod
. The dev
group commands are intended for working
with the project during development, and the prod
group commands are intended for working with the project in
production. All prod
group commands are used only in CI/CD, and dev
group commands are used only during development.
Typically, the dev
group contains commands to start the project, and the prod
group contains commands for
deployment. Here is an example from the Makefile
of one of my test projects:
dev-swarm-up: build
docker swarm init
docker stack deploy -c ./deploy/portainer-stack.yml portainer
docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions
docker config create loki_config ./deploy/loki-config.yml
docker stack deploy -c ./deploy/grafana-loki-stack.yml loki
docker stack deploy -c ./deploy/app-stack.yml app
...
.PHONY: swarm-up
In this example, I initialize Docker Swarm and deploy several services. All commands from the prod
group should be in
the format prod-<command>
, and all commands from the dev
group should be in the format dev-<command>
.
Conclusion
In this article, I described my approach to project structure. I do not claim that this is the only correct approach, but it suits me until new requirements appear. If you have any ideas for improving this approach, then write me a word. I will be happy to listen to your ideas and, perhaps, add them to my approach and use them in my projects.