Domain.Aggregate reference
Aggregate
Section titled “Aggregate”Source:
packages/domain/src/Aggregate.ts
One entity that decides, such as a bank account: a consistency boundary. It takes a command, checks it against its current state, and records events or returns a rejection. Each instance handles one command at a time, and its events, new state and reply commit together.
Aggregate: Deciding and evolving
Section titled “Aggregate: Deciding and evolving”import * as Domain from "@alchemy.run/domain";import * as Schema from "effect/Schema";
export const AccountId = Schema.String.pipe(Schema.brand("AccountId"));
export class MoneyWithdrawn extends Domain.Event("MoneyWithdrawn", { amount: Schema.Int, balanceAfter: Schema.Int,}) {}
export class InsufficientFunds extends Domain.Rejection("InsufficientFunds", { balance: Schema.Int, requested: Schema.Int,}) {}
export class WithdrawMoney extends Domain.Command("WithdrawMoney", { target: AccountId, input: { amount: Schema.Int }, events: [MoneyWithdrawn], rejects: [InsufficientFunds], reply: { balance: Schema.Int },}) {}
export class Account extends Domain.Aggregate("Account", { id: AccountId, state: Schema.Struct({ balance: Schema.Int }), initial: { balance: 0 }, commands: [WithdrawMoney], decide: { WithdrawMoney: (s, cmd) => cmd.amount > s.balance ? new InsufficientFunds({ balance: s.balance, requested: cmd.amount }) : [new MoneyWithdrawn({ amount: cmd.amount, balanceAfter: s.balance - cmd.amount })], }, evolve: { MoneyWithdrawn: (_, e) => ({ balance: e.balanceAfter }), }, reply: { WithdrawMoney: (s) => ({ balance: s.balance }), },}) {}Aggregate: Authorizing in decide
Section titled “Aggregate: Authorizing in decide”A handler can be an Effect.fn that reads what the command’s middleware
provides.
decide: { WithdrawMoney: Effect.fn(function* (s, cmd) { const actor = yield* CurrentActor; if (actor.customerId !== s.owner) return yield* new NotOwner(); return [new MoneyWithdrawn({ amount: cmd.amount, balanceAfter: s.balance - cmd.amount })]; }),},Aggregate: Exposing reads
Section titled “Aggregate: Exposing reads”read lets an Api expose the aggregate itself, so a client can get it,
watch it, and follow its events. validate judges every read.
read: { middleware: [Session], rejects: [NotOwner], validate: Effect.fn(function* (_request, s) { const actor = yield* CurrentActor; if (actor.customerId !== s.owner) return yield* new NotOwner(); }),},Aggregate: Sending and scheduling
Section titled “Aggregate: Sending and scheduling”const accounts = yield* Account;const { reply } = yield* accounts.send(accountId, new WithdrawMoney({ amount: 30 }));yield* accounts.schedule(accountId, new SendStatement(), { after: "30 days" });