Сразу бросается в глаза, что поле remaining, если оно физически существует
в модели слотов — нарушает нормальную форму базы данных. Однако, если мы
сделаем сервис, который будет всегда(!) читать только из кеша (и забудем
думать про cache stampede), то надо ли на текущем этапе делать эту
денормализацию? Возьму на себя смелость и обойдусь без этого поля. Когда
весь код будет написан, поле можно будет безболезненного добавить, и это
будет незначительный рефакторинг.
Мы будем поддерживать актуальный кеш, в котором поле remaining будет
вычисляться: capacity минус сумма холдов со статусом confirmed и сумма
холдов со статусом held не старше пяти минут.
Все запросы на изменение данных будут не просто инвалидировать кеш, они будут его перестраивать. Если это окажется неэффективно (много запросов, долгие ответы, нехватка соединений) — мы можем вынести перестройку кеша в асинхронный процесс (очереди).
Примечательно, что запрос на создание брони должен обеспечивать идемпотентность, но при этом в запросе может меняться идентификатор слота. В техническом задании это явно не оговаривается, но кажется очевидным, что идемпотентность должна обеспечиваться не глобально, а в рамках каждого слота по отдельности. Также замечу, что повторный вызов продлевает бронь (это логично).
В техническом задании сказано, что брони живут 5 минут. Это означает, что бронь старше 5 минут не может быть подтверждена. Также сказано, что фоновую очистку можно не реализовывать. Значит у нас в базе будут оставаться устаревшие брони; как с ними поступить?
- Сделать дополнительную проверку «не устарела ли бронь» перед её подтверждением?
- Или подтверждать даже устаревшую бронь, если есть доступное место?
Предпочту второй вариант: почему бы и нет, это не ломает систему бронирования; а клиенту не нужно лишний раз создавать бронь. А когда (и если) мы внедрим фоновую очистку, то с устаревшими бронями просто перестанем сталкиваться.
Но важное замечание: устаревшие брони не должны учитываться в remaining.
Значит до тех пор, пока мы не сделаем очистку базы от устаревших броней (и
проистекающую из этого перестройку кеша), нам придется перестраивать кеш по
расписанию (это не очень экономно, но у нас и не прод).
Делаем базу данных
php artisan migrate
Наполняем её тестовыми данными. Тестовые данные подобраны так, чтобы вместимость слота совпадала со значением его первичного ключа. Добавлено три слота.
php artisan db:seed
Запускаем приложение
php artisan serve
Получение списка доступных слотов:
curl -X GET --location "http://127.0.0.1:8000/slots/availability"Создание брони. Первый вызов — 201. Последующие — 200:
curl -X POST --location "http://127.0.0.1:8000/slots/1/hold" -H "Idempotency-Key: 666bf998-7f27-4285-a966-6f85ed9f31e1"Подтверждение брони (можно многократно):
curl -X POST --location "http://127.0.0.1:8000/holds/1/confirm"Отмена брони (можно многократно):
curl -X DELETE --location "http://127.0.0.1:8000/holds/1"