Канал событий

Канал событий

Канал событий (англ. event channel) — фундаментальный шаблон проектирования, используется для создания канала связи и коммуникации через него посредством событий. Этот канал обеспечивает возможность разным издателям публиковать события и подписчикам, подписываясь на них, получать уведомления.


Этот шаблон - является расширением шаблона "Издатель-подписчик (Publish/Subscribe)" путем добавления функций, которые присущи распределенной среде. Так, канал является централизованным и подписчик может получать опубликованные события от более, чем одного объекта, даже если он зарегистрирован только на одном канале.

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

Концептуальное описание канала событий показано ниже:


Диаграмма "Канал событий".png


Шаблон "Канал событий" использует сильно типизированные события; это означает, что подписчик может ожидать поступления определенных типов данных события, если регистрируется для определенного события. Он также позволяет подписчику отправлять события, а не только получать события, посланные ему.


Пример реализации канала событий на PHP


В примере используется дополнительный пакет Debugbar для Laravel.


Канал связи

Объявим интерфейс EventChannelInterface

interface EventChannelInterface
{

    /**
     * @param string $topic
     * @param SubscriberInterface $subscriber
     * @return void
     */public function subscribe(string $topic, SubscriberInterface $subscriber): void;

    /**
     * @param string $topic
     * @param string $data
     * @return void
     */public function publish(string $topic, string $data): void;

}


Как мы видим, класс будет реализовывать пару основных методов для подписки и публикации.

Для хранения тем и подписчиков создаем ассоциативный массив, где ключом является канал, а значением - массив подписчиков.

Реализуем класс EventChannel.

class EventChannel implements EventChannelInterface
{

    /**
     * @var array
     */protected array $topics = [];

    /**
     * @param string $topic
     * @param SubscriberInterface $subscriber
     * @return void
     */public function subscribe(string $topic, SubscriberInterface $subscriber): void{
        $this->topics[$topic][] = $subscriber;

        \Debugbar::info("{$subscriber->getName()} subscribed on topic [{$topic}]");
    }

    /**
     * @param string $topic
     * @param string $data
     * @return void
     */public function publish(string $topic, string $data): void{
        if (empty($this->topics[$topic])) {
            return;
        }

        foreach ($this->topics[$topic] as $subscriber) {
            $subscriber->notify($data);
        }
    }

}


Подписчик

Интерфейс

interface SubscriberInterface
{

    public function notify(string $data);

    public function getName();

}


С помощью метода notify будет происходить уведомление подписчика о событиях канала. Метод getName просто вернет имя подписчика.

Пример реализация интерфейса подписчика

class Subscriber implements SubscriberInterface
{

    public function __construct(
        protected string $name
    ){
        //
    }

    public function notify($data){
        \Debugbar::info("Subscriber {$this->getName()} notified of the news [{$data}]");
    }


    public function getName(){
        return $this->name;
    }

}


Издатель

Интерфейс

interface PublisherInterface
{

    public function publish(string $data);

}


Издателю дадим возможность публиковать события в канал методов publish. Также добавим тему издателя и внедрим зависимость канала событий с помощью интерфейса.

class Publisher implements PublisherInterface
{

    public function __construct(
        protected string $topic,
        protected EventChannelInterface $eventChannel
    ){
        //
    }

    public function publish(string $data){
        $this->eventChannel->publish($this->topic, $data);
    }

}


Пример использования канала событий

// Создаем канал событий
$newsChannel = new EventChannel();

// Создаем издателей
$newYorkTimes = new Publisher('newyorktimes-news', $newsChannel);
$rbk = new Publisher('rbk-news', $newsChannel);
$guardian = new Publisher('guardian-news', $newsChannel);

// Создаем подписчиков
$nick = new Subscriber('Nick');
$veronika = new Subscriber('Veronika');
$monika = new Subscriber('Monika');

// Подпишем наших подписчиков на события издателей
$newsChannel->subscribe('newyorktimes-news', $nick);
$newsChannel->subscribe('newyorktimes-news', $monika);
$newsChannel->subscribe('guardian-news', $veronika);
$newsChannel->subscribe('rbk-news', $nick);
$newsChannel->subscribe('rbk-news', $veronika);

// Публикуем события
$newYorkTimes->publish('Microsoft Lays Off 10,000 as It Looks to Trim Costs');
$rbk->publish('Lorem ipsum dolor sit amet');
$newYorkTimes->publish('Israel’s Supreme Court Blocks Appointment of Minister');
$guardian->publish('Solve the climate crisis by mining the moon, researchers say');