@alchemy.run/域名
由域驱动的设计框架 效果,该部分 炼金术。模拟您的业务为命令、汇总、事件、观点和政策。 在内存中测试,然后在Cloudflare持久对象上运行,而不改变模型.
npm install @alchemy.run/domain effect
命令要求更改,总和决定,事件记录了所发生的情况:
// src/account.ts
import * as Domain from "@alchemy.run/domain";
import * as Schema from "effect/Schema";
export class MoneyDeposited extends Domain.Event("MoneyDeposited", {
amount: Schema.Int,
balanceAfter: Schema.Int,
}) {}
export class TooLarge extends Domain.Rejection("TooLarge", { max: Schema.Int }) {}
export class DepositMoney extends Domain.Command("DepositMoney", {
target: Schema.String,
input: { amount: Schema.Int },
events: [MoneyDeposited],
rejects: [TooLarge],
reply: { balance: Schema.Int },
}) {}
export class Account extends Domain.Aggregate("Account", {
id: Schema.String,
state: Schema.Struct({ balance: Schema.Int }),
initial: { balance: 0 },
commands: [DepositMoney],
decide: {
DepositMoney: (s, cmd) =>
cmd.amount > 10_000
? new TooLarge({ max: 10_000 })
: [new MoneyDeposited({ amount: cmd.amount, balanceAfter: s.balance + cmd.amount })],
},
evolve: {
MoneyDeposited: (s, e) => ({ balance: e.balanceAfter }),
},
reply: {
DepositMoney: (s) => ({ balance: s.balance }),
},
}) {}
export class Bank extends Domain.Model("Bank", { aggregates: [Account] }) {}
行为被测试为一个故事:鉴于这些事件,当这个命令时,然后这些事件:
// test/account.test.ts
import { Story } from "@alchemy.run/domain/Story";
import { it } from "@effect/vitest";
import { Account, Bank, DepositMoney, MoneyDeposited, TooLarge } from "../src/account.ts";
const story = Story.make(Bank, {});
const account = Account.ref("a-1");
it.effect("deposits and replies with the balance", () =>
story(
Story.given(account, new MoneyDeposited({ amount: 50, balanceAfter: 50 })),
Story.when(account, new DepositMoney({ amount: 100 })),
Story.then(account, new MoneyDeposited({ amount: 100, balanceAfter: 150 })),
Story.replied({ balance: 150 }),
),
);
it.effect("rejects large deposits", () =>
story(
Story.when(account, new DepositMoney({ amount: 20_000 })),
Story.rejected(new TooLarge({ max: 10_000 })),
),
);