Domain.Saga reference
Source:
packages/domain/src/Saga.ts
A process that spans several aggregates and takes time: a transfer that debits one account and credits another, or an order that waits for payment. Each step runs once and its result is kept, so a run that’s interrupted picks up where it left off. A step that fails after earlier ones succeeded can undo them with compensations.
A saga is an Effect workflow. Its steps are Activitys, it waits with
DurableClock and DurableDeferred, and it undoes steps with
withCompensation. A policy usually decides when to start one.
Saga: Declaring a saga
Section titled “Saga: Declaring a saga”import * as Domain from "@alchemy.run/domain";import * as Schema from "effect/Schema";import { AccountId } from "./AccountId.ts";import { TransferId } from "./TransferId.ts";
export class DestinationClosed extends Domain.Rejection("DestinationClosed") {}
export class ExecuteTransfer extends Domain.Saga("ExecuteTransfer", { key: TransferId, input: { from: AccountId, to: AccountId, amount: Schema.Int }, rejects: [DestinationClosed],}) {}Saga: Implementing it
Section titled “Saga: Implementing it”import * as Effect from "effect/Effect";import { Activity } from "effect/workflow";import { Account } from "./Account.ts";
export const ExecuteTransferLive = ExecuteTransfer.toLayer( Effect.gen(function* () { const accounts = yield* Account; return Effect.fn(function* ({ key, from, to, amount }) { yield* Activity.make({ name: "debit", execute: accounts.send(from, new DebitTransfer({ transferId: key, amount })).pipe(Effect.orDie), }).pipe( ExecuteTransfer.withCompensation(() => Activity.make({ name: "refund", execute: accounts.send(from, new RefundTransfer({ transferId: key, amount })).pipe(Effect.orDie), }), ), ); yield* Activity.make({ name: "credit", error: DestinationClosed, execute: accounts .send(to, new CreditTransfer({ transferId: key, amount })) .pipe(Effect.catchTag("NotOpen", () => new DestinationClosed()), Effect.orDie), }); }); }),);Saga: Starting it
Section titled “Saga: Starting it”export const TransferExecutionLive = TransferExecution.toLayer( Effect.gen(function* () { const executions = yield* ExecuteTransfer; return Effect.fn(function* ({ source, event }) { yield* executions.start(source, { from: event.from, to: event.to, amount: event.amount }); }); }),);