Quel est le meilleur moyen de parvenir à la cohérence des bases de données dans les systèmes basés sur microservices?
Au GOTO in Berlin , Martin Fowler parlait de microservices et l'une des "règles" qu'il mentionnait était de conserver les bases de données "par service", ce qui signifie que les services ne peuvent pas se connecter directement à une base de données "détenue" par un autre. un service.
C'est super beau et élégant, mais dans la pratique, cela devient un peu délicat. Supposons que vous ayez quelques services:
Maintenant, un client effectue un achat sur votre interface, qui appellera le service de gestion des commandes, ce qui enregistrera tout dans la base de données - aucun problème. À ce stade, il sera également fait appel au service de programme de fidélité pour qu'il crédite/débite des points de votre compte.
Désormais, lorsque tout est sur le même serveur de base de données/base de données, tout devient facile, car vous pouvez tout exécuter en une seule transaction: si le service de programme de fidélité n'écrit pas dans la base de données, nous pouvons tout restaurer.
Lorsque nous effectuons des opérations de base de données sur plusieurs services, cela n’est pas possible car nous ne comptons pas sur une seule connexion ni ne tirons parti de l’exécution d’une seule transaction. Quels sont les meilleurs modèles pour que les choses restent cohérentes et vivent heureux? la vie?
J'ai hâte d'entendre vos suggestions! ... et merci d'avance!
C'est super beau et élégant mais en pratique ça devient un peu délicat
En pratique, cela signifie que vous devez concevoir vos microservices de manière à assurer la cohérence métier nécessaire lorsque vous respectez la règle:
ces services ne peuvent pas se connecter directement à une base de données "détenue" par un autre service.
En d’autres termes, ne formulez aucune hypothèse sur leurs responsabilités et modifiez les limites au besoin jusqu’à ce que vous trouviez le moyen de faire en sorte que cela fonctionne.
Maintenant, à votre question:
Quels sont les meilleurs modèles pour garder les choses cohérentes et vivre une vie heureuse?
Pour les choses qui n'exigent pas une cohérence immédiate, et la mise à jour des points de fidélité semble appartenir à cette catégorie, vous pouvez utiliser un modèle pub/sous fiable pour répartir les événements d'un microservice à traiter par d'autres. Le bit le plus fiable est que vous souhaitiez de bonnes tentatives, une annulation et une idempotence (ou une transactionnalité) pour le traitement des événements.
Si vous utilisez .NET, voici quelques exemples d’infrastructures prenant en charge ce type de fiabilité: NServiceBus et MassTransit . Divulgation complète - Je suis le fondateur de NServiceBus.
Mise à jour: Suite aux commentaires concernant les points de fidélité: "si les mises à jour du solde sont traitées avec un retard, un client peut en réalité commander plus d’articles que de points".
Beaucoup de gens ont du mal à respecter ce type d’exigence de cohérence. Le fait est que ce type de scénario peut généralement être traité en introduisant des règles supplémentaires, par exemple si un utilisateur se voit notifier des points de fidélité négatifs. Si T passe sans que les points de fidélité soient résolus, informez-en que des frais seront facturés à M en fonction d'un taux de conversion. Cette politique doit être visible pour les clients lorsqu'ils utilisent des points pour acheter des objets.
Je ne traite généralement pas de microservices, et cela n’est peut-être pas une bonne façon de faire les choses, mais voici une idée:
Pour reformuler le problème, le système est constitué de trois parties indépendantes mais communicantes: le client, le serveur de gestion des commandes et le programme de fidélité. Le client veut s'assurer qu'un état est sauvegardé à la fois dans le backend de gestion des commandes et dans le backend du programme de fidélité.
Une solution possible serait d'implémenter un type de commit en deux phases :
Si cela est mis en œuvre, les modifications ne seront pas nécessairement atomic , mais elles seront éventuellement cohérentes . Pensons aux endroits où il pourrait échouer:
Même pour les transactions distribuées, vous pouvez entrer dans «statut en cas de doute» si l'un des participants se bloque au milieu de la transaction. Si vous concevez les services comme une opération idempotente, la vie devient un peu plus facile. On peut écrire des programmes pour remplir les conditions commerciales sans XA. Pat Helland a écrit un excellent article sur ce sujet intitulé "Life Beyond XA". En gros, l’approche consiste à formuler des hypothèses aussi minimes que possible sur les entités distantes. Il a également illustré une approche appelée Open Nested Transactions ( http://www.cidrdb.org/cidr2013/Papers/CIDR13_Paper142.pdf ) pour modéliser les processus métier. Dans ce cas particulier, la transaction d'achat serait un flux de premier niveau et la gestion de la fidélité et des commandes serait des flux de prochain niveau. L'astuce consiste à créer des services granulaires en tant que services idempotents avec une logique de compensation. Ainsi, si quelque chose échoue n'importe où dans le flux, des services individuels peuvent le compenser. Donc, par exemple Si la commande échoue pour une raison quelconque, la fidélité peut déduire le point accumulé pour cet achat.
Une autre approche consiste à modéliser en utilisant une cohérence éventuelle en utilisant une méthode CALM ou CRDT. J'ai écrit un blog pour mettre en évidence l'utilisation de CALM dans la vie réelle - http://shripad-agashe.github.io/2015/08/Art-Of-Disorder-Programming Peut-être que cela vous aidera.
Je suis d'accord avec ce que @Udi Dahan a dit. Je veux juste ajouter à sa réponse.
Je pense que vous devez maintenir la demande auprès du programme de fidélisation afin qu’elle échoue à un autre moment. Il y a différentes façons de Word/faire ceci.
1) Rendez l’échec de l’API du programme de fidélité récupérable. C'est-à-dire qu'il peut persister dans les requêtes afin qu'elles ne soient pas perdues et puissent être récupérées (réexécutées) ultérieurement.
2) Exécutez les demandes du programme de fidélité de manière asynchrone. C'est-à-dire, persistez la demande quelque part d'abord, puis autorisez le service à la lire à partir de ce magasin persistant. Ne supprimez du magasin persistant que si l'exécution est réussie.
3) Faites ce que Udi a dit et placez-le dans une bonne file d'attente (modèle de publication/sous pour être exact). Cela nécessite généralement que l’abonné fasse l’une des deux choses suivantes: persistez la demande avant de la retirer de la file (goto 1) --OU-- commencez par emprunter la demande de la file d’attente, puis, après avoir traité la demande, avec succès retiré de la file d'attente (c'est ma préférence).
Tous trois accomplissent la même chose. Ils déplacent la demande vers un endroit conservé où elle peut être traitée jusqu'à son achèvement. La demande n'est jamais perdue et réessayée si nécessaire jusqu'à atteindre un état satisfaisant.
J'aime utiliser l'exemple d'une course de relais. Chaque service ou morceau de code doit s'emparer de la demande avant de permettre à l'ancien morceau de code de le lâcher. Une fois le transfert effectué, le propriétaire actuel ne doit pas perdre la demande tant qu'elle n'a pas été traitée ou transférée à un autre élément de code.