Ajouter de nouvelles structures de base de données sans casser les applications en cours d'exécution

Vous avez une table users avec une colonne full_name. L'équipe produit souhaite séparer les noms en first_name et last_name pour une meilleure personnalisation. Vous devez effectuer ce changement sans arrêter l'application ni casser les fonctionnalités existantes.

L'approche évidente serait de supprimer full_name, d'ajouter les deux nouvelles colonnes et de mettre à jour tout le code en une seule fois. Mais cela nécessite des déploiements coordonnés, un temps d'arrêt et un plan de rollback parfait en cas de problème. En pratique, ce genre de changement mène souvent à des rollbacks tardifs et à des utilisateurs mécontents.

Il existe une méthode plus sûre : ajouter d'abord la nouvelle structure, sans toucher à l'ancienne. Laissez l'ancienne et la nouvelle coexister jusqu'à ce que vous soyez prêt à basculer complètement.

La phase d'expansion : ajouter sans supprimer

Le pattern expand-contract commence par l'étape la plus sûre possible. Vous ajoutez de nouvelles colonnes, tables ou contraintes à la base de données sans toucher à ce qui existe déjà. Les anciennes applications qui continuent de fonctionner avec l'ancien schéma ne remarqueront aucune différence. Les nouvelles applications peuvent commencer à utiliser les nouvelles structures immédiatement.

C'est l'idée centrale de la phase d'expansion : vous introduisez de nouveaux objets de base de données sans modifier ni supprimer les objets existants. L'ancien schéma reste pleinement fonctionnel. Le nouveau schéma est un ajout, pas un remplacement.

Le diagramme ci-dessous montre l'état avant et après de la table users pendant la phase d'expansion, ainsi que la façon dont les applications anciennes et nouvelles interagissent avec le schéma.

flowchart TD subgraph Before[Avant la phase d'expansion] T1[Table users] C1[full_name VARCHAR] end subgraph After[Après la phase d'expansion] T2[Table users] C2[full_name VARCHAR] C3[first_name VARCHAR NULL] C4[last_name VARCHAR NULL] end OldApp[Ancienne application] -->|lecture seule| C2 NewApp[Nouvelle application] -->|lecture/écriture| C2 NewApp -->|lecture/écriture| C3 NewApp -->|lecture/écriture| C4 Before -->|ALTER TABLE ADD COLUMN| After

Un exemple concret

Prenons la table users avec une colonne full_name. Dans la phase d'expansion, vous ajoutez deux nouvelles colonnes :

Voici le SQL pour ajouter les nouvelles colonnes :

ALTER TABLE users ADD COLUMN first_name VARCHAR(100) NULL;
ALTER TABLE users ADD COLUMN last_name VARCHAR(100) NULL;
ALTER TABLE users ADD COLUMN first_name VARCHAR(100) NULL;
ALTER TABLE users ADD COLUMN last_name VARCHAR(100) NULL;

La colonne full_name reste exactement telle quelle. Les anciennes applications qui lisent full_name continuent de fonctionner sans aucune modification de code. Les nouvelles applications peuvent commencer à écrire dans first_name et last_name tout en lisant full_name pour la rétrocompatibilité.

Aucun temps d'arrêt. Aucun déploiement coordonné. Aucun risque de casser les requêtes existantes.

La règle critique : les nouvelles colonnes doivent être optionnelles

L'erreur la plus courante dans la phase d'expansion est d'ajouter une nouvelle colonne avec NOT NULL et sans valeur par défaut. Cela casse immédiatement toute application qui insère des lignes sans spécifier la nouvelle colonne. Les anciennes applications qui ne connaissent pas la colonne échoueront à chaque insertion.

La règle est simple : chaque nouvelle colonne doit être nullable ou avoir une valeur par défaut sensée. Si vous avez besoin d'une contrainte NOT NULL, ajoutez d'abord la colonne comme nullable, remplissez les données existantes, puis ajoutez la contrainte dans une phase ultérieure. Ne forcez pas toutes les applications existantes à modifier leurs instructions INSERT en même temps.

