大型Fiber项目需按业务域划分internal/目录(如auth、payments),禁用技术层目录;根目录必备cmd/、internal/、pkg/、migrations/;跨模块依赖须接口抽象+构造函数注入;配置须早初始化且非指针化。

大型 Fiber 项目不能靠 main.go 堆逻辑,目录结构混乱直接导致测试难写、模块耦合、CI 失败率高、新人上手三天还找不到用户注册逻辑在哪。
按功能域而非技术层划分 internal/ 子目录
别建 controllers/、models/、services/ 这种跨业务的“技术层目录”——Fiber 不是 Rails 或 Spring Boot,强行套用只会让 auth 和 payments 模块互相 import 实体或 handler,一改全崩。
- 每个子目录必须对应一个真实业务能力,例如
internal/auth/、internal/payments/、internal/notifications/ - 每个业务域下固定包含
handlers/(只处理 HTTP 请求/响应)、services/(纯业务逻辑,无 HTTP 依赖)、repositories/(仅封装 DB/Cache 调用)、validators/(独立验证规则) - 即使某个模块当前没数据库操作,也必须保留
repositories/目录——否则后续加 DB 时要改 import 路径、重构测试桩,成本翻倍
根目录四件套缺一不可:cmd/、internal/、pkg/、migrations/
这四个目录不是“建议”,而是构建可维护性的基础设施。少一个,go test 就无法隔离单元测试,go build -o bin/app cmd/main.go 就会失败,CI 流程也得重写。
-
cmd/:只放main.go和启动逻辑(加载配置、注入依赖、调用app.Listen()),禁止写任何业务代码 -
internal/:所有不对外暴露的业务实现,Go 的internal包机制会天然阻止外部 module import,这是强约束 -
pkg/:放可复用的中间件(如 JWT 验证器)、工具函数(如时间格式化)、第三方适配器(如 Stripe client 封装),它会被多个internal/*模块 import -
migrations/:存20240101_create_users_table.up.sql类型的迁移脚本,配合github.com/golang-migrate/migrate/v4使用,不要手写db.Exec("CREATE TABLE...")
跨模块依赖必须接口抽象 + 构造函数注入
禁止 internal/auth/services/login_service.go 直接 import "yourproject/internal/users/repositories"——这不是“方便”,是给未来埋雷。一旦 users 模块重构 UserRepo 接口,auth 模块立刻编译失败,且无法单独测试。
- 在
pkg/interfaces/(或模块内定义)声明type UserReader interface { GetByID(ctx context.Context, id int) (*User, error) } -
auth/services/login_service.go只依赖该接口,构造函数接收ur UserReader参数 - 启动时在
cmd/main.go中 newusers/repositories/user_repo.go实例,传给 login service 初始化 - 单元测试时可直接传入 mock 实现,不碰 DB、不启 HTTP server
配置初始化必须早于任何业务模块加载
常见错误是把 config.Load() 放在某个 handler 里首次调用——结果 internal/payments/services/charge_service.go 读 config.StripeKey 时 panic,但堆栈不显示哪行触发,只报 nil pointer dereference。
- 在
cmd/main.go最顶部调用config.MustLoad()(带 panic 的校验版) - 配置结构体字段全部设为非指针(
Port int而非*int),避免零值误用 - 环境变量名统一前缀(如
APP_HTTP_PORT、DB_URL),config/env.go中用os.Getenv()显式读取并转换,不依赖自动绑定库 - 所有模块通过参数接收
*config.Config实例,禁止全局变量或单例模式
最易被忽略的是:模块间边界不是靠目录名体现的,而是靠 Go 的 internal 包规则 + 接口抽象 + 启动时依赖注入这三层硬约束共同守住的。少一层,半年后项目就变成“牵一发而动全身”的状态。


















