CouchDB文档build模原则
我有一个问题,我一直试图回答一段时间,但无法弄清楚:
您如何devise或分割CouchDB文档?
以博客文章为例。
半“关系”的方法是创build一些对象:
- 岗位
- 用户
- 评论
- 标签
- 片段
这很有道理。 但是我试图用couchdb(因为所有的原因,这是伟大的)来模拟相同的事情,这是非常困难的。
大多数博客文章给你一个简单的例子,如何做到这一点。 他们基本上以相同的方式分割它,但是说你可以为每个文档添加“任意”属性,这绝对是很好的。 所以你在CouchDB里有这样的东西:
- 发布(在文档中带有标签和摘录“伪”模型)
- 评论
- 用户
有些人甚至会说你可以把评论和用户放在那里,所以你可以这样做:
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" ... } } } } }
你可以很容易地发表意见,find你想要的东西。
那么我的问题是,你如何确定何时将文档分成较小的文档,或者何时在文档之间build立“关系”?
我认为它会更“面向对象”,并且更容易映射到价值对象,如果它被这样划分:
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 } }
…但是它开始看起来更像一个关系数据库。 而且,我经常inheritance一些看起来像“整个文档”的东西,所以用关系来build模是比较困难的。
我已经阅读了关于如何/何时使用关系数据库和文档数据库的很多事情,所以这不是主要问题。 我更想知道,在CouchDB中build模数据时,应用什么样的规则/原则是很好的。
另一个例子是XML文件/数据。 有些XML数据嵌套深度超过10层,我想用ActiveRecord,CouchRest或任何其他对象关系映射器来呈现JSON,并使用相同的客户端(Ajax on Rails或Flex)来可视化。 有时我会得到整个网站结构的巨大XML文件,就像下面这个一样,我需要把它映射到Value Objects,以便在我的Rails应用程序中使用,所以我不需要另外编写序列化/反序列化数据的方式:
<pages> <page> <subPages> <subPage> <images> <image> <url/> </image> </images> </subPage> </subPages> </page> </pages>
所以一般的CouchDB问题是:
- 你用什么规则/原则来划分文件(关系等)?
- 把整个网站放到一个文件里可以吗?
- 如果是这样,你如何处理任意深度级别的序列化/反序列化文档(如上面的大json例子,或xml例子)?
- 或者你不把它们变成VO,你只是决定“这些是太嵌套到对象关系图,所以我只能使用原始的XML / JSON方法访问它们”?
非常感谢您的帮助,如何将您的数据与CouchDB分离的问题一直难以说“这就是我应该从现在开始做的事情”。 我希望很快到达那里。
我研究了以下网站/项目。
- CouchDB中的分层数据
- CouchDB Wiki
- 沙发 – CouchDB应用程序
- CouchDB权威指南
- PeepCode CouchDB Screencast
- CouchRest
- CouchDB自述文件
…但他们还没有回答这个问题。
已经有一些很好的答案了,但是我想添加一些更新的CouchDBfunction来处理由viatropos描述的原始情况。
分割文件的关键是可能存在冲突的地方(如前所述)。 您不应该在单个文档中大量“纠结”文档,因为您将获得完全不相关更新的单个修订path(例如,添加了对整个站点文档的修订版本)。 pipe理各种较小文档之间的关系或连接起初可能会造成混淆,但是CouchDB提供了几种将不同的部分组合成单个响应的选项。
第一个大的是视图整理。 当您将键/值对发送到map / reduce查询的结果中时,这些键将根据UTF-8归类进行sorting(“a”在“b”之前)。 您还可以将map / reduce中的复杂键作为JSON数组输出: ["a", "b", "c"]
。 这样做可以让你包含一个由数组键构成的“树”。 使用上面的例子,我们可以输出post_id,然后输出我们引用的东西的types,然后输出它的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”参数将结果过滤为单个post的数据:
?start_key = [“123412804910820”]&end_key = [“123412804910820”,{},{}]
或者甚至专门提取某种types的列表:
?start_key = [“123412804910820”,“comment”]&end_key = [“123412804910820”,“comment”,{}]
这些查询参数组合是可能的,因为空对象(“ {}
”)总是在sorting规则的底部,而null或“”总是在最上面。
在这些情况下,来自CouchDB的第二个有用的补充是_list函数。 这将允许您通过某种模板系统(如果您需要HTML,XML,CSV或其他types)运行上述结果,或者如果您希望能够请求整个post的内容(包括作者和评论数据),并作为单个JSON文档返回,该文档与您的客户端/ UI代码所需的内容相匹配。 这样做可以让你以这种方式请求文章的统一输出文档:
/ db / _design / app / _list / posts / unified ?? start_key = [“123412804910820”]&end_key = [“123412804910820”,{},{}]&include_docs = true
你的_list函数(在本例中被命名为“unified”)将会把view map / reduce的结果(在这种情况下命名为posts)运行,并通过一个JavaScript函数运行它,需要(JSON,HTML等)。
结合这些东西,你可以在你认为有用的任何级别上分割你的文档,并且更新,冲突和复制是“安全的”,然后在需要的时候根据需要把它们放在一起。
希望有所帮助。
这本书说,如果我没有记错的话,直到“伤害”才能够正常化,同时要记住文件可能更新的频率。
- 你用什么规则/原则来划分文件(关系等)?
作为一个经验法则,我包括显示有关该项目的页面所需的所有数据。 换句话说,你可以在真实世界的一张纸上打印一切,然后交给别人。 例如,股票报价文件除了数字外,还会包括公司的名称,交易所,货币; 合同文件将包括交易对手的名称和地址,所有关于date和签字的信息。 但不同date的股票报价将形成单独的文件,单独的合同将形成单独的文件。
- 把整个网站放到一个文件里可以吗?
不,那将是愚蠢的,因为:
- 您将不得不在每次更新时读写整个网站(文档),这是非常低效的;
- 你不会从任何视图caching中受益。
我知道这是一个古老的问题,但我碰到它试图找出这个完全相同的问题的最佳方法。 Christopher Lenz写了一篇关于在CouchDB中build模“连接”的好方法 。 我的一个结论是:“允许不冲突地添加相关数据的唯一方法是将相关数据放入单独的文档中。” 所以,为了简单起见,你想倾向于“非规范化”。 但是由于在某些情况下写入冲突,你会遇到一个天然的障碍。
在你的post和评论的例子中,如果单个post及其所有评论都存在于一个文档中,那么两个人同时发表评论(即针对文档的同一修订版本)将导致冲突。 在“单个文档中的整个站点”情况下,这会变得更糟。
所以我认为经验法则是“非正规化,直到它受到伤害”,但是它会“伤害”的地方在于,对于文档的同一修订版发布多个编辑的可能性很高。
我认为Jake的回应指出了使用CouchDB最重要的方面之一,这可能有助于您做出范围确定:冲突。
如果您有作为post本身的数组属性的注释,并且您只是拥有一个包含大量“post”文档的“发布”数据库,就像杰克和其他人正确指出的,您可以想象一个场景一个非常受欢迎的博客post,两个用户同时向post文档提交编辑,导致该文档发生冲突和版本冲突。
ASIDE: 正如本文所指出的那样 ,也要考虑到每次你申请/更新文档时,你都必须完整地获取/设置文档,所以要传递一个大量的文档来代表整个网站或者一个文章的评论可能会成为你想避免的问题。
在将post与评论分开build模的情况下,如果两个人对一个故事提交评论,则这些简单地成为该DB中的两个“评论”文档,不存在冲突的问题; 只需两个PUT操作即可将两个新注释添加到“注释”数据库。
然后为了写回给你回帖的意见,你会传递postID,然后发出所有引用该父postID的评论,按照某种逻辑顺序进行sorting。 也许你甚至会传递类似于[postID,byUsername]的内容作为“评论”视图的关键字,以指示父post,以及如何将结果sorting或沿着这些行sorting。
MongoDB处理文档有点不同,允许在文档的子元素上build立索引,所以你可能会在MongoDB邮件列表中看到同样的问题,而有人说“只是将这些注释作为父邮件的属性”。
由于Mongo的写locking和单主控性质,两个人添加注释的冲突修改问题不会在那里出现,而且由于子内存的原因,内容的查询能力不会太差,索引。
这就是说,如果你的数据库中的子元素将是巨大的(比如成千上万的评论),我相信这两个阵营的build议是把这些单独的元素; 对于Mongo来说,我确实已经看到了这种情况,因为文档及其子元素的大小有一些上限。