Support for Saga compensating transactions in Typescript

Hello everyone :slightly_smiling_face:
I was wondering whether there is a way to support saga compensating transactions in Typescript?
If so, where can I find examples and best practices.
Thanks

Welcome to the forum! Here’s a sample: samples-typescript/saga at main · temporalio/samples-typescript · GitHub

Sure! Workflows actually makes implementing saga-style compensation very easy. As a workflow is simply code, your compensation logic can be defined in the workflow itself, based on your language’s built-in exception handling mechanisms.

In TypeScript, for example, you will simply perform appropriate compensations from a catch block surrounding activities that could fail. Here is an example of how one might do it (this is by no mean the only way to do it):

export async function createAccount(...) {
  let address?: Address;
  let client?: Client;
  let bankAccount?: BankAccount;

  try {
    address = await addClient(...);
    client = await addClient(...);
    bankAccount = await addBankAccount(...);
  } catch (e) {
    // Do compensations here, depending on which elements had been created
    if (bankAccount) await removeBankAccount(...);
    if (client) await removeClient(...);
    if (address) await removeAddress(...);
    throw e;
  }
}

For more complex scenarios, you may expand the principle exposed above to something more tailored to your specific use cases. For example, you might consider registering “compensation functions” into a collection after each completed activity; then, from the catch block, you would simply invoke each of these function, in reverse order. See samples-typescript/saga at main · temporalio/samples-typescript · GitHub for a demonstration of this strategy.

1 Like

Looks good. Thanks a lot :ok_hand:

@Fitz and I just released a video live-coding compensating transactions, so if you like video content (and we link to the corresponding GitHub code, check it out: Temporal Unscripted: Compensating Transactions (part of Saga Pattern) in TypeScript - YouTube

It should be noted that an activity might complete after its compensation under certain conditions even after the workflow believes the activity has failed, timed out, or cancelled. This is due to the fact the when dealing with distributed systems the common way of detecting node failures is using time outs.
In the Temporal case, one manifestation of this is:
Activity attempt N-1 is running and has not been notified of cancellation or is oblivious to its timeout (due to network partition issues).
A new attempt N is started and fails while N-1 is still running.
Workflow gets notified of the activity failure and starts a compensating action.
Compensating action completes.
Attempt N-1 completes overriding the compensating action.

For completely reliable compensations, the original activity (the one being compensated for) should be able to not take effect if a compensation has already happened.
This would require certain consistency/linearizability guarantees from the service or resource being manipulated.