If a project extends beyond a local machine, you will most probably have to get integrated with some third party systems.

I’d like to consider the case when such a third party system wants to receive notifications about any changes in our system. For example, the goods catalogue upgrade.

Task

There is a trading platform that provides access to its database of goods by means of WEB-services. Partners of the platform wish to get info about changes in the DB in the shortest possible time.

Alternate solution

We know all our partners and can request documentation on their software.

So, we can provide working with our partners’ API and notify them directly in case of any change of goods catalogue.

Such an approach requires individual connection of each new client. If there are some changes in partner’s software, we will need additional resources to restore its efficiency. Altogether it implies extra charges and another area of responsibility.

However, we don’t like to set such rigid links, therefore, we’ll do the following...

We take “observer” pattern as the basis. In such a way we will provide our partners with opportunity of subscribing for events and receiving necessary notifications.

Realization

Records of subscriptions should be kept somewhere. Let the type of storage remain on developer’s conscience. We will just consider WHAT should be stored.

  • Event. There can be quite a lot of event types, so to avoid sending notifications to all the subscribers, we have to know what each of them has subscribed for.
  • URL. Here is the simplest variant. Notification implies sending HTTP-request to specified URL. If such an approach will work, we can add support of other protocols and techniques.
  • Failure rate. Notification of subscribers is an operation that requires resources (processing time, memory, traffic). After sending a request, we await for affirmative response. However on the other side something can break and sending requests to such address is quite a pointless exercise. So we have to track failures and stop notifying when achieving definite rating.

Let’s assume the software component is programmed with PHP.

To subscribe for an event one should send POST-request to some URL. For example, https://b2b.api.my-soft.ru/event-subscription. And transfer URL and event parameters (the alias events).

We code it this way (on the basis of 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();
}

The algorithm of actions is quite simple:

  • Check up all the necessary data have been received in correct format;
  • Check up the event type is being available;
  • Check up the possibility of subscribing (canAddEvent method);
  • Save;
  • Inform user the subscription is successful.

Next, the client awaits for notification.

The catalogue is added with a new good. We have to notify all our clients about it. However, there can be so many of them. We are not able to do that in real time, as far as a user awaits for response. We need the task to be processed in turn.

To provide notification, first we have to pick all the subscriptions from DB, check them all and send corresponding requests.

This can be organized in one or two stages.

If we use two stages an event itself queues up first. At the second stage there is a first-come selecting of subscribers and their notifying.

However we can organize selecting and tasking at once. There is quite a cheap operation:

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

We select active subscribers for an event taken place and put them to queue within a cycle.

RemoteCallback is realized in the following way:

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);
    }
}

The operational procedure is the following: we send a POST-request to specified URL. If it is successful, we zero the failure register out - or, otherwise, increase the rate.

Here we should talk about conditions and limitations. Failure is considered a HTTP-status != 200, or a deferred response. In example above client takes 3 second for connection establishment and 3 more second for request processing. If partner’s system do not meet the term, it is considered a failure.

During the processing of subscription request the canAddEvent method is applied. There can be any preset, however in my case it checks limitation for an amount of listeners - no more than three of them for each event type.

And of course, one need authentication for working with such API methods.

That’s it. I have described the simplest variant. If it is necessary, one can add support of SOAP protocol, connections via sockets or something like that. At last, we have to provide message resending in case of response failure.



Comments

Add
You didn't comment anything. Want to read something?. Want to be first?
You have to login

Could be interesting


Сергей 0

Concurrent cache more

During the system implementation, performance ceiling was reached. We have a lot of requests, that initialize complex queries, load servers. So the response time increases what is unacceptable. And extensive way is not our way.

0 01.05.2017 19:41:21