Принципы моделирования документов 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"
                    ...
                }
            }
        }
    }
}

Вы можете легко сделать взгляды, чтобы найти то, что вы хотели с этим.

Тогда у меня возникает вопрос: как вы определяете, когда разделить документ на более мелкие документы или когда сделать "ОТНОШЕНИЯ" между документами?

Я думаю, что было бы гораздо более "объектно-ориентированным", и было бы проще сопоставить с объектами значения, если бы они были разделены следующим образом:


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 или любого другого объектно-реляционного сопоставителя. Иногда я получаю огромные XML-файлы, которые представляют собой всю структуру сайта, как показано ниже, и мне нужно сопоставить их с объектами Value для использования в моем приложении 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. Иерархические данные в CouchDB
  2. CouchDB Wiki
  3. Диван - CouchDB App
  4. CouchDB Полное руководство
  5. PeepCode CouchDB Screencast
  6. CouchRest
  7. CouchDB README

... но они до сих пор не ответили на этот вопрос.

4 ответа

Уже было несколько хороших ответов на этот вопрос, но я хотел добавить несколько более свежих функций CouchDB к смеси опций для работы с исходной ситуацией, описанной viatropos.

Ключевым моментом, по которому можно разделить документы, могут быть конфликты (как упоминалось ранее). Вы никогда не должны хранить массово "запутанные" документы в одном документе, так как вы получите единый путь редакции для совершенно не связанных обновлений (например, добавление комментариев, добавляющее редакцию ко всему документу сайта). Поначалу управление отношениями или связями между различными более мелкими документами может сбивать с толку, но CouchDB предоставляет несколько вариантов объединения разрозненных фрагментов в отдельные ответы.

Первый большой - это сопоставление представлений. Когда вы отправляете пары ключ / значение в результаты запроса map/ Reduce, ключи сортируются на основе параметров сортировки UTF-8 ("a" предшествует "b"). Вы также можете вывести сложные ключи из вашей карты / уменьшить как массивы JSON: ["a", "b", "c"], Это позволит вам включить "дерево", построенное из ключей массива. Используя приведенный выше пример, мы можем вывести post_id, затем тип объекта, на который мы ссылаемся, затем его идентификатор (если необходимо). Если затем мы выводим идентификатор ссылочного документа в объект в возвращаемом значении, мы можем использовать параметр запроса include_docs, чтобы включить эти документы в вывод map/ проводить:

{"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" документа, из которого была выдана строка (в данном случае "пост"). Обратите внимание, что эти результаты будут включать поле 'id', ссылающееся на исходный документ, из которого был сделан emit. Я оставил это для пространства и читабельности.

Затем мы можем использовать параметры 'start_key' и 'end_key', чтобы отфильтровать результаты до данных одного сообщения:

? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}] 
Или даже специально распаковать список для определенного типа:
? start_key = ["123412804910820", "comment"] & end_key = ["123412804910820", "comment", {}] 
Эти комбинации параметров запроса возможны, потому что пустой объект (" {} ") всегда находится в нижней части параметров сортировки, а нулевое значение или" "всегда находится в верхней части.

Вторым полезным дополнением от CouchDB в этих ситуациях является функция _list. Это позволит вам запустить приведенные выше результаты через какую-либо систему шаблонов (если вам нужен HTML, XML, CSV или что-то еще) или вывести унифицированную структуру JSON, если вы хотите иметь возможность запрашивать весь контент поста (включая данные об авторе и комментариях) с одним запросом и возвращаются в виде единого документа JSON, который соответствует потребностям вашего клиентского / пользовательского интерфейса. Это позволит вам запросить единый выходной документ поста следующим образом:

 / db / _design / app / _list / posts / unified?? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}] & include_docs = true 
Ваша функция _list (в данном случае с именем "унифицированная") будет принимать результаты отображения / уменьшения (в данном случае с именем "posts") и запускать их через функцию JavaScript, которая будет отправлять ответ HTTP в типе контента, который вы используете. нужно (JSON, HTML и т. д.).

Комбинируя эти вещи, вы можете разделить ваши документы на любом уровне, который вы считаете полезным и "безопасным" для обновлений, конфликтов и репликации, а затем при необходимости собрать их вместе, когда они потребуются.

Надеюсь, это поможет.

