📐 Construire une app en JS
48. Ce qu'il reste à faire

Ce qu'il reste à faire

Notre plateforme est en ligne. Un vacancier peut chercher un logement, se connecter, réserver, consulter et annuler ses réservations. Le propriétaire est prévenu.

Regardons ce que ça a coûté, ce qui manque, et ce que ça coûtera.

Le chemin parcouru

Nous avons commencé par une conversation, pas par un framework. Les huit premiers chapitres ne contiennent presque pas de code : ils contiennent des questions posées à un expert métier, et deux lignes de test.

const booking = book(accommodation, tenant, stay);
expect(booking.status).toBe("confirmed");

Tout le reste en découle.

Les décisions techniques, elles, sont arrivées tard, et dans cet ordre :

chapitredécision
2un moteur de test
22React, pour une démo
36PostgreSQL
40un serveur HTTP
43TypeScript

Trente-cinq chapitres avant de choisir une base de données. Et le choix a pris une page, parce que le cahier des charges était déjà écrit — dans les ports.

C'est le résultat le plus important du cours, et il tient dans une phrase du chapitre 12 :

Retarder les choix techniques = imposer une architecture modulaire.

Ce que nous n'avons pas fait

La liste est longue, et c'est normal. Une première version qui couvre tout n'est pas une première version.

L'inscription. Nos utilisateurs sont créés à la main dans un tableau. C'est une commande signup, un test, et la validation d'email qui va avec.

L'espace propriétaire. Le catalogue est saisi à la main (chapitre 19). Le jour où un propriétaire créera sa fiche, Accommodation deviendra une vraie entité avec ses invariants — le chapitre 28 l'avait annoncé.

Le paiement. L'expert métier n'en voulait pas pour la première version. C'est le plus gros morceau de la suite.

La recherche par destination. Le champ existe depuis le chapitre 25 et ne sert toujours à rien.

Les avis, les photos multiples, les messages, le multilingue, le mobile. Chacun est un projet.

Le vrai test d'une architecture

Une architecture ne se juge pas sur son élégance. Elle se juge sur une seule question :

Combien coûte le prochain changement ?

Faisons l'exercice sur quatre demandes qui arriveront.

« Je veux pouvoir chercher par ville »

Une requête, pas une commande. Rien à toucher dans le domaine : aucun invariant ne dépend de la ville.

Une clause where a.location ilike $1 dans le service de lecture du chapitre 32, un paramètre de plus dans le loader, un champ branché dans le bandeau du chapitre 27.

Une demi-journée, et zéro risque de régression : aucune règle métier n'a été approchée.

« Le propriétaire aussi doit pouvoir annuler »

Une commande, cancelBookingAsHost. Le tableau du chapitre 4 nous dit tout de suite qu'il faut de la vigilance : une commande, donc des autorisations et des invariants.

Elle réutilise Booking.cancel() du chapitre 33, mais avec une autorisation différente (est-ce bien mon logement ?) et des règles différentes (un préavis, un dédommagement). Elle émet un événement distinct, BookingCancelledByHost, parce que les mails ne diront pas la même chose — et le chapitre 35 nous a appris à ne pas déguiser deux faits différents en un seul.

Deux jours. La difficulté est métier, pas technique : jusqu'à quand ? avec quelle compensation ?

« Il faut payer en ligne »

Le gros morceau, et c'est là que l'architecture se paie.

Le parcours change (chapitre 19) : la réservation naît pending et devient confirmed au paiement. Nous avions écarté ce statut faute d'usage ; il revient avec un usage.

Ce qu'il faut ajouter : un port payments, un adaptateur vers un prestataire, un contrôleur pour son webhook, une expiration des réservations non payées.

Ce que nous avons déjà : les événements (chapitre 35), la boîte d'envoi et l'exigence d'idempotence (chapitre 42) — indispensable, un prestataire de paiement rejoue ses webhooks —, la transaction (chapitre 39), et un Booking dont le statut ne change que par des transitions nommées.

Ce que nous ne toucherons pas : Stay, Occupancy, CalendarDay, la disponibilité, la capacité. Ces règles-là ne dépendent pas du paiement.

Deux à trois semaines. Sans cette architecture, la même demande signifierait rouvrir chaque contrôleur pour y intercaler un état intermédiaire.

« On passe en marque blanche, chaque agence a son site »

La demande qui fait peur, et qui arrive toujours.

Elle est structurante : il faut un identifiant de tenant sur presque toutes les tables et dans presque toutes les requêtes. Ce n'est pas gratuit.

Mais notez où le travail se situe : dans les repositories et les requêtes, c'est-à-dire dans infra. Le contexte transporte déjà l'utilisateur (chapitre 9) ; il transportera l'agence. Les commandes changent peu, les Value Objects pas du tout.

Un mois. C'est cher. Ce serait un trimestre avec des requêtes SQL disséminées dans des contrôleurs.

Ce que je referais autrement

Un cours qui ne montre que des réussites ment. Voici trois choses que je changerais.

