针对复杂写入的Firebase提交/回滚 [英] Firebase Commit/Rollback for complex writes

查看:155
本文介绍了针对复杂写入的Firebase提交/回滚的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

如果用户提交收据,则必须更新收据对象以及发票对象以及其他总帐对象。
$ b 如果更新开始,但用户在中途丢失了互联网连接,则所有更改都应回滚。

在Firebase中实现这个功能的最佳方法是什么?

解决方案

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



你需要这个吗?



一般而言,如果您不需要这样的话:


  • 不同用户每分钟写同一个记录到SAME记录)
  • 你的依赖是直接的(B依赖于A,C依赖于A,但A不依赖于B或C) / li>
  • 您的数据可以合并到一个路径中


开发人员有点担心孤儿记录出现在他们的数据。
一个写入和另一个写入之间的web套接字失败的机会可能是微不足道的,在基于
时间戳的ID之间的冲突顺序的某个地方。这并不是说这是不可能的,但是这通常是低效率的,极不可能的,而不应该成为你的主要关注点。

另外,孤儿是非常容易清理的一个脚本,甚至只需要在JS控制台输入几行代码。所以再次,
他们往往是非常低的结果。



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

将所有必须以原子方式写入的数据放入单一路径中。然后,您可以将其编写为单个交易中找到。


而其他的依赖于这个,只需先写主要的,然后在回调中写入其他的。添加安全规则来强制执行此操作,以便主记录在其他人被允许写入之前始终存在。



如果您正在使数据非规范化,以简化和快速处理迭代(例如获取用户的名称列表),然后简单地将数据索引到单独的路径中。
然后,您可以在一个单一的路径中获得完整的数据记录,并且可以在一个快速的查询/排序列表中查看名称,电子邮件等。


当这是有用的吗?



这是一个适当的工具来使用,如果你有一个非规范化的记录集:


  • 实际上不能合并为一条路径
  • 具有复杂的依赖性(A依赖于C,C依赖于B,B依赖于A )

  • 记录是以高并发性写入的(也就是说,不同用户每分钟可能有数百个写操作到SAME记录)


    你如何做到这一点?



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


    $ b $ h 1)创建一个更新计数器,使用事务增加:

    $ p code> function updateCounter counterRef,next){
    counterRef.transaction(function(current_value)){
    return(current_val UE || 0)+1;
    },function(err,committed,ss){
    if(err)console.error(err)
    else if(committed)next(ss.val());
    },false);




    $ b $ h3给它一些安全规则

     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允许删除这些记录,使用.write来防止删除
    .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 =新的Firebase(网址); 
    $ b updateCounter(function(newCounter){
    var data = {foo:'bar',update_counter:newCounter,update_key:'myKey'};
    fb.child('pathA ').set(data);
    fb.child('pathB').set(/ *其他一些数据* /);
    //根据您的使用情况,您可能希望事务处理
    //在写之前检查数据状态,但不是必须的
    });



    5)回滚



    回滚是一点点涉及,但可以建立这个原则:
    $ b


    • 在调用set之前存储旧值
    • 监视每个集合的运行失败情况

    • 在任何提交的更改上设置回旧的值,但保留新的计数器



    一个预建库



    我今天写了一个lib,这样做和塞满了GitHub 。随意使用它,但请确保你没有通过阅读你需要这个吗?上面。


    I'm writing a financial app with Firebase and for an receipt to be submitted, a number of other objects also need to be updated. For the data to be valid, all data updates need to be completed successfully. If there's an error in one of the writes, all updates must be rolled back.

    For example:

    If the user submits a receipt, the receipt object must be updated as well as an invoice object as well as other general ledger objects.

    If the update started but the user lost internet connection half way through, all changes should be rolled back.

    What's the best way to achieve this in Firebase?

    解决方案

    First, let's chat for a minute about why someone might want to do commit/rollback on multiple data paths...

    Do you need this?

    Generally, you do not need this if:

    • you are not writing with high concurrency (hundreds of write opes per minute to the SAME record by DIFFERENT users)
    • your dependencies are straightforward (B depends on A, and C depends on A, but A does not depend on B or C)
    • your data can be merged into a single path

    Developers are a bit too worried about orphaned records appearing in their data. The chance of a web socket failing between one write and the other is probably trivial and somewhere on the order of collisions between timestamp based IDs. That’s not to say it’s impossible, but it's generally low consequency, highly unlikely, and shouldn’t be your primary concern.

    Also, orphans are extremely easy to clean up with a script or even just by typing a few lines of code into the JS console. So again, they tend to be very low consequence.

    What can you do instead of this?

    Put all the data that must be written atomically into a single path. Then you can write it as a single set or a transaction if necessary.

    Or in the case where one record is the primary and the others depend on this, simply write the primary first, then write the others in the callback. Add security rules to enforce this, so that the primary record always exists before the others are allowed to write.

    If you are denormalizing data simply to make it easy and fast to iterate (e.g. to obtain a list of names for users), then simply index that data in a separate path. Then you can have the complete data record in a single path and the names, emails, etc in a fast, query/sort-friendly list.

    When is this useful?

    This is an appropriate tool to use if you have a denormalized set of records that:

    • cannot be merged practically into one path in a practical way
    • have complex dependencies (A depends on C, and C depends on B, and B depends on A)
    • records are written with high concurrency (i.e. possibly hundreds of write ops per minute to the SAME record by DIFFERENT users)

    How do you do this?

    The idea is to use update counters to ensure all paths stay at the same revision.

    1) Create an update counter which is incremented using transactions:

    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) Give it some security rules

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

    3) Give your records security rules to enforce the 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) Write the data with the update_counter

    Since you have security rules in place, records can only successfully write if the counter does not move. If it does move, then the records have been overwritten by a concurrent change, so they no longer matter (they are no longer the latest and greatest).

    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) Rollbacks

    Rollbacks are a bit more involved, but can be built off this principle:

    • store the old values before calling set
    • monitor each set op for failures
    • set back to old values on any committed changes, but keep the new counter

    A pre-built library

    I wrote up a lib today that does this and stuffed it on GitHub. Feel free to use it, but please be sure you aren't making your life complicated by reading "Do you need this?" above.

    这篇关于针对复杂写入的Firebase提交/回滚的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