Saga time out method

Hi Team,
I need to create a saga for following feature !

1 . I receive date and order id based on that saga will set a time out for after 7 days .but every day I will receive more than 100 order along the date , i need to set up a different timeout for all orders . If orders get repeat then i have update the timer if its not triggered . Could any one help me the solution and guidance.

Note : currently we put a scheduled message in azure queue and put one entry in saga table. we feel that there is no need of entry in database.

Kindly suggest

Hi Kannan,

There is no such a update Saga timer so the option is to use Timeout state.

In your scenario the Saga correlation will be the OrderId. The Saga data could have some identifier represents the actual state. When message with date and order id arrives you can set the saga data identifier with new value, pass this value to Timeout state and Request Timeout. When Timeout times up you can check if identifier value is the same in Saga data and Timeout state. If yes then run the logic otherwise do nothing - it means that newest message for order id arrived and there is a new Timeout waiting for times up.

Does this make sense?

Thanks Mike.

My question is how to update: Example today i have received below sequence , Time out consider 2 day

order 1, Date is may28 - > Execution is May 30
order 2 , Date is May28 -> Execution is May 30
order 1 , Date is May29 -> Execution is May 31

In that case first time out should not execute … Can you please guide

Sure :slight_smile:

Based on your example I see two conditions:

  1. Request new Timeout only when current order Date is greater than previous order Date so the order Date can be the identifier
Handle(message, context)
    if (!this.Data.OrderDate.HasValue() || message.OrderDate > this.Data.OrderDate)
        this.Data.OrderDate = message.OrderDate;
        await this.RequestTimeout(context, TimeSpan.FromDays(2), new TimeoutProcessOrder { OrderDate = message.OrderDate })
  1. Process Tiemout logic only when identifier hasn’t changed
Timeout(state, context)
    if (state.OrderDate == this.Data.OrderDate)
        // do something

So there is no such a “time out should not execute”. Once requested it will be always executed but you can decide if logic should be run after times up.

Off course this is only some proposition. The best is to write tests with all possibilities scenarios using Unit Tests especially with out of order message processing.

I don’t know what are you want to do in the Timeout logic but take to consideration the special concurrency scenario when in the same time for the same Saga instance Timeout times up and new message arrives. There should be some business policy specifying proper state regardless of which part will be executed first.

Hope this help.

Thanks a lot… Got complete idea.

But how can i get the previous value ! … Should I retry from the SQL table .?

How can get all the old value in state

order 1, Date is may28 - > Execution is May 30 ( received time 10 am)
order 2 , Date is May28 -> Execution is May 30 ( received time 11 am)
order 1 , Date is May29 -> Execution is May 31 ( received time 12 am)

When the event hit as 12 am how to get the value 10 am and 11 am value.

I am aware that when the request time out is called it give an entry in SQL saga table.

Thanks in advance !

The previous state is remembered by NServiceBus within timeout message

new TimeoutProcessOrder { OrderDate = message.OrderDate })
// for example new TimeoutProcessOrder { OrderDate = '2020-05-28 10:00:00:000' }

NServiceBus gives you the state value when handle timeout message

Timeout(state, context)
    if (state.OrderDate == this.Data.OrderDate)
        // state.OrderData has value '2020-05-28 10:00:00:000'

The Saga logic is per order id so for ‘order id = 1’ and ‘order id = 2’ there are two separate Saga instances so it will be two separate timeout requesting:

  • when the event hits as May 31 12 am (order id = 1) you will have access to previous value ‘2020-05-28 10:00:00:000’
  • when the event hits as May 30 11 am (order id = 2) you will have access to previous value ‘2020-05-28 11:00:00:000’

So in this implementation you can have access only for previous state for concrete order id.

If you want to have access to all order states then you have to store this values in separate table (outside Saga data) and have the logic checking this structure. One important thing, don’t select data inside Timeout handler. Instead of this send the separate message and replay to Saga with some calculated value.

Another option is to design Saga such a way to have all needed information in own Saga data.

In my case , Order and date is combination key… We are planning create single saga for those combination…

order 1 and Date May 28 , we create an entry in saga table with execute date .
order 2 and Date May 28 , we create an another entry in saga table with execute date .
order 2 and Date May 29 , we create an another entry in saga table with execute date

Now the logic is , order 1 may 28 reach timeout I have to check the saga table if the order is already present then I have to ignore the execution

Thanks,
Kannan

Hey Kannan,

Thought I’d throw in some ideas here.

Perhaps you could explain a bit more about the business reason for maintaining two sagas for the same order? Think this will help us understand the use case a bit more.

I’m in the process of implementing a saga for long running orders where they can timeout if not actioned within a given time frame. Rightly or wrongly I’m not using the saga timeout but instead use a few extra services.

In my case I have a saga management service - this is responsible for maintaining the core order saga details ie create and update the saga. In my case I have one saga record per order.

When the order is created I fire off an event e.g. ManagedOrderCreated. This event is handled by a few other services, a service which materialises a view for the end user, but also a Managed Order Expiry service. This Managed Order Expiry service sole purpose is to monitor and action the expiry of a Managed Order. When a Managed Order expires it fires off a Managed Order Expired event which the main saga service listens to and finalises the saga.

When an order is updated a Managed Order Expiry Change Event is fired (if the date has changed) so the Managed Order Expiry service can update the expiry info.

By having this sort of setup it allows us to have another service listening to the different events and writing out a history (log book) of what has changed, which keeps the saga usage quite lite.

Obviously this is our use case and yours could be different, but thought I’d share that idea incase it helps.

Regards,
Marcus

Thanks a lot !

Actually my case we have System A ,B , C … I am system B . A is sending a event (Printorderplacementdate) along with order id and date, We received from them on daily basis. Then B will put in timer to wake up in 7 days. ( SQL saga entry )
Once Timeout happen then B will raise an event to System C.

