📐 Construire une app en JS
06 Transaction

L'intégrité

Reprenons notre commande de réservation book().

Elle provoque une modification de l'état du système, puisqu'elle modifie le calendrier des réservations de l'hébergement.

Mais qu'est-ce qu'elle ne modifie pas ?

Bah tout le reste.

Mais posons la question autrement : qu'est-ce qui doit rester exactement pareil lors du procesus de réservation ?

Les entités qui doivent être inchangées

Pour que le processus de réservation n'enfreigne pas les règles métiers, certaines entités doivent deumerer inchangées au cours du traitement de la réservation.

  1. L'hébergement Si le nombre de chambres de l'hébergement est modifié pendant la réservation, on risque d'accepter une réservation pour 6 personnes alors que l'hébergement vient d'être modifié pour accueillir que 4.

  2. Le locataire Si l'administrateur décide de geler le compte utilisateur du locataire (parce qu'il a eu un litige avec une précédente réservation), on ne veut pas d'une nouvelle réservation.

  3. Le calendrier initial Si une autre réservation est réalisée simultanément par un autre locataire, on risque d'avoir 2 réservations sur la même période.

// Avant : le calendrier est vide
const initialCalendar = Calendar.of(accomodation)
expect(initialCalendar.count()).toBe(0)
 
// 2 locataires différents tentent une réservation simultanée
const bookings = await Promise.all(
    book(accomodation, tenant1, stay),
    book(accomodation, tenant2, stay),
)
 
// 1 seule réservation est acceptée.
const alteredCalendar = Calendar.of(accomodation)
expect(alteredCalendar.count()).toBe(1)

Commande et transaction

La plupart des développeurs se désintéressent de cette question parce que les cas sont très rares. Il est très improbable d'avoir une modification de l'hébergement ou du locataire exactement au milieu d'un processus de réservation.

Très improbable, tant que le système est peu sollicité.

Si par chance vous êtes victime de votre succès, et que les réservations pleuvent, alors les bugs pleuvent aussi. Des bugs difficiles à reproduire (donc à corriger) parce qu'ils surviennent de façon aléatoire.

Pourtant, ça n'est pas compliqué de s'en préoccuper. Il suffit d'isoler le périmètre des données nécessaires à l'exécution du traitement.

Pendant la réservation d'un hébergement par un locataire, le système doit vérouiller tout modification du calendrier, de l'hébergement et du locataire. On nomme parfois ce périmètre comme un aggrégat.

Pour empêcher la modification de l'aggrégat, on doit l'isoler au travers d'une transaction.

La transaction possède un début et une fin. Elle encapsule la commande afin d'isoler les entités de son aggrégat.

Implémentation d'un système de transaction

Le développeurs a 3 choix pour aborder cette question de l'isolation :

  1. Il ne le prend pas en compte (volontairement). Parfois, ça n'est juste pas grave d'avoir une incohérence. Pour sa probabilité d'occurence reste très faible.

  2. déléguer la question à la base de données relationnelle. Les bases de données implémentent un mécanisme de transaction. Dans Postgresql, on dispose par exemple de 3 options selon le niveau d'isolation souhaité :

  • read commited
  • repeatable read
  • serializable

Ce niveau est paramétrable pour chaque transaction. Il doit faire l'objet d'un compromis : plus le niveau d'isolation est élevé, plus le risque d'échec de la commande augmente.

  1. Coder un sémaphore En Javascript, la boucle d'événements (event loop) est mono-thread. Donc on peut facilement coder une liste globale des hébergements en cours de réservation.

La commande débute par le contrôle de cette liste (y-a-t-il déjà le même hébergement en cours de réservation ?), puis l'ajoute immédiatement sans susprendre l'exécution. A la fin de la commande, on retire l'hébergement de la liste.

Lorsque le système grossit et que l'on multiplie les processus du backend, on doit recourir au sharding pour assurer que les réservation d'un hébergement sont toujours gérées par le même noeud.

https://www.postgresql.org/docs/current/transaction-iso.html (opens in a new tab)