DEV Community

loading...
Cover image for Dive to Clean Architecture with Golang

Dive to Clean Architecture with Golang

bmf_san profile image Kenta Takeuchi Originally published at bmf-tech.com ・7 min read

This article is the 2th day article of Makuake Advent Calendar 2020.

This content of the article is a translation and correction of Golangでクリーンアーキテクチャに入門する.

Introduction

I usually develop CMS in private, and operate it as my blog.

It's a monolithic application called Rubel, built on Laravel and React.

Rubel has chosen a not-so-rare architecture such as MVC or Flux.

Using familiar architectures and convenient frameworks gave me a pleasant development experience.

However, over the years I've been using it, I have come to feel the difficulty of maintenance because of the degree of dependence on fast-changing frameworks and many libraries.

It's not that the framework and language adopted are bad, but I felt a problem with the design itself.

Then, I found out about Clean Architecture when I was looking for a good idea.

Currently, I am developing an application named Gobel that uses Clean Architecture as a replacement for Rubel.

I would like to write about the outline of Clean Architecture and the impressions I actually used.

What is the Clean Architecture?

History of system architecture

Before the idea of Clean Architecture came out, there were some architectural ideas.

ex.
Hexagonal Architecture(Ports and Adapters)
Onion Architecture
Screaming Architecture
DCI
BCE
etc...

These ideas have in common the purpose of "separation of interests".

  • Independence from the framework
  • Testable
  • Independence from UI
  • Independence from database
  • Independence from other technologies

Breaking dependence on everything and pursuing testability seem to be the central idea of architecture.

Clean Architecture

This is a schematic of the Clean Architecture.

It has been simplified by referring to the figure in blog.cleancoder.com.

image

Clean Architecture consists of software divided into one or more layers.

As each layer has its own responsibilities, we aim to achieve the purpose of separation of interests.

The inner layer is indifferent to the outer layer, and do not directly call what is defined in the outer layer.

Use the interface to resolve the dependency direction from the outer layer to the inner layer.

Specifically, we use a rule called DIP.

Entities

Entities encapsulate the most important business rules.

Since it is the most core business logic, it is the part that is least likely to be influenced by the outside world.

ex. Objects with methods, series of data structures and functions, etc.

Use Cases

Use cases define application-specific business rules.

In other words, it has the logic of system behavior.

Interface Adapters

Interface adapters are layers for transforming data structures used in entities and use cases into easy-to-use formats for Frameworks and Drivers.

Concepts such as Controllers, Gateways and Presenters are included in this layer.

Frameworks and Drivers

The framework and driver are the outermost layers and are the most sensitive to the outside world.

Provide wrappers and interfaces to avoid adverse effects so that external influences do not affect the inner layers.

Includes concepts such as Devices, Web, UI, DB and External Interfaces.

Clean Architecture implementation

I prepared a sample code of Clean Architecture in Go.

go-clean-architecute-web-application-boilerplate

The directory structure is as follows.

./app/
├── database
│   ├── migrations
│   │   └── schema.sql
│   └── seeds
│       └── faker.sql
├── domain
│   ├── post.go
│   └── user.go
├── infrastructure
│   ├── env.go
│   ├── logger.go
│   ├── router.go
│   └── sqlhandler.go
├── interfaces
│   ├── post_controller.go
│   ├── post_repository.go
│   ├── sqlhandler.go
│   ├── user_controller.go
│   └── user_repository.go
├── log
│   ├── access.log
│   └── error.log
├── main.go
└── usecases
    ├── logger.go
    ├── post_interactor.go
    ├── post_repository.go
    ├── user_interactor.go
    └── user_repository.go
Enter fullscreen mode Exit fullscreen mode

The directory structure and each layer are defined as follows:

Layer Directory
Frameworks & Drivers infrastructure
Interface interfaces
Usecases usecases
Entities domain

DIP

DIP (Dependency Reversal Principle) is a concept that needs to be understood around the practice of Clean Architecture.

DIP is one of the SOLID principles, a rule about the constraint between modules that abstraction should not depend on details.

Clean Architecture uses this rule to keep the dependency direction from outside to inside.

By utilizing the interface, DIP is protected and restrictions between layers are also protected.

If you implement it according to the rules of each layer honestly, the dependency direction will be from the inside to the outside.

In such a case, define an interface and make a dependency on abstraction to reverse the direction of dependency.

To practice DIP in Golang, it's best to understand Golang's idea of "Accepts interfaces, return structs".

