DDD, difference between a Saga and an Event Dispatcher?
Asked Answered
C

3

16

On multiple sites (e.g. here or here Sagas are described as a mechanism that listens to domain events and reacts to them, executing new commands, and finally modifying the domain, etc.

Is there any difference between a Saga and a simple event dispatcher, where you have some subscribers react to events?

Chopper answered 14/9, 2015 at 7:53 Comment(0)
M
16

A "saga" maintains process state. A more accurate term is a process manager. The term "saga" was popularised by NServiceBus which is why many people nowadays refer to it as a "NServiceBus saga". A true saga is a database concept.

Anyway, since an event dispatcher has no interest in process state it is not a process manager. A service bus, as you noted, can also act as an event dispatcher, typically to other systems, although a service bus handles a whole lot more.

There are ways to deal with process state without making use of a saga, e.g.: routing slips and "choreography". Process managers are more of an "orchestration" mechanism.

Process managers can make your life a whole lot simpler so it does a bit more than an event dispatcher.

Essentially your subscriber(s) will interact with your process manager to effect any changes related to the process.

You may be thinking that this is a bit like workflow and you will be correct. However, a workflow engine is quite a heavy affair whereas a process manager should be a first class citizen in your DDD world :)

Process Manager Example

The following is just a quick, off the top of my head, and broad sample. Initially the data to create a member is stored as state in the process manager. Only once the e-mail address has been verified is the actual member created and stored with its valid e-mail address.

Then a welcome e-mail is sent, perhaps using a service bus. Once the response from the EMailService endpoint is received that the mail has been successfullly sent does that handler instruct the process manager that the e-mail has been sent and then completes the process manager.

So there would be a MemberRegistrationProcessRepository. Completing a process may result in it being archived or even deleted if it is really no longer required.

I have a suspicion that event sourcing will lend itself nicely to process managers but to keep the sample simple I have put together the following based on what I have previously implemented myself.

What I have also done previously is to keep track of the status changes and we had an SLA of 15 minutes per status. This was monitored and all process managers sitting on a status for more than 15 minutes would be reported to the core operational team to investigate.

In C# one could have something like this:

public class MemberRegistrationProcess
{
    public Guid ProcessId { get; private set; }
    public string Name { get; private set; }
    public EMailAddress EMailAddress { get; private set; }
    public string Status { get; private set; }

    public static MemberRegistrationProcess Create(string name, EMailAddress eMailAddress)
    {
        return new MemberRegistrationProcess(Guid.NewGuid(), name, eMailAddress, "Started");
    }

    public MemberRegistrationProcess(Guid processId, string name, EMailAddress eMailAddress, string status)
    {
        ProcessId = processId;
        Name = name;
        EMailAddress = eMailAddress;
        Status = status;
    }

    public void EMailAddressVerified(IMemberRepository memberRepository)
    {
        if (!Status.Equals("Started"))
        {
            throw new InvalidOperationException("Can only verify e-mail address if in 'started' state.");
        }

        memberRepository.Add(new Member(Name, EMailAddress));

        Status = "EMailAddressVerififed";
    }

    public void WelcomeEMailSent()
    {
        if (!Status.Equals("EMailAddressVerififed"))
        {
            throw new InvalidOperationException("Can only set welcome e-mail sent if in 'EMailAddressVerififed' state.");
        }

        Status = "WelcomeEMailSent";
    }

    public void Complete(Member member)
    {
        if (!Status.Equals("WelcomeEMailSent"))
        {
            throw new InvalidOperationException("Can only complete in 'WelcomeEMailSent' state.");
        }

        member.Activate();

        Status = "Complete";
    }
}
Mauramauralia answered 15/9, 2015 at 4:5 Comment(2)
could you add an abstract example of what process state means and how is handled by the process manager? Thanks!Chopper
I have added a sample. Keep in mind that this can be as complex or simple as required. The status of the process is also state and indicates how far the process has moved.Mauramauralia
P
8

A Saga is a long running process that triggers by events outside the domain. That events could happen in seconds, minutes or days.

