← Retour

Implémenter une architecture — du domaine au code

Une fois les objectifs clarifiés et le domaine métier découpé, comment passer à l'implémentation technique sans créer de dette ni de désordre ?

Poursuivons avec notre exemple de prise de commande e-commerce (Catalogue, Panier, Paiement, Commande) pour aborder les choix de structuration du code, de qualité, de performance et d'exploitation.

Les questions clés pour implémenter l'architecture

❓ Comment éviter un code désordonné et difficile à maintenir ?

Pour éviter que le code technique vienne polluer les règles métier, on applique une architecture hexagonale (ports & adaptateurs). Le cœur de l'application définit des contrats (des interfaces), et ce sont les outils techniques externes qui viennent s'y brancher. Cela permet de changer une base de données ou un outil sans réécrire la logique métier.

🛒 Dans notre exemple : le calcul du montant du panier ne connaît ni PostgreSQL ni l'API bancaire. Il demande simplement à une interface de sauvegarder les données et à une autre de valider le paiement. On peut ainsi tester tout le processus sur sa machine sans vraie base de données.

❓ Comment concevoir des API durables ?

Une bonne API doit rester stable pour que les applications clientes (web, mobile) ne cassent pas à chaque mise à jour. On définit des formats d'échange clairs, on ajoute un numéro de version dans l'URL, et on documente chaque route pour que toute l'équipe comprenne son fonctionnement.

🛒 Dans notre exemple : les points d'entrée POST /v1/panier/items et POST /v1/panier/payer sont fixés dès le départ. Même si le backend change sa manière interne de traiter le paiement en v2, l'application mobile en v1 continue de fonctionner sans interruption.

❓ Comment garantir la qualité du code au quotidien ?

On met en place plusieurs niveaux de vérification automatique. Les tests unitaires vérifient les petits calculs en une fraction de seconde, les tests d'intégration testent les liaisons avec la base de données, et les linters s'assurent que le code respecte un style propre et lisible.

🛒 Dans notre exemple : un test unitaire vérifie en quelques millisecondes que l'ajout d'une quantité négative dans le panier est refusé. Un test d'intégration vérifie ensuite qu'un paiement réussi enregistre bien la commande en base et décrémente le stock disponible.

❓ Comment observer et diagnostiquer l'application en production ?

On ne peut pas corriger ce qu'on ne voit pas. Chaque action de l'utilisateur doit générer des logs clairs et structurés. On attribue un identifiant unique à chaque requête dès son arrivée, ce qui permet de suivre son trajet complet à travers tous les composants.

🛒 Dans notre exemple : quand un client clique sur "Payer", on génère un identifiant unique correlationId. Si le paiement échoue, on recherche cet identifiant pour voir exactement où le blocage a eu lieu (refus bancaire, timeout réseau ou rupture de stock de dernière minute).

❓ Comment optimiser les performances sans complexifier inutilement ?

La règle d'or est de mesurer avant d'optimiser. On identifie les données très souvent consultées mais peu modifiées pour les garder en mémoire rapide (cache), tout en conservant un accès direct à la base pour les opérations critiques en temps réel.

🛒 Dans notre exemple : la liste des fiches produits du catalogue est stockée en cache rapide pour afficher les pages instantanément à des milliers de visiteurs. En revanche, le stock restant au moment du clic "Payer" est lu directement en base pour éviter les réservations en double.

❓ Comment déployer de nouvelles versions sereinement ?

On automatise la chaîne de livraison avec un pipeline CI/CD. Dès qu'un développeur propose une modification, les tests sont exécutés automatiquement. Si tout est vert, la mise en production se fait sans couper le service, avec la possibilité de revenir en arrière instantanément en cas de problème.

🛒 Dans notre exemple : quand on met à jour le module de commande, la nouvelle version est d'abord déployée pour 5 % des utilisateurs. Si le taux de commandes réussies reste normal, le déploiement s'étend à 100 %. En cas d'erreur anormale, le système revient immédiatement à l'ancienne version.

❓ Comment garder une trace des choix techniques importants ?

Les équipes changent et la mémoire s'efface. Pour chaque décision d'architecture majeure, on rédige une courte fiche décisionnelle (ADR) expliquant le problème, les options évaluées et la raison du choix final. Ce document est stocké directement dans le dépôt de code.

