Comment gérer les effets secondaires dans Event Sourcing?

14

Supposons que nous voulons implémenter un petit sous-système de sécurité pour une application financière qui avertit les utilisateurs par e-mail si un modèle étrange est détecté. Pour cet exemple, le modèle consistera en trois transactions comme celles illustrées. Le sous-système de sécurité peut lire les événements du système principal à partir d'une file d'attente.

Ce que j'aimerais obtenir, c'est une alerte qui est une conséquence directe des événements qui se produisent dans le système, sans représentation intermédiaire qui modélise l'état actuel du modèle.

  1. Surveillance activée
  2. Transaction traitée
  3. Transaction traitée
  4. Transaction traitée
  5. Alerte déclenchée (id: 123)
  6. E-mail d'alerte envoyé (pour id: 123)
  7. Transaction traitée

Dans cette optique, je pensais que le sourcing d'événements pouvait très bien s'appliquer ici, même si j'ai une question sans réponse claire. L'alerte déclenchée dans l'exemple a un effet secondaire clair, un e-mail doit être envoyé, une circonstance qui ne devrait se produire qu'une seule fois. Par conséquent, cela ne devrait pas se produire lors de la relecture de tous les événements d'un agrégat.

Dans une certaine mesure, je vois l'e-mail qui doit être envoyé similaire aux matérialisations générées par le côté requête que j'ai vu tant de fois dans la littérature sur le sourcing CQRS / Event, avec une différence pas si subtile cependant.

Dans cette littérature, le côté requête est construit à partir de gestionnaires d'événements qui peuvent générer une matérialisation de l'état à un point donné en relisant tous les événements. Dans ce cas, cependant, cela ne peut pas être accompli exactement comme ça pour les raisons expliquées précédemment. L'idée que chaque état est transitoire ne s'applique pas si bien ici . Nous devons enregistrer le fait qu'une alerte a été envoyée quelque part.

Une solution simple pour moi serait d'avoir une table ou une structure différente où vous gardez des enregistrements des alertes qui ont été déclenchées précédemment. Comme nous avons une pièce d'identité, nous pourrions vérifier si une alerte avec la même pièce d'identité a été émise auparavant. La possession de ces informations rendrait le SendAlertCommand idempotent. Plusieurs commandes peuvent être émises, mais l'effet secondaire ne se produira qu'une seule fois.

Même en ayant cette solution à l'esprit, je ne sais pas si c'est un indice qu'il y a quelque chose de mal avec cette architecture pour ce problème.

  • Mon approche est-elle correcte?
  • Y a-t-il un endroit où je peux trouver plus d'informations à ce sujet?

Il est étrange que je n'ai pas pu trouver plus d'informations à ce sujet. J'ai peut-être utilisé une mauvaise formulation.

Merci beaucoup!

Jacob
la source

Réponses:

12

Comment gérer les effets secondaires dans Event Sourcing?

Version courte: le modèle de domaine n'effectue pas d'effets secondaires. Il les suit . Les effets secondaires sont effectués en utilisant un port qui se connecte à la frontière; lorsque l'e-mail est envoyé, vous renvoyez l'accusé de réception au modèle de domaine.

Cela signifie que l'e-mail est envoyé en dehors de la transaction qui met à jour le flux d'événements.

C'est précisément là où, à l'extérieur, c'est une question de goût.

Donc, conceptuellement, vous avez un flux d'événements comme

EmailPrepared(id:123)
EmailPrepared(id:456)
EmailPrepared(id:789)
EmailDelivered(id:456)
EmailDelivered(id:789)

Et à partir de ce flux, vous pouvez créer un pli

{
    deliveredMail : [ 456, 789 ],
    undeliveredMail : [123]
}

Le pli vous indique quels e-mails n'ont pas été reconnus, vous les renvoyez donc:

undeliveredMail.each ( mail -> {
    send(mail);
    dispatch( new EmailDelivered.from(mail) );
}     

En fait, il s'agit d'un commit en deux phases: vous modifiez SMTP dans le monde réel, puis vous mettez à jour le modèle.

Le modèle ci-dessus vous donne un modèle de livraison au moins une fois. Si vous voulez au plus une fois, vous pouvez le retourner

undeliveredMail.each ( mail -> {
    commit( new EmailDelivered.from(mail) );
    send(mail);
}     

Il existe une barrière de transaction entre la durabilité d'EmailPrepared et l'envoi de l'e-mail. Il existe également une barrière de transaction entre l'envoi de l'e-mail et la durabilité d'EmailDelivered.

La messagerie fiable d' Udi Dahan avec les transactions distribuées peut être un bon point de départ.

VoiceOfUnreason
la source
2

Vous devez séparer les «événements de changement d'état» des «actions»

Un événement de changement d'état est un événement qui modifie l'état de l'objet. Ce sont ceux que vous stockez et rejouez.

Une action est quelque chose que l'objet fait à d'autres choses. Ceux-ci ne sont pas stockés dans le cadre de l'événement Sourcing.

Pour ce faire, vous pouvez utiliser des gestionnaires d'événements, que vous câblez ou non selon que vous souhaitez exécuter les actions.

public class Monitor
{
    public EventHander SoundAlarm;
    public void MonitorEvent(Event e)
    {
        this.eventcount ++;
        if(this.eventcount > 10)
        {
             this.state = "ALARM!";
             if(SoundAlarm != null) { SoundAlarm();}
        }
    }
}

Maintenant, dans mon service de surveillance, je peux avoir

public void MonitorServer()
{
    var m = new Monitor(events); //11 events
    //alarm has not been sounded because the event handler wasn't wired up
    //but the internal state is correctly set to "ALARM!"
    m.SoundAlarm += this.SendAlarmEmail;
    m.MonitorEvent(e); //email is sent
}

Si vous devez enregistrer les e-mails envoyés, vous pouvez le faire dans le cadre de SendAlarmEmail. Mais ce ne sont pas des événements au sens de Event Sourcing

Ewan
la source