CouchDB 文档建模原则

我有一个问题,我一直想回答一段时间了,但不能弄明白:

How do you design, or divide up, CouchDB documents?

以一篇博客文章为例。

半“关系”的方法是创建一些对象:

  • 邮局
  • 用户
  • 评论
  • 标签
  • 片段

这就说得通了。但是我正在尝试使用 couchdb (出于所有这些原因,它很棒)来建模同样的事情,这是非常困难的。

大多数的博客文章都给出了一个简单的例子来说明如何做到这一点。它们基本上以相同的方式划分,但假设您可以向每个文档添加“任意”属性,这绝对是好的。所以在 CouchDB 中会有这样的东西:

  • 发布(在文档中使用“伪”模型的标记和片段)
  • 评论
  • 用户

有些人甚至会说你可以把评论和用户放进去,这样你就有了:



编号: 123412804910820
标题: 《我的职位》
主体: “许多内容”
Html: “ < p > 许多内容 

” 作者: 姓名: 兰斯 年龄: “23” } tags: ["sample", "post"] 意见{ 评论{ 编号: 93930414809 “有趣的帖子” } 评论{ 编号: 19018301989 body: "I agree" } } }

That looks very nice and is easy to understand. I also understand how you could write views that extracted just the Comments from all your Post documents, to get them into Comment models, same with Users and Tags.

但随后我又想,“为什么不把我的整个网站放到一个单独的文档中呢?”:



域名: 「 www.blog.com 」
主人: “我”
页{
页{
标题: “博客”
职位{
后{
编号: 123412804910820
标题: 《我的职位》
主体: “许多内容”
html: "<p>Lots of Content</p>"
作者:
姓名: 兰斯
年龄: “23”
}
标签: [“ sample”,“ post”]
意见{
comment {
编号: 93930414809
“有趣的帖子”
}
评论{
编号: 19018301989
我同意
}
}
}
后{
id: 18091890192984
标题: “第二职位”
...
}
}
}
}
}

您可以很容易地使视图找到您想要的东西。

那么我的问题是,你如何决定什么时候把文档分成更小的文档,或者什么时候在文档之间建立“关系”?

我认为如果像这样划分的话,它会更加“面向对象”,并且更容易映射到值对象:


职位{
后{
编号: 123412804910820
标题: 《我的职位》
主体: “许多内容”
Html: “ < p > 许多内容 

” Author _ id: “ Lance1231” 标签: [“ sample”,“ post”] } } 作者 author { 编号: Lance1231 姓名: 兰斯 年龄: “23” } } comments { 评论{ Id: “ comment1” “有趣的帖子” Post _ id: 123412804910820 } comment { Id: “ comment2” 我同意 Post _ id: 123412804910820 } }

但后来看起来更像是关系数据库。而且通常我继承的东西看起来像“文档中的整个站点”,所以用关系建模它更加困难。

我读过很多关于如何/何时使用关系数据库和文档数据库的文章,所以这不是主要问题。我更想知道的是,在 CouchDB 中建模数据时应用什么样的好规则/原则。

另一个例子是 XML 文件/数据。一些 XML 数据有10多个层次的嵌套,我想使用相同的客户端(例如,Ajax on Rails,或 Flex)可视化,我想呈现来自 ActiveRecord,CouchRestor 任何其他对象关系映射器的 JSON。有时候我会得到一个巨大的 XML 文件,它们是整个站点的结构,就像下面这个,我需要将它映射到 Value Objects,以便在我的 Rails 应用程序中使用,这样我就不必编写另一种序列化/反序列化数据的方法:


<pages>
<page>
< 子页 > 
< 子页 > 
< 图片 > 
< 图片 > 
< 网址/> 
</image>
 
 
 
</page>
 

所以一般的 CouchDB 问题是:

  1. 您使用什么规则/原则来划分您的文档(关系等) ?
  2. 将整个站点放在一个文档中可以吗?
  3. 如果是这样,如何处理具有任意深度级别的序列化/反序列化文档(如上面的大型 json 示例或 xml 示例) ?
  4. 或者你不把它们变成 VOs,你只是决定“这些对象关系映射太嵌套了,所以我只是使用原始的 XML/JSON 方法访问它们”?

非常感谢您的帮助,如何在 CouchDB 中划分数据的问题让我很难说“从现在开始我就应该这样做”。我希望很快就能到那儿。

我研究了以下网站/项目。

  1. CouchDB 中的分层数据
  2. CouchDB 维基
  3. Sofa-CouchDB 应用程序
  4. 最终指南
  5. PeepCode CouchDB 屏幕播放
  6. CouchRest
  7. CouchDB 自述

...but they still haven't answered this question.

14502 次浏览

说,如果我没记错的话,要去规范化,直到“受伤”,同时要记住文档更新的频率。

  1. 您使用什么规则/原则来划分您的文档(关系等) ?

作为一个经验法则,我包括所有的数据,需要显示一个有关该项目的页面。换句话说,你在现实世界中交给别人的一张纸上打印的所有东西。例如,股票报价文件将包括公司名称、交易所名称、货币名称以及数字; 合同文件将包括交易对手的名称和地址,以及关于日期和签字人的所有信息。但不同日期的股票报价将构成单独的文件,单独的合同将构成单独的文件。

  1. 将整个站点放在一个文档中可以吗?

不,那太傻了,因为:

  • you would have to read and write the whole site (the document) on each update, and that is very inefficient;
  • 您不会从任何视图缓存中获益。

I know this is an old question, but I came across it trying to figure out the best approach to this exact same problem. Christopher Lenz wrote a nice blog post about 在 CouchDB 中建模“连接”的方法. One of my take-aways was: "The only way to allow non-conflicting addition of related data is by putting that related data into separate documents." So, for simplicity sake you'd want to lean towards "denormalization". But you'll hit a natural barrier due to conflicting writes in certain circumstances.

