> Какие виды уведомлений используются в системах (SMS, push, email и т.д.) (Go)

Уровень: senior · Роль: backend · Категория: Технические вопросы

Компании: Моторика

Стек: Go

> Пример ответа

Короткий ответ

В системах используются четыре основных канала уведомлений: email, push, SMS и in-app. Каждый имеет свои trade-off по доставке, стоимости и пользовательскому опыту. Для backend-инженера важно понимать не только сами каналы, но и архитектуру их интеграции: очередь сообщений, шаблонизацию, дедупликацию, retry-механизмы и observability. Выбор канала зависит от срочности, контекста и требований к гарантии доставки.

Подробное объяснение

Каналы уведомлений делятся по двум осям: срочность и гарантия доставки.

  • Email - асинхронный, не срочный, дешёвый. Гарантия доставки низкая (спам-фильтры, неверные адреса). Используется для подтверждений, рассылок, отчётов.
  • Push - доставка через FCM/APNs, требует установленного приложения. Средняя срочность, средняя стоимость. Есть ограничения по размеру payload (до 4KB у FCM). Не гарантирует доставку, если устройство офлайн.
  • SMS - высокая срочность, высокая стоимость, максимальная совместимость (работает на любом телефоне). Используется для OTP, критичных алертов. Требует интеграции с SMS-провайдером (Twilio, Vonage).
  • In-app - уведомления внутри приложения (баннеры, badge, центр уведомлений). Низкая стоимость, высокая кастомизация. Не требует внешних сервисов.

Также существуют Webhook и WebSocket для server-to-server уведомлений, но это скорее интеграционный механизм, чем пользовательский канал.

Архитектурно система уведомлений обычно строится так:

  1. Producer - сервис, генерирующий событие (например, заказ создан).
  2. Очередь (RabbitMQ, Kafka) - буферизация и гарантия at-least-once.
  3. Notification service - определяет канал, шаблон, получателя, приоритет.
  4. Provider adapters - абстракция над внешними API (email-провайдер, FCM, SMS-шлюз).
  5. Delivery tracking - статусы (sent, delivered, failed), retry с exponential backoff.

Ключевые паттерны: fan-out (одно событие → несколько каналов), template rendering (Go text/template), deduplication (по ключу события + получателя), rate limiting (чтобы не спамить).

На практике

Для senior-позиции важно уметь проектировать систему с учётом:

  • Приоритизации каналов - например, OTP всегда через SMS, но fallback на email.
  • Fallback-логики - если push не доставлен за N секунд, отправить SMS.
  • Идемпотентности - повторная отправка не должна создавать дубликаты.
  • Мультитенантности - разные клиенты с разными провайдерами и шаблонами.
  • Мониторинга - метрики по каждому каналу: latency, error rate, conversion rate.

В Go типичная реализация - интерфейс Notifier:

GO
type Notifier interface {
Send(ctx context.Context, msg Message) error
}

Каждый канал реализует этот интерфейс. Композиция через middleware (retry, logging, rate limit) - стандартный подход.

Пример кода

GO
type Message struct {
ID string
UserID string
Channel ChannelType // Email, Push, SMS
Template string
Payload map[string]any
Priority Priority
}
type Notifier interface {
Send(ctx context.Context, msg Message) error
}
type EmailNotifier struct {
client *smtp.Client // или API провайдера
}
func (n *EmailNotifier) Send(ctx context.Context, msg Message) error {
body, err := renderTemplate(msg.Template, msg.Payload)
if err != nil {
return fmt.Errorf("render template: %w", err)
}
// отправка через провайдера
return n.client.Send(msg.UserID, body)
}
// Middleware для retry
func WithRetry(next Notifier, maxRetries int) Notifier {
return NotifierFunc(func(ctx context.Context, msg Message) error {
var err error
for i := 0; i < maxRetries; i++ {
err = next.Send(ctx, msg)
if err == nil {
return nil
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(time.Duration(1<<i) * time.Second):
}
}
return err
})
}

Как отвечать на собеседовании

Начни с классификации каналов, затем переходи к архитектуре. Покажи, что понимаешь trade-off: SMS дорого, но надёжно; push дёшево, но зависит от платформы. Упомяни, что выбор канала определяется бизнес-требованиями, а не техническими предпочтениями.

Обязательно затронь:

  • Как обрабатываешь отказы провайдеров (retry, circuit breaker).
  • Как обеспечиваешь идемпотентность (ключ = eventID + userID + channel).
  • Как тестируешь интеграции (mock провайдеров, contract tests).
  • Как масштабируешь (очередь, worker pool, sharding по userID).

Если спросят про конкретный кейс - например, "уведомление о бронировании" - предложи схему: событие → очередь → notification service → параллельная отправка в email и push, с fallback на SMS при high priority.

Что проверяет интервьюер

  • Понимание асинхронной архитектуры и роли очередей.
  • Умение проектировать отказоустойчивые системы (retry, dead letter queue).
  • Знание внешних интеграций и их ограничений (rate limits, payload size).
  • Способность абстрагироваться от конкретных провайдеров.
  • Навыки идиоматичного Go (интерфейсы, middleware, context).

Типичные ошибки

  • Смешивание каналов и провайдеров: "SMS - это Twilio" - нет, SMS это канал, Twilio это провайдер.
  • Игнорирование идемпотентности - повторная отправка при retry создаёт дубликаты.
  • Отправка уведомлений синхронно в HTTP-хендлере - блокирует запрос, теряет сообщения при сбое.
  • Отсутствие fallback-логики - если email-провайдер лежит, пользователь не получает уведомление.
  • Не учитывать rate limits провайдеров - можно получить бан за массовую рассылку.
  • Хранение шаблонов в коде - должно быть в конфиге или БД, чтобы менять без деплоя.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью