Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Senior: питання на співбесіді з теми «Сповіщення»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

2 питання

Вбудованих каналів чотири - mail, database, broadcast, vonage. Усе інше - Slack, Telegram, SMS українського провайдера, пуші - або пакет, або власний канал.

Канал - це клас з одним методом:

class TelegramChannel
{
    public function __construct(private TelegramClient $client)
    {
    }

    public function send(object $notifiable, Notification $notification): void
    {
        $chatId = $notifiable->routeNotificationFor('telegram');

        if ($chatId === null) {
            return;
        }

        $this->client->sendMessage($chatId, $notification->toTelegram($notifiable));
    }
}

Підключення в сповіщенні:

class VacancyPublished extends Notification
{
    public function via(object $notifiable): array
    {
        return [TelegramChannel::class, 'mail'];
    }

    public function toTelegram(object $notifiable): string
    {
        return "Нова вакансія: {$this->vacancy->title}";
    }
}

Куди слати - вирішує сам отримувач:

class User extends Authenticatable
{
    public function routeNotificationForTelegram(): ?string
    {
        return $this->telegram_chat_id;
    }
}

Навіщо це, а не просто виклик API. Сповіщення дає те, чого ручний виклик не має: один клас описує повідомлення для всіх каналів одразу, via() вирішує канали за налаштуваннями користувача, ShouldQueue відправляє асинхронно, а Notification::fake() дозволяє перевірити відправлення в тесті без мережі.

Докладніше в документації: Сповіщення

Сповіщення з інтерфейсом ShouldQueue надсилаються у фоні. Важлива деталь реалізації: для кожного отримувача і кожного каналу Laravel ставить в чергу окреме завдання.

Сповіщення з каналами ['mail', 'database', 'slack'] для 1000 користувачів - це 3000 завдань.

Наслідки:

  • канали незалежні: якщо Slack недоступний, пошта й запис у базі все одно пройдуть. Повторюватиметься лише завдання Slack;
  • порядок не гарантований: запис у базі може з'явитися раніше чи пізніше за лист;
  • масові сповіщення створюють сплеск завдань - варто винести їх в окрему чергу.

Різні черги й підключення для каналів:

public function viaQueues(): array
{
    return [
        'mail' => 'mail',
        'slack' => 'notifications-slow',
        'database' => 'default',
    ];
}

Так повільний чи нестабільний канал не затримує швидкі. Аналогічно viaConnections() для різних підключень черги, а withDelay() - для різних затримок по каналах.

Остаточна перевірка у воркері - shouldSend:

public function shouldSend(object $notifiable, string $channel): bool
{
    return $this->invoice->fresh()->isUnpaid();
}

Між постановкою в чергу й обробкою можуть минути хвилини (при затримці - години). Нагадування про неоплачений рахунок, який уже оплатили, - типовий баг. shouldSend викликається у воркері окремо для кожного каналу, і false скасовує надсилання.

Транзакції - afterCommit:

$user->notify((new InvoicePaid($invoice))->afterCommit());

Без нього воркер може обробити сповіщення до коміту і не знайти запис або надіслати сповіщення про те, що потім відкотилося.

Збої:

  • метод failed(Throwable $e) на класі сповіщення викликається, коли завдання вичерпало спроби;
  • $tries, backoff(), retryUntil() і middleware завдань (RateLimited для API месенджерів) працюють як у звичайних завданнях;
  • події NotificationSending (можна скасувати, повернувши false), NotificationSent і NotificationFailed - для аудиту й метрик.

Ідемпотентність: при повторі після тайм-ауту лист чи SMS може піти двічі - провайдер прийняв повідомлення, але відповідь не дійшла. Для критичних каналів зберігайте ID сповіщення (він спільний для всіх каналів одного надсилання) і перевіряйте, чи його вже доставлено.

Докладніше в документації: Сповіщення: черги