2009-10-07 52 views
108

我有一个问题,我一直在努力,现在回答了一段时间,但无法弄清楚:原则建模的CouchDB文档

如何设计,或瓜分,CouchDB的文件呢?

以博客文章为例。

半 “关系” 的方式做到这一点是创建几个对象:

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

这非常有道理。但我正在尝试使用couchdb(出于所有原因,它很棒)来模拟相同的事情,这非常困难。

大部分的博客文章给你一个简单的例子,如何做到这一点。他们基本上以相同的方式划分它,但是说你可以为每个文档添加'任意'属性,这绝对是很好的。所以你必须在CouchDB中是这样的:

  • 后(与标签和片断“伪”车型在DOC)
  • 评论
  • 用户

有些人甚至会说你可以扔在那里的评论和用户,所以你有这样的:


post { id: 123412804910820 title: "My Post" body: "Lots of Content" html: "<p>Lots of Content</p>" author: { name: "Lance" age: "23" } tags: ["sample", "post"] comments { comment { id: 93930414809 body: "Interesting Post" } comment { id: 19018301989 body: "I agree" } } } 

这看起来非常好,很容易理解。我也了解如何编写视图,从所有Post文档中提取评论,以便将它们导入评论模型中,与用户和标签相同。

但后来我想,“为什么不干脆把我的整个网站到单个文件?”:


site { domain: "www.blog.com" owner: "me" pages { page { title: "Blog" posts { post { id: 123412804910820 title: "My Post" body: "Lots of Content" html: "<p>Lots of Content</p>" author: { name: "Lance" age: "23" } tags: ["sample", "post"] comments { comment { id: 93930414809 body: "Interesting Post" } comment { id: 19018301989 body: "I agree" } } } post { id: 18091890192984 title: "Second Post" ... } } } } } 

你可以很容易地使视图来找到你想和什么。

那么我的问题是,您如何确定何时将文档分成较小的文档,或何时在文档之间建立“关系”?

我认为这将是更加“面向对象”,也更容易映射到值对象,如果它被分成像这样:


posts { post { id: 123412804910820 title: "My Post" body: "Lots of Content" html: "<p>Lots of Content</p>" author_id: "Lance1231" tags: ["sample", "post"] } } authors { author { id: "Lance1231" name: "Lance" age: "23" } } comments { comment { id: "comment1" body: "Interesting Post" post_id: 123412804910820 } comment { id: "comment2" body: "I agree" post_id: 123412804910820 } } 

...但随后开始看起来更像一个关系数据库。而且我经常会继承一些看起来像“整个网站在一个文件中”的东西,所以用关系模型化它更加困难。

我读过很多有关如何/何时使用关系数据库与文档数据库的内容,因此这不是主要问题。我更想知道的是,在CouchDB中建模数据时应用的最佳规则/原则是什么。

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


<pages> <page> <subPages> <subPage> <images> <image> <url/> </image> </images> </subPage> </subPages> </page> </pages> 

所以一般CouchDB的问题是:

  1. 什么规则/原则,你用瓜分你的文件(关系等)?
  2. 可以将整个网站放入一个文档吗?
  3. 如果是这样,你如何处理具有任意深度级别的序列化/反序列化文档(如上面的大json示例或xml示例)?
  4. 或者您是否将它们转化为VO,您是否决定“这些内容过于嵌套到对象关系映射,因此我只能使用原始XML/JSON方法访问它们”?

非常感谢您的帮助,关于如何使用CouchDB分隔数据的问题一直很难说“这就是我应该从现在开始做的事情”。我希望很快到达那里。

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

  1. Hierarchical Data in CouchDB
  2. CouchDB Wiki
  3. Sofa - CouchDB App
  4. CouchDB The Definitive Guide
  5. PeepCode CouchDB Screencast
  6. CouchRest
  7. CouchDB README

...但他们还没有回答这个问题。

+2

哇你在这里写了一篇文章... :-) – Eero 2009-10-07 10:46:35

+7

嘿,这是一个很好的问题 – elmarco 2009-10-07 19:43:29

回答

15

book说,如果我记得正确,直到“它伤害”,同时牢记您的文档可能更新的频率,正常化。

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

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

  1. 可以将整个网站放到一个文档中吗?

不,那将是愚蠢的,因为:

  • 你就必须读取和写入每次更新整个网站(文档),这是非常低效的;
  • 你不会从任何视图缓存中受益。
+3

感谢您与我进行了一点。我的想法是“包含所有需要的数据来显示有关该项目的页面”,但这仍然很难实现。 “页面”可能是一个网页的评论,用户的网页,帖子的网页,或评论和帖子的页面等你将如何划分起来以后,主要是?您也可以将您的合同显示给用户。我得到'类似'的文件,这有助于将它们分开。 – 2009-10-07 11:55:44

15

我知道这是一个古老的问题,但我碰到它试图找出这个完全相同的问题的最佳方法。克里斯托弗Lenz写了一篇关于methods of modeling "joins" in CouchDB的好博客文章。我的一个结论是:“允许不冲突地添加相关数据的唯一方法是将相关数据放入单独的文档中。”所以,为了简单起见,你需要倾向于“非规范化”。但是由于在某些情况下写入冲突,你会遇到天生的障碍。

在你的帖子和评论的例子中,如果单个帖子及其所有评论都存在于一个文档中,那么两个人试图同时发表评论(即针对文档的同一修订版)会导致冲突。在“单个文档中的整个站点”情况下,这会变得更糟。

