Catégorie : Bonnes Pratiques

  • Bonnes pratiques de schema de base de donnees

    Bonnes pratiques de schema de base de donnees

    Le schema est la partie d’un systeme la plus difficile a changer une fois pleine de donnees. Vous pouvez reecrire un service en un week-end. Migrer une table d’un milliard de lignes sans interruption est un projet. Je passe donc du temps reel sur le schema en amont, car le cout d’une erreur ne fait que grandir. Voici les decisions que je ne regrette pas.

    Laissez la base imposer les regles

    Le code applicatif n’est pas l’endroit pour garantir l’integrite des donnees, car il y a toujours un autre chemin d’entree : un script de migration, une correction manuelle, un second service, un developpeur dans une console. La base est le seul point de passage par lequel tout transite, c’est donc la que les regles appartiennent. J’utilise NOT NULL agressivement, les cles etrangeres pour imposer les relations, les contraintes d’unicite pour empecher les doublons, et les contraintes de verification pour les plages de valeurs.

    CREATE TABLE orders (
      id          bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
      customer_id bigint NOT NULL REFERENCES customers(id),
      status      text   NOT NULL DEFAULT 'pending'
                  CHECK (status IN ('pending','paid','shipped','cancelled')),
      total_cents integer NOT NULL CHECK (total_cents >= 0),
      created_at  timestamptz NOT NULL DEFAULT now()
    );

    Chaque contrainte ici est un bug qui ne pourra jamais atteindre la production. Un statut « shippd » est rejete au moment de l’ecriture au lieu de casser un rapport trois semaines plus tard.

    Normalisez d’abord, denormalisez sur preuve

    Je commence normalise : chaque fait vit a un seul endroit. Une donnee dupliquee est une verite dupliquee, et les copies divergent des que quelqu’un en met une a jour et oublie l’autre. La normalisation garde les ecritures simples et la justesse peu couteuse.

    La denormalisation est une optimisation de performance, et comme toute optimisation je veux une mesure avant de la faire. Quand une requete precise est vraiment trop lente et que le profil pointe les jointures, alors je vais cacher une valeur calculee ou dupliquer une colonne, sachant que je prends en charge la synchronisation des copies. Le faire par anticipation, c’est ainsi qu’on obtient un schema plein de champs auxquels personne ne fait confiance.

    Choisissez cles et types deliberement

    Chaque table recoit une cle primaire synthetique, en general un bigint identite ou un UUID. J’evite les cles naturelles comme les adresses e-mail en cle primaire, car la seule chose que vous pouvez promettre d’une cle naturelle, c’est qu’elle changera, et changer une cle primaire referencee partout est un calvaire. Utilisez des types qui veulent dire quelque chose :

    • Stockez l’argent en centimes entiers, jamais en flottants. Le flottant et la monnaie sont de vieux ennemis.
    • Utilisez toujours un horodatage avec fuseau pour le temps, et stockez en UTC.
    • Prenez un enum natif ou une contrainte de verification au lieu de champs de statut en texte libre.
    • Utilisez le vrai type JSON de la base pour des donnees vraiment non structurees, pas un bloc de texte.

    Indexez pour vos lectures, mais connaissez le cout

    Un index rend les lectures rapides et les ecritures un peu plus lentes, et il prend de la place. J’indexe les cles etrangeres, les colonnes que je filtre regulierement, et les colonnes par lesquelles je trie. Je n’indexe pas tout, car un index inutilise n’est que du poids sur chaque insertion. La facon de savoir, c’est de regarder : la plupart des bases vous diront quels index restent inutilises, et ceux-la sont candidats au retrait.

    Les index composites valent la peine d’etre compris, car l’ordre des colonnes compte. Un index sur (customer_id, created_at) aide une requete qui filtre par client et trie par date, mais ne fait rien pour une requete qui ne filtre que par date. Le meme etat d’esprit d’observabilite que j’ai decrit dans les bonnes pratiques d’observabilite s’applique ici : mesurez les vrais schemas de requete avant de deviner.

    Traitez les migrations comme du code

    Chaque changement de schema passe par un fichier de migration, verse dans la gestion de versions, relu comme tout autre changement. Aucune instruction ALTER manuelle lancee a la main en production, jamais, car l’environnement suivant ne l’aura pas et vous passerez une journee a chasser la difference. La meme discipline de relecture que dans les bonnes pratiques de revue de code s’applique, avec un soin supplementaire, puisqu’une mauvaise migration peut verrouiller une table ou perdre des donnees.

    • Faites des migrations vers l’avant et additives quand vous le pouvez. Ajoutez une colonne, remplissez-la, puis retirez l’ancienne plus tard dans une etape separee.
    • Evitez les changements qui prennent un long verrou exclusif sur une grande table en service.
    • Testez la migration sur une copie de donnees a la taille de la production avant de la lancer pour de vrai.

    Un schema que vous pouvez faire evoluer en securite vaut plus qu’un schema parfait que vous avez peur de toucher. Construisez pour le changement, car le changement est la seule certitude.

  • Bonnes pratiques de journalisation et d’observabilite

    Bonnes pratiques de journalisation et d’observabilite

    Vous decouvrez la qualite de votre observabilite au pire moment possible : quand quelque chose est casse, que les clients le remarquent, et que vous ne savez pas pourquoi. Tout ce que je fais ici vise ce moment. Le but est de repondre a « que se passe-t-il et pourquoi » en minutes, pas en heures. Une bonne observabilite, c’est la difference entre un incident calme et un incident frenetique.

    Journalisez des donnees structurees, pas des phrases

    Les lignes de log lisibles par l’humain semblent sympathiques jusqu’a ce que vous deviez en chercher dix millions. Alors vous ecrivez des regex fragiles contre de la prose. Je journalise des enregistrements structures, cle-valeur ou JSON, pour que les logs soient interrogeables comme une base plutot que fouilles comme un journal intime.

    // Pas ca :
    log.info("Utilisateur " + userId + " echec connexion depuis " + ip)
    
    // Ca :
    log.info("login_failed", {
      user_id: userId,
      ip: ip,
      reason: "bad_password",
      attempt: 3
    })

    Maintenant « montre-moi toutes les connexions echouees de cet utilisateur dans la derniere heure » est un filtre, pas un projet d’archeologie. Choisissez des noms de champ coherents entre services pour que le meme concept ait la meme cle partout, et une requete ecrite une fois fonctionne sur tout le systeme.

    Utilisez les niveaux avec discipline

    Les niveaux de log n’aident que s’ils veulent dire quelque chose de coherent. Quand tout est journalise en INFO, le niveau n’est que du bruit. Ma regle empirique :

    • ERROR, c’est quelque chose de casse qu’un humain doit regarder. Si ca ne merite pas d’attention, ce n’est pas une erreur.
    • WARN, c’est inattendu mais gere, le genre de chose qu’il vaut la peine de surveiller pour un motif.
    • INFO, ce sont des evenements metier importants : une commande passee, un travail termine.
    • DEBUG, c’est du detail pour le developpement local, en general coupe en production.

    Le test pour ERROR est simple : si une alerte se declenchait pour chacun, seriez-vous en colere ? Si oui, ce n’est pas vraiment une erreur, et vous venez de vous entrainer a ignorer le niveau cense vous reveiller.

    Faites circuler un identifiant de requete partout

    Dans tout systeme a plus d’un service, une seule action utilisateur devient une douzaine de lignes de log eparpillees sur plusieurs machines. Sans un fil qui les relie, vous devinez. Je genere un identifiant de correlation a la frontiere et le passe a chaque appel en aval et dans chaque ligne de log. Alors un seul identifiant reconstitue tout le chemin d’une requete, pour la meme raison qui me pousse a garder des formes d’erreur coherentes dans le guide de conception d’API REST : quand autre chose doit suivre la piste, la structure gagne.

    Mesurez les trois choses qui parlent de sante

    Les logs parlent d’evenements precis. Les metriques parlent du systeme dans son ensemble, et ce sont elles qui font tourner vos tableaux de bord et vos alertes. Pour tout service qui traite des requetes, je suis le debit, les erreurs et la duree : combien de requetes, combien ont echoue, et combien de temps elles ont pris. Regarder la distribution de latence plutot que la moyenne compte, car la moyenne cache la queue lente ou les vrais utilisateurs souffrent.

    • Suivez la latence au 95e et au 99e percentile, pas seulement la moyenne.
    • Suivez le taux d’erreur en pourcentage pour qu’il ait du sens a tout niveau de trafic.
    • Suivez la saturation, le taux de remplissage de vos ressources, pour voir les ennuis avant qu’ils deviennent une panne.

    Alertez sur les symptomes, pas sur les causes

    Une alerte devrait signifier qu’un humain doit agir maintenant. Sinon, ce devrait etre un tableau de bord, pas une alerte. La facon la plus rapide de faire detester l’astreinte, ce sont des alertes qui se declenchent sans cesse et ne veulent rien dire, car les gens apprennent a les balayer puis ratent celle qui comptait. J’alerte sur les symptomes vus par l’utilisateur, comme un taux d’erreur qui franchit un seuil ou une latence qui explose son budget, plutot que sur des causes internes comme un CPU eleve, qui peut etre parfaitement normal.

    Une derniere chose qui paie : ne journalisez jamais de secrets, mots de passe, jetons ou details de paiement complets. Il est facile de les laisser fuiter dans les logs par accident, et les logs s’etalent sur des systemes aux controles d’acces plus faibles que votre base. Le meme soin guide par les contraintes que j’ai decrit dans les bonnes pratiques de schema de base de donnees vaut ici aussi. Decidez ce qui est sensible, puis assurez-vous que ca n’atteigne jamais une ligne de log.

  • Bonnes pratiques de revue de code

    Bonnes pratiques de revue de code

    La revue de code est l’habitude au plus fort levier qu’une equipe puisse avoir, et c’est aussi celle qu’on rate le plus souvent. J’ai recu des revues qui ressemblaient a un interrogatoire et des revues qui approuvaient 800 lignes avec un pouce leve en quatre secondes. Les deux sont des echecs. Une bonne revue attrape de vrais problemes, diffuse la connaissance et laisse l’auteur soutenu plutot que juge.

    Relisez ce que les humains font bien

    Ne gaspillez pas votre attention sur le formatage, l’ordre des imports ou la longueur d’une ligne. Un linter et un formateur automatique s’en chargent, et ils ne se fatiguent jamais. Si votre equipe se dispute sur le style dans les commentaires de PR, vous avez un manque d’outillage, pas un probleme de discipline. Configurez le formateur, validez la config, et passez a autre chose.

    Ce qu’une machine ne peut pas verifier, c’est si le code est correct, s’il resout le vrai probleme, et si quelqu’un dans six mois le comprendra. C’est la que va mon attention :

    • Est-ce que ca fait ce que la description annonce ?
    • Que se passe-t-il aux bords : entree vide, valeurs nulles, acces concurrent, un appel reseau qui pend ?
    • Y a-t-il une approche plus simple cachee derriere celle-ci ?
    • Le nommage aura-t-il du sens pour quelqu’un qui n’etait pas dans la piece ?

    Gardez des changements assez petits pour etre vraiment relus

    La limite dure de la qualite de relecture, c’est la taille. La recherche et mon experience disent la meme chose : au-dela de quelques centaines de lignes, la detection de defauts s’effondre parce que les relecteurs survolent. Quand je recois une PR enorme, je demande a l’auteur de la decouper. Une serie de petites PR ciblees recoit un vrai examen sur chacune. C’est aussi pourquoi je rebase et ecrase avec soin avant d’ouvrir une PR, une habitude que j’ai decrite dans les bonnes pratiques Git.

    Commentez comme un collegue, pas comme un compilateur

    Le ton fait l’essentiel du travail. Le meme point passe completement differemment selon la formulation. Je pose des questions au lieu de rendre des verdicts, et je clarifie quels commentaires sont bloquants et lesquels sont optionnels.

    # Au lieu de :
    C'est faux.
    
    # Essayez :
    Que se passe-t-il ici si items est vide ? Je crois
    qu'on depasserait la fin. On pourrait proteger avec
    une verification de longueur, ou ce cas est-il
    impossible en amont ?
    
    # Et etiquetez les details :
    detail : on pourrait inliner, non bloquant

    Marquer les details comme non bloquants est une petite chose qui retire enormement de friction. L’auteur sait ce qu’il doit corriger pour fusionner par rapport a ce qui n’est que mon gout. Je prends aussi soin de dire quand quelque chose est vraiment bien. Une revue qui n’est que critique entraine les gens a redouter le processus.

    Relisez vite et finissez ce que vous commencez

    Une PR qui reste sans relecture deux jours bloque une personne et pourrit a mesure que main avance dessous. Je traite les demandes de relecture comme une priorite quasi en haut de ma file, idealement le jour meme. Le cout d’une PR a l’arret s’accumule : l’auteur change de contexte, puis doit tout recharger quand le retour arrive enfin.

    Quand je relis, j’essaie de donner tous mes retours en une passe plutot que de distiller des commentaires sur trois tours. Rien n’est plus demoralisant que tout corriger, etre relu, et decouvrir cinq nouveaux commentaires qui etaient visibles depuis le debut.

    L’auteur a aussi des devoirs

    Les revues vont plus vite quand l’auteur les rend faciles. Avant de demander une relecture, j’ecris une description qui explique ce qui a change et pourquoi, je laisse des commentaires en ligne sur mon propre diff pour pointer ce qui n’est pas evident, et je m’assure que la CI est au vert. Une bonne description peut diviser par deux le temps de relecture, car le relecteur ne reconstitue pas l’intention a partir du diff.

    • Enoncez le probleme, pas seulement la solution.
    • Signalez tout ce dont vous n’etes pas sur et sur quoi vous voulez un regard.
    • Incluez des captures ou un exemple de sortie pour tout ce qui touche l’utilisateur.

    Etre en desaccord, puis s’engager

    Parfois l’auteur et le relecteur voient simplement les choses autrement. Quand le desaccord porte sur le gout plutot que sur la justesse, je m’en remets a la decision de l’auteur apres avoir exprime mon avis une fois. Trainer une PR sur cinq tours pour une preference stylistique brule un capital de bonne volonte dont vous aurez besoin plus tard. Gardez les positions fermes pour ce qui compte vraiment : la justesse, la securite, et les contrats de donnees qui me tiennent tant a coeur dans le guide de conception d’API REST. Reussissez ceux-la et laissez filer les petites choses.

  • Bonnes pratiques de workflow Git et de branches

    Bonnes pratiques de workflow Git et de branches

    J’ai travaille dans des equipes qui traitaient Git comme un rituel sacre et dans d’autres qui le traitaient comme un tiroir fourre-tout. Aucun de ces extremes ne livre du bon logiciel. Apres des annees a nettoyer des historiques chaotiques et a demeler des catastrophes de fusion, j’ai adopte une serie d’habitudes qui gardent les choses calmes. Aucune n’est ingenieuse. C’est justement le but.

    Choisissez un modele de branches et cessez d’en debattre

    Le modele compte moins que l’accord. Pour la plupart des equipes produit, j’utilise le developpement sur tronc commun avec des branches de fonctionnalite a courte duree de vie. Vous partez de main, vous travaillez un jour ou deux, vous fusionnez, vous supprimez la branche. Plus une branche vit longtemps, plus elle derive, et plus la fusion finale devient penible. Les branches de version a longue duree ont leur place dans un logiciel qui sort a cadence fixe vers des clients qui ne peuvent pas se mettre a jour a la demande, mais pour une application web deployee plusieurs fois par jour, elles ne sont que du poids inutile.

    Ce que j’evite activement, c’est le montage GitFlow elabore ou develop, release, hotfix et feature s’entrelacent. J’ai vu ce systeme derouter les nouveaux pendant des semaines. Si votre deploiement est continu, vos branches devraient l’etre aussi.

    Ecrivez des commits qui expliquent le pourquoi

    Un message de commit est une note pour celui qui lira l’historique a 2h du matin pendant un incident, et cette personne pourrait etre vous. Le diff montre deja ce qui a change. Le message doit capturer pourquoi. Je garde la ligne de sujet sous environ cinquante caracteres, a l’imperatif, et j’utilise le corps pour expliquer le raisonnement quand le changement n’est pas evident.

    fix: empeche le double paiement lors d'une nouvelle tentative
    
    Le client de paiement reessayait sur un 504 alors que
    le debit etait deja passe cote passerelle. On clef
    desormais la requete avec un jeton d'idempotence pour
    que la passerelle deduplique. Ferme #482.

    Les commits atomiques sont l’autre moitie de l’affaire. Un changement logique par commit. Quand un commit fait trois choses sans rapport, vous ne pouvez jamais en annuler une seule proprement, et le bisect devient inutile. Si vous ecrivez « et » dans une ligne de sujet, ce sont deux commits.

    Rebasez votre propre travail, fusionnez le travail partage

    C’est la regle qui evite le plus de douleur. Avant d’ouvrir une pull request, je rebase ma branche sur la derniere version de main pour que mes changements reposent sur la realite actuelle et que la relecture soit fluide. Mais des qu’une branche est partagee ou qu’une PR est ouverte et que d’autres l’ont regardee, j’arrete de rebaser et je fusionne, car reecrire un historique publie oblige tous les autres a recuperer leur etat local.

    • Rebasez pour ranger vos commits locaux avant qu’ils soient publics.
    • Utilisez le rebase interactif pour ecraser les inevitables commits « correction de faute » et « vraie correction ».
    • Ne forcez jamais le push d’une branche sur laquelle d’autres construisent.
    • Protegez main pour que personne ne puisse y pousser directement.

    Gardez main toujours livrable

    La propriete la plus precieuse d’un depot, c’est que main fonctionne toujours. Si main est au vert, vous pouvez sortir une version a tout moment, et un deploiement casse se repare en annulant une seule fusion. J’impose cela avec des verifications obligatoires : les tests et le linting doivent passer avant meme que le bouton de fusion apparaisse. Cela rejoint directement ma facon de mener les relectures, que j’ai abordee dans les bonnes pratiques de revue de code. Un historique propre rend les relectures plus rapides, et de bonnes relectures gardent l’historique propre. Les deux se nourrissent mutuellement.

    Rendez l’annulation banale

    Quand quelque chose casse en production, l’action sure la plus rapide est en general d’annuler, pas de deboguer en direct. Ecraser chaque PR en un seul commit sur main rend cela trivial : une PR est un commit, et l’annuler retire toute la fonctionnalite proprement. J’aime les fusions ecrasees pour cette raison precise sur le code applicatif, meme si pour les bibliotheques ou l’historique de chaque commit a une vraie valeur, je conserve l’historique complet.

    Etiquetez vos versions pour toujours pouvoir repondre a « qu’est-ce qui tournait mardi dernier ». Une etiquette legere ne coute rien et transforme une question vague en reponse d’une ligne.

    Quelques habitudes qui paient discretement

    • Validez un .gitignore sense des le premier jour pour que secrets et artefacts de build n’entrent jamais dans l’historique. Retirer un identifiant fuite de l’historique gache un apres-midi.
    • Tirez avec rebase par defaut pour eviter le bruit des commits de fusion a chaque synchro.
    • Gardez des PR petites. Une PR de 200 lignes recoit une vraie relecture. Une PR de 2000 lignes recoit un tampon.

    Git recompense la discipline plus que le savoir. Vous n’avez pas besoin de memoriser les commandes de plomberie. Vous avez besoin d’un petit ensemble d’accords que tout le monde suit vraiment. Le meme raisonnement revient quand je concois des stockages de donnees, ce que j’ai couvert dans les bonnes pratiques de schema de base de donnees, ou quelques conventions fermes tot epargnent un nettoyage enorme plus tard.

  • Guide de conception d’API REST

    Guide de conception d’API REST

    Une API est une promesse. Des qu’un client en depend, chaque bizarrerie que vous avez livree devient permanente, parce que quelqu’un quelque part a ecrit du code contre cette bizarrerie. J’ai maintenu des API pendant des annees et celles qui ont bien vieilli partageaient le meme trait : elles etaient ennuyeuses et previsibles. Voici comment j’y arrive.

    Modelisez les ressources en noms, prenez les verbes a HTTP

    L’URL devrait nommer une chose. La methode dit ce que vous lui faites. Je vois sans cesse des points d’entree comme /getUser et /createOrderNow, et ils combattent tout l’interet de HTTP. Une conception propre utilise des noms au pluriel pour les collections et laisse la methode porter l’action.

    GET    /orders          liste les commandes
    POST   /orders          cree une commande
    GET    /orders/42       recupere une commande
    PATCH  /orders/42       met a jour des champs
    DELETE /orders/42       la supprime
    
    GET    /orders/42/items ressource imbriquee

    PATCH pour les mises a jour partielles et PUT pour le remplacement complet est une distinction a garder. La plupart des vraies mises a jour touchent quelques champs, donc PATCH est ce que je prends, et PUT devient le cas rare ou le client possede vraiment toute la representation.

    Utilisez les codes de statut comme les clients l’attendent

    Renvoyez le code de statut qui correspond a la realite. Un 200 sur une requete echouee parce qu’on « a mis l’erreur dans le corps » casse tout client generique et tout outil de supervision qui lit la ligne de statut. L’ensemble que j’utilise couvre presque tout :

    • 200 pour une lecture ou une mise a jour reussie, 201 quand vous avez cree quelque chose.
    • 400 pour une entree malformee, 422 quand l’entree est bien formee mais semantiquement invalide.
    • 401 quand vous ne savez pas qui ils sont, 403 quand vous le savez et qu’ils n’ont pas le droit.
    • 404 pour une ressource manquante, 409 pour un conflit comme un doublon.
    • 500 uniquement pour de vraies pannes serveur, jamais pour une erreur du client.

    Rendez les erreurs lisibles par la machine

    Un corps d’erreur devrait aider le code appelant a reagir, pas juste afficher une chaine. Je renvoie un code machine stable a cote d’un message humain, pour que les clients puissent brancher sur le code sans analyser une prose que je pourrais reformuler plus tard.

    {
      "error": {
        "code": "insufficient_funds",
        "message": "La carte a ete refusee.",
        "field": "payment_method"
      }
    }

    La coherence compte ici plus que l’ingeniosite. Chaque erreur de l’API devrait avoir la meme forme, pour qu’un client ecrive un seul gestionnaire d’erreur au lieu de dix. C’est le meme instinct que j’apporte aux logs, dont j’ai parle dans les bonnes pratiques d’observabilite : la structure vaut mieux que la prose quand autre chose doit la lire.

    Prevoyez le versionnage avant d’en avoir besoin

    Vous devrez faire un changement cassant tot ou tard. Decidez comment avant de livrer la v1. Je mets la version dans le chemin, /v1/orders, parce qu’elle est visible, cacheable et triviale a router. Le versionnage par en-tete est plus elegant sur le papier et plus penible en pratique quand quelqu’un debogue avec curl. Quel que soit votre choix, la regle est de ne jamais casser une version existante. Les ajouts comme de nouveaux champs optionnels vont bien. Retirer un champ ou changer son type est une nouvelle version.

    Paginez et filtrez des le premier jour

    Toute collection qui peut grandir grandira, et un GET qui renvoie dix mille lignes finira par expirer et emporter une base de donnees avec lui. J’ajoute la pagination a chaque point d’entree de liste des le depart, meme quand les donnees sont minuscules, parce que l’ajouter plus tard est un changement cassant. La pagination par curseur gere mieux les grands jeux de donnees mouvants que l’offset, qui derive quand des lignes sont inserees en cours de parcours.

    • Renvoyez un curseur stable et un signal clair de « il y en a plus ».
    • Autorisez le filtrage par parametres de requete, et documentez exactement quels champs sont filtrables.
    • Plafonnez la taille de page cote serveur pour qu’un client ne puisse pas tout demander d’un coup.

    Soyez strict sur ce que vous acceptez, genereux sur ce que vous renvoyez

    Validez l’entree durement a la frontiere et rejetez tout ce qui est malforme avec un 400 ou 422 clair. Plus vous attrapez les mauvaises donnees pres du bord, moins elles peuvent corrompre l’aval, ce qui ramene directement aux contraintes sur lesquelles je m’appuie dans les bonnes pratiques de schema de base de donnees. Cote sortie, gardez les reponses stables et previsibles pour que les clients puissent faire confiance a la forme. Une API stricte a la porte et coherente a la sortie est une API qu’on aime construire, et cette bonne volonte est ce qui fait adopter votre plateforme.