
В этой статье мы рассмотрим, как смоделировать структуру, содержащую списки рассылок и данные о людях, которые входят в эти списки.
Ниже представлены требования:
- Человек может иметь один или более e-mail адресов;
- Человек может состоять в любом количестве списков рассылок;
- Человек может выбрать любое название для любого списка рассылки, в котором состоит.
Стратегия «без встраиваний»
Давайте посмотрим, как будет выглядеть наша модель данных, если никакие данные никуда не встраивать.
У нас есть подписчики People с именами и паролями:
{
_id: PERSON_ID,
name: "Василий Пупкин",
pw: "Хешированный пароль"
}
У нас есть коллекция адресов Adresses, в которой каждый документ содержит e-mail адрес и привязку к конкретному подписчику:
{
_id: ADDRESS_ID,
person: PERSON_ID,
address: "vpupkin@gmail.com"
}
У нас есть группы Groups, каждая из которых содержит только идентификатор группы (она, конечно же, может содержать и другие данные, но мы специально опустим этот момент, дабы сконцентрироваться на подписках)
{
_id: GROUP_ID
}
И наконец, мы имеем коллекцию подписок Memberships. Каждая Подписка объединяет людей в Группы, кроме этого, содержит название, которое человек выбрал для данной Группы, и ссылку на e-mail адрес, который он хочет использовать для получения рассылки в данной Группе:
{
_id: MEMBERSHIP_ID,
person: PERSON_ID,
group: GROUP_ID,
address: ADDRESS_ID,
group_name: "Семья"
}
Такая модель данных понятна, легка в разработке и проста в обслуживании. Мы создали модель, которую удобно использовать в реляционной базе данных. При этом мы совсем не приняли во внимание документо-ориентированный подход MongoDB. Давайте рассмотрим, что мы будем делать, чтобы получить, например, e-mail адреса всех членов одной Группы, имея один известный e-mail адрес и название этой Группы:
- В коллекции Addresses по известному e-mail найдем
PERSON_ID
; - В коллекции Memberships по полученному
PERSON_ID
и известному названию Группы найдемGROUP_ID
; - Опять же в коллекции Memberships по полученному
GROUP_ID
найдем список Подписок данной Группы; - И наконец из коллекции Addresses по
ADDRESS_ID
, пройдя по каждой Подписке из полученного списка, получим список e-mail адресов.
Слегка сложновато, не правда ли?
Стратегия «все встроено»
Теперь рассмотрим случай, когда все данные встроены в один документ. Для этого мы возьмем все Подписки Группы и встроим их в модель Группы. Плюсом в каждую Подписку встроим данные об Подписчике и его e-mail адресах:
{
_id: GROUP_ID,
memberships: [{
address: "vpupkin@gmail.com",
name: "Василий Пупкин",
pw: "Хешированный пароль",
person_addresses: ["vpupkin@gmail.com", "vpupkin@mail.ru", ...],
group_name: "Семья"
}, ...]
}
Смысл встраивать все связные данные в один документ заключается в том, что теперь некоторые запросы к данным делать намного проще. Запрос из предыдущей части статьи становится совсем простым (помните, нам нужно, имея один известный e-mail адрес и название Группы, узнать e-mail адреса остальных членов этой Группы):
- В коллекции Groups найдем Группу, содержащую Подписку, в которой
group_name
совпадает с известным нам названием Группы и массивperson_addresses
содержит известный нам e-mail; - Разберем полученный документ для извлечения остальных e-mail адресов.
Гораздо проще. Но что будет, если Подписчик захочет поменять имя или пароль? Нам придется менять его имя или пароль в каждой встроенной Подписке каждой Группы, в которой состоит этот Подписчик. Это также касается добавления нового или удаление существующего e-mail адреса из массива
person_addresses
. Такие моменты говорят нам об определенном характере данной модели: она хорошо подходит для специфичных запросов (потому что все необходимые данные уже внутри, типа pre-join), но может стать кошмаров в долгосрочной перспективе в плане сопровождения.Стратегия «частичного встраивания»
Подход, который я чаще всего рекомендую, это начать думать о модели данных без встраивания. После того, как у вас есть черновая модель, можно начинать выделять случаи, когда встраивание имеет смысл. Как правило, это отношения один-ко-многим.
Например, несолько e-mail адресов из коллекции Addresses принадлежат одному Подписчику (они также участвуют в модели Подписки) и обычно меняются не так часто. Поэтому мы объединим их в массив и добавим в нашу модель Подписчика, сделав её чуточку схожей с ментальной моделью.
Каждая Подписка связана с конкретным Подписчиком и конкретной Группой, поэтому можно встроить Подписки как в модель Подписчика, так и в модель Группы. В подобных случаях важно думать и о модели доступа к данным и о размере встраиваемых данных. Мы ожидаем, что люди вряд ли подпишутся на рассылку более чем из 1000 разных групп, и отдельно взятая группа в свою очередь также вряд ли наберет более 1000 подписчиков. В данном случае, цифры нам ничего полезного не говорят. Однако, наша модель доступа к данным, напротив, говорит нам, что при выводе на экран необходимо видеть все подписки конкретного человека. Для упрощения запроса мы встроим Подписки в модель Подписчика. Преимущество ещё и в том, что список e-mail адресов Подписчика находятся в модели Подписчика, а в Подписке используется один из адресов этого списка, и если нам нужно изменить или удалить e-mail адрес, это можно сделать в одном месте.
Теперь наша модель данных выглядит так:
{
_id: PERSON_ID,
name: "Василий Пупкин",
pw: "Хешированный пароль",
addresses: ["vpupkin@gmail.com", "vpupkin@mail.ru", ...],
memberships: [{
address: "vpupkin@gmail.com",
group_name: "Семья",
group: GROUP_ID
}, ...]
}
Это модель Подписчика, кроме неё есть ещё модель Группы, которая идентична той, что рассмотрна в описании стратегии «без встраивания».
Запрос, который мы обсуждали выше, теперь будет выглядеть так:
- В коллекции People найдем Подписчика с искомым e-mail адресом, среди Подписок которого есть Подписка с искомым названием;
- Используя
GROUP_ID
найденной Подписки, найдем в коллекции People других Подписчиков этой Группы и возьмем их e-mail адреса непосредственно из Подписки.
Это по-прежнему почти так же просто, как и в случае, когда все встроено, но теперь наша модель данных намного чище и проще в обслуживании. Надеюсь, эта статья было полезной для вас.