In this post, we will explore how to organize a Go project. Unlike other languages where frameworks often provide code organization patterns, in Go we have the freedom to organize the project as we prefer. This is good, but without a clear pattern, the code can become disorganized and difficult to maintain. Therefore, it is essential to establish a structure that allows developers to easily locate where each part of the code should be placed.

In this structure, we will be able to apply the principles of Clean Architecture and use the advantages of the Go language to create a well-organized and easy-to-maintain project. It is worth noting that the structure should change according to the needs of each project, so it is important to adapt to the specific context.

Go is a language organized in packages, and therefore it is essential to think about the project structure so that the packages are well-defined and easy to understand. Some guidelines help maintain code organization and quality:

  • Responsibility Isolation: Each package should have a well-defined responsibility, avoiding mixing functionalities. This makes the code more modular and easier to understand.
  • Dependency Minimization: Packages should depend on other packages only when really necessary. In Go, we can fall into circular dependency if we are not careful with dependencies.
  • Standardized Structure: Using a predictable structure makes it easier to understand and navigate the code, especially in teams.
  • Testability: Organize the code to facilitate the creation of unit and integration tests.
  • Dependency Inversion: Use interfaces to abstract dependencies, especially external packages, keeping the code more flexible and decoupled.
  • Independence from Third-Party Packages: Avoid direct dependencies on external packages, using interfaces and whenever possible, creating wrappers to interact with these dependencies.

cmd

Let’s start with the cmd folder, where the main application code, the main.go file, is located. This file is responsible for integrating all project dependencies and generating the executable. We can create subfolders within cmd for each executable application in the project, such as api and worker, organizing the entry points of each service.

├── cmd
│   ├── api
│   │   └── main.go
│   └── worker
│       └── main.go 

domain

Domain (domain types)

The domain folder is where the application’s domain types are located. Domain types are data structures that represent the main business concepts of the application. For example, in a sales system, domain types could include Order, Product, Customer. These types reflect the main entities of the application.

So, let’s create the domain types for our project as follows:

├── domain
│   ├── product.go
│   └── order.go

The code inside each file would be something like this:

// domain/product.go
package domain

import (
	"time"

	"go.mongodb.org/mongo-driver/bson/primitive"
)

type Product struct {
	ID          primitive.ObjectID `json:"id" bson:"_id,omitempty"`
	Name        string             `json:"name" bson:"name"`
	Description string             `json:"description" bson:"description"`
	Price       float64            `json:"price" bson:"price"`
	UpdatedAt   time.Time          `json:"updated_at" bson:"updated_at"`
	CreatedAt   time.Time          `json:"created_at" bson:"created_at"`
}
// domain/order.go
package domain

import (
	"time"

	"go.mongodb.org/mongo-driver/bson/primitive"
)

type OrderStatus string

const (
	OrderStatusPending  OrderStatus = "pending"
	OrderStatusApproved OrderStatus = "approved"
)

type Order struct {
	ID        primitive.ObjectID `json:"id" bson:"_id,omitempty"`
	Customer  string             `json:"customer" bson:"customer"`
	Products  []Product          `json:"products" bson:"products"`
	Total     float64            `json:"total" bson:"total"`
	Status    OrderStatus        `json:"status" bson:"status"`
	UpdatedAt time.Time          `json:"updated_at" bson:"updated_at"`
	CreatedAt time.Time          `json:"created_at" bson:"created_at"`
}

internal

The internal folder is where the code that should not be accessed externally is located. Go itself prevents packages within the internal folder from being accessed by other files outside the same main package. I like to use this folder to place the business logic of my application, separating it into subfolders by domain (domain types).

├── internal
│    ├── order
│    │   ├── dto.go
│    │   ├── service.go 
│    │   ├── repository.go
│    │   └── handler.go
│    └── product
│        ├── dto.go
│        ├── service.go 
│        ├── repository.go
│        └── handler.go

Note that this is where the business logic of the application is implemented. Each domain type has a service.go file that contains the business logic for that type. The repository.go file contains the data access logic for that type. The handler.go file contains the HTTP request handling logic for that type. The dto.go file contains the data transfer types that are used to represent the data that is passed between layers.

DTO

Although we have the domain folder with the data structures that we will store in the database, we may need a different data structure to represent the data we want to treat as editable in our API. For this, we can use the dto.go file to create these structures. In the case of a PUT or POST endpoint, I add the Input suffix, so for an order creation function, I would have something like this:

// internal/order/dto.go
type CreateInput struct {
	Customer string  `json:"customer" validate:"required"`
	Total    float64 `json:"total" validate:"required"`
	Status   string  `json:"status" validate:"required,oneof=pending approved"`
}

In the case of a GET endpoint for those that will have query params, I add the Params suffix, so for an order listing function, I would have something like this:

// internal/order/dto.go
type ListParams struct {
	Customer string `json:"customer"`
}

To define the response payload of the endpoints, you can create a new struct specific to the return. In this case, I use the Response suffix.

// internal/order/dto.go
type CreateResponse struct {
	Customer string      `json:"customer"`
	Products []Product   `json:"products"`
	Total    float64     `json:"total"`
	Status   OrderStatus `json:"status"`
}

Service

The service is where the business logic is implemented. It is responsible for orchestrating calls to the repository, validating input and output data, and executing the business logic.

// internal/order/service.go
package order

import (
	"context"

	"github.com/cmparrela/ddev-pkg-oriented-design/domain"
	"github.com/go-playground/validator/v10"
)

type Service interface {
	Create(ctx context.Context, input CreateInput) (CreateResponse, error)
}

type service struct {
	validator  validator.Validate
	repository Repository
}

func NewService(validator validator.Validate, repository Repository) Service {
	return &service{
		validator:  validator,
		repository: repository,
	}
}

func (s *service) Create(ctx context.Context, input CreateInput) (*domain.Order, error) {
	err := s.validator.Struct(input)
	if err != nil {
		return nil, err
	}

order := domain.Order{
		Customer: input.Customer,
		Total:    input.Total,
		Status:   domain.OrderStatus(input.Status),
	}

	order, err := s.repository.Create(ctx, order)
	if err != nil {
		return nil, err
	}

	return order, nil
}

config

The config folder is where the application’s configuration files are located.

├── config
│   └── config.go

Example configuration file:

//config/config.go
package config

import (
	"github.com/kelseyhightower/envconfig"
)

type Config struct {
	MongoURI          string `envconfig:"mongo_uri" default:"mongodb://localhost:27017"`
	MongoDatabaseName string `envconfig:"mongo_database_name" default:"app-example"`
}

func New() (cfg Config, err error) {
	err = envconfig.Process("", &cfg)
	return
}

pkg

The pkg folder is usually used to place internal libraries, wrappers, and code that can be reused in other projects, for example, a logging package, a database connection package, etc.

├── pkg
│   ├── httpclient
│   │   └── client.go
│   ├── mongodb
│   │   └── mongo.go
│   ├── validator
│   │   └── validator.go
│   └── logger
│       └── logger.go

Complete Structure

Here is the complete project structure:

├── cmd
│   ├── api
│   │   └── main.go
│   ├── worker
│   │   └── main.go 
├── domain
│   ├── product.go
│   ├── order.go
├── internal
│   ├── order
│   │   ├── dto.go
│   │   ├── service.go 
│   │   ├── repository.go
│   │   └── handler.go
│   ├── product
│   │   ├── dto.go
│   │   ├── service.go 
│   │   ├── repository.go
│   │   └── handler.go
├── config
│   ├── config.go
├── pkg
│   ├── httpclient
│   │   └── client.go
│   ├── mongodb
│   │   └── mongo.go
│   ├── validator
│   │   └── validator.go
│   ├── logger
│   │   └── logger.go