一、缘起
1. 背景:系统重构选型 Go kit
为什么不直接使用 Web 框架(如 Gin)?
根据旧系统使用 Flask 的经验,稍具规模、多人协作的工程代码,如果没有严格的分层约束,很容易做成一锅蛋炒饭(我们应该做盖浇饭)。
旧系统的常见问题:
- HTTP 层和业务层,常常傻傻分不清楚(导致测试业务层代码,需要模拟 HTTP 请求);
- HTTP/DB 层的细节,常常会混入业务层(比如业务层在处理 Flask 或者 SQLAlchemy 的数据结构);
- 没有使用依赖注入,单元测试必须借助 Mock 工具(比如 Python 的 unittest.mock)。
为什么不使用 Go micro?
- Go micro 作为一套微服务开发框架,复杂度较高,可控度不够;
- 新系统一开始的定位是 “优雅(模块化)的大单体”(备注),所以选择了更灵活的 Go kit。
备注:Microservice Architecture at Medium(Medium 的微服务架构 )。
It is fine to start with a monolithic architecture, but make sure to modularize it and architect it with the above three microservice principles (single purpose, loose coupling and high cohesion), except that the “services” are implemented in the same tech stack, deployed together and run in the same process.
选择 Go kit 意味着什么?
- 成熟的分层思想:整洁架构(意义指数:★★★)
- 优雅的错误处理:如何将业务层 error 转换为 HTTP 状态码(意义指数:★★☆)
- 灵活的协议切换:同时支持 HTTP 和 gRPC(意义指数:★★☆)
2. 问题:手写代码太繁琐
Go kit 核心代码分为三层:
业界吐槽:
Why I Recommend to Avoid Using the go-kit Library。
3. 为什么要造轮子?
Go kit 列举的 两款工具:
- kujtimiihoxha/kit(缺点:不再维护)
- metaverse/truss(缺点:需要手写 Protocol Buffers)
一言以蔽之:已有的轮子不简洁、不好用。
二、设计
1. 初衷
Kun 最初的定位:一款 HTTP 代码生成工具,让大家专注于 Service 层代码(业务逻辑),无需手写 Transport 层和 Endpoint 层代码。
Kun 可以生成的代码包括:
- Endpoint 层代码
- Transport 层代码
- HTTP server
- HTTP client
- HTTP test
- OAS 文档
2. 哲学
- 聚焦最有价值的事
- 别再折腾 Web 框架了,专注于你的业务价值!
- 尽量只写 Go 代码
- 用 Go(而不是其他 DSL)来描述你的服务定义。
- 可维护性很重要
- 拥抱整洁架构(核心是思想本质,而不是条条框框)。
3. 原理
参考 How it works。
4. 注解
为什么要用注解?
选项 | 简洁度 | 可维护性 |
---|---|---|
Go interface 上的直接注解 | ★★☆ | ★★★ |
单独的服务定义(如 YAML 文件) | ★★☆ | ★★☆(备注) |
备注:「直接注解」的可维护性高于「YAML 文件」,源自 Where to configure transcoding。
Two mechanisms are supported for doing this: direct annotations in your
.proto
file, and in YAML as part of your gRPC API configuration file. We recommend usingproto
annotations for ease of reading and maintenance.
注解设计
语法借鉴:
设计考量:
- 越典型的场景越简洁
- 最典型的 JSON API 只需写一行
//kun:op
; - 支持自动绑定 URL 中的 Path 参数;
- Query 也是常用的 HTTP 参数,所以
//kun:param
中默认in=query
,同时//kun:body
中指定-
可以让所有参数从 Body 切换绑定到 Query; - HTTP 参数名称默认为 snake case(下划线风格),也支持切换到 lower camel case(驼峰风格);
200
是最常用的成功响应状态码,所以是//kun:success
中默认statusCode=200
。
- 最典型的 JSON API 只需写一行
- 复杂的场景也能支持
- 需要使用 Query、Header、Cookie 参数时,可以用
//kun:param
来指定; - 支持参数聚合:将多个 HTTP 参数绑定到一个 Method 参数上;
- 复杂请求和响应的编解码,可以通过自己实现 HTTP Codec 来达成。
- 需要使用 Query、Header、Cookie 参数时,可以用
- 服务接口完全由业务决定
- Go interface 中每个 Method 的入参和出参,不强求一定要定义成结构体,可以从业务出发使用最自然的签名。
三、展望
Kun 未来的定位:不仅仅局限于一款 Go kit 的代码生成工具,而是成为一款通用的 Go 服务通信工具 —— 致力于处理 Go 服务之间的通信,让开发者专注于业务价值。
Kun 对服务通信的约定:
- 服务通信应该依赖于抽象接口,而不是具体类型;
- 服务之间只依赖 Go interface;
- 当前 Kun 生成的 HTTP client 代码也实现了服务定义的 Go interface。
- 基于约定 1,服务通信的类型应该是可以灵活切换的。
- 最终 Kun 可能会支持的通信类型:
- 进程内函数调用;
- RPC(比如 HTTP 和 gRPC);
- 异步消息(尚不支持)。
- 最终 Kun 可能会支持的通信类型: