Aller au contenu principal
LaPolaris lance ses formations. 40% de réduction jusqu'au 30 juin En savoir plus

Single Source of Truth : le principe qui met fin aux données qui se contredisent dans votre application

Le prix affiché sur la page produit n'est pas le même que celui du panier. L'utilisateur est connecté ici mais déconnecté là. Ces bugs n'arrivent pas par hasard. Ils viennent d'un défaut d'application du principe de Single Source of Truth.

Architecture logicielle ·
Adel LATIBI
Adel LATIBI

Le Briefing Dev - les ressources et actus de la semaine, droit dans ta boîte chaque vendredi gratuitement.

Un jour, un client signale un bug bizarre : il a ajouté un produit au panier, mais le prix affiché dans le panier n'est pas le même que celui affiché sur la page du produit. Le développeur ouvre le code. Le prix est stocké à trois endroits : dans la base de données, dans un cache Redis, et dans un état local côté frontend. Les trois ne sont plus synchrones. La question n'est plus "comment réparer ce bug" mais "comment éviter que ça recommence".

La réponse s'appelle Single Source of Truth (SSoT), ou source unique de vérité en français. Ce principe est moins formalisé que SOLID ou DRY, mais il est partout. Dans la gestion d'état frontend, dans les architectures distribuées, dans la conception de base de données, dans les systèmes de configuration. Et quand il est violé, les symptômes sont toujours les mêmes : des données qui se contredisent, des bugs difficiles à reproduire, une confiance érodée dans le système.

Le problème concret : quand plusieurs endroits détiennent la même vérité

Le principe est simple à énoncer : toute donnée dans votre système doit avoir un seul endroit qui fait autorité. Un seul fichier, un seul service, un seul champ en base. Tout le reste n'est qu'une copie, une projection ou un cache, et doit être reconnu comme tel.

Quand ce principe est violé, la même information existe à plusieurs endroits sans hiérarchie claire entre eux. Aucun n'a la priorité sur l'autre, et leurs mises à jour se font indépendamment. Tôt ou tard, ils finissent par diverger.

Voici un exemple typique en backend :

// Table "utilisateurs"
{
  id: 42,
  email: "marie@example.com",
  nom: "Dupont",
  est_actif: true
}

// Table "sessions"
{
  user_id: 42,
  user_email: "marie@example.com",   // copie de email
  user_nom: "Dupont",                // copie de nom
  derniere_connexion: "2026-04-20"
}

// Table "commandes"
{
  id: 1001,
  client_id: 42,
  client_email: "marie@example.com", // autre copie
  client_nom: "Dupont"               // autre copie
}

Trois tables, trois copies du nom et de l'email. Le jour où Marie change son adresse email dans son profil, la modification est faite dans utilisateurs. Les autres copies restent inchangées. Résultat : les emails transactionnels partent à l'ancienne adresse, les logs affichent deux emails différents selon la requête, le support ne sait plus quelle adresse est la bonne.

Ce genre de situation n'est pas rare. Elle est même très courante dans les projets qui ont grandi par couches successives, chaque nouvelle fonctionnalité dupliquant ses données pour "aller plus vite" ou "éviter de faire une jointure".

Ce que dit vraiment le principe

Single Source of Truth ne veut pas dire "ne jamais copier une donnée". Dans un système réel, il faut souvent dupliquer pour des raisons de performance, de résilience ou de découplage. Le principe dit quelque chose de plus subtil : pour chaque donnée, il doit exister un endroit qui fait autorité, et tous les autres endroits qui la contiennent doivent être reconnus comme des copies dérivées de cette source.

"La duplication n'est pas le problème. L'ambiguïté sur qui détient la vérité l'est." Reformulation moderne du principe

Dans notre exemple, l'email et le nom doivent avoir un propriétaire : la table utilisateurs. Les sessions et les commandes peuvent soit ne pas contenir ces champs et faire une jointure quand elles en ont besoin, soit contenir un snapshot explicite (par exemple "l'email au moment de la commande", qui reste figé volontairement), soit être synchronisées à chaque changement via un mécanisme clair.

Ce qu'on élimine, c'est le flou : l'ambiguïté sur quel endroit est la référence et quel endroit suit.

Les trois grands domaines d'application

Dans la gestion d'état frontend

C'est probablement là que le principe est le plus discuté aujourd'hui. React, Vue, Svelte, Angular ont tous popularisé l'idée qu'un composant ne doit pas détenir sa propre copie d'un état qui appartient logiquement à un parent ou à un store global.

Le symptôme classique d'une violation :

// Parent
const [utilisateur, setUtilisateur] = useState({ nom: "Marie" });

// Enfant
function Profil({ utilisateur }) {
  const [nomLocal, setNomLocal] = useState(utilisateur.nom);  // copie locale
  // ...
}

