在Firebase上构造数据的最佳方法是什么?


111

我是Firebase的新手,我想知道构造数据的最佳方法是什么。

我有一个简单的例子:

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

Answers:


137

更新:现在有一个关于数据结构文档。另外,请参见这篇有关NoSQL数据结构的优秀文章。

与RDBMS相比,分层数据的主要问题在于,由于我们可以嵌套数据,因此很容易嵌套。通常,尽管缺少join语句和查询,您还是希望在某种程度上标准化数据(就像使用SQL一样)。

您还希望在关注读取效率的地方进行非规范化。这是所有大型应用程序(例如Twitter和Facebook)使用的技术,尽管它违反了我们的DRY原则,但通常是可伸缩应用程序的必要功能。

这里的要点是您要努力进行写操作以使读操作变得容易。将分别读取的逻辑组件分开放置(例如,对于聊天室,不要将消息,有关会议室的元信息以及成员列表都放在同一位置,如果您希望以后可以迭代这些组)。

Firebase的实时数据和SQL环境之间的主要区别是查询数据。由于数据的实时性,没有简单的说法“在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太。如果这些东西都没有成千上万,那就没什么大不了了。但对于1万名用户而言,每条消息多达5千条,意义重大。

因此,现在,用于分层实时结构的最佳策略变得更加明显:

/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帖子中提供了有关非规范化数据的一些详细信息,因此也请检查一下。我看到Frank已经发布了Anant的文章,因此在这里我不再重申,但这也是一本好书。


感谢您的加藤见识!
漏斗

2
暂且。Firebase v2版本中的视图将包含一些使该过程自动化的强大功能。
加藤

意识到我要在这里恢复旧的注释线程,但是我在努力寻找更新的解决方案。这仍然是最好的方法吗?即获取所有users_with_gmail_accounts,然后运行forEach?
owiewio

48

火力地堡是非常一样的关系数据库。如果您想将其与任何事物进行比较,我会将其与分层数据库进行比较。

Anant最近在Firebase博客上写了一篇很棒的帖子,内容涉及对数据进行非规范化:https//www.firebase.com/blog/2013-04-12-denormalizing-is-normal.html

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


谢谢弗兰克!这真的很有帮助。正是我想要的!
漏斗

4

在关系世界中,您的方案看起来像一对多,例如您的申请者有很多申请。如果我们使用Firebase Nosql的方式,则如下所示。它应该可以扩展,而不会出现任何性能问题。这就是为什么我们需要如下所述的非规范化。

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

applications:{
application1:{
    .
    .
},
application2:{
    .
    .
},
application3:{
    .
    .
},
application4:{
    .
    .
}}

很好,但是我有一个后续文章,我们如何从Swift或使用Firebase SDK的任何地方创建此结构?另外,我们如何使用Firebase验证规则验证添加到应用程序节点的新数据是否确实存在于应用程序列表中?
Tommie C.

@prateep,很好的例子。但是这里的问题是当我删除路径application / application1时,其中application1对于某些申请人而言是子级的。如果我尝试访问不存在的路径申请人/ application1。因此,您需要在application1:{申请人:{applicant1:true} ...}之类的两个地方更新索引,因此现在当我删除applicant1时,我必须检查其子申请人并更新申请人的子节点进行申请。:)
Satish Sojitra
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.