domain包中定义User结构体并打bson或gorm标签会导致职责污染,使其不再是纯领域模型,破坏分层架构边界,引发编译依赖穿透和后续模块移除失败。

为什么直接在 domain 包里定义 User 结构体并打 bson 或 gorm 标签会出问题
这不是语法错误,而是职责污染。一旦 domain.User 出现 gorm.Model 字段、json: 标签或 db: 映射,它就不再是纯领域模型,而成了“数据库结构体+业务语义”的混合体。GoLand 里 Ctrl+Click 跳转时,你会看到它被 infrastructure/mysql 和 api/handler 同时 import —— 这意味着编译期依赖已穿透边界,后续删掉 MySQL 模块时 go build ./domain 会直接失败。
domain 包里只放行为,不放字段映射
GoLand 中新建 domain/user.go 时,结构体必须满足:无第三方 import、无任何序列化标签、所有状态变更通过方法触发。常见错误是把校验逻辑写在 setter 里却允许外部直接赋值字段。
- 正确做法:
type User struct { name string; age int }(全小写私有字段),提供NewUser(name string, age int) (*User, error)工厂函数,在构造时校验 - 禁止:
func (u *User) SetName(name string) { u.name = name }—— 绕过校验,破坏不变量 - GoLand 提示:如果
domain.User出现在infrastructure/mysql/user_repo.go的 import 列表里,立刻删掉;应只 importdomain接口,如UserRepository
用两个独立结构体解耦存储与领域语义
GoLand 里建包要物理隔离:domain/user.go 和 infrastructure/mysql/user_model.go 必须在不同目录,且后者不能被 domain import。字段名可以一致,但标签和职责严格分离。
GoLand 2026.1.1 是 2026.1 发布后的首个维护修正版本,适合已经开始体验 2026.1 新功能并希望同步补丁的开发者。它更适合用于入门项目、现有项目迁移测试和 IDE 行为验证。
-
domain.User:只有ConfirmEmail()、ChangeName()方法,无标签 -
infrastructure.mysql.UserModel:含gorm:"column:full_name"和json:"name",仅用于 ORM 和 HTTP 序列化 - 转换发生在 repository 实现层:
repo.FindByID()从 DB 读出UserModel,再调用domain.NewUser()构造领域对象 - GoLand 报错提示
import cycle not allowed时,检查是否误把infrastructure路径写进了domain文件的 import 行
GoLand 中快速验证解耦是否生效
别只看目录结构,用 GoLand 内置终端执行三步验证:
- 删掉整个
infrastructure/目录后,运行go build ./domain/...—— 必须成功 - 右键
domain/→ “Run Tests”,确保所有测试不依赖config.yaml或数据库连接 - 打开 Terminal,执行
go mod graph | grep 'your-module-name/',确认输出中没有domain → infrastructure或infrastructure → domain的反向箭头
真正卡住人的不是 GoLand 设置,而是把“建了 domain 文件夹”当成解耦完成——只要 domain 包里还藏着一个带 gorm.Model 的结构体,边界就不存在。

















