正确初始化方式只有一种:app := fiber.New(),之后按需注册中间件;模块间通信必须基于显式依赖注入,禁止全局变量或fiber.Default();目录结构严格分层为handler/service/repository/model,各层职责隔离;路由应扁平注册并统一挂载中间件;跨模块依赖必须通过interface定义。

fiber.New() 是多模块项目的唯一安全起点
所有模块间通信必须基于显式依赖注入,而不是全局变量或 fiber.Default()。后者自带 Logger、Recover、RequestID 三个中间件,会把 Authorization 头写进日志、拖慢 TTFB 0.3–0.8ms、并在高并发下压垮磁盘 I/O——这不是“开发便利”,是给模块耦合埋雷。
正确初始化方式只有一种:app := fiber.New(),之后按需注册中间件。比如权限校验只挂到 /api 分组,健康检查路由 /health 完全不经过它。
- 模块间传递的不是
*fiber.App,而是初始化好的依赖实例:如*repository.UserRepo、service.UserService、*zap.Logger - 避免在
handler/里 new DB 或调viper.GetString();配置和数据连接应在main.go统一加载,再传入各 service 构造函数 - 每个 service 结构体字段持有一个
log *zap.Logger,而非全局var log *zap.Logger,方便模块级日志采样或静默
四层目录结构必须物理隔离,不能靠 import 路径混淆职责
真实项目里,handler/ 膨胀的根源从来不是“没时间拆”,而是从第一天就把路由、参数解析、DB 查询、错误响应全塞进一个 app.Post("/users", ...) 回调里。半年后你 grep c.BodyParser(&req) 会发现它散落在 7 个文件中,改个字段类型就得同步 5 处校验逻辑。
推荐结构(全部放在 internal/ 下,禁止跨层反向 import):
立即学习“go语言免费学习笔记(深入)”;
-
handler/:只做三件事——读请求(c.Params()/c.Query()/c.BodyParser())、调service方法、写响应(c.JSON()/c.Status().SendString())。不碰 DB、不处理业务规则、不写 if-else 判断权限 -
service/:纯业务逻辑。例如CreateUser(req CreateUserReq) (User, error)里调repo.Create()+mail.SendWelcome()+cache.Invalidate()。它不关心 HTTP,只返回结构体或 error -
repository/:只封装数据操作。GORM 的db.Create()、Redis 的client.Set()全在这里。接收model.User,返回error或原始数据(如int64、[]byte),不返回*fiber.Ctx相关东西 -
model/:定义数据契约。struct 字段带json:tag、GORM 的gorm:tag,但不包含方法。别在这里写func (u User) FullName() string——那是service层的事
分组路由必须显式拼接完整路径,不能依赖嵌套自动补前缀
app.Group("/admin").Group("/api").Group("/v2") 不会生成 /admin/api/v2 路径匹配逻辑——每级 Group() 返回的是独立 *fiber.Router 实例,它不继承父前缀,也不拼接路径。你必须在最终注册时写全量路径,比如 v2.Get("/users", handler) 才真正匹配 /admin/api/v2/users。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
三层以上嵌套本质是维护陷阱,不是语法限制问题:
- 每级
Group()都新建中间件栈,如果每层都Use()同类中间件(如日志、鉴权),实际会重复执行,且顺序难追踪 - 路径字符串硬编码在多处(如
"/admin"出现在app.Group()和所有子路由中),重构时极易漏改 -
StrictRouting: true(默认)下,/admin/api/v2/users/和/admin/api/v2/users是两个不同路由,三层嵌套后这类歧义更难调试
更稳妥的替代方案:app.Use("/admin/api/v2", middleware) 统一挂载中间件(它对所有方法生效,且匹配前缀边界),然后扁平注册所有路由:app.Get("/admin/api/v2/users", handler)、app.Post("/admin/api/v2/orders", handler)。
模块间接口契约必须用 interface 定义,避免直接依赖 concrete type
比如 service.UserService 不应直接持有 *gorm.DB,而应依赖 repository.UserRepository 接口:
type UserRepository interface {
Create(u model.User) error
FindByID(id uint) (model.User, error)
}
这样测试时可轻松 mock,上线时也能切换为 Redis 实现或 gRPC client 实现,而无需动 service 层代码。
常见误用:
- 在
handler/里直接new GORM DB实例:db, _ := gorm.Open()→ 应该由repository/接收初始化好的*gorm.DB作为依赖注入 - service 构造函数接收
*fiber.App→ 它只需要 logger、repo、cache 等具体能力,不需要整个框架实例 - model 层 struct 嵌入
gorm.Model并暴露ID uint字段给外部使用 → 这会让数据库主键泄漏到 API 响应中,应通过 DTO 转换隔离
最易被忽略的一点:所有跨模块调用必须经过 interface 边界,哪怕只是临时加个日志字段——一旦 concrete type 泄漏,后续替换存储引擎或引入新中间件时,你会被迫重写十几个文件。

















