从目录分层到交付验证:我的 Go 后端工程实践

从目录分层到交付验证:我的 Go 后端工程实践 写 Go 项目最头疼的事情不是语法——语法一天就能上手。真正让人纠结的是代码往哪放。Go 官方不像 Rails 或 Django 那样给你规定目录结构。你搜Go project layout能搜出来一堆方案什么 golang-standards/project-layout、clean architecture、hexagonal architecture……每个都有道理但每个都不能直接拿来就用。我也纠结过一阵。最早写 Go 就一个main.go打天下后来项目大了开始拆文件夹拆的过程中踩了不少坑——层太多了写起来烦层太少了改起来痛。折腾了几个项目之后慢慢沉淀出一套自己用着顺手的结构。不敢说这是最优解但至少是在实际项目里磨合过的。这篇就聊聊这套结构长什么样以及每个设计决策背后的想法。整体目录结构先把全貌亮出来cmd/ api/ API 服务入口 worker/ 异步队列消费者入口 migrate/ 数据库迁移入口 config/ 环境变量加载与类型化配置 internal/ server.go HTTP 服务装配 worker.go Worker 服务装配 bootstrap/ 进程级资源初始化和关闭 errcode/ 错误码定义 handler/ HTTP handler service/ 业务逻辑 repository/ 数据访问层 model/ 数据模型 middleware/ Gin 中间件 router/ 路由注册 task/ 异步任务定义 taskqueue/ 异步队列边界 worker/ 异步队列运行时组件 pkg/ auth/ JWT 鉴权 cache/ Redis 封装 database/ 数据库初始化 log/ 日志初始化 response/ 统一 JSON 响应 validator/ 参数校验 tests/ integration/ PostgreSQL、Redis、Asynq 集成测试 .github/ workflows/ ci.yml 自动化测试、lint 和容器验证 Dockerfile 三个进程入口的多阶段镜像构建 .dockerignore Docker 构建上下文排除规则 compose.yaml 本地完整依赖栈与启动顺序 Makefile 开发、验证和容器命令入口几个顶层目录的职责cmd/进程入口。每种进程一个子目录main.go只负责进程级编排API 和 Worker 管理启动与关闭Migrate 执行一次性迁移后退出config/只负责从环境变量加载类型化配置不在这里创建数据库、Redis 或 JWTinternal/bootstrap/初始化 API 和 Worker 的共享进程资源组装 Registry并统一负责关闭Migrate 作为一次性入口直接管理自己的数据库连接internal/模块私有的应用代码、进程装配和运行时组件。Go 的internal约定保证这些包不会被外部项目直接引用pkg/基础设施辅助包。大部分保持业务无关少数包例如统一响应仍服务于当前应用契约tests/integration/连接真实 PostgreSQL 和 Redis并通过 Asynq client 验证任务持久化使用独立 build tag 与默认单元测试隔离根目录工程文件Dockerfile、Compose、Makefile和 GitHub Actions 共同负责本地运行、构建与自动化验证不承载业务逻辑这不是照搬 golang-standards/project-layout。那个仓库里有些目录我觉得没必要比如api/、third_party/有些是我后来根据实际需要加的比如拆了三个 cmd。项目结构这种东西合适比标准重要。