Канал событий (англ. event channel) — фундаментальный шаблон проектирования, используется для создания канала связи и коммуникации через него посредством событий. Этот канал обеспечивает возможность разным издателям публиковать события и подписчикам, подписываясь на них, получать уведомления.
Этот шаблон - является расширением шаблона "Издатель-подписчик (Publish/Subscribe)" путем добавления функций, которые присущи распределенной среде. Так, канал является централизованным и подписчик может получать опубликованные события от более, чем одного объекта, даже если он зарегистрирован только на одном канале.
В общем случае, шаблон "Канал событий" описывает интерфейсы для объектов-представителей и для объектов-подписчиков, а также для объекта-издателя. Использование неких представителей позволяет взаимодействовать настоящим издателям и подписчикам вне рамках самого канала, но посредством него.
Концептуальное описание канала событий показано ниже:
Шаблон "Канал событий" использует сильно типизированные события; это означает, что подписчик может ожидать поступления определенных типов данных события, если регистрируется для определенного события. Он также позволяет подписчику отправлять события, а не только получать события, посланные ему.
Пример реализации канала событий на 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');