Amorçage de bases de données de microservices

10

Étant donné le service A (CMS) qui contrôle un modèle (produit, supposons que les seuls champs qu'il possède sont l'identifiant, le titre, le prix) et les services B (expédition) et C (e-mails) qui doivent afficher le modèle donné, quelle devrait être l'approche synchroniser les informations de modèle données sur ces services dans une approche de sourcing d'événements? Supposons que le catalogue de produits change rarement (mais change) et qu'il existe des administrateurs qui peuvent accéder très souvent aux données des envois et des e-mails (les fonctionnalités sont par exemple: B: display titles of products the order containedet C:) display content of email about shipping that is going to be sent. Chacun des services a sa propre base de données.

Solution 1

Envoyer toutes les informations requises sur le produit dans l'événement - cela signifie la structure suivante pour order_placed:

{
    order_id: [guid],
    product: {
        id: [guid],
        title: 'Foo',
        price: 1000
    }
}

Sur le service B et C, les informations sur les produits sont stockées dans l' productattribut JSON de la orderstable

Ainsi, pour afficher les informations nécessaires, seules les données extraites de l'événement sont utilisées.

Problèmes : en fonction des autres informations qui doivent être présentées en B et C, la quantité de données en cas d'événement peut augmenter. B et C peuvent ne pas nécessiter les mêmes informations sur le produit, mais l'événement devra contenir les deux (sauf si nous séparons les événements en deux). Si des données données ne sont pas présentes dans un événement donné, le code ne peut pas l'utiliser - si nous ajoutons une option de couleur à un produit donné, pour les commandes existantes en B et C, le produit donné sera incolore à moins que nous ne mettions à jour les événements et les réexécutions .

Solution 2

Envoyer uniquement le guide du produit dans l'événement - cela signifie la structure suivante pour order_placed:

{
    order_id: [guid],
    product_id: [guid]
}

Sur les services, les informations sur les produits B et C sont stockées dans l' product_idattribut de la orderstable

Les informations sur les produits sont récupérées par les services B et C lorsque cela est requis en effectuant un appel d'API au A/product/[guid]point de terminaison

Problèmes : cela rend B et C dépendants de A (à tout moment). Si le schéma du produit change sur A, des changements doivent être effectués sur tous les services qui en dépendent (soudainement)

Solution 3

Envoyer uniquement le guide du produit dans l'événement - cela signifie la structure suivante pour order_placed:

{
    order_id: [guid],
    product_id: [guid]
}

Sur les services B et C, les informations sur les produits sont stockées dans un productstableau; il y a toujours product_idsur la orderstable, mais il y a réplication des productsdonnées entre A, B et C; B et C peuvent contenir des informations sur le produit différentes de celles de A

Les informations sur les produits sont prédéfinies lorsque les services B et C sont créés et sont mises à jour chaque fois que les informations sur les produits changent en effectuant un appel au A/productpoint de terminaison (qui affiche les informations requises de tous les produits) ou en effectuant un accès DB direct à A et en copiant les informations nécessaires sur les produits requises pour des données données. un service.

Problèmes : cela rend B et C dépendants de A (lors de l'ensemencement). Si le schéma du produit change sur A, des changements doivent être effectués sur tous les services qui en dépendent (lors de l'amorçage)


