DEV Community

loading...

Clean Architecture using Golang

eminetto profile image Elton Minetto Updated on ・3 min read

Update

I published an updated version of this post. Check it out at: Clean Architecture, 2 years later

What is Clean Architecture?

In his book “Clean Architecture: A Craftsman’s Guide to Software Structure and Design” famous author Robert “Uncle Bob” Martin presents an architecture with some important points like testability and independence of frameworks, databases and interfaces.

The constraints in the Clean Architecture are :

  • Independent of Frameworks. The architecture does not depend on the existence of some library of feature laden software. This allows you to use such frameworks as tools, rather than having to cram your system into their limited constraints.
  • Testable. The business rules can be tested without the UI, Database, Web Server, or any other external element.
  • Independent of UI. The UI can change easily, without changing the rest of the system. A Web UI could be replaced with a console UI, for example, without changing the business rules.
  • Independent of Database. You can swap out Oracle or SQL Server, for Mongo, BigTable, CouchDB, or something else. Your business rules are not bound to the database.
  • Independent of any external agency. In fact your business rules simply don’t know anything at all about the outside world.

More at https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

So, based on this constraints, every layer must be independent and testable.

From Uncle Bob’s Architecture we can divide our code in 4 layers :

  • Entities: encapsulate enterprise wide business rules. An entity in Go is a set of data structures and functions.
  • Use Cases: the software in this layer contains application specific business rules. It encapsulates and implements all of the use cases of the system.
  • Controller: the software in this layer is a set of adapters that convert data from the format most convenient for the use cases and entities, to the format most convenient for some external agency such as the Database or the Web
  • Framework & Driver: this layer is generally composed of frameworks and tools such as the Database, the Web Framework, etc.

Clean Architecture in Golang

Let’s use as an exemple the package user:

ls -ln pkg/user
-rw-r — r — 1 501 20 5078 Feb 16 09:58 entity.go
-rw-r — r — 1 501 20 3747 Feb 16 10:03 mongodb.go
-rw-r — r — 1 501 20 509 Feb 16 09:59 repository.go
-rw-r — r — 1 501 20 2403 Feb 16 10:30 service.go

In the file entity.go we have our entities:

ca-1

In the file repository.go we have the interface that define a repository, where the entities will be stored. In this case the repository means the Framework & Driver layer in Uncle Bob architecture. His content is:

ca-2

This interface can be implemented in any kind of storage layer, like MongoDB, MySQL, and so on. In our case we implemented using MongoDB, as seen in mongodb.go:

ca-3

The file service.go represents the Use Case layer, as defined by Uncle Bob. In the file we have the interface Service and his implementation. The Service interface is:

ca-4

The last layer, the Controller in our architecture is implemented in the content of api:

cd api ; tree
.
|____handler
| |____company.go
| |____user.go
| |____address.go
| |____skill.go
| |____invite.go
| |____position.go
|____rice-box.go
|____main.go

In the following code, from api/main.go, we can see how to use the services:

ca-5

Now we can easily create tests to our packages, like:

ca-6

Using the Clean Architecture we can change the database from MongoDB to Neo4j, for instance, without breaking the rest of application. And we can grow our software without losing quality and speed.

References

https://hackernoon.com/golang-clean-archithecture-efd6d7c43047
https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
https://github.com/eminetto/clean-architecture-go

Discussion

pic
Editor guide
Collapse
dzintars profile image
Dzintars Klavins

I thought that /api is for protobufs, swagger, etc. files and /pkg is for shared libraries.
Mostly seen that application logic is put into /internal.
What if i have 100 independent services in the same repository, does this example still apply? Will try to implement and see how it goes.
Tnx for the article.

Collapse
chidiwilliams profile image
Chidi Williams

Thanks for your article.

From this implementation, the service only utilizes one Repository. What do I do if I have two different databases (maybe, Mongo as main storage and Elastic for search)? How would I create and use a service that would have getUsers() (Mongo) and searchUser() (Elastic)?

Collapse
eminetto profile image
Elton Minetto Author

Here your service can receive over one Repository. One implemented using Mongo (userRepo) and other using Elastic (userSearch):

userService := user.NewService(userRepo, userSearch, queueService)

I think this could be a valid solution

Collapse
ezhydzetski profile image
Euheni Zhydzetski

By this implementation, User struct from entity.go is a domain/business entity. So it should know nothing about json(API), bson(Persistance) or any other infrastructure.
Such Golang tags make the domain entity dependent from infrastructure. And it is an insidious dependency: linter can't easly detect it on syntax layer (no imports) and should understand semantic of tags information.
So, I recommend to avoid any tags in domain layer. Instead use DTOs and mappings in infrastructure:

  • http.UserJSON, or even http.UserViewJSON, http.UserCreateJSON with json tags
  • mongodb.UserDoc with bson tags
  • http.UserJSON -> domain.User <- mongodb.UserDoc

Thanks for the article. Ready for discussion.

Collapse
eminetto profile image
Elton Minetto Author

Yes! Actually, this is a correct behavior and I’m using something like this in my codes. I was thinking in an update to this post, but your comment cover it :) thanks

Collapse
chanyut profile image
Chanyut Leecharoen

Thank you for article. I decide to apply clean architecture for my existing project. Which all model's struct supports only MongoDB. How would I design model structure to make it independent from database. My first problem is which field ID (primary key)'s type could be? since MySQL use integer, MongoDB use ObjectID.

Collapse
eminetto profile image
Elton Minetto Author

Hi, sorry for the delay to answer...

I wrote a new post about Clean Architecture:

dev.to/eminetto/clean-architecture...

In this post I'm using UUID as ID:

github.com/eminetto/clean-architec...

github.com/eminetto/clean-architec...

I think this is a good approach.

Collapse
noppawitt profile image
Noppawit Thairungroj

What if I have 2 services in different packages that use each other’s service which causes a circular import. How to prevent this problem in this structure?

Collapse
eminetto profile image
Elton Minetto Author

I believe that if two packages are so coupled they should probably be merged into one. You may need to refactor your packages to address this scenario.