Web 技术研究所

我一直坚信着,Web 将会成为未来应用程序的主流

Web 中的事务与撤销

  我们经常会将一系列复杂并且有依赖关系的操作做为一个事务处理。一个事务从开始执行到完成往往需要很长时间。在事务执行的期间如果再执行一个与之冲突的事务就无法确定其最终的执行结果。这时候只能对操作的目标对象加锁,并使用同步执行才可以解决问题。
  其实同步执行的体验是很差的。一个典型的例子就是 Windows 上安装软件。有些软件安装程序在安装过程点击取消的行为是等到安装完成后再立刻执行卸载。这不是坑么?用户点击取消的目的就是为了停止当前执行的事务,如果只是将操作同步推入任务队列还不如不放置这个取消按钮!
  虽然这种行为的体验非常差,但这确实是个很难解决的问题。比如我们的 Web 程序,点击按钮后可能会给服务器发送一个请求。如果网络卡了,请求一直完成不了,我们该如何操作?如何告诉用户程序目前的状态?即使此时页面上又个「取消」按钮,那么这个取消按钮的行为应该是什么?
  当一个请求发出后,无论是客户端还是服务器端都不知道这个请求的状态如何。客户端在收到响应前最多只能知道这个请求目前正在发送中。在这个状态下真的没办法执行取消操作,即使把电脑砸了都没用。就像快递已经寄出,把快递发起城市的分公司**了也无法停止。
  虽然在发送端无法停止,但是如果告诉服务器端这个请求已被和谐就可以实现拦截。比如一个从北京发往上海的快递,我们在快递抵达上海之前立刻飞到上海,把这份快递拦截下来即可。在程序上,我们可以在这个事务请求到达服务器前发起另一个请求告诉服务器不要让这个请求生效,这样就可以做到拦截。但拦截的请求也会因为网络原因而卡住。我们是否要考虑如何拦截一个「拦截请求」?其实如果我们对每个操作都添加一个唯一 ID,针对某个 ID 的「拦截」是一个幂等操作,是可以「重试」的,即使卡住,用户也可以重新多次发起拦截。以上这些看似是一个比较完整的方案,但其仅局限于请求的堵塞。如果事务请求早就发到服务器,只是响应时遇到了堵塞,那恐怕就没什么意义了。
  事务有许多类型,比如用户的个人资料修改,等都是比较容易撤销的。而一些账户相关、支付相关的操作就很难撤销。对于容易撤销的操作,即使事务请求先到服务器,之后的「拦截」请求也可以撤销掉原来的操作。但对于难以撤销甚至不可撤销的操作,「拦截」请求就不能在作为「撤销」使用。不过此时的「拦截」请求也不是完全没用的。如果服务器在响应后网络堵塞,发起事务的请求可能很晚才能收到响应。事务请求之外的请求,可能就不会遇上网络堵塞,通过操作的唯一 ID 可以在事务请求无法正常响应的情况下获取到请求的响应状态。
  这样整个系统就完整了。但这一整套事务与拦截是基于每个操作都有唯一 ID 的,而且这些 ID 是由前端提供的。也许有人会觉得不安全?我个人觉得只要做好用户权限验证等工作,让「撤销」请求和事务请求的权限保持一致就不会有什么安全问题。
  除了安全性之外还有数据库设计问题,因为用 ID 对应操作的话就需要额外的表来保存 ID 与操作的对应关系。事务的执行状态也需要维护,还要考虑对于可撤销和不可撤销事务之间操作的差异。可能是一张表,也可能是多张表。总之在表的设计上可能需要费点心思。
  最后就是与 REST API 的接合问题。通过一个操作的 ID 来获取操作的状态,甚至撤销一个事务。这样的操作虽然和 REST API 本身没什么关系,但是就操作而言,获取一个事务的状态应可以这样 GET /transactions/TRANSACTION_ID   或者要拦截一个事务可以这样 POST /interceptors

{"transaction_id":"TRANSACTION_ID"}
  执行一个新事务时可以这么做 PUT /users/ACCESS_TOKEN/intro

{"transaction_id":"TRANSACTION_ID","intro":"INTRO"}
  感觉这个设计并不会破坏 REST API 本身的风格,所以这个方案可行性还是比较高的。但我目前暂时没有在具体的项目中投入使用,也许还需要做更进一步的思考吧?我很可能会想漏一些问题,大家要是觉得我的想法有什么漏洞请随便喷,我会尽量给出想出解决方案的。
网名:
34.203.213.*
电子邮箱:
仅用于接收通知
提交 悄悄的告诉你,Ctrl+Enter 可以提交哦
神奇海螺
[查看全部主题]
各类Web技术问题讨论区
发起新主题
本模块采用即时聊天邮件通知的模式
让钛合金F5成为历史吧!
次碳酸钴的技术博客,文章原创,转载请保留原文链接 ^_^