module

package
v0.0.0-...-d283dcf Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 2, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

README

模块开发指南

本文档记录内置模块开发约定,尤其是会通过 gg module copy <name> 复制到业务项目中的模块。

module/<name> 表示一套可复用的通用业务能力,不是单个 model、service 或 middleware 文件的简单集合。一个模块可以由 model、service、middleware、cronjob、provider/adapter、复制清单和接入说明共同组成;这些内容作为整体实现同一个业务域的对外契约。

核心规则

  • 同一个模块通过 gg module add <name> 使用,和通过 gg module copy <name> 后再 gg gen 使用,必须得到相同的数据库表、路由、中间件、和 service 行为。
  • 修改模块时必须同时检查 model、service、middleware、路由、module 注册、复制清单、测试和文档,避免 add 路径正常但 copy 路径缺文件、缺 adapter、缺 middleware。

Design 契约

可复制模块需要同时支持两种使用方式:

  • gg module add <name> 使用框架内置的 module.Register / module.Use / module.NewWrapper 注册模型、服务和路由。
  • gg module copy <name> 先复制 internal/model/<name> 的 DSL Design(),再通过 gg gen 生成业务项目本地的 router、service shell,最后合并框架侧 service 业务代码。

因此,模块的 DSL Design() 必须完整表达内置模块的对外契约,不能只为了生成 service 文件写一个近似定义。

必须保持一致的内容:

  • 路由路径必须一致。Design() 中的 Route(...) 要和内置 module 的 Route()module.NewWrapper(...) 路径一致,包括单复数、连字符、下划线和是否带业务前缀。
  • action 集合必须一致。内置 module 注册了哪些 phase,Design() 就应该声明哪些 action;没有真实语义的 action 不要为了默认 CRUD 随手声明。
  • exact 路由语义必须一致。内置 module 使用 module.Exact(...) 原样注册路径时,Design() 对应 action 必须写 Exact(),避免 gg gen 追加默认 CRUD 后缀。
  • public/auth 语义必须一致。内置 module 的 Pub()module.NewWrapper(..., pub, ...) 是 public 时,Design() 对应 action 必须写 Public();需要登录的 action 不写 Public()
  • request/response 契约必须一致。自定义请求、响应类型要通过 Payload[T]()Result[T]() 写进 Design(),避免 copy 后生成默认模型签名。List、Get 是 HTTP GET 接口,禁止声明 Payload[T](),只声明 Result[T](),service 请求类型统一使用 *model.Empty。Import、Export 委托固定签名的 service 方法,禁止声明 Payload[T]()Result[T]()
  • service 文件目标必须一致。存在自定义 service 代码的 action 必须写 Service();如果多个 action 共用一个 service 文件,所有相关 action 都要写相同的 Filename(...)

装配一致性

Design() 只覆盖模型、路由和 service。Register() 里其余的装配语句——安装框架钩子、挂中间件——copy 不会自动复现,必须按下面的规则声明,否则 add 路径正常而 copy 路径静默缺失。

  • 模块的 internal/service/<name> 禁止用 init() 安装框架钩子。包只在被 import 时才链接,而 copy 路径的 import 来自生成的路由注册,项目屏蔽路由就会连带抹掉它,装配随之静默失效。
  • 需要安装的钩子实现必须导出,由 Register() 显式调用;copy 路径由项目自有装配代码调用同一个函数。
  • 该实现文件如果不服务任何路由,必须写进 module.jsonincludeSourceFiles,否则 copy 不会带上它。
  • copy 路径必须执行的装配调用写进 module.jsonrequiredAssemblygg check 会强制项目做出该调用;postNotes 只保留机器检查不了的接入步骤。
  • 中间件实现必须放在框架 middleware/<file>.go 并导出零参构造函数,Register() 调用它完成注册,同一文件和 handler 写进 module.jsonmiddleware;实现写在 module/<name> 内部会让它只在 add 路径生效。

Service 文件边界

公用辅助代码不要放进具体 action service 文件

开发可复制模块时,多个 service action 共享的函数、类型、常量不要放在某一个具体 action 的 service 文件里,这类代码应放到独立 helper 文件中。

原因是 gg module copy 按类型信息闭包复制 helper 文件(详见 internal/ggmodule 的包文档):被复制文件引用了「声明 service struct 但不被任何 DSL action 复制」的文件时,copy 会在 preflight 直接报错,共享代码必须挪进独立 helper 文件才能通过。

简单规则:

  • action service 文件只放该 action 自身的 service struct、action 方法、hook 方法和强绑定该 action 的私有逻辑。
  • 多个 action 共享的逻辑必须放到非 action service 文件中。
  • provider/adapter 接口可以放在 service helper 文件中,但具体宿主 adapter 不要放在 internal/service/<name>

Provider 和 Adapter 边界

当模块需要宿主项目提供某种能力时,不要让 internal/service/<name> 直接 import 具体业务 model 或基础设施实现。模块 service 只依赖自己定义的最小接口,注册层或项目侧 adapter 负责把宿主实现转换成模块需要的抽象对象。

module/mfa 为例:

  • internal/service/mfa 定义 AccountAdministrator,管理端 MFA 流程只关心「当前操作者是否有权管理目标账号的绑定」。
  • internal/service/mfa 的 admin status/reset 流程通过 currentAccountAdministrator() 完成授权判定,未安装 adapter 时安全默认全部拒绝,不 import IAM 实现。
  • module/mfa/iam_account_administrator.go 是框架内置 IAM adapter,把框架 IAM 的租户管理员规则转换成 servicemfa.AccountAdministrator
  • module/mfa/register.go 安装内置 adapter;业务项目通过 gg module copy mfa 后,应在 service/mfa 之外的项目自有文件里调用 servicemfa.SetAccountAdministrator(...)
  • module/mfa/module.jsonpostNotes 必须提示 copy 后需要补齐项目自有 adapter。

