加载中...

本篇要回答的问题:画图程序连服务端的第一步——网络协议该怎么设计(RESTful 范式、重试友好性、版本管理)?第一个服务端实现版本为什么要先做成 Mock 版

上一讲(实战三)我们让画图程序支持了浏览器端的离线持久化。本讲正式迈向服务端,但先不写正式服务,而是聚焦两件事:定义网络协议 + 给出一个串联业务用的 Mock 服务端。暂不考虑多租户授权(留待服务端开发篇)。

服务端是多文档的(浏览器端是单文档 DOM,文档叫 drawing),核心功能:创建/获取/删除 drawing,在 drawing 中创建/取/修改/删除 shape,以及改 shape 的 zorder。

画图程序完整网络协议表

网络协议

遵循一套 RESTful 范式:

操作 协议
创建对象 POST /objects
修改对象 POST /objects/<ObjectID>
删除对象 DELETE /objects/<ObjectID>
查询对象 GET /objects/<ObjectID>
列出对象 GET /objects(或 ?key=value,本例未用)

重试友好性

关键判断:网络不稳定,一次请求失败时你不一定能确定真实状态——可能服务端已执行、只是返回时网络出问题。所谓重试友好性,是指同一操作执行两遍,结果与只执行一遍一致

各类操作的重试友好性对比:

操作 是否重试友好 原因 / 改造
只读(查询/列出) 天然幂等
创建 shape POST /drawings/<id>/shapes 由客户端传 ShapeID,重复创建可返回 409 冲突
创建 drawing POST /drawings 无参数,重复调用会建出两个 drawing
改 zorder(front/back 相对值) 执行两遍会移动 2 层

把"不友好"改造成"友好"的手段:

手段 说明
客户端传 id / name / uuid 本质差别不大;name 若被后续引用就等同 id;uuid 是常规改造手法(理解为对象序列号或请求序列号)
改用绝对值而非相对值 zorder 用 Zorder = 5(绝对)替代 front/back(相对)
请求序列号 X-Req-Uuid 通用方法;代价是服务端要记录最近成功的所有 RequestUUID,收到时检查是否已执行过

关键洞见:网络协议与 localStorage 存储的 Shape JSON 格式不同——网络协议里类型藏在 key("path": {...}),localStorage 里用 "type": "path" 平铺。原因:localStorage 只是本地缓存、影响小,怎么方便怎么来(无 Schema);而网络协议未来可能作为开放 API,需严谨对待。

版本升级

关键判断:网络协议是一组开放 API,一旦放出就难收回,需考虑兼容。常见做法是带版本号POST /v1/objects);发生不兼容变更时升版本(v2)。

带版本号的好处:

好处
可逐步下线旧版流量,一段时间内两版协议并存
新老版本业务服务器相互独立,前端由 nginx / 应用网关分派

第一个实现版本:为什么先做 Mock

第一个服务端版本怎么做,有两种选择:

方案 特点
憋大招 直接做业务架构设计→评审→编码→测试→上线
Mock 版(作者选此) 不依赖数据库,业务逻辑全基于内存数据结构,只串联业务

为什么 Mock?因为服务端有大量非业务的通用难题:

通用难题 含义
高可靠 数据不能丢,硬盘坏、甚至机房地震都不能丢
高可用 服务无单点,几台停机仍能访问;极端如支付宝要异地双活

关键判断:先做 Mock 的价值有二——其一,让团队并行:网络协议是协作基础,Mock 让前端不必等后端,后端可自主对协议做高覆盖单测;其二,让业务最快被串联,快速验证网络协议有效性,发现不满足需求可及时调整。

Mock 服务端(Go 实现,paintdom)分两层:

源码 说明
Model 层 shape.go / drawing.go 与网络无关的纯业务核心,DOM 树比浏览器多一层:Document => Drawing => Shape => ShapeStyle(浏览器的 QPaintDoc 对应这里的 Drawing
Controller 层 service.go 实现网络协议

关键洞见:为什么把网络协议层看作 Controller 层?因为服务端通常无显示模块,故无 View 层;网络协议层负责接受用户输入——只不过输入不是日常交互,而是来自自动化(Automation)程序的 API 请求

总结

本讲给出网络协议的设计考量(RESTful 范式、重试友好性、版本管理),并选择先做一个不依赖数据库、只串联业务的 Mock 服务端——让前后端并行、让协议尽早被验证。网络协议是前后端耦合的"使用界面",是影响团队开发效率的关键。

先把"是什么"回答清楚

概念 一句话说明
RESTful 范式 POST/DELETE/GET 对应 创建/删除/查询,资源用 /objects/<id>
重试友好性 同一操作执行两遍结果与一遍一致;靠客户端传 id/uuid、绝对值、X-Req-Uuid 实现
协议版本号 /v1//v2/,便于并存、灰度下线、网关分派
Mock 服务端 不依赖 DB、纯内存、只串联业务,让前后端并行、尽早验证协议
网络协议层=Controller 服务端无 View;它接受来自 Automation 的 API 输入,本质是 Controller

一句话速记

网络协议是前后端耦合的"使用界面":用 RESTful 范式 + 重试友好性(客户端传 id/uuid、绝对值替相对值)+ 版本号;第一个实现先做只串业务、不依赖 DB 的 Mock 版,让前后端并行、协议尽早被验证。

几条值得记住的判断

  • 重试友好性 = 幂等:创建对象由客户端传 id/uuid,是把"不友好"改造成"友好"的关键。
  • 网络协议是开放 API,要带版本号、要比本地缓存(localStorage)严谨得多。
  • 先做 Mock 服务端:核心价值是让团队并行、让业务与协议最快被串联验证。

思考题

文中把"创建对象"从重试不友好改造为友好,靠的是让客户端预先分配 id/uuid。回到你的接口:哪些"创建类"接口在网络抖动重试后会产生重复数据?它们能否改成由客户端携带幂等键(uuid / X-Req-Uuid)来根治?

公告栏
这是我的个人知识库。
记录技术,也记录生活 —— 读过的、试过的、想明白的,都堆在这儿。
最新文章
网站资讯
文章数目 :
5
已运行时间 :
本站总字数 :
15.7k
本站访客数 :
本站总访问量 :
最后更新时间 :
全局知识图谱
当前页面 已访问 文章 标签
ESC 关闭 · 滚轮缩放 · 拖拽移动 · Ctrl+G 开关