nservicebusnservicebus-sagas

nservicebus sagas - stuck trying to understand the purpose and benefit


I have read multiple times the documentation on the website. I am reading again and again the same articles and I cannot understand what they are trying to achieve with sagas. Besides, there are almost no resources in internet related to this subject.

But I am completely stuck trying to understand the purpose and benefit of defining so called sagas. I understand handlers (IHandleMessages) - these are interceptors. But I can't understand what Saga is for. The language in the documentation assumes that I am supposed to know something special to grasp that idea, but I dont.

Can someone explain to me in simple words, hopefully with real-life example a situation where I must or should define Saga, and what is the benefit of doing so? I have created an app with multiple endpoints and Saga definition as shown in samples, it works (I guess) but I don't understand what these sagas were defined for... In many samples they use RequestTimeout() method in Saga class. Why, why would anyone want to cause a timeout intentionally? I dont want to put any code fragments here, because its unrelated, I need to understand why I would want to use "Sagas" whatever that means?

Thank you.


Solution

  • NServiceBus Saga is a variant of a Process Manager described in the Enterprise Integration Patterns book.

    To understand when to use Saga, one has to need it. Let's assume you're using regular message handlers only to implement new user registration process. At some point in time, you discover that only 40% of the brand-new registrants confirm their email address and becoming active user accounts. There are two things you'd like to address.

    1. Remind new registrants to confirm their email after 24 hours after registration by sending a reminder.
    2. Remove registrant info (email for example) from the data store to be compliant with GDPR within 48 hours.

    Now how do you do that with a regular message handler? A handler would receive the initial request (first message, m1) to kick off registration by generating an email with a confirmation link and that's it. Once the handler is done, it's done for good. But your process is not finished. It's a long-running logical process that has to span 48 hours before completed. It's no longer just a single message processing, but a workflow at this point. A workflow with multiple checkpoints. Similar to a state machine. To move from one state to another, a certain condition has to be fulfilled. In case of NServiceBus, those would be messages. A message to send a reminder after 24 hours (let's call it m2) is not going to be triggered by any user action. It's a "system" message. A timed message that should be kicked off automatically. So is with the message to instruct the system to remove registrant information if validation link was not activated. The theme can be observed: need to schedule messages in the future to re-hydrate the workflow and continue from the state it was left last time.

    That's what timeouts are. Those are requests to re-hydrate/continue saga/workflow from the point it was left last time at a certain point in time - minutes, hours, days, months, years.

    This is what this kind of workflow would look like as a saga (oversimplified and doesn't take into consideration all the edge cases).

    class RegistrationWorkflow : 
      Saga<WorkflowState>,
      IAmStartedByMessages<RegisterUser>,
      IHandleMessages<ActivationReceived>,
      IHandleTimeouts<NoResponseFor24Hours>,
      IHandleTimeouts<NoResponseFor48Hours>
    {
      protected override void ConfigureHowToFindSaga(SagaPropertyMapper<WorkflowState> mapper)
      {
        // omitted for simplicity, see message correlation
        // https://docs.particular.net/nservicebus/sagas/message-correlation
      }
    
      public async Task Handle(RegisterUser message, IMessageHandlerContext context)
      {
        Data.RegistrationId = message.RegistrationEmail;
    
        await RequestTimeout<NoResponseFor24Hours>(context, TimeSpan.FromHours(24));
      }
    
      public async Task Handle(ActivationReceived message, IMessageHandlerContext context)
      {
        Data.ConfirmationReceived = true;
    
        // email was confirmed and account was activated
        await context.Send(new PromoteCandidateToUser 
        { 
          CandidateEmail = Data.RegistrationEmail 
        });
    
        MarkAsComplete()
      }
    
      public async Task Timeout(NoResponseFor24Hours timeout, IMessageHandlerContext context)
      {
        if (Data.ConfirmationReceived)
        {
          return;
        }
    
        await context.Send(new SendReminderEmailToActivateAccount { Email = Data.RegistrationEmail });        
    
        await RequestTimeout(context, TimeSpan.FromHours(24), new NoResponseFor48Hours());
      }
    
      public async Task Timeout(NoResponseFor48Hours timeout, IMessageHandlerContext context)
      {
        if (Data.ConfirmationReceived)
        {
          return;
        }
    
        context.Send(new CleanupRegistrationInformationForGDPRCompliancy 
        { 
          RegistrationEmail = Data.RegistrationEmail 
        });
    
        MarkAsComplete();
      }
    }
    

    Since this is a state machine, the state is persisted between Saga invocations. Invocation would be caused either by a message a saga can handle (RegisterUser and ActivationReceived) or by timeouts that are due (NoResponseFor24Hours and NoResponseFor48Hours). For this specific saga, the state is defined by the following POCO:

    class WorkflowState : ContainSagaData
    {
      public string RegistrationEmail { get; set; }
      public bool ConfirmationReceived { get; set; }
    }
    

    Timeouts are nothing but plain IMessages that get deferred. The timeouts used in this samples would be

    class NoResponseFor24Hours : IMessage {}
    class NoResponseFor48Hours : IMessage {}
    

    Hope this clarifies the idea of Sagas in general, what Timeouts are and how they are used. I did not go into Message Correlation, Saga Concurrency, and some other details as those can be found at the documentation site you've referred to. Which bring us to the next point.

    I have read multiple times the documentation on their website. It is absolutely terrible. I am reading again and again the same articles and I cannot comprehend what they are trying to achieve.

    The site has a feedback mechanism you should absolutely provide. enter image description here

    Besides there almost no resources in internet related to this subject.

    Hope to see you posting a blog (or a series of posts) on this topic. By doing so you'll have a positive contribution.

    Full disclaimer: I work on NServiceBus