Каналы уведомлений из системы

Назначение

Система сообщает внешним клиентам о происходящем через события (классы событий). Клиент подписывается на интересующие события и получает уведомления по мере их возникновения, вместо того чтобы периодически опрашивать REST-API. Статья описывает доступные каналы, различия событий общего вида и событий модели данных (modelevents), а также подход к поддержанию согласованных данных у нескольких рабочих мест (АРМ).

Каналы

Канал Кто подписывается Какие события Доставка

Websocket, User-API subscr

Пользователь в рамках своей сессии

Только modelevents.data_changed

В открытое websocket-соединение; подписки удаляются при его закрытии

Websocket, Token-API subscr

Внешняя система по токену канала интеграции

Все классы, в том числе modelevents

В открытое websocket-соединение; подписки удаляются при его закрытии

HTTP веб-хуки, /rest/v1/service/webhooks

Внешняя система по токену канала интеграции

Все классы, кроме modelevents

HTTP-запрос на URL подписки или канала интеграции; без подтверждения и повторов

Kafka

Настраивается в домене (параметры kafka_params, kafka_mustache)

Классы, отмеченные в Классы событий общего вида; modelevents — только встроенной модели доменного центра (см. Kafka и изменения динамической модели данных)

Запись в топики брокера

Прямые уведомления

Пользователь в рамках своей сессии, без подписки

Состояние пользователя (user_state_changed), уведомления сценариев (capability scriptnotify)

В открытое websocket-соединение пользователя

Рекомендации по выбору:

  • Интерфейс пользователя, которому нужны изменения данных в реальном времени, — websocket User-API, события modelevents.

  • Внешняя система с постоянным соединением — websocket Token-API: все классы событий, порядок доставки сохраняется в пределах соединения.

  • HTTP веб-хуки не рекомендуются (см. Почему веб-хуки дороже websocket); применяются, только если внешняя система не может держать websocket-соединение.

  • Аналитика и шина событий — Kafka.

Почему веб-хуки дороже websocket

  • На каждое уведомление открывается отдельное HTTP-соединение (для https — с TLS-рукопожатием) и закрывается после ответа. Websocket передаёт уведомления кадрами в уже открытом соединении.

  • Запрос синхронный: система ждёт ответа приёмника (до 10 секунд). Уведомления на один адрес отправляются последовательно, поэтому медленный или недоступный приёмник задерживает все следующие уведомления на этот адрес и занимает ресурсы обработчиков системы.

  • Доставка не подтверждается и не повторяется: при недоступности приёмника уведомление теряется, и для критичных данных нужна периодическая сверка через REST-API. В websocket потеря соединения обнаруживается сразу, и клиент восстанавливает состояние перечиткой.

  • События модели данных (modelevents) через веб-хуки недоступны.

Kafka и изменения динамической модели данных

События modelevents динамической модели данных в Kafka не отправляются — только подписчикам websocket. В Kafka попадают modelevents встроенной модели доменного центра (например, /rest/v1/uc/sipusers) и события общего вида по Классы событий общего вида.

Поток изменений классов динамической модели в Kafka даёт история изменений:

  • у класса включается опция store_changehistory_mode (sync или async): каждое изменение сущности записывается в класс истории изменений домена platform/log/HistoryChanges — единый приёмник изменений всех классов домена с этой опцией;

  • класс истории изменений размещается в хранилище с режимом хранения transactionlog (Kafka; для чтения — также ClickHouse): записи истории поступают в топик Kafka. По умолчанию класс создаётся с режимом history в хранилище по умолчанию, поэтому для потока в Kafka его размещение меняют.

Запись истории содержит время и автора изменения, класс, операцию (kind: create, update, replace, delete, clear; rollback — изменение отозвано из-за сбоя сохранения), идентификатор и название сущности, изменённые поля, значение созданной или удалённой сущности. В режиме sync операция с классом завершается после записи в историю; в режиме async запись выполняется асинхронно с сохранением порядка изменений.

