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

121

我是新手,想知道在 Firebase 上构建数据的最佳方法。

我有一个简单的例子:

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

3个回答

154
更新:现在有一份关于数据结构的文件。doc on structuring data。此外,可以查看这篇关于NoSQL数据结构的精彩文章。
与关系型数据库相比,分层数据的主要问题在于我们可以嵌套数据,所以诱人的想法是将所有内容都放在同一个地方。通常情况下,你应该对数据进行一定程度的规范化(就像使用SQL时一样),尽管缺少连接语句和查询。
在需要提高读取效率的地方,也要进行去规范化。这是所有大型应用程序(例如Twitter和Facebook)使用的技术。虽然这与我们的DRY原则相悖,但通常是可扩展应用程序的必要特性。
要点在于,你需要在写入方面努力,以便使读取变得容易。将逻辑上不同的组件分开(例如,在聊天室中,如果您希望稍后迭代组,则不要将消息、关于房间的元信息和成员列表全部放在同一个位置)。
Firebase实时数据与SQL环境之间的主要区别在于查询数据。由于数据实时性(它不断变化、分片和调和),没有简单的方法可以说“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));

这种方法的问题在于,我刚刚强制客户端下载了所有用户的信息和小部件。如果其中任何一项数量超过数千个,则会有很大问题。所以现在,分层实时结构的最佳策略变得更加明显:
/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的文章,因此我就不在这里重复了,但是那也是一篇很棒的文章。


谢谢你的见解,Kato! - hopper
2
目前为止,Firebase v2版本中的视图将包含一些出色的功能,可用于自动化该过程。 - Kato
我知道我在这里重新激活了一个旧的评论线程,但我很难找到一个更现代的解决方案。这仍然是最好的方法吗?即获取所有具有Gmail帐户的用户,然后运行forEach? - owiewio

50

Firebase与关系型数据库有很大不同,如果想进行比较,我会将其与分层数据库进行比较。

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:{
    .
    .
}}

很好,但我有一个后续问题,我们如何使用Firebase SDK从Swift或任何其他地方创建此结构?另外,我们如何使用Firebase验证规则验证添加到应用程序节点的新数据实际上存在于应用程序列表中? - Tommie C.
@prateep,好的例子。但问题在于当我删除路径applications/application1时,application1是某些申请人的子级。如果我尝试访问不存在的路径applicants/application1,那么你需要在两个地方更新索引,如application1:{applicants:{applicant1:true} ...},所以现在当我删除applicantion1时,我必须检查它的子申请人并更新申请人的子节点以适应application。 :) - Satish Sojitra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接