-
Notifications
You must be signed in to change notification settings - Fork 0
Code Structure
├── go.mod
└── src
├── cmd
│ ├── command_1
│ │ └── main.go
│ └── command_2
│ └── main.go
└── internal
├── config
│ └── config.go
├── entity
│ ├── domain_1
│ │ └── entity.go
│ └── domain_2
│ └── entity.go
├── handlers
│ ├── grpc
│ │ └── domain_1
│ │ └── v1
│ │ ├── handlers.go
│ │ └── models.go
│ ├── http
│ │ └── domain_1
│ │ └── v1
│ │ ├── handlers.go
│ │ └── models.go
│ └── messaging
│ └── domain_1
│ └── v1
│ ├── handlers.go
│ └── models.go
├── middlewares
│ ├── grpc
│ │ └── middleware.go
│ ├── http
│ │ └── middleware.go
│ └── messaging
│ └── middleware.go
├── server
│ ├── grpc
│ │ └── server.go
│ ├── http
│ │ └── server.go
│ └── messaging
│ └── server.go
└── services
└── domain_1
├── datastore
│ ├── interface.go
│ ├── mongo
│ │ ├── mongo.go
│ │ └── types.go
│ └── mysql
│ ├── mysql.go
│ └── types.go
├── domain_1_impl.go
└── interface.go
On the root level for a Go project we will need to have 2 things
-
go.modfile, this file has to be on the root for your IDE's or LSP's to identify the project as a Go project. If its not in root then you will have to fight with the IDE or LSP to configure it. Since the idea of this structure is to make your life easy, let's keep it in the root of the project -
src/directory will contain the meat of your service, this is where all the Go code lives. When someone looks at your repo and wants to see the Go code, this is where they should go
Before explaining the structure, I wanted to explain what a domain means in a nutshell. A domain represents the primary area of focus for the software being developed—the sphere of knowledge, behaviors, and business rules that the application seeks to address. The term "domain" is often used interchangeably with "business domain" or "problem domain."
src/ directory will contain 2 dirs
cmdinternal
The cmd directory acts as the entry point into your app, Be it starting up your app, running a database migration, or running something ad-hoc, This is the place where it has to live. The code in this directory does not and should not contain any business logic, it only contains the application initialization logic.
Ideally, if there are multiple commands it's better to give each command a unique name as a dir in the cmd directory and create .go files inside it. For example, if you have a web server with some database migrations the cmd dir will look something like this
-
src/cmd/app/main.go- This file/command will be responsible for starting the application/server -
src/cmd/migrations/main.go- This file/command will be responsible for starting the database migrations
By segregating the commands this way you will organize the code into appropriate responsibilities
The internal package contains all the source code related to the app like business logic, middleware, handlers, etc. As the name implies internal package contains all the internal code specific to the application. The only dir that is allowed to use this package is cmd, Although I say that, any package within the internal package can use the packages within it ( make sure there is no circular dependency. No other packages should import anything from it.
Now I will explain the internals of the internal package step by step. But before doing that I want to mention one important thing that I saw in other Go projects and its about the util package and pkg directory. This structure strongly discourages using these things because they eventually become a dumping ground for all "miscellaneous" things. The goal of this structure is to promote the readability of a project not the inverse
The config package contains the application config like HTTP Server config, Database config, 3rd party config, etc.
I do not want to dwell on what an entity means because if you already read till here it means that you read the Hexagonal Architecture blog. If you have not, I would highly recommend reading that before proceeding further.
If you see the sample structure above, I have separated entities into their own domains. It promotes readability and makes it easier to identify which entity belongs to which domain
The handlers package will contain your application's handlers or controllers ( if you are coming from a non-Go language ). The handlers package will contain sub-packages depending on your application's request handling style. For example,
- If your application is
RESTfulthen we will create ahttppackage within thehandlerspackage - If your application is
GRPCthen we will create agrpcpackage within thehandlerspackage - If your application is
Async( meaning it reads messages from a queue like Kafka ) then we will create amessagingpackage within thehandlerspackage
Once you decide the type of your application ( http, grpc or messaging ), we will now create a sub-package within it with the convention {domain_name}/{version}. For example, if your domain is payments with HTTP-based handlers then the structure would be something like handlers/http/payments/v1
This way all the handlers, and models ( request and response types ) that belong to a specific domain of a specific version are sitting together in one place 😉
The middlewares package will contain HTTP middleware's or GRPC interceptors or Messaging middleware's. Depending on the request type, its better to create a separate package for each request type as shown in the structure above.
There is no need for a concept of domain here because usually middlewares are global for an application
Similar to middlewares package, the server package is also global to the application and will contain code that typically handles routing and middleware initialization.
Similar to middlewares package, Depending on the request type, its better to create a separate package for each type of server as shown in the sample structure above
This package will contain all the business logic for your application. If you see the above structure each domain is self-contained as its package. One domain can depend on other domains as long as they are not causing any circular dependency issues (If one domain want to user other domain, it MUST import other domain's interface )
P.S domain_1 is just a mere example that I am taking here for the sake of simplicity, you should name your domains appropriately.
Each domain package must contain a file called interface.go which will act as a contract on what this domain will be doing. For example, if the domain is users then the interface.go file should contain something like below
type UserService interface {
GetUser()
CreateUser()
...
}
The idea of having an interface.go file is to have the ability to
- Mock the interface and write unit tests
- Have a strong contract to swap implementations without relying on concrete implementations
Once the interface is defined, we can create other .go files to implement the interface
If your domain wants to interact with a datastore, you can create a directory within the domain called datastore with a file called interface.go that defines what data layer methods the business logic will call. Once the interface is defined, we can create packages within the datastore package for different database implementations ( As shown in the tree above ).
For example, in the above structure, there are 2 packages with name mongo and mysql. Let's say you start an application with mysql as a database requirement and you implement the methods defined in the datastore interface. Now there is a requirement that you want to change the implementation from mysql to mongo. Now instead of changing all the mysql code, what you do is create a new package called mongo within datastore and implement the datastore interface. This way now in the domain, you just swap the mysql implementation with mongo. The business logic stays the same