ZITADEL internal/ 后端架构与 AI Agent 开发规范:分层边界、Source of Truth 与 Nx 验证链路
【免费下载链接】zitadelZITADEL - Identity infrastructure, simplified for you.项目地址: https://gitcode.com/GitHub_Trending/zi/zitadel
本文以 ZITADEL 仓库中面向 AI Agent 的 internal/AGENTS.md 为纲,系统解读internal/后端代码的目录职责、三条"事实来源"(Source of Truth)原则、命令/查询/仓库三层边界规则,并结合apps/api的 Nx 工程配置还原lint、test-unit、test-integration三个验证目标的真实执行链路。读完后,你不仅能按该规范安全地修改 ZITADEL 后端,还能理解"关系表即记录系统(system of record)+ 事件写保留"这一核心架构模式在源码中的落地方式。
1. internal/ 是什么:ZITADEL 后端域逻辑的承载层
internal/AGENTS.md 开宗明义地给出上下文定义:
internal/contains core backend domain logic for ZITADEL: commands, queries, repositories, eventstore integration, API service layers, and supporting infrastructure.
对照仓库实际目录结构,这句话完全对得上。internal/下按职责切分为若干包:
- internal/command/:CQRS 中的 Command 侧。核心入口是 internal/command/command.go 中定义的
Commands结构体——它持有eventstore *eventstore.Eventstore、权限检查checkPermission domain.PermissionCheck、加密算法、通知发送器等依赖,所有业务写入(实例、组织、用户、项目、会话等)都在这里编排。目录内按实体命名成对出现xxx.go(命令实现)与xxx_model.go(事件模型),例如org.go+org_model.go、user_auth.go+user_grant_model.go,并配有xxx_test.go单元测试; - internal/query/:CQRS 中的 Query 侧,包含各类查询对象(
user.go、org.go、project_grant.go、introspection.go等)以及projection/投影实现,负责把事件流折叠(reduce)成可查询的当前状态; - internal/eventstore/:事件存储内核。从顶层定义可见其构成:
event.go/event_base.go(事件定义)、aggregate.go(聚合与乐观并发)、push.go(事件写入)、query.go(事件读取)、read_model.go/write_model.go(读写模型抽象)、lock.go/unique_constraints.go(锁与唯一约束)、queue.go(队列驱动); - internal/repository/:仓储层,约 200 个 Go 文件,是查询侧读取关系表与事件的落地实现;
- internal/api/:传输适配层,按协议划分——
grpc/(由 proto 生成的 gRPC/connectRPC 存根,约 530 个文件)、oidc/、saml/、scim/、http/、ui/等; - 支撑性基础设施:internal/crypto/、internal/domain/、internal/database/、internal/notification/、internal/i18n/ 等。
这种"command/query 分离 + eventstore 内核 + 薄 API 适配"的组织方式,正是下节边界规则的存在依据。
2. Source of Truth:动手前必须核对的三份事实来源
internal/AGENTS.md 的 "Source of Truth" 一节列出了三条强制性的事实来源,逐条拆解如下。
2.1 Go 工具链:先读根目录 go.mod
规范第一条要求"Inspection of rootgo.modbefore Go work"。仓库实际内容印证了这是一个对工具链敏感的项目:
// go.mod(仓库根目录) module github.com/zitadel/zitadel go 1.25.0 toolchain go1.25.11关键信息有三点:模块路径为github.com/zitadel/zitadel(所有import "github.com/zitadel/zitadel/internal/..."都基于此);语言版本为 Go 1.25.0,且锁定了go1.25.11工具链——本地若使用低于此版本的 Go,go build/go test可能自动拉取或失败;依赖里可见connectrpc.com/connect v1.19.2、connectrpc.com/otelconnect,说明 API 传输协议基于 connectRPC(见 4.3 节)。proto/AGENTS.md 中"如果生成或后续修复触碰了 Go 代码,运行 Go 工具前先检查根go.mod"的表述与这条规则相互呼应。
2.2 架构模式:关系数据是记录系统,事件写保留为历史/审计
第二条是整个后端架构最核心的一句:
Relational data is the system of record; keep existing event writes that provide history/audit trails.
即:关系表(projection 生成的当前状态表)是权威数据源,但事件写入不能删,因为它们承载历史与审计能力。在源码中可以直接验证这条原则的落地机制:
- 写入路径:
internal/command通过eventstore.Eventstore.Push(internal/eventstore/push.go)把领域事件写入事件流; - 折叠路径:
internal/query/projection/中的投影处理器消费这些事件。例如 internal/query/projection/administrator_relational.go 中定义了reduceInstanceAdminAdded、reduceOrganizationAdminChanged、reduceProjectGrantAdminRemoved等一系列reduce方法,每个方法把一条管理员工事件翻译成handler.Statement(对关系表的 INSERT/UPDATE/DELETE 语句),从而维持关系表与事件流一致; - 功能开关:internal/feature/feature.go 中存在
EnableRelationalTables bool特性位(键名含enable_relational_tables),说明关系表投影是受 feature flag 控制的渐进式架构迁移——从源码结构看,这正是"从纯事件溯源走向关系表即记录系统"过渡期的证据。
由此得到的实操含义:修改internal/时,若发现既有代码在做事件写入,不要因为"关系表已经是权威"就顺手删掉;删除事件写会破坏历史/审计能力,违反该架构模式。
2.3 API 契约:以 API_DESIGN.md 与 proto/AGENTS.md 为准
第三条指出 API 面向的 schema 决策应遵循 API_DESIGN.md 与 proto/AGENTS.md。前者确立了几个对后端开发有直接影响的原则:
- API first:所有功能必须能通过 API 访问,UI 只是 API 的消费者之一;
- Protobuf + connectRPC:自 V2 API 起以 connectRPC 为主传输协议,同时兼容 gRPC 与 HTTP/1.1;
- 面向资源设计:V2 API 围绕资源(Organization、User、Project 等)设计,每个资源有唯一标识符和属性集合,整个生命周期可由 API 管理;
- 版本策略:服务用主版本号独立版本化,主版本内保证向后兼容,破坏性变更必须开新主版本;新建服务应从 v2 起步(v1 保留给旧的 context 式 API);
- 弃用规范:废弃方法必须设置 OpenAPI 的
deprecated = true选项,并可在 rpc 定义上方以 proto 注释给出替代方法链接与迁移指引。
proto/AGENTS.md 则补充了 proto 变更的工程侧要求:变更后必须验证下游消费方(@zitadel/client、@zitadel/api、@zitadel/docs),并给出三个已验证的 Nx 目标:
pnpm nx run @zitadel/proto:generate # 生成 TS Proto 包 pnpm nx run @zitadel/api:generate # 生成 API 资产/存根 pnpm nx run @zitadel/docs:generate # 生成文档工件也就是说,internal/api/grpc/下的大量生成文件不应手工编辑;契约变更的正确路径是改proto/下定义后走生成流程。
3. 边界规则:业务逻辑该放在哪一层
"Boundary Rules" 一节给出了三条边界约束,它们直接对应internal/的分层:
- 业务行为优先实现在 command/query 层与 repository 包,而不是传输处理器里。落地对照:传输适配层 internal/api/grpc/(约 530 个文件,绝大部分为 proto 生成存根)与
internal/api/http/只负责协议解包、认证上下文提取与错误映射;真正的业务编排在 internal/command/(如org.go、user.go)与 internal/query/ 中完成。若在 handler 里写业务分支,就绕过了Commands中注入的权限检查、加密、通知等横切依赖; - 不要用临时的直接持久化绕过既有的 event/repository 流程。这条与 2.2 节的"关系数据是记录系统"配合理解:新增读模型要走
internal/query/projection/的投影机制(事件 →handler.Statement→ 关系表),新增写路径要走internal/command→ eventstore push,而不是在某个包内私开一条 SQL 直写; - API/服务适配器保持薄,可复用的域行为放进 internal 域包。从
Commands结构体的依赖注入方式(checkPermission、newHashedSecret、idGenerator、eventstore等字段,见 internal/command/command.go)可以推断,域行为被刻意与传输层解耦,便于单测与复用。
4. 验证工作流:三个 Nx 目标背后的真实执行链
internal/AGENTS.md 的 "Validation Workflow" 要求用 API 项目目标来验证后端改动:
pnpm nx run @zitadel/api:lint pnpm nx run @zitadel/api:test-unit pnpm nx run @zitadel/api:test-integration这三个目标定义在 apps/api/project.json 中,逐条展开其实际行为,比规范本身更有实战价值。
4.1 @zitadel/api:lint —— golangci-lint 全量检查
// apps/api/project.json "lint": { "description": "Lints the Go code with golangci-lint using the configuration in .golangci.yaml", "dependsOn": ["lint-install", "generate-stubs", "generate-assets"], "command": "PATH=\"${PWD}/.artifacts/bin/$(go env GOOS)/$(go env GOARCH):$PATH\" golangci-lint run --timeout 15m --config ./.golangci.yaml --verbose", "cache": true }要点:它先依赖generate-stubs与generate-assets(即先执行 proto/静态资产生成,保证生成代码参与检查),再用.golangci.yaml配置运行 golangci-lint,超时上限 15 分钟,且带 Nx 缓存。lint 检查的输入是sources(cmd/**/*.go、internal/**/*.go、proto/**/*.go、pkg/**/*.go、main.go等),因此改完internal/代码后必须过这一关。
4.2 @zitadel/api:test-unit —— 带竞态检测的单元测试
// apps/api/project.json "test-unit": { "description": "Runs the unit tests with coverage", "dependsOn": ["generate"], "command": "go test -race -coverprofile=profile.api.test-unit.cov -coverpkg=./internal/...,./backend/... ./..." }要点:
- 先依赖
generate(proto 代码生成),避免"改了 proto 没生成就测"造成的假阴性; -race开启竞态检测,-coverpkg=./internal/...,./backend/...说明覆盖率统计明确覆盖internal/全部包——这正是"你改的就是被量化的部分"的体现;- 产物
profile.api.test-unit.cov声明为 Nx 输出,命中缓存时可跳过重复执行。
4.3 @zitadel/api:test-integration —— 端到端集成测试链路
集成测试是三者中最重的一个,apps/api/project.json 将其拆成一条"数据库 + 缓存 → 构建 → 起服务 → 跑测试"的依赖链:
test-integration-build:以-tags integration -race -cover编译出独立测试二进制zitadel.test;test-integration-run-db:通过nx run @zitadel/devcontainer:compose up ... db-api-integration cache-api-integration拉起集成测试专用数据库与缓存容器(continuous 任务,长期运行);test-integration-run-api:以test-integration-api配置启动 API 服务,环境变量含GOCOVERDIR(覆盖率数据目录)与GORACE(竞态日志路径),配置来源为 apps/api/test-integration-api.yaml;test-integration:依次执行——wait-on ... "${ZITADEL_API_URL}/debug/ready":轮询/debug/ready就绪端点,超时 30 分钟;go test -race -count 1 -tags integration -timeout 60m -parallel 1 $(go list -tags integration ./... | grep -e "integration_test"):串行、禁用测试缓存地运行所有integration_test包(注释说明原因是"测试针对进程外的 API 运行");go tool covdata textfmt ...把覆盖率数据转成profile.api.test-integration.cov。
这意味着集成测试不是纯 Go 进程内测试,而是真实的"PostgreSQL + 缓存 + 独立 API 进程"组合;涉及 connectRPC 端点、事件持久化、投影折叠的改动必须能在这条链路上跑通。测试入口目录为 internal/integration/,其中2 *.pem与测试配置佐证了 TLS 与外部服务依赖。
4.4 验证顺序建议
结合三条 Source of Truth 与边界规则,一次典型的internal/改动可按如下顺序验证:
# 0. 若改了 proto 契约:先生成 pnpm nx run @zitadel/api:generate # 1. 静态检查 pnpm nx run @zitadel/api:lint # 2. 单元测试(竞态 + 覆盖率) pnpm nx run @zitadel/api:test-unit # 3. 集成测试(需要 Docker,自动拉起 db/cache 容器与 API 进程) pnpm nx run @zitadel/api:test-integration5. 小结:把规范读成工程约束
internal/AGENTS.md 篇幅不长,但它把 ZITADEL 后端的工程纪律压缩成了四条可执行约束:
| 规范条目 | 工程含义 | 仓库佐证 |
|---|---|---|
先读go.mod | Go 1.25.0 / toolchain go1.25.11,模块路径决定 import 前缀 | go.mod |
| 关系数据是记录系统,保留事件写 | 读路径走 projection 折叠的关系表,写路径必须继续走 eventstore push | internal/query/projection/administrator_relational.go、internal/eventstore/push.go |
| API 契约遵循 API_DESIGN.md / proto/AGENTS.md | API first、面向资源、connectRPC、主版本向后兼容 | API_DESIGN.md、proto/AGENTS.md |
| 业务逻辑放 command/query/repo,handler 保持薄 | 传输层只做协议适配 | internal/command/command.go、internal/api/grpc/ |
| 用三个 Nx 目标验证 | lint → 带 race 的单元测试 → 进程外集成测试链 | apps/api/project.json |
遵循这套约束,你的改动将同时满足"架构一致性(不破坏事件/投影流)"与"可验证性(三个目标全绿)"两条底线,这正是该 Agent 规范想要保障的。
【免费下载链接】zitadelZITADEL - Identity infrastructure, simplified for you.项目地址: https://gitcode.com/GitHub_Trending/zi/zitadel
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考