Anti-patterns en architecture logicielle

Erreurs courantes en architecture logicielle distribuée : comment les identifier, leur impact réel et les stratégies pour les corriger.

Que sont les anti-patterns

Un anti-pattern est une solution récurrente qui paraît raisonnable au premier abord mais qui engendre des problèmes importants à moyen et long terme. Contrairement à une simple erreur, les anti-patterns sont des pièges courants dans lesquels tombent des équipes expérimentées, souvent à cause de la pression du temps, d’un manque de contexte ou de décisions qui étaient correctes à un moment donné mais qui ont cessé de l’être.

Reconnaître ces anti-patterns à temps vous permet d’éviter une dette technique coûteuse et de prendre des décisions architecturales mieux éclairées.

Pourquoi les anti-patterns apparaissent

Les anti-patterns n’apparaissent pas par incompétence. Ils surgissent pour des raisons compréhensibles :

CauseExemple
Pression du temps« Ajoutons cette fonctionnalité au service existant, il n’y a pas le temps d’en créer un nouveau »
Manque de contexte« Tous les services peuvent lire dans cette base de données, c’est plus simple »
Décisions qui vieillissent« Quand nous étions 3 développeurs, ça fonctionnait bien ; maintenant nous sommes 30 »
Optimisation prématurée« Séparons tout en microservices dès le premier jour »
Inertie organisationnelle« On a toujours fait comme ça »

1. Monolithe distribué (Distributed Monolith)

Problème

Il se produit lorsqu’un système est divisé en plusieurs services qui dépendent si fortement les uns des autres qu’ils ne peuvent être déployés, mis à l’échelle ni évoluer de façon indépendante. En pratique, vous avez la complexité opérationnelle des microservices sans aucun de leurs avantages.

┌──────────┐     ┌──────────┐     ┌──────────┐
│Servicio A│────►│Servicio B│────►│Servicio C│
│          │◄────│          │◄────│          │
└──────────┘     └──────────┘     └──────────┘
     │                │                │
     └────────────────┴────────────────┘
        Despliegue conjunto obligatorio
        Modelos de datos compartidos
        Llamadas síncronas en cadena

Les signes les plus évidents sont :

  • Vous devez déployer plusieurs services en même temps pour qu’un changement fonctionne
  • Un changement dans le contrat d’un service casse les autres
  • Les services partagent des bibliothèques internes contenant de la logique métier
  • Les tests d’intégration nécessitent de démarrer tout le système

Impact

  • Déploiements fragiles : Un changement dans un service oblige à coordonner le déploiement des autres, éliminant l’agilité qui avait motivé la séparation
  • Défaillances en cascade : La dépendance synchrone entre services fait que la panne de l’un entraîne les autres
  • Complexité sans bénéfice : L’équipe paie le coût opérationnel de plusieurs services sans gagner d’indépendance réelle
  • Vitesse de développement réduite : Les équipes ne peuvent pas travailler en parallèle car les changements se bloquent mutuellement

Comment le détecter

  • Posez la question : puis-je déployer ce service sans toucher à aucun autre ? Si la réponse est « non » fréquemment, vous avez un monolithe distribué
  • Vérifiez si les services partagent des schémas de base de données ou des bibliothèques contenant de la logique de domaine
  • Observez si les mises en production exigent une coordination entre plusieurs équipes pour un seul changement fonctionnel
  • Mesurez le coupling score : comptez combien de services sont déployés ensemble à chaque mise en production

Correction

StratégieDescription
Définir des contrats clairsUtiliser des API versionnées avec des contrats explicites. Chaque service expose une interface stable et évolue en interne sans casser ses consommateurs
Communication asynchroneRemplacer les appels synchrones en chaîne par des événements. Publier un événement au lieu de faire un appel direct
Bounded Contexts (DDD)Appliquer le DDD pour identifier les limites réelles du domaine. Chaque service doit être propriétaire de son contexte
Évaluer une re-consolidationSi les services sont si couplés qu’ils ne peuvent être séparés, envisagez de les réunir à nouveau. Un monolithe bien conçu vaut mieux qu’un monolithe distribué
Anti-Corruption LayerUtiliser un ACL entre les services pour traduire les modèles et réduire le couplage direct

2. Base de données partagée (Shared Database)

Problème

Plusieurs services lisent et écrivent directement dans la même base de données, en accédant aux mêmes tables. Cela crée un couplage invisible : les services ne dépendent pas d’API explicites mais de la structure interne des tables.

