Architecture système : des décisions, une équipe et la preuve que le changement a du sens
Un atelier pratique : quand structurer l’architecture, qui impliquer, comment décider, documenter les frontières et reprendre un système existant. Avec carte mentale, code et mini-jeu de décision.
L’article complet est disponible ci-dessous. Activez JavaScript pour les quiz et les simulations.
Un atelier d’architecture sans mise en scène
Une bonne architecture commence quand on cesse de deviner.
Avant le diagramme, le framework ou les microservices, il faut répondre à une question simple et difficile : qu’est-ce qui changera souvent, qu’est-ce qui doit rester stable et qu’est-ce qu’on ne peut pas casser au deuxième déploiement ?
Une bonne architecture permet de faire évoluer le produit sans panique. L’équipe sait où sont les responsabilités. Paiement, formulaire, espace client, e-mail et rapport ne forment pas un bloc que personne n’ose toucher avant le week-end.
Elle fixe les frontières, consigne les choix, réduit le coût des changements et décrit la réaction aux incidents.
Elle ne remplace ni réflexion, ni tests, ni responsabilité, ni échanges métier. La documentation seule n’exploite pas la production.
Lorsqu’une correction touche plusieurs modules sans que personne puisse prévoir ses effets secondaires.
Une définition utile en pratique
L’architecture rassemble les décisions difficiles à inverser.
On peut changer un framework, une couleur ou un endpoint. Modifier le modèle de données, les responsabilités, les paiements, les droits, le pipeline ou l’observabilité est plus difficile.
Une décision coûteuse à inverser mérite une réflexion architecturale. Une décision facilement réversible ne nécessite pas un grand comité. Cette règle protège autant du désordre sans règles que de la bureaucratie sans valeur.
Ce qu’il faut
Cinq éléments sans lesquels l’architecture reste un dessin.
Le manque concerne souvent la responsabilité, pas la technologie. On sait qu’il faut une intégration, mais personne ne précise qui gère les reprises, les doublons, les erreurs, la recette et la clôture.
L’architecture doit donc être courte, lisible et liée au flux réel. Si un client paie un audit, le système doit savoir ce que signifie ce paiement, quand lancer la mesure, comment enregistrer le rapport, notifier et afficher l’échec d’une étape.
Quand une structure légère suffit-elle et quand faut-il formaliser davantage ?
| Situation | Socle suffisant | À ne pas oublier |
|---|---|---|
| Site marketing | Standards de composants, SEO, formulaires, analytique. | Sauvegarde des contenus, sécurité des formulaires, suivi des erreurs. |
| Espace client | Rôles, sessions, journaux et audit des changements clairs. | Autorisations, rétention, réinitialisation du mot de passe, tests E2E. |
| Checkout et paiements | Flux distinct de commande et de réception. | Idempotence du webhook, signatures, remboursement, statut du paiement. |
| Intégrations ERP/API | Contrats, files, reprises, cartographie des données. | Doublons, DLQ, cohérence et versionnement. |
| Système réglementé | ADR, revue sécurité, contrôle d’accès, piste d’audit. | RGPD, sous-traitance des données, rétention, procédure d’incident. |
Personnes et responsabilités
Qui doit porter l’architecture et combien de personnes impliquer ?
Deux modèles fonctionnent mal : une personne impose son architecture comme une vérité, ou douze personnes donnent un avis sans assumer la décision.
La plupart des projets nécessitent un petit groupe : responsable de l’objectif métier, propriétaire technique du backend et des données, personne du frontend ou de l’UX, exploitation et sécurité, plus un spécialiste métier si nécessaire : vente, comptabilité, logistique ou assistance.
Davantage de personnes ne garantit pas une meilleure architecture. Cela peut ralentir les décisions et diluer les responsabilités. Préférez une petite équipe capable de choisir, expliquer et prouver.
Le client paie un audit qui peut durer plusieurs minutes. Que faites-vous ?
Je lance l’audit complet dans le webhook Stripe.
J’enregistre le paiement et l’événement dans l’outbox.
J’affiche « rapport bientôt disponible » sans file.
Choisissez une décision et découvrez sa conséquence.
Exemple de production
Une petite fonction peut coûter cher si les frontières sont mauvaises.
Imaginons : URL, paiement, analyse, résultat enregistré, e-mail et rapport dans l’espace client. Simple sur une diapositive. En production : webhook Stripe reçu deux fois, e-mail échoué, audit lent, actualisation après paiement ou rapport créé mais non enregistré. Que fait le système ?
C’est exactement ici que commence l’architecture : dans le comportement face au monde réel, avant le choix d’une bibliothèque.
Du code qui alerte
Même un code court peut en faire trop.
L’exemple mélange paiement, audit, rapport, e-mail et prestataire au même endroit. Après une panne, il devient difficile de savoir ce qui s’est déjà passé.
public async Task CompleteOrder(Guid orderId)
{
var order = await db.Orders.FindAsync(orderId);
order.Status = "Paid";
var audit = await crawler.Run(order.TargetUrl);
order.ReportJson = JsonSerializer.Serialize(audit);
await email.Send(order.Email, "Raport gotowy", audit.Summary);
await stripe.MarkProcessed(order.PaymentIntentId);
await db.SaveChangesAsync();
}
Une meilleure frontière
La décision d’abord, puis les effets secondaires.
La transaction enregistre un fait et l’intention de poursuivre. Audit, e-mail et intégrations peuvent ensuite avancer séparément avec reprises, supervision et statut clair.
public sealed record ConfirmAuditPayment(
Guid OrderId,
string PaymentIntentId,
DateTimeOffset ConfirmedAt);
public sealed class ConfirmAuditPaymentHandler
{
public async Task Handle(ConfirmAuditPayment command)
{
var order = await orders.Get(command.OrderId);
order.MarkAsPaid(command.PaymentIntentId, command.ConfirmedAt);
outbox.Add(new AuditRequested(order.Id, order.TargetUrl));
outbox.Add(new CustomerNotificationRequested(order.Id));
await unitOfWork.SaveChanges();
}
}
SOLID sans dogme
Les principes doivent aider, pas masquer l’absence de décisions.
Single Responsibility : si changer l’e-mail casse le paiement, les responsabilités sont mêlées. Open/Closed : si chaque prestataire oblige à modifier la moitié du système, il manque un contrat. Dependency Inversion : si le domaine dépend du détail technique, ce détail gouverne le produit.
Commencez simplement par des contrats qui tiennent leurs promesses :
public interface AuditReportStore
{
Task Save(AuditReport report, CancellationToken cancellationToken);
}
public interface AuditNotifier
{
Task NotifyReportReady(Guid orderId, CancellationToken cancellationToken);
}
public sealed class CompleteAuditHandler
{
public async Task Handle(AuditCompleted completed, CancellationToken ct)
{
await reports.Save(completed.Report, ct);
await notifier.NotifyReportReady(completed.OrderId, ct);
}
}
Mini-jeu
Reconstituez le parcours de la décision d’architecture.
Plan de panne et de reprises
Objectif métier
Tests, supervision et rollback
Modèle de données et source de vérité
Frontières du processus
Contrat d’intégration
Remettez les étapes dans l’ordre. Partez de l’objectif, pas de la technologie.
Effacer les réponses
Reprendre un système existant
Avant de refactoriser, comprenez ce qui ne doit pas être cassé.
Reprendre du code n’est ni chercher un coupable ni chasser les classes laides. C’est enquêter sur les flux critiques, données sources, intégrations, décideurs et endroits où un test manquant peut coûter de l’argent au client.
Le test de caractérisation ne dit pas que l’ancien code est beau. Il dit : « Voici le comportement actuel à ne pas modifier par accident. »
[Fact]
public async Task Paid_order_requests_audit_only_once_for_duplicate_webhook()
{
var paymentIntentId = "pi_123";
await app.HandleStripeWebhook(paymentIntentId);
await app.HandleStripeWebhook(paymentIntentId);
var order = await app.FindOrderByPayment(paymentIntentId);
Assert.Equal(OrderStatus.Paid, order.Status);
Assert.Single(await app.Outbox.EventsOfType<AuditRequested>(order.Id));
}
Vous reprenez un module sans tests. Que faites-vous le premier jour ?
Je refactorise immédiatement la classe la plus laide.
J’écris un test de caractérisation du flux critique.
Je lis le README en supposant qu’il est à jour.
Choisissez et découvrez la conséquence pour la maintenance.
L’ADR en pratique
Une décision exige une raison, des conséquences et une preuve.
Nous la consignons pour comprendre dans trois mois pourquoi le système fonctionne ainsi, pas pour produire de la documentation pour elle-même.
ADR-004 : lancer l’audit de manière asynchrone après le paiement
Contexte :
L’audit d’un site peut durer de quelques dizaines de secondes à plusieurs minutes.
Le checkout doit se terminer rapidement.
Décision :
Après le webhook Stripe, nous enregistrons la commande comme payée
et plaçons l’événement AuditRequested dans l’outbox.
Conséquences :
Le tableau de bord affiche le statut "en attente".
Le worker peut relancer l’audit après une erreur.
Il faut surveiller la file et les tâches en échec définitif.
Preuves :
Test d’un webhook en double, test de redémarrage du worker,
log de corrélation orderId.
Lancer le preflight de la décision
Prêt à vérifier. Cliquez pour contrôler la traçabilité de la décision.
Questions de contrôle
Sans réponse, l’architecture n’est pas encore prête.
Conclusion
Une bonne architecture se voit surtout quand quelque chose échoue.
Si le système affiche un statut clair, répète sans danger, conserve les données, alerte, revient en arrière et laisse l’humain décider au bon endroit, l’architecture commence à rentabiliser son coût.
Elle réduit le désordre et permet de livrer sans peur ; c’est cela qui compte, pas la mode.
Commencez par une décision. Une bonne architecture doit se défendre dans un scénario réel, pas seulement sur un diagramme.
Réinitialiser l’atelier