3

Let's assume I've got a saga data containing a state with one of the following values:

JustWaiting, AwatingPrepareDrink, WaitingForPayment

I also a have different messages to handle, but only want to handle them when the state has a particular value.

ie.: Handle PrepareDrinkMessage only when state is AwaitingPrepareDrink

To achieve this, I'm currently doing something like that:

public async Task Handle(PrepareDrinkMessage message)
{
    if(Data.CurrentState != BaristaSagaData.State.AwatingPrepareDrink)
    {
        return;
    }

    //do some stuff...


    //state transition
    Data.CurrentState = BaristaSagaData.State.WaitingForPayment;
}

The problem I'm having with this approach is that it's quite possible that the incoming message what just received a little to soon (the worker is possibly currently doing work in another handler that will transition to the correct state).

I tried replacing this:

if(Data.CurrentState != BaristaSagaData.State.AwatingPrepareDrink)
{
    return;
}

with this:

if(Data.CurrentState != BaristaSagaData.State.AwatingPrepareDrink)
{
    //too soon, try again in 10 seconds
    await _bus.defer(TimeSpan.FromSeconds(10), message);
    return;
}

However, this causes the saga revision to increase while another handler is doing some work. When the other handler finishes, a concurrency exception occurs because the revision has increased in the meantime.

Is there any other way to prevent handling a message according to state?

Is there any way to defer messages without impacting the revision?

Thanks for your help!

Bredstik
  • 626
  • 5
  • 13
  • 1
    Excellent question! I think it could be solved if it was possible to somehow mark the saga data as "not updated" - e.g. similarly to how `MarkAsComplete()` flags the saga data in a secret way that will end up deleting it, maybe something like `MarkAsUnchanged()` could do what you need... thoughts? – mookid8000 Sep 17 '15 at 20:00
  • That would be fantastic! I guess it would have to be used wisely, just like MarkAsComplete(). – Bredstik Sep 17 '15 at 20:46

1 Answers1

1

I thought it was such a good idea that I added it to 0.98.12, which is available on NuGet.org in a few minutes.

Now you can change

if (Data.CurrentState != BaristaSagaData.State.AwatingPrepareDrink)
{
    //too soon, try again in 10 seconds
    await _bus.Defer(TimeSpan.FromSeconds(10), message);
    return;
}

into

if (Data.CurrentState != BaristaSagaData.State.AwatingPrepareDrink)
{
    //too soon, try again in 10 seconds
    await _bus.Defer(TimeSpan.FromSeconds(10), message);
    MarkAsUnchanged();
    return;
}

Let me know if it works out for you :)

which will cause the pipeline step that loads and updates saga data to skip updating this particular saga data instance.

mookid8000
  • 18,258
  • 2
  • 39
  • 63
  • Don't get me wrong, it fixes my issue and I thank you for that, but how can I now track that it's been deferred 10 times for instance and that I want to stop deferring after x amount of times? Any suggestions? – Bredstik Sep 18 '15 at 00:39
  • 1
    one way would be to add a special header to the deferred message where you keep count of the number of times you attempted to process it – mookid8000 Sep 18 '15 at 04:57
  • From what I understand, a saga handler cannot process while another long process handler is currently executing: it will cause a concurrency exception. Don't you think it's a valid scenario? – Bredstik Sep 18 '15 at 19:29
  • Let's continue with my barista analogy. The saga receives and handles the "MakeTheDrink" message (it takes 10s to complete). 5s after it started making the drink, it handles the "PaymentCompleted" message information that it'll be able to serve the drink when it's done (setting a flag in the data). Right now, this would cause a concurrency exception because it would alter the saga data revision while the first handler is still processing. Do you think such a scenario is possible right now in rebus? – Bredstik Sep 18 '15 at 19:30
  • 1
    Since the saga relies on updating a piece of data, and the entire philosophy is built around guaranteeing that you, as a developer, can treat the saga as if there is only ever one single message being processed by each saga instance at any given point in time, I suggest you let another message handler perform any long-running message handling logic and let the saga use request/reply to do the coordination – mookid8000 Sep 18 '15 at 19:57
  • 1
    So `MakeTheDrink` would be a message that went to another handler (e.g. as a `MakeTheDrinkRequest`, which would reply back (e.g. with a `MakeTheDrinkReply`), and then that handler could take as long as it wanted to – mookid8000 Sep 18 '15 at 19:59
  • As usual, great explanation! Thanks – Bredstik Sep 18 '15 at 20:02