Context Scope

Architecture Propre : Construire des Systèmes Logiciels Durables

📅 Publié le 20 January 2025 • ⏱️ 5 min de lecture

Architecture Propre : Construire des Systèmes Logiciels Durables

La Clean Architecture, popularisée par Robert "Uncle Bob" Martin, est une approche architecturale qui vise à créer des systèmes logiciels maintenables, testables et indépendants des frameworks. Voici un guide pratique pour une implémentation réussie.

Les principes fondamentaux

1. Indépendance des frameworks

Votre logique métier ne doit dépendre d'aucun framework spécifique. Les frameworks sont des détails d'implémentation, pas le cœur de votre application.

❌ Problème courant : La logique métier est mélangée avec les controllers web (Express, Fastify, etc.). La validation, les règles métier et la gestion HTTP sont entrelacées.

✅ Solution : Extraire la logique métier dans des Use Cases indépendants. Les controllers deviennent de simples adaptateurs qui traduisent les requêtes HTTP en commandes métier.

2. Indépendance de la base de données

Vos use cases ne doivent pas connaître les détails de persistance. La base de données est un détail d'implémentation.

Principe clé : Définir les interfaces de persistance dans la couche domaine, pas dans l'infrastructure. Le Use Case dépend d'une abstraction (UserRepository), l'implémentation concrète (Postgres, MongoDB) est injectée.

L'interface définit les opérations métier (save, findByEmail) sans détails d'implémentation SQL ou NoSQL. Cela permet de changer de base de données, d'ajouter un cache, ou de tester avec des mocks sans toucher à la logique métier.

Structure en couches

Architecture en anneaux concentriques

┌─────────────────────────────────────┐
│           Frameworks & Drivers      │  ← Web, DB, External APIs
├─────────────────────────────────────┤
│         Interface Adapters          │  ← Controllers, Presenters, Gateways
├─────────────────────────────────────┤
│           Application Logic         │  ← Use Cases, Interactors
├─────────────────────────────────────┤
│              Entities               │  ← Business Rules, Domain Models
└─────────────────────────────────────┘

Implémentation pratique

Couche Entities : Les règles métier fondamentales. Validation, invariants, comportements du domaine.

Couche Use Cases : Orchestration des entités et des dépendances. Logique applicative spécifique (créer un utilisateur, passer une commande).

Couche Adapters : Traduction entre le monde extérieur et la logique métier. Les controllers HTTP, repositories, services externes sont ici.

Exemple de flux : HTTP Request → Controller (adapter) → Use Case (orchestration) → Entity (règles métier) → Repository (adapter)

Gestion des dépendances

Dependency Inversion Principle

Règle d'or : Les dépendances pointent toujours vers l'intérieur. Le domaine ne dépend de rien. Les Use Cases dépendent du domaine. L'infrastructure dépend des Use Cases.

