У меня есть веб-приложение, построенное на PHP (Symfony 3) и MySQL (Doctrine). Все это работает хорошо, и теперь я хочу создать REST API, чтобы сделать некоторые части данных приложения общедоступными.
Чтобы упростить ситуацию, скажем, у меня есть страница /продукты и для каждого продукта страница с подробностями /идантификационный номер продукта}. На странице продуктов пользователи могут применять несколько фильтров к списку продуктов, например, какие категории они хотят. Большинство фильтров представляют собой просто список флажков, из которых пользователь может выбирать (без текстовых фильтров).
В таблице продуктов много взаимосвязей, хотя она и не была чрезмерно нормализована; это присуще области, с которой я работаю. Чтобы получить все данные для одной строки продукта, мне нужно сделать + - 20 объединений по 15 отдельным запросам. Да, я знаю, это много, но большинство таблиц - это просто таблицы поиска, а общее время запроса занимает всего + - 3 мс. Фильтрация списка продуктов выполняется с помощью построителя запросов на чистом SQL. Поскольку на странице продуктов отображается только список названий продуктов, производительность здесь не проблема.
Но вот проблема: REST API должен будет сгенерировать список полных объектов продукта со всеми данными (а не только именами). Как вы понимаете, фильтрация + все дополнительные соединения / запросы и GROUP BY не очень хороши для производительности. Чтобы решить эту проблему, я подумал о создании какой-то гибридной системы, использующей только SQL для записи обновлений в базу данных и сохраняя денормализованное хранилище документов только для чтения для извлечения продуктов.
Самой простой реализацией, которую я могу придумать, было бы создание таблицы product_api_cache, в которой хранятся продукты, созданные как JSON, готовые для отображения в API. Если пользователь запрашивает ресурс / api / products, построитель запросов применяет фильтры для возврата списка идентификаторов продуктов, которые я затем могу использовать для получения JSON продуктов из таблицы product_api_cache.
Более продвинутая реализация могла бы использовать подходящее хранилище документов, такое как ElasticSearch или MongoDB. Я не уверен, как это будет сочетаться с текущей системой фильтрации (построителем SQL-запросов). Означает ли это, что мне нужно продублировать всю логику фильтрации специально для ElasticSearch?
Кроме того, JSON, возвращаемый API, не на 100% соответствует фактическому объекту продукта (он немного упрощается за счет его сериализации). Означает ли это, что мне нужно написать 2 отдельных уровня сериализации? Первый - для хранения индивидуальной JSON-версии объекта продукта, чтобы ElasticSearch мог правильно запрашивать его, а второй - для сериализации результата ElasticSearch в упрощенном представлении для пользователя. Поскольку ElasticSearch возвращает JSON, означает ли это, что мне нужно десериализовать этот результат в объект продукта, чтобы затем снова сериализовать объект продукта?
Что было бы разумным способом реализовать это? Есть еще способы сделать это? Я думаю не о том?






Я думаю, что самым простым и быстрым решением было бы хранить кешированные версии объектов ответа API. Конечно, у вас будут дублированные данные. В зависимости от вашего случая вы можете решить, приемлемо это или нет. Я бы не стал беспокоиться, если у вас нет / не будет размер базы данных более нескольких гигабайт. (родственник)
Если вы используете эластичный поиск, вам придется абстрагироваться от логики фильтрации, как вы думали. Но вы также можете использовать стратегию таблиц mysql (id = data) в ES, но таким образом вы будете использовать ES только для извлечения данных.
И просто напоминание. Вам действительно нужна такая оптимизация? Возможно, вам это не понадобится. Это точно не с точки зрения производительности. Но зачем усложнять кодовую базу, если в этом нет необходимости?
Я остановлюсь на варианте денормализованной таблицы. Если вы создаете денормализованную таблицу для чтения данных, вы в основном реализуете CQRS (см. https://martinfowler.com/bliki/CQRS.html). Я делал это несколько раз, "объект-оболочка" имеет исходный объект в качестве одного из своих свойств, например:
class ProductExtended {
/** @var Product **/
private $product;
/** @var float **/
private $originalPrice;
/** @var float **/
private $discountedPrice;
...
Вы запрашиваете свой объект ProductExtended, как любой другой объект, но в базе данных это одна таблица, поэтому производительность выше и фильтрация проще, если вы добавляете в ProductExtended все доступные для поиска свойства.
Усилия переходят к обновлению этой таблицы: вам нужно добавить слушателей для любых изменений в Product, и, возможно, также команду, которая перестраивает все это, чтобы быть уверенным, что вы отловите любое «ручное» или не прослушиваемое изменение.
Это действительно было бы лучшим решением, чем хранение только JSON, потому что этот объект-оболочка также может использоваться вне API для ускорения работы. Однако я все же хочу, чтобы данные в API были нормализованы; возможно, я мог бы сформулировать это лучше. Проблема в нормализации, но сами данные не могут быть денормализованы, потому что они иерархичны по своей природе. Мое первое предложенное решение могло бы работать как кеш, но я не думаю, что это возможно с использованием CQRS? По сути, мне нужна почти точная копия объекта продукта, но без всех накладных расходов на соединения.
Я остановлюсь на варианте ElasticSearch. Если вы используете ES, вам не нужно индексировать объект «как есть». Вместо этого вы можете создать денормализованную версию и проиндексировать версию что, чтобы вы могли использовать все расширенные параметры фильтрации ES (и нет, вам не обойтись с традиционными QueryBuilder и DQL). Идентификатор объекта будет связующим звеном между объектом БД и данными ES.
Если у вас есть продвинутая логика фильтрации и / или много данных, вы, возможно, захотите изучить этот путь. ES очень мощный и очень быстрый, и (при правильной интеграции с Doctrine) он будет возвращать ваши исходные сущности в качестве результатов, поэтому он прозрачен с точки зрения потребителя API.
Сам MySQL имеет встроенную поддержку JSON. Фактически, он также работает как хранилище документов аналогично MongoDB.
Мне действительно может не понадобиться ElasticSearch прямо сейчас, но в будущем я могу использовать более продвинутый текстовый поиск. Хотя это может быть более сложно, это может быть хорошим учебным опытом.