Pull to refresh

Comments 19

Тема эта очень интересная и меня давно интересует. Хорошо что модно напрямую пообщаться с разработчиком. Такой ORM если он упрощает JOIN между коллекциями и позволяет делать выборку одним запросом из нескольких коллекций именно одним (а не N+1) запросом к базе данных при этом с возможностью задавать фильтр по значению из любой коллекции. В Mongoose это кстати можно делать см. mongoosejs.com/docs/populate.html. В этом смысле Mongoose это тоже ODM+ORM. Аналогичный функционал есть и у справедливо критикуемой watreline которая вообще организует одинаковый интерфейс к релязионным базам и к mongodb.

Как у Вас с этими вопросами обстоит дело (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 при любых изменениях в нём.

В MySQL 8.0 добавлены функции для изменения отдельных полей JSON. Индексация тоже есть, делается через генерируемые колонки.

Функции такие и в PostgreSQL есть, вопрос в том, как это на диск при изменениях пишется. Монга перезаписывает весь документ только если какое-то поле выросло в размерах. Как с этим в MySQL?

Частичная перезапись JSON в MySQL 8.0 возможна при удалении поля или при установке нового значения существующего поля, если новое значение не длиннее места, занятого полем при предыдущем полном изменении/добавлении.

Замечательная вещь, await user.groups просто гениально, надеюсь решение доростет до продакшена

Спасибо. Как я написал в статье, я фронтендер, у меня есть парочка мелких проектов где использую эту библиотеку, но говорить о production ready по ним никак нельзя. Тут мне остаётся лишь надеятся, что кто-то попробует использовать в более крупном проекте и расскажет о том, что всё хорошо или о возникших проблемах. Так что если рискнёте, то обязательно отпишитесь, даже если всё будет хорошо работать.
Выглядит перспективно. Надо бы еще добавить в query и resolve типы обьектов, что бы IDE мог подсказать поля
Sign up to leave a comment.

Articles