La logique métier
Notre commande de réservation comporte la création d'une nouvelle réservation :
const booking = Booking.of({tenantId:user.id, accomodationId,adults,children,from,to});
On a juste besoin de construire une nouvelle réservation.
Mais quelles sont les règles d'une réservation correcte ?
Par exemple :
- la date de fin du séjour
to
doit être supérieure ou égale à la date d'arrivéefrom
. - on ne peut pas accepter une réservation dans le passé
- le nombre d'occupants (adultes, enfants) ne peut pas dépasser la capacité d'accueil du logement
- on ne peut pas réserver un logement qui n'est pas disponible (p.ex : déjà loué sur la période demandée)
- un même vacancier ne peut avoir 2 locations sur la même période
- etc...
Ces règles sont le coeur de notre application. Elle traduisent l'expertise métier d'un professionnel de la location saisonnière.
Choisir un style de programmation
Comment coder ces règles métiers ?
Plusieurs solutions sont possibles, toutes valables.
Pour choisir, on s'appuie sur :
-
Les standards de codage dans l'équipe. Sur une gros projet, vous ne travaillez pas seul. Et probablement pas de zéro. Des standards (= des habitudes communes) ont probablement émergé. Parfois elles sont décrites explicitement par un tech lead ou par le CTO.
-
L'API que l'on aimerait Si vous avez carte blanche, placez-vous à la place d'un autre développeur qui découvre le code. Et faîtes en sorte que ce soit compréhensible ET modifiable facilement.
-
La loi de Pareto 80/20 80% des règles métier sont faciles à coder, mais 20% sont difficiles. Alors inutile de complexifier 100% du code pour 20%. Ce n'est pas un concours de celui qui connaît le + de design patterns orientés objet. Ni de celui qui veut faire étalage de sa maîtrise de la monade reader.
Si la règle est simple, faisons du code simple. Si une fonction suffit, faites une fonction.
Coller au métier
Dans notre cas, on veut créer une réservation avec 6 paramètres que l'on regroupe en 4 questions :
- Qui loue ? ->
tenantId
- Quel hébergement ? ->
accomodationId
- Combien d'occupants ? ->
adults
,children
, - A quelle période ? ->
from
,to
Ce sont les questions qu'un professionnel de la location saisonnière poserait naturellement à un client à la recherche d'un logement.
Alors collons à cela.
Regroupons les paramètres from
et to
dans un objet interval
qui adresse la question "à quelle période ?". Car ça n'aurait pas de sens d'avoir une valeur from
sans valeur to
pour une location saisonnière
(en revanche, cette affirmation serait différente pour une location classique).
const booking = Booking.of({
tenantId:user.id,
accomodationId,
hosts : {adults,children},
interval : {from,to}
});
Le code colle au métier.
Gérer les erreurs
Nous avons envisagé une façon élégante de retourner un contexte en erreur lorsqu'un problème survient dans une commande.
Par exemple dans la commande de login
:
return context.withError(UnknownUserEmail(email))
Mais que faire lorsqu'une erreur survient lors de la création d'une réservation ?
Nous avons naïvement codé le cas joyeux dans lequel tout se passe bien.
const booking = Booking.of({tenantId:user.id, accomodationId,adults,children,from,to});
Que vaut booking
si l'intervalle des dates est erroné ?
Javascript propose des exceptions. Si ça se passe mal, on en jette une, et advienne que pourra.
if (Interval.of(from, to).isWrong()) {
throw new Error(WrongInterval(from, to))
}
Facile à jeter. Mais qui l'attrappe ? La contrepartie de l'exception est de disposer d'un endroit pour les gérer.
Mais il y a un autre problème : je ne sais pas deviner si une fonction peut retourner une exception, à moins de regarder son code. C'est un sacré coup de canif dans la modularité !
La monade Maybe
Préférons le résultat à l'exception.
Au lieu de retourner une réservation, retournons "peut-être une réservation, ou alors une erreur".
class Maybe {
value
error
constructor (value, error) {
this.value = value;
this.error = error;
}
static ok (value) {
return new Maybe(value, null);
}
static error (error) {
return new Maybe(null, error);
}
isError () { return !!this.#error}
}
Plus besoin de lever une exception. On peut désormais retourner l'espoir d'une réservation :
if (Interval.of(from, to).isWrong()) {
return Maybe.error(WrongInterval(from, to))
}
L'appelant s'attend à recevoir un objet de la classe Maybe, contenant soit une valeur (la réservation), soit une erreur. Il ne peut pas ignorer ces 2 états possibles. L'objet Maybe retourné lui indique que l'erreur est possible, sans avoir à mettre le nez dans le code de la fonction appelée.