mcpField
A
alchemy-domain

alchemy-domain

github.com/alchemy-run开发2.0k165 次安装今天更新
代码仓库

关于 alchemy-domain

这是一套使用 `@alchemy.run/domain` 构建后端的工作流技能,以类型化消息和状态转换建模。它指导开发者从需求分析、领域建模和故事测试,推进到 Cloudflare 托管及 foldkit 前端。

主要功能

  1. 01需求拆解为领域模型
  2. 02类型化领域构件
  3. 03基于故事的行为测试
  4. 04Cloudflare 托管指导
  5. 05foldkit 前端工作流

适用场景

  1. 01建模命令与业务事件
  2. 02通过故事测试领域行为
  3. 03在 Cloudflare 上托管模型
  4. 04用 foldkit Web 应用驱动模型

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/alchemy-run/alchemy --skill alchemy-domain

安装后在 Claude Code 中输入/alchemy-domain确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
building-blocks.md
frontend.md
hosting.md
layout.md
modeling.md
testing.md

详细介绍

使用 @alchemy.run/domain 构建

@alchemy.run/domain 将后端建模为类型化消息和纯状态转换。命令由聚合决定,并由聚合记录事件。视图将事件折叠为读取模型。策略对事件作出响应。Saga 负责运行长期流程。投影将数据写入模型之外,查询则提供写入的数据。同一模型既可在内存中运行以供测试,也可在生产环境中运行于 Cloudflare Durable Objects。

文档:https://alchemy.run/domain,所有页面都列在 https://alchemy.run/llms.txt 中。这里的每个章节和参考文件中的每个章节末尾都有一个 More 链接,指向深入介绍该主题的文档页面。完整的参考项目:alchemy 仓库中的 examples/cloudflare-domain(一个带有 foldkit 应用的银行)。

工作流

按以下顺序进行。每个步骤都有一个包含模板的参考文件。

  1. 将需求拆解为构建模块。参见 references/modeling.md。
  2. 规划 monorepo:一个领域包、一个服务端包和一个 Web 包,每个构建模块对应一个带有类型后缀的文件。参见 references/layout.md。
  3. 根据模板编写每个构建模块。参见 references/building-blocks.md。
  4. 在实现之前及实现过程中,用故事描述行为。参见 references/testing.md。
  5. 从服务端包在 Cloudflare 上托管模型,导出其追踪数据,并通过一个 alchemy stack 部署。参见 references/hosting.md。
  6. 在 Web 包中通过 foldkit 驱动模型。参见 references/frontend.md。

更多内容:教程,第 1 部分。

从需求到构建模块

将需求视为一连串事实来阅读,并为找到的每种事物命名:

  • 业务记录的事实,用过去时命名(MoneyWithdrawn):一个 事件。
  • 请求更改某项内容的请求,用 <动词><名词> 命名(WithdrawMoney):一个 命令。
  • 拒绝请求的理由(InsufficientFunds):一个 拒绝。
  • 规则必须在单个事务中成立的事物(某个账户的余额):一个 聚合。它根据自身状态决定如何处理命令。
  • 某人提出的问题(余额、账单、仪表板):一个 视图,按其所涉及的对象作为键。
  • “每当 X 发生,就执行 Y”:一个 策略。
  • 包含步骤、等待和撤销操作的流程(转账:扣款、入账、失败时退款):一个由策略启动的 Saga。
  • 调用的模型外部事物(欺诈评分、支付):一个 外部系统。
  • 写入模型外部存储的内容(分类账表):一个 投影。按键排序且可读取的日志(账户卡片)是投影追加写入的 流。
  • 跨键查询并从该存储获取答案(按页查看某个账户的分类账;按金额从大到小查看所有账户的大额取款):一个 查询。绝不要手写路由。
  • 谁能执行什么、查看什么:一个列出命令和读取操作的 Api,并带有中间件(会话、MFA),且每个构建模块都自行授权:在 decide 中授权命令,在其 Layer 的 validate 中授权视图或流,在处理器中授权查询。

跨聚合的规则绝不能变成跨聚合事务。为不变量建立专属聚合,并以其保护的对象作为键(例如以用户名为键的 UsernameClaim)。或者先预留再提交,并在策略或 Saga 中为所有等待操作设置截止时间。

更多内容:什么是领域驱动设计?、领域模型。

