[Из песочницы] Сериализация данных на уровне базы
Привет, Хабр!
Сидел я как-то и пытался отдать фронту JSON с объектами недвижимости, у которых была масса зависимостей. На бэке стояла Symfony 4, knp pagination и JMSSerializer, ну в принципе стандартные вещи, но проблема в том, что когда ты пытаешься отдать объект со всеми вложенными сущностями и коллекциями, то все начинает тормозить на уровне сериализации этих данных.
Сначала нужно сделать запрос в базу, потом сериализатор подтянет постепенно все остальное, потом все это будет обернуто в JSON и только потом все вернется на фронт.
Идея
У меня появилась идея, а почему бы не возвращать на фронт с бэка сразу JSON напрямую из базы, да, надо написать офигительный SQL, но ведь можно сделать инструмент который это сделает за вас. Я принялся за написание идеи, репозиторий на гитхабе, за основу взята модель данных из доктрины, связи OneToOne, ManyToOne, OneToMany и ManyToMany. Так же этот инструмент легко можно прикрутить к Symfony 4 и он сам себя настроит, в итоге вам нужно будет только заинъектить фабрику QueryBuilderFactory и получить оттуда QueryBuilder для нужной таблицы по классу сущности.
Так же мой сериализатор использует группы сериализации которые вы можете задать с помощью аннотации Expose на поле сущности, не забудьте так же на сущность навесить аннотацию Table и указать алиас таблицы, лучше использовать те, которые вы обычно задаете.
Пример генерации SQL
addSimpleField('adg_id')
->addSimpleField('adg_name')
;
$table = (new Table('estate', 'est', 'est_id'))
->addSimpleField('est_id')
->addSimpleField('est_name')
->addOneToManyField($oneToManyTable, 'advert_groups', new FieldStrategy('adg_estate'));
$mapping = new Mapping();
$mapping
->addMap($table, 'est_id', 'id')
->addMap($table, 'est_name', 'name')
->addMap($oneToManyTable, 'adg_id', 'id')
->addMap($oneToManyTable, 'adg_name', 'name');
$builder = new QueryBuilder($table, new FieldWrapper($mapping));
$builder
->setOffset(2)
->setLimit(1);
$sql = $builder->jsonArray();
В результате будет сгенерирован следующий SQL:
SELECT JSON_ARRAYAGG(JSON_OBJECT('id',est_res.est_id,'name',est_res.est_name,'advert_groups',(SELECT JSON_ARRAYAGG(JSON_OBJECT('id',adg.adg_id,'name',adg.adg_name)) FROM advert_group adg INNER JOIN estate est_2 ON est_2.est_id = adg.adg_estate WHERE est_2.est_id = est_res.est_id))) FROM (SELECT * FROM estate est LIMIT 1 OFFSET 2) est_res
Результат:
[{"id": 3, "name": "Москва, окская улица, 3к1", "advert_groups": [{"id": 10, "name": "avito-1115362430"}]}]
Итоги
Полная инструкция по применению будет скоро добавлена в репозиторий на гитхаб. В результате, когда я прикрутил это в своем проекте, я получил очень быстрые ответы от REST API и при этом я смог выдать массу объектов с большим количеством вложенных зависимостей, к примеру, то что я хотел получить через JMSSerializer я получал за 40сек, сейчас за 230 милисек, и это при условии, что Kernel Subscriber читает аннотации на полях сущностей через рефлекцию, я хочу в скором времени это реализовать через cache Symfony.
Статья будет дополняться…