D'après ma compréhension, l'approche correcte serait d'aller avec la solution 1, et de mettre à jour l'historique des événements selon une certaine logique (si le catalogue de produits n'a pas changé et que nous voulons ajouter une couleur à afficher, nous pouvons mettre à jour l'historique en toute sécurité pour obtenir l'état actuel des produits et remplir les données manquantes dans les événements) ou répondre à la non-existence de données données (si le catalogue de produits a changé et que nous voulons ajouter de la couleur à afficher, nous ne pouvons pas être sûrs si à ce moment-là dans le passé produit donné avait une couleur ou non - nous pouvons supposer que tous les produits du catalogue précédent étaient noirs et répondre en mettant à jour les événements ou le code)

eithed
la source
En ce qui concerne updating event history- Dans la recherche d'événements, l'historique des événements est votre source de vérité et ne doit jamais être modifié, mais seulement aller de l'avant. Si les événements changent, vous pouvez utiliser la version des événements ou des solutions similaires, mais lors de la relecture de vos événements jusqu'à un moment précis, l'état des données doit être tel qu'il était à ce moment-là.
Non
En ce qui concerne le stockage des données (schémas, etc.) pour les requêtes et les champs ajoutés / supprimés, etc., nous nous sommes utilisés cosmosDB pour stocker les données dans JSON telles qu'elles étaient à l'époque. La seule chose qui a alors besoin de version est les événements et / ou les commandes. Vous devez également mettre à jour les contrats de point final et les objets de valeur contenant les données répondant aux requêtes d'un client (web, mobile, etc ...). Les données plus anciennes n'ayant pas de champ auront une valeur par défaut ou vide, ce qui convient toujours à l'entreprise, mais l'historique des événements reste intact et ne fait que progresser.
Non
@ Non, updating event historyje veux dire: parcourez tous les événements, en les copiant d'un flux (v1) dans un autre flux (v2) pour maintenir un schéma d'événement cohérent.
eithed
En passant, dans le domaine du commerce / e-commerce, vous voudrez peut-être saisir le prix comme indiqué étant donné que les prix changent fréquemment. Un prix tel qu'il est affiché à l'utilisateur peut être différent au moment où la commande réelle est capturée. Il existe un certain nombre de façons de résoudre le problème, mais c'est une qui devrait être envisagée.
CPerson
@CPerson yup - le prix peut être l'un des attributs transmis dans l'événement lui-même. D'autre part, URL de l'image peut exister dans l' événement (représentant l' intention de display image at the point when purchase was made) ou ne peut pas (représentant l' intention de display current image as it within catalog)
eithed

Réponses:

3

La solution n ° 3 est vraiment proche de la bonne idée.

Une façon d'y penser: B et C mettent chacun en cache des copies "locales" des données dont ils ont besoin. Les messages traités en B (et également en C) utilisent les informations mises en cache localement. De même, les rapports sont produits à l'aide des informations mises en cache localement.

Les données sont répliquées de la source vers les caches via une API stable. B et C n'ont même pas besoin d'utiliser la même API - ils utilisent le protocole d'extraction approprié à leurs besoins. En effet, nous définissons un contrat - protocole et schéma de message - qui contraint le fournisseur et le consommateur. Ensuite, tout consommateur pour ce contrat peut être connecté à n'importe quel fournisseur. Les modifications incompatibles en amont nécessitent un nouveau contrat.

Les services choisissent la stratégie d'invalidation du cache appropriée à leurs besoins. Cela peut signifier extraire les modifications de la source sur une planification régulière, ou en réponse à une notification que les choses peuvent avoir changé, ou même "à la demande" - agissant comme une lecture dans le cache, retombant dans la copie stockée des données lorsque la source n'est pas disponible.

Cela vous donne une "autonomie", dans le sens où B et C peuvent continuer à offrir une valeur commerciale lorsque A est temporairement indisponible.

Lecture recommandée: Données sur l'extérieur, Données sur l'intérieur , Pat Helland 2005.

VoiceOfUnreason
la source
Oui, je suis entièrement d'accord avec ce que vous avez écrit ici et la solution 3 est la solution goto que j'ai appliquée, cependant, ce n'est pas l'approche de recherche d'événements, car, si nous allons rejouer les événements, nous ne voulons pas nécessairement utiliser l'état actuel du produit; nous voulons utiliser l'état tel qu'il était au moment de l'événement. Bien sûr, cela pourrait être bien (en fonction des besoins de l'entreprise). Si, mais nous voulons garder une trace des changements au catalogue, qui nécessite l' événement de sourcing ceux aussi bien, et dépend la quantité de données qui est, nous pourrions mieux retomber à la solution 1.
eithed
1
Je pense que vous l'avez avec la solution n ° 3. Si vous avez besoin de rejouer la cohérence avec le catalogue, source d'événement aussi. Vous n'avez besoin de relire que lorsque vous réamorcez, ce qui est probablement au démarrage - une fois que vous avez terminé, vous n'avez qu'à regarder les nouveaux événements, donc la quantité de données n'est probablement pas un vrai problème. Cependant, même dans ce cas, vous avez la possibilité (si nécessaire) d'utiliser des points de contrôle, c'est-à-dire "voici l' état à partir de l'événement 1 000", donc vous prenez cela et maintenant vous n'avez plus qu'à rejouer l'événement 1 001 à l'actuel au lieu de l'historique entier .
Mike B.
2

