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 |
| Kind | Entity (справочник; не aggregate root — правится admin/SEED) |
| Базовые контракты SDK | IEntity (Guid) |
| Source of truth | notifications |
| Таблица | 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 | заглушка (отложено) |
Telegram | Telegram-бот (chat-id) | ../providers/telegram.md | заглушка (отложено) |
Max | Мессенджер MAX (VK) | ../providers/max.md | заглушка (отложено) |
WhatsApp | WhatsApp Business | ../providers/whatsapp.md | заглушка (отложено) |
Viber | Viber 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) | Источник | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | PK | app | Идентификатор config. |
Channel | Channel | Да | .HasConversion<string>() | app | Канал. |
Code | string | Да | unique ux_channel_configs_code = Channel | app | Ключ резолва (ICodeEntity). |
IsEnabled | bool | Да | default false (InApp — true) | app/flag | Включён ли канал (feature-флаг). |
ProviderKey | string | Да | varchar(48) | app | Ключ адаптера (in-app/email-smtp/sms-*/…). |
IsInterruptive | bool | Да | — | app | Учитывать тихие часы (см. выше). |
SupportsTitle | bool | Да | — | app | Канал поддерживает заголовок. |
SupportsDeepLink | bool | Да | — | app | Канал поддерживает диплинк. |
MaxBodyLength | int? | Нет | > 0 | app | Лимит тела (sms — напр. 480); null — без лимита. |
MaxAttempts | int | Да | default 5, 1..10 | app | Порог retry (notification.md INV-NTF-7). |
RetryBackoff | RetryBackoffPolicy (owned) | Да | см. VO | app | Политика backoff повторов. |
RateLimitPerMinute | int? | Нет | > 0 | app | Локальный throttle (уважать лимит провайдера). |
CreatedAt/UpdatedAt | DateTimeOffset | Да | — | app | Тайминги. |
Version | long | Да | >= 1 | app | IVersionedEntity. |
RowVersion | uint | Да | concurrency (xmin) | db | IOptimisticBlockedEntity. |
Value objects / EF owned types
RetryBackoffPolicy (owned)
Типизированная политика повторов (строгая типизация — не bare-json).
| Поле | Тип | Обязательность | Ограничения | Колонка |
|---|---|---|---|---|
Strategy | BackoffStrategy | Да | Fixed/Exponential (.HasConversion<string>()) | backoff_strategy |
InitialDelaySeconds | int | Да | >= 1 | backoff_initial_seconds |
MaxDelaySeconds | int | Да | >= InitialDelaySeconds | backoff_max_seconds |
Модели JSONB (IDocument, versioned)
JSONB-модели не используются: конфигурация — плоские типизированные колонки + owned RetryBackoffPolicy.
Секреты провайдеров (api-key/токены) не хранятся здесь — в secret-store (провайдер-адаптер,
../providers/, «Безопасность»).
Связи
| Связь | Тип | Ключ | Правило загрузки | Delete behavior |
|---|---|---|---|---|
NotificationEntity | reference (обратная) | Notification.Channel → Code | FindBy(Code = Channel) при диспатче | restrict |
| provider-адаптер | внешняя | ProviderKey | DI-резолв INotificationChannelProvider | — |
Инварианты и переходы состояний
| Инвариант | Условие | Где проверяется | Ошибка |
|---|---|---|---|
| INV-CHN-1 | Один ChannelConfigEntity на канал (Code = Channel уникален); строки SEED-ятся мигратором по всем значениям Channel | unique + SEED | NTF_CHANNEL_DUPLICATE |
| INV-CHN-2 | IsEnabled = false → канал исключается из кандидатов на enqueue (уведомление по нему не создаётся, notification.md «Резолв каналов»). Если канал выключили после enqueue — уже созданные Pending не удаляются: dispatcher их не берёт до повторного включения | consumer (enqueue) + dispatcher | — (skip канала) |
| INV-CHN-3 | MaxBodyLength задан → RenderedBody усекается с многоточием при рендере (лог truncated) | рендер | — |
| INV-CHN-4 | MaxAttempts/RetryBackoff — источник порога и backoff для retry-worker (значения читаются на момент попытки) | retry-worker | — |
| INV-CHN-5 | Capability-деградация: SupportsTitle = false → RenderedTitle не доставляется (sms); SupportsDeepLink = false → DeepLink в сообщение не включается. Рендер и адаптер уважают возможности канала | рендер + provider-адаптер | — |
Статус-машины нет: канал управляется булевым IsEnabled (включение по мере интеграции провайдера).
Конкурентность конфигурации. Правку ChannelConfigEntity (ops/admin) и чтение конфигурации dispatcher-ом/
retry-worker-ом разводит оптимистичная блокировка версии записи: диспатч читает актуальную строку config на
каждую отправку, поэтому включение/выключение канала и смена лимитов подхватываются без рестарта; проигравшая
параллельная правка повторяется на свежей версии.
Доменные и интеграционные события
Событий не публикует — платформенный справочник (изменение IsEnabled/лимитов правит admin/ops, влияет на
поведение dispatcher-а через чтение config).
Индексы, хранение, безопасность
| Аспект | Значение |
|---|---|
| Индексы | PK; ux_channel_configs_code unique |
| Партиционирование / retention | none (число строк = число каналов) |
| Concurrency | RowVersion (xmin); Version — ревизия |
| Permissions / PII | правка — ops/admin (notifications.templates.manage или отдельный notifications.channels.manage); PII нет; секреты провайдеров — вне таблицы |
| Audit | IAuditableEntity + Version |
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (3): Entity / Aggregate: NotificationEntity, Entity / Aggregate: NotificationTemplateEntity, Entity / Aggregate: UserNotificationSettingsEntity
- API (1): PUT /api/public/notifications/settings — Обновить мои настройки уведомлений
- Use Cases (2): UC-NTF-001. Пользователь настраивает уведомления, UC-NTF-003. Сбой провайдера → повтор доставки
- Фоновые задачи (1): Background Job: NotificationRetryWorker
- index.md (1): Notifications Service
- providers (8): Provider Adapter: Email Messaging (заглушка), Provider Adapter: In-App (Talent.Common.Realtime) Messaging, Provider Adapter: MAX Messaging (заглушка), Provider Adapter: Push Messaging (заглушка), Provider Adapter: SMS Messaging (заглушка), Provider Adapter: Telegram Messaging (заглушка), Provider Adapter: Viber Messaging (заглушка), Provider Adapter: WhatsApp Messaging (заглушка)
- registries (2): Реестр инвариантов PIN, Реестр сущностей PIN
Связанные документы
- notification.md (
Channel, диспатч, retry), notification-template.md (ChannelTemplateEntity— тело по каналам), провайдеры — ../providers/ (по одному адаптеру на канал). - README контекста; tenancy — ADR-0052, realtime SDK — 11-backend-sdk.
- Правила: entity-documentation-rules, strict-typed-documentation-rules.