The difference with simple event bus is that a Saga keeps a state machine that can be persisted to handle long running process in a "disconnected" workflow due to the external events.

The easiest way to understand it is a real life example, the classic "We sent you a confirmation e-mail to finish your registration in our awesome forum" should work:

Example with NServiceBus:

// data to be persisted to start and resume Saga when needed
public class UserRegistrationSagaData : ISagaEntity 
{
    public Guid Id { get; set; }
    public string Originator { get; set; }
    public string OriginalMessageId { get; set; }
    public  string Email { get; set; }
    public  int Ticket { get; set; }
}


    // the saga itself
    public class UserRegistrationSaga :
                     Saga<UserRegistrationSagaData>,
                     // tell NServiceBus the Saga is created when RequestRegistration message arrives
                     ISagaStartedBy<RequestRegistration>,
                     // tell NServiceBus the Saga is resumed when ConfirmRegistration message arrives
                     // (user click in the link inside the e-mail)                              
                     IMessageHandler<ConfirmRegistration>   
    {
        public override void ConfigureHowToFindSaga() //primary keys of this saga in persistence
        {
            ConfigureMapping<RequestRegistration>(saga => saga.Email, message => message.Email);
            ConfigureMapping<ConfirmRegistration>(saga => saga.Ticket, message => message.Ticket);
        }

        // when requestRegistrarion arrives this code is executed
        public void Handle(RequestRegistration message)
        {

            // generate new ticket if it has not been generated
            if (Data.Ticket == 0)
            {
                Data.Ticket = NewUserService.CreateTicket();
            }

            Data.Email = message.Email;

            MailSender.Send(message.Email,
                          "Your registration request",
                          "Please go to /registration/confirm and enter the following ticket: " + Data.Ticket);

            Console.WriteLine("New registration request for email {0} - ticket is {1}", Data.Email, Data.Ticket);
        }

        // when ConfirmRegistration arrives this code is executed
        public void Handle(ConfirmRegistration message)
        {
            Console.WriteLine("Confirming email {0}", Data.Email);
            NewUserService.CreateNewUserAccount(Data.Email);
            MailSender.Send(Data.Email,
                          "Your registration request",
                          "Your email has been confirmed, and your user account has been created");

            // tell NServiceBus that this saga can be cleaned up afterwards
            MarkAsComplete();
        }
    }
}

A simple

Bus.Send(new RequestRegistration(...))

by a i.e. web controller should do the work.

Hard-coding this behavior with a simple event bus will require you to simulate a state machine in your domain in a ugly way; i.e. to add a boolean field "confirmed" in your users table in domain persistence and having to query and work with "confirmed = true" users in users management module of your system. Or having a table of "pending of confirmation users" in your persistence domain. I think you will get the idea.

So, a Saga is like a simple event bus that helps you to not pollute your domain and domain persistence with a state machine because the "disconnected" long running process. This is just responsibility segregation in good OO design.

Pistachio answered 14/9, 2015 at 12:54 Comment(2)
so, which is the difference between a event dispatcher / event bus / event listeners? (i.e: github.com/greenrobot/EventBus) In the example I just see a typical event subscribed which is listening to a desired event, and then, when somebody dispatch it to it (the event bus), the listener reacts to itChopper
The saga has logic for progressing the process by storing and changing state.Prichard
K
2

That is a good question because it is confusing to distinguish between these concepts. And I agree with the answers that stated that the saga is a business flow.

And because sagas can span across multiple bounded contexts, therefore multiple microservices or modules, then they can be implemented in two ways:

  • Event orchestration
  • Event Choreography

Event orchestration is a kind of a process manager or a flow orchestrator, which is a central component that is needed to orchestrate the whole business flow. So it will create the saga, then coordinate the entire flow across multiple microservices or modules, then end the saga.

Event choreography is much simpler and can be done by the saga participants emit and subscribe to events. That can be done by event bus, dispatchers, and subscribers.

So the saga itself can be implemented with event dispatchers and subscribers. The difference is with the saga, The emitted/subscribed to events should make sense in the business flow of the saga itself.

I hope I made things simpler :D

Kolosick answered 3/10, 2021 at 17:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.