┌──────────┐   ┌──────────┐   ┌──────────┐
│Servicio A│   │Servicio B│   │Servicio C│
└────┬─────┘   └────┬─────┘   └────┬─────┘
     │              │              │
     ▼              ▼              ▼
┌──────────────────────────────────────┐
│         Base de Datos Compartida     │
│                                      │
│  ┌─────────┐ ┌─────────┐ ┌────────┐ │
│  │ Tabla X │ │ Tabla Y │ │ Tabla Z│ │
│  └─────────┘ └─────────┘ └────────┘ │
│                                      │
│  Todos leen y escriben en todas      │
└──────────────────────────────────────┘

Les signes les plus évidents sont :

  • Plusieurs services font des requêtes directes sur les mêmes tables
  • Un changement de schéma oblige à modifier plusieurs services
  • Il n’y a pas de « propriétaire » clair pour chaque table ou ensemble de données
  • Les migrations de base de données sont des événements à haut risque

Impact

  • Couplage caché : Les services semblent indépendants mais sont liés par la structure de la base de données. Un ALTER TABLE peut casser des services dont vous ignoriez même qu’ils utilisaient cette table
  • Scalabilité limitée : Vous ne pouvez pas mettre à l’échelle la base de données de façon indépendante par service
  • Migrations dangereuses : Tout changement de schéma est un risque car vous ne savez pas qui dépend de quoi
  • Impossibilité d’évoluer : Chaque service reste lié à la technologie et à la structure de la base de données partagée

Comment le détecter

  • Vérifiez si plus d’un service possède des connection strings vers la même base de données
  • Recherchez les requêtes qui accèdent à des tables « appartenant » à un autre domaine fonctionnel
  • Posez la question : qui est le propriétaire de cette table ? Si la réponse est « plusieurs services », vous avez cet anti-pattern
  • Exécutez une analyse de dépendances : cartographiez quels services accèdent à quelles tables

Correction

StratégieDescription
Database per ServiceChaque service possède sa propre base de données. Seul le service propriétaire peut lire et écrire ses données
Exposer les données via APISi le Service B a besoin de données du Service A, il les demande via l’API de A
Événements pour la synchronisationUtiliser des événements de domaine pour que les services conservent des copies locales des données dont ils ont besoin
Outbox PatternPublier les événements de manière fiable lorsque les données changent, afin que les autres services se synchronisent
Migration incrémentaleN’essayez pas de tout séparer d’un coup. Identifiez les tables les plus problématiques et migrez service par service

3. God Service (Service Dieu)

Problème

Un seul service concentre trop de responsabilités et devient le point central par lequel passe presque toute la logique du système. Il commence généralement comme un service légitime qui accumule des fonctionnalités parce que « c’est plus facile de l’ajouter ici ».

                    ┌─────────────────────┐
                    │    God Service       │
                    │                     │
                    │  • Autenticación    │
                    │  • Usuarios         │
                    │  • Pedidos          │
                    │  • Pagos            │
                    │  • Notificaciones   │
                    │  • Reportes         │
                    │  • Inventario       │
                    └──────────┬──────────┘

              ┌────────────────┼────────────────┐
              ▼                ▼                ▼
        ┌──────────┐   ┌──────────┐    ┌──────────┐
        │Servicio X│   │Servicio Y│    │Servicio Z│
        │(simple)  │   │(simple)  │    │(simple)  │
        └──────────┘   └──────────┘    └──────────┘

        Todos dependen del God Service

Les signes les plus évidents sont :

  • Un service possède nettement plus d’endpoints que les autres
  • La plupart des changements fonctionnels nécessitent de modifier ce service
  • Le service assume plusieurs responsabilités sans lien entre elles
  • L’équipe qui le maintient constitue un goulot d’étranglement permanent

Impact

  • Goulot d’étranglement du développement : Toutes les équipes doivent modifier le même service
  • Point unique de défaillance : Si le God Service tombe, une grande partie du système cesse de fonctionner
  • Scalabilité inégale : Vous ne pouvez pas mettre à l’échelle uniquement les paiements s’ils sont empaquetés avec les rapports
  • Complexité croissante : Le service devient difficile à comprendre, à tester et à maintenir
  • Déploiements risqués : Chaque déploiement touche à de nombreuses fonctionnalités

Comment le détecter

  • Mesurez les lignes de code ou les endpoints par service. Si l’un en a 10x plus que la moyenne, c’est suspect
  • Examinez les commits : si un service concentre la majorité des changements de plusieurs équipes, c’est probablement un God Service
  • Posez la question : puis-je décrire la responsabilité de ce service en une phrase ? Si vous avez besoin d’une longue liste, il a trop de responsabilités
  • Analysez les dépendances : si la majorité des services dépendent d’un seul, c’est un God Service

