Skip to Content
DevelopTransactions

High level transactions

On top of the durability controls and retry controls, the SDKs also provide high level functions for defining transactions supporting compensation actions in case of getting reverted.

Although Golem’s automatic retry policies and low-level atomic regions provide a lot of power automatically, many times a set of external operations such as HTTP requests needs to be executed transactionally; if one of the operations fails, the whole transaction need to be rolled back by executing some compensation actions.

The SDK provides support for two different types of transactions:

  • fallible transactions are only dealing with domain errors
  • infallible transactions must always succeed, and Golem applies its active retry policy to it

Fallible transactions

Many times external operations (such as HTTP calls to remote hosts) need to be executed transactionally. If some operations failed, the transaction needs to be rolled back; compensation actions need to undo whatever the already successfully performed operations did.

A fallible transaction only deals with domain errors. Within the transaction every operation that succeeds gets recorded. If an operation fails, all the recorded operations get compensated in reverse order before the transaction block returns with a failure.

In TypeScript, a fallible transaction can be executed using the async fallibleTransaction function, by passing an async callback that can execute operations on the open transaction (see below).

Infallible transactions

An infallible transaction must always succeed; in case of a failure or interruption, it gets retried. If there is a domain error, the compensation actions are executed before the retry.

In TypeScript, an infallible transaction can be executed using the async infallibleTransaction function, by passing an async callback that can execute operations on the open transaction (see below).

Operations

Both transaction types require the definition of operations.

It is defined with the following interface:

/** * Represents an atomic operation of the transaction which has a rollback action. * * Implement this interface and use it within a `transaction` block. * Operations can also be constructed from closures using `operation`. */ export interface Operation<In, Out, Err> { /** * The action to execute. * @param input - The input to the operation. * @returns A promise resolving to the result of the operation. */ execute(input: In): Promise<Result<Out, Err>> /** * Compensation to perform in case of failure. * Compensations should not throw errors. * @param input - The input to the operation. * @param result - The result of the operation. * @returns A promise resolving to the result of the compensation. */ compensate(input: In, result: Out): Promise<Result<void, Err>> }

There are two ways to define an operation:

  1. Implement the Operation interface manually
  2. Use the operation function to create an operation from a pair of closures
export function operation<In, Out, Err>( execute: (input: In) => Promise<Result<Out, Err>>, compensate: (input: In, result: Out) => Promise<Result<void, Err>>, ): Operation<In, Out, Err>

Executing operations

The defined operations can be executed in fallible or infallible mode:

import { fallibleTransaction, infallibleTransaction, operation, Result } from "@golemcloud/golem-ts-sdk" // example operation with compensation const op = operation<number, string, string>( async (idx) => { // the operation / side effect return Result.ok("id-" + idx) }, async (idx, id) => { // compensation console.log(`reverting ${id}, ${idx}`) return Result.ok(undefined) } ) // with fallibleTransaction errors have to be handled and propagated using the Result type const resultFallible = await fallibleTransaction(async tx => { const firstId = await tx.execute(op, 1) if (firstId.isErr()) return firstId const secondId = await tx.execute(op, 2) if (secondId.isErr()) return secondId return Result.ok([firstId.val, secondId.val]) }) // with infallibleTransaction no explicit error handling is needed, as it is handled by Golem retries const resultInfallible = await infallibleTransaction(async tx => { const firstId = await tx.execute(op, 1) const secondId = await tx.execute(op, 2) return [firstId, secondId] })
Last updated on