1
votes

Correction d'un état incohérent immédiatement ou paresseusement lorsque des données sont demandées

Nos utilisateurs passent par plusieurs étapes de flux de travail - plus ils vont loin, plus nous créons d'objets. Nous permettons également aux utilisateurs de revenir à l'étape 1 et de modifier l'un des objets existants. Ce qui peut entraîner des incohérences, nous devons donc mettre à jour / supprimer certains des objets à l'étape 2. Je vois 2 options:

  1. Mettez à jour / supprimez immédiatement les objets de l'étape 2. Cela mène à:

    • L'opération censée être un simple PATCH d'un champ d'entité devient compliquée. Et c'est un objet partagé entre plusieurs flux de travail - nous devrons donc ajouter des instructions if et faire des choses différentes en fonction du flux de travail.
    • Dépendances circulaires. Les opérations à l'étape 1 doivent connaître les objets / opérations à l'étape 2.
    • À chaque demande de l'étape n ° 1, nous devrons charger les données de l'étape n ° 2 afin de déterminer si l'étape n ° 2 doit vraiment être mise à jour. Ce qui ralentit les opérations à l'étape 1. Donc, pour changer 1 enregistrement dans DB, nous devrons charger des centaines (voire des milliers) d'enregistrements pour l'étape n ° 2.
    • De nombreuses actions à l'étape 1 peuvent nécessiter une correction de l'état à l'étape 2. Nous devons donc nous assurer de ne rien oublier aujourd'hui et à l'avenir.
  2. Correction de l'étape n ° 2 paresseusement - lorsque l'utilisateur s'y rend (notre approche actuelle). L'étape n ° 2 reconnaîtra que les objets sont incohérents et les corrigera. Ce qui conduit à un seul endroit où nous devons nous soucier, mais:

    • Jusqu'à ce que l'utilisateur ouvre Étape # 2 - DB contiendra des objets incohérents. Cela n'a entraîné aucun problème jusqu'à présent. Mais je peux imaginer que cela pourrait compliquer les futures migrations SQL.
    • Nous mettons à jour l'état de la base de données à la demande GET. Celui-ci ne semble pas être un gros problème puisque GET reste de toute façon idempotent. Mais c'est toujours gênant.

Quelqu'un connaît de meilleures approches? Ou peut-être des améliorations à ces deux?

Mise à jour

Je n'ai pas trouvé de solution parfaite, mais nous avons finalement implémenté une version améliorée de # 1. Lors de la mise à jour de l'état à l'étape 1, nous définissons également un indicateur «besoin de reconstruire l'étape 2», lorsque l'interface utilisateur ouvre l'étape 2, il vérifie d'abord cet indicateur et émet un PUT pour reconstruire l'état, puis seulement il obtient l'étape 2.

Cela signifie toujours que l'état de la base de données est incohérent pendant un certain temps. Mais au moins, nous le saurons avec certitude grâce au drapeau dans DB. Et si nécessaire, nous pourrions écrire des migrations en tenant compte de cet indicateur. Cela permet également (si nécessaire à l'avenir) de créer un travail asynchrone pour corriger l'état.


2 commentaires

Une troisième option: vous pouvez exécuter un travail par lots pendant la nuit pour réconcilier l'état de tout flux de travail modifié. Ensuite, vous obtenez une sorte de cohérence éventuelle dans la base de données.


@ jaco0646, bien l'utilisateur peut passer à l'étape 2 tout de suite, donc même si nous allons avec le travail, nous devrons être en mesure de corriger l'état lors du prochain GET. Bien que nous puissions combiner les approches et garantir que les expériences sont dans un état cohérent même si l'utilisateur a quitté la maison sans passer à l'étape suivante.


3 Réponses :


0
votes

Je pense qu'il est plus flexible de séparer l' état et le contexte où les objets sont stockés. Toute création d'un nouvel objet à n'importe quelle étape s'accompagne de la préservation de l'invariant et de la cohérence du contexte.

Il existe des règles d'états distinctes - ce sont des règles de transition de l'un à l'autre et des objets disponibles pour la création et des règles distinctes pour le contexte, des règles pour sa cohérence, qui est assurée à chaque fois qu'il change.


2 commentaires

