多版本匿名文档存储的建模
Closed this issue · 3 comments
ben7th commented
我们准备开发一个多版本匿名文档存储框架/服务。目前正在整理需求和技术实现要点。
多版本匿名文档存储,代表网站是:http://jsfiddle.net/
jsfiddle 具有以下的使用特性:
- 任意时候打开网页,都可以匿名地开始撰写 js 代码;此时的网址为:http://jsfiddle.net 此时的网页状态称为“匿名创建”状态
- 任何时候在“匿名创建”状态的网页点击 save 按钮(包括没有进行任何编辑时),都会后台保存当前编辑器内容,生成一个新的路径:类似 http://jsfiddle.net/mjtdvgd3/ 。其中 mjtdvgd3 是 8 位的随机字符串。此时的网页状态称为“版本记录”状态。
- 任何时候在“版本记录”状态的网页点击 update 按钮。都会将当前编辑器内容保存为一个新的版本。网址类似: http://jsfiddle.net/mjtdvgd3/1/ http://jsfiddle.net/mjtdvgd3/2/ 依次类推
- 点击 fork 时,将生成一个新的随机字符串,并把当前状态保存到新随机字符串对应的网址 http://jsfiddle.net/ayps5xko/
- 任何人任何时间访问 http://jsfiddle.net/mjtdvgd3/ http://jsfiddle.net/mjtdvgd3/1/ http://jsfiddle.net/mjtdvgd3/2/ 等网址时都可以读取改版本对应的内容进行编辑。
其他特性:
- 某个编辑状态可能会包含多个要保存的文件。以 js-fiddle 为例,一个编辑状态包含 html javascript css 三个文件。
- 某个编辑状态可能会包含一些环境变量设置。同样以 js-fiddle 为例,一个编辑状态的环境变量设置包括依赖的库,语言选择(javascript/coffeescript)等等。
补充需求:
- 某些特定情况下可能希望直接更改一个指定版本的内容,而不更新版本号。例如思维导图编辑器这样的使用场景
ben7th commented
建模:
类名
class VersionedMap
# 此类并不是直接暴露的一个 include MongoId 方法的类
# 而是在更基础的类上包装的
类方法
# 创建一个空的 versioned_map 实例
# 但并不进行持久化保存
versioned_map = VersionedMap.new
# 根据 token 获取一个 versioned_map 实例
# token 是八位随机字符串
# 获取不到时返回 nil
versioned_map = VersionedMap.find(:token)
实例方法
# 设置键值对到 versioned_map
# key 必传, value 可选,默认是空字符串
# 创建键值对和修改键值对都是使用这个方法
# 此操作只是在内存中设置,并不会导致持久化保存
versioned_map.set(key, value = '')
# 删除一个键值对
# 此操作只是在内存中设置,并不会导致持久化保存
versioned_map.remove(key)
# 持久化保存一个 versioned_map
# 保存的时候为其分配 token
# 无论何时调用此方法,都生成新的 token
# 具体见下面的用例
token = versioned_map.save
# 为一个 versioned_map 持久化保存新的版本
# 版本号是递增的自然数 1,2,3,4 ...
version = versioned_map.update
# 取得指定对象的指定版本
# 如果传入的是 nil 或 0,则返回初始版本
versioned_map_1 = versioned_map.get_version(version)
# 注意,返回的对象和调用方法的对象不是同一个对象
# 取得指定对象的最大版本号
max_version = versioned_map.max_version
# 如果取得的最大版本号是 10,意味着总共有 11 个版本
# 因为初始版本也是一个版本
# 取得最后一个版本对象
versioned_map.latest
# 取得当前对象的版本号
# 如果当前版本是初始版本,返回 nil
# 如果不是初始版本,返回版本号
versioned_map.version
# 取得当前对象的 token
versioned_map.token
特别注意 save 和 update 的关系
和导致 version 与 token 变化的逻辑
# 测试用例
map = VersionedMap.new
token0 = map.save
map.token.should == token0
map.version.should == nil
token1 = map.save
map.token.should == token1
map.version.should == nil
token0.should != token1
map.update
map.version.should == 1
map.token.should == token1
map.update
map.version.should == 2
map.token.should == token1
token2 = map.save
map.token.should == token2
map.version.should == nil
用例:实现 jsfiddle 的逻辑
# 第一次点 Save 按钮的流程
# 前端传入 test.html, test.js, test.css 几个文件的内容
# 创建一个对象
versioned_map = VersionedMap.new
# 设置 test.html, test.js, test.css 的值
versioned_map.set 'test.html', '...'
versioned_map.set 'test.js', '...'
versioned_map.set 'test.css', '...'
# 持久化保存并获取 token
token = versioned_map.save
# 前端根据拿回的 token 生成 /:token 这样的网址
# 每次点 Update 按钮的后台流程
# 前端传入 test.html, test.js, test.css 几个文件的内容
# 前端传入 token
# 根据 token 获取到对象
versioned_map = VersionedMap.find token
# 设置变化后的 test.html, test.js, test.css 的值
versioned_map.set 'test.html', '...'
versioned_map.set 'test.js', '...'
versioned_map.set 'test.css', '...'
# 保存新的版本
version = versioned_map.update
# 前端根据拿回的 token 和 version 生成 /:token/:version 这样的网址
# 点 fork 按钮时的后台流程
# 前端传入 test.html, test.js, test.css 几个文件的内容
# 前端传入 token
# 根据 token 获取到对象
versioned_map = VersionedMap.find token
# 设置 test.html, test.js, test.css 的值
versioned_map.set 'test.html', '...'
versioned_map.set 'test.js', '...'
versioned_map.set 'test.css', '...'
# 保存新的对象,获取新的 token
token = versioned_map.save
# 前端根据拿回的 token 生成 /:token 这样的网址
# 打开 /:token 或 /:token/:version 这样的地址时的流程
# 后端拿到 token 和 version
# 取得对象
versioned_map = VersionedMap.find token
# 取得对象版本
# 不管 version 是 nil 还是 有值 都可以直接传
versioned_map_1 = versioned_map.get_version version
# 获取对象上的信息
versioned_map.get 'test.html'
versioned_map.get 'test.css'
versioned_map.get 'test.js'
kaid commented
ben7th commented
新工程对应issue:mindpin/versioned_map#1