【问题标题】:Structuring Data in Firestore in belongs-to and has-many relationship在 Firestore 中以“属于”和“拥有”的关系构建数据
【发布时间】:2020-07-24 02:59:55
【问题描述】:

来自 SQL/Rails 背景,我有一个项目我想使用 Firebase/Firestore(与 React),并在设置数据和收集结构方面寻求帮助。

“模型”:

  • 用户
  • 项目
  • 类别
  • 地点

一个用户可以拥有许多项目,一个项目属于一个用户。 一个类别可以有很多项目,一个项目属于一个类别。

我需要能够在主页上显示所有类别、位置和项目。 首先,我需要列出所有项目,它们可以按类别和/或位置进行过滤。

如何最好地在 Firestore 中设置这些数据?

对于 Rails 风格的解释,我很抱歉,但 SQL 就在我的脑海里。

【问题讨论】:

  • 我们通常根据要执行的查询来构建 Firestore 数据库。这些查询是什么?
  • 我将有一个索引组件来查询所有项目以及一个过滤器组件,我将向其中传递类别和位置。单击一个项目会打开一个新页面,我会在其中列出更多详细信息(标题、描述、...以及位置、类别)。
  • 问什么还不清楚。查询是一个过滤器,所以评论也有点混乱。你为什么不只有 4 个集合。例如,项目集合包含项目文档,每个文档都有一个 belongs_to_user 字段和一个用户 uid 作为值?物品也一样。有一个项目集合,其中每个项目文档都有一个字段 belongs_to_category 字段和类别 documentID 的值?位置如何关联?你能澄清一下这个问题吗?
  • 我认为 Item 与 Location 或 User 有某种关联?

标签: firebase react-native google-cloud-firestore


【解决方案1】:

这是一种简单的方法,但它确实满足了问题中列出的要求。

您将需要四个集合,一个用于问题中提到的每个类别,然后每个集合中的文档将包含有关该项目的信息以及对谁“拥有”该文档的引用

Users
   uid_0
      name: "Kirk"
   uid_1
      name: "McCoy"
   uid_2
      name: "Spock"

Items
   item_0
      name: "Communicator"
      belongs_to_user: uid_0
      category: "cat_0"
      location: "location_3"
   item_1
      name: "Tricorder"
      belongs_to_user: "uid_1"
      category: "cat_0"
      location: "location_2"
   item_2
      name: "Captains Chair"
      belongs_to_user: "uid_0"
      category: "cat_2"
      location: "location_0"
   item_3
      name: "Science station"
      belongs_to_user: "uid_2"
      category: "cat_1"
      location: "location_0"

Categories
   cat_0
      name: "Hand held devices"
   cat_1
      name: "Stations"
   cat_2
      name: "Furniture"

Locations
   location_0
      name: "Bridge"
   location_1
      name: "Engine Room"
   location_2
      name: "Sick Bay"
   location_3
      name: "Shore leave"

一个用户可以有很多项目,一个项目属于一个用户。

用户数据存储在用户集合中,documentId 是用户 ID。在上面的示例中,用户 Kirk (uid_0) 有两个项目,item_0(通信器)和 item_2(船长椅子)。同样,用户 McCoy 有一个三录仪,Spock 作为一个科学站。

一个类别可以有很多项目,一个项目属于一个类别。

在示例中,我们有三个类别,Communicator 和 Tricorder 都属于 cat_0,手持设备。

首先我需要列出所有项目,它们可以通过 类别和/或位置。

上述结构将使您能够获得所有项目的列表。然后可以按用户、所属类别和位置过滤它们。

【讨论】:

  • 感谢您的详细介绍,我会尝试两种方法!
  • 但是您会为引用选择什么内容类型?
  • @ChristophBerger 字符串! DocumentId 只能是字符串,所以这是唯一的选择。
  • 谢谢@Jay!非常感谢,我会试一试,您的解决方案看起来非常干净且类似于 SQL。
  • @ChristophBerger 如果它最终成为解决方案,请接受它,以便它可以帮助其他人。
【解决方案2】:

假设您正在考虑外键,就像在 sql DB 中一样。在 nosql 场景中通常发生的情况是,您应该避免关系,而是将关系扁平化为“大”文档(nosql 中的 document=json 对象)。