package examples

// Logger is an interface which will be used for an argument of a function.
type Logger interface {
    Printf(string, ...interface{})
}

// FooController is a struct which will be returned by function.
type FooController struct {
    Logger Logger
}

// NewFooController is a function for an example, "Accept interfaces, return structs".
// Also, this style of a function take on a role of constructor for struct.
func NewFooController(logger Logger) *FooController {
    return &FooController{
        Logger: logger,
    }
}
Enter fullscreen mode Exit fullscreen mode

think it's a basic implementation pattern that you often see in Golang.

By making it interface-dependent in this way, you can write code that is resistant to changes and easy to write tests.

Here is an example of DIP implementation in Golang.

First, the code that is not DIP.

package examples

// sqlHandler is a struct for handling sql.
type sqlHandler struct{}

// Execute is a function for executing sql.
func (sqlHandler *sqlHandler) Execute() {
    // do something...
}

// FooRepository is a struct depending on details.
type FooRepository struct {
    sqlHandler sqlHandler
}

// Find is a method depending on details.
func (ur *FooRepository) Find() {
    // do something
    ur.sqlHandler.Execute()
}
Enter fullscreen mode Exit fullscreen mode

Next is the code according to DIP.

package examples

// SQLHandler is an interface for handling sql.
type SQLHandler interface {
    Execute()
}

// sqlHandler is a struct which will be returned by function.
type sqlHandler struct{}

// NewSQLHandler is a function for an example of DIP.
// This function depend on abstruction(interface).
// This pattern is an idiom of constructor in golang.
// You can do DI(Dependency Injection) by using nested struct.
func NewSQLHandler() SQLHandler {
    // do something ...

    // sqlHandler struct implments SQLHandler interface.
    return &sqlHandler{}
}

// Execute is a function for executing sql.
// A sqlHanlder struct implments a SQLHandler interface by defining Execute().
func (s *sqlHandler) Execute() {
    // do something...
}

// FooRepository is a struct depending on an interface.
type FooRepository struct {
    SQLHandler SQLHandler
}

// Find is a method of FooRepository depending on an interface.
func (ur *FooRepository) Find() {
    // do something
    ur.SQLHandler.Execute()
}
Enter fullscreen mode Exit fullscreen mode

By defining an interface and returning a struct according to the idea of "Accept interfaces, return structs", the dependency will be directed to the interface.

Before

SQLHandler
  ↑
FooRepository
Enter fullscreen mode Exit fullscreen mode

After

SQLHandler
   ↓   
SQLHandler Interface
   ↑
FooRepository
Enter fullscreen mode Exit fullscreen mode

In Clean Architecture, DIP is applied and dependency reversal is performed when the inner layer wants to utilize the rules of the outer layer.

How to read the code

When reading the Clean Architecture code, you need to be aware of the dependencies between layers.

In my opinion, I think it's easier to understand if you read the code from the outside to the inside.

In the example of go-clean-architecute-web-application-boilerplate, it looks like the following.

main.go

router.go・・・Infrastructure
 ↓
user_controller.go・・・Interfaces
 ↓
user_interactor.go・・・Use Cases
 ↓
user_repository.go・・・Use Cases
 ↓
user.go・・・Domain

Impressions

Many files

The number of files is inevitably large because the code is split into multiple layers.

It may not be a good or bad judgment axis in itself, but I think that the bias in the number of files for each layer may trigger a review of layer division.

If you're a "Pragmatic Programmer", I think it's a good idea to have a code generator.

Definition of convention

I felt that it was better to clearly define the responsibilities and coding standards for each layer.

Clean Architecture is just a design policy, not a framework, so I felt that an approach like creating a framework was required.

You may be at a loss if the definition of responsibility is not clarified.

ex. At what layer should validation and transaction logic be defined?

I think that the coding standard is set to some extent in the framework, so I may not think too much about it, but I thought that it is better to prepare the coding standard firmly in order to follow the policy of Clean Architecture.

The necessity of an architect

In team development, I think there are often discussions about design policies.

It's not limited to Clean Architecture, but I strongly felt that it was important for the architect to take responsibility for the final decision-making of the design policy.

Monolith or microservices?

I think it depends on the occasion which one has the advantage.

I don't think Clean Architecture cares whether monoliths are good or microservices are good.

I think one of the relationships between the two is the ease of migration, but it depends on the architect's skill whether Clean Architecture has an advantage in that regard.

References

Discussion (0)

pic
Editor guide