Сериализация данных на уровне базы

Discussion in 'Ваши проекты.' started by farento_com, 26 Jun 2019.

  1. farento_com

    farento_com Member

    Joined:
    25 Jun 2019
    Messages:
    8
    Likes Received:
    7
    Reputations:
    0
    Сидел я как-то и пытался отдать фронту JSON с объектами недвижимости, у которых была масса зависимостей. На бэке стояла Symfony 4, knp pagination и JMSSerializer, ну в принципе стандартные вещи, но проблема в том, что когда ты пытаешься отдать объект со всеми вложенными сущностями и коллекциями, то все начинает тормозить на уровне сериализации этих данных.


    Сначала нужно сделать запрос в базу, потом сериализатор подтянет постепенно все остальное, потом все это будет обернуто в JSON и только потом все вернется на фронт.


    Идея

    У меня появилась идея, а почему бы не возвращать на фронт с бэка сразу JSON напрямую из базы, да, надо написать офигительный SQL, но ведь можно сделать инструмент который это сделает за вас. Я принялся за написание идеи, репозиторий на гитхабе, за основу взята модель данных из доктрины, связи OneToOne, ManyToOne, OneToMany и ManyToMany. Так же этот инструмент легко можно прикрутить к Symfony 4 и он сам себя настроит, в итоге вам нужно будет только заинъектить фабрику QueryBuilderFactory и получить оттуда QueryBuilder для нужной таблицы по классу сущности.


    Так же мой сериализатор использует группы сериализации которые вы можете задать с помощью аннотации Expose на поле сущности, не забудьте так же на сущность навесить аннотацию Table и указать алиас таблицы, лучше использовать те, которые вы обычно задаете.


    Пример генерации SQL

    <?php

    use \Mash\MysqlJsonSerializer\QueryBuilder\Table\JoinStrategy\FieldStrategy;
    use \Mash\MysqlJsonSerializer\Wrapper\FieldWrapper;
    use \Mash\MysqlJsonSerializer\QueryBuilder\Table\Table;
    use \Mash\MysqlJsonSerializer\Wrapper\Mapping;
    use \Mash\MysqlJsonSerializer\QueryBuilder\QueryBuilder;

    $oneToManyTable = (new Table('advert_group', 'adg', 'adg_id'))
    ->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.

    репозиторий: https://github.com/AndreyMashukov/mysql-json-serializer