所以我认为经验法则是“非正规化直到它受到伤害”,但是它会“伤害”的地方是很可能发生针对文档的同一版本的多次编辑。

+0

有趣的回应。考虑到这一点,人们应该怀疑是否有任何合理的高流量网站甚至可以在一个文档中为单个博客文章提供所有评论。如果我阅读这个权利,这意味着每当有人快速连续添加评论时,您可能需要解决冲突。当然,我不知道他们必须有多快才能触发这一点。 – pc1oad1etter 2011-02-18 15:15:05

+1

如果评论是沙发文档的一部分,同时评论帖可能会有冲突,因为您的版本范围是包含所有评论的“帖子”。在每个对象都是文档集合的情况下,这些会简直成了两个新的“评论”的链接返回到岗位和没有碰撞的关注文档。我还要指出的是,建立在“面向对象”的文档设计的观点是直截了当 - 你在后,例如关键传球,然后发出所有的意见,通过某种方法来分类,该职位。 – 2011-09-17 01:57:22

5

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

如果您有作为帖子本身的数组属性的注释,并且您只是有一个'post'数据库和一大堆'post'文档,就像Jake和其他人正确指出的那样设想一个非常受欢迎的博客文章,其中两个用户同时向帖子文档提交编辑,导致该文档发生冲突和版本冲突。

ASIDE:this article points out一样,还要考虑到每次您要求/更新该文档时,您必须完整地获取/设置文档,以便将大量文档传递给代表整个网站或帖子的文档很多评论都可能成为你想避免的问题。

在将帖子与评论分开建模并且两个人对故事提交评论的情况下,这些简单地成为该DB中的两个“评论”文档,没有冲突的问题;只需两次PUT操作即可将两个新评论添加到“评论”数据库。

然后,要写回给您回帖的评论的视图,您需要传递postID,然后发出引用该父帖子ID的所有评论,并按照某种逻辑顺序进行排序。也许你甚至会传递类似[postID,byUsername]的内容作为'comments'视图的关键字,以指示父帖子以及您希望结果排序的方式或沿着这些行的内容。

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

由于Mongo的写锁定和单主控性质,添加注释的两个人的冲突修订问题不会在那里出现,并且内容的查询能力不会太差因为分指数。这就是说,如果你的子元素或者数据库将会是巨大的(比如说成千上万的评论),我相信这是两个阵营建立这些独立元素的建议;对于Mongo来说,我确实已经看到了这种情况,因为对于文档及其子元素的大小有一些上限。

+0

非常有帮助。谢谢 – 2013-10-17 04:00:56

23

已经有一些很好的答案了,但是我想添加一些更新的CouchDB功能来处理由viatropos描述的原始情况。

分解文档的关键点是可能存在冲突的地方(如前所述)。您不应该在单个文档中将大量“纠结”文档集中在一起,因为您将获得完全不相关更新的单个修订路径(例如,添加评论为整个站点文档添加修订版)。管理各种较小文档之间的关系或连接起初可能会让人困惑,但CouchDB提供了多种选项,可将不同部分组合成单一响应。

第一大问题是视图整理。当您将键/值对发送到map/reduce查询的结果中时,这些键将根据UTF-8归类进行排序(“a”位于“b”之前)。您还可以将地图/缩减中的复杂键作为JSON数组输出:["a", "b", "c"]。这样做可以让你包含一个由数组键构成的“树”。使用上面的示例,我们可以输出post_id,然后输出我们引用的事物类型,然后输出其ID(如果需要)。如果我们再输出的引用文件的ID为在所返回,我们可以使用“include_docs”查询参数包括在地图上的那些文件值的对象/减少输出:

{"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'从该行发出的文档(在本例中是'post'文档)。请注意,这些结果将包括一个引用源文件的'id'字段。我把它留给空间和可读性。

然后,我们可以使用“start_key”和“end_key”参数来筛选结果到一个单一的文章的数据:

?start_key=["123412804910820"]&end_key=["123412804910820", {}, {}]
甚至专门提取列表中某种类型:
?start_key=["123412804910820", "comment"]&end_key=["123412804910820", "comment", {}]
这些查询参数的组合是可能的,因为空对象(“ {}”)始终位于排序规则的底部,而null或“”总是位于最上面。

在这些情况下,来自CouchDB的第二个有用的补充是_list函数。这将允许您通过某种模板系统(如果您需要HTML,XML,CSV或其他类型)运行上述结果,或者如果您希望能够请求整个帖子的内容(包括作者和评论数据),并作为单个JSON文档返回,该文档与客户端/用户界面代码所需的内容相匹配。这样做将允许您以这种方式请求帖子的统一输出文档:

/db/_design/app/_list/posts/unified??start_key=["123412804910820"]&end_key=["123412804910820", {}, {}]&include_docs=true
您的_list函数(在本例中名为“统一”)将采用视图映射/缩减(在本例中为“posts”)的结果并运行它们通过一个JavaScript函数发送你需要的内容类型的HTTP响应(JSON,HTML等)。

结合这些东西,你可以在任何级别您有帮助的“安全”的更新,冲突和分裂复制你的文件,然后把他们重新走到一起根据需要会要求当他们。

希望有所帮助。

+2

不知道这是否有助于兰斯,但我知道一件事;它绝对帮助我很多!这太棒了! – Mark 2012-01-27 17:10:48