Задать вопрос
ghost404
@ghost404
PHP Developer

Время кеширование запросов влияет на бизнес логики. Что делать?

Проблема до безумия проста, но адекватного решения я не вижу.

У меня есть сущность Новость.

У новости, кроме всего прочего, есть свойство Дата публикации (date_publication) определяющие можно ли показывать новость. То есть дата, после которой новость можно опубликовать на сайте.

Это основное бизнес правила/ограничение.

Соответственно, при выводе списка новостей я должен сравнивать Дату публикации с текущим временем и тат я сталкиваюсь с проблемой кэширования.

Все мы знаем что запросы использующие текущее время не кешируются. Например MySQL не кеширует запросы в которых используется функция NOW() или CURRENT_TIMESTAMP(). Поэтому мне нужно в явном виде передавать текущую дату:

date_publication <= "2017-04-10 13:50:00"

Новости, это такой контент, который как правило обновляется не часто, особенно на ресурсах где новости являются не корневым контекстом (Сore Сontext).

Я хочу кешировать на 10 минут запрос к БД который возвращает список новостей. И для того чтоб это работало мне не достаточно просто сказать - кэшируй этот запрос 10 минут. Тут я упираюсь в целый ряд ограничений:

  • Я использую для доступа к БД Doctrine;
  • Я использую стандартную функцию setResultCacheLifetime() из Doctrine для кеширования результатов запросов, которая автоматически генерит ID кеша на основе параметров запроса;
  • Можно конечно в явном виде указать ID кеша setResultCacheId(), но это влечет к переусложнению и фактически реализации почти тойже функциональности что и по умолчанию;
  • Вместо Doctrine здесь может выступать любой движок БД поддерживающий кешировани запросов. Я привел Doctrine как пример;
  • Под капотом все запросы кешируются в Redis;
  • Бизнес логика требует чтоб я сравнивал реально, текущее время, но в этом случае ID кеша будет меняться каждую секунду и кеш в результате работать не будет.


Я задавал этот вопрос ребятам из Doctrine и мне дали понять, что это не их забота и решатся эта задача должна на другом уровне. И вот я пытаюсь понять на каком же именно уровне должна решатся эта задача. Прошу мне в этом помочь.

Собственно, решением этой задачи будет округлить текущее время до времени кешировани:

$now = new \DateTime();
// округляем дату вниз чтоб в выборку не попали данные которых там быть не должно
$now->setTimestamp(floor($now->getTimestamp() / $cacheLifetime) * $cacheLifetime);


Но в этом случае

  1. Текущее время оказывается не настоящим, а бизнес требует чтоб описывая ограничения мы использовали настоящее время;
  2. Для округления мы должны использовать время кеширования запроса и должны указывать его дважды (или дважды использовать);
  3. Если при описании бизнес правила мы используем текущую дату, то непонятно в какой момент мы должны округлять дату в запросе.


Для описания бизнес правил я использую шаблон проектирования Спецификация. И спецификация, описывающая нужное ограничение, может быть сложным агрегатом в котором правило проверки Даты публикации может быть зарыто очень глубоко. И вытащить его от туда, чтоб округлить дату, может быть очень не просто.

Чтоб понимать как это может выглядеть, опишу пример использования спецификации:

$result = $rep->match(
	new AndX(
		new NewsPublished(),
		new Slice($slice_size, $slice_number)
	),
	new Cache(600)
);


что разворачивается в:

$result = $rep->match(
	new AndX(
		new Equals('enabled', true),
		new LessOrEqualThan('date_publication', new \DateTime()),
		new Limit($slice_size),
		new Offset(($slice_number - 1) * $slice_size)
	),
	new Cache(600)
);


Есть мысль, что округление должно выполнятся в функции match(), которая разбирает спецификации и собирает на их основе SQL запрос. Для применения спецификаций, я использую стороннюю библиотеку и ее автор со мной не согласен.

И вообще, кеширование это про доступ к данным (Persistence Layer) и не должно пересекаться с доменным уровнем (Domain Layer).

И вот уже несколько дней я сижу и думаю. А как же правильней? Понятно что костыль подоткнуть не проблема, а правильно то как? Так чтоб код был ясный и прозрачный, легко читаемый, без костылей и соответствовал единому языку (Ubiquitous Language).
  • Вопрос задан
  • 355 просмотров
Подписаться 2 Оценить 4 комментария
Пригласить эксперта
Ответы на вопрос 2
@dinegnet
Все мы знаем что запросы использующие текущее время не кешируются. Например MySQL не кеширует запросы в которых используется функция NOW() или CURRENT_TIMESTAMP(). Поэтому мне нужно в явном виде передавать текущую дату:


Ты не там кэшируешь.
Полагаться в кэшировании на MySQL - глупая неээфективная стратегия.

Ты ищешь проблему вообще не в том месте.
Ответ написан
@entermix
Я задавал этот вопрос ребятам из Doctrine и мне дали понять, что это не их забота и решатся эта задача должна на другом уровне.

Все правильно.

И вот я пытаюсь понять на каком же именно уровне должна решатся эта задача. Прошу мне в этом помочь.

Просто добавьте поле published (bool), например, и публикуйте отложенные новости используя планировщик.

Соответственно выборка SELECT FROM `posts` WHERE `published` = 1
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы