📐 Construire une app en JS
13 App

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 fonction book()

Codons la logique métier de notre objet Booking , seule brique manquante pour faire passer notre test.