Architecture Hexagonale
Pattern architectural isolant la logique métier des dépendances externes via des ports et adaptateurs pour une maintenabilité optimale.
Mis à jour le 24 février 2026
L'architecture hexagonale, également connue sous le nom de Ports & Adapters, est un pattern architectural créé par Alistair Cockburn qui vise à isoler complètement la logique métier d'une application de ses dépendances techniques externes. Cette approche permet de construire des systèmes hautement testables, évolutifs et indépendants des frameworks, bases de données ou interfaces utilisateur.
Fondements
- Le cœur métier (domaine) ne dépend d'aucune technologie externe et contient uniquement les règles business
- Les ports définissent des contrats d'interface entre le domaine et le monde extérieur (abstraction)
- Les adaptateurs implémentent ces ports pour connecter des technologies concrètes (base de données, API, UI)
- La direction des dépendances pointe toujours vers l'intérieur, vers le domaine (Dependency Inversion Principle)
Avantages
- Testabilité maximale : le domaine peut être testé sans aucune dépendance externe réelle
- Indépendance technologique : changement de framework, base de données ou API sans modifier la logique métier
- Évolutivité facilitée : ajout de nouveaux canaux d'entrée/sortie par simple création d'adaptateurs
- Maintenabilité accrue : séparation claire des responsabilités et réduction du couplage
- Réutilisabilité du code métier : le domaine peut être utilisé dans différents contextes applicatifs
Exemple concret
Prenons l'exemple d'un système de gestion de commandes e-commerce. Le domaine contient la logique de validation et traitement des commandes, tandis que les adaptateurs gèrent les interactions avec PostgreSQL, Stripe et une API REST.
// DOMAINE - Logique métier pure
class Order {
constructor(
public readonly id: string,
public readonly items: OrderItem[],
public status: OrderStatus
) {}
validate(): void {
if (this.items.length === 0) {
throw new DomainError('Order must contain at least one item');
}
}
calculateTotal(): Money {
return this.items.reduce(
(sum, item) => sum.add(item.price.multiply(item.quantity)),
Money.zero()
);
}
}
// PORT - Interface sortante
interface OrderRepository {
save(order: Order): Promise<void>;
findById(id: string): Promise<Order | null>;
}
interface PaymentGateway {
processPayment(amount: Money): Promise<PaymentResult>;
}
// USE CASE - Application service
class PlaceOrderUseCase {
constructor(
private orderRepo: OrderRepository,
private paymentGateway: PaymentGateway
) {}
async execute(command: PlaceOrderCommand): Promise<Order> {
const order = new Order(uuid(), command.items, 'pending');
order.validate();
const paymentResult = await this.paymentGateway.processPayment(
order.calculateTotal()
);
if (paymentResult.isSuccess()) {
order.status = 'confirmed';
await this.orderRepo.save(order);
}
return order;
}
}
// ADAPTATEUR - Implémentation concrète
class PostgresOrderRepository implements OrderRepository {
constructor(private db: DatabaseClient) {}
async save(order: Order): Promise<void> {
await this.db.query(
'INSERT INTO orders (id, items, status) VALUES ($1, $2, $3)',
[order.id, JSON.stringify(order.items), order.status]
);
}
async findById(id: string): Promise<Order | null> {
const row = await this.db.queryOne('SELECT * FROM orders WHERE id = $1', [id]);
return row ? this.mapToDomain(row) : null;
}
}
class StripePaymentGateway implements PaymentGateway {
async processPayment(amount: Money): Promise<PaymentResult> {
const stripe = new Stripe(process.env.STRIPE_KEY);
const charge = await stripe.charges.create({
amount: amount.cents,
currency: amount.currency
});
return PaymentResult.fromStripe(charge);
}
}Mise en œuvre
- Identifier et modéliser le domaine métier avec ses entités, value objects et règles business
- Définir les ports primaires (use cases) exposant les fonctionnalités métier
- Définir les ports secondaires (interfaces) pour les dépendances externes (persistence, messaging, etc.)
- Implémenter les adaptateurs primaires (REST API, GraphQL, CLI) qui appellent les use cases
- Implémenter les adaptateurs secondaires (repositories, gateways) respectant les interfaces du domaine
- Configurer l'injection de dépendances pour câbler adaptateurs et domaine
- Tester le domaine de manière isolée avec des mocks d'adaptateurs
Conseil Pro
Commencez toujours par modéliser votre domaine métier en ignorant complètement les aspects techniques. Posez-vous la question : "Comment fonctionnerait mon métier si je n'avais ni base de données, ni API, ni interface graphique ?" Cette approche force une vraie réflexion sur la valeur métier et évite la contamination technique du domaine.
Outils associés
- NestJS : framework Node.js avec support natif de l'injection de dépendances facilitant l'architecture hexagonale
- TypeDI / InversifyJS : conteneurs d'injection de dépendances pour TypeScript
- Jest / Vitest : frameworks de test essentiels pour tester le domaine isolément
- Class-validator : validation déclarative pour les value objects et entités
- TypeORM / Prisma : ORMs adaptables en adaptateurs de persistence
L'architecture hexagonale représente un investissement initial en structuration qui se rentabilise rapidement sur des projets à moyen/long terme. En isolant la logique métier des détails techniques, elle permet aux équipes de se concentrer sur la valeur business tout en garantissant une base de code évolutive, testable et résiliente face aux changements technologiques. C'est un choix stratégique pour les systèmes critiques nécessitant stabilité et agilité.

