Most of us are working on distributed systems. Most of us are implementing long running processes. Of course we would like all our long running processes to be:
- simple
- fast
- decoupled
- reliable
- easy to implement
- easy to understand
- easy to change
- easy to monitor
But this is impossible, so you need to make trade offs. This is why it’s important to have the right tool for the job. But, much of the information out there describes one tool – RPC style integration (e.g. services calling each other over the web, through HTTP). And although this is a good tool, it’s not the best tool in every situation. The purpose of this blog post series is to present some message based patterns that are useful when designing and implementing long running processes.
What is a long running process
First, let’s start with what is a process. A process is a set of operations that are executed in a given order as result of a trigger.
public Task Handle(PlaceOrder message, IMessageHandlerContext context) { Data.OrderId = message.OrderId; Data.TotalValue = message.TotalValue; Log.Info($"Placing Order with Id {message.OrderId}"); RequestTimeout(context, TimeSpan.FromSeconds(1), new BuyersRemorseTimeout()); return Task.CompletedTask; }
In this example, the trigger is the PlaceOrder message, and the instructions are in the body of the method.
A long running process is a process that needs to handle more than one message.
{ public Task Handle(PlaceOrder message, IMessageHandlerContext context) { Data.OrderId = message.OrderId; Data.TotalValue = message.TotalValue; Log.Info($"Placing Order with Id {message.OrderId}"); RequestTimeout(context, TimeSpan.FromSeconds(1), new BuyersRemorseTimeout()); return Task.CompletedTask; } public Task Timeout(BuyersRemorseTimeout state, IMessageHandlerContext context) { context.Publish<IOrderPlaced>( o => { o.OrderId = Data.OrderId; o.TotalValue = Data.TotalValue; }); MarkAsComplete(); return Task.CompletedTask; } }
As you can see, in the handler of the PlaceOrder message, we set some state (the OrderId and TotalValue) and we raise a timeout. In the second handler, when we receive the BuyersRemorseTimeout, we read the state that we saved in the first handler and publish an event.
Long running means that the same process instance will handle multiple messages. That’s it! Long running doesn’t mean long in the sense of time. At least not for people. Such a process could complete in microseconds. Also, a long running process does not need to be actively processing its entire lifetime. Most of the time, it will probably just wait for the next trigger.
Examples
Let’s see some examples of long running processes. We’ll describe a couple of business oriented processes and one that is more technical.
Enterprise Process – Order fulfillment
Let’s start with the order fulfillment process. This is what happens between the time you hit the Buy button, until the shop ships your order. This is an enterprise process because many departments in the business need to collaborate in order for you to receive your order. Sales needs to approve the sale. Finance needs to charge your credit card. Inventory needs to pack the items. Shipping needs to ship the order.
Business Policy – Buyer’s remorse
Long running processes can be defined in a single service. Take for example the buyer’s remorse policy.
There’s this thing, called buyer’s remorse, that says that people feel sorry after making an important purchase. This is why most of the cancellations and refunds happen within a two hour time frame after buying something. Of course, cancellations cost the business money. They need to refund the customer, unpack the order or cancel the shipment. You can cut some cost if you don’t actually start processing the order until the buyer’s remorse timeout is over.
In this case, the process instance starts with the PlaceOrder command and finishes with either CancelOrder or BuyersRemorseTimeout. If the order is cancelled, we just delete the process instance – which is cheap. If the order is not cancelled by the time we get the BuyersRemorseTimeout, then we publish the OrderPlaced event and complete the process instance. You can apply the same pattern if you want to build the “Undo Send” functionality in an email client.
IT Process – Integrating with a 3rd party
The third example is an IT process – handling the integration with a third party. After Finance receives the OrderPlaced event, it needs to charge the customer. To do that, it will send a ChargeCreditCardRequest to a Credit Card Processor Gateway. This, in turn, will call a 3rd party payment provider. After receiving a response, it will send a response back to Finance. So, in this case, the triggers are the OrderPlaced event and the ChargeCreditCardResponse.
Conclusion
Now you have a better idea of what is a long running process. If you want to see more examples, check Bernd Rücker‘s article about 5 Workflow Automation Use Cases You Might Not Have Considered. In the next blog post we’ll see how to model long running processes by using the two main integration architectural styles: choreography and orchestration.