Files
go-omni/GENERATION.md

6.7 KiB
Raw Blame History

公共字段、中间件工厂与生成选项

本页对应 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。

既有项目迁移

  1. 首次更新保持 middleware: handlers,现有 hook 和手写 Configure 不变。
  2. 改为 factories 并重新生成,会补 dependencies.go 和 *Factory.go,保留原来的手写文件。
  3. 将旧实现迁入工厂,提供 Dependencies 的访问器;移除 Init 中按名字覆盖 map 的代码。
  4. 旧函数不再需要时由项目删除。若新工厂符号与旧文件中的同名函数冲突,整理旧文件;生成器不覆盖业务代码。
  5. 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