Комментарии 19
Как у Вас с этими вопросами обстоит дело (JOIN, N+1, WEHRE на полях из связанных коллекций)?
Вот эта структура кажется не очень перспективной:
В БД появятся две коллекции Pet и Owner с записями:
{
"_id": «5a...1f44»,
«name»: «Tatoshka»
}
и
{
"_id": «5a...1f43»,
«name»: «Dmitry»,
«pets»: [«5a...1f44»]
}
Пока мы связываем коллекции Owner и Pets, то хранить массивом связанные значения вроде бы не сильно накладно. Но если это Country и Citizen — то это уже не так приятно.
Да, похоже забирать данные по ссылкам Mongoose действительно умеет, про сохранение правда ничего не сказано.
Как у Вас с этими вопросами обстоит дело (JOIN, N+1, WEHRE на полях из связанных коллекций)?
делается дополнительный отложенный запрос при первом обращении к полю.
Вот эта структура кажется не очень перспективной:
в реляционных БД сохранить массив в поле не особо просто, там для хранения связи делается промежуточная таблица. Здесь тоже можно было так сделать, плюс будет в более быстром редактировании связи при большом количестве записей в ней, но будет и минус — более медленное чтение, две выборки вместо одной. Так что да, если отношение один/многие к очень-очень многим и записи в связи часто меняются, то реализованный механизм не очень подойдёт.
делается дополнительный отложенный запрос при первом обращении к полю.
То есть тот самый N+1-й?
Да, но +1 только для реально используемых данных. Я рассматривал ещё вариант с указанием методам find и findAll полей которые необходимо выкачать, примерно так:
User.find<User>({ name: 'Dmitry' }, { groups: true }, m);
тут плюс был бы в отсутствии необходимости использовать await
при дальнейшем чтении таких полей, но минус в том, что не всегда заранее знаешь какие поля понадобятся и иногда приходилось бы выкачивать лишнее, да и указывать groups: true
тоже не очень то хочется. В результате остановился на том, что есть.
Почему бы не совместить оба варианта? Когда точно знаешь что понадобятся некоторые поля указываешь как в mongoose populate и подгружаешь все что нужно одним запросом, но и фишка с await user.groups тоже остается
А что если оставлять await даже если данные сразу выкачиваются? По идее он почти не мешает, а вот если пытаться от него избавиться, то сразу возникают проблемы с типизацией, так как тип будет зависеть от аргументов в find/findAll. Прийдётся указывать что-то типа Promise<Array<User> | null> | User | null
, что, конечно, будет совсем не удобно. То есть данные будут забираться сразу и оборачиваться в уже resolved промис.
Реализовал не убирая await, пример:
let user = (await User.find<User>({ name: 'Dmitry' }, ['groups']))!;
(await user.groups)![0].printData();
Поля передаются пока только в виде массива, позже попробую ещё в виде объектов доделать, чтобы можно было подподдокументы сразу вытаскивать.
Что с организацией пула? Могу дать рекомендацию дать доступ к нативным параметрам драйвера mongodb. Наиболее важные связаны с параметрами пула соединений а так же с реконнектами (соединение может на время исчезнуть например при рестарте базы данных)
Способ соединения описанный в статье скорее для тестов, более продвинутый выглядит так:
import {
BaseModel,
Field,
Maraquia,
Model
} from '@riim/maraquia';
import { MongoClient } from 'mongodb';
const db = (await MongoClient.connect('mongodb://localhost:27017/Test')).db('Test');
const m = new Maraquia(db);
let user = (await User.find<User>({}, m))!;
user.printData();
класс Maraquia — что-то вроде адаптера, его инстанс передаётся в методы find
, findAll
, save
и remove
последним дополнительным параметром. Передать можно один раз, он будет запомнен, то есть если он был передан в find
, то позже при сохранении в save
уже можно не передавать. Так же он автоматически копируется в порождаемые модели (при чтении полей соответствующих встроенным документам).
При таком способе, во-первых, можно настроить соединение как угодно работая напрямую с драйвером mongodb, во-вторых, можно работать сразу с несколькими базами создав несколько инстансов Maraquia.
Напомню, в реляционных БД нет возможности просто взять и встроить документ в поле другого документа (в этой статье записи таблиц тоже называются документами, хоть это и некорректно), можно конечно хранить в поле JSON в виде строки, но индекс по данным в нём сделать не выйдет.Я немного дополню. В PostgreSQL поддерживается индексация JSONB.
Есть такое, причём, насколько я понимаю, единственная причина по которой PostgreSQL нельзя назвать полноценной документоориентированной БД — это полная физическая перезапись JSONB при любых изменениях в нём.
Функции такие и в PostgreSQL есть, вопрос в том, как это на диск при изменениях пишется. Монга перезаписывает весь документ только если какое-то поле выросло в размерах. Как с этим в MySQL?
Замечательная вещь, await user.groups просто гениально, надеюсь решение доростет до продакшена
Maraquia — ORM для MongoDB