Introduction to Hexagonal Architecture
When building complex backend systems, keeping the core business logic isolated from external concerns (like databases, frameworks, or external APIs) is crucial. This is where Hexagonal Architecture, also known as Ports and Adapters, comes in.
Why Hexagonal?
In a traditional layered architecture, dependencies often flow downwards, leading to the business logic being tightly coupled to the database or UI layer. Hexagonal architecture flips this by ensuring all dependencies point inwards toward the core domain model.
Key Concepts
- Domain: The core business logic and entities.
- Ports: Interfaces that define how the domain communicates with the outside world (both inbound like controllers, and outbound like repositories).
- Adapters: The concrete implementations of these ports (e.g., a PostgreSQL repository, or an Express.js controller).
“Better systems create better opportunities.” - Kapil Sharma
Implementing in Node.js
Using TypeScript makes defining Ports (interfaces) incredibly straightforward. By relying on Dependency Injection, you can easily swap out a MongoDB adapter for a PostgreSQL adapter without ever touching your core domain logic.
// Port
export interface UserRepository {
findById(id: string): Promise<User | null>;
}
// Domain Service
export class UserService {
constructor(private userRepo: UserRepository) {}
async getUser(id: string) {
return this.userRepo.findById(id);
}
}
By decoupling these layers, testing becomes trivial. You can pass a mock repository to the UserService and write lightning-fast unit tests that don’t require spinning up a database.