如何在Firebase中编写非规范化数据

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在Firebase中编写非规范化数据相关的知识,希望对你有一定的参考价值。

我在Stucturing Data上阅读过Firebase文档。数据存储很便宜,但用户的时间不是。我们应该优化get操作,并在多个地方写。

那么我可能会存储一个列表节点和一个列表索引节点,两者之间有一些重复的数据,至少是列表名称。

我正在使用ES6并在我的javascript应用程序中承诺处理异步流,主要是在第一次数据推送后从firebase获取ref密钥。

let addIndexPromise = new Promise( (resolve, reject) => {
    let newRef = ref.child('list-index').push(newItem);
    resolve( newRef.key()); // ignore reject() for brevity
});
addIndexPromise.then( key => {
   ref.child('list').child(key).set(newItem);
 });

我知道我的应用程序只在客户端上运行,如何确保数据在所有地方保持同步?

为了进行健全性检查,我在我的承诺中设置了setTimeout并在解析之前关闭了我的浏览器,实际上我的数据库不再一致,保存了额外的索引而没有相应的列表。

有什么建议?

答案

好问题。我知道有三种方法,我将在下面列出。

我将采用一个稍微不同的例子,主要是因为它允许我在解释中使用更具体的术语。

假设我们有一个聊天应用程序,我们存储两个实体:消息和用户。在我们显示消息的屏幕中,我们还显示用户的名称。因此,为了最大限度地减少读取次数,我们也会将每个聊天消息的用户名称存储起来。

users
  so:209103
    name: "Frank van Puffelen"
    location: "San Francisco, CA"
    questionCount: 12
  so:3648524
    name: "legolandbridge"
    location: "London, Prague, Barcelona"
    questionCount: 4
messages
  -Jabhsay3487
    message: "How to write denormalized data in Firebase"
    user: so:3648524
    username: "legolandbridge"
  -Jabhsay3591
    message: "Great question."
    user: so:209103
    username: "Frank van Puffelen"
  -Jabhsay3595
    message: "I know of three approaches, which I'll list below."
    user: so:209103
    username: "Frank van Puffelen"

因此,我们将用户配置文件的主副本存储在users节点中。在消息中我们存储uid(所以:209103等等:3648524),以便我们可以查找用户。但是我们还将用户的名称存储在消息中,这样当我们想要显示消息列表时,我们就不必为每个用户查找。

那么现在当我进入聊天服务的“个人资料”页面并将我的名字从“Frank van Puffelen”更改为“puf”时会发生什么。

Transactional update

执行事务更新是大多数开发人员最初可能会想到的。我们总是希望消息中的username与相应配置文件中的name匹配。

使用多路写入(在20150925上添加)

自Firebase 2.3(适用于JavaScript)和2.4(适用于androidios)以来,您可以通过使用单个多路径更新轻松实现原子更新:

function renameUser(ref, uid, name) {
  var updates = {}; // all paths to be updated and their new values
  updates['users/'+uid+'/name'] = name;
  var query = ref.child('messages').orderByChild('user').equalTo(uid);
  query.once('value', function(snapshot) {
    snapshot.forEach(function(messageSnapshot) {
      updates['messages/'+messageSnapshot.key()+'/username'] = name;
    })
    ref.update(updates);
  });
}

这将向Firebase发送单个更新命令,以在其配置文件和每条消息中更新用户的名称。

以前的原子方法

因此,当用户更改其个人资料中的name时:

var ref = new Firebase('https://mychat.firebaseio.com/');
var uid = "so:209103";
var nameInProfileRef = ref.child('users').child(uid).child('name');
nameInProfileRef.transaction(function(currentName) {
  return "puf";
}, function(error, committed, snapshot) {
  if (error) { 
    console.log('Transaction failed abnormally!', error);
  } else if (!committed) {
    console.log('Transaction aborted by our code.');
  } else {
    console.log('Name updated in profile, now update it in the messages');
    var query = ref.child('messages').orderByChild('user').equalTo(uid);
    query.on('child_added', function(messageSnapshot) {
      messageSnapshot.ref().update({ username: "puf" });
    });
  }
  console.log("Wilma's data: ", snapshot.val());
}, false /* don't apply the change locally */);

非常参与,精明的读者会注意到我在处理消息时作弊。第一个作弊是我从不为听众调用off,但我也不使用交易。

如果我们想从客户端安全地执行此类操作,我们需要:

  1. 安全规则,确保两个地方的名称匹配。但是规则需要允许足够的灵活性,以便在我们更改名称时暂时保持不同。所以这变成了一个非常痛苦的两阶段提交方案。 将所有username字段的消息更改为so:209103null(一些神奇的值) 将用户nameso:209103更改为'puf' 改变usernameso:209103 nullpuf的每条消息中。 该查询需要两个条件的and,Firebase查询不支持这两个条件。因此,我们最终会得到一个额外的属性uid_plus_name(值为so:209103_puf),我们可以查询。
  2. 客户端代码,以事务方式处理所有这些转换。

这种方法让我头疼。通常这意味着我做错了什么。但即使这是正确的方法,头部受到伤害我更有可能犯编码错误。所以我更愿意寻找更简单的解决方案。

Eventual consistency

更新(20150925):Firebase发布了一项功能,允许对多个路径进行原子写入。这与下面的方法类似,但只有一个命令。请参阅上面的更新部分以了解其工作原理。

第二种方法取决于将用户操作(“我想将我的名字改为'puf'”)与该操作的含义分开(“我们需要更新配置文件中的名称,因此:209103和每个包含user = so:209103的消息) 。

我将在我们在服务器上运行的脚本中处理重命名。主要方法是这样的:

function renameUser(ref, uid, name) {
  ref.child('users').child(uid).update({ name: name });
  var query = ref.child('messages').orderByChild('user').equalTo(uid);
  query.once('value', function(snapshot) {
    snapshot.forEach(function(messageSnapshot) {
      messageSnapshot.update({ username: name });
    })
  });
}

我再次在这里采取一些快捷方式,例如使用once('value'(这通常是使用Firebase获得最佳性能的一个坏主意)。但总的来说,这种方法更简单,代价是不能同时完全更新所有数据。但最终消息将全部更新以匹配新值。

Not caring

第三种方法是最简单的方法:在许多情况下,您根本不需要更新重复数据。在我们在这里使用的示例中,您可以说每条消息都记录了我当时使用的名称。直到现在我才更改我的名字,所以有意义的是旧的消息显示我当时使用的名称。这适用于次要数据本质上是事务性的许多情况。它当然不适用于所有地方,但适用于“不关心”的地方是最简单的方法。

Summary

虽然以上只是对如何解决这个问题的广泛描述而且它们肯定是不完整的,但我发现每次我需要扇出重复数据时,它会回到这些基本方法之一。

另一答案

为了增加Franks的好评,我用一组Firebase Cloud Functions实现了最终的一致性方法。只要主值(例如用户名)发生更改,就会触发函数,然后将更改传播到非规范化字段。

它没有交易那么快,但在许多情况下它并不需要。

以上是关于如何在Firebase中编写非规范化数据的主要内容,如果未能解决你的问题,请参考以下文章

尝试了解在我的场景中使用 firebase 数据库进行非规范化工作的效果如何

AngularFire - 如何查询非规范化数据?

如何向 Firebase 验证服务器?

如何从 Firebase 获取数据到 Recyclerview 中的片段?

如何从一个片段中删除数据,这些片段应该反映在google firebase中的其他片段中

片段中的Firebase数据不是持久的,会重新下载