Context est une classe mutable, dont les méthodes retournent this (chapitre 13). Nous avons ensuite passé des chapitres à défendre l'immutabilité. Un contexte immutable, dont chaque withX retourne une nouvelle instance, serait plus cohérent — et supprimerait toute une classe de bugs le jour où quelqu'un conservera une référence.

getAvailableAccommodations est dans MemoryBookingRepository. Le chapitre 20 l'y a mise "faute de mieux", en le disant. Elle aurait dû migrer dans le service de lecture au chapitre 32. Elle y est restée par inertie — la forme la plus commune de dette technique.

Le contexte transporte les données d'une requête (withData, chapitre 32). Ça marche, et ça mélange deux mécanismes : un contexte d'exécution et un canal de retour. Un app.query() distinct de app.run() serait plus honnête.

Aucun de ces trois points n'est bloquant. Tous coûteraient une demi-journée à corriger. C'est exactement ce qu'on attend d'un code vivant : pas d'être parfait, mais que ses imperfections restent bon marché.

Les règles, rassemblées

Une trentaine de principes ont été formulés au fil des chapitres. Voici ceux que je vous invite à emporter.

Sur le métier

  • Le développeur doit parfaitement connaître le business pour lequel il travaille. (19)
  • Un bon nom est une question qu'on ne peut plus éviter. (21)
  • Une règle inventée par le développeur est une régression que personne ne signalera. (29)
  • Le langage ubiquitaire est une discipline qu'on tient à chaque commit, pas un vœu de départ. (1, 21)

Sur la conception

  • La conception ne se décide pas à l'avance. Elle se découvre, un test à la fois. (15)
  • Rendez les états invalides non représentables. (21, 44)
  • Une valeur mérite sa classe quand elle réunit au moins deux signes : un invariant, une primitive ambiguë, deux données inséparables. (21)
  • Une classe quand un état et un comportement sont indissociables ; une fonction sinon. (45)
  • Tout ce qui donne un résultat différent à chaque appel est une dépendance. Le reste est pur. (17, 21, 30, 33)
  • Un port se nomme d'après le besoin du domaine, jamais d'après la technologie. (34)
  • Ce qui sort du domaine n'est pas ce qui y vit. (30, 32, 35, 41)

Sur les commandes et les requêtes

  • Une commande refuse, une requête filtre. Les deux partagent le prédicat, pas l'exigence. (28)
  • L'appelant ne déclare pas qui il est. Il le prouve, et le serveur décide. (9, 30, 32)
  • Une commande peut être rejouée : elle ne doit rien faire d'irréversible. (39, 42)
  • On publie ce qui est acquis : après validation, jamais avant. (35)
  • On ne supprime pas, on change d'état. (33)

Sur les tests

  • La première ligne de code du projet est un test. (2)
  • N'écrivez aucun invariant tant qu'aucun test ne l'exige. (15)
  • Rouge, vert, refactor. La troisième étape est celle où se prend la conception. (21)
  • Un double injecté est une implémentation ; un module remplacé à chaud est une supposition. (46)
  • Chaque règle qui traverse la frontière doit être tenue par un test de contrat. (29, 38)
  • Chaque bug corrigé commence par un test qui échoue. (46)
  • Les types vérifient les formes, les tests vérifient les comportements. (43)

Sur l'ingénierie

  • Retarder les choix techniques impose une architecture modulaire. (12, 36)
  • N'ajoutez pas une méthode avant d'avoir un appelant. (25, 28)
  • Entre une garantie déclarative et une garantie procédurale, prenez la déclarative. (39)
  • Une sauvegarde qui n'a jamais été restaurée n'existe pas. (47)
  • Si la règle est simple, faites du code simple. (14)

Cette dernière est la plus difficile à tenir. Elle est aussi celle qui distingue un développeur expérimenté d'un développeur qui veut le paraître.

Un mot pour finir

Vous avez peut-être remarqué ce que ce cours ne contient pas.

Aucune discussion sur le meilleur framework. Aucun comparatif de bibliothèques. Presque aucune configuration.

Ce n'est pas du mépris pour l'outillage : nous avons utilisé Vitest, React, Vite, PostgreSQL, Express, TypeScript, et chacun a rendu service. C'est que ces choix-là s'apprennent en une semaine et se remplacent en un mois.

Ce qui prend des années, c'est de savoir poser les bonnes questions au bon moment. De reconnaître une entité d'une valeur. De sentir qu'un invariant manque. De résister à l'envie de coder ce que personne n'a demandé. D'appeler l'expert métier avant d'écrire un if qui divisera son chiffre d'affaires par deux.

Le chapitre 1 disait :

Ce dialogue sera la clé de réussite du projet. Loin devant la maîtrise des techniques de code.

Quarante-sept chapitres plus tard, c'est toujours vrai. Les plus grosses économies du projet n'ont pas été faites par du code : elles ont été faites par cinq questions posées à quelqu'un qui connaissait son métier.

Le meilleur code que vous écrirez cette année sera celui que vous n'écrirez pas.


Une question, un projet, une équipe à former ?

Écrivez-moi : contact@camilab.co