我有一个问题,我一直试图回答一段时间,但无法弄清楚:
你如何设计或分割CouchDB文件?
以博客文章为例.
半"关系"方式是创建一些对象:
岗位
用户
评论
标签
片段
这很有道理.但我正在尝试使用couchdb(由于所有原因,这很好)来模拟同样的事情并且这是非常困难的.
大多数博客文章都为您提供了一个如何执行此操作的简单示例.它们基本上以相同的方式划分它,但是说你可以为每个文档添加"任意"属性,这绝对是好的.所以你在CouchDB中有这样的东西:
发布(文档中带有标签和片段"伪"模型)
评论
用户
有些人甚至会说你可以把评论和用户放在那里,所以你有这个:
post {
id: 123412804910820
title: "My Post"
body: "Lots of Content"
html: "Lots of Content
"
author: {
name: "Lance"
age: "23"
}
tags: ["sample", "post"]
comments {
comment {
id: 93930414809
body: "Interesting Post"
}
comment {
id: 19018301989
body: "I agree"
}
}
}
这看起来非常好,很容易理解.我也理解你如何编写只从你所有的Post文档中提取注释的视图,让它们进入Comment模型,与用户和标签相同.
但后来我想,"为什么不把我的整个网站放到一个文档中?":
site {
domain: "www.blog.com"
owner: "me"
pages {
page {
title: "Blog"
posts {
post {
id: 123412804910820
title: "My Post"
body: "Lots of Content"
html: "Lots of Content
"
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"
...
}
}
}
}
}
您可以轻松地创建视图以找到您想要的内容.
那么我的问题是,你如何确定何时将文件分成较小的文件,或何时在文件之间建立"关系"?
我认为它会更加"面向对象",并且更容易映射到Value Objects,如果它被划分为:
posts {
post {
id: 123412804910820
title: "My Post"
body: "Lots of Content"
html: "Lots of Content
"
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应用程序中使用,因此我不必编写另一种序列化/反序列化数据的方法:
所以一般的CouchDB问题是:
您使用什么规则/原则来划分文件(关系等)?
是否可以将整个网站放入一个文档?
如果是这样,您如何处理具有任意深度级别的文档的序列化/反序列化(如上面的大型json示例或xml示例)?
或者你不把它们变成VO,你只是决定"这些太嵌套到Object-Relational Map,所以我只是使用原始XML/JSON方法访问它们"?
非常感谢您的帮助,如何将数据与CouchDB分开的问题我很难说"这是我从现在开始应该如何做到的".我希望很快能到达那里.
我研究了以下网站/项目.
CouchDB中的分层数据
CouchDB Wiki
沙发 - CouchDB应用程序
CouchDB权威指南
PeepCode CouchDB截屏视频
CouchRest
CouchDB自述文件
......但他们仍然没有回答这个问题.
已经有了一些很好的答案,但是我想添加一些更新的CouchDB功能,以便结合使用viatropos描述的原始情况.
拆分文档的关键点是可能存在冲突(如前所述).您永远不应该将大量"纠结"的文档放在一个文档中,因为您将获得完全不相关的更新的单个修订路径(例如,添加注释添加整个站点文档的修订).管理各种较小文档之间的关系或联系可能会让人感到困惑,但CouchDB提供了几种将不同部分组合成单个响应的选项.
第一个重要的是视图整理.当您将键/值对发布到map/reduce查询的结果中时,键将根据UTF-8排序规则进行排序("a"出现在"b"之前).您还可以从map/reduce中输出复杂的键作为JSON数组:["a", "b", "c"]
.这样做可以让你包含一个由数组键构建的"树".使用上面的示例,我们可以输出post_id,然后输出我们引用的东西的类型,然后输出它的ID(如果需要).如果我们然后将引用文档的id输出到返回值中的对象中,我们可以使用'include_docs'查询参数将这些文档包含在map/reduce输出中:
{"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文档返回,与您的客户端/ UI代码所需的文档相匹配.这样做可以让您以这种方式请求帖子的统一输出文档:
/db/_design/app/_list/posts/unified??start_key=["123412804910820"]&end_key=["123412804910820", {}, {}]&include_docs=true你的_list函数(在本例中命名为"unified")将获取视图map/reduce的结果(在本例中命名为"posts")并通过JavaScript函数运行它们,该函数将在内容类型中发回HTTP响应需要(JSON,HTML等).
结合这些内容,您可以将文档拆分为您认为有用的级别,并且可以"安全"地进行更新,冲突和复制,然后在需要时将它们重新组合在一起.
希望有所帮助.
如果我没记错的话,这本书会说,在"伤害"之前进行反规范化,同时记住文档可能更新的频率.
您使用什么规则/原则来划分文件(关系等)?
根据经验,我包括显示有关项目的页面所需的所有数据.换句话说,你将在真实世界的纸上打印的所有东西都可以交给某人.例如股票报价单文件除了数字外还包括公司名称,交易所,货币; 合同文件将包括交易对手的姓名和地址,所有关于日期和签字的信息.但不同日期的股票报价将形成单独的文件,单独的合同将形成单独的文件.
是否可以将整个网站放入一个文档?
不,那会很愚蠢,因为:
您必须在每次更新时读取和写入整个站点(文档),这是非常低效的;
你不会受益于任何视图缓存.
我知道这是一个老问题,但我遇到了它试图找出解决这个问题的最佳方法.Christopher Lenz撰写了一篇关于在CouchDB中建模"连接"的方法的精彩博文.我的一个收获点是:"允许非冲突添加相关数据的唯一方法是将相关数据放入单独的文档中." 因此,为了简单起见,您希望倾向于"非规范化".但是在某些情况下,由于写入冲突,你会遇到天然障碍.
在你的帖子和评论的例子中,如果一个帖子及其所有评论都存在于一个文档中,那么两个人试图同时发布评论(即针对文档的同一修订版)将导致冲突.这将在您的"单个文档中的整个站点"场景中变得更糟.
所以我认为经验法则是"非规范化,直到它受到伤害",但它会"伤害"的地方是你很有可能针对同一版本的文档发布多次编辑.
我认为Jake的回应指出了与CouchDB合作的最重要方面之一,它可以帮助您做出范围界定决策:冲突.
如果您将评论作为帖子本身的数组属性,并且您只有一个"帖子"数据库,其中包含一堆巨大的"帖子"文档,正如杰克和其他人正确指出的那样,您可以想象一个场景一个非常受欢迎的博客文章,其中两个用户同时向帖子文档提交编辑,导致该文档发生冲突和版本冲突.
ASIDE:正如本文所指出的,还要考虑每次请求/更新该文档时,您必须完整地获取/设置文档,因此传递代表整个站点或大量文档的大量文档对它的评论可能成为你想要避免的问题.
如果帖子与评论分开建模,两个人就故事提交评论,那么这些文件只会在该DB中成为两个"评论"文档,没有冲突问题; 只需要两个PUT操作就可以在"comment"db中添加两条新注释.
然后,要编写返回帖子评论的视图,您将传递postID,然后发出引用该父帖ID的所有注释,并按某种逻辑顺序排序.也许你甚至传递像[postID,byUsername]这样的东西作为'评论'视图的关键,以指示父帖子以及你希望结果排序的方式或者沿着那些方向排序的东西.
MongoDB处理文档的方式略有不同,允许在文档的子元素上构建索引,因此您可能会在MongoDB邮件列表上看到相同的问题,并且有人说"只需将注释作为父帖子的属性".
由于Mongo的写锁定和单主属性质,两个人添加注释的冲突修订问题不会出现在那里,如上所述,内容的查询能力不会因为子索引.
话虽这么说,如果你们任何一个数据库中的子元素都很庞大(比如成千上万的评论),我相信两个阵营都建议制作这些单独的元素; 我肯定已经看到了Mongo的情况,因为文档及其子元素的大小有一些上限.