设计新的 provider/adapter 边界时遵守这些规则:

  • service 接口只暴露模块真实需要的数据,不透传宿主项目的完整领域模型或基础设施对象。
  • adapter 命名应该表达被适配的宿主能力,service 接口命名应该表达模块需要的能力;不要在通用文档中规定固定命名模板。
  • service 层必须提供安全默认实现。没有安装 adapter 时应返回明确配置错误,不应 panic,也不应悄悄放行安全流程。
  • adapter 负责调用宿主项目能力并做错误归一;service 负责模块自身状态和业务规则。
  • 如果 copy 后项目必须实现 adapter,需要在 module.jsonpostNotes 中写清楚接入入口和必要步骤,不规定具体文件名。

复制清单

如果模块复制需要跳过框架源文件、复制中间件或输出复制后的接入提示,在模块目录放置 module.json

{
  "copy": {
    "excludeSourceFiles": [
      "internal/model/<name>/<file>.go"
    ],
    "middleware": [
      {
        "sourceFile": "middleware/authz.go",
        "scope": "auth",
        "handler": "Authz"
      }
    ],
    "postNotes": [
      "在项目接入层安装模块所需的 adapter。"
    ]
  }
}
  • excludeSourceFiles 使用 framework root 相对路径,表示 gg module copy 不复制这些源文件,也不让它们参与 model/action 规划。
  • includeSourceFiles 使用 framework root 相对路径,表示这些 service 源文件即使不被任何 action 引用也必须复制。
  • middleware[].sourceFile 只能指向 framework middleware/*.go 源文件,目标固定复制到项目 middleware/ 下的同名文件。
  • middleware[].scope 只能是 globalauth,分别对应 middleware.Register(...)middleware.RegisterAuth(...)
  • middleware[].handlersourceFile 中返回 gin handler 的零参函数名,例如 Authz
  • requiredAssembly[] 声明项目必须做出的装配调用,字段为 import(包全路径)、function(导出函数名)和 reason(缺失后果),由 gg check 强制。
  • postNotes 只在复制成功后输出,用于提示项目侧必须补齐、且机器检查不了的 adapter、配置或初始化步骤。

Documentation

Overview

Package module provides a unified module registration system that automatically registers models, services, and HTTP routes for CRUD operations.

A module consists of three components:

  • Model: Database entity implementing types.Model
  • Service: Business logic implementing types.Service
  • Module: Configuration implementing types.Module

Usage:

  1. Define model (embedding model.Base), request/response types, and service (embedding service.Base)
  2. Implement module with types.Module interface
  3. Call module.Use() with desired route options

Example:

module.Use[*User, *UserReq, *UserRsp](
    &UserModule{},
    module.CRUD(
        consts.PHASE_CREATE,
        consts.PHASE_LIST,
        consts.PHASE_GET,
    ),
)

Route paths are normalized (leading slashes and "api/" prefix are removed). Authentication is controlled by Module.Pub() method.

See module/helloworld for complete examples.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Init

func Init() error

Init releases pending module registrations.

Module registration runs asynchronously because module.Use can be called before the framework router and service registries are initialized. Init only opens that gate; callers that need registered models, services, or routes to be visible must call Wait after Init.

func NewWrapper

func NewWrapper[M types.Model, REQ types.Request, RSP types.Response](route string, param string, pub bool, svc ...types.Service[M, REQ, RSP]) types.Module[M, REQ, RSP]

func Use

func Use[M types.Model, REQ types.Request, RSP types.Response](mod types.Module[M, REQ, RSP], options ...UseOption)

Use registers a module with the framework, automatically setting up model, service, and HTTP route registration for the specified route options.

Type Parameters:

  • M: Model type implementing types.Model
  • REQ: Request type for API operations
  • RSP: Response type for API operations

Parameters:

  • mod: Module instance implementing types.Module[M, REQ, RSP]
  • options: Route options created by CRUD or Exact.

Routes are registered based on mod.Route() and mod.Param(). Authentication is determined by mod.Pub().

Must be called during application initialization, typically in a Register() function.

func Wait

func Wait()

Wait blocks until pending module.Use registrations are complete.

It waits for model, service, and route registration only. Database table creation and seed record insertion are handled separately by the database runtime. Callers that need module-provided tables and seed records to exist must call the database runtime drain after Wait, not before it, because module registration can enqueue new model.Register work.

Types

type UseOption

type UseOption struct {
	// contains filtered or unexported fields
}

UseOption configures how module.Use registers routes for one or more phases.

func CRUD

func CRUD(phases ...consts.Phase) UseOption

CRUD registers phases using the framework's default CRUD route pattern.

func Exact

func Exact(phases ...consts.Phase) UseOption

Exact registers phases against the module route exactly as declared.

type Wrapper

type Wrapper[M types.Model, REQ types.Request, RSP types.Response] struct {
	// contains filtered or unexported fields
}

func (*Wrapper[M, REQ, RSP]) Param

func (w *Wrapper[M, REQ, RSP]) Param() string

func (*Wrapper[M, REQ, RSP]) Pub

func (w *Wrapper[M, REQ, RSP]) Pub() bool

func (*Wrapper[M, REQ, RSP]) Route

func (w *Wrapper[M, REQ, RSP]) Route() string

func (*Wrapper[M, REQ, RSP]) Service

func (w *Wrapper[M, REQ, RSP]) Service() types.Service[M, REQ, RSP]

Directories

Path Synopsis
Package versionmod provides the version API module; the name avoids conflicting with the standard library "runtime/version" package.
Package versionmod provides the version API module; the name avoids conflicting with the standard library "runtime/version" package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL