Comment gérer les effets secondaires dans CRQS lors de la relecture d'événements?

10

On dit que dans CQRS, il est facile de corriger un bug, il vous suffit de redéployer puis de rejouer les événements.

Mais, que se passe-t-il si l'un des événements devait faire en sorte qu'un système externe hors de votre contrôle "expédie un article" au client si vous rejouiez simplement les événements, l'article serait expédié deux fois.

Comment résolvez-vous cela?

Jas
la source

Réponses:

6

Vous devez faire une séparation claire entre les événements modifiant l'état de votre modèle de lecture et les événements (potentiellement) modifiant l'état des systèmes externes. Assurez-vous que vous n'avez pas "d'événements mixtes" modifiant les deux états ensemble. De cette façon, vous pouvez rejouer vos événements dans un "mode de relecture" spécifique où ces événements pour le système externe ne sont plus déclenchés. Dans ce mode, vous "simulez" également tous les événements qui avaient été initialement déclenchés par le système externe (vous les récupérez maintenant dans la file d'attente de relecture).

N'oubliez pas, l'étape de redéploiement signifie en fait de réinitialiser l'état du modèle de lecture à un point antérieur dans le temps. Ce n'est probablement rien que vous puissiez faire (ou devez faire) pour l'état des systèmes externes.

Doc Brown
la source
"N'oubliez pas, l'étape de redéploiement signifie en fait de réinitialiser l'état du modèle de lecture à un point antérieur dans le temps. Ce n'est probablement rien que vous puissiez faire (ou devez faire) pour l'état des systèmes externes." -> mais que se passe-t-il si je veux que ma relecture réessaye les appels système externes échoués comme l'expédition? dans ce cas, mon redéploiement d'une relecture non seulement réinitialiserait l'état du modèle de lecture mais provoquerait également des événements externes, cela semble-t-il juste ou il me manque quelque chose?
Jas
2
@Jas: vous ne voulez pas abuser de la "relecture" pour réessayer un appel système externe ayant échoué. Vous utilisez le "replay" pour obtenir le modèle de lecture de votre propre système dans le même état qu'auparavant. Cela signifie qu'en cas d'échec d'une demande d'expédition, votre système a déjà été informé de cet échec et a stocké ces informations quelque part dans son état. La relecture s'assure que ces informations sont toujours là après "redéployer et relire". Ainsi, après la relecture, votre système pourrait appliquer une stratégie de «réessai d'expédition en cas d'échec» (ce qui n'a rien à voir avec CQRS, tout système de commande robuste devrait simplement avoir une telle stratégie).
Doc Brown
Intéressant, c'est ce que j'avais à l'esprit de faire, je me demandais juste s'il y avait un "motif" là-dessus, donc je ne réinvente pas la roue!
Jas
3

Extrait de l'article de Martin Fowler sur la recherche d' événements :

L'idée fondamentale d'Event Sourcing est de garantir que chaque modification de l'état d'une application est capturée dans un objet d'événement et que ces objets d'événement sont eux-mêmes stockés dans la séquence dans laquelle ils ont été appliqués pendant la même durée de vie que l'état d'application lui-même.

Ainsi, lorsque vous devez restaurer l'état de votre système à un certain moment, vous rejouez l' état stocké , et non les gestionnaires d'événements, jusqu'à ce moment.

Cela étant dit, si vous travaillez uniquement avec des données d'état, il ne devrait y avoir aucun effet sur le système externe. Sauf si vous avez des déclencheurs ou des observateurs sur votre magasin d'événements, auquel cas vous devez les désactiver pendant la durée de la restauration. Puisque vous dites que vous n'avez aucun contrôle sur le système externe, il ne devrait y avoir aucune tentative de restaurer son état en utilisant l'API exposée, car vous ne savez pas quels effets secondaires il peut avoir dans leur système. Si la restauration place le système dans un état intermédiaire (par exemple en raison d'opérations ayant échoué dans le système externe), cela ne devrait pas relever des responsabilités d'une relecture d'événement.

devnull
la source
2

Mais, que se passe-t-il si l'un des événements devait faire en sorte qu'un système externe hors de votre contrôle "expédie un article" au client si vous rejouiez simplement les événements, l'article serait expédié deux fois.

Pour choisir un exemple spécifique, considérons comment une approche «au moins une fois» des effets secondaires pourrait fonctionner.

State currentState = State.InitialState
for(Event e : events) {
    currentState = currentState.apply(e)
}
for(SideEffect s : currentState.querySideEffects()) {
    performSideEffect(s)

Le modèle de domaine suit donc ce qui doit être fait; mais laisse la tâche réelle à l'application

Dans le contexte de l'exécution d'une commande, l'idée de base est la même. Les effets secondaires réels se produisent en dehors de la transaction qui met à jour le modèle.

Ainsi, les tests unitaires de votre modèle pourraient ressembler à quelque chose

{
    // Given
    State currentState = State.InitialState

    // When
    Events events = List.of(OrderPlaced)

    // Then
    List.of(SendEmail) === currentState.applyAll(events).querySideEffects()
}

{
    // Given
    State currentState = State.InitialState

    // When
    Events events = List.of(OrderPlaced, EmailSent)

    // Then
    List.EMPTY === currentState.applyAll(events).querySideEffects()
}

Les principaux points ici étant

  • La mise à jour du modèle est sans effet secondaire; les effets secondaires réels se produisent en dehors de la transaction qui met à jour le modèle.
  • Un événement décrivant le résultat de l'effet secondaire doit revenir.
VoiceOfUnreason
la source