L'enfant copie le nom dans son propre état. Désormais, deux endroits peuvent détenir deux valeurs différentes. Si le parent met à jour utilisateur, l'enfant ne le voit pas. Si l'enfant modifie nomLocal, le parent non plus. C'est la source d'une catégorie entière de bugs "pourquoi les données ne se mettent pas à jour" qui reviennent chaque semaine sur Stack Overflow.

La bonne approche : le nom reste dans le parent, l'enfant le lit en props et renvoie les modifications via une fonction callback. Une seule source, un seul endroit qui décide. Les formations React et Next.js insistent beaucoup sur ce réflexe, parce qu'il conditionne la fiabilité de toute l'application.

Dans la base de données

La normalisation des schémas relationnels est une application directe de Single Source of Truth. La troisième forme normale (3NF) revient à dire que chaque information doit être stockée une seule fois, et que toute autre référence passe par une clé étrangère. C'est pour ça qu'on ne stocke pas le nom du fournisseur dans la table des commandes : on stocke son identifiant, et on fait une jointure quand on en a besoin.

À l'inverse, la dénormalisation volontaire pour des raisons de performance est acceptable à condition qu'elle soit explicite et contrôlée. Un champ nombre_articles stocké sur la table commande est une copie dérivée. Il doit être mis à jour automatiquement via un trigger, une tâche asynchrone ou un recalcul déterministe. Si le développement dépend de maintenir cette cohérence à la main à chaque endroit qui touche aux articles, c'est une bombe à retardement.

Dans la configuration et les environnements

Un autre terrain fréquent : la configuration dupliquée entre fichiers .env, variables CI/CD, settings applicatifs, README, Dockerfile. Chaque endroit qui contient la même valeur (URL d'API, clé secrète, nom d'environnement) est une source potentielle de divergence.

La bonne pratique est de centraliser dans un seul endroit (souvent un service de secrets comme Vault, AWS Secrets Manager, Doppler ou les variables de la plateforme de déploiement) et de tout dériver à partir de là. Pour un projet qui commence, un simple fichier .env lu par tous les services suffit. L'important est qu'il n'y ait qu'un seul endroit à modifier pour changer une configuration.

Un cas réel de refactoring

Reprenons un scénario inspiré du vrai monde. Une boutique en ligne où le prix d'un produit est dupliqué dans trois endroits différents :

// Base de données
table produits { id, nom, prix }

// Cache Redis (pour accélérer l'affichage)
redis.set("produit:42:prix", 29.90)

// Etat frontend (chargé au premier affichage)
store.produits[42].prix = 29.90

Tant que le prix ne change pas, tout va bien. Le jour où un administrateur modifie le prix en base, trois questions se posent : le cache Redis est-il invalidé ? Le store frontend des utilisateurs déjà connectés est-il rafraîchi ? Que voit l'utilisateur dans son panier s'il l'a rempli avant le changement ?

La solution respectueuse du SSoT désigne explicitement la base de données comme source unique de vérité. Le cache Redis et l'état frontend sont reconnus comme des projections. Un mécanisme doit garantir qu'ils reflètent la source :

// 1. La modification passe toujours par la source
async function modifierPrix(produitId, nouveauPrix) {
  await db.query('UPDATE produits SET prix = ? WHERE id = ?', [nouveauPrix, produitId]);
  await redis.del(`produit:${produitId}:prix`);  // invalide le cache
  await pubsub.publish('produit.modifie', { id: produitId }); // notifie les clients
}

// 2. Le cache se repeuple uniquement depuis la source
async function getPrix(produitId) {
  const cached = await redis.get(`produit:${produitId}:prix`);
  if (cached !== null) return parseFloat(cached);
  const row = await db.query('SELECT prix FROM produits WHERE id = ?', [produitId]);
  await redis.set(`produit:${produitId}:prix`, row.prix, 'EX', 3600);
  return row.prix;
}

// 3. Le frontend écoute les notifications et se rafraîchit
pubsub.subscribe('produit.modifie', ({ id }) => {
  store.produits[id] = null;  // force un rechargement depuis l'API
});

Il y a toujours trois endroits qui contiennent le prix, mais la relation est maintenant claire : la base de données est la source, le cache et le store sont des miroirs qui doivent être invalidés quand la source change. Le flux de données est unidirectionnel et prévisible.

Le lien avec DRY, mais pas la même chose

On confond parfois Single Source of Truth avec DRY. Les deux sont liés, mais distincts. DRY parle de la duplication de logique, de code, d'intention. SSoT parle de la duplication de données, d'état, d'information.

