Implementing Clean Architecture in GO

A step by step guide with a real-world example

Vidhyanshu Jain
8 min readOct 9, 2020
Photo by Kaiyu Wang on Unsplash

Introduction

We always seek to make improvements to our software. It is certain that every few years ( sometimes within months) you will be facing the challenge of updating your software. It is impossible to write perfect software that you never need to change. So instead, we focus on writing software that scales, that can easily be modified and doesn't break when you make changes. To address this problem many programmers came up with code architecture patterns that allow easy modification of code. One such architecture is the “Clean Architecture” — By Robert C Martin popularly know as “Uncle Bob”.

The clean architecture follows the design principle of separation of concerns. In other words, the architecture encourages people to divide the code into layers and each layer is independent of each other. Each layer addresses a separate concern. When code is segmented in such ways, the code can be reused and can be easily replaced or updated without affecting other parts of the code. So suppose you want to move from your SQL Database to a NoSQL database you can do it without affecting the rest of your code.

To keep this post concise and more practical I will not be talking about the particulars of Clean Architecture here. I recommend you to read this Clean Code blog to get a better understanding of the architecture.

To follow along with the code you can check out this Github Repo

The Dependency Rule

Uncle Bob speaks about dividing the code into four layers:

  • Domain: Domain is the business logic that is independent of the application. In other words, the domain is common for the whole organization.
  • Use Case: Use Case is business logic that is particular to the application.
  • Interface: Interface consists of controllers, adapters, presenters. Interfaces allow us to connect our code to Infrastructure.
  • Infrastructure: Infrastructure is the layer where all the detail go. Web, Database, third-party libraries, file systems all belong to the interface layer.

The Dependency rules say that any inner layer should never know anything about the outer layer. That is the code in the inner layer should be independent of the code in the outer layer. And this statement confuses a lot of people, so I add one more line to the Dependency rule — “The inner layer tells the outer layer exactly what it needs and its the responsibility of the outer layer to full fill the inner layers requirement.”

Don’t worry you will understand this better once we walk through the code.

The Book Library Application

We will be writing a Book Library App. With the help of this Library app, we will see how to implement the clean architecture in Go.

This is a bare minimum applicaion to keep the focus on clean architecture concepts. In real life scenarios we will have more values and objects.

File Structure

This is what the file structure of our project looks like

src
├── domain
│ ├── author.go
│ └── book.go
├── infrastructure
│ ├── db
│ │ └── mongodb.go
│ └── router
│ ├── mux-router.go
│ └── router.go
├── interface
│ ├── controllers
│ │ ├── author-controller.go
│ │ ├── book-controller.go
│ │ └── controller.go
│ └── repository
│ ├── author-repository.go
│ ├── book-repository.go
│ └── repository.go
├── main.go
└── usecases
├── author-usecases.go
├── book-usecases.go
└── usecases.go

Domain

Our Library Software will be dealing with two entities — authors & books. In the domain layer, we put the piece of code that is core business logic but is independent of the application. So we will write the author and book entity in our domain layer.

As you can see in the book.go the file we don't have any imports. Because the domain layer is the innermost layer and according to the dependency rule it should be independent of everything. To implement a book entity we have written a struct Bookthat defines all the parameters of a book type. If there are any Business-specific methods with respect to this entity then it should be implemented in this file.

One thing worth pointing out is that we have a BookRepository Interface. A repository is a concept of Domain-driven Design, we are specifying that the Book Entity needs functions that enables the books to be fetched from somewhere or to be saved somewhere. We are not specifying where the data will come from or where it will go to — this is a detail, we don't write details for an outer layer in a lower layer. We are just specifying that we need to have a struct that implements methods to persist the data. Now the implementation of the method, whether its a file or a DB or is it in memory we don't specify here. It is up to the outer layers to write the implementation and inject it in the form of BookRepo.

Similarly, We have a author.go domain file.

Use Cases

Now we can define the application-specific business logic for our Library app. What we want from the app is to allow users to browse all the books present in our Library. We also want the ability to add books and authors to our Library. So in total, we have three use cases:

  • Save Author in our library
  • Save Book in our library
  • Fetch all books from our library

Since this is a small application I didn’t find the need to divide each use-case into different files. So I have divided all the book related use-cases in book-usecase.go and similarly, all the author related use-cases will go to author-usecase.go file. In a bigger application, I would have written each use-case in a separate file.

Let’s study the book-usecase.go file.

Note, how we are strictly following the dependency rule, imports only come from the inner layers.

