Better Programming

Advice for programmers.

Let’s Build a Movie API With Separated Layered Architecture Using Go

Dilara Görüm
Better Programming
Published in
5 min readApr 17, 2022

--

Photo by Felix Mooneeram on Unsplash

In my first article, we talked about testing with Go. Now, we will move forward to see how we can test our movie API more easily thanks to the three-layer architecture.

The idea of a layered architecture is built on the idea of programming to interfaces. When one module interacts with another though an interface, you can substitute one service provider for another [2].

Don’t worry! I will do my best to explain simply.

Let’s look at this diagram below. Look simple, right?

The logic behind this architecture every single layer has its own responsibility and they will be independent of each other. In that way, we can test them in a more isolable way. Yeap, again testing!

What are the roles of these layers (handler, service, and repository)❓

Handler: It is a layer which gets http request and returns http response to the client.

Service: It is a layer where our business logic is in.

Repository: It is a layer which provides all necessary data from external (DBs) or internal (in-memory) data source. For simplicity, we use in-memory.

We need to talk about Dependency injection before learning the role of interfaces in a diagram. Dependency injection is based on only abstractions (interfaces) not concrete types (structs). In that manner, we inject all our dependencies by using interfaces.

For example, we will have two services in the project: MockService and DefaultService.With the help of the service interface, we can use the methods of these two different structs in the Handler layer. In testing stage, Handler interact with MockService , on the other hand, it interact with DefaultService in production.

Dependency Inversion Example

Don’t worry, you will understand better when we’ll see the action:

Actually, when we call the service method in Handler and the repository method in Service, its reasonable to think of this relations as “call stack”.

This is the simple diagram that we did with the call stack. Our computer allocates memory for our function call.

Let’s imagine this memory as a box.

Our first call(in Handler) needs to be saved in memory.

Then, our second call(in Service) is saved in memory upon the Handler box.

Then, our third call(in Repository) is saved in memory upon Service box. The tricky is here is that our service needs to wait for the repository function to be done. When it is done, our handler needs to wait for the service function to be done. When it is done, the Handler function can do its work.

After it is done too, our stack will be empty. This is how the call stack works. You can read the ‘Grokking Algorithms Book’s Call Stack section to learn more.

This is the broad perspective of how our design is created. We can go deep into our project. We will handle just an example of PATCH request along with this article.

1. main.go

When the client send request with PATCH method, movieHandler’s UpdateMovie method is called using httprouter.

2a. Handler

This is the first layer where our API gets HTTP request.

Handler is the layer which transform response coming from service to http response. As you know, HTTP response is constituted of a status code, header and body.

2b. Handler Testing

Before continuing testing of the handler, I want to introduce mockgen package. This package helps our code to test easily.

Give it the interface source path which you want to mock, show it where the auto-generated mock file will be created and let it generate mock struct implementation on behalf of you. If we want to test the handler layer, we need to mock the service layer. We can do that install it like this:

mockgen -source service/movie_service_interface.go -destination service/mock_movie_service.go -package service

In the test file, we should test possible errors and success cases to increase the test coverage of the handler.

3a. Service

This layer includes our app business logic. By separating business logic into a specific layer, we can write unit tests easily.

3b. Service Testing

Like in the handler testing section, we need to mock our repository to test our service layer:

mockgen -source repository/movie_repository_interface.go -destination repository/mock_movie_repository.go -package repository

I will give you a trick. Whenever we change interfaces that we give to mockgen, we have to run mockgen command again.

Until now, we have two mock files and we need to write these long codes over and over when we add a new method to our interface. Such a tedious way! The solution is Makefile. With the Makefile, we use just generate-mockscommand to recreate our mock files.(make generate-mocks )

We are testing possible errors by mocking the repository.

4. Repository

This is where our data integration is implemented:

Bonus: HTTP Client Plugin

We’re trying to develop RESTful API, and in the developing process, we want to sure it works as expected. To be sure, we need to send HTTP requests to our API.

You can do that both way using curl, Postman, Insomnia, and Jetbrains HTTP Client plugin, etc. I really like to use Jetbrains HTTP Client plugin.

With the HTTP Client plugin, you can create, edit, and execute HTTP requests directly in the IntelliJ IDEA code editor.

Source Code

References

[1,2] Clean Architecture A Craftsman’s Guide to Software Structure and Design (Robert C. Martin)

[3] 97 things every programmer should know collective wisdom from the experts by Kevlin Henney

--

--

Responses (3)