go-kit三层架构(transport、endpoint、service)必须显式拆分,否则失去跨协议能力、可测试性与中间件扩展性;service只含纯业务逻辑,endpoint封装协议无关调用,transport负责协议转换与路由注册。

go-kit 的三层不是可选设计,而是必须显式拆分
不拆开 Endpoint、Service、Transport 三层,就等于没用 go-kit。它不像 Gin 那样“写个 handler 就能跑”,而是强制你把协议无关逻辑(Endpoint)、纯业务逻辑(Service)、传输细节(Transport)物理隔离。否则后续加 gRPC 支持、做单元测试、插熔断中间件都会卡住。
常见错误是把 http.HandlerFunc 直接塞进 main.go,或者让 Service 方法接收 *http.Request —— 这会让整个架构失去跨协议能力,也违背 go-kit 的核心契约。
-
Service层只定义接口,如type StringService interface { Uppercase(string) (string, error) },不依赖任何网络类型 -
Endpoint层函数签名固定为func(context.Context, interface{}) (interface{}, error),参数/返回值必须是纯 struct,比如UppercaseRequest{S string}和UppercaseResponse{V string, Err error} -
Transport层用httptransport.NewServer包裹 endpoint,并传入decodeUppercaseRequest和encodeResponse—— 这两个函数负责把 HTTP body 解成 struct、把 struct 序列化回 JSON
transport/http.NewServer 不是“启动服务”,只是构造 http.Handler
httptransport.NewServer 返回的是一个 http.Handler,不是服务器实例。它不监听端口、不解析路由、不设置 header,只做两件事:解包请求 → 调 endpoint → 打包响应。漏掉这一步,endpoint 就只是个普通函数,永远收不到请求。
典型遗漏包括:http.ListenAndServe(":8080", nil) 中传了 nil 却没意识到自己用了自定义 http.ServeMux;或写了 uppercaseHandler := httptransport.NewServer(...) 却忘了调 http.Handle("/uppercase", uppercaseHandler)。
- 必须手动注册 handler 到 mux:
mux.Handle("/uppercase", uppercaseHandler) - 启动时要传 mux 实例:
http.ListenAndServe(":8080", mux),不能传nil -
decodeXXXRequest函数里别用c.ShouldBindJSON()或json.Unmarshal(r.Body, &req)—— go-kit 已经帮你读了 body,你只需从r *http.Request里取字段赋值到 struct
Endpoint 中间件装饰器必须包裹 endpoint 函数,不能作用于 transport 层
日志、熔断、限流这些通用逻辑,必须在 Endpoint 层用闭包装饰,比如 loggingMiddleware(uppercaseEndpoint)。如果加在 httptransport.NewServer 外面,就只能拿到原始 HTTP 请求,拿不到业务参数和返回值,也无法统一处理 error 分类(比如区分业务错误和系统 panic)。
go-kit 提供的 kit/transport/http 和 kit/endpoint 是正交的:transport 只管协议转换,endpoint 才是安全边界。panic 在 endpoint 层 recover,错误分类在 endpoint 层做,指标打点也在 endpoint 层埋。
- 中间件函数签名应为
func(Endpoint) Endpoint,输入输出都是 endpoint 函数 - 不要在 decode/encode 函数里加日志——它们只负责数据转换,不该承担业务可观测性职责
- 多个中间件按顺序套用:
instrumenting(logging(auth(uppercaseEndpoint))),最外层最先执行
Service 实现不能直接调用 transport 或 endpoint
Service 层代码必须是“裸”的:不 import net/http、不碰 context.WithValue、不处理 JSON 编解码。它的唯一责任是实现接口方法,比如 func (s stringService) Uppercase(s string) (string, error)。所有外部依赖(DB、缓存、其他服务)都通过构造函数注入,而不是在方法里 new 出来。
容易踩的坑是:在 Service 方法里直接调 http.Post 去调其他服务 —— 这会让 Service 失去可测试性,也违背了 transport 层才该处理网络通信的原则。跨服务调用应走 client 端的 transport 层,比如用 httptransport.NewClient 构建 client endpoint。
- Service 方法参数和返回值类型必须和 endpoint 的 request/response struct 字段一一对应,但命名可以不同
- Service 方法里抛出的 error 会被 endpoint 中间件捕获并映射为 HTTP 状态码(需配合
transport/http.ServerErrorEncoder) - 不要在 Service 层做 context timeout 控制 —— 那是 transport 层或 endpoint middleware 的事
req.ParseForm() 错误,或对空 body 不做容忍,会导致 400 错误但日志里没有任何线索。这一层虽小,却是协议转换的第一道闸门。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











