Go微服务DDD目录结构应按限界上下文物理隔离,如internal/booking、internal/handling;domain包仅含纯结构体与仓储接口,禁止ORM标签;application层只编排用例,不涉技术细节;infra实现仓储,跨上下文通信走事件而非直接import。

Go微服务中DDD目录结构怎么组织才不踩坑
Go语言没有类继承和访问修饰符,直接照搬Java的DDD分层会出问题。真正能落地的结构必须满足三点:领域代码不依赖基础设施、包之间不能循环引用、internal外无法import业务逻辑。
为什么internal/domain里不能放数据库字段映射
很多团队把gorm.Model或bson.ObjectId塞进domain包,结果导致领域模型被ORM绑架。一旦换数据库或加缓存,整个领域层就得重写。
-
domain下只放纯Go结构体:比如Cargo、Location、HandlingEvent,字段全是基础类型或自定义值对象 - 所有数据库相关标签(如
gorm:"column:id")必须出现在infra/db或adapter/mongo里 - 仓储接口定义在
domain,但实现放在infra——例如domain/cargo/repository.go声明CargoRepository接口,infra/mongo/cargo_repo.go实现它
application层该放什么,不该放什么
这是最容易失控的一层。常见错误是把HTTP参数校验、DTO转换、甚至日志埋点全堆进去,结果application变成新的“万能service”。
- 只放用例协调逻辑:比如
CreateCargo调用cargoRepo.Save()+eventPublisher.Publish() - 拒绝任何技术细节:不出现
http.Request、json.Marshal、time.Now()(时间应由领域层生成) - 输入输出用DTO:比如
CreateCargoRequest和CreateCargoResponse,定义在application/dto,和domain完全隔离
限界上下文怎么体现在目录上
DDD强调“一个上下文一个模型”,但在Go里容易误把多个子域塞进同一个domain/user包。真实项目里,每个限界上下文必须物理隔离。
立即学习“go语言免费学习笔记(深入)”;
- 按业务能力建一级目录:
internal/booking、internal/handling、internal/tracking,而非按技术分层 - 每个上下文内自包含:
domain(模型+仓储接口)、application(用例)、infra(该上下文专用的DB实现) - 跨上下文通信走事件或API:比如
handling发HandlingEvent事件,tracking监听并更新状态,绝不直接import对方的domain
目录不是摆设,而是契约。一旦internal/booking/domain/cargo.go被internal/handling import,说明边界已经破裂——这种耦合在初期看不出问题,等要拆微服务时才会爆发。



















