关于 fitness-functions
EventCatalog Fitness Functions 是一项技能,可将架构规则定义为 TypeScript 检查,并针对 EventCatalog 目录数据运行。团队可查看违规项,并用命令行检查的非零退出码拦截拉取请求;检查对象是目录文档,而非运行中的系统。
主要功能
- 01TypeScript 架构规则
- 02只读目录 SDK 查询
- 03含资源链接的违规报告
- 04错误与警告级别
- 05用于拉取请求门禁的命令行检查
适用场景
- 01执行领域所有权边界规则
- 02检查目录资源间的依赖
- 03标记缺失的所有者或消息通道
- 04在 CI 中拦截目录变更
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ event-catalog/ skills --skill fitness-functions安装后在 Claude Code 中输入/fitness-functions确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/api.mdci.mdexamples.md详细介绍
EventCatalog 适应度函数
适应度函数是用代码编写的架构规则。每个函数都是一个 TypeScript 文件,通过只读 SDK 读取目录,并返回找到的违规项。eventcatalog fitness check 会运行所有函数、打印结果、写入 JSON 报告,并在规则未通过时以非零状态退出,因此可以用这些规则来卡住拉取请求。
团队决定健康的架构应该是什么样;EventCatalog 运行规则并报告结果。
适应度函数检查的是目录记录的架构,而不是正在运行的系统。规则的效果取决于它读取的目录数据。
要求(请先检查)
- 版本 —
@eventcatalog/core5.4.0 或更高版本(检查目录的package.json)。或者,@eventcatalog/cli1.4.0 或更高版本无需 core 即可运行相同的检查(npx @eventcatalog/cli fitness check)。Node.js 20 或更高版本。 - 编辑器类型(可选) — 无需安装其他内容即可运行适应度函数,但要使用类型和自动补全,请将
@eventcatalog/fitness安装为开发依赖项。使用create-eventcatalog创建的目录已包含该依赖项,以及fitness/中的两个入门函数和一个npm run fitness脚本。
适应度函数、代码检查器还是治理?
编写代码前,先选择合适的工具:
| 规则涉及…… | 使用 |
|---|---|
| 单个文件是否有效:frontmatter 字段、必需属性、无效引用、文件布局 | @eventcatalog/linter |
| 拉取请求中发生了什么变化(新增使用者、架构变更、弃用),以及通知 | eventcatalog governance check(governance.yaml) |
| 架构的形态:所有权、命名、边界、依赖关系、谁可以访问什么,涉及多个资源 | 适应度函数(本指南) |
说明
步骤 1:找到目录并检查现有内容
找到目录(包含 eventcatalog.config.js 的文件夹),并检查:
- 是否存在根目录下的
fitness/文件夹,或资源文件夹内的fitness/文件夹?阅读现有函数——复用其中的辅助函数并遵循其风格,不要创建重复规则。 - 适应度函数的 id 在整个目录中必须唯一。列出已在使用的 id。
package.json是否包含fitness脚本?是否已安装@eventcatalog/fitness?.eventcatalog/fitness/是否位于.gitignore中?检查会将报告写入该目录;报告不应提交。
步骤 2:将规则转化为明确的检查
像“服务应拥有自己的数据”这样的规则过于含糊,无法编写代码。请明确具体要求;如有不清楚之处,请询问用户:
- 具体什么情况算违规?(“某个服务向另一个领域系统所属的数据存储写入数据。”)
- **适用于哪些资源?**全部资源、一个领域、一个系统,还是一个服务?
- **仅检查最新版本,还是检查所有版本?**几乎总是只检查最新版本(
{ latestOnly: true })。 - 严重性 —
error会导致检查(和 CI)失败;warning会报告问题,但不会导致失败。如果新规则会在现有目录中发现大量违规项,请先将其设为warning,之后再收紧要求。 - **每条违规应指向什么,以及如何修复?**每条违规都应列出涉及的资源并建议修复方法。
检查目录数据是否支持该规则:如果规则需要 writesTo、owners 或 sends.to,而目录没有记录这些信息,请说明这一点——否则该规则会因缺少数据而始终通过。
第 3 步:选择文件存放位置
文件所在位置决定它接收的 resource,而不是它能查看的内容:每个函数都可以查询整个目录。
| 规则适用于 | 将文件放在 | resource 是 |
|---|---|---|
| 整个目录 | fitness/<name>.fitness.ts(允许使用子文件夹) | undefined |
| 一个领域 | domains/Orders/fitness/<name>.fitness.ts | Orders 领域 |
| 一个系统 | …/systems/checkout-system/fitness/<name>.fitness.ts | 该系统 |
| 一个服务 | …/services/OrderService/fitness/<name>.fitness.ts | 该服务 |
| 一个事件、命令或查询 | …/events/OrderCreated/fitness/<name>.fitness.ts | 该消息 |
| 一个数据存储 | …/containers/payment-database/fitness/<name>.fitness.ts | 该数据存储(type: 'container') |
| 一个特定版本 | …/versioned/1.0.0/fitness/<name>.fitness.ts | 该版本 |
频道、流程、实体、数据产品、ADR 和图表的处理方式相同。规则如下:
- 只有以
.fitness.ts结尾的文件才会运行。其他.ts文件(共享辅助文件)不会运行,因此 fitness 函数可以导入它们。 fitness/文件夹必须直接位于资源文件夹内(与其index.mdx并列)。其他位置的.fitness.ts文件不会运行,并会被报告为诊断信息。- 联合目录(
federated/…)中的 fitness 函数不会运行,联合也不会复制fitness/文件夹。
第 4 步:编写 fitness 函数
// domains/Orders/fitness/no-shared-data-stores.fitness.ts
import { defineFitnessFunction } from '@eventcatalog/fitness';
export default defineFitnessFunction({
id: 'orders-no-shared-data-stores', // unique across the catalog
name: 'Orders services only use Orders data stores',
description: 'Reaching into another domain’s database couples the domains together.',
severity: 'error', // or 'warning'; defaults to 'error'
async run({ resource, catalog }) {
if (resource?.type !== 'domain') throw new Error('Expected to run beside a domain');
// ...read what you need from `catalog`, decide what's wrong...
return [
{
message: 'Order Service writes to payment-database, which is outside the Orders domain',
resources: [
{ type: 'service', id: 'order-service', version: '1.0.0' },
{ type: 'container', id: 'payment-database' },
],
details: 'Optional extra context',
suggestion: 'Get payment data through the Payments API or its events.',
},
]; // [] means the rule passed
},
});
编写时请遵循以下约定:
export default defineFitnessFunction({ ... })—— 每个文件定义一个函数。run可以是同步或异步的。- 返回违规项,不要抛出异常。 空数组表示通过。抛出异常(或返回任何不是
{ message, resources?, details?, suggestion? }数组的值)都会使函数状态变为error,这也会导致检查失败,但表示“无法评估”,而不是“架构有误”。 - 根据
type缩小resource的类型范围(resource?.type === 'domain'会得到类型化的 Domain);如果文件位置不正确,请抛出明确的错误。 - 使用
catalog的读取方法 —— 名称和参数与@eventcatalog/sdk相同:getDomains、getServices、getEvents、getDataStores、getTeams、getGraph、getOwnersForResource、getSchemaForMessage、getResourcesByProperty,……集合获取方法在没有内容时会返回undefined,因此请这样写:(await catalog.getEvents({ latestOnly: true })) ?? []。这里没有写入方法,而且每次调用都会返回副本,因此修改结果不会造成影响。完整列表见:references/api.md。 - 只读取一次集合并构建查找结构(映射或集合),不要在循环中调用 catalog —— 例如,一次性收集每个服务发送的消息,而不是针对每条消息调用
getProducersAndConsumersForMessage。 - 在每个违规项中引用资源,格式为
{ type, id, version? }—— 在 EventCatalog 中,这些资源会从违规项链接出去。类型包括:domain、system、service、agent、event、command、query、flow、channel、entity、container(也接受dataStore)、data-product、adr、diagram、team、user。 - 编写读者可以据此采取行动的消息:指出资源名称(
Order Service sends Order Amended without a channel),并在suggestion中写明修复方法。 - 将共享逻辑放在辅助文件中(例如
fitness/helpers/catalog.ts),并使用相对路径导入。使用import type { ReadOnlyCatalog } from '@eventcatalog/fitness'导入类型。 - 保持函数的确定性:不要发起网络调用、写入文件,也不要依赖当前日期。
常见规则的示例(所有权、命名、生产者、循环依赖、领域边界、扇出、频道、架构、数据访问):references/examples.md。
步骤 5:运行并验证
npx eventcatalog fitness check # or: npm run fitness
npx eventcatalog fitness check --format json
输出会列出每个函数及其结果,然后列出违规项和错误:
EventCatalog Fitness
✓ Every message has an owner Passed
✗ orders / Orders services only use Orders data… 1 violation
• Order Service writes to payment-database, which is outside the Orders domain
Resources: order-service (service), payment-database (container)
Suggestion: Get payment data through the Payments API or its events.
! Every event has a producer 3 warnings
3 fitness functions evaluated
交付前请验证——永远不会失败的规则还不如没有规则:
- 新函数已运行,且状态符合预期。如果显示
Error,请修复(参见故障排除)。 - 结果正确。 手动对照目录文件,检查几个违规项(以及几个通过项)。
- 它确实可能失败。 如果它在当前目录中通过,请确认它能捕获真实的违规情况——与用户一起推演,或者在目录的临时副本中破坏一项资源,然后在那里运行检查。绝不要将测试修改留在用户的目录中。
- 引用均可解析。 显示为
(…, not found in the catalog)的引用,其类型或 ID 有误。
退出代码:0 全部通过或仅有警告,1 某个函数失败或无法评估,2 检查本身无法运行。
步骤 6:在 CI 中运行
在检查目录变更的流水线中,安装依赖后运行 npx eventcatalog fitness check。当规则失败时,它会以 1 退出,从而使作业失败。GitHub Actions、GitLab 和发布报告的示例:references/ci.md。
故障排除
| 症状 | 原因 / 修复方法 |
|---|---|
| 完全没有列出函数 | 文件名不以 .fitness.ts 结尾、不在 fitness/ 文件夹中,或者位于 federated/、node_modules、dist 或点号开头的文件夹下 |
! <file>: Not run: fitness functions must be in a fitness/ folder | 将文件移至根目录下的 fitness/ 文件夹,或直接移至某个资源的文件夹中 |
No resource found for this fitness function: '<folder>' has no index.mdx or index.md | fitness/ 文件夹不是直接位于某个资源的文件夹中 |
… is not in a resource folder (such as domains/ or services/) | 包含 fitness/ 的文件夹不是已识别的资源文件夹 |
The file has no default export | 添加 export default defineFitnessFunction({ ... }) |
Invalid fitness function: \id` must be a non-empty string, …` | 缺少 id/name/run,或者 severity 不是 'error' 或 'warning' |
Another fitness function already has the id '<id>' | ID 必须唯一;按路径排序时,第一个文件会运行,后续文件会报错 |
run() must return an array of violations … / Violation 0: … | run 返回了其他内容,或者某项违规缺少 message,或包含无效的 resources 条目 |
Cannot find module './helpers/…' | 辅助文件的相对路径有误——数一数到目录根目录需要向上几层 |
资源后出现 (…, not found in the catalog) | 引用的 type 或 id 与任何资源都不匹配;该引用会保留,但不会建立链接 |
仅在编辑器中出现 Cannot find module '@eventcatalog/fitness' | 将 @eventcatalog/fitness 安装为开发依赖;检查本身无需它也能运行 |
fitness is not a command | @eventcatalog/core 版本低于 5.4.0——请升级,或使用 npx @eventcatalog/cli fitness check |
信任
Fitness 函数是来自目录的代码,运行时拥有与项目中其他脚本相同的访问权限。无论在本地还是 CI 中,都只能运行你信任的 Fitness 函数。EventCatalog 绝不会在浏览器中、页面请求期间或从联合目录中运行它们。
参考文件
- references/api.md —
defineFitnessFunction、上下文、每个catalog方法、违规项、状态、JSON 报告、CLI 选项和发现规则 - references/examples.md — 常见规则的完整 Fitness 函数示例,包括全局和限定范围的规则,以及共享辅助函数
- references/ci.md — 在 GitHub Actions 和 GitLab 中运行检查、发布报告、读取 JSON 输出