Skip to content

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.

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],
}) {}
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),
});
});
}),
);
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 });
});
}),
);