Une fonction dupliquée dans trois fichiers est un problème DRY. Une valeur utilisateur stockée dans trois tables différentes est un problème SSoT. Les deux produisent le même symptôme (il faut modifier plusieurs endroits pour changer quelque chose), mais la nature du correctif est différente. Pour DRY, on extrait la logique dans une fonction commune. Pour SSoT, on désigne un propriétaire de la donnée et on supprime ou transforme les copies.

Ces deux principes se renforcent mutuellement. Un code bien découpé (DRY, Separation of Concerns) rend plus naturel l'application du SSoT, parce que chaque domaine de données a un endroit évident où vivre.

Quand la duplication est légitime

Il existe des cas où dupliquer une donnée est non seulement acceptable mais souhaitable. Le principe de SSoT n'interdit pas la duplication, il impose qu'elle soit intentionnelle et documentée.

Les snapshots historiques

Une commande conserve le prix du produit au moment de l'achat, même si le prix change ensuite. C'est volontaire. Cette copie ne doit surtout pas être synchronisée avec la source, parce qu'elle représente un fait historique gelé. Le nom de ce champ doit refléter cette intention : prix_au_moment_de_la_commande et non prix.

Les caches de performance

Redis, Varnish, un CDN, un state global côté client : tous sont des copies. Ce qui les rend acceptables, c'est qu'ils sont explicitement des caches, avec une politique d'invalidation claire. La source reste unique, les caches sont des accélérateurs dérivés.

Les réplications de base de données

Une réplique en lecture seule d'une base principale est une copie légitime. Le mécanisme de réplication garantit qu'elle reflète la source avec un délai acceptable. Tant que les écritures passent uniquement par la base primaire, le modèle SSoT est respecté.

Les projections dans CQRS

Dans une architecture Command Query Responsibility Segregation, les données sont volontairement dupliquées entre un modèle d'écriture et un modèle de lecture optimisés pour leurs usages respectifs. Cette duplication est structurée, et la direction du flux est explicite : les projections dérivent des commandes, jamais l'inverse.

Les signaux qui révèlent une violation

Comme les autres principes, SSoT se révèle négativement : par les symptômes de son absence. Voici les plus fréquents.

Les bugs "mais je l'ai mis à jour pourtant". Un utilisateur modifie une donnée, et elle apparaît correctement à certains endroits et pas à d'autres. C'est le signal le plus typique d'un SSoT violé. Quelque part, une copie n'est pas synchronisée avec la source.

Les scripts de "réconciliation" qui tournent chaque nuit. Quand une équipe en est à écrire des jobs planifiés pour comparer deux systèmes et corriger les divergences, c'est que la hiérarchie des sources n'est pas claire. Ces scripts traitent le symptôme, pas la cause.

Les débats récurrents sur "laquelle des deux valeurs est la bonne". Quand le support client ou les développeurs doivent régulièrement décider quelle valeur utiliser entre deux systèmes, c'est qu'aucun n'est officiellement la source de vérité. Chaque décision ad hoc est un pansement, pas une solution.

Les copier-coller de constantes métier. Un taux de TVA défini dans trois fichiers différents, une liste de pays hardcodée côté frontend et côté backend, un ensemble de statuts dupliqué entre base de données et code applicatif : chaque duplication est un risque de divergence future.

Comment introduire SSoT dans un projet existant

Rares sont les projets qui naissent avec une architecture parfaite. Dans un projet existant, la progression se fait par zones.

La première étape consiste à cartographier les données dupliquées. Pour chaque donnée métier importante (utilisateur, produit, commande, configuration), identifiez tous les endroits où elle vit. Un simple tableau avec trois colonnes suffit : donnée, emplacements, source supposée. Ce travail révèle souvent que personne dans l'équipe ne sait vraiment où est la source.

Ensuite, pour chaque donnée, désignez explicitement un propriétaire. Ce choix doit être documenté, idéalement dans un schéma d'architecture ou un ADR (Architecture Decision Record). Tout le reste devient dérivé.

Enfin, pour chaque dérivation, formalisez le mécanisme de synchronisation. Est-ce un cache avec TTL ? Un événement qui invalide ? Une replication asynchrone ? Un recalcul à la volée ? Le seul mauvais choix est "personne ne sait".

Cette démarche est un chantier classique abordé dans les formations LaPolaris sur le développement backend et les bases de données. Le chapitre sur la normalisation des schémas et la gestion des caches y tient une place importante, parce qu'il conditionne la fiabilité de toute application qui manipule des données dans plusieurs systèmes.

Ce que SSoT apporte vraiment

Les bénéfices d'un SSoT bien appliqué dépassent la correction de bugs. Ils touchent la confiance dans le système, la vitesse de développement et la qualité des décisions métier.

Les bugs de cohérence disparaissent. Quand chaque donnée a un propriétaire unique, il n'y a plus de situation où deux parties du système se contredisent. Les utilisateurs voient des données fiables, le support ne perd plus de temps à arbitrer entre deux vérités.

