针对复杂写入的Firebase提交/回滚
我正在使用Firebase撰写财务应用程序,要提交收据,还需要更新许多其他对象。 为使数据有效,所有数据更新都需要成功完成。 如果其中一个写入发生错误,则必须回滚所有更新。
例如:
如果用户提交收据,则必须更新收据对象以及发票对象以及其他总帐对象。
如果更新开始,但用户中途丢失了互联网连接,则所有更改都应回滚。
在Firebase中实现此目的的最佳方式是什么?
首先,让我们聊聊为什么有人可能想在多个数据path上执行提交/回滚…
你需要这个吗?
一般来说,你不需要这个,如果:
- 你不是写高并发(不同的用户每分钟写同一个logging数百)
- 你的依赖是直接的(B依赖于A,而C依赖于A,但A不依赖于B或C)
- 您的数据可以合并到一个单一的path
开发人员对数据中出现的孤立logging感到有点担心。 在一次写入和另一次写入之间出现web套接字失败的可能性可能是微不足道的,而且在基于时间戳的ID之间的冲突顺序上也是如此。 这并不是说这是不可能的,但是这通常是不太可能的,而且不应该成为你的主要关注点。
此外,孤儿是非常容易清理的脚本,甚至只需要在JS控制台input几行代码。 所以,他们往往是非常低的后果。
你能做什么而不是这个?
将所有必须以primefaces方式写入的数据放入单个path。 那么你可以把它写成一个单一的集合或一个事务,如果有必要的话。
或者在一个logging是主要logging并且其他logging取决于此的情况下,只需首先写入主logging,然后在回写中写入其他logging。 添加安全规则来强制执行此操作,以便在允许其他人写入之前始终存在主logging。
如果为了简化迭代(例如,获取用户名称列表)而对数据进行非规范化处理,那么只需在单独的path中对数据进行索引即可。 然后,您可以在一个快速查询/sorting列表中将完整的数据logging保存在单个path中,并input名称,电子邮件等。
什么时候这有用?
如果您有非规范化的一组logging,这是一个适当的工具:
- 实际上不可能实际上合并成一条path
- 有复杂的依赖关系(A依赖于C,C依赖于B,而B依赖于A)
- logging写入的并发性较高(即,不同用户每分钟可能有数百个写操作到同一logging)
你怎么做到这一点?
这个想法是使用更新计数器来确保所有的path保持在同一修订版本。
1)创build一个使用事务递增的更新计数器:
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)给你的logging安全规则来执行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写入数据
由于您有安全规则,只有计数器不移动,logging才能成功写入。 如果它确实移动了,那么这些logging就被一个并发的改变覆盖了,所以它们不再重要(它们不再是最新和最伟大的)。
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)回滚
回滚有一点涉及,但可以build立这个原则:
- 在调用set之前存储旧的值
- 监视每一组故障
- 在任何已提交的更改中设置回旧值,但保留新的计数器
预build库
我今天写了一个lib, 把它塞到GitHub上 。 随意使用它,但请确保你没有通过阅读“你需要这个吗? 以上。