
Hexagonal Architecture
Object Structural Pattern coined by Dr. Alistair Cockburn, in an article he wrote in 2005.
It promotes decoupling from technology and frameworks

One day you get to the office...
And you are told that you need to switch from MySQL to Postgress...

I bet it's going to be hard
Next, they ask you to create a REST API for product catalog
- for XML
- ...and then for JSON
- ...and then for File
- ...and then for CLI
- ...and then for your mother...
Enter: Hexagonal Architecture
Ports and Adapters
Or
Onion Architecture (similar)
Clean Architecture (similar)
Layered Architecture (similar)
Data, Context and Interaction (CDI)
To a degree it's the Architectural extension of the Adapter pattern


...allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code.
A quick review - Layered Architecture
- UI
- Application
- Domain
- Infrastructure
- Domain
- Application
- Communication between layers can only be downwards
- In a strict approach each layer can talk to the next one
- In a relaxed approach, each layer can talk to all the layers directly below it
Hexagonal Architecture
CORE
Outside
Outside


The Adapter Pattern
//target (our payment object interface)
interface IPayment {
id: string;
total: number;
submitPayment: Function;
}
//our payment class
class Payment implements IPayment {
constructor(public id: string, public total: number) {}
public submitPayment() {
console.log(`Proprietary Payment Amount: ${this.total} - ID: ${this.id}`);
}
}
//adaptee (3rd party lib)
interface PayPalPayment {
id: number; //abstract away the type in the adapter
amount: number; //abstract away the field name in the adapter
sendPayment: Function; //abstract away in the adapter
}
class ThirdPartyPayment implements PayPalPayment {
constructor(public id: number, public amount: number) {
this.id = id;
this.amount = amount;
}
public sendPayment() {
console.log(`3rd Party Payment Amount: ${this.amount} - ID: ${this.id}`);
}
}
enum PaymentType {
PayPal,
Proprietary
}
//adapter
class PaymentAdapter implements IPayment {
public id: string;
public total: number;
public type: PaymentType;
constructor(id: string, total: number, type: PaymentType) {
this.type = type;
this.id = id;
this.total = total;
}
public submitPayment() {
if (this.type === PaymentType.PayPal) {
const amount = this.total;
const id = parseInt(this.id);
const payment = new ThirdPartyPayment(id, amount);
payment.sendPayment();
} else if (this.type === PaymentType.Proprietary) {
const id = this.id.toString();
const payment = new Payment(id, this.total);
payment.submitPayment();
} else {
throw new Error('Invalid Payment Type');
}
}
}
//How we actuall use it
const payment: IPayment = new PaymentAdapter(
'123',
47.99,
PaymentType.Proprietary
);
payment.submitPayment();
const payment2: IPayment = new PaymentAdapter(
'543',
99.99,
PaymentType.PayPal
);
payment2.submitPayment();
Understanding the Clean Architecture
Policy vs. Detail
When we're writing code, at any given time, we're either writing Policy or Detail
-
Policy
-
Abstractions, Interfaces, Business logic, declarative
-
When we're specifying what should happen, and when
-
-
Detail
-
Implementations, Infrastructure, Imperative
-
When we specify how the policy happens
-




Text
REST
CLI
MySQL
MongoDB
LogStach
Redis
File System
export interface IEmailService {
sendMail(mail: IMail): Promise<IMailTransmissionResult>;
}https://khalilstemmler.com/articles/enterprise-typescript-nodejs/clean-nodejs-architecture/
class EmailNotificationsService implements IHandle<AccountVerifiedEvent> {
private emailService: IEmailService;
constructor (emailService: IEmailService) {
DomainEvents.register(this.onAccountVerifiedEvent, AccountVerifiedEvent.name)
}
private onAccountVerifiedEvent (event: AccountVerifiedEvent): void {
emailService.sendMail({
to: event.user.email,
from: 'me@khalilstemmler.com',
message: "You're in, my dude"
})
}
}https://khalilstemmler.com/articles/enterprise-typescript-nodejs/clean-nodejs-architecture/
// We can define several valid implementations.
// This infra-layer code relies on the Domain layer
// email service.
class MailchimpEmailService implements IEmailService {
sendMail(mail: IMail): Promise<IMailTransmissionResult> {
// alg
}
}
class SendGridEmailService implements IEmailService {
sendMail(mail: IMail): Promise<IMailTransmissionResult> {
// alg
}
}
class MailgunEmailService implements IEmailService {
sendMail(mail: IMail): Promise<IMailTransmissionResult> {
// alg
}
}import { EmailNotificationsService } from './services/EmailNotificationsService'
import { MailchimpEmailService } from './infra/services/MailchimpEmailService'
import { SendGridEmailService } from './infra/services/SendGridEmailService'
import { MailgunEmailService } from './infra/services/MailgunEmailService'
const mailChimpService = new MailchimpEmailService();
const sendGridService = new SendGridEmailService();
const mailgunService = new MailgunEmailService();
// I can pass in any of these since they are all valid IEmailServices
new EmailNotificationsService(mailChimpService) https://khalilstemmler.com/articles/enterprise-typescript-nodejs/clean-nodejs-architecture/
Benefits
- Maintainability — We build layers that are loosely coupled and independent. It becomes easy to add new features in one layer without affecting other layers.
- Testability — Unit tests are cheap to write and fast to run. We can write tests for each layer. We can mock the dependencies while testing. For example:- We can mock a database dependency by adding an in-memory datastore.
- Adaptability — Our core domain becomes independent of changes in external entities. For eg:- If we plan to use a different database, we don’t need to change the domain. We can introduce the appropriate database adapter.
Benefits
-
Delay technical decisions - We make big decisions when we start a project...how do we know that this is the best option? This pattern can help.
- Don't decide on a DB for example - write your business code against a mock DB, and only when you fully understand the domain, and structure - decide
- Swap technologies - Just write a new adapter to that new tech - Think about the open-close principle - it works!
- It lets you focus on Core
Read more
Hexagonal Architecture
By Eyal Mrejen
Hexagonal Architecture
- 202