Aller au contenu principal

Eventual Consistency

:::warning Attention à la traduction L'Eventual Consistancy se traduit par Cohérence Finale car, à la fin de la saga, les données SONT cohérentes entre elles, soit parce que tout s'est bien passé, soit parce qu'il y a eu compensation afin de maintenir, in fine, lesdites données :::

Remember that we try to identify aggregates i.e we're actively looking for areas, islands that need to be immediately consistent in an ocean of eventual consistency. [...] And maybe that's one of the biggest things with DDD, the revelation that a model doesn't really need to always be immediately consistent, like in the CRUD approach; some parts need it, others don't. 1

  • Chaque agrégat reste toujours valide en interne
  • Les agrégats ne sont pas forcément cohérents entre eux immédiatement
  • La cohérence globale est atteinte plus tard (eventual consistency)

Exemple

Règle métier

  • Une Commande peut être créée immédiatement
  • La réservation de stock se fait asynchronement
  • Pendant un court laps de temps, une commande peut exister sans stock réservé : C’est acceptable pour le métier

Agrégat 1 : Order (Commande)

public class Order {

private final UUID orderId;
private final UUID productId;
private final int quantity;
private OrderStatus status;

public OrderCreatedEvent createEvent() {
return new OrderCreatedEvent(orderId, productId, quantity);
}

public void markAsConfirmed() {
this.status = OrderStatus.CONFIRMED;
}

public void markAsRejected() {
this.status = OrderStatus.REJECTED;
}
}

Agrégat 2 : Inventory (Stock)

public class Inventory {

private final UUID productId;
private int availableQuantity;

public boolean reserve(int quantity) {
if (availableQuantity >= quantity) {
availableQuantity -= quantity;
return true;
}
return false;
}
}

ApplicationService

public class OrderService {
public void placeOrder(UUID productId, int quantity) {
Order order = new Order(UUID.randomUUID(), productId, quantity);

orderRepository.save(order); // transaction locale
eventBus.publish(order.createEvent()); // async
}
}
public class InventoryEventHandler {
public void on(OrderCreatedEvent event) {
Inventory inventory = inventoryRepository.findByProductId(event.productId());
Order order = orderRepository.findById(event.orderId());

if (inventory.reserve(event.quantity())) {
inventoryRepository.save(inventory);
order.markAsConfirmed();
} else {
order.markAsRejected();
}

orderRepository.save(order);
}
}

Ce qui est temporairement incohérent (et acceptable)

MomentCommandeStock
t0CREATEDNon réservé
t1CONFIRMEDRéservé

Avec deux commande en même temps

Situation de départ

  • Stock du produit P = 5
  • Deux commandes arrivent quasi simultanément
    • Commande A : quantité 3
    • Commande B : quantité 3

t0 – Réception des commandes

Order A → CREATED
Order B → CREATED
Stock → 5

t1 – Publication des événements

OrderCreatedEvent(A)
OrderCreatedEvent(B)

Les événements partent dans le bus (Kafka, RabbitMQ, etc.)

  • Aucun ordre garanti
  • Possibilité de traitement concurrent

t1 - Traitement séquentiel

A traité en premier puis B

Stock = 5
Reserve 3 → OK
Stock = 2
Order A → CONFIRMED

Stock = 2
Reserve 3 → FAIL
Order B → REJECTED

=> Acceptable si le métier l’accepte

Footnotes

  1. DDD Decoded - Don't Fear Eventual Consistency

X

Graph View