在Firebase中对私有数据访问的数据结构进行非规范化处理?

时间:2015-03-13 08:34:18

标签: json data-structures firebase jsonschema firebase-security

我想create data that scales(跟踪用户的私人数据)。

Firebase文档建议将子对象嵌套在父对象下,如下所示:

{
  "users": {
    "google:1234567890": {
      "displayName" : "Username A",
      "provider" : "google",
      "provider_id" : "1234567890",
      "todos": {
          "rec_id1": "Walk the dog",
          "rec_id2": "Buy milk",
          "rec_id3": "Win a gold medal in the Olympics",
          ...
      }
    }, ...
  }
}

(其中rec_id是Firebase.push()生成的唯一键。)

但如Denormalizing Your Data is Normal中所述,我认为以这种方式构建它会更好:

{
  "users" : {
    "google:1234567890" : {
      "displayName" : "Username A",
      "provider" : "google",
      "provider_id" : "1234567890"
    }, ...
  },
  "todos" : {
    "google:1234567890" : {
      "rec_id1" : {
        "todo" : "Walk the dog"
      },
      "rec_id2" : {
        "todo" : "Buy milk"
      },
      "rec_id3" : {
        "todo" : "Win a gold medal in the Olympics"
      }, ...
    }, ...
  }
}

然后只允许用户写入/读取它自己的数据应用以下安全规则:

{
  "rules": {
    "users": {
      "$uid": {
        // grants write and read access to the owner of this user account whose uid must exactly match the key ($uid)
        ".write": "auth !== null && auth.uid === $uid",
        ".read": "auth !== null && auth.uid === $uid"
      }
    },
    "todos": {
      "$uid": {
        // grants write and read access to the owner of this user account whose uid must exactly match the key ($uid)
        ".write": "auth !== null && auth.uid === $uid",
        ".read": "auth !== null && auth.uid === $uid"
      }
    }
  }
}

由于我是这类数据库的新手,我想知道我是否有任何不足之处,我希望如何构建它。

如第一个示例中所建议的那样,将所有待办事项直接嵌套在用户下面会更好吗?

1 个答案:

答案 0 :(得分:5)

首先,如果你还没有遇到过几个资源:

编辑:您显然已经遇到了这些资源,因为它已经在您的问题中进行了链接,但我建议您重新阅读“构建数据”指南几次。


这将我们带到你的场景......

您设置数据的两种方式实际上完成了同样的事情!

您会注意到,第一个示例实际上已列为"Structuring Your Data"指南中的反模式。

  • 如果您想要加载用户数据,然后在不同时间点击该用户,则以第二种方式执行此操作非常有用。
  • 如果只有一个用户访问每个待办事项列表,那么设置它的方式很好。
    • 例如,我在一个应用程序中执行此操作,我知道每个用户在历史记录列表中只有一个位置,我只想在某些情况下加载用户的历史记录。
    • /users/$userUid向我提供了用户数据,/history/$userUid向我提供了用户的历史记录。
    • 可以轻松分割货物。
  • 但是,如果todo列表在不同用户之间共享并且必须从多个来源更新,则此结构不会带来任何好处。
  • 如果您想要共享访问权限,那么您就是在正确的轨道上,只需要使用密钥作为参考。

不同的方法是:

  • 您可以将新的/todos/$uid对象推送到todo,以便获取新的唯一ID(称为密钥),而不是在/todos下显式设置待办事项对象。
  • 然后,您将该密钥添加到正确的user对象的todos孩子。
  • 这将允许您首先加载用户的数据,并仅获取用户所属的待办事项的索引(键),
  • 然后,您可以独立加载用户所属的所有todos

这样做会:

  • 防止用户对象变大
  • 允许多个用户更新单个todo,而无需在多个位置更新其子级参数。
  • 通过将数据分成不同的路径来获得可扩展的数据。

