复杂写入的Firebase提交/回滚

时间:2014-01-27 17:23:54

标签: ios firebase

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

例如:

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

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

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

1 个答案:

答案 0 :(得分:14)

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

你需要这个吗?

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

  • 你不是用高并发性写的(不同用户每分钟写入数百次写入操作)
  • 您的依赖关系很简单(B取决于A,C取决于A,但A不依赖于B或C)
  • 您的数据可以合并为一条路径

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

此外,孤儿非常容易用脚本清理,甚至只需在JS控制台中键入几行代码即可。再说一次, 他们的后果往往很低。

你可以做什么而不是这个?

将必须以原子方式写入的所有数据放入单个路径中。然后,如果需要,您可以将其编写为单个settransaction

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

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

什么时候有用?

如果你有一组非规范化的记录,这是一个合适的工具:

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

你是怎么做到的?

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

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);
}

2)给它一些安全规则

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

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()"
   }
}

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
});

5)回滚

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

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

预建库

我今天写了一篇文章来做这个和stuffed it on GitHub。随意使用它,但请确保通过阅读“你需要它吗?”而不是让你的生活变得复杂。上方。