Je ne sais pas comment cela se rapporte. Dans votre cas, vous avez un contexte qui doit garder les choses cohérentes. Le problème reste le même - "invoquez-vous" le contexte tout de suite pour rendre les choses cohérentes (et compliquer les opérations PUT / PATCH) ou faites-vous cela paresseusement lors du prochain GET?


Je veux dire que la modification des données ne doit pas se produire lors de l'extraction de données, mais lors de la création



0
votes

Qu'en est-il du nettoyage asynchrone des données sales?

  1. Chaque fois que l'utilisateur revient à l'étape 1 et change quelque chose, marquez toutes les données associées comme "sales" (par exemple, ajoutez des liens vers celle-ci dans la table "DirtyData") et c'est terminé pour le moment.
  2. Avoir un travailleur DataCleanup (par exemple, thread séparé ou smth) qui recherche constamment les données à nettoyer.
  3. Avant de modifier les données de l'étape 2, vérifiez si les données ne sont pas sales.

En fonction de votre logique, 3) peut entraîner une erreur de l'utilisateur (par exemple, l'utilisateur devra répéter l'étape 2). Si le travailleur DataCleanup a suffisamment de ressources (c'est-à-dire qu'il traite la table DirtyData presque instantanément), cela ne devrait se produire qu'en de très rares occasions. Si ce n'est pas OK, vous pouvez opter pour la vérification des données sales à chaque extraction, mais cela pourrait être coûteux.


1 commentaires

Les vérifications asynchrones sont excellentes si les données de l'étape 2 deviennent simplement obsolètes (nous ne les référencons pas) et sont faciles à déterminer à partir des colonnes de la base de données. Comme vous l'avez mentionné, cela devient un travail de nettoyage. Mais ma fonctionnalité est moins simple - par exemple, si quelque chose a été ajouté à l'étape 1, je devrais supprimer, mettre à jour ou ajouter des éléments à l'étape 2, et il est difficile de déterminer uniquement à partir des données de la base de données. Le travail de nettoyage n'est donc pas faisable. Exécuter des vérifications incorrectes à chaque extraction comme vous l'avez suggéré plus tard - est en fait une opération assez bon marché par rapport à d'autres choses à l'étape 2. C'est la raison pour laquelle nous l'avons choisi au départ.



0
votes

Il semble que vous soyez familier avec la spécification HTTP concernant les requêtes GET, mais pour les futurs lecteurs:

Pour l'autre puce sous 2, nous n'avons probablement pas besoin d'une spécification pour convenir que la persistance de données valides est préférable à la persistance de données invalides.

Alors, que pouvons-nous faire pour les puces sous 1 pour éviter une logique de branchement complexe dans une étape particulière et également des dépendances circulaires? Ma suggestion est une conception événementielle. Lorsque l'étape 2 change, elle doit déclencher un événement de modification. Dans ce scénario, l'étape 2 n'a aucune connaissance du ou des écouteurs concrets susceptibles de recevoir ses événements, elle reste donc découplée de toute logique de traitement complexe.

Il n'y a probablement aucun moyen de garantir que vous n'oublierez rien à l'avenir; mais si chaque étape du flux de travail est définie comme un écouteur, cela vous oblige à prendre en compte les événements de changement dans une certaine mesure chaque fois que vous implémentez une nouvelle étape.

Une note latérale sur la granularité: si une étape comporte de nombreux changements, elle peut regrouper ses événements plutôt que de déclencher chacun d'eux individuellement. Vous pouvez ajuster la taille pour plus d'efficacité.

En résumé, je considérerais fortement le modèle de conception Observer .


3 commentaires

Oui, Observer délie 2 fonctionnalités au moment de la compilation. Le seul inconvénient qui reste (et j'ai oublié de le mentionner) est que pour déterminer si l'étape n ° 2 a besoin de mises à jour, nous devons charger ses données. Cela signifie que peu importe si nous voulons réellement mettre à jour quelque chose à l'étape 2, nous devrons faire un travail supplémentaire qui ralentit toutes les opérations à l'étape 1. Eh bien, à moins que ces informations ne viennent de l'interface utilisateur ... Mais cela lie la fonctionnalité. À un niveau différent cependant.


Les mises à jour peuvent être asynchrones. Bien sûr, cela ajoute de la complexité en soi, mais il est possible d'éviter les performances affectées à l'étape 1.


La mise en cache est une autre solution au problème de performances et peut être plus simple que la logique asynchrone.