约定

  • 每个文件一个代码块,命名为 <Noun>.<kind>.ts。 类型包括 .aggregate、.command、.view、.policy、.saga、.projection、.stream、.query、.external、.api 和 .model,以及 .middleware、.context 和 .rejection。聚合的 ID 存放在其旁边的 <Aggregate>.id.ts 中。普通值(Money.ts)和辅助工具使用普通名称。
  • 命令文件包含命令、它记录的事件,以及仅由它返回的拒绝。 多个命令共用的拒绝或事件应放在各自的文件中。
  • 代码块的实现放在其文件中(TransferExecution 位于 TransferExecution.policy.ts),除非涉及基础设施;这种情况下,实现在服务端包中。
  • 文件夹对应子域(Account/、Transfer/)。模型、Apis、中间件和请求上下文放在包根目录。
  • 领域包只导入 @alchemy.run/domain 和 effect。 Cloudflare、Stripe、数据库和 HTTP 会话处理都属于服务端包。
  • 每个模型(有界上下文)对应一个领域包。 第二个模型应使用第二个领域包;一个服务端可以承载多个领域包。

更多信息:项目布局。

确保模型正确的规则

  • decide 是纯函数,或者是只读取命令中间件所提供内容(yield* CurrentActor)的 Effect.fn。它返回事件或拒绝,不执行任何 I/O。
  • evolve 只将事件应用于状态,不做任何决策。它和 reply 都是全函数:覆盖所有状态且永不失败,因为 decide 已经拒绝了不可能发生的情况。对于事件无法作用的状态,应保持原样(s._tag === "Open" ? { ...s, balance: e.balanceAfter } : s),切勿使用会抛出异常的守卫。
  • 事件携带 decide 得出的结果(balanceAfter),因此 evolve 直接复制该值,而不是重复执行规则。
  • 命令可能返回的每种拒绝都应列在其 rejects 中。调用方使用 Effect.catchTag 处理每种拒绝。
  • 为投影写入的数据存储提供服务时,应通过 Api 上的 Domain.Query,而不是手写路由:其处理器会检查调用方身份,并限定所读取的行。
  • 客户端永远不会读取聚合。应通过视图(单个键、实时)、流或查询来读取;查询的处理器可以使用 yield* Account 获取聚合的最新状态,然后调用 accounts.get(ref)。
  • 策略和投影至少会运行一次。确保其副作用具备幂等性:处理器发出的命令使用确定性 ID,存储中的行以 stream:seq 为键。
  • 不要在策略中等待 saga。启动 saga(start),并以其关联的聚合为键,然后对其各步骤记录的事件做出响应。
  • 处理器在 toLayer 中解析一次依赖项,且每次调用都不需要额外依赖。
  • 构建一个 Layer,并只提供一次(Layer.mergeAll、Layer.provide)。切勿链式调用 Effect.provide。
  • 切勿使用 throw。使用带标签的错误(Domain.Rejection、Schema.TaggedError)表示失败。
  • 跟踪会自动完成。仅在处理器中使用 Effect.annotateCurrentSpan 记录负载值:这些值会写入该处理器对应的引擎 span。使用 Schema.Redacted 标记机密,使其永远不会出现在跟踪或日志中。
  • 通过故事测试模型的行为:事件、回复、拒绝、视图和 saga 结果。不要测试 saga 的内部步骤。

更多信息:领域模型:由聚合做出决策,并一次性提交所有内容、Cloudflare:依赖这些保障。

变更检查清单

  1. 命名新的事实、命令和拒绝。将每条命令分别放入自己的 <Command>.command.ts 中,并包含其事件和拒绝。
  2. 将命令添加到其聚合的 commands 中,为其添加 decide 分支,并为每个事件添加 evolve 分支。
  3. 更新关注这些新事件的视图、策略和投影。
  4. 如果客户端会发送或读取该命令,则通过 Api 暴露该命令,并添加所需的中间件,最后一个参数设为 { description }。
  5. 为成功路径、每种拒绝、每种策略响应,以及每个查询的授权编写故事。
  6. 运行领域包的测试,其中一项测试应断言 Domain.verify(Model) 结果为空。然后对整个 monorepo 进行类型检查(tsc -b),并运行服务端和 Web 测试。

评论

支持 Markdown