The above logic we have done.

Now the challenge is, if we received an order 1/June5 and execute date is 12th June … we have received same order 1/June8 Now the execute date is 15th June So if the 12th June timeout happen we should not trigger the event to System C. However it should get execute on June 15th!

Can you please let me know how your managementservice is helps to update the SAGA.

My solution
while receive an event we need to store the order id and date in Table A . Then we will put a entry in SAGA table. If the timeout happens we need to check in Table A weather we have latest date for that order if so we just skip the business logic .

Kindly suggest.

Am not aware how to update the saga entry ?

Thanks,
Kanann.

This technique is called “delayed event handling”. Adding a delay in execution, but this execution can be ‘reset’ if within this delay window a specific event occurs. This invalidates the execution.

As @mikedevbo suggested, use timeout state for that. He used the name OrderDate but in your example, the name ExecutionDate likely is better as “execution date” is required to “slide”.

He already gave the solution but what missing was the API bits. I tried to clarify this a bit better.

Timeout creation:

var executionDelay = TimeSpan.FromDays(7);
var executionAt = DateTime.UtcNow + executionDelay;
 // Add the same value in the saga state AND the timeout message
Data.ExecuteAt = executionAt;
var timeoutMessage = new ExecuteOrderTimeout { ExecuteAt = executionAt };
await RequestTimeout<ExecuteOrderTimeout>(context, timeout, timeoutMessage);

Timeouthandler:

public async Task Timeout(ExecuteOrderTimeout state, IMessageHandlerContext context)
{
    // Check if still relevant or if a new timeout was sent
    if(Data.ExecuteAt != state.ExecuteAt)
    {
        return; // Ignore this timeout, as 'Data.ExecuteAt' has a new value meaning a new timeout was sent.
    }
    // Do your important stuff part of the executing this. Likely just publishing or sending something.
}

Please note, often there is not even a timestamp. You can also just use an incremental number acting as a version counter.

@mikedevbo The method RequestTimeout is async, I’m going to update your code as that must be awaited.

@KannanRamaswamy Correct me if I’m wrong but I have feeling that you try to find solution around manual selecting and updating Saga data but it’s not the case. The Saga data are managing be NServiceBus. You should only fill data values and/or set Saga as finished. Have you try completed the Saga tutorials? Reading @ramonsmits implementation I see solution for your scenario.

@m-monaghan so in your scenario the order expiration responsibility is out of management Saga. How do you decide when raising Managed Order Expired events from Managed Order Expiry service for all expired Managed Orders, some schedule/batch job?

@ramonsmits OK, the intention was to write pseudocode with C#-like syntax style :slight_smile:

@mikedevbo Yeah you are right ! I want to update the schedule entry when the new schedule occurred for the same order id with different date !.

After all our discussion I could understand that its not possible. Only way to be saga mark as complete when new entry present in saga schedule table for the same order .

@ramonsmits correct me if am wrong ,As per your code Data.ExecuteAt. may be get vanished when we redeploy or any error occurs ! … Please guide me for Saga state life span!

@mikedevbo /@ramonsmits Can I save the execute at in database and do the check in time out method ! … is that right approach !

My solution

  1. When the event receive (orderid,Print order date) ,Insert the value in TABLE A
  2. Create a timeout method with (7+ Print order date) days.This will create an entry in SAGA table
    3 . When the timeout occur, Retrieve table A and validate any new entry for the same order
    4 . If new entry is present mark as complete if not do the business logic.

Kindly provide your thoughts

@mikedevbo yeah, it runs on a schedule which looks for elapsed orders. I decided to do it this way as there are additional scenarios of allowing users to update the expiry and expire orders immediately, but with additional authorisation.

@KannanRamaswamy is there a need to use a saga? Are you orchestrating multiple events etc?

If not then would a specific scheduling framework be better, perhaps something like https://www.hangfire.io/ ?

So perhaps:

Option 1: As you have suggested, use the saga timeout as a schedule.
Option 2: Record the Order Id and Print Date in a table and have a second service which runs every day or n minutes etc looking for Orders which need to be printed. If you need to update the print date then it’s a simple change of a record.
Option 3: Use something like hangfire as the scheduler e.g. write the order id and hangfire job id to a table, then if you need to change the date you update the hangfire job.
Option4: … to be discovered :smiley:

@KannanRamaswamy You have to decide what kind of responsibility your SAGA should have. I see at lest two:

  1. Replace scheduling framework
  2. Manage Order state

In this topic discussion you have code proposition for both responsibilities.

As I understand you prefer to use SAGA only for point 1. so your solution is good with small modifications -> don’t make any operations on TABLE A inside SAGA logic:

The soultion could something like this:

  1. When the event receive (orderid,Print order date)
    • Start the SAGA -> IAmStartedByMessages -> create an entry in SAGA table
  2. In the IAmStartedByMessages handler send separate message to Insert the value in TABLE A and Reply message response
  3. In the SAGA response IHandleMessages handler create a timeout method with (7+ Print order date) days.This will create an entry in NServiceBus delayed infrastructure
  4. When the timeout occur send separate message to Retrieve Table A and validate any new entry for the same order and Replay message response
  5. If new entry is present mark SAGA as complete if not do the business logic.

Hope this help.

@mikedevbo Yeah finally I conveyed correctly.

Thanks @mikedevbo .

Just need to learn how to send message and capturing the reply (point 2 and point 3 ) . Do you have any piece of code. If so it’s very grateful

I will share my code shortly

You can find Send/Replay (Request/Response - Full Duplex pattern) example on the doc site:

If you want to see how it could work with Saga check out one of my implementation for blog comments functionality:

Cheers,
Michał