Le problème

Une recommandation d'architecture se termine souvent par une liste de bénéfices. Elle explique pourquoi l'option retenue est la bonne, sans dire ce qu'elle coûte. Elle paraît alors partiale, même quand la comparaison a été faite, et le lecteur part chercher lui-même les coûts qui manquent.

Prenons une boutique en ligne découpée en services. La recommandation : publier les événements de commande par une table outbox, écrite dans la même transaction que la commande. Elle règle la perte d'événements, mais elle a un prix que le texte ne mentionne pas.

Ce prix se découvre plus tard, de l'une de ces trois façons.

Le coût arrive comme une surprise. Le relais de l'outbox republie un événement après un arrêt, et le service stock réserve deux fois la marchandise d'une même commande. Le doublon était une propriété connue du choix, et il apparaît comme un incident.

On ne distingue plus un coût accepté d'un oubli. Le service shipping reçoit les commandes avec un léger retard, le temps que le relais passe. Rien ne dit si ce délai a été pesé lors du choix : chaque retard remet alors tout le choix en question.

Rien ne dit quand revenir sur le choix. Aucun seuil n'indique à partir de quel retard l'outbox ne convient plus. Le choix est alors réexaminé trop tôt, sur une impression, ou trop tard, quand le retard gêne déjà les clients de la boutique.

Les options

Pas de section de compromis

  • Ce qu'elle règle : la recommandation reste courte et se lit vite.
  • Ce qu'elle coûte : les coûts se découvrent en production. Le lecteur doit les reconstituer seul, et la recommandation perd son crédit au premier coût découvert.

Une liste générique de risques

La recommandation se termine par une section « Risques » : « complexité accrue », « courbe d'apprentissage », « impact sur les performances ».

  • Ce qu'elle règle : elle montre que des risques ont été envisagés.
  • Ce qu'elle coûte : ces lignes conviennent à n'importe quel choix. Aucune ne se vérifie dans le système, et aucune ne dit quand revenir sur la décision.

Une liste de risques a sa place à l'échelle d'un système entier : arc42 lui consacre une section, où les risques sont classés par priorité. Elle complète les compromis d'une décision, elle ne les remplace pas.

Des compromis écrits, chacun avec son signal de réexamen

  • Ce qu'elle règle : chaque coût est nommé, attribué et borné dans le temps. Un signal dit à quel moment le choix doit être réexaminé.
  • Ce qu'elle coûte : du temps d'analyse et d'écriture. Les signaux mesurables demandent une requête, un tableau de bord ou une alerte.

Notre recommandation

Chaque recommandation écrit ses compromis : ce que l'on perd, qui le porte, jusqu'à quand, ce qui le limite, et le signal de réexamen.

Dans notre méthode, la section « Les compromis assumés » suit la recommandation en une phrase et précède le plan de mise en œuvre. Cette section change la nature du coût. Le doublon reçu par stock n'est plus un incident : c'est un comportement prévu, contenu par un consommateur idempotent.

La section rend aussi la recommandation crédible. Une option sans inconvénient n'existe pas : un texte qui n'en montre aucun laisse penser que la comparaison n'a pas été faite. Les formats reconnus de décision d'architecture le prévoient, sous des noms différents :

  • le format de Michael Nygard termine chaque décision par ses conséquences, positives, négatives et neutres ;
  • le gabarit MADR liste les conséquences en « Good, because » et « Bad, because », et compare les options avec leurs avantages et leurs inconvénients ;
  • le Y-statement d'Olaf Zimmermann tient une décision en une phrase, qui se termine par « accepting that » : le prix accepté ;
  • la méthode ATAM du SEI recherche les points de compromis : là où une décision améliore une qualité du système au détriment d'une autre.

Le signal, enfin, rend la décision révisable. Martin Fowler conseille de noter dans la décision les changements de contexte qui doivent la faire réévaluer. Sans signal, un compromis n'a pas de date de fin.

Chaque compromis porte son signal de réexamen Recommandation Publier les événements par une table outbox , écrite dans la transaction de la commande Doublons possibles Ce que l'on perd la livraison unique de chaque événement Qui le porte stock et shipping , qui doivent être idempotents Jusqu'à quand permanent : base et broker sans transaction commune Latence de publication Ce que l'on perd la publication immédiate de l'événement Qui le porte shipping et l'écran de suivi de commande Jusqu'à quand tant que le relais lit la table par intervalles Une table, un processus Ce que l'on perd une part de la simplicité d'exploitation Qui le porte l'exploitation du service order Jusqu'à quand permanent Signal de réexamen un effet appliqué deux fois, ou un consommateur dont l'effet sort de sa base sans clé d'idempotence Signal de réexamen le délai de publication dépasse le seuil écrit dans la recommandation Signal de réexamen la lecture périodique charge la base Réexamen : confirmer, ajuster ou remplacer la recommandation Une recommandation remplacée reste lisible, avec un renvoi vers la nouvelle

Les articles de cette série appliquent la règle. Notre article sur la topologie RabbitMQ assume une queue qui mélange les événements d'un service. Il nomme aussi le signal qui doit faire découper cette queue : un retard mesuré sur des messages critiques.

Les compromis assumés

Cette pratique a aussi son prix.

Une section de plus à écrire et à lire. La recommandation s'allonge. Nous la limitons aux compromis qui changent le comportement du système ou son coût d'exploitation, pas à tout ce qui pourrait arriver.

La recommandation montre ses faiblesses. Les coûts apparaissent dès la première lecture, bien avant les bénéfices, qui ne se voient qu'en production. Un coût écrit peut faire préférer une autre option : mieux vaut le savoir avant la mise en œuvre qu'après.

Des signaux à outiller. Un signal mesurable demande une requête, un tableau de bord ou une alerte. D'autres se constatent, comme un consommateur incapable de dédupliquer : ils ne servent que si les compromis sont relus à chaque nouveau besoin.

Le signal qui doit faire réexaminer cette pratique : des compromis recopiés d'une recommandation à l'autre, sans lien avec le choix. Ou des signaux que rien ne mesure jamais. La section est alors devenue un rituel : il faut la raccourcir, ou outiller ses signaux.

Mise en œuvre

1. Partir des options écartées

Les coûts de l'option retenue se lisent dans la comparaison avec les autres. Ce qu'une option écartée faisait mieux est souvent ce que l'on perd. Publier juste après le commit envoie l'événement tout de suite : l'outbox perd cette immédiateté.

Pour chaque option écartée, posez une question : qu'apportait-elle que la recommandation n'apporte pas ? Chaque réponse est un compromis candidat. Ne gardez que ceux qui changent le comportement du système ou son exploitation.

2. Nommer ce que l'on perd

Ce que l'on perd est une propriété observable du système : une garantie, un délai, une capacité, une simplicité d'exploitation. « De la complexité » n'en est pas une. « Une table et un processus de plus à surveiller » en est une.

Le test : peut-on constater la perte dans le système ou dans le code ? Si ce n'est pas le cas, la formulation reste trop vague.

3. Dire qui le porte, et jusqu'à quand

Un coût est toujours porté quelque part : un service, une activité (développement, exploitation, support) ou les utilisateurs de la boutique. Nommez ce porteur. Un coût sans porteur désigné n'est traité qu'une fois découvert en production.

« Jusqu'à quand » s'écrit avec une condition plutôt qu'une date : « tant que le relais lit la table par intervalles », ou « jusqu'à la v2 du contrat ». Un coût permanent se déclare comme tel : il ne disparaîtra pas avec le temps.

4. Écrire ce qui limite le coût

Un compromis assumé n'est pas un coût subi : écrivez ce qui le contient. Pour les doublons, l'idempotence des consommateurs ; pour la latence, une alerte sur l'âge de la plus ancienne ligne non publiée. Si rien ne le limite, écrivez-le aussi.

5. Fixer le signal de réexamen

Le signal dit à quel moment le choix doit être réexaminé. Quand c'est possible, il est mesurable : une mesure, sa source, et un seuil écrit dans la recommandation. Le seuil vient du besoin métier, pas d'une valeur par défaut.

Pour la latence de l'outbox, la mesure se lit dans la table elle-même :

-- Délai de publication sur la dernière heure (table outbox du service order)
SELECT
    max(published_at - occurred_at) AS max_delay,
    percentile_cont(0.95) WITHIN GROUP (ORDER BY published_at - occurred_at) AS p95_delay
FROM outbox
WHERE published_at IS NOT NULL
  AND occurred_at > now() - interval '1 hour';

Les lignes non publiées n'y figurent pas : un relais arrêté relève de l'alerte sur la plus ancienne d'entre elles.

Sinon, le signal décrit un fait : un consommateur dont l'effet sort de sa base (e-mail, appel HTTP), ou un besoin régulier de rejeu. Ce type de signal se vérifie à chaque nouveau besoin, en relisant les compromis.

Un signal déclenche un réexamen, pas un changement automatique. Le réexamen se termine par une décision attendue : confirmer le choix, l'ajuster ou le remplacer.

Le gabarit MADR prévoit une section « Confirmation » : comment vérifier que la décision est bien appliquée. Le signal de réexamen pose la question suivante : la décision convient-elle encore ?

6. Un modèle à remplir

Compromis : <titre court>
Ce que l'on perd : <une propriété observable du système>
Qui le porte : <un service, une activité ou les utilisateurs>
Jusqu'à quand : <une condition, ou « permanent »>
Ce qui le limite : <la parade en place, ou « rien »>
Signal de réexamen : <une mesure et son seuil, ou un fait observable>
Où le lire : <la requête, le tableau de bord ou l'alerte>

7. Trois exemples

Les deux premiers viennent d'une recommandation d'outbox pour publier order.order.placed.v1. Le troisième vient d'une API construite avec API Platform, où les ressources sont des DTO séparés des entités Doctrine.

Compromis : doublons possibles
Ce que l'on perd : la livraison unique de chaque événement
Qui le porte : les services consommateurs, stock et shipping
Jusqu'à quand : permanent, base et broker sans transaction commune
Ce qui le limite : une table des événements traités par consommateur
Signal de réexamen : un effet appliqué deux fois, ou un consommateur
  dont l'effet sort de sa base sans clé d'idempotence
Où le lire : une requête qui cherche deux réservations par ligne de commande
Compromis : latence de publication
Ce que l'on perd : la publication immédiate de l'événement
Qui le porte : shipping et l'écran de suivi de commande
Jusqu'à quand : tant que le relais lit la table par intervalles
Ce qui le limite : une alerte sur la plus ancienne ligne non publiée
Signal de réexamen : le délai entre occurred_at et published_at
  dépasse le seuil écrit dans la recommandation
Où le lire : la requête de délai sur la table outbox
Compromis : plus de classes par ressource
Ce que l'on perd : la concision d'une entité exposée directement
Qui le porte : le développement de chaque nouvelle ressource de l'API
Jusqu'à quand : tant que les mappers sont écrits à la main
Ce qui le limite : une structure identique d'une ressource à l'autre
Signal de réexamen : des mappers qui recopient champ à champ des
  ressources presque identiques à leurs entités
Où le lire : le code des mappers, à chaque nouvelle ressource

Le troisième signal ne se mesure pas : il se constate à la lecture du code. Il suffit pourtant à dire quand un mapping automatique redevient une option.

8. Réexaminer sans réécrire

Quand un signal se déclenche, nous écrivons une nouvelle recommandation qui renvoie à l'ancienne. L'ancienne reste lisible, avec le statut « remplacée ». Nygard et Fowler décrivent ce statut : on sait quelle décision a valu, et pendant combien de temps.

Un index des recommandations, avec leur statut, montre celles qui sont en vigueur. Les compromis de chacune y restent consultables, avec leurs signaux.

Checklist

  • Une section « Les compromis assumés » dans chaque recommandation.
  • Chaque option écartée relue : ce qu'elle faisait mieux figure parmi les compromis.
  • Chaque compromis nomme une propriété observable, pas « de la complexité ».
  • Chaque compromis dit qui le porte : un service, une activité ou les utilisateurs.
  • Chaque compromis dit jusqu'à quand, ou se déclare permanent.
  • Ce qui limite chaque coût est écrit, ou son absence est dite.
  • Un signal de réexamen par compromis, avec un seuil écrit quand il est mesurable.
  • Chaque signal mesurable a sa requête, son tableau de bord ou son alerte.
  • Un réexamen produit une nouvelle recommandation, qui remplace l'ancienne sans la réécrire.

Sources