🛒 Dans notre exemple : on écrit un document court expliquant pourquoi on a choisi de passer par une file de messages asynchrone pour la génération de la facture au lieu de bloquer l'utilisateur sur la page de paiement pendant que le PDF est généré.

Exemple concret : architecture d'une application e-commerce

Prenons l'exemple d'une application de commande en ligne, conçue selon une architecture hexagonale (ports & adapters) :

Exemple d'architecture applicative hexagonale
<!-- Couche externe : adapters entrants -->
<rect x="10" y="20" width="150" height="60" rx="10" fill="#eef1ff" stroke="#6b5cff"/>
<text x="85" y="45" text-anchor="middle" font-size="13" fill="#2b2140" font-family="sans-serif">API REST</text>
<text x="85" y="62" text-anchor="middle" font-size="11" fill="#6b7280" font-family="sans-serif">(Controller)</text>

<rect x="10" y="280" width="150" height="60" rx="10" fill="#eef1ff" stroke="#6b5cff"/>
<text x="85" y="305" text-anchor="middle" font-size="13" fill="#2b2140" font-family="sans-serif">Worker events</text>
<text x="85" y="322" text-anchor="middle" font-size="11" fill="#6b7280" font-family="sans-serif">(Consumer)</text>

<!-- Domaine au centre -->
<polygon points="320,40 420,100 420,260 320,320 220,260 220,100"
  fill="#6b5cff" opacity="0.12" stroke="#6b5cff" stroke-width="2"/>
<text x="320" y="160" text-anchor="middle" font-size="15" fill="#2b2140" font-family="sans-serif" font-weight="bold">Domaine</text>
<text x="320" y="180" text-anchor="middle" font-size="12" fill="#2b2140" font-family="sans-serif">Commande</text>
<text x="320" y="198" text-anchor="middle" font-size="12" fill="#2b2140" font-family="sans-serif">Panier, Stock</text>
<text x="320" y="216" text-anchor="middle" font-size="12" fill="#2b2140" font-family="sans-serif">Règles métier</text>

<!-- Adapters sortants -->
<rect x="480" y="20" width="150" height="60" rx="10" fill="#fff4e6" stroke="#ffa94d"/>
<text x="555" y="45" text-anchor="middle" font-size="13" fill="#2b2140" font-family="sans-serif">Base de données</text>
<text x="555" y="62" text-anchor="middle" font-size="11" fill="#6b7280" font-family="sans-serif">(Repository)</text>

<rect x="480" y="150" width="150" height="60" rx="10" fill="#fff4e6" stroke="#ffa94d"/>
<text x="555" y="175" text-anchor="middle" font-size="13" fill="#2b2140" font-family="sans-serif">Paiement externe</text>
<text x="555" y="192" text-anchor="middle" font-size="11" fill="#6b7280" font-family="sans-serif">(Adapter)</text>

<rect x="480" y="280" width="150" height="60" rx="10" fill="#fff4e6" stroke="#ffa94d"/>
<text x="555" y="305" text-anchor="middle" font-size="13" fill="#2b2140" font-family="sans-serif">File de messages</text>
<text x="555" y="322" text-anchor="middle" font-size="11" fill="#6b7280" font-family="sans-serif">(Publisher)</text>

<!-- Flèches -->
<line x1="160" y1="50" x2="222" y2="110" stroke="#6b5cff" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="160" y1="310" x2="222" y2="250" stroke="#6b5cff" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="418" y1="110" x2="480" y2="55" stroke="#ffa94d" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="420" y1="180" x2="480" y2="180" stroke="#ffa94d" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="418" y1="250" x2="480" y2="305" stroke="#ffa94d" stroke-width="2" marker-end="url(#arrow)"/>

<defs>
  <marker id="arrow" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto">
    <path d="M0,0 L6,3 L0,6 Z" fill="#6b5cff"/>
  </marker>
</defs>

Le domaine (centre) ne dépend d'aucun détail technique : les adapters entrants (API, worker) et sortants (BDD, paiement, messages) s'y connectent via des ports/interfaces.

Pourquoi ça fonctionne :


Voir aussi :