Кеширование|Логирование|Ретраи на примере SMS

Отредачено 21.02.2025,
а чекнуто 689 💁

🔹 1. Кеширование

Зачем кешировать отправку SMS?

  1. Чтобы не дёргать внешний API слишком часто, если запросы повторяются (например, при повторном нажатии "Отправить код").
  2. Чтобы ускорить работу – достать данные из кеша быстрее, чем отправить HTTP-запрос.
  3. Чтобы избежать лишних трат – API-провайдеры SMS часто берут деньги за каждый запрос.

Как кешировать отправку SMS?

Допустим, мы не хотим отправлять одно и то же SMS чаще, чем раз в 5 минут.

✅ Создадим CacheSmsLibrary, которая оборачивает основную библиотеку и кеширует отправленные SMS:

use Illuminate\Support\Facades\Cache;

class CacheSmsLibrary implements SmsTemplatesLibraryInterface
{
    public function __construct(private SmsTemplatesLibraryInterface $library) {}

    public function sendTemplateById(int $id, string $phone, string $message): SmsResponseDTO
    {
        $cacheKey = "sms_sent_{$phone}_{$id}";
        if (Cache::has($cacheKey)) {
            return new SmsResponseDTO(success: false, message: "SMS недавно отправлено, подождите.", statusCode: 429);
        } // Отправляем SMS через основную библиотеку
        $response = $this->library->sendTemplateById($id, $phone, $message);
        if ($response->success) { // Кешируем на 5 минут
            Cache::put($cacheKey, true, now()->addMinutes(5));
        }
        return $response;
    }
}

📌 Теперь если одно и то же SMS пытаются отправить повторно в течение 5 минут – оно не будет отправлено повторно.

Применение:

app()->bind(SmsTemplatesLibraryInterface::class, function () { return new CacheSmsLibrary(new SmsProviderA()); });

🔹 2. Логирование

Зачем логировать отправку SMS?

  1. Для отладки – видеть, какие сообщения были отправлены.
  2. Для мониторинга – можно анализировать частоту отправок.
  3. Для обработки ошибок – фиксировать неудачные попытки.

Как логировать?

✅ Создадим LoggingSmsLibrary, которая оборачивает основную библиотеку и логирует отправку:

use Illuminate\Support\Facades\Log;

class LoggingSmsLibrary implements SmsTemplatesLibraryInterface
{
    public function __construct(private SmsTemplatesLibraryInterface $library) {}

    public function sendTemplateById(int $id, string $phone, string $message): SmsResponseDTO
    {
        Log::info("Отправка SMS [$id] на номер: $phone, текст: $message");
        $response = $this->library->sendTemplateById($id, $phone, $message);
        if ($response->success) {
            Log::info("SMS отправлено успешно: {$response->message}");
        } else {
            Log::error("Ошибка отправки SMS: {$response->message}");
        }
        return $response;
    }
}

Теперь все отправки SMS логируются, а если что-то пошло не так – мы это увидим в storage/logs/laravel.log.

Применение:

app()->bind(SmsTemplatesLibraryInterface::class, function () { return new LoggingSmsLibrary(new SmsProviderA()); });

👉 Теперь все вызовы SMS отправки будут логироваться.


🔹 3. Ретраи (повторная отправка при неудаче)

Зачем делать ретраи?

  1. Внешний API может временно не работать (например, проблемы с сервером провайдера).
  2. Интернет-соединение может прерываться – если SMS API недоступен, стоит попробовать ещё раз.
  3. Некоторые ошибки можно обработать повторной попыткой (например, если сервер ответил 500 – можно повторить).

Как делать ретраи?

✅ Создадим RetrySmsLibrary, которая повторяет отправку SMS до 3 раз, если произошла ошибка:

use Exception;

class RetrySmsLibrary implements SmsTemplatesLibraryInterface
{
    public function __construct(private SmsTemplatesLibraryInterface $library) {}

    public function sendTemplateById(int $id, string $phone, string $message): SmsResponseDTO
    {
        $attempts = 3;
        $delay    = 2; // секунды между попытками
        for ($i = 0; $i < $attempts; $i++) {
            try {
                $response = $this->library->sendTemplateById($id, $phone, $message);
                if ($response->success) {
                    return $response;
                }
                Log::warning("Ошибка отправки SMS, попытка $i: {$response->message}");
            } catch (Exception $e) {
                Log::error("Исключение при отправке SMS, попытка $i: " . $e->getMessage());
            } // Ждём перед повторной попыткой
            sleep($delay);
        }
        return new SmsResponseDTO(success   : false,
                                  message   : "Не удалось отправить SMS после $attempts попыток",
                                  statusCode: 500
        );
    }
}

📌 Теперь если первый запрос не удался, библиотека попробует ещё 2 раза с задержкой 2 секунды.

Применение:

app()->bind(SmsTemplatesLibraryInterface::class, function () { return new RetrySmsLibrary(new SmsProviderA()); });

💡 Итог: Комбинируем всё вместе!

Теперь можно объединить все улучшения в цепочку классов:

app()->bind(SmsTemplatesLibraryInterface::class, function () { return new CacheSmsLibrary( new LoggingSmsLibrary( new RetrySmsLibrary( new SmsProviderA() ) ) ); });

📌 Теперь у нас:
✅ Кеширование (не отправляем SMS повторно в течение 5 минут).
✅ Логирование (все попытки и ошибки фиксируются).
✅ Ретраи (если SMS не отправилось, пробуем ещё 2 раза).

Вся магия происходит без изменений в коде контроллера и сервиса! 🚀


🎯 Вывод

  1. Кеширование – помогает избежать повторных запросов, уменьшает нагрузку на API.
  2. Логирование – фиксирует отправки и ошибки, помогает при отладке.
  3. Ретраи – позволяют повторять запрос при временных ошибках.

Когда стоит использовать эти техники?

Метод Когда использовать?
Кеширование Если есть вероятность повторных SMS за короткий период (например, OTP-коды)
Логирование Всегда – полезно для мониторинга и отладки
Ретраи Если API SMS-провайдера не всегда стабильно работает

🔥 Лучший вариант – использовать все 3 техники, обернув основную библиотеку в цепочку классов! 🚀

ООП Laravel Оптимизация