【问题标题】:What's the best way of structuring data on firebase?在 Firebase 上构建数据的最佳方式是什么?
【发布时间】:2013-05-01 12:47:01
【问题描述】:

我是 firebase 新手,我想知道在其上构建数据的最佳方式是什么。

我有一个简单的例子:

我的项目有申请人和申请。 1个申请人可以有多个申请。如何在 firebase 上关联这 2 个对象?它像关系数据库一样工作吗?还是在数据设计方面需要完全不同的方法?

【问题讨论】:

    标签: firebase data-structures nosql firebase-realtime-database


    【解决方案1】:

    更新:现在有一个doc on structuring data。另外,请参阅NoSQL data structures 上的这篇精彩帖子。

    与 RDBMS 相比,分层数据的主要问题是嵌套数据很诱人,因为我们可以。通常,尽管缺少连接语句和查询,您仍希望在某种程度上规范化数据(就像使用 SQL 一样)。

    您还想在关注读取效率的地方denormalize。这是所有大型应用程序(例如 Twitter 和 Facebook)都使用的一种技术,虽然它违反了我们的 DRY 原则,但它通常是可扩展应用程序的必要功能。

    这里的要点是,您要努力进行写入以使读取变得容易。将单独阅读的逻辑组件分开(例如,对于聊天室,不要将消息、有关房间的元信息和成员列表放在同一个地方,如果您希望以后能够迭代组)。

    Firebase 的实时数据与 SQL 环境之间的主要区别在于查询数据。没有简单的方法可以说“SELECT USERS WHERE X = Y”,因为数据的实时性(它不断变化、分片、协调等,这需要更简单的内部模型来控制同步客户端)

    一个简单的例子可能会让你处于正确的心态,所以这里是:

    /users/uid
    /users/uid/email
    /users/uid/messages
    /users/uid/widgets
    

    现在,由于我们处于分层结构中,如果我想迭代用户的电子邮件地址,我会这样做:

    // I could also use on('child_added') here to great success
    // but this is simpler for an example
    firebaseRef.child('users').once('value')
    .then(userPathSnapshot => {
       userPathSnapshot.forEach(
          userSnap => console.log('email', userSnap.val().email)
       );
    })
    .catch(e => console.error(e));
    

    这种方法的问题是我刚刚强迫客户端下载所有用户的messageswidgets。如果这些东西都没有以数千计,那也没什么大不了的。但对于 10k 用户来说,每人发送超过 5k 条消息,这很重要。

    所以现在分层实时结构的最佳策略变得更加明显:

    /user_meta/uid/email
    /messages/uid/...
    /widgets/uid/...
    

    在这种环境中非常有用的另一个工具是索引。通过创建具有某些属性的用户的索引,我可以通过简单地迭代索引来快速模拟 SQL 查询:

    /users_with_gmail_accounts/uid/email
    

    现在,如果我想为 gmail 用户获取消息,我可以这样做:

    var ref = firebase.database().ref('users_with_gmail_accounts');
    ref.once('value').then(idx_snap => {
       idx_snap.forEach(idx_entry => {
           let msg = idx_entry.name() + ' has a new message!';
           firebase.database().ref('messages').child(idx_entry.name())
              .on(
                 'child_added', 
                 ss => console.log(msg, ss.key);
              );
       });
    })
    .catch(e => console.error(e));
    

    我在另一篇关于非规范化数据的 SO 帖子中提供了一些细节,so check those out as well。我看到 Frank 已经发布了 Anant 的文章,所以我不会在这里重复,但它也是一个很好的阅读。

    【讨论】:

    • 感谢加藤的洞察力!
    • 暂时。 Firebase v2 版本中的视图将包含一些用于自动化该过程的强大功能。
    • 意识到我在这里复活了一个旧的评论线程,但我正在努力寻找一个更新的解决方案。这仍然是最好的方法吗?即获取所有users_with_gmail_accounts,然后运行forEach?
    【解决方案2】:

    Firebase 与关系数据库非常。如果您想将其与任何东西进行比较,我会将其与分层数据库进行比较。

    Anant 最近在 Firebase 博客上写了一篇关于非规范化数据的精彩文章:https://www.firebase.com/blog/2013-04-12-denormalizing-is-normal.html

    我确实建议将每个申请的“ID”保留为每个申请人的孩子。

    【讨论】:

    • 谢谢弗兰克!这真的很有帮助。正是我想要的!
    【解决方案3】:

    您的场景在关系世界中看起来像一对多,根据您的示例,申请人有许多应用程序。如果我们采用 firebase nosql 方式,它如下所示。它应该在没有任何性能问题的情况下扩展。这就是为什么我们需要如下所述的非规范化。

    applicants:{
    applicant1:{
        .
        .
        applications:{
            application1:true,
            application3:true
        }
    },
    applicant2:{
        .
        .
        applications:{
            application2:true,
            application4:true
        }
    }}
    
    applications:{
    application1:{
        .
        .
    },
    application2:{
        .
        .
    },
    application3:{
        .
        .
    },
    application4:{
        .
        .
    }}
    

    【讨论】:

    • 好,但我有后续,我们如何从 Swift 或任何使用 Firebase SDK 的地方创建这个结构?另外,我们如何使用 Firebase 验证规则验证添加到应用程序节点的新数据是否确实存在于应用程序列表中?
    • @prateep,很好的例子。但是这里的问题是当我删除路径 applications/application1 时 application1 是某些申请人的孩子。如果我尝试访问不存在的路径申请者/应用程序 1。所以你需要更新两个地方的索引,比如 application1:{applicant:{applicant1:true} ...} 所以现在当我删除申请时,我必须检查它的子申请并更新申请的申请子节点。 :)
    猜你喜欢
    • 1970-01-01
    • 2022-01-19
    • 1970-01-01
    • 2013-07-22
    • 1970-01-01
    • 1970-01-01
    • 2010-10-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多