Проектирование схемы в хранилище без схемы — Примеры

Пример 1 — Коллекция, сформированная её чтениями

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

карточка товара: читать один товар с его тегами и ценой
поиск:            фильтр по категории, страницы в порядке имён
канал цен:        обновлять только поле цены

Решение

{
  _id: 91,
  schema_version: 2,
  name: "Kettle",
  category: "kitchen",
  price_pence: 2499,
  tags: ["steel", "1.7l"]
}

// индексы, каждый выводится из одного чтения
db.products.createIndex({ category: 1, name: 1 });   // поиск, включая его сортировку
// карточка товара и канал цен адресуют документ по _id (он уже проиндексирован).

В основе проекта — чтения: карточка товара получает всю запись за одно обращение, поэтому теги встроены и ограничены, а фильтр и сортировка поиска покрыты одним составным индексом. Поле версии говорит читателю, какая перед ним форма, поэтому новую форму можно выкатывать, пока старые документы остаются на месте. Однако обычно ошибаются, проектируя вокруг диаграммы сущностей, из-за чего обычному чтению нужно несколько обращений.

Пример 2 — Отклонение записи, не соответствующей форме

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

await products.insertOne({
  name: "Kettle",
  price: "2499",                 // на этот раз строка
  category: "kitchen"
});                              // принято: в хранилище нет схемы, которую можно нарушить

Решение

const fields = {
  name: (v) => typeof v === "string" && v.length > 0,
  category: (v) => typeof v === "string",
  price_pence: (v) => Number.isInteger(v) && v >= 0,
};

function validate(doc) {
  for (const [k, ok] of Object.entries(fields))
    if (k in doc && !ok(doc[k])) throw new Error(`bad field: ${k}`);
  return { ...doc, schema_version: 3 };
}

await products.insertOne(
  validate({ name: "Kettle", price_pence: 2499, category: "kitchen" })
);
// документ версии 2 без price_pence отображается при чтении

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