Я знаю, что это старый вопрос, но я наткнулся на него, пытаясь найти лучший подход к этой же проблеме. Кристофер Ленц написал хороший пост в блоге о методах моделирования "соединений" в CouchDB. Один из моих выводов был: "Единственный способ разрешить бесконфликтное добавление связанных данных - это поместить эти связанные данные в отдельные документы". Итак, для простоты вы хотите склониться к "денормализации". Но вы столкнетесь с естественным барьером из-за противоречивых записей при определенных обстоятельствах.

В вашем примере "Посты и комментарии", если одна публикация и все ее комментарии находятся в одном документе, тогда два человека, пытающиеся опубликовать комментарий одновременно (то есть против одной и той же редакции документа), вызовут конфликт. Это может стать еще хуже в сценарии "весь сайт в одном документе".

Поэтому я думаю, что эмпирическое правило будет "денормализовать до тех пор, пока оно не повредит", но точка, в которой оно "повредит", - это высокая вероятность того, что несколько изменений будут опубликованы в одной и той же редакции документа.

Книга говорит, если я правильно помню, денормализовать до тех пор, пока "это не повредит", учитывая при этом частоту обновления ваших документов.

  1. Какие правила / принципы вы используете, чтобы разделить ваши документы (отношения и т. Д.)?

Как правило, я включаю все данные, необходимые для отображения страницы, касающейся рассматриваемого элемента. Другими словами, все, что вы печатаете на реальном листе бумаги, который вы кому-то передаете. Например, документ котировки акций должен включать в себя название компании, биржу, валюту, в дополнение к номерам; договорный документ должен включать имена и адреса контрагентов, всю информацию о датах и ​​подписавших сторонах. Но котировки акций от разных дат будут формировать отдельные документы, отдельные контракты будут формировать отдельные документы.

  1. Можно ли поместить весь сайт в один документ?

Нет, это было бы глупо, потому что:

  • вам придется читать и писать весь сайт (документ) при каждом обновлении, и это очень неэффективно;
  • Вы не выиграете от кеширования любого представления.

Я думаю, что ответ Джейка является одним из самых важных аспектов работы с CouchDB, который может помочь вам принять решение об определении объема: конфликты.

В случае, когда у вас есть комментарии в качестве свойства массива самой публикации, и у вас просто есть "пост" БД с кучей огромных "пост" документов в нем, как правильно отметили Джейк и другие, вы можете представить сценарий действительно популярное сообщение в блоге, в котором два пользователя одновременно вносят изменения в документ публикации, что приводит к коллизии и конфликту версий этого документа.

ВНИМАНИЕ: Как указывается в этой статье, также учитывайте , что каждый раз, когда вы запрашиваете / обновляете этот документ, вы должны получить / установить документ целиком, поэтому передавая массивные документы, которые представляют весь сайт или пост с большим количеством комментариев на это может стать проблемой, которую вы хотели бы избежать.

В случае, когда посты смоделированы отдельно от комментариев, и два человека представляют комментарий к истории, они просто становятся двумя документами "комментариев" в этой БД без конфликта; всего две операции PUT, чтобы добавить два новых комментария в базу данных "comment".

Затем, чтобы написать представления, которые возвращают вам комментарии к сообщению, вы должны передать postID и затем отправить все комментарии, которые ссылаются на этот родительский идентификатор сообщения, отсортированные в некотором логическом порядке. Возможно, вы даже передаете что-то вроде [post ID, byUsername] в качестве ключа к представлению "комментарии", чтобы указать родительский пост и то, как вы хотите, чтобы результаты были отсортированы или что-то в этом духе.

MongoDB обрабатывает документы немного по-другому, позволяя строить индексы на подэлементах документа, поэтому вы можете увидеть тот же вопрос в списке рассылки MongoDB, и кто-то скажет "просто сделайте комментарии свойством родительской записи".

Из-за блокирования записи и единственного основного характера Mongo, конфликтующая проблема пересмотра двух людей, добавляющих комментарии, не возникнет там, и возможность запроса контента, как упоминалось, не слишком плоха из-за индексов.

При этом, если ваши подэлементы в обеих БД будут огромными (скажем, 10 тысяч комментариев), я считаю, что оба лагеря рекомендуют создавать эти отдельные элементы; Я, конечно, видел, что в случае с Mongo существуют верхние границы того, насколько большим может быть документ и его подэлементы.

Другие вопросы по тегам