以下是指南"Creating Data That Scales"部分的最后一个数据示例:(我添加了一些评论)

  // An index to track Mary's memberships
  {
    "users": {
      "mchen": {
        "name": "Mary Chen",
        // index Mary's groups in her profile
        "groups": {
           // the value here doesn't matter, just that the key exists
           // these keys are used to figure out which groups should 
           // be loaded (at whatever appropriate time) for Mary,
           // without having to load all the group's data initially (just the keys). 
           "alpha": true,
           "charlie": true
        }
      },
      ...
    },
    // Here is /groups. In here, there would be a group with the key
    // 'alpha' and another one with the key 'charlie'. Once Mary's
    // data is loaded on the client, you would then proceed to load 
    // the groups from this list, since you know what keys to look for.
    "groups": { ... }
  }

这实现了flatter structure

正如文件所说,

  

是。这是双向关系的必要冗余。它允许我们快速有效地获取Mary的会员资格,即使用户或群组列表可以扩展到数百万,或者安全和Firebase规则会阻止访问某些记录。


所以问题是,你的数据怎么样才能让用户拥有多个todo列表,这些列表也可以与其他用户共享?

以下是一个例子:

{
  "users" : {
    "google:1234567890" : {
      "displayName" : "Username A",
      "provider" : "google",
      "provider_id" : "1234567890",
      "todoLists" : {
        "todoList1": true,
        "todoList2": true
      }
    },
    "google:0987654321" : {
      "displayName" : "Username B",
      "provider" : "google",
      "provider_id" : "0987654321",
      "todoLists" : {
        "todoList2": true
      }
    }
  },
  "todoLists" : {
    "todoList1" : {
      // 'members' user for rules
      "members" : {
        "google:1234567890" : true
      },
      "records" : {
        "rec_id1" : {
          "todo" : "Walk the dog",
          "createdAt" : "1426240376047"
        },
        "rec_id2" : {
          "todo" : "Buy milk",
          "createdAt" : "1426240376301"
        },
        "rec_id3" : {
          "todo" : "Win a gold medal in the Olympics",
          "createdAt" : "1426240376301"
        }
      }
    },
    "todoList2" : {
      "members" : {
        "google:1234567890" : true,
        "google:0987654321" : true
      },
      "records" : {
        "rec_id4" : {
          "todo" : "Get present",
          "createdAt" : "1426240388047"
        },
        "rec_id5" : {
          "todo" : "Run a mile",
          "createdAt" : "1426240399301"
        },
        "rec_id6" : {
          "todo" : "Pet a cat",
          "createdAt" : "1426240400301"
        }
      }
    }
  }
}
  • 在这种情况下,用户A将加载两个列表,但用户B只会加载第二个列表。如果你正确设置规则,一切都会好的。
  • 在操作中,您首先要加载用户的数据,然后从/todoList
  • 加载用户待办事项列表中的每个待办事项列表

  • 但实际上,如果您正在创建一个应用,其中一个用户有一个todo列表,其中只有一个内容的待办事项,那么所有这些都是完全没必要的。
  • 旁注,这些" ID"应该是一个唯一的密钥,可以使用Firebase.push()来完成。
    • 有关使用push生成新ID的信息,请参阅this answer

总之,这一切都归结为您的应用需要数据的方式和时间,数据更新频率以及由谁更新,以及最小化不必要的读取和观察者。空间通常很便宜,操作(和观察者)通常都不是。

最后但并非最不重要的是,规则和安全是另一个非常重要的考虑因素。指南的最后一部分说:

  

"因此,索引更快,效率也更高。后来,当我们谈论保护数据时,这种结构也将非常重要。由于安全和Firebase规则无法执行任何类型的"包含"在子节点列表中,我们将依赖于广泛使用这样的密钥。"


它很早,我希望我没有喋喋不休,但是当我第一次从了解MySql到使用非结构化时,我遇到了同样的问题,所以我希望有所帮助!