L'architecture hexagonale expliquée : pourquoi vos APIs deviennent ingérables
Après 10 ans à reprendre des projets tech, je vois toujours les mêmes erreurs d'architecture qui transforment des APIs simples en cauchemars de maintenance. L'architecture hexagonale n'est pas une mode : c'est la différence entre un code qui évolue et un code qui vous fait démissionner.
L'architecture hexagonale expliquée : pourquoi vos APIs deviennent ingérables
Vous connaissez cette sensation ? Vous devez ajouter une fonctionnalité simple à votre API. Vous ouvrez le code. Vous réalisez qu'il faut modifier 12 fichiers, tester 3 bases de données différentes et prier pour que rien ne casse en production.
J'ai repris des dizaines de projets dans cet état. Le problème n'est jamais la complexité métier. C'est l'architecture. Plus précisément : l'absence d'architecture.
L'architecture hexagonale résout ce problème en séparant clairement ce qui change souvent (les détails techniques) de ce qui change rarement (la logique métier). Voici pourquoi elle transforme vos APIs de cauchemar en plaisir de développement.
Le problème des architectures en couches traditionnelles
La plupart des développeurs organisent leur code en couches : contrôleur → service → repository → base de données. Ça semble logique. En réalité, c'est un piège.
Prenons un exemple concret. Vous développez une API de facturation. Votre contrôleur appelle un service qui appelle un repository qui interroge PostgreSQL. Tout va bien.
Puis le client demande d'intégrer Stripe pour les paiements. Votre service doit maintenant gérer les webhooks, les erreurs réseau, les timeouts. Votre logique métier se mélange avec les détails d'implémentation de Stripe.
Ensuite, on vous demande d'ajouter PayPal. Puis une synchronisation avec Salesforce. À chaque nouvelle intégration, votre service grossit. Il devient responsable de tout : validation métier, transformation de données, gestion d'erreurs, retry logic.
Résultat ? Un fichier de 800 lignes où personne n'ose plus toucher à rien.
Les principes de l'architecture hexagonale
L'architecture hexagonale inverse la logique. Au lieu de partir des détails techniques, elle part du métier.
Le cœur métier au centre
Votre logique métier ne connaît rien des détails d'implémentation. Une facture reste une facture, qu'elle soit stockée en PostgreSQL, MongoDB ou dans un fichier JSON.
// Domain - Logique métier pure
class Invoice {
constructor(
private customerId: string,
private amount: number,
private items: InvoiceItem[]
) {}
calculateTotal(): number {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
markAsPaid(): void {
if (this.status === 'cancelled') {
throw new Error('Cannot pay cancelled invoice');
}
this.status = 'paid';
}
}
Cette classe ne sait pas comment elle sera persistée. Elle ne connaît pas Stripe. Elle fait une chose : modéliser le comportement d'une facture.
Les ports : contrats d'interface
Les ports définissent ce dont votre métier a besoin, sans spécifier comment.
// Port - Interface métier
interface PaymentProvider {
processPayment(amount: number, customerId: string): Promise<PaymentResult>;
refund(paymentId: string): Promise<RefundResult>;
}
interface InvoiceRepository {
save(invoice: Invoice): Promise<void>;
findById(id: string): Promise<Invoice | null>;
}
Votre logique métier dépend de ces abstractions. Pas des implémentations.
Les adaptateurs : implémentations spécifiques
Les adaptateurs implémentent les ports pour des technologies spécifiques.
// Adapter - Implémentation Stripe
class StripePaymentProvider implements PaymentProvider {
async processPayment(amount: number, customerId: string): Promise<PaymentResult> {
const stripe = new Stripe(process.env.STRIPE_KEY);
const payment = await stripe.paymentIntents.create({
amount: amount * 100, // Stripe en centimes
currency: 'eur',
customer: customerId,
});
return {
id: payment.id,
status: payment.status === 'succeeded' ? 'success' : 'failed'
};
}
}
L'adaptateur gère tous les détails spécifiques à Stripe : conversion en centimes, gestion des erreurs, configuration API.
Comment implémenter l'architecture hexagonale en pratique
Structure des dossiers
src/
├── domain/ # Logique métier pure
│ ├── entities/ # Modèles métier
│ ├── services/ # Services métier
│ └── ports/ # Interfaces
├── adapters/ # Implémentations
│ ├── persistence/ # Base de données
│ ├── external/ # APIs externes
│ └── web/ # Contrôleurs HTTP
└── infrastructure/ # Configuration, DI
Injection de dépendances
Votre logique métier reçoit ses dépendances par injection :
class InvoiceService {
constructor(
private invoiceRepo: InvoiceRepository,
private paymentProvider: PaymentProvider,
private notificationService: NotificationService
) {}
async processPayment(invoiceId: string): Promise<void> {
const invoice = await this.invoiceRepo.findById(invoiceId);
if (!invoice) throw new Error('Invoice not found');
const result = await this.paymentProvider.processPayment(
invoice.amount,
invoice.customerId
);
if (result.status === 'success') {
invoice.markAsPaid();
await this.invoiceRepo.save(invoice);
await this.notificationService.sendPaymentConfirmation(invoice);
}
}
}
Ce service ne sait pas s'il utilise Stripe ou PayPal. Il ne sait pas si les données sont en PostgreSQL ou DynamoDB. Il fait son travail : orchestrer le processus de paiement.
Configuration au démarrage
L'assemblage se fait au niveau infrastructure :
// Infrastructure - Assemblage des dépendances
const invoiceRepo = new PostgresInvoiceRepository(database);
const paymentProvider = new StripePaymentProvider();
const notificationService = new EmailNotificationService();
const invoiceService = new InvoiceService(
invoiceRepo,
paymentProvider,
notificationService
);
const invoiceController = new InvoiceController(invoiceService);
Les bénéfices concrets que j'observe
Tests simples et rapides
Vos tests métier n'ont plus besoin de base de données ni d'APIs externes :
describe('InvoiceService', () => {
it('should mark invoice as paid after successful payment', async () => {
const mockRepo = new MockInvoiceRepository();
const mockPayment = new MockPaymentProvider();
const service = new InvoiceService(mockRepo, mockPayment, mockNotification);
await service.processPayment('invoice-123');
const invoice = await mockRepo.findById('invoice-123');
expect(invoice.status).toBe('paid');
});
});
Ces tests s'exécutent en millisecondes. Pas de setup complexe, pas de nettoyage de données.
Évolution sans casse
Vous voulez passer de Stripe à PayPal ? Vous implémentez PayPalPaymentProvider et vous changez une ligne dans la configuration. Votre logique métier reste intacte.
Vous devez supporter PostgreSQL et MongoDB ? Même principe. Deux adaptateurs, une seule logique métier.
Code plus lisible
Chaque couche a une responsabilité claire :
- Le domaine modélise le métier
- Les adaptateurs gèrent les détails techniques
- L'infrastructure assemble le tout
Fini les services de 800 lignes qui font tout et n'importe quoi.
Quand ne pas utiliser l'architecture hexagonale
L'architecture hexagonale n'est pas magique. Elle ajoute de la complexité initiale.
Je ne la recommande pas pour :
- Les prototypes rapides
- Les applications avec une seule intégration stable
- Les équipes qui découvrent encore les bases de l'architecture
Par contre, dès que vous avez plusieurs intégrations externes ou que votre code dépasse 10 000 lignes, elle devient indispensable.
Ma méthode d'implémentation progressive
Je n'impose jamais l'architecture hexagonale dès le début d'un projet. J'y migre progressivement :
- Étape 1 : Extraire la logique métier des contrôleurs
- Étape 2 : Créer les premières interfaces (ports)
- Étape 3 : Déplacer les appels externes dans des adaptateurs
- Étape 4 : Configurer l'injection de dépendances
Cette approche évite le big bang architectural qui paralyse les équipes.
L'architecture hexagonale avec l'IA
L'IA excelle à générer des adaptateurs une fois que vous avez défini vos ports. Je demande souvent à Claude ou ChatGPT :
"Implémente un adaptateur Redis pour cette interface CacheRepository"
L'IA génère du code parfaitement intégré à votre architecture. Elle respecte les contrats définis par vos ports.
Par contre, elle est moins efficace pour concevoir l'architecture elle-même. La définition des ports et des boundaries métier reste un travail d'expert.
L'architecture hexagonale transforme vos APIs de dépendances techniques en logique métier claire et testable. Elle demande un investissement initial, mais elle vous fait gagner des mois de développement sur le long terme.
Si vous gérez une équipe technique ou que vous reprenez un projet legacy, c'est probablement l'architecture qui vous manque. Vos futurs développeurs vous remercieront.
Besoin d'aide pour restructurer votre architecture ? Contactez-moi sur phdr.dev pour un audit technique de votre codebase.