启动流程与 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{},
}
}提示:
Database、Redis、Cache、Migration默认在源码中处于注释状态。当你需要时,把对应行解开即可。这种"按需启用"的设计避免了空跑一个 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
}标准服务链:
migration.Migration—— 启动时执行goose.Up,把所有未应用的迁移跑完(详见「数据库迁移」章节)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 读取并挂载。