Correction

StratégieDescription
Identifier les bounded contexts (DDD)Cartographier les domaines réels au sein du God Service. Chaque domaine est candidat à devenir un service indépendant
Strangler Fig PatternExtraire les fonctionnalités une par une, en créant de nouveaux services qui interceptent les appels au God Service
Principe de responsabilité uniqueChaque service doit avoir une raison claire d’exister
Prioriser par impactCommencez par extraire la fonctionnalité qui change le plus fréquemment ou qui a des besoins de scalabilité différents
API Gateway / BFFUtiliser une gateway pour router les requests vers des services spécialisés plutôt que vers le God Service

4. Services bavards (Chatty Services)

Problème

Les services communiquent entre eux avec trop d’appels à granularité fine pour compléter une seule opération. Au lieu d’une requête qui récupère toute l’information nécessaire, un service effectue de multiples petits appels.

Servicio A necesita mostrar el perfil de un usuario:

  Servicio A → Servicio Usuarios: GET /users/123
  Servicio A → Servicio Usuarios: GET /users/123/preferences
  Servicio A → Servicio Pedidos:  GET /orders?user=123
  Servicio A → Servicio Pedidos:  GET /orders/456/details
  Servicio A → Servicio Pedidos:  GET /orders/789/details
  Servicio A → Servicio Pagos:    GET /payments?user=123
  Servicio A → Servicio Reviews:  GET /reviews?user=123

  Total: 7 llamadas de red para una sola pantalla

Les signes les plus évidents sont :

  • Une opération métier génère plus de 3-4 appels entre services
  • La latence est élevée alors que chaque service individuel répond rapidement
  • Les logs montrent des rafales d’appels corrélés pour un seul request

Impact

  • Latence cumulée : 7 appels de 50 ms chacun totalisent 350 ms dans le meilleur des cas (séquentiel)
  • Fragilité : Avec 7 appels et 99 % de disponibilité par service, la disponibilité effective descend à ~93 %
  • Charge réseau excessive : Le trafic interne peut dépasser le trafic externe
  • Difficulté de débogage : Retracer une erreur à travers plusieurs appels nécessite une traçabilité distribuée

Comment le détecter

  • Comptez les appels entre services que génère un seul request de l’utilisateur
  • Mesurez la latence end-to-end vs la latence individuelle de chaque service
  • Vérifiez si les services exposent des endpoints très granulaires

Correction

StratégieDescription
BFF (Backend for Frontend)Créer un service intermédiaire qui agrège les données de plusieurs services en une seule réponse
Endpoints agrégésConcevoir des endpoints qui renvoient toute l’information nécessaire à un cas d’usage
Cache localConserver des copies locales des données qui changent peu
Appels en parallèleSi les appels sont indépendants, les exécuter en parallèle
Événements pour données dérivéesUtiliser des événements pour que chaque service maintienne une vue matérialisée des données dont il a besoin

5. Modèle anémique (Anemic Domain Model)

Problème

Les entités du domaine sont des conteneurs de données sans comportement — elles n’ont que des getters et des setters. Toute la logique métier vit dans des services externes qui manipulent ces entités passives.

// ❌ Modelo anémico
class Pedido {
  id: string;
  estado: string;
  líneas: LíneaPedido[];
  total: number;
  // Solo getters y setters, sin lógica
}

class PedidoService {
  confirmar(pedido: Pedido) {
    if (pedido.estado !== 'borrador') throw new Error('...');
    if (pedido.líneas.length === 0) throw new Error('...');
    pedido.estado = 'confirmado';
    pedido.total = this.calcularTotal(pedido.líneas);
    // Toda la lógica está aquí, no en Pedido
  }
}
// ✅ Modelo rico
class Pedido {
  confirmar() {
    if (this.estado !== 'borrador')
      throw new Error('Solo se puede confirmar un pedido en borrador');
    if (this.líneas.length === 0)
      throw new Error('El pedido debe tener al menos una línea');
    this.estado = 'confirmado';
    this.total = this.calcularTotal();
    this.registrarEvento(new PedidoConfirmado(this.id));
  }
}

Impact

  • Logique dispersée : Les règles métier sont dupliquées dans plusieurs services
  • Difficile à tester : Les tests doivent configurer des services externes pour valider des règles simples
  • Violation de l’encapsulation : N’importe qui peut modifier l’état de l’entité sans passer par les règles
  • Difficile à découvrir : Il n’est pas évident de savoir quelles opérations sont valides sur une entité

