Données en retard : choisir ce que chaque opération peut accepter
Une donnée absente ou ancienne ne bloque pas tous les usages. Choisir, opération par opération, quand attendre, limiter le service ou accepter un retard.
Article
Deux clients achètent le dernier billet : comment éviter la double vente et envoyer la confirmation ? Transaction, concurrence et outbox expliquées pas à pas.
Deux clients essaient d'acheter le dernier billet. Le système doit accepter une seule vente, puis envoyer sa confirmation. Une transaction protège la décision locale. Une boîte d'envoi, ou outbox, permet ensuite de transmettre ce qui a été décidé sans perdre l'intention en cas de panne.
Dans CQRS, le modèle de commande reçoit les demandes de changement. Ici, sa règle est simple : ne pas vendre plus de billets qu'il n'en reste. Cette règle est un invariant.
Lire « un billet disponible » puis enregistrer une vente ne suffit pas. Deux demandes peuvent lire la même valeur avant que l'une écrive. Il faut coordonner la vérification et la modification.
Une transaction fournit le cadre, mais elle ne sérialise pas automatiquement tous les traitements. Selon la base et son niveau d'isolation, il faudra un verrou, une contrainte, une mise à jour conditionnelle ou un contrôle de version. Le mécanisme choisi doit empêcher les deux ventes de réussir.
Un agrégat regroupe les objets dont les règles doivent être protégées ensemble. Pour notre exemple, il pourrait porter le stock d'une représentation et les informations nécessaires à l'attribution d'un billet.
La frontière doit être assez large pour vérifier la règle, sans réunir inutilement tout le système. Un agrégat trop large augmente les conflits entre opérations qui auraient pu être indépendantes.
La recommandation de limiter une transaction à un agrégat appartient à une manière de concevoir le domaine. Elle demande de réfléchir aux règles réelles ; elle ne justifie pas de supprimer une validation indispensable parce qu'elle traverse la frontière choisie.
Supposons que le courriel parte, puis que l'enregistrement de la vente échoue. Le client reçoit une confirmation pour une vente absente de la base. Dans l'ordre inverse, une panne après l'enregistrement peut empêcher l'envoi.
Le service de messagerie ne participe généralement pas à la transaction locale. L'appeler entre deux écritures ne rend donc pas l'ensemble atomique, c'est-à-dire entièrement validé ou entièrement annulé.
Je recommande de garder les effets externes hors de cette transaction. Elle reste courte et sa réussite dépend des données qu'elle maîtrise. Les échanges avec l'extérieur sont organisés séparément.
Le pattern outbox ajoute une boîte d'envoi dans le même stockage transactionnel. La commande y note le message à transmettre en même temps qu'elle enregistre la vente.
Si la transaction échoue, ni la vente ni son message ne sont conservés. Si le processus d'envoi tombe ensuite, l'intention reste enregistrée et peut être reprise.
Le relais peut consulter périodiquement la table ou exploiter le journal de transactions. Ce choix technique ne change pas la propriété recherchée : conserver durablement le changement et l'intention de le diffuser.
Une panne peut survenir après l'envoi, mais avant que le relais ait enregistré sa réussite. À la reprise, il risque d'envoyer de nouveau le même message.
Le destinataire doit donc gérer les répétitions. Un identifiant stable permet, par exemple, de reconnaître une confirmation déjà traitée. C'est le rôle de l'idempotence.
L'outbox ne garantit pas que le destinataire sera toujours disponible ou acceptera tous les messages. Il faut suivre les éléments en attente, réessayer les erreurs temporaires et traiter les échecs persistants. Un message stocké mais jamais livré ne remplit pas le besoin.
Vérifier qu'un compte distant est actif, puis enregistrer localement une vente, laisse un intervalle pendant lequel le compte peut changer. La lecture distante apporte une information ; elle ne verrouille pas la règle entre les deux systèmes.
La réponse dépend du métier. Une réservation temporaire, un processus en plusieurs étapes, une compensation ou un changement de frontière peuvent être nécessaires. Si une contrainte doit être strictement garantie, il faut une coordination adaptée plutôt que l'ignorer.
Les autorisations demandent la même attention. Une vérification à l'entrée suffit pour certaines permissions stables. Si le droit dépend d'un état qui peut changer pendant l'opération, le contrôle doit être relié à la décision de façon cohérente.
Prenez une opération et listez ses effets : écritures locales, courriels, appels d'API et messages. Identifiez la règle qui doit être vraie au moment de la validation, puis les effets qui peuvent suivre.
Déplacez ces derniers dans une outbox lorsque le besoin s'y prête. Définissez ensuite la reprise, la gestion des doublons et le signalement des erreurs. Le client peut recevoir un identifiant et un statut précis, par exemple « vente enregistrée, confirmation en attente ».
Cette organisation ajoute un relais et un délai de propagation. Elle rend en revanche explicite ce qui a réussi et ce qui reste à faire. L'article suivant explique comment ces changements alimentent le modèle de lecture.
Explorer
Choisis dans l’index d’après les étiquettes de cet article — les plus proches d’abord, hors de sa série. Rien n’est écrit à la main : un article publié demain avec les mêmes étiquettes y prendra place.
Une donnée absente ou ancienne ne bloque pas tous les usages. Choisir, opération par opération, quand attendre, limiter le service ou accepter un retard.
CQRS distingue les règles qui valident une opération des données préparées pour les vues. Une commande client explique les deux modèles et leur synchronisation.
Dans un monolithe, un traitement différé peut laisser deux modules en décalage. Quand faut-il attendre une réponse et comment gérer une mise à jour tardive ?
Laisser un commentaire
Votre commentaire sera relu avant d'être publié.