Implementing Long Running Business Processes (a.k.a. Sagas) Using Azure Durable Functions — Customer Discount Calculation Based On Order History

Manvel Ghazaryan
4 min readJan 18, 2022

In the previous 2 posts we’ve looked into implementing long running business processes using Azure Durable Functions. We started with relatively simple case of a Buyer’s Remorse and then looked into Shipping orchestration. In this final post on the topic I want to look into implementation of client discount calculation based on the order history. This implementation follows the great talk “Code the future, now“ by Adam Ralph. Adam first looks into how this kind of requirements are usually implemented, discusses drawbacks of those approaches and shows different implementation using NServiceBus Sagas. I highly recommend to watch it !

Let us first define the requirements.

When customer submits an order, give them 10 % discount if total sum of orders within last week is greater than 100 $

I will not go over possible implementations of the requirement. I think Adam Ralph does a great job doing that. Instead let’s delve directly into solution proposed in the talk.

This is the idea. When customer submits the first order, we kick of a Saga. This will keep track of weekly total of sum of the orders. Whenever order is submitted, we increase weekly total and check our condition, e.g. if weekly total > 100 $ then give a discount of 10 %. To keep track of weekly total saga will schedule a decrement operation a week in the future. Decrement operation will decrement sum of the order from the weekly total.

Maybe an example will help to clarify.

  1. Customer submits first ever order worth 54 $. Saga is started, WeekTotal = 54 $. Since 54 $ < 100 $, no discount given. Decrement operation is scheduled to run in a week and perform WeekTotal — 54 operation.
  2. After 3 days customer submits and order worth 47 $. WeekTotal += 47, so we have WeekTotal of 101 $. Since WeekTotal > 100 $ customer gets 10 % discount (I know, we’re generous and soon will be out of business :) ).
  3. Fast forward to day 7 since the first order submission. Scheduled decrement operation is kicking off now, so WeekTotal -= 54, WeekTotal = 47 now.
  4. The next day customer submits another order worth 33 $. As before we increment WeekTotal += 33, so we have WeekTotal of 80 $ which is less than 100 $ and customer gets no discount.

Below is the timeline diagram of this.

© Adam Ralph “Code The Future, Now” NDC Oslo 2019.

I hope the idea is clear now. Let’s look into actual implementation using Azure Durable Functions.

Customer Durable Entity

First let’s look into Customer durable entity, which will represent statefull Saga keeping track of a week total of the orders.

We define 2 methods — add to week total and subtract. These are going to be called to modify week total of the customer orders sum.

Submitting An Order

As before we’re going to use HTTP /GET triggered function to kick off the processing. In the real world project this most probably will use some message binding (Azure Service Bus).

Problem with this implementation is that SignalEntityAsync completes when message is reliably enqueued to be processed by an entity. Processing of the message happens asynchronously. In a way this follows CQRS approach. With SingnalEntityAsync we send a message to be handled, and with ReadEntityStateAsync read the state. But we can’t reliable know if command was processed when we read the state. This can result in an undesired behavior. If customer’s last order should’ve made week total greater than 100 $ thus making customer eligible for a 10 % discount but because by the time we read the state command wasn’t processed, we got wrong week total & customer doesn’t get a discount.

For this reason we kick of an orchestration, because from the orchestrator it is possible to get the state immediately.

And the orchestrator itself

So we ask to add order total to week total and get modified week total back. Then we can check discount eligibility and process the order. Concurrency is not a concern here, because as we discussed in the last post all operations on a single entity are guaranteed to execute serially, that is, one after another.

Note SignalEntity call on the last line. It allows to schedule operation on the entity. So we schedule subtract operation from week total equal to the order total.

I updated Github repository with the full implementation of discount calculation.

--

--