Composition Root : Un seul endroit (souvent au démarrage de l'app) où toutes les dépendances sont assemblées. Les Use Cases reçoivent leurs dépendances par injection, jamais par instanciation directe.

Cela permet de remplacer n'importe quelle implémentation (DB, email, cache) sans modifier le code métier.

Testing Strategy

Test de la logique métier

Avantage majeur : Tester la logique métier sans démarrer de serveur, sans base de données, sans services externes.

Chaque Use Case est testé isolément avec des mocks de ses dépendances. Les tests sont :

  • Rapides : Pas d'I/O, exécution en millisecondes
  • Fiables : Pas de flakiness lié au réseau ou à l'état de la DB
  • Focalisés : Chaque test vérifie un comportement métier précis

Les tests d'intégration (avec vraie DB) restent utiles mais en nombre limité. L'essentiel de la couverture vient des tests unitaires des Use Cases.

Patterns d'implémentation

Request/Response Objects

Pourquoi : Découpler la signature du Use Case des détails du transport (HTTP, gRPC, CLI).

Chaque Use Case définit son propre contrat d'entrée/sortie indépendant du framework. Les adapters (controllers) traduisent entre le format externe (JSON HTTP) et le format interne (Request/Response objects).

Avantages :

  • Évolution : Changer l'API REST sans toucher aux Use Cases
  • Multi-transport : Le même Use Case peut être appelé via HTTP, GraphQL, CLI
  • Validation : Centraliser la validation dans les Request objects

Value Objects

Concept : Encapsuler les valeurs primitives avec leurs règles de validation. Un Email n'est pas juste un string, c'est un concept métier avec des contraintes.

Un Value Object expose une factory method statique (Email.create()) qui valide l'entrée. Le constructeur est privé pour forcer la validation.

Bénéfices :

  • Type safety : Impossible de passer un string là où un Email validé est attendu
  • Validation centralisée : Une seule fois, à la création
  • Immutabilité : Les Value Objects ne changent jamais
  • Égalité par valeur : Deux emails avec la même valeur sont égaux

Immutabilite avec TypeScript : readonly vs Object.freeze

❌ ANTI-PATTERN : Object.freeze

N'utilisez JAMAIS Object.freeze() en TypeScript. C'est un contrôle runtime redondant avec le système de types.

Problèmes :

  • Performance dégradée (vérification runtime inutile)
  • Double vérification (TypeScript + runtime)
  • Ne fonctionne pas avec les objets imbriqués
  • Complexité inutile avec un typage fort

✅ BONNE PRATIQUE : Types readonly TypeScript

Déclarez vos types avec readonly : { readonly id: string }. TypeScript garantit l'immutabilité à la compilation.

Avantages :

  • Performance optimale : Zéro overhead runtime
  • Type safety : Vérification à la compilation
  • Productivité : IntelliSense et refactoring assurés

Règle d'or : Utiliser exclusivement readonly TypeScript. Object.freeze est un anti-pattern.

Migration vers Clean Architecture

Approche graduelle

  1. Identifier les boundaries existantes
  2. Extraire la logique métier des controllers
  3. Créer les interfaces pour les dépendances externes
  4. Implémenter les use cases un par un
  5. Tester chaque couche indépendamment

Exemple de refactoring

Avant : Tout dans le controller - validation, logique métier, persistance, services externes sont mélangés. Impossible à tester sans base de données.

Après :

  • Controller : Traduction HTTP ↔ Request/Response objects
  • Use Case : Orchestration de la logique métier
  • Repository : Abstraction de la persistance
  • Entities : Règles métier et validation

Gain immédiat : Le Use Case devient testable en isolation avec des mocks. Le controller devient un simple adaptateur thin qui ne contient aucune logique métier.

Avantages en production

Testabilité

  • Tests unitaires rapides sur la logique métier
  • Mocks simples grâce aux interfaces
  • Isolation des couches pour des tests focalisés

Maintenabilité

  • Changements isolés dans leur couche respective
  • Évolution des frameworks sans impact métier
  • Compréhension facilitée par la séparation des responsabilités

Scalabilité

  • Équipes peuvent travailler sur des couches différentes
  • Déploiement indépendant des composants
  • Performance optimisée par couche

Conclusion

La Clean Architecture n'est pas une solution miracle, mais un ensemble de principes qui, bien appliqués, créent des systèmes robustes et évolutifs. La clé du succès réside dans :

  1. Comprendre les dépendances et leur direction
  2. Isoler la logique métier des détails techniques
  3. Tester chaque couche indépendamment
  4. Évoluer graduellement vers cette architecture

L'investissement initial en complexité est largement compensé par la facilité de maintenance et d'évolution à long terme.

📚 À lire aussi

Bounded Contexts : Guide Complet de Mise en Œuvre

Comment identifier, concevoir et implémenter des bounded contexts efficaces dans des domaines complexes. Patterns stratégiques, techniques de context mapping et exemples concrets.

#strategic #bounded-context #architecture