Les nouvelles fonctionnalités sont plus rapides à développer. Les développeurs savent où aller chercher une donnée et comment la modifier. Plus de réunions pour savoir "quel est le vrai champ" ou "lequel des deux systèmes il faut mettre à jour".

Les analyses métier sont plus fiables. Un dashboard qui tire ses chiffres d'une source unique donne les mêmes résultats que les applications opérationnelles. Quand ce n'est pas le cas, on entre dans le territoire des décisions stratégiques prises sur des données incohérentes.

L'onboarding des nouveaux développeurs est accéléré. Un système avec une source de vérité claire est plus facile à comprendre qu'un système où chaque donnée a trois emplacements et où il faut deviner lequel est la référence.

FAQ

Single Source of Truth signifie-t-il qu'il ne faut jamais dupliquer de données ?

Non. Dupliquer est parfois nécessaire pour des raisons de performance, de résilience ou d'historique. Ce que le principe exige, c'est que la duplication soit intentionnelle et que la source de vérité soit clairement désignée. Les copies doivent être reconnues comme telles, avec un mécanisme explicite de synchronisation ou un choix assumé de figer la copie (cas des snapshots).

Quelle différence entre SSoT et DRY ?

DRY concerne la duplication de code, de logique, d'intention. SSoT concerne la duplication de données et d'état. On peut violer DRY sans violer SSoT (plusieurs fonctions qui font la même chose, mais sur des données différentes) et inversement (une seule fonction qui va chercher une donnée dans trois endroits différents sans hiérarchie claire). Les deux principes sont complémentaires mais s'appliquent à des niveaux différents.

SSoT est-il compatible avec les architectures microservices ?

Oui, mais il demande une discipline particulière. Chaque microservice doit être propriétaire d'un domaine de données. Les autres services qui ont besoin de ces données les consomment via l'API du propriétaire ou via des événements, sans stocker leur propre copie complète. Quand une copie locale est nécessaire pour des raisons de performance, elle doit être mise à jour de manière asynchrone à partir des événements émis par le service propriétaire.

Comment gérer un cache tout en respectant SSoT ?

Le cache doit être traité comme une copie temporaire, pas comme une source. Les règles à respecter : les écritures passent toujours par la source, jamais directement par le cache ; le cache a toujours un mécanisme d'invalidation (TTL, pub/sub, write-through) ; en cas d'absence dans le cache, la lecture retombe sur la source. Si ces trois points sont respectés, le cache ne viole pas SSoT, il en est une projection.

SSoT s'applique-t-il aussi au frontend ?

Particulièrement, oui. Les bibliothèques comme Redux, Zustand, Pinia ou TanStack Query reposent toutes sur l'idée qu'un état donné a un seul endroit qui fait autorité. Les composants en deviennent des consommateurs. Violer ce principe en copiant des données dans des états locaux est la cause numéro un des bugs "l'UI ne se met pas à jour". Les formations React et JavaScript avancé abordent ces patterns en détail.

Comment appliquer SSoT à la configuration d'un projet ?

La règle est de centraliser chaque valeur de configuration à un seul endroit, puis de la dériver partout où elle est utilisée. Un fichier .env lu par l'application, une variable CI/CD lue par le pipeline, un secret dans un gestionnaire de secrets pour la production. Les clés et URLs ne doivent jamais être dupliquées dans plusieurs fichiers. Les valeurs affichées dans la documentation doivent être générées ou référencées, pas copiées à la main.

Un schéma de base de données doit-il toujours être en troisième forme normale ?

Pas nécessairement. La normalisation stricte est un outil pour respecter SSoT au niveau du schéma, mais elle a un coût en performance (jointures fréquentes). Dans certains cas, la dénormalisation est justifiée pour optimiser des requêtes critiques. Ce qui compte, c'est que la dénormalisation soit explicite, que les champs dupliqués soient clairement identifiés comme tels, et qu'un mécanisme garantisse leur cohérence (triggers, jobs, logique applicative). Le pire des cas est une dénormalisation accidentelle, où personne ne sait vraiment ce qui est source et ce qui est copie.

Quelle est la relation entre SSoT et les autres principes de code ?

SSoT dialogue avec tous les principes fondamentaux. Il renforce DRY en limitant la duplication, s'appuie sur la Separation of Concerns pour désigner clairement un propriétaire par domaine, et s'articule avec KISS en évitant les architectures où plusieurs systèmes se partagent des responsabilités floues. Il s'articule aussi avec YAGNI : inutile d'introduire un cache ou une réplication avant d'en avoir un réel besoin mesurable.

Vous êtes expert ?

Partagez votre expertise sur notre blog

Tutoriel, retour d'expérience, analyse — publiez un article invité et gagnez en visibilité.

Écrire pour nous