Всем привет!
В этой статье расскажу, как решил написать библиотеку https://packagist.org/packages/xman12/persistent-request и что там внутри.
Как и любая библиотека, эта решает свои задачи, а именно гарантированное выполнение запроса и последующую обработку. Я находил, как минимум, одно решение, которое работает с подобной проблематикой — это https://temporal.io/, но система монструозная, а мне хотелось, чего-то более легкого и приземленного, поэтому я решил написать свое решение этой задачи.
Перед разработкой я обозначил для себя требования, которым должна соответствовать библиотека:
работать с laravel (на этом фреймворке построен сайт, где нужно было решить задачу с обработкой запросов)
легкое и гибкое использование
минимум зависимостей
Вот что в итоге вышло по зависимостям:
- php >=7.4
- guzzlehttp/guzzle >=6.3
- laravel/framework >=5.4
- laravel/serializable-closure "1.*"
На схеме ниже показал, как работает библиотека:
Ну что ж, приступим к установке и разбору.
Для начала библиотеку нужно подключить через composer:
composer require xman12/persistent-request
Затем подключаем провайдера в config/app.php
'providers' => [PersistentRequest\ServiceProvider::class]
После подключения провайдера, нужно запустить команду, которая запускает очереди:
php artisan queue:work
Разберем более подробно на примере.
Для начала инициализируем сервис:
$requestService = app(\PersistentRequest\Services\RequestServiceInterface::class);
Теперь нужно создать объект запроса:
$requestGuzzle = new \GuzzleHttp\Psr7\Request('get', 'https://google.com');
После нужно подготовить объект RequestDTO, и происходит вся магия:
$requestDTO = new \PersistentRequest\DTO\RequestDTO(
$requestGuzzle,
\PersistentRequest\Events\SuccessEvent::class,
30,
5,
function (\GuzzleHttp\Psr7\Response $response) {
if (200 !== $response->getStatusCode()) {
throw new \Exception('error processed');
}
});
Первым аргументом идет класс для запроса в guzzle.
Вторым идет \PersistentRequest\Events\SuccessEvent::class — это имя события, которое будет вызвано после успешного выполнения запроса, в него будет передан объект Psr\Http\Message\ResponseInterface, с которым можно работать в listener.
Третьим передается время в секундах, через которое библиотека повторит запрос, в случае неудачи.
Четвертым аргументом передается количество попыток после которого прекратятся запросы, тут нужно быть аккуратным если не передать тут значение то запрос будет выполняться бесконечно - пока не станет успешным. В случае если число попыток передается и по итогу времени количество отказов равняется лимиту, в таком случае очередь удаляется и отправляется событие DeleteRequestEvent, на него стоит подписаться если вы хотите отслеживать данное поведение.
Пятым аргументом передается анонимная функция, расширяющая работу с сервисом. Невалидные или валидные ответы обрабатываются, и, если нужно повторить запрос, то достаточно кинуть исключение. Так можно, не реализовывая listener, описать логику обработки ответа в анонимной функции.
Далее выполняется сам запрос:
$requestService->execute($requestDTO);
Внутри функции execute происходит запись данных запроса в бд, на случай, если он не будет валидным согласно описанной логике. Если ответ успешен, запись из базы данных удаляется.
Соберем все в кучу:
$requestService = app(\PersistentRequest\Services\RequestServiceInterface::class);
$requestGuzzle = new \GuzzleHttp\Psr7\Request('get', 'https://google.com');
$requestDTO = new \PersistentRequest\DTO\RequestDTO(
$requestGuzzle,
\PersistentRequest\Events\SuccessEvent::class,
30,
5,
function (\GuzzleHttp\Psr7\Response $response) {
if (200 !== $response->getStatusCode()) {
throw new \Exception('error processed');
}
});
$requestService->execute($requestDTO);
Если запрос по какой-то причине «упал», то вызывается RetryRequestJob.php, который передается экземпляр RequestDTO с необходимыми параметрами.
Вот так просто написать логику запроса к сервису, который должен гарантированно выполниться. Без реализации повторных опросов на стороне самого сервиса. Этот алгоритм работы применим как в микросервисной архитектуре, где запрос/ответ не должны быть синхронными, так и в других системах, где ответ пользователю не отправляется в одной сессии.
Комментарии (6)
toratoda
13.10.2023 16:50у http клиента от laravel есть метод retry который позволяет повторить запрос сколько угодно раз, а так же обрабатывать ошибку по необходимости, я думаю это подошло бы на случай когда "моргнула сеть"
Vest
Извините, но очень похоже на изобретение того, что есть в message-driven architecture. Всякие там «кафки», ActiveMQ и так далее.
И вообще, если у вас запрос «упал», то лучше разбираться почему это произошло, а не долбить в сервер n-раз.
xman12 Автор
на счет «кафки», ActiveMQ - я MQ систему не изобретал, как раз эта либа работает на очередях.
И вообще, если у вас запрос «упал», то лучше разбираться почему это произошло, а не долбить в сервер n-раз. - ситуации бывают разные, бывают ситуации когда сеть моргнула в микросервисах или допустим система на другой стороне висит (я отталкиваюсь от реального кейса когда интеграция при интеграции с другой системой на их стороне висела api) в таких ситуациях библиотека помогает, тут так же можно внутри ее обработать и ситуации падения, внутри прописать сценарий логирования и увидеть проблему, не обязательно просто долбить - можно и залогировать и разобраться
Vest
А то, что вы пошлете один запрос, потом другой и в итоге API обработает его дважды — это лучше? Как по мне это слабое решение. А вдруг нам важна целостность запросов?
Ладно, мы побеседовали, у нас с вами разные мнения и это хорошо.