Общие правила подписки

  • events — список событий в формате EventClass.EventType или "any".

  • objects — фильтр по объектам, к которым привязано событие: "any" или список.

  • В мастер-домене события callevents и confevents всех доменов доступны по подписке на класс (callevents.*) или его событие (callevents.invite) с "objects": "any": подписки на "any" этих событий в мастер-домене не получают, подписка с объектами отклоняется. Поток включает все звонки платформы: для веб-хуков он чрезмерен (см. Почему веб-хуки дороже websocket).

  • expires — срок действия подписки в секундах. Подписку нужно продлевать повторным запросом с тем же id до истечения срока. expires = 0 удаляет подписку. Для веб-хуков срок не больше 86400; применённое значение возвращается в ответе.

  • id — идентификатор подписки; если не задан, генерируется и возвращается в ответе. Указывается в каждом уведомлении в поле sid.

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

  • Если событие подходит под несколько подписок одного подписчика, уведомление отправляется по каждой из них.

Формат уведомления:

["notify", {"sid": "...", "class": "...", "type": "...", "eventts": 1791975832258, "data": {...}}]

Для веб-хуков тело HTTP-запроса — объект из второго элемента массива.

События общего вида и события модели данных

События делятся на два вида, и подписка на них оформляется по-разному:

  • События общего вида сообщают о происходящем в системе: звонки, конференции, сценарии, состояния провайдеров и пользователей (Классы событий общего вида). У каждого класса свой набор событий. Подписка отбирает события по их списку и по объектам, к которым событие относится (Объекты событий общего вида); по содержимому события не фильтрует.

  • События модели данных (modelevents) сообщают об изменении записей коллекции: создание, изменение, удаление сущности. Событие одно — modelevents.data_changed, операция передаётся в уведомлении. Подписка оформляется на одну коллекцию и может отбирать записи по содержимому, ограничивать передаваемые поля и операции.

Различия в параметрах подписки:

Параметр События общего вида События модели данных

events

События классов, в том числе маска класса событий EventClass.*

Только modelevents.data_changed — исключительное и единственное значение в списке

objects

"any" или список объектов; достаточно совпадения одного

Одна коллекция: поле classpath (путь коллекции в REST-API) или единственный элемент objects

exargs

Не применяется

filter, index, mask, operations, content_mode

Разрешение подписки

Право подписки канала интеграции

Права IAM на чтение коллекции, политики безопасности модели данных (Политики доступа для modelevents)

Классы событий общего вида

Класс Token-API (websocket) Веб-хуки Kafka

callevents

да

да

да

confevents

да

да

да

ivrevents

да

да

да

providerevents

да

да

да

statesevents

да

да

да

ccsevents

да

да

да

ctievents (мастер-домен)

да

да

да

События сценариев (компонент «Генерация произвольного события», класс задаёт сценарий)

да (направление «Подписчики»)

да (направление «Подписчики»)

да (направления KAFKA)

scriptevents

нет

нет

да

selectorevents

нет

нет

да

События класса registrarevents не генерируются с версии 1.8.27; состояния регистраций доступны событиями modelevents коллекции /rest/v1/uc/sipuser_states.

Объекты событий общего вида

Событие связано с объектами, к которым оно относится, и отправляется по подписке, если хотя бы один из них указан в objects. Объект задаётся строкой:

Класс Объект

callevents

Логин SIP-пользователя или номер внешнего абонента

confevents

Идентификатор конференции или номер комнаты

providerevents

Код учётной записи провайдера

statesevents

Логин SIP-пользователя или идентификатор пользователя

ivrevents, ccsevents

Объекта нет, указывается "any"

События сценариев

Логин из поля user события

Объект можно задать также в виде {"type": "sipuser", "sipuser": "sip1"}; поддерживаются типы sipuser, other, conf, provider, user.

События модели данных

Класс указывается в classpath:

  • динамическая модель — /rest/v1/model/<section>/<Class>;

  • коллекции статической модели, поддерживающие события, — например /rest/v1/uc/sipusers;

  • коллекции пользовательского раздела /rest/v1/user/…​.

Операция передаётся в поле operation уведомления:

  • create — сущность создана, либо изменена так, что стала попадать под фильтр подписки;

  • update — сущность изменена, и прежнее, и новое значение попадают под фильтр;

  • delete — сущность удалена, либо изменена так, что перестала попадать под фильтр;

  • clear — коллекция очищена;

  • reload — обработка изменений по классу перезапущена; состояние коллекции нужно перечитать;

  • corrupt — отменяет предыдущее уведомление об изменении сущности: изменение не сохранено из-за сбоя хранилища; сущность нужно перечитать.

Операции clear и reload касаются всей коллекции и приходят независимо от фильтра. Операция corrupt возникает только у классов с режимом integrity_mode = sync_fast_notify, где уведомление отправляется до сохранения в хранилище. Если сохранение затем не удалось, по той же сущности отправляется corrupt; фильтр подписки применяется так же, как к исходному уведомлению, поэтому corrupt получают те подписчики, которые получили отменяемое изменение. Затронута одна сущность (eid): её нужно перечитать — в хранилище осталось прежнее значение. Запрошенная операция не выполнена: инициатор получает ошибку и при необходимости повторяет операцию.

Параметры exargs:

  • filter — выражение в виде списка, как в параметре filter REST-API коллекции. Событие отправляется, если под фильтр попадает прежнее или новое значение сущности.

  • mask — список полей сущности, передаваемых в уведомлении.

  • operations — список операций, о которых нужно уведомлять; reload, clear, corrupt приходят всегда.

  • content_mode — содержимое уведомления: 0 — без сущности, 1 — текущее значение сущности (по умолчанию), 2 — для update также прежнее значение.

  • index — индексное условие вида ["==",["property","<поле>"],["const",<значение>]]. Действует как фильтр «значение поля равно константе в прежнем или новом значении сущности», с тем же приведением операций к create и delete. Подписки с индексом раскладываются по значению поля, и при изменении сущности нужные подписки находятся поиском по значению, без проверки фильтра каждой подписки. Применяется вместо фильтра, когда на один класс подписано много подписчиков, каждый — на свою часть записей (например, исполнитель — на записи, адресованные ему). Если заданы и index, и filter, событие отправляется при выполнении обоих условий.

Политики доступа для modelevents

  • Подписка разрешена, если у пользователя (для Token-API — у пользователя, привязанного к каналу интеграции, иначе у ролей самого канала) есть право IAM на чтение (GET) элемента или коллекции класса.

  • Для динамической модели к подписке применяются политики безопасности класса: фильтр политики объединяется с фильтром подписки через И, маска политики ограничивает маску подписки. Политики не применяются для пользователей с ролью admin, для классов с отключённой безопасностью и для каналов интеграции без привязки к пользователю.

  • Для коллекций статической модели применяется только проверка прав IAM.

Согласованность данных у нескольких рабочих мест

Все клиенты, подписанные на один класс с одинаковым фильтром, получают одни и те же уведомления, поэтому их представления данных совпадают, если каждый клиент строит своё состояние по правилу «сначала подпишись, затем считай»:

  1. Подписаться на события коллекции с нужным фильтром и накапливать поступающие уведомления.

  2. Прочитать коллекцию через REST-API с тем же фильтром.

  3. Применить накопленные уведомления в порядке поступления, затем применять новые по мере поступления.

  4. При уведомлении reload, а также после восстановления соединения, повторить чтение коллекции; при corrupt — перечитать сущность eid.

  5. Продлевать подписку до истечения срока.

Порядок уведомлений по одной сущности сохраняется. Номер версии в уведомлении не передаётся; для контроля целостности кеша можно сравнивать контрольную сумму коллекции (операция hc в websocket rest) с сохранённой. Дополнительно рекомендуется периодическая полная перечитка с большим интервалом.

Загрузка кеша по всей коллекции

Для начальной загрузки большой коллекции в кеш вместо постраничного чтения используйте snapshot-чтение (параметр snapshot при чтении коллекции, REST-API динамической модели):

  • snapshot=true — выборка целиком одним JSON-документом: через HTTP REST-API — сразу в ответе, через websocket — ссылкой download_url на временный файл, который нужно скачать в течение минуты;

  • snapshot=<идентификатор> — постраничное чтение из зафиксированного снимка идентификаторов, не меняющегося между страницами (для коллекций от 3000 записей).

Snapshot-чтение поддерживают коллекции с хранением в памяти (ets, ram, runtime), а также category с полным кешированием (только snapshot=true).