本篇要回答的问题:如何把上一章的 mock 版 QPaint 服务端一步步改造成产品级程序?第一步——引入 RPC 框架(restrpc) 和单元测试(httptest),分别能省掉哪些样板代码、带来什么收益?
上一讲给出了服务端业务架构的通用套路(协议、授权、RPC、测试)。本讲是**"画图程序后端实战"四部曲的第一部**,正式动手:与上一章偏概要设计(系统架构、模块接口耦合)不同,这一章偏详细设计,把完整流程串起来。本讲先解决工程脚手架——RPC 框架与单元测试。
服务端 vs 桌面端的复杂性
| 维度 | 服务端开发 | 桌面端开发 |
|---|---|---|
| 难点 | 基础软件多,对知识面/理解深度要求高 | 用户交互逻辑复杂、代码量大 |
| 业务复杂性 | 相对简单 | 业务架构复杂性高 |
关键判断:上一章实战偏概要设计、重模块接口耦合、轻实现细节,所以"难以把全流程串起来";这一章改为偏详细设计来补上这块。
第一步:引入 restrpc 框架
mock 版 Service 约 280 行,改用 restrpc 后只剩约 163 行(不到原先 60%)。少写的正是 HTTP 收发的样板:
| 原先手写 | restrpc 自动完成 |
|---|---|
从 args[0] 取 URL 参数 |
由 env.Args[0] 传入 |
自己 json.Decode(req.Body) 解析请求体 |
在参数列表声明类型即自动解析 |
自己 ReplyError / Reply 回写 HTTP 包 |
在返回值列表返回数据即自动序列化回写 |
改造后处理函数"不像 HTTP 函数,更像普通函数",带来重要收益:
关键洞见:可以把 Service 当普通类来测,无需先包装 Client SDK 再测——大大降低单元测试成本。(代价:没走真实 HTTP,心里"多少有点不踏实",所以仍需补 HTTP 层测试。)
restrpc 的另两个要点:
| 机制 | 说明 |
|---|---|
| Env 扩展 | 框架不限定 Env 形态,只要求 OpenEnv/CloseEnv;ResponseWriter 以指针传入,便于改写(如做 API 审计日志接管返回包) |
| 路由 | 自动路由(方法名按规则:/→单词首字母大写,URL 参数→_,如 GetDrawings_Shapes_)或手工 routeTable(方法名仍须 Get/Put/Post/Delete 开头) |
业务逻辑的两层分层
QPaint 服务端业务被有意分为两层:
| 层 | 职责 | 实现 |
|---|---|---|
| 业务逻辑实现层 | 核心业务,组织为一棵 DOM 树(drawing.go / shape.go) | 不假设一定通过 RESTful 暴露 |
| RESTful API 层 | 接收网络请求并转为对 DOM 树的方法调用 | 有了 restrpc,每个方法常常只一句调用 |
为何这样分层?
| 可能性 | 类比 / 收益 |
|---|---|
| 也许根本不需要网络调用 | 类比 sqlite 嵌入式(本地函数调用)vs mysql(TCP 协议) |
| 也许需支持多种网络协议 | 改用 GraphQL 时底层不变,只加一层薄 GraphQL;甚至 REST 与 GraphQL 并存 |
关键判断:多协议并存时,不同接口模块共享同一份 DOM 树实例,实现多协议并存 + 完美解耦。
单元测试:从"几乎没有"到 httptest
原先测试很弱(只创建 drawing、断言 id=“10001”)。用 httptest 的 DSL 改写后更紧凑、可测更复杂流程:
| 能力 | 体现 |
|---|---|
| DSL 脚本 | post / get / ret / json / match 等指令直接描述请求与断言 |
| 变量与复用 | $(id1)、$(line1) 串起"建 drawing → 加直线 → 取回比对"完整链路 |
| 与 Go 互操作 | ctx.GetVar("id1") 在 Go 代码里取 DSL 变量做断言 |
| mockhttp | 七牛 mockhttp 不真监听端口也能测 HTTP |
总结
本讲是实战四部曲第一步:用 restrpc 砍掉 HTTP 样板、让 Service 像普通类一样可测,用 httptest 把单元测试从"聊胜于无"提升到可覆盖完整业务链路;同时确立"业务实现层(DOM 树)/ RESTful API 层"的分层,为后续多租户、多协议演进留出空间。代价是首次引入第三方依赖,需用 go mod 做版本管理。
先把"是什么"回答清楚
| 概念 | 一句话说明 |
|---|---|
| restrpc | 七牛 RESTful RPC 框架,自动做参数解析与返回值序列化 |
| Env | RPC 请求环境,可扩展(如注入授权、审计日志) |
| DOM 树分层 | 业务实现层(DOM 树)与 RESTful API 层解耦 |
| httptest / DSL | 业务友好的测试框架,用脚本描述请求与断言 |
| mockhttp | 不监听端口即可测 HTTP 的组件 |
一句话速记
实战第一步:用 restrpc 把 HTTP 函数变普通函数(省样板、好测试),用 DOM 树/REST 两层分层换来多协议解耦,用 httptest DSL 把单测做厚。
几条值得记住的判断
- restrpc 让 Service 像普通类一样可测,是降低单测成本的关键。
- 业务实现层不应假设一定走 RESTful,分层才能从容应对多协议(REST/GraphQL)并存。
- 一旦引入外部依赖,**版本管理(go mod)**就成为必修课。
思考题
本讲把"业务实现层"与"网络协议层"刻意分开,为的是协议可替换、可并存。回到你的项目:你的核心业务逻辑是否被某个具体协议(HTTP/某 RPC)绑死,以至于换协议就要重写业务?该如何抽出一层与协议无关的 DOM 式核心?
