加载中...

本篇要回答的问题:在改造 QPaint 时,引入多租户(多用户 uid)会不会改变 DOM 树结构?底层业务逻辑实现层的**数据结构(mongodb 表设计)与算法(用户故事的实现)**该怎么落地?

四部曲第一部解决了 RESTful API 层的脚手架(restrpc + httptest)。本讲转向底层业务逻辑实现层:从"使用界面(接口)→ 数据结构 → 算法"三段式展开,并完成多租户的初步改造(先用 mock 授权,真正的帐号授权留给第三、四部)。

使用界面:多租户不该改变 DOM 层级

DOM 树原本是三层:Document ⇒ Drawing ⇒ Shape。引入多租户后,要不要变成四层 Document ⇒ User ⇒ Drawing ⇒ Shape

关键判断多租户不应影响 DOM 树结构。正确做法是让 Drawing"隶属于某个 uid"——多租户只是给 DOM 树多加一层安全约定(避免访问到无权限资源),而非改变层级。

它不改层级,却会改变接口方法——Document 类各方法都要带上 uid 做归属校验:

方法 改造前 改造后
Add Add() (drawing, err) Add(uid) (drawing, err)
Get Get(dgid) Get(uid, dgid),校验 drawing 是否属于该 uid
Delete Delete(dgid) Delete(uid, dgid),同样校验归属

关键洞见:QPaint 里 Drawing/Shape 接口没受多租户影响,只是因为业务简单;应极力避免接口因多租户而变化,但有时这种影响不可避免。此外,描述使用界面时不能只讲语言层约定——如 Shape 的 json.Marshal 结果必须符合 API 层预期,这也是它的隐性约束。

数据结构:为什么选 mongodb

程序 = 数据结构 + 算法

服务端的数据结构不完全自己做主——存储即数据结构,所以第一要务是选合适的存储中间件,再在其上组织数据。

候选 是否适合 QPaint 原因
关系型数据库 不太适合 需提前固定 Schema
mongodb(文档型) 选它 图形(Shape)种类多、Schema 开放、今天无法提前预期

表(mongodb 叫 Collection)与索引设计:

内容 索引设计 理由
drawing 所有 drawing uid 建索引 迟早要 List 某用户的全部 drawing
shape 所有 shape (dgid, spid) 建联合唯一索引 spid 仅在 drawing 内唯一,需联合 dgid

drawing 表与 shape 表的结构与索引设计

算法:用户故事背后的实现机制

关键判断:到了详细设计阶段,需求分析里的角色与用户故事,就变成了子系统/模块/类/函数的使用界面;而算法就是用户故事背后的实现机制

典型用户故事的实现(伪代码,但每条 mongo 操作都真实有效):

用户故事 关键实现要点
创建 drawing(uid) db.drawing.insert({_id, uid, shapes:[]})
取 drawing 内容(uid, dgid) findOne({_id, uid}),再遍历 shapes 逐个取 shape
删除 drawing(uid, dgid) drawing.remove({_id, uid}) 成功后 shape.remove({dgid})
创建 shape 先确认 drawing 归属,再 shape.insert + drawing.update $push
删除 shape 确认归属后 $pull 成功再 shape.remove

关键洞见:凡涉及多次修改的操作都应以事务进行。否则如"删 drawing 成功、删 shape 前宕机",业务上看似正常,系统却残留永远清不掉的孤立 shape 对象。

网络协议:先上 mock 授权

底层已支持多租户,协议层也要相应调整。本讲只做最简单的:引入 mock 授权头 Authorization QPaintStub <uid>。由于有了授权,不能再直接用 restrpc.Env,于是自定义 Env 在 OpenEnv 里解析出 UID:

步骤 动作
自定义 Env 内嵌 restrpc.Env 并加 UID 字段
OpenEnv 解析 Authorization 头、校验前缀 QPaintStub、取出 uid
全局替换 把所有 restrpc.Env 换成自定义 Env,Document 调用补 env.UID

总结

本讲完成了底层业务逻辑实现层的多租户改造:结构上坚持"多租户不改 DOM 层级,只加安全约定 + 给 Document 接口加 uid";数据结构上因 Shape Schema 开放而选 mongodb,并精心设计 uid、(dgid,spid) 两处索引;算法上用伪代码描述用户故事,并强调多写操作必须事务化。协议层先用 mock 授权过渡,真正的帐号与 OAuth 留给后两讲。

先把"是什么"回答清楚

概念 一句话说明
多租户约定 不改 DOM 层级,仅作安全约定 + 接口加 uid 校验归属
存储即数据结构 服务端先选存储中间件,再在其上组织数据
mongodb / Collection 文档型数据库,适合 Schema 开放的 Shape
索引 drawing 按 uid、shape 按 (dgid,spid) 联合唯一
算法 用户故事背后的实现机制;多写操作须事务化

一句话速记

多租户只给 DOM 树加"归属约定"和接口 uid,不改层级;因 Shape Schema 开放选 mongodb,靠 uid 与 (dgid,spid) 索引支撑算法,多写操作必须走事务。

几条值得记住的判断

  • 多租户应避免改接口、更不该改 DOM 层级——能避则避,避不掉再改。
  • 服务端"数据结构"谈的其实是存储中间件选型 + 表/索引设计
  • 多步修改不上事务,迟早留下永远清不掉的孤立对象

思考题

本讲坚持"多租户只加约定、不改结构"。回到你的系统:当初引入多租户时,你是否粗暴地在数据模型里硬塞了一层 User,导致结构和接口被污染?哪些多写操作至今没有用事务包裹、正悄悄制造孤立数据?

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