Go × Clean Architecture implementation pattern

Click here for Japanese version

What is Clean Architecture?

It could be thought an architecture pattern that dissociates interest by realizing:

you may refer to various articles for further details, and we will focus on discussing about implementation patterns.

Sample App

nakabonne/cleanarchitecture-sample - GitHub

It just can be used to register user by posting to /users and is based on manuelkiessling/go-cleanarchitecture and using gorm as an ORM.

App Architecture

Generally, it can be divided into any layers, but you should divide according to “Common Closure Principle (CCP)”; it means bunching up the components that could be changed for the common reason. This time, I divided it into four layers according to original text.

Be sure to make dependencies keeping in a single direction from the outside to the inside, here’s the bottom line.

Directory structure

pkg
├── adapter
│   ├── controllers
│   ├── gateway
│   └── interfaces
├── domain
├── external
│   ├── mysql
└── usecase
    └── interfaces

The chart below shows the relationship between the directories and their roles.

Directory Layer
external frameworks & drivers
adapter interface adapters
usecase app business rules
domain enterprise business rules

Be thorough on dependency

I’ve got to walk you through an essential rule before entering the implementation explanation.

As mentioned earlier, dependencies must be kept in a single direction from the outside to the inside. On the contrary, any program has inputs and outputs, and scenes where the results processed inside are output to the outside frequently appear — that is, the scene that you want to depend on from the inside to the outside is typical. Solving the contradiction using the dependency inversion principle will be the key to keeping the clean architecture clean.

What is dependency inversion principle?

Briefly, the principle is that inside should not depend on the outside, but should depend on abstraction. It would be tough to understand, so let’s actually look at the code.

func (i *UserInteractor) Add(u domain.User) (int, error) {
    return i.UserRepository.Store(u)
}

The above represents the second layer from inside, the implementation of the app business rules layer. It can be used to try to store the userinfo into the DB outside. In this scene many people tend to do accessing the outer layers directly, but you’ve got to avoid that in order to make inside not dependent on outside.

The key to solving that is the dependency inversion principle, you can realize it by defining the interface.

type UserRepository interface {
    Store(domain.User) (int, error)
    FindByName(string) ([]domain.User, error)
    FindAll() ([]domain.User, error)
}

The repository interface should be defined in the same layer so that it depends on this interface. And concrete type can be kept dependency from inside to outside by passing from the outside.

This is the dependency inversion principle. You’d better see here for better understanding.

Implementation example of each layer

The implementation of the inner two layers typically can be changed depending on the project; we will focus on the outer two layers.

routing

It was actually implemented with gin for WAF, but it’s easy to replace because this layer uses it as a tool. Also, the fact that concrete type such as DB connection and logger etc are passed to inside in this layer realizes dependency inversion principle.

var Router *gin.Engine

func init() {
    router := gin.Default()

    logger := &Logger{}

    conn := mysql.Connect()

    userController := controllers.NewUserController(conn, logger)

    router.POST("/users", func(c *gin.Context) { userController.Create(c) })

    Router = router
}

ORM

ORMapper is defined in Adapter layer.
In this case, I am devoted to solving the impedance mismatch by converting the DB optimized type to the type optimized for domain logic.
The adapter layer actually depends on abstraction and the interface defined in this layer depends to some extent on the outer library.
In principle, I looked at the following is defined:

‘Abstract’ should not depend on implementation details

The role of this layer is conversion; it would be natural to know both sides to some extent.
Please let me know if you know a better implementation pattern here.

type (
    UserRepository struct {
        Conn *gorm.DB
    }

    User struct {
        gorm.Model
        Name  string `gorm:"size:20;not null"`
        Email string `gorm:"size:100;not null"`
        Age   int    `gorm:"type:smallint"`
    }
)

func (r *UserRepository) Store(u domain.User) (id int, err error) {
    user := &User{
        Name:  u.Name,
        Email: u.Email,
    }

    if err = r.Conn.Create(user).Error; err != nil {
        return
    }

    return int(user.ID), nil
}

Conclusions

As you can see, it is just too large to make REST api just to register userinfo.

You can separate concerns by abstraction, yet would it be worth that much effort to you? You’ve got to think carefully. It would be often that there is no effect if the application size is small since the code becomes complicated basically when it abstracts.

It would be greatly appreciated if you could point out if there is something wrong.