La même logique s'applique aux nouvelles tables. Une nouvelle table ne modifie aucune structure existante. Les anciennes applications n'ont jamais besoin de savoir qu'elle existe. Les nouvelles applications peuvent commencer à y écrire immédiatement. Il n'y a pas de conflit car rien n'a changé dans les tables qu'elles utilisent déjà.

Gérer les contraintes en toute sécurité

Les contraintes nécessitent une attention particulière pendant la phase d'expansion. Si vous ajoutez une contrainte UNIQUE sur une nouvelle colonne, assurez-vous que les données existantes ne la violent pas. Si vous ajoutez une FOREIGN KEY, vérifiez que toutes les lignes existantes référencent des lignes parent valides.

Pour les contraintes CHECK, vérifiez que la condition n'entre pas en conflit avec les données existantes. Certaines bases de données prennent en charge une option NOT VALID qui applique la contrainte uniquement aux nouvelles lignes, vous permettant de valider les données existantes séparément plus tard. C'est utile lorsque vous n'êtes pas sûr de la qualité des données dans les anciennes lignes.

Le principe est le même : n'introduisez pas une contrainte qui échouera sur les données existantes. Si vous ne pouvez pas le garantir, reportez la contrainte ou ajoutez-la d'une manière qui ne bloque pas les écritures.

L'importance du nommage

Les nouvelles colonnes et tables doivent avoir des noms clairs qui les distinguent de l'ancienne structure. Évitez les noms comme name_new, temp_name ou name_v2. Ceux-ci créent de la confusion pendant la phase de contraction lorsque vous devez savoir quelle structure est la structure canonique.

Utilisez des noms qui décrivent les données réelles. first_name et last_name sont meilleurs que name_split_1 et name_split_2. De bons noms facilitent la transition pour tous ceux qui travailleront avec le schéma plus tard.

Ce que la phase d'expansion n'exige pas

La phase d'expansion n'exige aucune modification de code dans les anciennes applications. Elles continuent d'utiliser le même schéma, les mêmes requêtes et la même logique. C'est ce qui rend la phase d'expansion sûre à exécuter à tout moment, même pendant les heures de pointe en production.

Aucun temps d'arrêt. Aucun redémarrage d'application. Aucune requête qui échoue soudainement parce qu'une colonne a disparu. Le seul changement concerne le schéma de la base de données, et ce changement est purement additif.

Quand la phase d'expansion est terminée

La phase d'expansion se termine lorsque les nouvelles structures existent dans la base de données et sont prêtes à être utilisées. Les anciennes applications utilisent toujours les anciennes structures. Les nouvelles applications peuvent commencer à utiliser les nouvelles structures. Les deux chemins fonctionnent simultanément.

À ce stade, vous avez une base de données qui prend en charge deux versions du schéma. C'est la base pour l'étape suivante : migrer progressivement les applications vers les nouvelles structures sans rien casser.

Liste de contrôle pratique pour la phase d'expansion

  • Les nouvelles colonnes sont nullables ou ont une valeur par défaut
  • Les nouvelles tables ne modifient pas les structures de tables existantes
  • Les nouvelles contraintes n'entrent pas en conflit avec les données existantes
  • Les noms distinguent clairement les nouvelles structures des anciennes
  • Les anciennes applications continuent de fonctionner sans modification de code
  • Les nouvelles applications peuvent commencer à utiliser les nouvelles structures immédiatement

Ce qu'il faut retenir

La phase d'expansion est le changement de base de données le plus sûr que vous puissiez faire car elle ne fait qu'ajouter. Pas de suppression, pas de modification, aucun risque de casser les applications en cours d'exécution. Ajoutez de nouvelles colonnes comme nullables, gardez les anciennes colonnes intactes et laissez les deux schémas coexister. Cela vous donne la liberté de migrer les applications à votre rythme, sans coordonner un déploiement big-bang ni planifier un temps d'arrêt.