Passer d'une architecture monolithique aux microservices est souvent présenté comme la solution miracle pour la scalabilité et l'indépendance. Cependant, de nombreuses équipes techniques tombent dans le piège de distribuer la complexité de leur base de données sans distribuer la logique. Cela donne lieu à des systèmes techniquement distribués mais conceptuellement monolithiques. En ingénierie des bases de données, la manière dont vous structurez vos données est tout aussi critique que la manière dont vous structurez votre code. Aujourd'hui, nous explorerons deux anti-modèles répandus : le Monolithe Distribué et la Sur-Normalisation, et discuterons de la manière de les éviter pour construire des microservices véritablement résilients.
Le Monolithe Distribué
Le monolithe distribué se produit lorsque les microservices semblent indépendants au niveau du code, mais sont fortement couplés au niveau des données. Cela se produit généralement lorsque les services partagent un seul schéma de base de données ou lorsqu'un service interroge directement les tables de base de données d'un autre service. Cela viole le principe fondamental des microservices : chaque service doit posséder ses données exclusivement.
Considérons un scénario où un OrderService (Service de Commande) a besoin de données client. Au lieu d'exposer ces données via une API bien définie, les développeurs pourraient effectuer des jointures directement dans la base de données du CustomerService (Service Client). Cela crée une dépendance rigide. Si le schéma des données client change, le service de commande sera cassé, que le code du service de commande ait été mis à jour ou non. Ce couplage empêche le déploiement et la mise à l'échelle indépendants.
Pour corriger cela, adoptez strictement le modèle « Une base de données par service ». Les services doivent communiquer via des événements asynchrones (par exemple, en utilisant Kafka ou RabbitMQ) ou via des APIs REST/gRPC synchrones. Cela garantit que la propriété des données reste encapsulée à l'intérieur des limites du service.
Le piège de la Sur-Normalisation
Tandis que la normalisation des données réduit la redondance et assure l'intégrité dans les bases de données relationnelles traditionnelles, elle devient un goulot d'étranglement de performance dans les environnements distribués. La sur-normalisation fait référence à la division excessive des données en de nombreuses petites tables, nécessitant des jointures complexes pour reconstituer des entités significatives.
Dans un monolithe, les jointures sont peu coûteuses car tout réside dans le même espace de processus. Dans les microservices, les données sont partitionnées à travers différentes limites réseau. Reconstituer une seule entité logique nécessite souvent d'interroger plusieurs services et de fusionner les résultats côté application. Cela conduit au problème de requête N+1, à une latence accrue et à une complexité plus élevée dans la gestion des erreurs.
Plutôt que de normaliser, envisagez la dénormalisation. Stockez les données redondantes là où elles sont nécessaires. Par exemple, si un OrderService affiche fréquemment le nom du client, stockez ce nom directement dans l'enregistrement de la commande. Bien que cela introduise des anomalies de mise à jour (vous devez mettre à jour le nom dans toutes les commandes existantes si le client le change), le compromis en termes de performance de lecture et de simplification de l'architecture est généralement bénéfique. Utilisez des tâches en arrière-plan pour gérer la cohérence éventuelle des mises à jour.
Exemple d'implémentation pratique
Regardons un exemple pratique opposant l'anti-modèle à l'approche recommandée. Dans l'anti-modèle, le schéma de la base de données pourrait ressembler à ceci :
-- ANTI-MODÈLE : Schéma partagé / Monolithe distribué
CREATE TABLE orders (
id UUID PRIMARY KEY,
user_id UUID, -- Clé étrangère directe vers la table user
status VARCHAR(20)
);
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) UNIQUE
);
-- Cette jointure couple fortement les services
SELECT o.id, u.email
FROM orders o
JOIN users u ON o.user_id = u.id;
Maintenant, considérez l'approche améliorée où les données sont dénormalisées et la propriété est claire :
Conclusion
Éviter les monolithes distribués et la sur-normalisation ne concerne pas seulement la correction technique ; il s'agit de discipline architecturale. En imposant des limites de données strictes et en acceptant la réalité de la latence réseau grâce à la dénormalisation, vous permettez à vos microservices de mettre à l'échelle indépendamment et de se déployer sans craindre des défaillances en cascade. Rappelez-vous, l'objectif n'est pas d'avoir la base de données la plus efficace pour une seule machine, mais le système le plus résilient pour un monde distribué. Commencez par auditer vos modèles d'accès actuels aux données, identifiez les couplages serrés et refactorisez progressivement vers un modèle qui privilégie le découplage lâche et la cohérence éventuelle.