Перейти к основному содержимому

Entity / Aggregate: Channel (enum) + ChannelConfigEntity

Дом канала доставки контекста notifications (greenfield): перечисление Channel и платформенная конфигурация ChannelConfigEntity (привязка провайдера, лимиты, capability). Проекции (notification.md, notification-template.md, provider-адаптеры ../providers/) ссылаются сюда.

Назначение

Channel — конечное множество каналов доставки. ChannelConfigEntity — платформенная настройка одного канала: какой провайдер используется (../providers/), включён ли канал (feature-флаг), лимиты повторов и rate-limit, capability (заголовок/вложение/HTML/деплинк). Dispatcher по Notification.Channel резолвит ChannelConfigEntity (Code = Channel) и вызывает соответствующий INotificationChannelProvider. Внешние каналы по умолчанию выключены (IsEnabled = false) — провайдеры-заглушки, реализация отложена; in-app включён и реально доставляется через Talent.Common.Realtime.*.

Классификация

ПолеЗначение
Контекстnotifications
KindEntity (справочник; не aggregate root — правится admin/SEED)
Базовые контракты SDKIEntity (Guid)
Source of truthnotifications
Таблицаnotifications.channel_configs
Tenant isolationне tenanted — платформенный справочник (ADR-0052 п.3)

Реализуемые capability-интерфейсы (Talent.Common.Primitives.Entity)

ИнтерфейсПрименяетсяКомментарий
IEntity (Guid)ДаId = Guid.CreateVersion7().
ICodeEntityДаCode = Channel.ToString() — уникальный ключ (один config на канал).
IEntityWithStatusНетвкл/выкл — булев IsEnabled, без статус-машины.
IAuditableEntity / IModifiedEntity / IVersionedEntity / IOptimisticBlockedEntityДаActor-stamping, ревизия, xmin.
ITenantedEntity/INullableTenantedEntity / ISoftDeleteEntity / IDocumentНетПлатформенный, фиксированный набор строк (по числу каналов).

Enum Channel

ЗначениеОписаниеПровайдер (адаптер)Реализация
InAppВнутренние уведомления (лента-колокольчик), realtime../providers/in-app.md (Talent.Common.Realtime.*)реальная
EmailЭлектронная почта (subject + html/text)../providers/email.mdзаглушка (отложено)
SmsСМС (короткий текст, без заголовка)../providers/sms.mdзаглушка (отложено)
PushМобильный/web push (title + body + deeplink)../providers/push.mdзаглушка (отложено)
TelegramTelegram-бот (chat-id)../providers/telegram.mdзаглушка (отложено)
MaxМессенджер MAX (VK)../providers/max.mdзаглушка (отложено)
WhatsAppWhatsApp Business../providers/whatsapp.mdзаглушка (отложено)
ViberViber Business../providers/viber.mdзаглушка (отложено)

Channel — «interruptive», если может прервать в тихие часы: Sms/Push/Telegram/Max/WhatsApp/Viber; InApp/Email — не-interruptive (тихие часы не дефер, notification.md INV-NTF-4).

Поля ChannelConfigEntity

ПолеТипОбязательностьОграничения (PK/FK/index/constraint)ИсточникОписание
IdGuidДаPKappИдентификатор config.
ChannelChannelДа.HasConversion<string>()appКанал.
CodestringДаunique ux_channel_configs_code = ChannelappКлюч резолва (ICodeEntity).
IsEnabledboolДаdefault false (InApptrue)app/flagВключён ли канал (feature-флаг).
ProviderKeystringДаvarchar(48)appКлюч адаптера (in-app/email-smtp/sms-*/…).
IsInterruptiveboolДаappУчитывать тихие часы (см. выше).
SupportsTitleboolДаappКанал поддерживает заголовок.
SupportsDeepLinkboolДаappКанал поддерживает диплинк.
MaxBodyLengthint?Нет> 0appЛимит тела (sms — напр. 480); null — без лимита.
MaxAttemptsintДаdefault 5, 1..10appПорог retry (notification.md INV-NTF-7).
RetryBackoffRetryBackoffPolicy (owned)Дасм. VOappПолитика backoff повторов.
RateLimitPerMinuteint?Нет> 0appЛокальный throttle (уважать лимит провайдера).
CreatedAt/UpdatedAtDateTimeOffsetДаappТайминги.
VersionlongДа>= 1appIVersionedEntity.
RowVersionuintДаconcurrency (xmin)dbIOptimisticBlockedEntity.

Value objects / EF owned types

RetryBackoffPolicy (owned)

Типизированная политика повторов (строгая типизация — не bare-json).

ПолеТипОбязательностьОграниченияКолонка
StrategyBackoffStrategyДаFixed/Exponential (.HasConversion<string>())backoff_strategy
InitialDelaySecondsintДа>= 1backoff_initial_seconds
MaxDelaySecondsintДа>= InitialDelaySecondsbackoff_max_seconds

Модели JSONB (IDocument, versioned)

JSONB-модели не используются: конфигурация — плоские типизированные колонки + owned RetryBackoffPolicy. Секреты провайдеров (api-key/токены) не хранятся здесь — в secret-store (провайдер-адаптер, ../providers/, «Безопасность»).

Связи

СвязьТипКлючПравило загрузкиDelete behavior
NotificationEntityreference (обратная)Notification.ChannelCodeFindBy(Code = Channel) при диспатчеrestrict
provider-адаптервнешняяProviderKeyDI-резолв INotificationChannelProvider

Инварианты и переходы состояний

ИнвариантУсловиеГде проверяетсяОшибка
INV-CHN-1Один ChannelConfigEntity на канал (Code = Channel уникален); строки SEED-ятся мигратором по всем значениям Channelunique + SEEDNTF_CHANNEL_DUPLICATE
INV-CHN-2IsEnabled = false → канал исключается из кандидатов на enqueue (уведомление по нему не создаётся, notification.md «Резолв каналов»). Если канал выключили после enqueue — уже созданные Pending не удаляются: dispatcher их не берёт до повторного включенияconsumer (enqueue) + dispatcher— (skip канала)
INV-CHN-3MaxBodyLength задан → RenderedBody усекается с многоточием при рендере (лог truncated)рендер
INV-CHN-4MaxAttempts/RetryBackoff — источник порога и backoff для retry-worker (значения читаются на момент попытки)retry-worker
INV-CHN-5Capability-деградация: SupportsTitle = falseRenderedTitle не доставляется (sms); SupportsDeepLink = falseDeepLink в сообщение не включается. Рендер и адаптер уважают возможности каналарендер + provider-адаптер

Статус-машины нет: канал управляется булевым IsEnabled (включение по мере интеграции провайдера).

Конкурентность конфигурации. Правку ChannelConfigEntity (ops/admin) и чтение конфигурации dispatcher-ом/ retry-worker-ом разводит оптимистичная блокировка версии записи: диспатч читает актуальную строку config на каждую отправку, поэтому включение/выключение канала и смена лимитов подхватываются без рестарта; проигравшая параллельная правка повторяется на свежей версии.

Доменные и интеграционные события

Событий не публикует — платформенный справочник (изменение IsEnabled/лимитов правит admin/ops, влияет на поведение dispatcher-а через чтение config).

Индексы, хранение, безопасность

АспектЗначение
ИндексыPK; ux_channel_configs_code unique
Партиционирование / retentionnone (число строк = число каналов)
ConcurrencyRowVersion (xmin); Version — ревизия
Permissions / PIIправка — ops/admin (notifications.templates.manage или отдельный notifications.channels.manage); PII нет; секреты провайдеров — вне таблицы
AuditIAuditableEntity + Version

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы