我正在使用Firebase编写财务应用程序,并且要提交收据,还需要更新许多其他对象。要使数据有效,需要成功完成所有数据更新。如果其中一个写入出现错误,则必须回滚所有更新。
例如:
如果用户提交收据,则必须更新收货对象以及发票对象以及其他总帐对象。
如果更新已开始但用户中途失去了互联网连接,则应回滚所有更改。
在Firebase中实现这一目标的最佳方式是什么?
答案 0 :(得分:14)
首先,让我们聊聊一下为什么有人可能想要在多个数据路径上进行提交/回滚......
通常,如果出现以下情况,则不需要:
开发人员对他们的数据中出现的孤立记录有点过于担心。 Web套接字在一次写入和另一次写入之间失败的可能性可能微不足道,并且在某些地方之间发生冲突 基于时间戳的ID。这并不是说这是不可能的,但它通常是低后果,极不可能,并且不应该是你的主要关注点。
此外,孤儿非常容易用脚本清理,甚至只需在JS控制台中键入几行代码即可。再说一次, 他们的后果往往很低。
将必须以原子方式写入的所有数据放入单个路径中。然后,如果需要,您可以将其编写为单个set或transaction。
或者在一个记录是主记录而其他记录依赖于此的情况下,只需先写入主记录,然后在回调中写入其他记录。添加安全规则以强制执行此操作,以便主记录在允许其他记录写入之前始终存在。
如果要简化数据的规范化以简化和快速迭代(例如,获取用户的名称列表),那么只需在单独的路径中索引该数据。 然后,您可以在单个路径中获取完整的数据记录,并在快速查询/排序友好列表中包含名称,电子邮件等。
如果你有一组非规范化的记录,这是一个合适的工具:
我们的想法是使用更新计数器来确保所有路径都保持相同的版本。
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);
}
"counters": {
"$counter": {
".read": true,
".write": "newData.isNumber() && ( (!data.exists() && newData.val() === 1) || newData.val() === data.val() + 1 )"
}
},
"$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()"
}
}
由于您具有安全规则,因此只有在计数器不移动时,记录才能成功写入。如果它确实移动了,那么记录已被并发更改覆盖,因此它们不再重要(它们不再是最新的和最好的)。
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
});
回滚涉及更多,但可以建立这个原则:
我今天写了一篇文章来做这个和stuffed it on GitHub。随意使用它,但请确保通过阅读“你需要它吗?”而不是让你的生活变得复杂。上方。