复杂写入的Firebase提交/回滚

Ben*_*ley 8 ios firebase

我正在使用Firebase编写财务应用程序,并且要提交收据,还需要更新许多其他对象.要使数据有效,需要成功完成所有数据更新.如果其中一个写入出现错误,则必须回滚所有更新.

例如:

如果用户提交收据,则必须更新收货对象以及发票对象以及其他总帐对象.

如果更新已开始但用户在中途丢失了互联网连接,则应回滚所有更改.

在Firebase中实现这一目标的最佳方法是什么?

Kat*_*ato 15

首先,让我们聊聊一下为什么有人可能想要在多个数据路径上进行提交/回滚...

你需要这个吗?

通常,如果出现以下情况,则不需要此:

  • 你不是用高并发性写的(DIFFERENT用户每分钟写入数百次写入操作)
  • 你的依赖是直截了当的(B取决于A,C取决于A,但A不依赖于B或C)
  • 您的数据可以合并为一个路径

开发人员对他们的数据中出现的孤立记录有点过于担心.Web套接字在一次写入和另一次写入之间失败的可能性可能微不足道,并且在基于时间戳的ID之间的冲突顺序上.这并不是说这是不可能的,但它通常是低后果,极不可能,并且不应该是你的主要关注点.

此外,使用脚本甚至只需在JS控制台中输入几行代码即可轻松清理孤儿.因此,它们往往是非常低的后果.

你能做什么而不是这个?

将必须以原子方式写入的所有数据放入单个路径中.然后,您可以根据需要将其写为单个事务.

或者在一个记录是主记录而其他记录依赖于此的情况下,只需先写入主记录,然后在回调中写入其他记录.添加安全规则以强制执行此操作,以便主记录在允许其他记录写入之前始终存在.

如果要简化数据的规范化以简化和快速迭代(例如,获取用户的名称列表),那么只需在单独的路径中索引该数据.然后,您可以在单个路径中获取完整的数据记录,并在快速查询/排序友好列表中包含名称,电子邮件等.

什么时候有用?

如果您有一组非规范化的记录,则可以使用此工具:

  • 不能以实际的方式实际合并到一条路径中
  • 有复杂的依赖关系(A取决于C,C取决于B,B取决于A)
  • 记录以高并发性编写(即,不同用户每分钟可能有数百个写操作到SAME记录)

你怎么做到这一点?

我们的想法是使用更新计数器来确保所有路径都保持相同的版本.

1)创建一个使用事务递增的更新计数器:

function updateCounter(counterRef, next) {
   counterRef.transaction(function(current_value) {
      return (current_value||0)+1;
   }, function(err, committed, ss) {
      if( err ) console.error(err)
      else if( committed ) next(ss.val());
   }, false);
}
Run Code Online (Sandbox Code Playgroud)

2)给它一些安全规则

"counters": {
   "$counter": {
      ".read": true,
      ".write": "newData.isNumber() && ( (!data.exists() && newData.val() === 1) || newData.val() === data.val() + 1 )"
   }
},
Run Code Online (Sandbox Code Playgroud)

3)提供记录安全规则以强制执行update_counter

"$atomic_path": {
   ".read": true,
   // .validate allows these records to be deleted, use .write to prevent deletions
   ".validate": "newData.hasChildren(['update_counter', 'update_key']) && root.child('counters/'+newData.child('update_key').val()).val() === newData.child('update_counter').val()",
   "update_counter": {
      ".validate": "newData.isNumber()"
   },
   "update_key": {
      ".validate": "newData.isString()"
   }
}
Run Code Online (Sandbox Code Playgroud)

4)使用update_counter写入数据

由于您具有安全规则,因此只有在计数器不移动时,记录才能成功写入.如果它确实移动,则记录已被并发更改覆盖,因此它们不再重要(它们不再是最新且最好的).

var fb = new Firebase(URL);

updateCounter(function(newCounter) {
   var data = { foo: 'bar', update_counter: newCounter, update_key: 'myKey' };
   fb.child('pathA').set(data);
   fb.child('pathB').set(/* some other data */);
   // depending on your use case, you may want transactions here
   // to check data state before write, but they aren't strictly necessary
});
Run Code Online (Sandbox Code Playgroud)

5)回滚

回滚涉及更多,但可以建立这个原则:

  • 在调用set之前存储旧值
  • 监视每个设置操作是否失败
  • 在任何已提交的更改中设置回旧值,但保留新计数器

预建的图书馆

我今天写了一个lib,它做了这个并把它塞进了GitHub.随意使用它,但请确保通过阅读"你需要它吗?"而不是让你的生活变得复杂.以上.