In this file, we have implemented CreateBook() and FindAll() methods. These methods are essentially our use-cases. We have bound our use-case methods with a struct BookInteractor we have done this to allow for dependency injection. BookInteractor takes in the value of BookRepository. And if you remember BookRepository is an interface with SaveBook() and FindAll() methods. So we are essentially telling the outer layers that if you want to make use of CreateBook() and FindAll() use-cases you have to provide me with a struct of type BookRepository.

Similarly, we have our author-usecase.go file

Interfaces

Now our business logic is ready. Business logic is the part of code that isn't expected to change much hence it forms the inner layers. The beauty of clean architecture is that at this point, you can decide whatever set of infrastructure you want to use. Let’s say you want to create a command-line application and store your data in a file. Then you can implement interfaces for command line and files. Or if you want to write a Java Program to use your application you can also do that.

In our case, we will be writing a Web interface (APIs) and we will be using Database for persistence. Our aim is to write interfaces for our infrastructure. Our interfaces will be independent of our infrastructure. It will be the responsibility of the infrastructure to implement methods of our interface. So let's say we want to change our database we will only have to make changes in our infrastructure layer.

Now Our application requires two interfaces one for APIs and another for databases. You may want to write more interfaces like command-line, logs, etc. But, to keep it simple I will only implement database and API.

Repository

Following the DDD naming convention, I am using the term repository for our database interface. We have a author-repository.go file for author related database methods and book-repository.go for book related methods. We will also create the file repository.go. This file contains the part of code that is common for both author and book.

Inside the repository.go file, We will implement a DBHandler interface. This interface is a template which the database infrastructure will have to implement. In other words, we are telling the database if you need to connect to our application please provide a struct with the following methods FindAllBooks() , SaveBook() and SaveAuthor() .

Let’s take a look inside the book-repository.go file:

Inside our book-repository.go file we will implement our SaveBook and findAll methods which are bounded by the BookRepo struct. This allows for the DBHandler injection which in-turn allows us to use the dbHandler methods.

Note that we have not actually implemented the DBHandler methods yet. We will be implementing these methods in the infrastructure layer where we write our database related methods. And then inject them in our bookRepository.

Similarly, we have our author-repository.go file.

With this, we have finished our Database Interface. Notice How we have finished writing the Datavinterface but we never discussed which database we are going to use. What this means is that till this layer we are truly independent of the database we are going to use.

Controllers

Controllers will be our API interfaces. Controllers will implement all the functions that our endpoints will call to connect to our application. The controller interface allows for the interaction of our application with the API framework that will be implemented in the infrastructure layer.

Let’s take a look in our book-controller.go file

All our API endpoints related to Book are written as functions. ie. methods for our BookController struct. BookController takes in a BookInteractor parameter that allows for the injection of book-usecase methods.

As you can see that the controller methods are fairly simple because the main business logic is handled by book-usecase with the help of bookInteractor.

Similarly, we have author-controller.go file

Infrastructure

We have finally reached our last and the outermost layer. The Infrastructure layer. We have two infrastructure components — Database and Router. Now we can choose the database and router of our choice. I am choosing the MongoDB database and Mux router.

Mongo

The file mongodb.go consists of a DBHandler struct which contains all the code related to the database. One thing we need to make sure is that our database handler should match the DBHandler interface defined in our interface layer. This is the perfect example of the statement — “it is the responsibility of the outer layer to fulfill the requirements of the inner layer”

So our mongodb.go file looks like this.

Our DBHandler struct implements the FindAllBook SaveBook and SaveAuthor methods that let us persist/fetch data from the database.

Router

The file mux-router.go consists of a Router Struct Which implements the GET POST and SERVE methods. These methods allow you to call there respective methods from mux

Putting it together

We can finish up by writing our main.go file which will bind all the layers together and create and serve our application.

The first thing that this code does is connect to the database and create a DBHandler.

Then we have two functions getBookController and getAuthorControlller. Inside these functions, we will see how we have a chain of dependency injection. First, we have injected the DBHandler struct to BookRepo and created an instance of BookRepo. Then we injected that BookRepo into BookInteractor and created a new instance of bookInteractor which was further injected into the bookController to return a controller. And then we can finally call the controller methods with there corresponding endpoints.

And with this, we have written a Clean Architecture implementation in GO.

Conclusion

We have created a very simple code that implements Clean Architecture. This is by no means a complete application a lot of improvements can still be made like adding loggers using dotEnvs etc. But I hope that I was able to provide a proper basic idea behind writing software that is scalable and easily manageable.

Happy Hacking!

--

--

Vidhyanshu Jain
Vidhyanshu Jain

Written by Vidhyanshu Jain

Software Developer at Amazon, System Design Enthusiast

Responses (3)