6.7 KiB
公共字段、中间件工厂与生成选项
本页对应 v0.19.0-preview。CLI 与框架依赖固定到同一版本,安装见 README。
一份项目生成配置
新项目根目录的 omni.gen.json 只描述生成选项:
{
"repository": "none",
"middleware": "factories"
}
repository: none默认不创建空 Repository 文件;group按 group 补齐可编辑骨架。已有文件始终保留;切回 none 不会删除业务文件。SQL、ORM 和仓储划分由项目决定。middleware: factories是新项目默认值,自动调用带依赖的工厂。handlers保留原有Defaults()及手动注册方式,供既有项目迁移。- 该文件与
.api一起提交。generate/check/dry-run 和 workspace 采用相同规则;validate/export/format 只处理接口契约。 - 旧 manifest 没有配置时补
middleware: handlers、repository: none。已有配置不能直接删除,避免意外切换中间件模式。 - v0.19 已删除
mappings和mappingFiles;遇到旧配置会明确报错,不读取子映射文件或执行映射生成。处理步骤见 copyx 迁移。
公共字段组合
type EmployeeFields {
// 员工姓名。
Name string `json:"name" binding:"max=80"`
Salary Money `json:"salary"`
}
type EnterpriseRequest {
EmployeeFields
}
type AdminRequest {
EmployeeFields
CompanyID string `path:"companyId"`
}
Money 由项目按既有 scalar 语法声明。组合行只有已声明的对象类型名,不加指针或字段 tag。公共对象可以通过已有相对 import 或声明过的共享根 @common/... 引入。
生成器展开为普通 Go 字段。JSON 保持平铺;字段的绑定来源、校验、注释、可空性和 OpenAPI 使用同一份展开结果。不会在 JSON 中增加 EmployeeFields 一层。重复 Go 字段、重复来源字段名、循环组合、未知对象和过深展开均失败;即使重复字段类型相同,也不能隐式覆盖。要改变某个字段的规则,应拆出更小的公共组合。
允许多层组合,上限 32 层、单类型 4096 字段、全契约展开 65536 字段。Go 匿名指针嵌入并非本功能;生成 DTO 中不会使用匿名嵌入。
DTO 与业务类型
普通 Logic 直接接收生成的 Request、返回生成的 Response,不额外创建相同结构。只有独立核心或明确不同的输入/输出职责才保留业务类型。
必要转换直接写 Go,或用可选 copyx.Copy(&target, source)。同名同类型字段自动赋值,字段改名和类型变化通过 Go 参数、转换函数明确设置,见 完整示例。无需 JSON 映射配置、包路径字符串或额外生成文件。
copyx 在运行时检查类型和字段,不提供旧映射生成阶段的静态字段覆盖保证;CI 应给实际类型对添加测试。普通业务字段新增通常只改 .api 和 Logic;独立 DTO/领域类型都需要此字段时,两边各加一次,同名同类型无需追加拷贝配置。
中间件工厂自动注册
.api 沿用 middleware: RequireAccess 或 auth 对应的中间件声明。新项目会生成可编辑的 internal/middleware/requireAccessFactory.go,只需实现一次:
// internal/middleware/dependencies.go,由项目维护。
type AccessChecker interface {
CheckAccess(context.Context, string) error
}
type Dependencies interface {
AccessChecker() AccessChecker
}
// internal/middleware/requireAccessFactory.go,由项目维护。
func NewRequireAccess(deps Dependencies) (httpx.Middleware, error) {
checker := deps.AccessChecker()
if checker == nil { return nil, errors.New("access checker is required") }
return func(next httpx.Handler) httpx.Handler {
return func(c *httpx.Context) error {
if err := checker.CheckAccess(c.Context(), c.Header("Authorization")); err != nil {
return err
}
return next(c)
}
}, nil
}
// internal/svc/access.go,由项目维护;ServiceContext.Init 初始化 c.Auth。
func (c *ServiceContext) AccessChecker() middleware.AccessChecker { return c.Auth }
编译器检查 ServiceContext 是否实现依赖接口,middleware 不导入 svc。多个工厂可以共用这些小接口。所需 imports 和具体鉴权服务仍属于项目。
启动顺序为:框架基础依赖 → svc.Init → 生成的 middleware.New(c) 调用各具名工厂 → 创建 App/注册路由 → 监听。中间件实例创建一次,由请求并发复用;不要把单次请求信息存在实例字段里。工厂之间没有初始化顺序依赖;路由执行顺序仍以 .api 声明为准。
工厂返回错误或 nil 中间件均阻止监听;错误链保留,已登记资源按现有生命周期清理。工厂 panic 也先清理依赖再传播。资源初始化和清理登记放在 svc.Init,工厂借用资源;HTTP 请求排空之前不会关闭这些依赖。
factory 模式不再在 Init 写 Middlewares["..."];残留非空手写注册表会明确报错,防止生成注册静默覆盖它。未实现的工厂让启动失败;未实现的 Logic 仍返回 501。
既有项目迁移
- 首次更新保持
middleware: handlers,现有 hook 和手写 Configure 不变。 - 改为 factories 并重新生成,会补
dependencies.go和*Factory.go,保留原来的手写文件。 - 将旧实现迁入工厂,提供 Dependencies 的访问器;移除 Init 中按名字覆盖 map 的代码。
- 旧函数不再需要时由项目删除。若新工厂符号与旧文件中的同名函数冲突,整理旧文件;生成器不覆盖业务代码。
generate --check与go test ./...检查后再运行。工厂没有全部实现前,服务不会启动。
文件维护边界
| 项目维护 | 生成器持续维护 |
|---|---|
.api 公共字段与接口 |
展开后的 DTO、Handler、OpenAPI |
omni.gen.json 的 Repository/中间件选项 |
对应骨架和注册代码;已有手写骨架不覆盖 |
| 独立 Go 业务类型、scalar、可选 copyx 调用和测试 | 不生成结构转换文件 |
| middleware Dependencies、具名工厂、svc 访问器 | 中间件 registry、启动调用 |
| Logic、Repository、配置、svc 初始化/清理登记 | main、runtime、路由接入 |
独立三端工资契约测试位于 cmd/go-omni/apigen/testdata/composition,不导入任何接入项目。它验证公共字段、Money scalar、三端 Handler、copyx、重新生成保留业务内容,以及新增字段不需要更新转换规则。
GO_OMNI_SCAFFOLD_INTEGRATION=1 go test ./cmd/go-omni/apigen -run TestCopyxThreeEndpointConsumer -count=1