Le strict nécessaire
Finissons-en avec la création de la réservation.
En programmation orientée objet, on opterait pour une classe Booking.
Mais pour l'instant, nous avons seulement besoin d'une fonction Booking.of() en charge de la création d'une nouvelle réservation.
En bref, une sorte de constructeur.
Construire une réservation
Ce constructeur prend des paramètres.
const booking = Booking.of({
tenantId:user.id,
accommodationId,
hosts : {adults,children},
interval : {from,to}
});On brûle d'envie de créer cette classe Booking, et de lui adjoindre plein de contrôles utiles dont nous aurons forcément besoin plus tard :
- vérifier que la date d'arrivée est dans le futur
- contrôler que la date de départ est après la date d'arrivée
Mais aussi :
- le logement demandé ne doit pas être déjà loué
- la taille du logement est limitée. Il ne peut pas accueillir plus de personnes que le max prévu.
Etc.
Nous sommes tous tentés de foncer pour coder tout cela élégamment dans cette magnifique classe Booking.
Pourtant, je vous propose la démarche exactement inverse : on ne fait rien.
Ou plus exactement rien d'autre que le strict minimum nécessaire à faire passer notre test au vert.
Objectif : vert
Rappelons-nous 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({
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.bookings.listBookingsForAccommodationId("accommodation-1")
expect(bookings).toHaveLength(1)C'est lui qui guide notre démarche de conception. Comme une boussole.
Et que nous dit-il ?
Que n'importe qui peut réserver n'importe quoi.
Alors faisons le choix le plus simple pour construire notre objet et faire passer notre test.
const booking = {
tenantId:user.id,
accommodationId,
hosts : {adults,children},
interval : {from,to}
};Pas besoin d'une fonction pour construire une réservation directement issue des paramètres, sans vérifier aucun invariant (pour l'instant !).
Que reste-t-il sur notre liste ?
- les dépendances
- la classe
App - le constructeur de l'objet
Bookingappelé dans la fonctionbook()
Tout y est !
Et les règles métier ?
Vous vous demandez peut-être où sont passées toutes les règles dont l'expert métier nous a parlé : le logement déjà loué, la date d'arrivée dans le futur, la capacité d'accueil.
Nulle part. Volontairement.
Certaines seraient faciles à coder :
// la date de départ doit être après la date d'arrivée
if (from >= to) {
return Maybe.error(WrongInterval(from, to))
}D'autres impliqueraient d'aller interroger l'état du système :
// Un logement ne peut pas être loué 2 fois sur la même période
const bookings = await dependencies.bookings.listBookingsForAccommodationId(accommodationId);
if (bookings.some(booking => overlapped(booking.interval, interval))) {
return Maybe.error(AccommodationNotAvailable(accommodationId))
}On sent qu'il sera difficile d'embarquer tout cela dans un constructeur. Aucun choix de conception évident ne se dégage.
Raison de plus pour ne rien décider maintenant.
Laissons les tests décider
Voici la démarche : on ne code AUCUN invariant tant qu'aucun test ne l'exige.
Puis, pour chaque règle, on ajoute un test, et on apporte la modification minimale qui le fait passer au vert.
Cette méthode paraît complètement à l'opposé d'un processus d'ingénierie mûrement réfléchi, dans lequel on coderait élégamment d'emblée tous les cas dont l'expert métier nous a parlé.
Et pourtant, vous serez étonnés du temps gagné et de la simplicité du code obtenu. La conception ne se décide pas à l'avance. Elle se découvre, un test à la fois.
Il est temps de passer notre premier test au vert.