Getting Started with Clean Architecture in Golang

Overview

I tried implementing Clean Architecture in Golang and decided to organize my thoughts here.

The content largely follows the slides I created.

There may be parts I haven't fully understood, along with my interpretations and thoughts, so some parts might not be entirely accurate.

Slides

Since I had the opportunity to give a lightning talk, here are the slides I used:

Dive to clean architecture with golang

Source Code

Here is the source code:

bmf-san/go-clean-architecture-web-application-boilerplate

The implementation using the MVC pattern is also tagged and preserved:

1.0.0 - MVC

Background

I have a CMS application called github - bmf-san/Rubel, which is no longer maintained but is used to run this blog.

To replace this application, I chose Go as the language and decided to adopt Clean Architecture to rethink the architecture.

The reason I considered adopting Clean Architecture is that I believe it is the optimal pattern for an application architecture that can be maintained for a long time without depending on libraries or other technologies.

Rubel uses frameworks like Laravel and React, but since it was implemented heavily relying on these frameworks, I felt it was a waste of time to keep up with the frequent updates of these relatively modern and fast-evolving frameworks.

Ideally, I wanted to focus on adding and improving CMS features. However, for an application I plan to maintain for a long time, spending time on non-essential development seemed unreasonable.

By minimizing dependencies on frameworks, libraries, and other technologies, and leveraging Go's standard library as much as possible, I thought I could create a highly maintainable application.

While I have a mindset that "scratch development is king," this application is not one that requires immediate adaptation to business requirements like service development. Since the development also includes a learning aspect, I think this approach is somewhat reasonable.

Although I feel I am taking a strategy close to optimal for personal development, I understand there will be aspects I won't see until the operational phase.

The replacement for Rubel, currently under development, is here:

github - bmf-san/Gobel

Table of Contents

  • What is Clean Architecture?
  • Implementing Clean Architecture (implementation details are not deeply covered)
  • Reflections

What is Clean Architecture?

History of System Architectures

Before the idea of Clean Architecture emerged, several architectural ideas existed:

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

These ideas share the common goal of "separation of concerns," aiming for:

  • Independence from frameworks
  • Testability
  • Independence from UI
  • Independence from databases
  • Independence from other technologies

In essence, they pursue decoupling from dependencies and enhancing testability.

Clean Architecture

The famous diagram often associated with Clean Architecture originates from this source:

cleancoder.com - The Clean Architecture

Here is an explanation of each layer:

Entities

  • Entities encapsulate the most important business rules.
    • e.g., Objects with methods or a set of data structures and functions.

Use Cases

  • Use cases contain specific business rules of the application.

Interface Adapters

  • Interface adapters transform data for entities and use cases.

Frameworks and Drivers

  • Frameworks and drivers consist of tools like frameworks and databases.

Rules Between Layers

The constraints between the layers are as follows:

  • There are four layers, but you can add or remove layers as needed.
  • Inner layers do not know about outer layers.
    • → Dependencies should flow from outer to inner layers.

Implementing Clean Architecture

Directory Structure

The source code introduced earlier is repeated here:

bmf-san/go-clean-architecture-web-application-boilerplate

The correspondence between layers and directories is as follows:

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

DIP

Before implementing Clean Architecture, you need to understand the Dependency Inversion Principle (DIP).

DIP is one of the SOLID principles and is a rule about module dependencies, stating that abstractions should not depend on details.

While I won't go into detail about this rule, in the context of Clean Architecture, this rule is followed by using interfaces to maintain dependency direction from outer to inner layers, adhering to the constraints between layers.

When implementing each layer's rules straightforwardly, situations may arise where dependencies point from inner to outer layers. In such cases, defining interfaces and depending on abstractions ensures the dependency direction is maintained. This is a key aspect of implementation.

Accept Interfaces, Return Structs

Golang has a concept of "accept interfaces, return structs," which aligns well with implementing DIP.

This is a common implementation pattern in Golang. By depending on interfaces, you can write code that is resilient to changes and easier to test.

DIP in Golang

Here is an example of DIP in Golang.

Code Without DIP

Code Considering DIP

By introducing an interface, the dependency relationship changes, effectively reversing the dependency direction.

Before:

After:

In the Clean Architecture example, the code in infrastructure and interfaces corresponds to this. bmf-san/go-clean-architecture-web-application-boilerplate

Code Reading

When tackling Clean Architecture, I found it easier to start with code reading or copying rather than jumping straight into implementation.

When reading code, I personally found it helpful to read from the outer layers inward:

main.go ↓ router.go (Infrastructure)  ↓ user_controller.go (Interfaces)  ↓ user_interactor.go (Use Cases)  ↓ user_repository.go (Use Cases)  ↓ user.go (Domain)

Reflections

  • Since I am relatively new to Golang, I had to repeatedly revisit language features like interfaces and structs.
  • When faced with questions like "Where should I write this?", I felt it would be beneficial to have an architect lead and make decisions.
    • When adopting Clean Architecture, having someone in the team to act as an architect seems essential.
      • Though this might not be limited to Clean Architecture...
  • I felt that Clean Architecture is more of a mindset than an implementation pattern, and I realized the need to study various architectural patterns more broadly.
  • It seems to be more suited for monolithic applications.
    • For microservices, simpler and more disposable architectural patterns with lower learning costs might be preferred.
  • A framework is just a tool, not a way of life.
    • A quote from the original text of "Clean Architecture: A Craftsman's Guide to Software Structure and Design."
    • What a great phrase.

References