没有具体的接收者。

Alex 试图说明的是,您应该考虑哪个是您设计中的核心元素。这似乎是“项目”元素。

可能的设计是:

  • 将类别和位置直接存储到代表您的项目的 json 对象中。

  • 然后用数组列出前端的item

  • 最后在前端使用JS的array.filter()函数来过滤响应用户动作显示的内容。

如果您只需要显示属于某个用户的项目,请在“项目”文档中添加一个“用户”字段,并要求 Firestore 仅返回那些“用户”字段与您登录的用户匹配的文档。

这有点过时了,但应该可以帮助您理解 nosql 中的数据非规范化: https://firebase.googleblog.com/2013/04/denormalizing-your-data-is-normal.html?m=1

编辑

参考 Jay 的回答和 cmets 中指出的 MongoDB 文章,我一直在努力做出决定:是嵌入数据还是引用它,但我总是最终嵌入小文档。

你能分享一下你的经验吗?

我一直觉得在前端创建额外的查询以重新加入所需的信息 (a-la sql) 很无聊,而在指向其他 small 文档方面我很少有优势。我想这主要取决于应用程序设计。

简而言之

我更喜欢类似的东西

users_collection > user_uuid_0
{
    "name": "John"
    "items": [
        {/*...plain item1 object here...*/},
        {/*...plain item2 object here...*/},
        /*...*/
    ]
}

而不是:

users_collection > user_uuid_0
{
    "name": "John"
    "items_ids":[
      "item_uuid_0",
      "item_uuid_1",
      /*...*/
    ]
}

---

items_collection > item_1
{
   /*all item fields*/
   "belong_to": "user_uuid_0"
}

items_collection > item_2
{
   /*all item fields*/
   "belong_to": "user_uuid_0"
}

后一种解决方案的好处是更加灵活和解耦:您可以更改用户详细信息而不会对项目产生任何影响,并且您的有效负载更小(如果流量大小比您服务中的操作数更重要)。

缺点是:需要对数据库进行 2 次查询(1 次用于获取用户文档,1 次用于获取按 id 过滤的项目),而只需 1 次;它需要在前端进行一些操作才能重新收集所有内容,而无需添加任何操作,因为文档的先前布局已经嵌入了所有内容。

如果满足以下条件,前者效果很好:

  • 您有一对多的关系(一对多倾向于像 Jay 建议的那样使用 uuid refs)
  • 您的文档很小(否则 Jay 的解决方案会更好)
  • 如果您按操作而非流量大小计费

当我们考虑像 firestore 这样的托管 nosql 数据库时,通常,您的 CRUD API 基本上是一个 REST api 或非常接近它的东西(没有 JPA、DTO 投影等等......),根据我的经验,第一个布局更适合。

但是,这主要与应用设计和服务成本有关。

【讨论】:

  • 这个答案因其设计简单而出类拔萃。我想提一下这个article 系列,关于在 NoSQL 中建模一对多和多对一关系。虽然这篇文章是为 MongoDB 写的,但我仍然觉得其中解释的概念很有用。
  • @Happy-Monad,我也在考虑那篇文章。
  • @ChristophBerger 这是一个非常可靠的答案,对于 Denormalizing is Normal 文章 +1 - 我在谈论结构时经常使用它。一些想法。我相信这里的建议是将项目保存在一个数组中。虽然 Firestore 支持数组,但它们不太适合 NoSQL。有很多不使用它们的理由,但对我来说,可能有 90% 的时间我可以设计出更好的方法来构建和存储数据。第二件事是保持数据浅层 - 这是 NoSQL 每次的赢家。
  • 最后是数据复制;虽然在 RTDB 中用于查询等是常见做法和普遍做法,但 Firestore 具有减轻对重复数据的大部分需求的功能。数组包含和复合查询非常有用,其中最重要的是Collection Group Queries。这是一个游戏规则改变者,并解决了许多以前需要非规范化数据的情况。所以。不要过度反规范化!保持您的结构平坦,并随意使用参考文档而不是重复数据。
  • 整个讨论非常有价值,非常感谢。谢谢大家!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-22
  • 2013-09-19
相关资源
最近更新 更多