← Назад в блог

Как назначать цены по чужому магазину, которым вы не управляете

Опубликовано
4 мин чтения
--- просмотров

Если вы перепродаёте, ваши цены зависят от чужих цен. Звучит как задача интеграции, а на деле это задача кэширования. Ошибка в кэшировании приводит либо к тому, что магазин врёт про цены, либо к тому, что он падает вместе с источником, у которого выдался плохой день.

Почему два очевидных подхода не работают

Запрашивать при загрузке страницы. Каталог с тридцатью товарами превращается в тридцать исходящих HTTP-запросов, и время загрузки теперь ограничено самым медленным из них. Источник тормозит, тормозит и ваш магазин. Источник лежит, лежит и магазин. Вы взяли зависимость, которую не чините, и поставили её перед каждым посетителем.

Выгружать по ночам. Задержек нет, зато магазин показывает неверные цены до суток. Распродажи у источника начинаются и заканчиваются без предупреждения, и покупатель, пришедший после её конца, платит цену, которую вы уже не готовы держать. Либо вы съедаете разницу на тысячах заказов.

Ни то ни другое нельзя доточить настройками. Оба варианта неверны по своей природе: первый связывает доступность, второй связывает свежесть.

Кэш с коротким сроком жизни

Середина выглядит скучно и работает: забирать по расписанию, класть результат в хранилище, отдавать из хранилища. В WordPress это транзиент со сроком жизни.

Работает такая схема потому, что страница никогда не инициирует запрос наружу. Отрисовка читает только кэш. Если значения нет, страница показывает последнюю известную цену или прячет товар, но никуда не идёт. Обновление живёт в кроне, вне пользовательского пути, и медленный ответ источника там никому ничего не стоит.

Свежесть превращается в регулятор вместо свойства архитектуры. Каталогу, меняющемуся раз в сутки, хватит суточного обновления, а скачущий каждый час придётся перечитывать ежечасно. Вы выбираете, насколько сильно готовы ошибаться, и это честная форма такого решения.

Спросить про все регионы, потом решать

Дальше идёт часть, характерная именно для перепродажи. У одного товара цена нередко своя в каждом региональном магазине, поэтому «цена у источника» не число. Это множество.

Свернуть множество в одно число - решение бизнеса, а не техники, и оно меняется. Можно брать минимальную легитимную региональную цену. Можно фиксированный регион из-за налоговых правил. Можно медиану с наценкой. Любой вариант бывает верным, и верность зависит от рынка и закона.

Значит, выносим за интерфейс:

interface IProductPriceStrategy
{
    public function choose(array $pricesByCurrency): Price;
}

final class ChooseLowestPriceStrategy implements IProductPriceStrategy { /* … */ }

Стратегия остаётся единственным местом, знающим правило. Подмена стоит одного класса вместо охоты по кодовой базе за каждым местом, где считалась цена. Это важнее, чем кажется. Правила ценообразования как раз относятся к той логике, которую копипастят в шаблон, в REST-эндпоинт и в скрипт выгрузки, а потом версии расходятся.

Типизировать ответы источника

Разбирайте ответ в типизированные DTO, а не читайте ключи массива по месту:

$prices = GetPricesResponse::fromArray($body);   // бросит исключение, если формат изменился

Иначе переименованное на стороне источника поле даст вам null, который превратится в ноль, а ноль превратится в товар по нулевой цене на живом магазине. Нужен громкий сбой во время планового обновления, где его увидит лог и никто больше. Без DTO вы получаете тихий сбой, который найдёт покупатель.

Чем платим

Кэш - это второй источник правды, а вторые источники правды расходятся с первыми. Товар, удалённый у поставщика, останется в вашем каталоге, пока следующее обновление этого не заметит. Такое надо обрабатывать явно: либо обновление сверяет удаления, либо у товаров есть отметка «последний раз видели тогда-то», и залежавшиеся прячутся.

Есть и проблема холодного старта. Пустой кэш после выката означает, что до первого обновления ни у одного товара нет цены. Прогрев кэша легко забыть в скрипте деплоя и очень заметно в продакшене.

Общее правило

Когда вы зависите от системы, которой не управляете, решайте отдельно, насколько свежими вам надо быть и насколько доступными. Запрос по месту связывает и то и другое с чужой системой. Кэш даёт купить доступность ценой устаревания, а обменный курс в этой сделке назначаете вы.

Открыт для работы по контракту

Я доступен для работы по контракту. Если у вас есть интересная идея проекта, запишитесь на звонок через Calendly.

Записаться на 30-минутный звонок