Hexagonal architecture in front-end
In this article we will discover the hexagonal architecture and understand why it is interesting to use it as a front-end. This article uses a project I created in React & Next.js to provide you with a concrete example. The project is available on Github, the link is at the end of the article. A version of this article with Redux will be available soon.
The hexagonal architecture is based on the concept of dependency inversion. If you are not familiar with this concept, I invite you to read the article I wrote about it before reading this one: see the article on dependency inversion.
What is hexagonal architecture?
It is a code architecture based on the use of ports, adapters and dependency inversion. The goal is to have an application loosely coupled with external dependencies such as a database, an API, a framework or even a library.
To simplify these terms, the goal is to have an interface, i.e. a contract, which defines how an action will send and receive information to external services such as a database, an API, a library or other.
2. The different parts
a. Use case
A use case defines a user action. The objective is not to use libraries or frameworks here because the goal is not to couple the logic to these tools.
In front-end, a use case can be a function or a class. More specifically in a React project, redux can be an exception and considered the use cases. In this case, the actions are the use cases, the state is the model, and the selectors are used to map data between different models.
b. Primary Port
These are the contracts between the use cases and the primary adapters. They can be represented by interfaces. In general, the use case is also considered a primary port, so there is no need to create an interface for it.
c. Primary adapter
The primary adapter is the element that dialogues with the domain. Its role is to trigger use cases. For example in front, adapters can be React components that trigger redux actions or simple domain functions.
d. Secondary port
The secondary port is the interface that defines the contract between the use cases and the secondary adapters. This interface is used by use-cases to call external services implemented by secondary adapters. It is here that we mainly find the principle of dependency inversion.
To gain flexibility, you can also use dependency injection to avoid having to declare your implementation of this contract each time you use use-cases. For this, in front, middlewares like redux-thunk or redux-observable allow to do it via arguments passed to the configuration of your redux store. If you don't use redux, you can use InversifyJS.
e. Secondary adapter
The secondary adapter is the implementation of the secondary port. They are called indirectly by the use-cases. For example, on the front-end, they can be represented by classes or functions that make HTTP requests, access a client-side database, etc.
Why use the hexagonal architecture in front-end?
1. No longer be dependent on libraries and frameworks
Thanks to the hexagonal architecture, the logic of your front-end project is no longer dependent on libraries and frameworks such as React, Vue or Next.js.
This implies that if one of these tools is no longer maintained or no longer suits you, you can change libraries or frameworks without having to redevelop the entire project. It will only be necessary to modify the visual part of the project.
2. Being able to easily and efficiently test a front-end project
Thanks to dependency inversion and the fact that your domain must be independent, you no longer need to render your components, "mock" your HTTP requests, etc.
You only need to use a mock implementation of your external service like API in your tests. Thus, your tests are divided into three main categories:
- unit tests : they are used to test your domain
- integration tests : they test your external implementations
- end-to-end tests : they are useful for testing your entire application at the same time
3. Have the same core for a mobile app and a web app
This point joins the first point explained above. Since your logic is independent of the frameworks and libraries used, you can have the same code for the logic of your applications.
4. Developing the front-end without the back-end
Thanks to the dependency inversion represented by the secondary port, you can develop the front-end of an application without the back-end.
To do this, just proceed in the same way as for the tests using a fake implementation.
This method is useful in case you want to develop a feature to test it with a few users. It is also useful in case the back-end developers have not yet been able to develop the API.
5. Developing the front-end without a network
This point joins the previous point, thanks to the inversion of dependencies you can use a false implementation which simulates the back-end or the access to external services. Thus, you do not need internet access to develop the front-end.
Example of a front-end project with a hexagonal architecture in React & Next.js
At the beginning of this article, I presented you with a diagram to visualize the hexagonal architecture. I propose a new one but with terms related to the front-end.
Now that we have seen what the hexagonal architecture is and what are the advantages of using it, we will see how to use it in a front-end project, in React and Next.js.
For this, I chose a simple and known subject: a todo list. The project is available on Github by clicking on this link: see the project.
In this article, we will focus on a feature of the project: the recovery of the task list. We'll start by developing one of the primary adapters, which is a React component.
1. Creating the primary adapter
In this component, we simply retrieve a list of tasks via a getTodos use case passing it the secondary port implementation TodosOutput. Once the to-do list is retrieved, we store it in our React state using a mapper to ensure that the retrieved object matches the template we are using in our component.
2. Création du use case
The use case used to retrieve the task list is represented by a function. It takes the secondary port TodosOutput as a parameter in order to use it to retrieve the to-do list. As you see, our use case (domain) has no knowledge of how it retrieves the data. It just uses the secondary port, which is an interface, to retrieve that data.
3. Creation of the secondary port
Here we create our secondary port used by the use case. It is a Typescript interface that is used to establish a contract.
4. Creating the secondary adapter
As you can see, the TodosLocalStorage class represents the secondary adapter, which implements the TodosOutput interface, the secondary port. Here we are using localStorage to store and retrieve data, we could also have used HTTP requests to access a client-side API or database.
All the interest of the hexagonal architecture is precisely to have several implementations or to change them according to the services used without needing to modify our core.
5. Example of two unit tests
Here is an example of some unit tests. As I explained at the beginning of this article, unit tests are intended to test the core of the application, the logic.
Thus, our test uses the getTodos use case, this time passing it a different secondary adapter: TodosInMemory. This adapter is another implementation of the TodosOutput secondary port. It is a class that implements the TodosOutput interface.
The tests are pretty simple, they run and get the data returned from the getTodos use case and compare it to some expected fake data.
6. Creating another secondary adapter for unit testing
Here is the secondary adapter used for previous unit tests.
To conclude, the hex architecture is a tool to organize your code in such a way that the logic of your project is independent of external tools.
It is just as important on the front-end in order to be independent of libraries and frameworks which evolve regularly and quickly. It allows you to correctly and easily test your application, in addition to being able to evolve the external services used quickly. You have to see this as plugins that must respect rules to be used by our application.
If this article helped you better understand Hexagon Architecture and how to use it front-end, please share it with your colleagues.
Project link on Github: https://github.com/dimitridumont/clean-architecture-front-end