Проблема до безумия проста, но адекватного решения я не вижу.
У меня есть сущность Новость.
У новости, кроме всего прочего, есть свойство
Дата публикации (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);
Но в этом случае
- Текущее время оказывается не настоящим, а бизнес требует чтоб описывая ограничения мы использовали настоящее время;
- Для округления мы должны использовать время кеширования запроса и должны указывать его дважды (или дважды использовать);
- Если при описании бизнес правила мы используем текущую дату, то непонятно в какой момент мы должны округлять дату в запросе.
Для описания бизнес правил я использую шаблон проектирования
Спецификация. И спецификация, описывающая нужное ограничение, может быть сложным агрегатом в котором правило проверки
Даты публикации может быть зарыто очень глубоко. И вытащить его от туда, чтоб округлить дату, может быть очень не просто.
Чтоб понимать как это может выглядеть, опишу пример использования спецификации:
$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).