Better Programming

Advice for programmers.

Follow publication

A Hexagonal Approach to Writing Microservices for Scalable and Decentralized Business

How to use Ports and Adapter with TypeScript

Carlos Cunha
Better Programming
Published in
7 min readMar 30, 2022

--

Photo by Sam Balye on Unsplash

Since the beginning of our careers as software engineers, we heard about good practices, good architecture, and the respective manifests that preach them to us all, but you don’t always clearly see how to put that into practice on daily basis. Not criticizing, clean code and clean architecture was the basis for my formation.

Last year I was brainstorming with my squad here at GeekHunter, we were trying to put the house in order regarding the architecture of our main system and some steps we could take to reach what we called a Stateless Core with a Functional Shell.

We are enjoying the fruits that we sow and now we are way closer to a hundred percent Stateless Core.

But that reminded me of a POC project that I did, also last year, to implement the concepts of the Hexagonal or Ports Adapters architecture using TypeScript.

Today I would like to share it with the community, having practiced these concepts I can see how that little project helped me in the past to better see the ground and put the skin on the game when trying architectural improvements.

1 —The Problem

This project was written taking one of the dozens of assessment tests that the dev hunters send to you on Linkedin, in order to enter some random company.

The context here is a telephony company specializing in long-distance calls that wants to release a new plan to the market. It is called “FaleMais”.

The company bill its clients for a minute of a call, depending on the origin of the caller and the destination of the call, as the following table:

Origem Code/ Destination Code/ Price for minute

With the new FaleMais product, the customer purchases a plan and can talk for free up to a certain time (in minutes) and only pays for the extra minutes. Excess minutes are increased by 10% on the normal minute rate. The plans are FaleMais 30 (30 minutes), FaleMais 60 (60 minutes), and FaleMais 120 (120 minutes).

The company, VxTel, concerned with transparency with its customers, wants to provide a web page where the customer can calculate the value of the call. There, the customer can choose the codes of the origin and destination cities, the call time in minutes, and choose the FaleMais plan. The system should show two values: (1) the value of the link with the plan and (2) without the plan. The initial cost of acquiring the plan should be disregarded for this problem.

Here we are looking for the backend only, cause we want to approach with Hexagonal architecture. This is not necessarily a scalable problem, but it can be. If in the real world, lots of calls would be made per second and a good architecture would definitely be needed. Not mentioning the Data Science part here.

I will skip the configuration part of the project here, that is not the focus. It is valid to take note that is written in TypeScript using Docker.

First I want to explain the directories struct that is an essential part of the understanding of hexagonal architecture.

Hexagonal Architecture Directories

First, we have our Core. It’s the stateless core that we mentioned above. Everything inside it dir is meant to be stateless, declarative and implementable code.

In this case, it contains Entities representing what we are dealing with in the domain, Repositories, which are interfaces saying how to obtain/store data and Services doing whatever is needed to achieve the business objective, services also handle the business rules. The core shall never depend on anything outside its directory.

Then we have data sources and use-cases both are part of the functional shell. We can have many shells as we want.

In this case, the data-sources are implementations of our repositories defined in our Core, here you will find a mysql implementation and a postgresql implementation because I decided to store things on separate databases in order to not compromise the availability of the whole system (and as a POC).

Here you could have any data source you want if you work with AI and need to fetch elastic data, implement it here, and so on…

The use-cases hold the APIs as microservices. They use both data sources and Core Services to provide a REST API. We can split this the way we want, we can have different routes in different ports, or we can transform each use-case in a serverless function easily.

2 — The Stateless Core

Going deep into our app Core we can find the structures that define the Entities of the problem:

These two interfaces are enough to map our problem. First, we have the Plans which is an entity with a name, a free minutes time to speak, and an additional percent tax that is applied when the free minutes are exceeded.

Then we have the Taxes that map the cost at the source and destination table presented above.

Going to the Repositories layer inside our core we have interfaces that define how to fetch or store data. They are interfaces because they don’t wanna know how data fetching is implemented.

They both define two methods, one to fetch the data by some identification and another to fetch all data.

Now we can dive into the Services and see how that is connected with the business rule.

We have three services, one for billing, one for listing all taxes and plans, and another for billing taking a plan into the account.

Core Services

Billing is a service that simply does what is described in the problem:
It takes a source and a destination (called A and B), the total minutes of the call, and calculate its billing. It also allows you to pass an additional cost per minute.

Notice that this service uses the repository, so it needs to receive it injected in the constructor.

After this, we have a simple listing service that will be used to fetch all plans and taxes. It also receives repositories as dependency injection.

The last service is the one that calculates the bill given a specific plan.

It was designed to extend the existing Billing service. You can notice that it will always return the method from Billing service, after calculating the additional tax, if needed.

With that our stateless core is ready, and we are now able to use this core services with any database we want and expose it in any REST, Graphql or RPC endpoint that we want.

3 — The Functional Shell (Data Sources)

As mentioned before, was decided to use two databases here, more as a proof of concept.

Data Sources

Both of the implementations have a config dir storing the migrations and QueryBuilders, which could be a common interface if more data sources be needed. The ORM here is Knex. And aside config dir we have the Data Source implementation.

Mysql Data Source

Is important to navigate through the data source config, but that isn’t the focus here. So let’s skip that part as you can browse it from the repository.

The PlansDataSource implements the PlansRepository from the core using some of the configs.

Going to postegres implementation we have the TaxesDataSource, and we also have some configs that will be skipped.

That said we are now one step of having a fully hexagonal architecture working from top to bottom.

4 — The Functional Shell (Use Cases)

Now that everything is set up, and most important, uncoupled, to pass a glue in all that there is nothing better than a use-case as a microservice. Here we have two: Billing and Listing.

Use Cases

At the billing use case, we have a whole implementation with an Express server of a REST API with some validations and a call to a core service.

And in another port, in another service, maybe in another cluster if you want (hello there AWS lambda), we have the listing service.

There’s More

After this entire article, you can visit this GitHub repository to fully understand the implementation and I really hope that this helps you to reach an understanding of clean and hexagonal architecture.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Carlos Cunha
Carlos Cunha

Written by Carlos Cunha

Carlos is Software Engineer since 2018 and works at @GetSafe as Backend Engineer. An enthusiast of astronomy and a musician by passion.

Responses (4)

Write a response