📐 Construire une app en JS
17 Invariant Futur

Un premier invariant

Quelles sont les règles métiers à respecter pour la création d'un réservation ?

Certaines sont évidentes :

  • le logement demandé ne doit pas être déja loué
  • la date d'arrivée doit être dans le futur
  • la date de départ doit être après la date d'arrivée
  • la taille du logement est limitée. Il ne peut pas accueillir plus de personnes que le max prévu.

Quoi d'autre ?

Discutons avec l'expert métier, et posons lui des questions.

  • Un vacancier qui a déjà une réservation pour un logement peut-il en faire une autre sur une même période ?
  • Un vacancier qui a un litige en cours peut-il faire une réservation ?

Toutes ces règles à respecter sont des invariants métier.

Découper et ordonner

Quelle est l'activité favorite des développeurs à l'école maternelle ?

Le découpage.

C'est une compétence essentielle (et sous-estimée) du métier.

En découpant finement les fonctionnalités, le code évolue de façon continue. Par petits incréments très rapprochés.

Les invariants sont des fonctionnalités importantes. Ils garantissent l'intégrité des données. Un système de réservation permettant de faire n'importe quoi ne sert à rien.

Alors commençons par un invariant, et un seul pour découper finement. Par exemple :

  • la date d'arrivée d'une nouvelle réservation doit être dans le futur

Et comment procède-t-on ? Avec un test bien sûr !

Un nouveau test

Ecrivons la tentative de réservation dans le passé.

const app = new App(testDependencies())
 
// Tentative de réservation dans le passé
const session = await app.run([
    login({email:"faketenant@mail.com", password:"secret"}),
    book({
        accomodationId:"accomodation-1", 
        adults:2, children:3, 
        from : new Date("2023-06-02"),
        to :   new Date("2023-06-02"),
    })
]);
 
expect(session.error).toBe(InvalidInterval())

Et là, un problème se pose. Il faut choisir une date passée. Est-ce que le 2 juin 2023 est une date passée ?

Tout dépend de la date du jour au moment de l'exécution du test !

Alors une façon consiste à choisir une date très loin dans le passé. Genre le 2 juin 1854. Mais on voit combien ce choix est fragile. D'ailleurs, notre test précédent utilisait la date du 02 juin 2024.

Le 1er juin 2024, le test passe. Et soudain, le 3 juin, il ne passe plus !

La solution : considérer la fonction qui retourne la date courante comme une dépendance.

Le service dateProvider

L'horloge interne, tout comme l'accès à la base de données, est une dépendance.

Revisitons les dépendances de tests injectées dans notre classe App, en lui ajoutant un service permettant d'obtenir la date courante :

Lorsqu'il s'agira de mettre en service notre application, on utilisera un provider retournant la date système :

export const systemDateProvider = {
    now : () => Date.now()
}

Abstraction de la représentation d'une date

Mais pourquoi la date du jour serait-elle générée par Date.now(), qui retourne le nombre de millisecondes écoulés depuis le 01/01/1970 ? Et non pas new Date() qui retourne un objet Date standard de JavaScript ?

Comment souhaite-t-on représenter une date dans notre application ? C'est une question cruciale qui risque de revenir tout casser lorsqu'il s'agira de comparer les dates de fuseaux horaires différents !

Imaginons qu'un utilisateur au Mexique chercher à réserver un logement de vacances en France. Quel serait la façon naturelle de gérer les dates de sa réversation ?

Discutons-en avec l'expert métier. Il nous fournit une indication précieuse : les dates (et heures) de réservation sont gérées sur le fuseau horaire du lieu du logement. Si le logement est en France, alors on affiche ses dates de réservation ou de disponibilité selon la timezone FR.

Vous aurez l'impression de ré-inventer la roue. Mais vous avez tout intérêt à construire votre propre abstraction des dates gérées dans votre application.

Faisons au plus simple, nous pourrons toujours améliorer cela plus tard.

export const systemDateProvider = {
    now : () => Date.now(),
    toDate: (something) => new Date(something).valueOf(),
    sameDay: (date1, date2, timezone = "FR-fr") => new Date(date1).toLocaleDate(timezone) === new Date(date2).toLocaleDate(timezone),
    isBefore: (date1, date2) => date1 < date2
}

Injection d'un provider de test

Mais pour nos tests, nous utiliserons un autre provider que ce systemDateProvider.

Pour tester les fonctionnalités liées à la date courante, nous avons besoin d'un provider qui retourne une date arbitraire et fixe.

Pour notre test, imaginons que la date du jour soit le 12 juin 2022.

Une réservation sur le 02 juin 2022 est alors dans le passé de façon certaine. Peu importe la date à laquelle le test est exécuté.

const testDateProvider = {
    ...systemDateProvider,
    now : () => new Date('2023-06-12')
}

Ajoutons ce provider à nos dépendances de test.

export const testDependencies = {
    users : new MemoryUserRepository();
    bookings : new MemoryBookingRepository();
    dateProvider: testDateProvider
}

Et voilà. C'est comme si tout nos tests s'exécutaient indéfiniment le 12 juin 2023.

Ce problème de date du jour étant réglé, laissons le test nous guider pour affiner notre commande de réservation.