go中无抽象类,ddd应适配go惯用法:结构体组合+显式依赖传递;接口窄而具体;领域模型无外部依赖;分层靠包路径而非接口;生成代码仅限数据搬运;聚合根事件同步返回;测试须离线隔离。

Go 里没有 abstract class,别硬套 DDD 分层模板
DDD 不是 Go 的原生范式,强行照搬 Java 风格的 Repository 接口 + DomainService 抽象类,反而会让代码变重、测试变难、IDE 跳转失灵。
Go 的惯用法是:用结构体组合 + 函数参数显式传递依赖,而不是靠继承或接口泛化一切。比如一个订单状态流转,不需要定义 IOrderStateTransitioner,直接写 func (o *Order) TransitionToPaid(payClient PaymentClient) error 更清晰。
- 接口应窄而具体,比如
PaymentClient只暴露Charge(ctx, id, amount),不是一堆空方法等着实现 - 领域模型(
Order、Product)尽量保持无外部依赖,不 importdatabase/sql或http - 分层边界靠包路径体现(
domain/、application/、infrastructure/),不是靠 interface 声明“假装隔离”
go:generate 能帮你省掉 70% 的样板代码,但别生成领域逻辑
DDD 项目里最枯燥的是重复写 Validate()、ToDTO()、FromDTO()、Scan()/Value()(数据库驱动转换)。这些完全可以交给 go:generate + stringer 或自定义工具。
但注意:生成代码只处理「数据搬运」和「基础校验」,绝不生成业务规则。比如 Order.IsValid() 里含“优惠券不能和满减同用”,这种逻辑必须手写、可读、可测、可 debug。
- 推荐用
entgen或kallax生成Scan/Value方法,避免手写 SQL 扫描错位 -
validator标签(如validate:"required,email")配合go-playground/validator处理 DTO 入参,但 domain 层仍需独立Validate() error方法做领域语义检查 - 生成的代码进
internal/gen/,不 commit 到domain/下——否则 PR 里全是 diff 噪音
聚合根的 Apply() 和事件发布必须同步,别用 channel 异步搞丢一致性
很多人学 DDD 时看到“领域事件”,第一反应是开 goroutine 发到 chan Event。这在 Go 里非常危险:一旦 handler panic,事件就丢了;事务没提交前发事件,下游可能查不到上下文数据。
正确做法是:在聚合根内部用切片暂存事件,操作完成后统一返回,由 application 层在事务提交成功后显式调用 eventbus.Publish(events...)。
- 聚合方法如
order.Cancel() error应返回[]Event,不是 void - 不要在
infrastructure包里直接 importapplication或domain的 event 类型——用interface{}或专用pubsub.Event封装 - 如果真要用异步(如发邮件),那是 application 层或 infrastructure 层的事,和 domain 层的
Apply()无关
单元测试跑得慢?大概率是你在 test 里 new 了 sql.DB 或 http.Client
DDD 项目测试慢,往往不是因为逻辑复杂,而是 test setup 里初始化了真实依赖。比如每个 test 都 sql.Open("sqlite", ":memory:"),或者起一个 httptest.Server。
领域层测试必须 100% 离线:mock 只 mock 接口(PaymentClient),不 mock 具体实现(StripeClient);所有时间依赖走 clock.Clock 参数注入;随机数用 rand.Rand 显式传入。
- 测试
application.PlaceOrder时,传入&mockPaymentClient{},而不是启动 Stripe 沙箱 - 用
testify/mock或 hand-rolled mock(一个 struct 实现接口)都行,但别用gomonkeypatch 全局函数——它会让测试脆弱且无法并行 - 真正需要集成验证的地方(如 repository SQL 正确性),单独放
integration/目录,用//go:testmain控制是否运行
DDD 在 Go 里最难的不是建模,是守住 domain 层的纯粹性:它不能有 time.Now()、不能有 log.Printf、不能有 os.Getenv()。这些细节漏一点,后面就全是债。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