Il y a deux choses difficiles en informatique, et l'une d'elles est l'invalidation du cache.

La solution 2 est absolument ma position par défaut, et vous ne devriez généralement envisager d'implémenter la mise en cache que si vous exécutez l'un des scénarios suivants:

  1. L'appel d'API au service A entraîne des problèmes de performances.
  2. Le coût du service A étant en panne et incapable de récupérer les données est important pour l'entreprise.

Les problèmes de performances sont vraiment le principal moteur. Il existe de nombreuses façons de résoudre le problème n ° 2 qui n'impliquent pas la mise en cache, comme garantir que le service A est hautement disponible.

La mise en cache ajoute une complexité importante à un système et peut créer des cas marginaux difficiles à raisonner et des bogues très difficiles à répliquer. Vous devez également atténuer le risque de fournir des données périmées lorsque de nouvelles données existent, ce qui peut être bien pire d'un point de vue commercial que (par exemple) d'afficher un message indiquant que «le service A est en panne - veuillez réessayer plus tard».

De cet excellent article d'Udi Dahan:

Ces dépendances vous envahissent lentement, reliant vos lacets ensemble, ralentissant progressivement le rythme de développement, sapant la stabilité de votre base de code où les modifications apportées à une partie du système cassent d'autres parties. C'est une mort lente de mille coupures, et en conséquence, personne n'est exactement sûr de la grande décision que nous avons prise qui a rendu tout si mauvais.

De plus, si vous avez besoin d'une interrogation ponctuelle des données produit, cela doit être géré de la manière dont les données sont stockées dans la base de données produit (par exemple, les dates de début / fin), doit être clairement exposé dans l'API (la date effective doit être une entrée pour l'appel d'API pour interroger les données).

