Firebase:通过每个用户的副本构建数据?数据损坏的风险? [英] Firebase: structuring data via per-user copies? Risk of data corruption?

查看:178
本文介绍了Firebase:通过每个用户的副本构建数据?数据损坏的风险?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

实现一个具有多对多关系的Android + Web(Angular)+ Firebase应用程序:用户>小工具(小工具可以共享给多个用户)。

注意事项:
$ b $ ol
列出用户拥有的所有小组件。

  • 用户只能看到共享给他/她的小部件。
  • 能够看到给定的小部件共享的所有用户。

  • 一个Widget可以由拥有相同权限的多个用户拥有/管理(修改Widget并将其更改为共享对象)。类似于Google云端硬盘分享给特定用户的方式。

  • 实现抓取(join-style)的方法之一是去与这个建议: https://www.firebase.com/docs /android/guide/structuring-data.html 加入展开数据)。
    但是我对这种方法有所怀疑,因为我发现数据加载会很慢(至少在Android上) - 我在另一个问题上询问了这个问题 - Firebase Android:慢速加入使用许多监听器,似乎与文档相矛盾。



    所以,这个问题是关于另一种方法:用户拥有的所有Widgets的每用户副本。如Firebase + Udacity教程ShoppingList ++( https://www.firebase.com/blog/2015-12-07-udacity-course-firebase-essentials.html )。



    他们的结构如下所示:

    特别是这个部分 - userLists

     userLists:{
    abc @ gmail,com:{
    -KBt0MDWbvXFwNvZJXTj:{
    listName :
    owner:xyz @ gmail,com,
    timestampCreated:{
    timestamp:1456950573084
    },
    timestampLastChanged:{
    timestamp:1457044229747
    },
    timestampLastChangedReverse:{
    timestamp:-1457044229747
    }
    }
    },
    xyz @ gmail,com:{
    -KBt0MDWbvXFwNvZJXTj:{
    listName :Test List 1 Rename 2,
    owner:xyz @ gmail,com,
    timestampCreated:{
    timestamp:1456950573084
    },
    timestampLastChanged:{
    timestamp:1457044229747
    },
    timestampLastChangedReverse:{
    timestamp:-1457044229747
    }
    $,
    -KByb0imU7hFzWTK4eoM:{
    listName:List2,
    owner:xyz @ gmail,com,
    timestampCreated: {
    timestamp:1457044332539
    },
    timestampLastChanged:{
    timestamp:1457044332539
    },
    timestampLastChangedReverse:{
    timestamp:-1457044332539
    }
    }
    }
    },

    正如您所看到的,购物清单Test List 1 Rename 2 info的副本出现在两个地方(2个用户)。



      {
    ownerMappings:{
    -KBt0MDWbvXFwNvZJXTj:xyz @ gmail,com,
    -KByb0imU7hFzWTK4eoM:xyz @ gmail,com
    },
    sharedWith:{
    -KBt0MDWbvXFwNvZJXTj :{
    abc @ gmail,com:{
    email:abc @ gmail,com,
    hasLoggedInWithPassword:false,
    name: Agenda TEST,
    timestampJoined:{
    timestamp:1456950523145
    }
    }
    }
    },
    shoppingListItems :{
    -KBt0MDWbvXFwNvZJXTj:{
    -KBt0heZh-YDWIZNV7xs:{
    买入:false,
    itemName:item,
    owner:xyz @ gmail,com
    }
    }
    },
    uidMappings:{
    google:112894577549422030859:abc @ gmail,com,
    google:117151367009479509658:xyz @ gmail,com
    },
    userFriends:{
    xyz @ gmail,com
    abc @ gmail,com:{
    email:abc @ gmail,com,
    hasLoggedInWithPassword:false,
    name:议程TEST ,
    timestampJoined:{
    timestamp:1456950523145
    }
    }
    }
    },

    用户:{
    abc @ gmail,com:{
    email:abc @ gmail,com,
    hasLoggedInWithPassword:false,
    name :议程测试,
    timestampJoined:{
    timestamp:1456950523145
    }
    },
    xyz @ gmail,com:{
    email:xyz @ gmail,com,
    hasLoggedInWithPassword:false,
    name:Karol Depka,
    timestampJoined:{
    timestamp:1456952940258
    }
    }
    }
    }


    $ b $但是,在我的应用程序跳到实现类似的结构之前,我想澄清一些疑问。

    这里是我的interrel在他们的ShoppingList ++应用程序中,他们只允许在 ownerMappings <<$> / code>节点。因此没有其他人可以重命名购物清单。我想有多个所有者/管理员,拥有平等的权利。这样一个每用户保留复制结构仍然适用于多个所有者/管理员用户,而不会冒数据损坏/不同步或恶作剧的风险?

  • 数据损坏可能出现在场景中像这样:User1下线,将Widget1重命名为Widget1Prim。 User1处于脱机状态时,User2将Widget1共享给User3(User3的副本尚未知道重命名)。 User1上线并发送有关Widget1重命名的信息(仅限于他自己的和User2的副本,客户端代码在重命名时已知道 - 不更新User3的副本)。现在,在一个天真的实现,User3将有旧的名字,而其他人将有新的名字。这可能是罕见的,但仍然有点担心。
  • 可能/应该点2的数据损坏情况。 (例如,在AppEngine上)听取更改并确保向所有用户副本进行正确的传播?
  • 和/或可能/应该点2中的数据损坏情况。通过对共享和重命名的改变执行冗余监听来解决,并将更改传播到每个用户的副本,以处理特殊情况?大多数时候这不是必须的,所以会导致性能/带宽损失和复杂的代码。是否值得?

  • 接下来,一旦我们有多个版本疯狂地部署,如果进行多少数据处理责任在于客户端的代码?例如,如果我们添加一个新的关系,旧的客户端版本还不知道,看起来不是很脆弱?然后,返回到例如服务器端的syncer-ensurerer进程。 AppEngine(描述问题3.)?

  • 看起来像是一个好主意,也有一个每个Widget /购物清单的主参考副本,以便为任何可以更新每个用户副本的syncer-ensurerer操作类型提供良好的真相源。
  • 关于rules.json / rules.bolt权限的任何特殊注意事项/陷阱/阻止程序对于以这种(多余的)方式构建的数据,我们可以通过知道原子多路径更新> updateChildren() - 肯定会使用它们。



    欢迎任何其他提示/观察。 TIA。

    解决方案

    我建议整个系统只有一个小部件的副本。它将有一个来源用户ID和一组有权访问它的用户。小部件树可以保存用户权限和更改历史记录。任何时候进行更改,都会将分支添加到树中。然后分支机构可以被升级到像GIT那样的主类。这将保证数据的完整性,因为以前的版本从未被更改或删除。这也将简化你的提取...我认为:)

    pre
    用户:
    bob :{
    widgets:[
    xxx:{
    widgetKey:xyz,
    permissions:*,
    lastEdit ...
    }
    ]



    widgets:
    xyz:{
    masterKey:abc,
    data:{...} ,
    所有者:bob,
    },
    ...
    ]
    widgetHistory:[
    xyz:[
    v1:{
    data:{...},
    },
    v2,
    v3
    ]
    123:[
    ...
    ],
    ...
    ]
    }


    Implementing an Android+Web(Angular)+Firebase app, which has a many-to-many relationship: User <-> Widget (Widgets can be shared to multiple users).

    Considerations:

    1. List all the Widgets that a User has.
    2. A User can only see the Widgets which are shared to him/her.
    3. Be able to see all Users to whom a given Widget is shared.
    4. A single Widget can be owned/administered by multiple Users with equal rights (modify Widget and change to whom it is shared). Similar to how Google Drive does sharing to specific users.

    One of the approaches to implement fetching (join-style), would be to go with this advice: https://www.firebase.com/docs/android/guide/structuring-data.html ("Joining Flattened Data") via multiple listeners. However I have doubts about this approach, because I have discovered that data loading would be worryingly slow (at least on Android) - I asked about it in another question - Firebase Android: slow "join" using many listeners, seems to contradict documentation .

    So, this question is about another approach: per-user copies of all Widgets that a user has. As used in the Firebase+Udacity tutorial "ShoppingList++" ( https://www.firebase.com/blog/2015-12-07-udacity-course-firebase-essentials.html ).

    Their structure looks like this:

    In particular this part - userLists:

      "userLists" : {
        "abc@gmail,com" : {
          "-KBt0MDWbvXFwNvZJXTj" : {
            "listName" : "Test List 1 Rename 2",
            "owner" : "xyz@gmail,com",
            "timestampCreated" : {
              "timestamp" : 1456950573084
            },
            "timestampLastChanged" : {
              "timestamp" : 1457044229747
            },
            "timestampLastChangedReverse" : {
              "timestamp" : -1457044229747
            }
          }
        },
        "xyz@gmail,com" : {
          "-KBt0MDWbvXFwNvZJXTj" : {
            "listName" : "Test List 1 Rename 2",
            "owner" : "xyz@gmail,com",
            "timestampCreated" : {
              "timestamp" : 1456950573084
            },
            "timestampLastChanged" : {
              "timestamp" : 1457044229747
            },
            "timestampLastChangedReverse" : {
              "timestamp" : -1457044229747
            }
          },
          "-KByb0imU7hFzWTK4eoM" : {
            "listName" : "List2",
            "owner" : "xyz@gmail,com",
            "timestampCreated" : {
              "timestamp" : 1457044332539
            },
            "timestampLastChanged" : {
              "timestamp" : 1457044332539
            },
            "timestampLastChangedReverse" : {
              "timestamp" : -1457044332539
            }
          }
        }
      },
    

    As you can see, the copies of shopping list "Test List 1 Rename 2" info appears in two places (for 2 users).

    And here is the rest for completeness:

    {
      "ownerMappings" : {
        "-KBt0MDWbvXFwNvZJXTj" : "xyz@gmail,com",
        "-KByb0imU7hFzWTK4eoM" : "xyz@gmail,com"
      },
      "sharedWith" : {
        "-KBt0MDWbvXFwNvZJXTj" : {
          "abc@gmail,com" : {
            "email" : "abc@gmail,com",
            "hasLoggedInWithPassword" : false,
            "name" : "Agenda TEST",
            "timestampJoined" : {
              "timestamp" : 1456950523145
            }
          }
        }
      },
      "shoppingListItems" : {
        "-KBt0MDWbvXFwNvZJXTj" : {
          "-KBt0heZh-YDWIZNV7xs" : {
            "bought" : false,
            "itemName" : "item",
            "owner" : "xyz@gmail,com"
          }
        }
      },
      "uidMappings" : {
        "google:112894577549422030859" : "abc@gmail,com",
        "google:117151367009479509658" : "xyz@gmail,com"
      },
      "userFriends" : {
        "xyz@gmail,com" : {
          "abc@gmail,com" : {
            "email" : "abc@gmail,com",
            "hasLoggedInWithPassword" : false,
            "name" : "Agenda TEST",
            "timestampJoined" : {
              "timestamp" : 1456950523145
            }
          }
        }
      },
    
      "users" : {
        "abc@gmail,com" : {
          "email" : "abc@gmail,com",
          "hasLoggedInWithPassword" : false,
          "name" : "Agenda TEST",
          "timestampJoined" : {
            "timestamp" : 1456950523145
          }
        },
        "xyz@gmail,com" : {
          "email" : "xyz@gmail,com",
          "hasLoggedInWithPassword" : false,
          "name" : "Karol Depka",
          "timestampJoined" : {
            "timestamp" : 1456952940258
          }
        }
      }
    }
    

    However, before I jump into implementing a similar structure in my app, I would like to clarify a few doubts.

    Here are my interrelated questions:

    1. In their ShoppingList++ app, they only permit a single "owner" - assigned in the ownerMappings node. Thus no-one else can rename the shopping list. I would like to have multiple "owners"/admins, with equal rights. Would such a keep-copies-per-user structure still work for multiple owner/admin users, without risking data corruption/"desynchronization" or "pranks"?
    2. Could data corruption arise in scenarios like this: User1 goes offline, renames Widget1 to Widget1Prim. While User1 is offline, User2 shares Widget1 to User3 (User3's copy would not yet be aware of the rename). User1 goes online and sends the info about the rename of Widget1 (only to his own and User2's copies, of which the client code was aware at the time of the rename - not updating User3's copy). Now, in a naive implementation, User3 would have the old name, while the others would have the new name. This would probably be rare, but still worrying a bit.
    3. Could/should the data corruption scenario in point "2." be resolved via having some process (e.g. on AppEngine) listening to changes and ensuring proper propagation to all user copies?
    4. And/or could/should the data corruption scenario in point "2." be resolved via implementing a redundant listening to both changes of sharing and renaming, and propagating the changes to per-user copies, to handle the special case? Most of the time this would not be necessary, so it could result in performance/bandwidth penalty and complicated code. Is it worth it?
    5. Going forward, once we have multiple versions deployed "in the wild", wouldn't it become unwieldy to evolve the schema, given how much of the data-handling responsibility lies with the code in the clients? For example if we add a new relationship, that the older client versions don't yet know about, doesn't it seem fragile? Then, back to the server-side syncer-ensurerer process on e.g. AppEngine (described in question "3.") ?
    6. Would it seem like a good idea, to also have a "master reference copy" of every Widget / shopping-list, so as to give good "source of truth" for any syncer-ensurerer type of operations that would update per-user copies?
    7. Any special considerations/traps/blockers regarding rules.json / rules.bolt permissions for data structured in such a (redundant) way ?

    PS: I know about atomic multi-path updates via updateChildren() - would definitely use them.

    Any other hints/observations welcome. TIA.

    解决方案

    I suggest having only one copy of a widget for the entire system. It would have an origin user ID, and a set of users that have access to it. The widget tree can hold user permissions and change history. Any time a change is made, a branch is added to the tree. Branches can then be "promoted" to the "master" kind of like GIT. This would guarantee data integrity because past versions are never changed or deleted. It would also simplify your fetches... I think :)

    { 
      users:[
        bob:{
          widgets:[
            xxx:{
               widgetKey: xyz,
               permissions: *,
               lastEdit... 
            }
          ]
        }
        ...
      ]
      widgets:[
        xyz:{
           masterKey:abc,
           data: {...},
           owner: bob,
        },
        ...
      ]
      widgetHistory:[
        xyz:[
          v1:{
             data:{...},
          },
          v2,
          v3
        ]
        123:[
           ...
        ],
        ...
      ]
     }
    

    这篇关于Firebase:通过每个用户的副本构建数据?数据损坏的风险?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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