clean architecture在go中靠import路径强制约束依赖流向,domain层禁用time.time等标准库类型,须用clock接口抽象并由外层注入;repository接口必须定义在domain或usecase层,cmd/main.go仅组装依赖并启动。

Clean Architecture 在 Go 里不是目录套壳,而是 import 路径写错一行就破功。 它不靠命名规范撑场面,只认实际依赖流向——domain 层哪怕多 import 一个 time 或 database/sql,整层就失效。
domain 层为什么连 time.Time 都不能用
领域模型一旦带上标准库类型,就等于把业务逻辑和具体实现绑死。比如 CreatedAt time.Time 看似无害,但会导致:
- 无法在测试中注入固定时间(mock Clock 不起作用)
- 序列化时字段名被 JSON tag 强制覆盖,破坏 domain 纯净性
- 迁移至其他时区/时间源时,必须改所有实体定义
正确做法是抽象为接口并由外层注入:
type Clock interface {
Now() time.Time
}
type User struct {
ID string
CreatedAt string // 用 string 存 ISO8601,或自定义 type CreatedAt string
Status UserStatus
}
func NewUser(name string, clock Clock) *User {
return &User{
ID: uuid.New().String(),
CreatedAt: clock.Now().Format(time.RFC3339),
Status: Inactive,
}
}
注意:uuid.New() 也不能出现在 domain 层——ID 生成应由 usecase 或 infrastructure 提供,domain 只接收。
repository 接口必须定义在 domain 或 usecase,不能放在 infrastructure
Go 没有包访问控制,依赖方向全靠 import 路径约束。如果把 UserRepository 接口放在 infrastructure 包里,usecase 就得 import 它,等于外层依赖内层,违反依赖倒置。
常见错误现象:
- 测试时无法用内存仓库替换 DB 实现(因为接口在 infra,mock 也得 import infra)
-
go test ./...报错 “import cycle not allowed”,根源就是 infra → domain ← usecase → infra 的环
正确结构:
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
-
domain/user_repository.go:定义type UserRepository interface { Save(*User) error; FindByID(string) (*User, error) } -
infrastructure/repository/user_postgres.go:实现该接口,importdomain和github.com/lib/pq -
usecase/user_create.go:构造函数接收UserRepository接口,不 import 任何 infra 包
cmd/main.go 里写一行 DB 初始化就是架构污染
main 函数唯一职责是解析 flag、调 app.Run()、返回 error。任何初始化动作(DB 连接、Viper 加载、路由注册)都必须下沉到 internal/app 或 bootstrap 包。
否则会直接导致:
- 单元测试绕不开文件系统(
viper.ReadInConfig()报 “no such file”) - 无法为 CLI 或定时任务复用同一套启动逻辑
-
go run cmd/main.go在 CI 中失败,因为环境变量或配置路径和本地不一致
正确写法示例:
// cmd/main.go
func main() {
port := flag.String("port", ":8080", "server port")
flag.Parse()
app := app.NewApp()
if err := app.Run(*port); err != nil {
log.Fatal(err)
}
}
而 app.NewApp() 内部才做 config.Load()、db.Connect()、router.Setup() 等事——这些都属于可替换、可测试的组件,不是 main 的责任。
go-cleanarch 工具跑出 0 error ≠ 架构干净
它只检查 import 方向是否符合预设层名规则(如 domain 不能 import infrastructure),但完全不管:
- domain 层里有没有偷偷用
sql.NullString - usecase 方法里有没有直接调
http.Get() - repository 实现里有没有暴露
*sql.Rows返回值
更隐蔽的问题是路径格式:它要求模块路径严格匹配 github.com/your/repo/domain,如果你用了 github.com/your/repo/internal/domain,工具直接跳过,不报错也不检查——你以为过了,其实没校验。
所以每次改完目录结构,必须手动确认:go-cleanarch -domain=internal/domain -application=internal/usecase -interfaces=internal/api -infrastructure=internal/infrastructure,缺一个参数,检查就形同虚设。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










