跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

Golang如何做DDD领域驱动设计_Golang DDD教程【通俗】

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语言免费学习笔记(深入) ”;
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()
里 import
infra/mysql
和
interface/http
同时出现——这说明你没隔离好依赖流向 如果发现某个 handler 同时用了
UserRepository
和
OrderRepository
,先问:这两个是不是本该属于不同 bounded context? 复杂点往往不在模型多深,而在包路径是否真的表达了语义边界;容易被忽略的是,
go list -f '{{.Deps}}' ./domain
应该不包含任何 infra 或 transport 相关路径——这才是 DDD 在 Go 里落地最实在的验证方式。

相关文章