Phil Sandler
la source
1
@SavvasKleanthous "Le réseau est fiable" est l'une des erreurs de l'informatique distribuée. Mais la réponse à cette erreur ne devrait pas être "mettre en cache chaque bit de données de chaque service dans chaque autre service" (je me rends compte que c'est un peu hyperbolique). Attendez-vous à ce qu'un service ne soit pas disponible et traitez-le comme une condition d'erreur. Si vous avez une situation rare où la baisse du service A a un impact commercial majeur, alors (soigneusement!) Envisagez d'autres options.
Phil Sandler
1
@SavvasKleanthous considère également (comme je l'ai mentionné dans ma réponse) que le retour de données périmées dans de nombreux cas peut être bien pire que de lancer une erreur.
Phil Sandler
1
@eithed Je faisais référence à ce commentaire: "Si, toutefois, nous voulons garder une trace des modifications apportées au catalogue, cela nécessite également la recherche d'événements". Dans tous les cas, vous avez la bonne idée - le service Produit devrait être responsable du suivi des changements dans le temps, pas les services en aval.
Phil Sandler
1
En outre, le stockage des données que vous observez, bien qu'il présente certaines similitudes avec la mise en cache, ne présente pas les mêmes problèmes. Plus précisément, l'invalidation n'est pas nécessaire; vous obtenez la nouvelle version des données lorsqu'elle se produit. Ce que vous ressentez est une cohérence retardée. Cependant, même en utilisant une demande Web, il existe une fenêtre d'incohérence (bien que minuscule).
Savvas Kleanthous
1
@SavvasKleanthous En tout cas, mon point principal n'est pas d'essayer de résoudre des problèmes qui n'existent pas encore, en particulier avec des solutions qui apportent leurs propres problèmes et risques. L'option 2 est la solution la plus simple et devrait être le choix par défaut jusqu'à ce qu'elle ne réponde pas aux exigences de l'entreprise . Si vous pensez que le choix de la solution la plus simple qui peut fonctionner est (comme vous le dites) "vraiment mauvais", alors je pense que nous ne sommes pas d'accord.
Phil Sandler
2

Il est très difficile de dire simplement qu'une solution est meilleure que l'autre. Le choix d'une solution parmi les solutions 2 et 3 dépend d'autres facteurs (durée du cache, tolérance de cohérence, ...)

Mes 2 cents:

L'invalidation du cache peut être difficile mais l'énoncé du problème mentionne que le catalogue de produits change rarement. Ce fait fait des données produit un bon candidat pour la mise en cache

Solution n ° 1 (NOK)

  • Les données sont dupliquées sur plusieurs systèmes

Solution n ° 2 (OK)

  • Offre une forte cohérence
  • Fonctionne uniquement lorsque le service produit est hautement disponible et offre de bonnes performances
  • Si le service de messagerie électronique prépare un résumé (avec beaucoup de produits), le temps de réponse global pourrait être plus long

Solution n ° 3 (complexe mais préférée)

  • Préférez l'approche API au lieu d'un accès direct à la base de données pour récupérer les informations sur le produit
  • Résilient - les services consommateurs ne sont pas affectés lorsque le service produit est en panne
  • Les applications consommatrices (services d'expédition et de messagerie) récupèrent les détails du produit immédiatement après la publication d'un événement. La possibilité que le service produit baisse dans ces quelques millisecondes est très faible.
Sudhir
la source
1

De manière générale, je déconseille fortement l'option 2 en raison du couplage temporel entre ces deux services (sauf si la communication entre ces services est super stable et peu fréquente). Le couplage temporel est ce que vous décrivez this makes B and C dependant upon A (at all times)et signifie que si A est en panne ou inaccessible depuis B ou C, B et C ne peuvent pas remplir leur fonction.

Je crois personnellement que les options 1 et 3 ont toutes deux des situations où elles sont valides.

Si la communication entre A et B & C est si élevée, ou si la quantité de données nécessaires pour participer à l'événement est suffisamment importante pour en faire un problème, l'option 3 est la meilleure option, car la charge sur le réseau est beaucoup plus faible. et la latence des opérations diminue à mesure que la taille du message diminue. Les autres préoccupations à considérer ici sont:

  1. Stabilité du contrat: si le contrat du message quittant A changeait souvent, alors mettre beaucoup de propriétés dans le message entraînerait beaucoup de changements chez les consommateurs. Cependant, dans ce cas, je pense que ce n'est pas un gros problème car:
    1. Vous avez mentionné que le système A est un CMS. Cela signifie que vous travaillez sur un domaine stable et en tant que tel, je ne pense pas que vous verrez des changements fréquents
    2. Étant donné que le B et le C sont expédiés et envoyés par e-mail, et que vous recevez des données de A, je pense que vous subirez des changements additifs au lieu de les interrompre, qui peuvent être ajoutés en toute sécurité chaque fois que vous les découvrez sans retravailler.
  2. Couplage: Il y a très peu ou pas de couplage ici. D'abord puisque la communication se fait via des messages, il n'y a pas de couplage entre les services autre qu'un court temporel lors de l'ensemencement des données, et le contrat de cette opération (qui n'est pas un couplage que vous pouvez ou devez essayer d'éviter)

L'option 1 n'est pas quelque chose que je rejetterais cependant. Il y a la même quantité de couplage, mais en termes de développement, cela devrait être facile à faire (pas besoin d'actions spéciales), et la stabilité du domaine devrait signifier que celles-ci ne changeront pas souvent (comme je l'ai déjà mentionné).

Une autre option que je suggérerais est une légère variation à 3, qui ne consiste pas à exécuter le processus au démarrage, mais à la place observer un événement "ProductAdded et" ProductDetailsChanged "sur B et C, chaque fois qu'il y a un changement dans le catalogue de produits en A. Cela rendrait vos déploiements plus rapides (et donc plus faciles à résoudre un problème / bug si vous en trouvez).


Modifier 2020-03-03

J'ai un ordre de priorité spécifique pour déterminer l'approche d'intégration:

  1. Quel est le coût de la cohérence? Pouvons-nous accepter quelques millisecondes d'incohérence entre les choses changées dans A et celles qui se reflètent dans B & C?
  2. Avez-vous besoin de requêtes ponctuelles (également appelées requêtes temporelles)?
  3. Y a-t-il une source de vérité pour les données? Un service qui en est propriétaire et considéré en amont?
  4. S'il y a un propriétaire / une seule source de vérité, est-ce stable? Ou nous attendons-nous à voir de fréquents changements de rupture?

Si le coût de l'incohérence est élevé (en gros, les données de produit dans A doivent être cohérentes dès que possible avec le produit mis en cache en B et C), vous ne pouvez pas éviter d'avoir à accepter l'inviolabilité et de faire une demande synchrone (comme un site Web / reste demande) de B & C à A pour récupérer les données. Être conscient! Cela ne signifie toujours pas une cohérence transactionnelle, mais minimise simplement les fenêtres en cas d'incohérence. Si vous devez absolument et positivement être immédiatement cohérent, vous devez repenser vos limites de service. Cependant, je très crois fermement que ce ne devrait pas être un problème. Par expérience, il est en fait extrêmement rare que l'entreprise ne puisse pas accepter quelques secondes d'incohérence, vous ne devriez donc même pas avoir besoin de faire des demandes synchrones.

Si vous avez besoin de requêtes ponctuelles (ce que je n'ai pas remarqué dans votre question et que je n'ai donc pas inclus ci-dessus, peut-être à tort), le coût de la maintenance sur les services en aval est si élevé (vous devrez dupliquer logique de projection d'événement interne dans tous les services en aval) qui rend la décision claire: vous devez laisser la propriété à A et interroger une demande ad hoc sur le Web (ou similaire), et A doit utiliser la recherche d'événements pour récupérer tous les événements que vous connaissiez au moment de projeter à l'état, et de le renvoyer. Je suppose que cela peut être l'option 2 (si j'ai bien compris?), Mais les coûts sont tels que le couplage temporel est meilleur que le coût de maintenance des événements dupliqués et de la logique de projection.

Si vous n'avez pas besoin d'un moment précis et qu'il n'y a pas de propriétaire clair et unique des données (ce que dans ma réponse initiale, j'ai supposé en fonction de votre question), alors un schéma très raisonnable serait de tenir des représentations du produit dans chaque service séparément. Lorsque vous mettez à jour les données des produits, vous mettez à jour A, B et C en parallèle en faisant des demandes Web parallèles à chacun, ou vous disposez d'une API de commande qui envoie plusieurs commandes à chacun de A, B et C. B & C utilise leur version locale des données pour faire leur travail, qui peuvent être périmées ou non. Ce n'est pas l'une des options ci-dessus (même si elle pourrait être proche de l'option 3), car les données dans A, B et C peuvent différer, et "l'ensemble" du produit peut être une composition des trois données sources.

Savoir si la source de la vérité est un contrat stable est utile car vous pouvez l'utiliser pour utiliser le domaine / les événements internes (ou les événements que vous stockez dans votre sourcing d'événements comme modèle de stockage dans A) pour l'intégration entre A et les services B et C. Si le contrat est stable, vous pouvez l'intégrer via les événements du domaine. Cependant, vous avez alors une préoccupation supplémentaire dans le cas où les changements sont fréquents ou si le contrat de message est suffisamment important pour que le transport soit une préoccupation.

Si vous avez un propriétaire clair, avec un contrac qui devrait être stable, les meilleures options seraient l'option 1; une commande contiendrait toutes les informations nécessaires, puis B et C feraient leur fonction en utilisant les données de l'événement.

Si le contrat est susceptible de changer, ou de rompre souvent, suite à votre option 3, cela revient à des demandes Web pour récupérer les données produit est en fait une meilleure option, car il est beaucoup plus facile de maintenir plusieurs versions. B ferait donc une demande sur la v3 du produit.

Savvas Kleanthous
la source
Ouais, je suis d'accord. Bien que le suivi des modifications du catalogue de produits ProductAddedsoit ProductDetailsChangedplus complexe, nous devons en quelque sorte garder ces données synchronisées entre les bases de données, au cas où des événements seraient rejoués et nous aurions besoin d'accéder aux données du catalogue du passé.
eithed
@eithed J'ai mis à jour la réponse pour développer certaines hypothèses que j'ai faites.
Savvas Kleanthous