In your example of Posts and Comments, if a single post and all of its comments lived in one document, then two people trying to post a comment at the same time (i.e. against the same revision of the document) would cause a conflict. This would get even worse in your "whole site in a single document" scenario.

因此,我认为经验法则是“反规范化,直到它受到伤害”,但它将“受到伤害”的关键是,你有很高的可能性,多个编辑被张贴在同一个文档的修订版。

我认为 Jake 的回应指出了使用 CouchDB 最重要的方面之一,这可能有助于您做出范围界定的决定: 冲突。

在这种情况下,你有一个评论作为文章本身的数组属性,你只有一个“ post”数据库,其中包含大量的“ post”文档,正如 Jake 和其他人正确指出的那样,你可以想象一个非常流行的博客文章中的场景,两个用户同时提交对文章文档的编辑,导致文档冲突和版本冲突。

旁白: 正如这篇文章指出的那样,还要考虑到每次你请求/更新文档时,你必须获取/设置文档的全部内容,因此传递一个大量的文档,这些文档要么代表整个站点,要么代表一篇有很多评论的文章,这可能会成为你想要避免的问题。

如果文章与评论是分开建模的,两个人对一个故事提交评论,那么这些文档就会变成该数据库中的两个“评论”文档,没有冲突问题; 只需两个 PUT 操作就可以向“评论”数据库添加两个新的评论。

然后,为了编写视图返回文章的注释,需要传入 postID,然后发出所有引用该父文章 ID 的注释,并按某种逻辑顺序排序。也许您甚至可以传入类似[ postID,byUsername ]这样的内容作为‘ comments’视图的键,以指示父帖子以及您希望结果如何排序或类似的内容。

MongoDB 处理文档的方式略有不同,它允许在文档的子元素上构建索引,因此您可能会在 MongoDB 邮件列表中看到同样的问题,并且有人说“只要将注释作为父文档的属性”。

由于 Mongo 的写锁和单主特性,不会出现两个人添加注释的冲突修订问题,也不会因为子索引而影响内容的查询能力。

也就是说,如果你的子元素在 都不是 DB 中将是巨大的(比如说成千上万的评论) ,我相信这是两个阵营的建议,使这些单独的元素; 我肯定已经看到了 Mongo 的情况,因为有一些上限限制文档及其子元素可以有多大。

对于这个问题已经有了一些很好的答案,但是我想在处理 viatropos 所描述的原始情况的选项组合中添加一些更新的 CouchDB 特性。

拆分文档的关键点是可能存在冲突的地方(如前所述)。你永远不应该把大量的“纠缠”文档放在一个单独的文档中,因为你会得到一个完全不相关的更新的单独的修订路径(例如,添加注释给整个站点文档添加一个修订)。管理各种小型文档之间的关系或连接一开始可能会令人困惑,但 CouchDB 提供了几个选项,可以将不同的部分组合成单个响应。

The first big one is view collation. When you emit key/value pairs into the results of a map/reduce query, the keys are sorted based on UTF-8 collation ("a" comes before "b"). You can also output complex keys from your map/reduce as JSON arrays: ["a", "b", "c"]. Doing that would allow you to include a "tree" of sorts built out of array keys. Using your example above, we can output the post_id, then the type of thing we're referencing, then its ID (if needed). If we then output the id of the referenced document into an object in the value that's returned we can use the 'include_docs' query param to include those documents in the map/reduce output:

{"rows":[
{"key":["123412804910820", "post"], "value":null},
{"key":["123412804910820", "author", "Lance1231"], "value":{"_id":"Lance1231"}},
{"key":["123412804910820", "comment", "comment1"], "value":{"_id":"comment1"}},
{"key":["123412804910820", "comment", "comment2"], "value":{"_id":"comment2"}}
]}

请求同样的观点与’?Include _ docs = true’将添加一个“ doc”键,该键将使用“ value”对象中引用的“ _ id”,或者如果“ value”对象中没有引用的“ _ id”,它将使用发出该行的文档的“ _ id”(在本例中为“ post”文档)。请注意,这些结果将包括一个“ id”字段,该字段引用发出源文档。我把它放在外面是为了空间和可读性。

然后我们可以使用‘ start _ key’和‘ end _ key’参数将结果过滤到一篇文章的数据中:

?start_key=["123412804910820"]&end_key=["123412804910820", {}, {}]
或者甚至专门提取特定类型的列表:
? start _ key = [“123412804910820”,“ comments”] & end _ key = [“123412804910820”,“ comments”,{}]
这些查询参数组合是可能的,因为一个空对象(“ {}”)总是在排序的底部,而 null 或“”总是在顶部。

在这些情况下,CouchDB 的第二个有用的补充是 _ list 函数。这将允许您通过某种模板系统运行上述结果(如果您想要 HTML、 XML、 CSV 或其他任何返回) ,或者输出一个统一的 JSON 结构,如果您想要能够请求一个单一的请求整篇文章的内容(包括作者和评论数据) ,并作为一个单一的 JSON 文档返回,以满足您的客户端/UI 代码需要。这样做将允许您以这种方式请求文章的统一输出文档:

Start _ key = [“123412804910820”] & end _ key = [“123412804910820”,{} ,{}] & include _ docs = true
Your _ list 函数(在本例中命名为“ united”)将获取视图 map/reduce (在本例中命名为“ posts”)的结果,并通过 JavaScript 函数运行它们,该函数将以您需要的内容类型(JSON、 HTML 等)返回 HTTP 响应。

将这些事情结合起来,您可以将您的文档按照您认为对更新、冲突和复制有用和“安全”的任何级别进行分割,然后在需要时将它们放回到一起。

希望能帮上忙。