Correction

StratégieDescription
Déplacer la logique vers les entitésLes règles métier qui opèrent sur les données d’une entité doivent vivre dans l’entité
Appliquer le DDDUtiliser des agrégats avec des invariants qui protègent la cohérence
Value ObjectsEncapsuler des concepts comme Argent, Email, Adresse avec leur propre validation
Domain EventsLes entités publient des événements lorsqu’elles changent d’état

6. Intégration par base de données (Database Integration)

Problème

Les services s’intègrent en lisant et écrivant dans les tables d’autres services au lieu de communiquer par API ou par événements. C’est une variante plus subtile de la base de données partagée.

Servicio A necesita saber si un usuario es premium:

  ❌ SELECT tipo FROM usuarios WHERE id = 123
     (accede directamente a la tabla del Servicio B)

  ✅ GET /api/users/123/subscription
     (consulta al Servicio B por su API)

Impact

  • Couplage invisible : Il n’y a pas de contrat explicite, seulement une connaissance implicite de la structure des tables
  • Impossible à versionner : Vous ne pouvez pas faire évoluer le schéma sans casser les consommateurs cachés
  • Aucun contrôle d’accès : N’importe quel service peut lire ou écrire n’importe quelle donnée

Correction

StratégieDescription
API explicitesChaque service expose une API pour accéder à ses données
Événements de domainePublier les changements sous forme d’événements pour que d’autres services s’y abonnent
ACL PatternCréer une couche de traduction entre le modèle interne et les consommateurs

Tableau comparatif

Anti-patternSigne principalRisque majeurPremière action
Monolithe distribuéVous ne pouvez pas déployer un service seulComplexité sans bénéficeDéfinir des contrats et des bounded contexts
Base de données partagéePlusieurs services accèdent aux mêmes tablesMigrations dangereusesAttribuer l’ownership des tables
God ServiceUn service concentre toute la logiquePoint unique de défaillanceIdentifier les domaines à extraire
Services bavardsBeaucoup d’appels pour une opérationLatence et fragilitéCréer des endpoints agrégés ou un BFF
Modèle anémiqueEntités sans comportementLogique dispersée et dupliquéeDéplacer la logique vers les entités (DDD)
Intégration par BDRequêtes directes sur les tables d’autres servicesCouplage invisibleExposer des API explicites

Comment prévenir les anti-patterns

Les anti-patterns apparaissent rarement d’un coup. Ils s’accumulent progressivement. Quelques pratiques qui aident à les prévenir :

  • Revues d’architecture régulières : Évaluez régulièrement si les services conservent leur indépendance
  • Fitness functions : Définissez des métriques automatisées qui détectent le couplage (nombre de dépendances, taille des services, fréquence des déploiements conjoints)
  • ADR (Architecture Decision Records) : Documentez les décisions architecturales et leurs raisons
  • Ownership clair : Chaque service, table et API doit avoir une équipe responsable
  • Principe du rasoir : Si vous ne pouvez pas justifier pourquoi un service est indépendant, il ne devrait probablement pas l’être
  • Suivi du couplage : Mesurez combien de services sont déployés ensemble, combien d’appels synchrones existent entre services, et combien de tables sont partagées

Relation avec d’autres patterns

Les anti-patterns se préviennent et se corrigent en appliquant les bons patterns :

Anti-patrón                    Patrón que lo corrige
─────────────────────────────────────────────────────
Monolito Distribuido    →  DDD (Bounded Contexts)
                        →  Event-Driven Architecture
                        →  ACL Pattern

Base de Datos Compartida →  Database per Service
                         →  Outbox Pattern (sincronización)
                         →  Event-Driven Architecture

God Service             →  Microservices Pattern
                        →  DDD (descomposición por dominio)
                        →  API Gateway / BFF

Servicios Parlanchines  →  BFF Pattern
                        →  API Gateway Pattern
                        →  Event-Driven Architecture

Modelo Anémico          →  DDD (diseño táctico)
                        →  Aggregates + Domain Events

Integración por BD      →  ACL Pattern
                        →  Outbox Pattern
                        →  Shared Kernel (controlado)

Prochaines étapes

Maintenant que vous connaissez les anti-patterns les plus courants, approfondissez les patterns qui les préviennent : le DDD pour définir des bounded contexts clairs, le Microservices Pattern pour décomposer correctement, l’Event-Driven Architecture pour découpler les services, et le BFF Pattern pour résoudre le problème des services bavards.