kratos默认脚手架不是ddd实现,因其internal/biz和internal/data未按领域语义重构,缺少internal/domain层,仓储接口与实现未分离,仅提供分层容器而非ddd内容模板。

直接用 Kratos 默认脚手架就能跑 DDD,但默认结构不等于 DDD 实现——internal/biz 和 internal/data 这两层必须按领域语义重构,否则只是“分层命名”,不是“分层建模”。
kratos new 生成的目录结构为什么不能直接当 DDD 脚手架用
Kratos 的 kratos new 命令生成的是一个符合“清晰架构(Clean Architecture)”约定的骨架,它有 service、biz、data 三层,但默认没强制领域边界。比如:
-
internal/biz里放的可能是UserService这类 CRUD 服务,而非UserUsecase或CustomerAggregate这类领域概念 -
internal/data直接暴露userRepo接口和 GORM 实现混在一起,没分离仓储接口(domain)和实现(infrastructure) - 没有
internal/domain目录,导致实体、值对象、聚合根、领域事件等无处安放
换句话说,Kratos 提供的是“分层容器”,不是“DDD 内容模板”。你得手动补全领域层,并调整依赖流向。
如何手动补全 Kratos 的 DDD 四层结构
在 internal/ 下新增 domain 目录,并确保各层之间只允许上层依赖下层(禁止反向依赖):
-
internal/domain:放聚合根(Customer)、实体(OrderItem)、值对象(Money)、领域服务接口(PaymentService)、仓储接口(OrderRepository) -
internal/biz:改名为internal/app或保留但明确其为“应用层”,只调用domain接口,编排用例(CreateOrderUsecase),不碰具体实现 -
internal/data:只实现domain中定义的仓储接口,如orderRepository结构体要实现domain.OrderRepository,且不能 importdomain以外的业务逻辑 -
internal/service:保持不变,仅做协议转换(HTTP/gRPC 请求 ↔app输入输出),不写业务规则
关键检查点:go list -f '{{.Deps}}' ./internal/app 输出里不能出现 internal/data;internal/data 的 go.mod 不能 require internal/biz 或 internal/domain。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
proto 定义与 domain 层的映射容易踩哪些坑
Kratos 强制用 Protobuf 描述 API,但 Protobuf message ≠ 领域模型。常见错误包括:
- 把
user.proto中的Usermessage 当作领域实体直接塞进domain.User—— 实际上它只是 DTO,需在service层转成domain.Customer - 在 proto 里加业务校验逻辑(如
google.api.rules)当成领域规则 —— 真正的不变式(如“订单金额必须大于零”)必须写在domain.Order的构造函数或方法里 - 用
optional字段表达业务可选性,却没在 domain 层做空值防护 ——domain.Customer.WithEmail(…)应该返回 error,而不是让上层传nil
建议做法:所有 proto 生成的 struct 只出现在 service 和 app 的输入/输出参数中;domain 层代码完全不 import api 包。
wire 注入时如何保证 domain 层不被污染
Wire 的 Inject 函数通常写在 cmd/server/wire.go,这里最容易把 infra 实现提前注入到 app 层。正确姿势是:
- 定义
ProviderSet时,先声明 domain 接口(如domain.OrderRepository),再绑定 data 实现(如data.NewOrderRepo) - app 层构造函数只接收 domain 接口,例如:
func NewCreateOrderUsecase(repo domain.OrderRepository) *CreateOrderUsecase - 避免在
app层 importdata或调用data.NewXXX—— 所有 new 操作都应在 wire 的 provider 里完成
如果 wire_gen.go 里出现了 data.NewUserRepo 被直接传给 app.NewUserService,说明 wire 配置漏了抽象层,需要回退一步补上 interface 定义。
真正难的不是目录怎么建,而是每次加新功能时,都要问一句:“这个逻辑属于 domain、app、还是 infra?”——答案错了,后面所有注入、测试、替换都会变麻烦。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










