Remote observer

от автора

Если проект вышел за рамки локальной машины, скорее всего придётся интегрироваться с какими-нибудь сторонними системами.

Хочу рассмотреть случай, когда упомянутая внешняя система хочет получать уведомления о каких-либо изменениях в нашей системе. Например, обновление каталога товаров.

Задача

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

Вариант решения

Мы знаем всех наших партнёров, можем запросить документацию по их ПО.

Можно реализовать работы с API наших партнёров, и при изменении товарного каталога оповещать их напрямую.

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

Очень не хочется устанавливать такие жесткие связи. Поэтому будем делать следующее.

За основу возьмём паттерн «наблюдатель». Предоставим нашим коллегам подписываться на события и получать необходимые им уведомления.

Реализация

Записи о подписках должны где-то храниться. Тип хранилища останется на совести разработчика. Рассмотрим ЧТО нужно хранить.

  • Событие. Типов событий может быть достаточно много и чтобы не рассылать оповещения всем подряд, нужно знать кто на что подписался.
  • URL. Берем самый простой вариант. Оповещение предполагает отправку HTTP-запроса на указанный URL. Если идея себя оправдает, то можно будет добавить поддержку других протоколов и технологий.
  • Количество отказов. Оповещение подписчиков это работа, которая требует ресурсов (процессорное время, память, трафик). После отправки запроса, мы ожидает положительного ответа. Но на той стороне может что-то сломаться и слать туда запросы становится бессмысленным. Соответственно нужно следить за отказами и прекращать оповещение при достижении некоторого значения.

Предположим, что программная часть написана на PHP.

Для подписки на событие, необходимо отправить POST-запрос на некоторый URL. Например, b2b.api.my-soft.ru/event-subscription. И передать параметры URL и event(алиас события).

Обрабатываем так (на базе Laravel):

public function subscribe() {     $request = $this->getRequest();     $eventName = $request->input('event');     $url = $request->input('callback');      $validator = \Validator::make([         'url' => $url,         'event' => $eventName     ], [         'url' => 'url|required',         'event' => 'required'     ]);      if ($validator->fails()) {         throw new BadRequestHttpException($validator->errors()->first());     }      $repository = $this->getRepository();     if (!$repository->eventAvailable($eventName)) {         throw new BadRequestHttpException(trans('api.error.eventSubscription.wrongEvent'));     }      if (!$repository->canAddEvent($eventName)) {         throw new BadRequestHttpException(trans('api.error.eventSubscription.maxCallbacks'));     }      $model = $repository->createModel([         'client_id' => $request->attributes->get('client')->id,         'event' => $eventName,         'url' => $url     ]);     if ($repository->save($model)) {         return $this->response($model);     }     throw new \Exception(); } 

Алгоритм действий прост:

  • Проверяем, что все нужные данные пришли и пришли в нужном формате
  • Проверяем, что тип события доступен
  • Проверяем возможность подписки (метод canAddEvent)
  • Сохраняем
  • Сообщаем, что подписка успешна

Далее клиент ждёт оповещения.

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

Чтобы произошло оповещение, сначала нужно выбрать их хранилища все подписки, пройтись по ним и послать соответствующие запросы.

Это можно организовать в один или два этапа.

В два, это когда сначала, в очередь ставится само событие. А на втором этапе, в порядке очереди, происходит выборка подписчиков и их оповещение.

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

$subscribersRepository->with(['event' => $event->getEventName()])->getActive()->each(function ($model) use ($event) {     $this->dispatch(new \Commands\RemoteCallback(         $model->id,         $model->url,         $event->getData()->toArray()     )); }); 

Выбираем активных подписчиков на произошедшее событие и в цикле ставим их в очередь.

RemoteCallback реализован следующим образом:

public function handle(EventSubscriptionRepository $subscriptionRepository) {     $client = new \Guzzle\Http\Client();     $res = $client->post($this->url, [], $this->data, ['connect_timeout' => 3, 'timeout' => 3]);     try {         if ($res->send()->getStatusCode() !== 200) {             throw new \Exception();         }         $subscriptionRepository->dropErrors($this->subscriptionId);     } catch (\Exception $e) {         $subscriptionRepository->incrementError($this->subscriptionId);     } } 

Порядок действий таков. Делаем POST-запрос на указанный URL. Если успех, то обнуляем счетчик отказов, иначе увеличиваем.

Здесь стоит пройтись по условиям и ограничениям. Про количество отказов уже было сказано выше. Отказом считается HTTP статус != 200, либо медленный ответ. В примере выше, клиенту выделяется 3 секунды на установление соединения и 3 секунды на обработку запроса. Если партнёрская система за это время не уложилась, то считать отказом.

Во время обработки запроса на подписку проверяется возможность этого (метод canAddEvent). В нём может быть всё что угодно, но в моём случае проверяется ограничение на количество слушателей. Не более 3х на каждый тип событий.

Плюс, конечно же, для работы с такими методами API необходима авторизация.

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

ссылка на оригинал статьи https://habr.com/ru/post/503594/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *