go clean architecture 的第一道红线是 cmd 必须极简:cmd/api/main.go 仅解析 flag、调用 app.run()、返回 error;配置加载、db 初始化、路由注册等全下沉至 internal/app,禁止在 cmd 中 import infrastructure 或 repository。

cmd 目录里只放 main.go,别的都算越界
Go Clean Architecture 的第一道红线就是:cmd 必须极简——它不是启动脚本集合,更不是配置加载中心。你塞进去的每一行初始化代码,都在给单元测试埋雷。
-
cmd/api/main.go只做三件事:解析 flag、调用app.Run()、返回 error;其余全甩给internal/app - 禁止在
cmd里 importinternal/infrastructure或internal/repository;否则依赖箭头就反了,domain 层反而被 infra 拖着走 - 常见错误现象:
go test ./...报错 “cannot load config: no such file”,根源是main.go里硬编码了viper.ReadInConfig(),导致测试时绕不开文件系统 - 正确做法:把配置加载、DB 连接池创建、HTTP 路由注册全部下沉到
app.NewApp(),main.go仅负责“启动”这个动作
domain 层不能出现 sql.NullString 或 time.Time
领域模型一旦带上数据库或框架类型,就等于给业务逻辑焊死了持久化方式——这不是设计,是自缚手脚。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
domain/user.go中的时间字段必须是CreatedAt string或type CreatedAt time.Time(自定义类型,不直接用标准库 type),转换逻辑交给internal/adapter - 空值语义统一用 Go 原生指针:
*string、*int64,而不是sql.NullString;后者绑死了database/sql的Scan/Value方法,mock 仓库时根本没法构造合法实例 - ORM tag(如
gorm:"column:name")只能出现在internal/adapter/repository的映射结构体里,绝不出现在 domain 层 - 使用场景:写单元测试时,你要用内存仓库模拟用户创建,如果 domain 结构体含
sql.NullString,连json.Unmarshal都会失败——因为它的内部字段不可导出
go mod tidy 后 go.sum 疯涨?先查是不是误引了重型 SDK
go.sum 膨胀本身不危险,但若里面混进 google.golang.org/grpc/cmd/protoc-gen-go-grpc 或 golang.org/x/tools,说明 build 流程已把开发期工具打进生产依赖。
- 执行
go list -m all | grep aws,确认是否真需要整个github.com/aws/aws-sdk-go-v2;90% 场景只需service/s3子模块 - 用
replace锁定轻量替代:比如用minio/minio-go替代 S3 SDK,或用redis/go-redis替代github.com/go-redis/redis/v8(后者带大量 context 包间接依赖) - 性能影响:重型 SDK 会显著拖慢 CI 构建速度,镜像体积可能多出 50MB+,且增加 CVE 暴露面
- 一个信号:如果
go.sum里出现protoc-gen-go-grpc或gopls相关条目,基本可断定是go build时误用了本地 GOPATH 下的 dev 工具链
HTTP handler 是适配器,不是控制器
很多人把 api/handler/user_handler.go 当成 MVC 里的 controller,结果把参数校验、DTO 转换、业务编排全塞进去——这直接污染了 internal/adapter 层职责。
- handler 只做三件事:解析 HTTP 请求(
c.Param/c.ShouldBindJSON)、调用 usecase 接口、把 usecase 返回的ResponseModel封装成 HTTP 响应 - 业务规则、状态判断、跨用例协调,全部移入
internal/usecase;handler 不得持有 DB 实例、不调用 repo、不处理事务 - 容易踩的坑:在 handler 里直接调用
repo.UserRepo.Create(),等于跳过 usecase 层,导致业务逻辑散落、无法复用、难以测试 - 示例对比:
❌ 错误:user, _ := repo.Create(c.Context(), req)
✅ 正确:res, err := uc.CreateUser(ctx, &usecase.CreateUserInput{...})
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










