How a start-up implemented hexagonal architecture – The case of Packmind
Tags: codequality, architecture, programming, tutorial
Published: 2023-03-27 15:19:00


Our platform [Packmind](https://packmind.com) is developed in a Client-Server model with a Front side developed in TS and React, while our back-end relies on Node.js. To give some context, the code base started 3 years ago, and we’ve always been a small team with max. 4 developers working on the code simultaneously. We currently have 156 KLOC in our back-end and 154 KLOC on the front-end. 

Our **motivations** for implementing concepts from [hexagonal architecture](https://www.happycoders.eu/software-craftsmanship/hexagonal-architecture/) were to support the **maintainability** of our code base, and ease our life when working on it to add new features.  When we discovered concepts from [Domain-Driven Design](https://www.packmind.com/why-read-red-book-domain-driven-design/), Clean & Hexagonal Architecture, we liked the idea of building **separate bounded contexts** and having in our code domains that encapsulate **the business logic**, being free of any third-party framework and technical implementation. 

As we also use **TDD** (Test-Driven Development) in our coding process, we thought that applying ideas from hexagonal architecture and TDD would help to produce consistent hexagons covered by tests. This post **shares how we currently implement a hexagon** in our Node.js back-end. 

*This is how we do it in March 2023. Our practices have evolved for 2 years, and they’re likely to be improved in the future; if it’s the case, we’ll share a digest in a new post ;)* 

## The key concepts

In our context, a hexagon targets a specific domain of our app, for instance, the automatic suggestions or the notifications. We enforce to make it self-sufficient and ensure it can be easily enabled or disabled at runtime. Each hexagon contains **4 distinct modules**:

- **Domain**: Contains the entities and aggregates specific to a domain and the repository interfaces (abstractions) to manipulate these entities.
- **Application**: Contains the services offered by the hexagon. This layer directly manipulates the Domain.
- **Infrastructure**: Implements the domains’ repository with concrete back-ends such as a database, files, in-memory, and more.
- **UI**: Handles how the rest of the world interacts with the application (Rest API, CLI, …).

## Illustration

Let’s take a concrete example with a hexagon handling the **Knowledge Hexagon**, the core concept being the *BestPractices*, used to raise coding standards from our IDE & Code review extensions. We’ll skip irrelevant details for readability. Note that we’ve still included the folder structures if it can help some readers to understand how to organize their code.

### Domain & Infra

First, let’s zoom in on the **Knowledge Hexagon** on the relationship between the **Domain** and the **Infra** layers. In our context, we use a MongoDB implementation:

![Hexagonal Architecture](https://www.promyze.com/wp-content/uploads/Hexagonal-Architecture-1536x1349.png)

The domain is simple and only contains entities and the repositories interfaces. As you can notice, the domain has no link with the Infra layer. We’ve chosen to use a “main” interface *KnowledgeRepositories,* that will expose all the repositories of our domain. In the Infra layer, we use *Converter* components to transform objects **from Infra to Domain**, and inversely. This is optional, and it all depends if you rely on an ORM or not. 

Instantiating the Infra Layer looks like this:

```tsx
const bpMongoRepo = new BestPracticeRepositoryMongo();
const knowledgeRepositories: IKnowledgeRepositories = new KnowledgeRepositories(bpMongoRepo);
const infra: KnowledgeInfra = new KnowledgeInfra(knowledgeRepositories);

// const knowledgeRepositories: IKnowledgeRepositories = new KnowledgeRepositories(new BestPracticeRepositoryinMemory())) would allow to use an in-memory implementation instead
```

### Application

Still in the *Knowledge Hexagon*, Now let’s zoom in on the **Application** layer and how it interacts with the **Domain** layer:

![Hexagonal Architecture - Application](https://www.promyze.com/wp-content/uploads/Hexagonal-Architecture-Application-1536x1046.png)

The application will stand as an API, containing **all the services** the hexagon offers to manipulate the domain, both entities and repositories. As domain repositories in the domain layer are abstractions (e.g., interfaces), they need concrete implementations at runtime. So we can reuse the one instantiated for the Infra layers. 

```tsx
const bpMongoRepo = new BestPracticeRepositoryMongo();
const knowledgeRepositories: IKnowledgeRepositories = new KnowledgeRepositories(bpMongoRepo); 
const knowledgeServices: KnowledgeServices= new KnowledgeServices(knowledgeRepositories); 
```

### UI (Presentation)

Finally, let’s zoom in on the **UI** layer, which helps the rest of the world to interact with our hexagon:

![Hexagonal Architectur - UI](https://www.promyze.com/wp-content/uploads/Hexagonal-Architectur-UIe-1536x1127.webp)

In our context, we have two types of interactions:

- From our **end-users** that interact with the app from their IDE/Code review plugins or their web browsers. The express framework is used in this context.
- From other parts of **our API** (e.g., others hexagons) to avoid re-implementing business logic in a hexagon. We call them *InternalExposedMethods*. As we don’t use micro-services in our context, we considered that using such HTTP calls does not make sense. Instead, using these exposed methods works fine and fills our needs.

## The big picture

Finally, if we all put things together, here is the big picture from the macro-level perspective. Again, we’ve included one concept (*BestPractice*) from the *Knowledge Hexagon*, but we could have added more in this context (such as *CodeExample* listed above in the Infra layer).

![Big Picture.png](https://www.promyze.com/wp-content/uploads/Big-Picture-1166x1536.png)

Here is how the hexagon can be started programmatically:

```tsx
class KnowledgeHexagonApp {
	 constructor() {
       //Infra
       const bpMongoRepo = new BestPracticeRepositoryMongo();
       const knowledgeRepositories: IKnowledgeRepositories = new KnowledgeRepositories(bpMongoRepo); 
       const infra: KnowledgeInfra = new KnowledgeInfra(knowledgeRepositories);

      //Application
      const knowledgeServices: KnowledgeServices= new KnowledgeServices(knowledgeRepositories); 
      
      //UI
      const routers = [new BestPracticeRouter(knowledgeServices), ...];
      const exposedMethods = new InternalKnowledgeExposedMethods(knowledgeServices);
  }
}
```

You can adapt this example to change the infrastructure layer to something different than MongoDB dynamically. If you want to use SQL or in-memory, this could be injected at runtime as a **dependency** of the *KnowledgeHexagonApp* constructor.

## Managing tests

Each hexagon embeds a test suite resulting from the TDD practice. For the record, we use [Mocha](https://www.packmind.com/setup-mocha-in-watch-mode-for-tdd-in-node-js/) and *Chai* to write and run our tests. In our context, our tests layer has two main parts:

1. The first is to test the **Application** Layer and its functions, using *Fake* repositories defined in the Infra folder and using in-memory implementations (basic TypeScript List/Map/…). 
2. The second targets **our concrete MongoDB implementation** of the repositories. We’re not testing the Mongoose framework or the MongoDB infrastructure, but we ensure we have the right behavior when interacting with the repositories. It would be a pity to have a bug in production just because we misuse our ORM, right? We use the [mongo-unit](https://www.npmjs.com/package/mongo-unit) library that runs an embedded in-memory MongoDB server for that purpose. We use hard-coded fixtures in specific files, which mongo-unit will ingest before the **beforeEach** call.

![Hexagonal Architecture - Tests](https://www.promyze.com/wp-content/uploads/Hexagonal-Architecture-Tests-1536x1066.png)

## Events management

It does not appear above, but in our API we’ve implemented a custom event management system. We have two requirements:

- we want to track core events in our system (a best practice has been created) and record them in our database;
- each hexagon should be able to react to specific events (a user has been deleted)

We’ve developed a lightweight system that relies on the [EventEmitter](https://nodejs.dev/en/learn/the-nodejs-event-emitter/) provided by NodeJS. We’ve decided to centralize all our events in a shared module outside hexagons as *payload* objects, such as *BestPracticeCreatedPayload* emitted when a best practice is created. Here is how it’s implemented in the hexagon:

![Hexagonal Architecture - Events](https://www.promyze.com/wp-content/uploads/Hexagonal-Architecture-Events-2048x1182.png)

In the **UI layer**, we can find the *Listeners* that react to events emitted by others hexagons in the API. 

In the **Application** layer, the events will be emitted and handled by other hexagons. 

In the **Infra** layer, we implement the services that will perform the event messaging. Instead of using EventEmitter from Node, we could use other alternatives, such as sending a message to a RabbitMQ channel.

## Other discussions

One important point is that **most hexagons use shared concepts** like *BestPractice* or *Users*. We voluntarily chose to duplicate their definitions in the Domain and Infra layers. One cool thing with Mongoose is that, when writing the mapping model, we can specify only the fields we need in the hexagon. And if you ask, “*what happens when the model evolves ?*”, I’d say this does not happen very often; when it does, only a few hexagons are impacted, so we don’t consider this a main concern for us.

This version currently fills our need, especially because we’re a **small team,** and we want each of us to be able to work on any component of the app. We [ban knowledge silos](https://www.packmind.com/community-of-practice-in-software-engineering-teams/), and our weekly [workshops](https://www.packmind.com/share-best-practices-workshops/) to discuss coding standards are helpful. So we’re all responsible for the code, and no one is in charge of a particular domain. So if we radically change how best practices are designed, we can easily discuss this. Our monolith works fine with a single database instance, and if we need in the future to deploy micro-services, we’ve prepared the ground for that. Plus, as we use **TDD, we’ve built a security net** to prevent possible regressions. 

Finally, our main specificity was to build the *ExposedServices* when it made sense for each hexagon. As we run in monolithic approach, we considered a relevant solution. If, in the future, our system evolves and we need to run micro-services, we’ll need to replace the methods calls with class HTTP calls.  

## Adapt generic concepts in your context

As said earlier, the way we wrote hexagons has evolved and might still evolve. We continuously train our skills on that topic and don’t claim we’re experts in that field (feel free to share your thoughts with us; we’d be happy to take them! :-)).

To keep our coding standards aligned, we use our platform [Packmind](https://packmind.com) to share and discuss our best practices. If you also want to define your tailored coding standards, not only on hexagonal architecture, but also on Security, JavaScript, Performance, or whatever topic that matters to you, Packmind might be a good ally for that. 

In a future post, we’ll focus on how we’ve implemented concepts from the **clean** **architecture in front-end**, still using TDD, to remain independent as much as possible from the underlying framework (React in our context) and the API.