news 2026/9/14 13:35:26

ZITADEL internal/ 后端架构与 AI Agent 开发规范:分层边界、Source of Truth 与 Nx 验证链路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ZITADEL internal/ 后端架构与 AI Agent 开发规范:分层边界、Source of Truth 与 Nx 验证链路

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 工程配置还原linttest-unittest-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.gouser_auth.go+user_grant_model.go,并配有xxx_test.go单元测试;
  • internal/query/:CQRS 中的 Query 侧,包含各类查询对象(user.goorg.goproject_grant.gointrospection.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.2connectrpc.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 中定义了reduceInstanceAdminAddedreduceOrganizationAdminChangedreduceProjectGrantAdminRemoved等一系列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/的分层:

  1. 业务行为优先实现在 command/query 层与 repository 包,而不是传输处理器里。落地对照:传输适配层 internal/api/grpc/(约 530 个文件,绝大部分为 proto 生成存根)与internal/api/http/只负责协议解包、认证上下文提取与错误映射;真正的业务编排在 internal/command/(如org.gouser.go)与 internal/query/ 中完成。若在 handler 里写业务分支,就绕过了Commands中注入的权限检查、加密、通知等横切依赖;
  2. 不要用临时的直接持久化绕过既有的 event/repository 流程。这条与 2.2 节的"关系数据是记录系统"配合理解:新增读模型要走internal/query/projection/的投影机制(事件 →handler.Statement→ 关系表),新增写路径要走internal/command→ eventstore push,而不是在某个包内私开一条 SQL 直写;
  3. API/服务适配器保持薄,可复用的域行为放进 internal 域包。Commands结构体的依赖注入方式(checkPermissionnewHashedSecretidGeneratoreventstore等字段,见 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-stubsgenerate-assets(即先执行 proto/静态资产生成,保证生成代码参与检查),再用.golangci.yaml配置运行 golangci-lint,超时上限 15 分钟,且带 Nx 缓存。lint 检查的输入是sourcescmd/**/*.gointernal/**/*.goproto/**/*.gopkg/**/*.gomain.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 将其拆成一条"数据库 + 缓存 → 构建 → 起服务 → 跑测试"的依赖链:

  1. test-integration-build:以-tags integration -race -cover编译出独立测试二进制zitadel.test
  2. test-integration-run-db:通过nx run @zitadel/devcontainer:compose up ... db-api-integration cache-api-integration拉起集成测试专用数据库与缓存容器(continuous 任务,长期运行);
  3. test-integration-run-api:以test-integration-api配置启动 API 服务,环境变量含GOCOVERDIR(覆盖率数据目录)与GORACE(竞态日志路径),配置来源为 apps/api/test-integration-api.yaml;
  4. 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-integration

5. 小结:把规范读成工程约束

internal/AGENTS.md 篇幅不长,但它把 ZITADEL 后端的工程纪律压缩成了四条可执行约束:

规范条目工程含义仓库佐证
先读go.modGo 1.25.0 / toolchain go1.25.11,模块路径决定 import 前缀go.mod
关系数据是记录系统,保留事件写读路径走 projection 折叠的关系表,写路径必须继续走 eventstore pushinternal/query/projection/administrator_relational.go、internal/eventstore/push.go
API 契约遵循 API_DESIGN.md / proto/AGENTS.mdAPI 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 13:34:36

手部几何识别实战:基于Matlab的图像预处理与BP神经网络实现

简介:基于Matlab实现的手部几何特征识别系统,面向生物识别、人机交互与无障碍技术开发者,提供从图像捕获、预处理、特征提取到匹配识别的完整算法流程,可作为课程设计或科研入门参考。压缩包共30个文件,约1.25MB&#…

作者头像 李华
网站建设 2026/9/14 13:32:38

SNMP协议栈选型:Net-SNMP与国产自研SDK的信创适配之道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 13:31:30

合并两个有序数组:从暴力排序到双指针原地归并的保姆级教程

合并两个有序数组这道题,在 LeetCode 上挂着 Easy 的标签,但真到了面试现场,它能淘汰的人远比想象中多。我印象很深,有一次候选人把“先合并再排序”写出来,然后理直气壮说这就是最优解,我追问了一句“那如…

作者头像 李华
网站建设 2026/9/14 13:27:03

MATLAB滑动窗口技术:高效数据预处理与特征提取

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 13:22:51

SSM框架实战:网上报销系统源码深度解析

简介:基于Java SSM(Spring、SpringMVC、MyBatis)与MySQL实现的网上报销系统,面向毕业设计、课程设计及Java Web初学者,可用于学习SSM整合、审批流程和数据库设计。资源共303个文件,约12.17MB,包…

作者头像 李华
网站建设 2026/9/14 13:22:10

Scalar vs Stainless:Stainless 停运后的 SDK 生成器能力对比与迁移承接

Scalar vs Stainless:Stainless 停运后的 SDK 生成器能力对比与迁移承接 【免费下载链接】scalar Scalar is an open-source API platform:                                       🌐 Modern REST API Client …

作者头像 李华