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:
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:
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...
- When adopting Clean Architecture, having someone in the team to act as an architect seems essential.
- 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
- github - manuelkiessling/go-cleanarchitecture
- github - rymccue/golang-standard-lib-rest-api
- github - hirotakan/go-cleanarchitecture-sample
- Recruit Technologies - Go言語とDependency Injection
- Clean ArchitectureでAPI Serverを構築してみる
- github - ponzu-cms/ponzu
- クリーンアーキテクチャの書籍を読んだのでAPIサーバを実装してみた
- Go × Clean Architectureのサンプル実装
- Uncle Bob – Payroll Case Study (A full implementation)