Sagas — Part 3: Choreography Instead?

When to Choose Choreography

Sagas — Part 3: Choreography Instead?
Photo by ketan rajput on Unsplash

Sagas — Part 1: An Introduction
Sagas — Part 2: Sagas in Distributed System
Sagas — Part 2b: Sagas in Distributed Systems Continued

Part 1 of this series detailed when I’d choose Orchestration over Choreography and why. However, I’m a big fan of choreography as it provides low coupling, and that’s a big win when designing a microservices system.

A structure is stable if cohesion is strong and coupling is low — Larry Constantine

Hence, I should do choreography justice and discuss when I’d choose it. I will not spend time explaining choreography and its benefits here — I will leave that to a future post — as that will take away from the focus of this post.

When to Choose Choreography

I personally prefer to choreograph if the process we are trying to model meets all the following criteria:

  • Simple
  • Stable
  • Does not need compensatory transactions

Simplicity is subjective, so I evaluate it last. I usually start with the other two criteria as there are easier to assess. It is a good candidate for choreography if the business process is stable and does not require compensatory transactions.

Change is inevitable. Hence, determining the stability of a process is a judgement call based on the information we know at the time — especially if it’s a new business. I’d start with choreography if the consensus is that the process is relatively stable. If that doesn’t prove to be the case in the future, I will move to orchestration.

As I have explained in Part 1 of this series:

The problem with choreography is that, in my opinion, it does not work as well when the process is complex or is likely to change frequently. The decentralised nature of choreography means the overarching process is scattered across multiple services, making it harder to determine what the process involves — without resorting to documentation or monitoring tools. If the process requires changing, then it’s likely that several services must be altered and deployed at the same time, increasing the risk of making changes.
Ash Mageed, Sagas — Part 1: An Introduction

In the first post in this series, I also stated that choreography makes it harder to reason about the business process as it is scattered among several services. Compensatory steps make this even more challenging as not only do you need to keep track of progress completing the business process, but you need to do so for rolling it back too. I usually lean towards choosing orchestration if the process involves several compensatory actions.

But shouldn’t all steps require compensatory actions?

Not necessarily. There are different ways you can model steps in your business processes:

Using the Reservation Pattern

Considering the Saga in Part 2, instead of taking payment, we can reserve the amount — if you’re using a credit card, this is an authorisation — for a period of time in line with our Sagas SLAs. This could, for example, be if the order is not completed within 20 minutes, then automatically undo the reservation.

This requires two things to be implemented correctly, a confirmation that the process has been completed successfully in order not to expire the reservation. Secondly, we must check whether the reservation is still in place before completing the process. I use this pattern judiciously, as not every operation is reservation friendly.

UI Composition and Orphaned Data

UI Composition is a technique by which a user interface is composed of numerous partial views, each served up by a different microservice. Each partial view knows how to fetch data from its microservice and send data to that microservice.

To represent a process using UI Composition, a client-side generated ID is used to tie the different bits of data in the process together. This is then sent as a correlation ID to each participating microservice by its partial view. The microservice saves the data locally and waits for the designated event carrying the correlation ID to trigger its part of the process. I have blogged about this here if you’re interested in more details.

A few years ago, I was working for a SaaS startup, and we had an onboarding process where new users would sign up and choose the plan they wanted, their username and so on. We use UI Composition to send this information to two microservices: the user service and the credentials service with the same correlation ID. The credentials service saves the data and waits for the UserCreated event with that correlation ID to arrive from the user service to start processing the user — issuing access tokens, setting up roles, etc. Sometimes the event never arrives because the user does not get created for several business reasons, so the information in the credentials service remains there. We could have listened to a UserCreationFailed event to clear the data, but the data staying there had no impact — and storage is cheap, so we chose to simplify the process and keep the orphaned data.

Retrying with Manual Workarounds as Fallbacks

Sometimes you get processes where it is good for business for the entire process to succeed. There could be many reasons, such as penalties for not completing or lost income opportunities. For those operations, we may choose to retry them by using different business solutions and then escalate to a manual workaround if everything else fails.

For example, a Saga that fulfils shoe orders at your typical retailer could have a step to “pick inventory”, but it so happens that the last shoe was taken by another customer. Should the Saga now fail, cancel the order and undo all completed steps? Ordinarily, No. Most retailers fall back to “Backorders” to complete the purchase process and adjust the delivery time accordingly.

A backorder is an order for a good or service that cannot be filled at the current time due to a lack of available supply.
Investopedia (https://www.investopedia.com/terms/b/backorder.asp)

This is beneficial to the business as they want to take your money, so it makes sense to attempt different strategies to fulfil their orders and get that money.

Occasionally, a backorder is not applicable — maybe there is a long backorder list for the product already — and maybe we automatically send an e-mail with alternative product recommendations, or if this was a larger business deal, someone could manually intervene and call up the customer.