Architecture propre en JavaScript : maîtriser l'Hexagonal en pratique

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Votre code est une forteresse : pourquoi l'architecture Hexagonale sauvera votre projet JavaScript

\n\n

Avez-vous déjà été confronté à une situation où le changement de base de données ou le remplacement d'une API externe se transformait en enfer de réécriture de la moitié du projet ? Ou peut-être écriviez-vous des tests unitaires qui ressemblaient davantage à des tests d'intégration, parce que la logique métier était étroitement couplée à Express ou MongoDB ? Si oui — bienvenue dans le monde de l'architecture Hexagonale (Ports and Adapters).

\n\n

Ce n'est pas simplement un motif à la mode issu des livres d'Alistair Cockburn. C'est une manière pratique de rendre votre code JavaScript robuste, testable et indépendant des frameworks. Dans cet article, nous ne ferons pas de cours ennuyeux. Nous prendrons un problème réel (un système de gestion de commandes) et le construirons étape par étape selon les principes de l'architecture hexagonale en JavaScript. Vous verrez comment la logique métier reste vierge et pure, tandis que tout le « bruit » (bases de données, requêtes HTTP, files d'attente) reste à l'extérieur.

\n\n

Prêts ? C'est parti !

\n\n

Qu'est-ce que l'architecture Hexagonale (et quel est le rapport avec les ports et adaptateurs) ?

\n\n

Imaginez une passerelle (gateway) de vaisseau spatial. À l'intérieur — un tableau de bord (votre logique métier). À l'extérieur — une infinité de planètes différentes (bases de données, frameworks web, services externes). La passerelle est le port (interface). Et le scaphandre qui permet de respirer sur une planète spécifique est l'adaptateur.

\n\n

L'idée principale : le cœur de l'application (core) ne doit pas savoir qui frappe à sa porte ni comment. Il déclare uniquement des « ports » (interfaces). Et les implémentations concrètes (adaptateurs) sont connectées de l'extérieur. Cela offre trois super-pouvoirs :

\n\n
    \n
  • Isolation de la logique métier — vous changez de base de données sans toucher au code de calcul des remises.
  • \n
  • Testabilité — vous testez la logique sans lancer Docker ni une vraie base de données.
  • \n
  • Indépendance vis-à-vis du framework — vous pouvez passer d'Express à Fastify en écrivant simplement un nouvel adaptateur.
  • \n
\n\n

Passons au code.

\n\n

Configuration du projet et structure des dossiers

\n\n

Pour commencer, créons une structure simple. Nous écrirons en TypeScript pur (ou JavaScript avec JSDoc — selon vos préférences). Notre objectif est un système de gestion de commandes.

\n\n
src/\n├── core/\n│   ├── domain/          # Entités et Value Objects\n│   ├── ports/           # Interfaces (ports)\n│   └── services/        # Logique métier (use cases)\n├── adapters/\n│   ├── primary/         # Adaptateurs qui appellent notre cœur (HTTP, CLI)\n│   └── secondary/       # Adaptateurs que le cœur appelle (BDD, API externes)\n└── app.js               # Point d'entrée (assemblage de tout)\n
\n\n

Étape 1. Le cœur : Entités du domaine et ports

\n\n

Commençons par le plus important — ce qui ne dépend de rien. Créons l'entité Order (commande).

\n\n

core/domain/Order.js

\n\n
export class Order {\n  constructor({ id, userId, items, status = 'pending' }) {\n    this.id = id;\n    this.userId = userId;\n    this.items = items; // Tableau { productId, quantity, price }\n    this.status = status;\n    this.total = this.calculateTotal();\n  }\n\n  calculateTotal() {\n    return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);\n  }\n\n  approve() {\n    if (this.status !== 'pending') {\n      throw new Error('Seules les commandes en attente peuvent être approuvées');\n    }\n    this.status = 'approved';\n  }\n}\n
\n\n

Définissons maintenant les ports — les interfaces que notre cœur attend du monde extérieur.

\n\n

core/ports/OrderRepository.js

\n\n
// C'est un port (interface). Nous ne l'implémentons pas ici.\nexport class OrderRepository {\n  async save(order) { throw new Error('Non implémenté'); }\n  async findById(id) { throw new Error('Non implémenté'); }\n}\n
\n\n

core/ports/EmailService.js

\n\n
export class EmailService {\n  async sendOrderConfirmation(order) { throw new Err                

Blogs

Book Recommendations