kratos单体模式适合已有一定规模、需统一可观测性或规划半年内拆微服务的场景;否则应选用gowind admin的kratos-bootstrap封装层,它屏蔽分布式组件,仅保留http、日志、追踪等核心能力。

先确认 Kratos 单体模式是否真适合你
别一上来就改框架。Kratos 原生支持单体运行,但它的默认模板是为微服务设计的——带注册中心、服务发现、gRPC gateway、Protobuf 强约束等。如果你只是想把一个 Gin 或标准 net/http 的旧单体项目“换个壳”,又没打算后续拆服务,那大概率会掉进配置冗余、启动变慢、proto 维护成本陡增的坑里。
真正适合用 Kratos 重构单体的场景是:已有一定规模(日请求 >20 万)、团队开始出现协作瓶颈、需要统一可观测性(Tracing/Logging/Metrics)、或明确规划半年内要拆微服务。否则,GoWind Admin 提供的 Kratos-Bootstrap 封装层才是更轻量的选择,它直接屏蔽了 registry、consul、etcd 等所有分布式组件,只保留 HTTP server、middleware、log、trace、config 等核心能力。
分层迁移:biz/data/service 不要一步到位
Kratos 强制分层(api/biz/data/service)是优点也是负担。老项目往往是一大坨 handler + model + dao 混在一起,硬拆容易卡在中间层职责模糊上。
- 第一阶段:只动入口,不碰业务逻辑。把原有
http.HandleFunc或 Gin 路由,替换成 Kratos 的http.NewServer,挂载原有 handler 函数作为中间件或直接 ServeHTTP;先享受kratos/middleware(如 logging、recovery、prometheus)和transport.HTTPRequestContext()带来的上下文透传能力 - 第二阶段:把数据库操作抽到
data层。注意不是重写 ORM,而是把原 DAO 包封装成data.UserRepo接口,实现类放在data/mysql下,避免直接 import 原始 sqlx/gorm 实例 - 第三阶段:把业务逻辑从 handler 里拎出来,放进
biz层。重点识别“跨资源聚合”“状态流转”“规则判断”类逻辑——这些才是biz的主战场;而service层只做 API 结构体转换,占比建议压到 10% 以内
proto 文件怎么接,而不是硬怼
旧项目没有 Protobuf?别急着全量生成 *.pb.go。Kratos 的 proto 不是银弹,它是契约,不是枷锁。
推荐渐进式接入方式:
- 新接口(比如 /v2/orders)必须定义
order.proto,走标准kratos proto client生成;老接口(/v1/users)继续用 JSON struct tag,通过api/v1/user.go手写RegisterUserHandler注册到 HTTP server -
go_package必须与实际 import 路径一致,例如option go_package = "github.com/your/project/api/order;order",生成后文件顶部必须是package order,否则编译报undefined: xxx - 如果旧项目用的是 snake_case 字段名(
user_name),proto 里仍可写string user_name = 1;,但要在 struct tag 里显式声明json:"user_name",避免前后端字段错位 - 不要在 proto 里定义复杂嵌套结构来“复用”,容易导致 data 层 DTO 泄露。用
biz层的 domain struct 做中转,保持各层边界清晰
启动 panic 和 context 丢失是最常踩的两个坑
kratos.New() 启动直接 panic,90% 是传了 nil 的 server.Server 或 registry.Registry。尤其当你删掉了 consul 配置,却忘了把 consul.New() 调用也删掉,或者没判 error 就直接用了返回值——它返回的是 (nil, err),不是 (srv, nil)。
HTTP 中间件里拿不到 ctx.Value(),是因为你塞值的地方和取值的地方不在同一个 request context 生命周期里。标准写法是:
func MyMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(ctx, myKey, "value")
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
别用 transport.HTTPContext()——它是空 context;也别依赖 context.Background() 塞值,它跟 request 无关。真正的链路起点,永远是 r.Context()。
最易被忽略的一点:data 层的数据库连接池、缓存 client、HTTP client,必须在 kratos.New() 之前初始化完毕,并注入到 biz 层的 Usecase 中。它们不是框架自动装配的,Kratos 没有 Spring Boot 那种 Bean 容器——所有依赖都得手动 new、手动传,否则 runtime panic 时连错误栈都指向不到源头。











