单体 Gin 项目的目录分层规范

一句话结论:单体 Gin 项目分层目标是“依赖单向、职责清晰、可测试、不循环导入”。推荐:cmd 放唯一入口做组装;internal 下按 config/model/dao/service/handler/middleware/router 分层,依赖方向固定为 handler→service→dao→model,中间件只做横切;错误码、响应、日志等放 common。main 里“配置→日志→DB/Redis→service→router→server”手写依赖注入(构造器逐层传参),不用全局变量与魔法 DB。这套结构参考了 Go 官方 internal 约定与常见 project-layout,但不必教条,团队一致即可。

目录结构

cmd/server/main.go          # 入口:读配置、组装依赖、启动+优雅退出
internal/
  config/                   # Config 结构体与 viper 加载(见配置章节)
  model/                    # 表结构/实体,全层共享,避免循环导入
  dao/                      # 数据访问(GORM/Redis 封装),只碰 model
  service/                  # 业务逻辑/事务编排,只依赖 dao 接口
  handler/                  # Gin handler:绑定/校验/调 service/组响应
  middleware/               # 认证、日志、限流、恢复等横切逻辑
  router/                   # 路由注册与中间件装配顺序
  common/                   # errcode、resp、logger 等基础设施
configs/                    # 各环境 yaml

分层规则

职责依赖禁止
handler参数绑定校验、调用 service、统一响应service、common拼 SQL、写业务规则
service业务规则、事务、领域逻辑dao、model直接碰 gin.Context
dao数据访问细节model业务分支
model数据结构业务逻辑
  • 接口放哪:dao 层接口由 service 定义并注入,测试时 service 用 fake dao,不用引 mock 框架也能单测。
  • main 组装:NewService(dao) / NewHandler(svc) / NewRouter(handler, mw) 一路构造注入;gin.Engine 只在 router/main 出现。
  • 命名与卫生:internal 防止被外部包导入;utils 别做成垃圾桶;handler 要薄、service 要厚(薄 controller 厚 service);没有 import cycle(跨层共享一律走 model/common)。

为什么这么分(反问式自检)

  • 能不改 SQL 就替换存储实现吗?——能,dao 接口隔离。
  • 能对 service 写纯单测吗?——能,依赖是接口。
  • 换框架(Gin→Echo)影响多大?——只动 handler/router 层,业务与数据不动。

追问记忆点

  • 追问:为什么 handler 不能直接 import dao?——会让业务逻辑散落、测试困难、依赖方向混乱;必须经 service 编排。
  • 追问:接口定义在消费方还是实现方?——Go 惯例是消费方(service)定义所需最小接口,实现方(dao)被动满足,避免反向依赖。
  • 追问:一定要 wire/依赖注入框架吗?——单体手写构造函数足够,显式、可读、可编译期检查;复杂微服务再考虑容器。
  • 记忆点:入口组装、分层单向依赖、接口解耦、model/common 共享、无循环导入;薄 handler + 厚 service。
笔记加载中…