EventSourced Saga Implementierung

Ich habe ein Ereignisquellen-Aggregat geschrieben und jetzt eine Ereignisquellen-Saga implementiert ... Ich habe festgestellt, dass beide gleich sind und ein Ereignisquellen-Objekt als Basisklasse erstellt, von der beide abgeleitet sind.

Ich habe eine Demo hier gesehenhttp: //blog.jonathanoliver.com/cqrs-sagas-with-event-sourcing-part-ii-of-ii aber das Gefühl, dass es ein Problem geben könnte, da Befehle im Falle eines Prozessabsturzes verloren gehen könnten, da das Senden von Befehlen außerhalb der Schreibtransaktion liegt?

public void Save(ISaga saga)
{
    var events = saga.GetUncommittedEvents();
    eventStore.Write(new UncommittedEventStream
    {
        Id = saga.Id,
        Type = saga.GetType(),
        Events = events,
        ExpectedVersion = saga.Version - events.Count
    });

    foreach (var message in saga.GetUndispatchedMessages())
        bus.Send(message); // can be done in different ways

    saga.ClearUncommittedEvents();
    saga.ClearUndispatchedMessages();
}

Stattdessen verwende ich Greg Youngs EventStore. Wenn ich ein EventSourcedObject (entweder ein Aggregat oder eine Saga) speichere, sieht die Sequenz folgendermaßen aus:

Repository ruft eine Liste der neuen MutatingEvents ab. Schreibt sie zum Streaming.EventStore löst neue Ereignisse aus, wenn Streams in den Stream geschrieben und für ihn festgeschrieben werden.Wir warten auf die Ereignisse aus dem EventStore und behandeln sie in EventHandlern.

Ich implementiere die beiden Aspekte einer Saga:

Um @ aufzunehmVeranstaltunge, das kannÜbergangszustan, was wiederum kannemit Befehle.Um ein @ zu habAlar wo wir irgendwann in der Zukunft (über einen externen Zeitgeberdienst) zurückgerufen werden können.

Frage

Wie ich es verstehe Event-Handler sollten keine Befehle ausgeben (Was passiert, wenn der Befehl fehlschlägt?) - aber bin ich damit einverstanden, da die Saga das eigentliche Element ist, das die Erstellung von Befehlen (als Reaktion auf Ereignisse) über dieses @ steuerVeranstaltun proxy und alle Fehler beim Senden von Befehlen können extern behandelt werden (in dem externen EventHandler, der sich mit @ befasCommandEmittedFromSaga und sendet erneut, wenn der Befehl fehlschlägt)?

der vergesse ich das Umschließen von Ereignissen und speichere nativeCommands undEvents im selben Stream (gemischt mit einer Basisklasse-Nachricht - die Saga würde sowohl Befehle als auch Ereignisse verbrauchen, ein Aggregat würde nur Ereignisse verbrauchen)?

Jedes andere Referenzmaterial im Internet zur Umsetzung von Event-Saga? Kann ich meine Ideen auf Herz und Nieren prüfen?

Einiger Hintergrundcode ist unten.

Saga gibt einen Befehl zum Ausführen aus (eingeschlossen in ein CommandEmittedFromSaga-Ereignis)

Befehl unten ist in Ereignis eingeschlossen:

public class CommandEmittedFromSaga : Event
{
    public readonly Command Command;
    public readonly Identity SagaIdentity;
    public readonly Type SagaType;

    public CommandEmittedFromSaga(Identity sagaIdentity, Type sagaType, Command command)
    {
        Command = command;
        SagaType = sagaType;
        SagaIdentity = sagaIdentity;
    }
}

Saga fordert zu einem späteren Zeitpunkt einen Rückruf an (AlarmRequestedBySaga-Ereignis)

Alarm-Rückrufanforderung wird in einem Ereignis eingeschlossen und wird am oder nach dem angeforderten Zeitpunkt an die Saga zurückgesendet:

public class AlarmRequestedBySaga : Event
{
    public readonly Event Event;
    public readonly DateTime FireOn;
    public readonly Identity Identity;
    public readonly Type SagaType;

    public AlarmRequestedBySaga(Identity identity, Type sagaType, Event @event, DateTime fireOn)
    {
        Identity = identity;
        SagaType = sagaType;
        Event = @event;
        FireOn = fireOn;
    }
}

Alternativ kann ich sowohl Befehle als auch Ereignisse im selben Stream des Basistyps Message @ speicher

public abstract class EventSourcedSaga
{
    protected EventSourcedSaga() { }

    protected EventSourcedSaga(Identity id, IEnumerable<Message> messages)
    {
        Identity = id;

        if (messages == null) throw new ArgumentNullException(nameof(messages));

        var count = 0;

        foreach (var message in messages)
        {
            var ev = message as Event;
            var command = message as Command;

            if(ev != null) Transition(ev);
            else if(command != null) _messages.Add(command);
            else throw new Exception($"Unsupported message type {message.GetType()}");

            count++;
        }

        if (count == 0)
            throw new ArgumentException("No messages provided");

        // All we need to know is the original number of events this
        // entity has had applied at time of construction.
        _unmutatedVersion = count;
        _constructing = false;
    }

    readonly IEventDispatchStrategy _dispatcher = new EventDispatchByReflectionStrategy("When");
    readonly List<Message> _messages = new List<Message>();
    readonly int _unmutatedVersion;
    private readonly bool _constructing = true;
    public readonly Identity Identity;

    public IList<Message> GetMessages()
    {
        return _messages.ToArray();
    }

    public void Transition(Event e)
    {
        _messages.Add(e);
        _dispatcher.Dispatch(this, e);
    }

    protected void SendCommand(Command c)
    {
        // Don't add a command whilst we are in the constructor. Message
        // state transition during construction must not generate new
        // commands, as those command will already be in the message list.
        if (_constructing) return;

        _messages.Add(c);
    }

    public int UnmutatedVersion() => _unmutatedVersion;
}

Antworten auf die Frage(2)

Ihre Antwort auf die Frage