启动流程与 AppProvider

启动流程与 AppProvider

go-web 的启动流程围绕两个核心概念展开:Assembly(装配链)Server(服务链)。前者负责把一切基础设施(配置、日志、数据库……)注册到 DI 容器,后者负责真正运行长连接服务(迁移、HTTP 服务器)。

整个流程由 AppProvider 接口声明,默认实现是 config/app.go 中的 App 结构体。

整体流程

main.go
  └── gomander.Run(func() {
        cmd.Start(
          cmd.WithTemplateFs(...),
          cmd.WithWebStaticFs(...),
          cmd.WithApp(config.App{}),
        )
      })
        └── cmd.Start (cmd/run.go)
              ├── 注册信号: SIGINT / SIGTERM / SIGHUP
              ├── 循环:
              │   ├── initializeServices(o)
              │   │   ├── container.RegisterAssemblies(App.Assemblies())
              │   │   │     # Env → Config → Logger → Database → Redis → Cache
              │   │   ├── config.Set("static.fs", o.StaticFs)
              │   │   └── for s := range App.Servers(): s.Run()
              │   │         # Migration(goose.Up) → HttpServer(Gin)
              │   └── select {
              │         case sig := <-sigChan:
              │           SIGHUP  → stop + reloadAssemblies + continue
              │           SIGINT/SIGTERM → stop + return
              │         case <-reload.GetReloadChan():
              │           stop + reloadAssemblies + continue
              │       }

AppProvider 接口

type AppProvider interface {
    Assemblies() []AssemblyInterface
    Servers() []ServerInterface
}

默认实现 config/app.go:

type App struct{}

func (a App) Assemblies() []interfaces.AssemblyInterface {
    return []interfaces.AssemblyInterface{
        &envAssembly.Env{},
        &configAssembly.Config{
            DefaultConfigs: Config{}.Get(),
        },
        &loggerAssembly.Logger{},
        // 以下默认注释,按需开启
        // &databaseAssembly.Database{},
        // &redisAssembly.Redis{},
        // &cacheAssembly.Cache{},
    }
}

func (a App) Servers() []interfaces.ServerInterface {
    return []interfaces.ServerInterface{
        // &migration.Migration{},
        &service.HttpServer{},
    }
}

提示:DatabaseRedisCacheMigration 默认在源码中处于注释状态。当你需要时,把对应行解开即可。这种"按需启用"的设计避免了空跑一个 Demo 时被强制要求 MySQL/Redis 已就绪。

Assembly 装配链

AssemblyInterface 用于把 Provider 注册进 DI 容器:

type AssemblyInterface interface {
    Register() error
}

每个 Assembly 在 Register() 中调用 container.Register(...) 把对应的 Provider 注册到全局容器。container.RegisterAssemblies 内部会按顺序遍历执行,并通过拓扑排序解决依赖关系。

标准装配链顺序:

# Assembly 注册的服务 依赖
1 envAssembly.Env gsr.EnvReader(Viper)
2 configAssembly.Config gsr.Provider(配置中心) Env
3 loggerAssembly.Logger gsr.Logger(Zap) Config
4 databaseAssembly.Database *gorm.DB Config + Logger
5 redisAssembly.Redis Redis 客户端 Config + Logger
6 cacheAssembly.Cache gsr.Cacher Config + Redis

Server 服务链

ServerInterface 用于运行长连接服务:

type ServerInterface interface {
    Run() error
    Stop() error
}

标准服务链:

  1. migration.Migration —— 启动时执行 goose.Up,把所有未应用的迁移跑完(详见「数据库迁移」章节)
  2. service.HttpServer —— 启动 Gin HTTP 服务,挂载 http.router 中注册的路由

Run() 一般是非阻塞的(HttpServer 内部以 goroutine 启动 Gin),Stop() 在收到信号或热重载时被调用。

信号处理

信号 行为
SIGINT / SIGTERM 停止所有 Server,程序退出
SIGHUP 停止所有 Server → 重新装配容器 → 重新启动 Server(等价于热重载)
reload.GetReloadChan() SIGHUP 等价的程序内触发通道,可在业务代码中 reload.GetReloadChan() <- struct{}{} 主动触发热重载

热重载实质是:container.ReloadAssemblies 会先 ResetAll() 清空所有已实例化的服务(若实现了 Destroyable 则调用 Destroy()),再重新执行装配链。这意味着热重载后所有单例都是全新的实例,可读取最新的配置。

自定义 AppProvider

要自定义启动流程(例如增加 gRPC server、跳过迁移、加入额外装配),只需实现 AppProvider 并在 main.go 中通过 cmd.WithApp 传入:

type MyApp struct{}

func (MyApp) Assemblies() []interfaces.AssemblyInterface {
    return []interfaces.AssemblyInterface{
        &envAssembly.Env{},
        &configAssembly.Config{DefaultConfigs: config.Config{}.Get()},
        &loggerAssembly.Logger{},
        &databaseAssembly.Database{},
        &myAssembly.Kafka{}, // 自定义装配
    }
}

func (MyApp) Servers() []interfaces.ServerInterface {
    return []interfaces.ServerInterface{
        &migration.Migration{},
        &service.HttpServer{},
        &myService.GrpcServer{}, // 自定义服务
    }
}

func main() {
    gomander.Run(func() {
        cmd.Start(cmd.WithApp(MyApp{}))
    })
}

Functional Options

cmd.Start 接受以下 options(定义在 cmd/options.go):

Option 用途
cmd.WithApp(AppProvider) 指定 AppProvider 实现
cmd.WithTemplateFs(embed.FS) 注入嵌入模板 FS
cmd.WithWebStaticFs(embed.FS) 注入嵌入静态资源 FS
cmd.WithStaticFs(...) 同上的别名

注入的 FS 在装配完成后被写入 static.fs 配置项,供 HttpServer 读取并挂载。