La classe App
Comme toujours, revenos à notre test pour déterminer ce qu'il faut coder.
// 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({
accomodationId:"accomodation-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.listBookingsForAccomodationId("accomodation-1")
expect(bookings).toHaveLength(1)
Notre classe App
ne semble pas compliquée, puisqu'elle comporte :
- un constructeur qui prend en paramètre le container des dépendances
- une méthode
run
- un attribut public
dependencies
Injection des dépendances
L'injection des dépendances dans l'app se fait facilement, puisque le container est passé au constructeur.
class App {
constructor (public dependencies) {
this.dependencies = dependencies;
}
async run(usecases) {}
}
En revanche, la méthode run
demande plus de ténacité.
Gestion du contexte
Souvenons-nous qu'une commande est une fonction qui retourne une fonction.
Par exemple pour login() :
function login(payload) {
const {email, password} = payload;
return async function (dependencies, context) {
const user = await dependencies.users.findByEmail(email);
if (!user) {
// Utilisateur inconnu
return context.withError(UnknownUserEmail(email))
}
if (user.encryptedPassword !== encrypt(password)) {
// Erreur de mot de passe
return context.withError(WrongPassword(user))
}
// Tout est OK, ajoutons l'utilisateur au contexte
// pour les commandes suivantes qui en auront besoin
return context.withUser(user)
}
}
On passe les paramètres de login et mot de passe à notre commande,
et elle retourne une fonction asynchrone function(dependencies, context)
qui retourne un contexte pour la commande suivante.
Pour les dépendances, le problème est réglé. Reste la question du contexte.
Toujours de la même façon, déduisons le code du contexte de notre test.
On voit 2 méthodes withError
et withUser
.
class Context {
withError (error) {
this.error = error;
return this;
}
withUser (user) {
this.user = user;
}
}
Désormais, on dispose du nécessaire pour coder notre class App :
class App {
constructor (dependencies) {
this.dependencies = dependencies;
}
async run(usecases) {
var context = new Context()
for (const usecase of usecases) {
// Les commandes attendent les dépendances et le contexte
context = await usecase(this.dependencies, context)
}
}
}
Terminé ? Pas complètement.
Notre méthode run
enchainent toutes les commandes, même lorsqu'une erreur est survenue.
Ajoutons donc un fusible pour arrêter en cas d'erreur dans le contexte, et retournons le contexte pour que l'appelant puisse gérer l'erreur rencontrée.
class App {
constructor (public dependencies) {
this.dependencies = dependencies;
}
async run(usecases) {
var context = new Context()
for (const usecase of usecases) {
// Inutile de continuer en cas d'erreur
if (context.isOk()) {
context = await usecase(this.dependencies, context)
}
}
return context
}
}
Au passage, on a eu besoin d'une méthode isOk()
dans la class Context
qu'il nous faut donc ajouter :
class Context {
withError (error) {
this.error = error;
return this;
}
withUser (user) {
this.loggedUser = user;
return this;
}
isOk() {
return !this.error
}
}
Que reste-il sur notre liste ?
- les dépendances
- la classe
App
- le constructeur de l'objet
Booking
appelé dans la fonctionbook()
Codons la logique métier de notre objet Booking
, seule brique manquante pour faire passer notre test.