Skip to content

Code Structure

Venkata S S K M Chaitanya edited this page Oct 31, 2024 · 26 revisions

Sample Structure Tree

├── 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

Structure Explanation

On the root level for a Go project we will need to have 2 things

  • go.mod file, 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

What is a domain?

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

src/ directory will contain 2 dirs

  • cmd
  • internal

cmd directory

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


internal Package

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


config Package

The config package contains the application config like HTTP Server config, Database config, 3rd party config, etc.


entity Package

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


handlers Package

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 RESTful then we will create a http package within the handlers package
  • If your application is GRPC then we will create a grpc package within the handlers package
  • If your application is Async ( meaning it reads messages from a queue like Kafka ) then we will create a messaging package within the handlers package

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 😉


middlewares Package

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


server Package

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


services Package

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 )

domain_1 Package

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

What if my domain wants to have a datalayer?

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