Документы и коллекции — Примеры

Пример 1 — Какие связи встраивать

Проектируются запись пользователя и запись заказа. Строки заказа читаются вместе с заказом и должны меняться вместе с ним; у пользователя один адрес; пользователь принадлежит организации, которую делят многие. Для каждой связи решите, встраивать её или оформлять ссылкой, и покажите документы.

заказ -> его строки              (читаются вместе, их немного)
пользователь -> его адрес        (читаются вместе, один)
пользователь -> его организация  (делится многими, обновляется независимо)

Решение

// один документ на заказ: строки меняются вместе с заказом, поэтому одна запись атомарна
{
  "_id": 8842,
  "public_id": "ord_8842_pub",
  "customer_id": 41,
  "items": [
    { "sku": "A-1", "qty": 2 },
    { "sku": "B-7", "qty": 1 }
  ],
  "placed_at": "2026-05-01T09:12:00Z"
}

// один документ на пользователя: адрес ограничен и читается вместе с пользователем
{
  "_id": 41,
  "name": "Sam",
  "address": { "line1": "1 Mill Lane", "city": "Leeds" },
  "organisation_id": 7      // ссылка: разделяется, обновляется по своему расписанию
}

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

Пример 2 — Две сессии, одно потерянное обновление

У первой строки заказа меняется количество, пока другая сессия исправляет город доставки в том же заказе. Первый путь кода читает весь документ, правит одно вложенное поле и записывает его обратно, и другое изменение исчезает. Измените запись так, чтобы затронутым могло оказаться только то поле, которое она и собирается менять.

const order = await orders.findOne({ _id: 8842 });
order.items[0].qty = 3;
await orders.replaceOne({ _id: 8842 }, order);

// тем временем другая сессия задаёт address.city в том же заказе;
// после этой записи это изменение исчезло.

Решение

await orders.updateOne(
  { _id: 8842, "items.0.sku": "A-1" },
  { $set: { "items.0.qty": 3 } }      // затрагивает только этот путь
);
// изменение города, сделанное любой из сессий, не тронуто.

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