Принципы моделирования документов CouchDB
У меня есть вопрос, на который я пытаюсь ответить некоторое время, но не могу понять:
Как вы разрабатываете или делят документы CouchDB?
Возьмите сообщение в блоге, например.
Полуреактивный способ сделать это - создать несколько объектов:
- Сообщение
- Пользователь
- Комментарий
- Тег
- Отрывок
Это имеет большой смысл. Но я пытаюсь использовать 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"
...
}
}
}
}
}
Вы можете легко сделать представление, чтобы найти то, что вам нужно.
Тогда у меня есть вопрос: как вы определяете, когда разделить документ на более мелкие документы или когда делать "ОТНОШЕНИЯ" между документами?
Я думаю, что это было бы намного больше "Object Oriented" и проще было бы сопоставлять объекты Value, если бы они были разделены так:
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 уровней, и я хотел бы визуализировать, что с использованием одного и того же клиента (Ajax on Rails, например, или Flex), который я хотел бы сделать JSON из ActiveRecord, CouchRest или любого другого Relation Mapper объекта. Иногда я получаю огромные XML файлы, которые представляют собой всю структуру сайта, как и приведенную ниже, и мне нужно будет сопоставить ее с Value Objects для использования в моем приложении Rails, поэтому мне не нужно писать другой способ сериализации/десериализации данных
<pages>
<page>
<subPages>
<subPage>
<images>
<image>
<url/>
</image>
</images>
</subPage>
</subPages>
</page>
</pages>
Итак, общие вопросы CouchDB:
- Какие правила/принципы вы используете для разделения ваших документов (отношений и т.д.)?
- Можно ли разместить весь сайт в одном документе?
- Если да, то как вы обрабатываете сериализацию/десериализацию документов с произвольными уровнями глубины (например, пример большого json выше или пример xml)?
- Или вы не превращаете их в VO, вы просто решаете, что "эти слишком вложены в объектно-реляционную карту, поэтому я просто получаю доступ к ним с использованием необработанных методов XML/JSON"?
Большое спасибо за вашу помощь, вопрос о том, как разделить ваши данные с CouchDB, мне трудно сказать "вот как я должен это делать с этого момента". Я надеюсь скоро приехать.
Я изучил следующие сайты/проекты.
... но они все еще не ответили на этот вопрос.
Ответы
Ответ 1
На это уже были отличные ответы, но я хотел добавить некоторые более свежие функции CouchDB к сочетанию опций для работы с исходной ситуацией, описанной viatropos.
Ключевым моментом, в котором нужно разделить документы, являются конфликты (как упоминалось ранее). Вы никогда не должны держать массивно "запутанные" документы вместе в одном документе, так как вы получите единственный путь к версии для полностью несвязанных обновлений (добавление комментариев, добавляющее, например, ревизию ко всему документу сайта). Управление отношениями или связями между различными, меньшими документами может сбивать с толку сначала, но CouchDB предоставляет несколько вариантов объединения разрозненных фрагментов в отдельные ответы.
Первый большой - сортировка вида. Когда вы испускаете пары ключ/значение в результаты запроса map/reduce, ключи сортируются на основе сопоставления UTF-8 ( "a" предшествует "b" ). Вы также можете вывести сложные ключи из вашей карты/уменьшить как массивы JSON: ["a", "b", "c"]
. Выполнение этого позволит вам включить "дерево" типов, построенных из ключей массива. Используя приведенный выше пример, мы можем вывести post_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", который либо будет использовать ссылку "_id", на которую ссылается объект "значение", либо если это не присутствует в объекте "значение", он будет использовать "_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, если вы хотите иметь возможность запрашивать весь контент (включая автор и комментарии) с единственным запросом и возвращаются в виде единого документа JSON, который соответствует вашему коду клиентской стороны/пользовательскому интерфейсу. Это позволит вам запросить унифицированный выходной документ следующим образом:
/db/_design/app/_list/posts/unified??start_key=["123412804910820"]&end_key=["123412804910820", {}, {}]&include_docs=true
Функция _list (в данном случае с именем "унифицированная" ) будет принимать результаты карты/сокращения вида (в этом случае называются "сообщения" ) и запустите их через функцию JavaScript, которая отправит ответ HTTP в нужном типе контента (JSON, HTML и т.д.).
Объединяя эти вещи, вы можете разделить свои документы на любом уровне, который вы найдете полезным и "безопасным" для обновлений, конфликтов и репликации, а затем поместите их вместе по мере необходимости, когда они будут запрошены.
Надеюсь, что это поможет.
Ответ 2
book говорит, если я правильно помню, денормализовать до тех пор, пока "это не повредит", в то время как имея в виду частоту обновления ваших документов.
- Какие правила/принципы вы используете для разделения ваших документов (отношений и т.д.)?
Как правило, я включаю все данные, необходимые для отображения страницы относительно рассматриваемого элемента. Другими словами, все, что вы напечатали на бумаге реального мира, которую вы передадите кому-то. Например. документ котировки акций будет содержать название компании, биржу, валюту, в дополнение к цифрам; контрактный документ будет включать имена и адреса контрагентов, всю информацию о датах и подписавших стороны. Но котировки акций с разных дат будут составлять отдельные документы, отдельные контракты будут составлять отдельные документы.
- Можно ли разместить весь сайт в одном документе?
Нет, это было бы глупо, потому что:
- вам нужно будет прочитать и написать весь сайт (документ) для каждого обновления, и это очень неэффективно;
- вам не пригодится какое-либо кэширование просмотров.
Ответ 3
Я знаю, что это старый вопрос, но я столкнулся с этим, пытаясь найти лучший подход к этой же самой проблеме. Кристофер Ленц написал хороший блог о методах моделирования "присоединяется" в CouchDB. Один из моих приемов был: "Единственный способ разрешить непротиворечивое добавление связанных данных - это связать эти связанные данные с отдельными документами". Итак, для простоты вы хотите склониться к "денормализации". Но вы столкнетесь с естественным барьером из-за противоречивых писем в определенных обстоятельствах.
В вашем примере сообщений и комментариев, если одна запись и все ее комментарии проживали в одном документе, тогда два человека, пытающихся опубликовать комментарий в одно и то же время (т.е. против того же пересмотра документа), конфликт. Это будет еще хуже в сценарии "всего сайта в одном документе".
Итак, я думаю, что эмпирическое правило будет "денормализоваться до тех пор, пока оно не повредит", но точка, где это будет "больно", - это то, где у вас есть высокая вероятность того, что несколько редактирований были отправлены против одной и той же ревизии документа.
Ответ 4
Я думаю, что ответ Джейка называет один из самых важных аспектов работы с CouchDB, который может помочь вам принять решение о скоринге: конфликты.
В случае, когда у вас есть комментарии как свойство массива самой записи, и у вас просто есть "пост" БД с кучей огромных "почтовых" документов в нем, как Джейк и другие правильно указали, что вы могли себе представить сценарий в действительно популярном сообщении в блоге, в котором два пользователя одновременно отправляют редактирование в почтовый документ, что приводит к конфликту конфликтов и версий для этого документа.
ASIDE: Как эта статья указывает на, также учитывайте, что каждый раз, когда вы запрашиваете/обновляете этот документ, вы должны получить/установить документ в целом, поэтому передача массивных документов, представляющих весь сайт или сообщение с большим количеством комментариев по нему, может стать проблемой, которую вы бы хотели избежать.
В случае, когда должности моделируются отдельно от комментариев, а два человека представляют комментарий к истории, они просто становятся двумя "комментариями" в этой БД, без конфликтов; всего две операции PUT, чтобы добавить два новых комментария в "комментарий" db.
Затем, чтобы написать представления, которые вернут вам комментарии к сообщению, вы должны передать в postID, а затем испустить все комментарии, ссылающиеся на этот родительский идентификатор, отсортированные в некотором логическом порядке. Возможно, вы даже передаете что-то вроде [postID, byUsername] в качестве ключа к представлению "комментарии", чтобы указать родительский пост и как вы хотите, чтобы результаты были отсортированы или что-то в этих строках.
MongoDB обрабатывает документы немного по-другому, позволяя создавать индексы на подэлементах документа, поэтому вы можете увидеть тот же вопрос в списке рассылки MongoDB, а кто-то говорит "просто сделайте комментарии свойством родительского сообщения",.
Из-за блокировки записи и единственного мастера Mongo конфликтная проблема пересмотра двух пользователей, добавляющих комментарии, не будет spring там, и возможность запроса содержимого, как уже упоминалось, также не выполняется плохо из-за субиндексов.
Говоря, что если ваши подэлементы в одной из БД будут огромными (скажем, 10 тысяч тысяч комментариев), я считаю, что рекомендации обоих лагерей составляют эти отдельные элементы; Я, конечно, видел, что это имеет место с Монго, поскольку существуют некоторые ограничения сверху на то, насколько большой документ и его подэлементы могут быть.
Ответ 5
Чтобы добавить информацию в этот пост, я изучаю эту проблему в этой статье. Рекомендации по моделированию данных для баз данных документов NoSQL JSON и методики моделирования данных NOSQL. Эти два поста касаются не только CouchDB, но и общих руководящих указаний по моделированию баз данных NoSQL, например, в первой статье рассказывается о денормализации, родительско-дочерних отношениях - встроенном объекте, дизайне ключей составной строки, общих данных, данных событий и атрибутах объектов - Значение, дерево, список смежности, данные графика и отображение наследования, включительно, сравнивают эти методы в NoSQL и SQL. Благодарю.