Authentification
Dans notre use-case de réservation, on indique par le paramètre tenantId l'identifiant du vacancier à l'origine de la demande.
book({
tenantId:"faketenant@mail.com",
accommodationId:"accommodation-1",
adults:2, children:3,
from : new Date("2024-06-02"),
to : new Date("2024-06-02"),
})C'est en général une mauvaise pratique de laisser l'appelant déclarer lui-même qui est à l'origine de la demande.
Pourquoi ?
Parce que c'est une faille possible de sécurité. Cela sous-entend qu'un vacancier pourrait réaliser une réservation au nom d'un autre vacancier.
Bien sûr, on peut se prémunir de cette possibilité dans le code du use-case. Mais il vaut mieux intégrer la sécurité à la racine. On souhaite de la sécurité par conception (security by design).
Modifions notre test pour ne plus passer de tenantId dans la commande book().
Enchaîner les use-cases
Concrètement, notre vacancier entre son login et son mot de passe sur notre site. Puis il recherche un logement. Puis il effectue une réservation.
Voilà le parcours utilisateur. L'histoire que l'on veut raconter au travers de notre code.
Simplifions-la en considérant que l'utilisateur sait exactement quel hébergement il veut réserver.
Avant de réserver, l'utilisateur passe par une authentification. Racontons cette histoire dans notre test.
// Injection des dépendances
const app = new App(testDependencies())
// Réalisation de la location
await app.run([
login({email:"faketenant@mail.com", password:"secret"}),
book({
// tenantId:"faketenant@mail.com", <- supprimons ça
accommodationId:"accommodation-1",
adults:2, children:3,
from : new Date("2024-06-02"),
to : new Date("2024-06-02"),
})
]);
// Le calendrier comporte la réservation
const bookings = await app.dependencies.listBookingsForAccommodationId("accommodation-1")
expect(bookings).toHaveLength(1)Observez le changement de notre méthode App.run().
Elle prend en paramètres une liste de commandes, et non plus une seule commande. Ainsi notre test raconte explicitement l'histoire du parcours de l'utilisateur :
- d'abord s'authentifier, avec un email et un mot de passe
- puis faire la réservation
Ce qui conduit à une nouvelle commande login().
Le contexte
D'abord, login() est-elle vraiment une commande ?
Rappelons-nous la définition : une commande change l'état du système.
La réponse honnête est : pas vraiment. Telle que nous allons la coder, login() ne modifie rien du tout — elle lit un utilisateur et vérifie un mot de passe.
Ce qu'elle modifie, c'est l'état de la session : qui est connecté. Et cet état-là ne vit pas en base de données, il vit dans le contexte d'exécution que nous allons construire à l'instant.
Retenons la nuance : login() se comporte comme une commande — elle prend place dans le scénario, elle peut échouer, elle change ce que les commandes suivantes ont le droit de faire — sans pour autant toucher aux entités persistées.
Mais ne perdons pas de vue l'objectif initial : ne plus passer de tenantId dans la commande book().
Les informations de connexion déterminées par la commande login() doivent donc être passées à la commande book() qui a besoin de savoir qui est l'utilisateur à l'origine de la demande de réservation.
Nous allons donc créer un contexte d'exécution. Ce contexte sera passé de commandes en commandes.
Le contexte sera :
- vide au départ
- éventuellement rempli avec l'utilisateur authentifié par une commande
login() - éventuellement assorti d'une erreur, pour éviter de dérouler les commandes suivantes après l'échec de l'une d'elles.
Vous entendrez parfois appeler cela une monade. Nous verrons au chapitre 21 que le terme mérite d'être employé avec précaution — et surtout qu'il n'apporte rien pour l'instant. Laissons-le de côté, et commençons à coder notre application à partir de notre test.
Enfin !