É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 contained
et 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' product
attribut JSON de la orders
table
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_id
attribut de la orders
table
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 products
tableau; il y a toujours product_id
sur la orders
table, mais il y a réplication des products
donné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/product
point 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)
la source
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à.updating event history
je 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.display image at the point when purchase was made
) ou ne peut pas (représentant l' intention dedisplay current image as it within catalog
)Réponses:
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.
la source
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:
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:
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).
la source
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)
Solution n ° 2 (OK)
Solution n ° 3 (complexe mais préférée)
la source
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:
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:
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.
la source
ProductAdded
soitProductDetailsChanged
plus 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é.