Go中无DDD框架,需靠包边界、接口隔离与显式依赖注入落实领域驱动设计,禁止领域层依赖基础设施,Repository接口定义在domain层,实现置于infra层。

Go 里没有“DDD 框架”,别找 ddd.NewDomainModel() 这种东西
Go 语言本身不提供 DDD 原语,也没有官方或主流的“DDD 框架”。所谓“Go 做 DDD”,本质是用 Go 的结构体、接口、包组织和依赖管理,去落实领域建模的约束。你不会在 go get 列表里看到 github.com/ddd-go/core 这样的包——真有,大概率是玩具项目。
常见错误现象:import "github.com/xxx/ddd" 后发现文档全是抽象概念、泛型模板、自动生成代码,但业务逻辑一加就崩;或者把所有实体塞进一个 domain/ 包,结果 User 和 Order 互相调用数据库方法,领域层彻底失守。
- DDD 在 Go 里靠的是「包边界」+「接口隔离」+「显式依赖注入」,不是靠库
- 每个 bounded context 应该是一个独立的 Go module(或至少是独立子目录),
go.mod不应跨 context 共享 - 领域层代码不能 import
database/sql、gin、gorm—— 这些必须出现在 infra 或 adapter 层
func (u *User) ChangeEmail(new string) error 是对的,但 u.Save() 是错的
领域对象的核心职责是表达业务规则,不是持久化。一旦你在 User 结构体里写 Save()、Update() 这类方法,就等于把存储细节泄漏进了领域层——这直接破坏了领域模型的纯粹性。
使用场景:用户修改邮箱时要校验格式、检查是否已被注册、触发邮箱变更通知。这些逻辑应该在 User 内部完成;但“存到 MySQL 还是 Redis”、“用事务还是最终一致性”,得交给外部的 repository 实现。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
ChangeEmail()可以返回error(比如ErrEmailAlreadyTaken),但绝不碰数据库 - repository 接口定义在 domain 层,比如
type UserRepository interface { Save(*User) error },实现放在infra/mysql/ - 别用
u.ID直接拼 SQL——ID 应该是值对象(如type UserID string),避免裸用int64导致类型混用
为什么 go:generate + mockgen 在 DDD 中容易翻车
Mocking 领域服务(比如 PaymentService)看似方便,实则常掩盖设计缺陷:你 mock 的那个接口,很可能本不该存在——它可能是应用层不该暴露的细节,或是两个 bounded context 之间不该直连的耦合点。
性能 / 兼容性影响:生成的 mock 代码会随接口变化频繁重建,而 domain 层接口本应稳定;更麻烦的是,测试里一堆 mockPayment.EXPECT().Charge(...),反而让你忽略真正要测的:领域对象状态是否按规则流转。
- 优先用真实小对象(如内存版
InMemoryUserRepository)做集成测试,比 mock 更贴近领域行为 - 只有当依赖是跨进程(如调第三方支付 API)且无法本地模拟时,才考虑 mock,且 mock 接口必须定义在 domain 层,而非 infra 层
- 别为
Entity或ValueObject写 mock——它们没副作用,直接 new + 调用就行
从 main.go 开始,用依赖注入守住分层边界
DDD 在 Go 里最脆弱的一环,就是应用层(application layer)怎么把 domain、infra、interface(如 HTTP handler)串起来。写错一句 userRepo := mysql.NewUserRepo(db) 在 handler 里,整条链路就垮了。
参数差异:用构造函数注入(不是全局变量、不是单例函数),让每层只拿到它明确需要的接口。比如 http.NewUserHandler(userApp *userapp.UserService, notifier notify.Notifier),而不是传整个 *App 结构体。
- application service 不持有 db 或 http.Client,只持 domain 接口和 infrastructure 接口
- 避免在
main()里 importinfra/mysql和interface/http同时出现——这说明你没隔离好依赖流向 - 如果发现某个 handler 同时用了
UserRepository和OrderRepository,先问:这两个是不是本该属于不同 bounded context?
复杂点往往不在模型多深,而在包路径是否真的表达了语义边界;容易被忽略的是,go list -f '{{.Deps}}' ./domain 应该不包含任何 infra 或 transport 相关路径——这才是 DDD 在 Go 里落地最实在的验证方式。


















