go整洁架构实践靠cmd、internal、pkg三层硬边界实现:cmd仅解析配置并组装依赖树,internal按职责分包且禁止框架/db类型侵入,pkg独立可测不依赖internal,所有import路径须与go.mod严格一致。

Go 框架开发中,代码整洁不是靠画架构图实现的,而是靠三道硬边界守住的:cmd 只组装、internal 不越界、pkg 可外引。一旦违反,换 Gin 为 Echo 或 MySQL 换 TiDB 就得改满屏 SQL 错误处理。
cmd/main.go 只能做两件事:解析配置、组装依赖树
常见错误是把 Redis 初始化、健康检查 handler、甚至 JWT 验证逻辑全塞进 cmd/main.go。这导致 internal 包为了复用,反过来 import cmd,彻底破坏依赖单向性。
- 正确做法只保留:
loadConfig()、sql.Open()、mysql.NewUserRepo()、service.NewUserService()、http.NewUserAPI()这类构造链 -
http.NewUserAPI必须来自internal/api/http,不能是 cmd 里手写gin.GET路由 - 所有 DB 客户端、中间件、HTTP server 实例化,都必须落在
internal/infrastructure或internal/api下,而非 cmd
internal/service 层不能出现任何框架类型
看到 service.Register(*gin.Context) 或 c.Param("id") 就该立刻停手——这不是业务编排,是胶水代码,已丧失可测试性与复用性。
- 接口输入必须是纯结构体:
Register(*RegisterRequest)✅,FindUserByID(ctx context.Context, id int)✅ - 禁止 import
github.com/gin-gonic/gin或database/sql到internal/service或internal/domain - 如果
go test ./internal/service报错 “cannot find package github.com/gin-gonic/gin”,说明 service 层已被框架污染
repository 接口定义必须在 internal/port 或 internal/repository
数据访问契约和具体实现必须物理隔离。接口定义在上层(供 service 调用),实现沉到底层(带 SQL、Redis、HTTP client)。
- 接口文件如
internal/port/user_repository.go只含:type UserRepository interface { FindByID(context.Context, int) (*User, error) } - 实现文件如
internal/infrastructure/mysql/user_repo.go才 importdatabase/sql并实现该接口 - service 层只依赖
internal/port,不感知 mysql、redis、grpc 等具体技术选型 - 测试时可直接传入 mock 实现,无需启动数据库或网络
重构时最容易被忽略的点:import 路径与 go.mod 的一致性
目录结构再规范,只要 go.mod 里没声明 replace 规则,或 import 路径写成 ./internal/user 而非 example.com/project/user,模块边界就形同虚设。
- 所有跨包 import 必须使用模块路径,例如:
import "example.com/project/user",而非相对路径 - 根目录
go.mod中需显式声明:replace example.com/project/user => ./internal/domain/user - 否则
go test ./internal/service会因找不到user包而失败,更无法对user模块独立go vet或golangci-lint
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











