Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Service Client Bundle Laravel Package

deeep/service-client-bundle

View on GitHub
Deep Wiki
Context7

Middleware

🌐 Язык: English | Русский

HTTP-клиент использует middleware pipeline для обработки запросов и ответов.

Порядок выполнения

Middleware выполняются в порядке убывания приоритета. Больший приоритет = выполняется раньше.

Request Flow (высокий → низкий приоритет):

┌─────────────────────────────────────────────────────────────────────┐
│                           HTTP Client                               │
└─────────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────────┐
│  [100] AuthMiddleware                                               │
│        Добавляет Authorization header                               │
│        Почему первый: credentials должны быть добавлены до retry    │
└─────────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────────┐
│  [90] RetryMiddleware                                               │
│       Повторяет запрос при ошибках (429, 5xx)                       │
│       Почему здесь: retry должен охватывать logging и circuit       │
└─────────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────────┐
│  [80] LoggingMiddleware                                             │
│       Логирует каждую попытку запроса                               │
│       Почему здесь: логировать каждый retry, но после auth          │
└─────────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────────┐
│  [60] CircuitBreakerMiddleware                                      │
│       Блокирует запросы к "упавшим" сервисам                        │
│       Почему здесь: circuit учитывает каждую попытку retry          │
└─────────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────────┐
│  [10] MultiHostMiddleware                                           │
│       Выбирает хост по стратегии (failover, round-robin, parallel)  │
│       Почему последний: выполняется непосредственно перед transport │
└─────────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────────┐
│                         GuzzleTransport                             │
│                    Отправка HTTP запроса                            │
└─────────────────────────────────────────────────────────────────────┘

Встроенные Middleware

Приоритет Middleware Назначение Включен по умолчанию
100 AuthMiddleware Добавляет заголовки аутентификации Да
90 RetryMiddleware Повторы с экспоненциальной задержкой Да (настраиваемый)
80 LoggingMiddleware Логирование запросов/ответов Да (настраиваемый)
60 CircuitBreakerMiddleware Паттерн Circuit Breaker Нет (по умолчанию)
10 MultiHostMiddleware Обработка стратегий multi-host Автоматически

Почему такой порядок?

AuthMiddleware (100) — первый

Аутентификация должна быть добавлена до любой логики retry. Если auth добавлять после retry, то:

  • Первый запрос пойдёт без credentials
  • Retry будет добавлять auth повторно

RetryMiddleware (90) — после auth

Retry должен охватывать все нижележащие middleware:

  • Логировать каждую попытку
  • Учитывать circuit breaker состояние
  • Пробовать разные хосты при failover

LoggingMiddleware (80) — логирует каждую попытку

Находится внутри retry-цикла, поэтому:

  • Логируется каждая попытка, а не только финальный результат
  • Credentials уже добавлены (не логируются отдельно)

CircuitBreakerMiddleware (60) — защита от каскадных сбоев

Проверяет состояние circuit для каждой попытки:

  • Если circuit открыт — запрос не отправляется
  • Failure/success учитываются в статистике

MultiHostMiddleware (10) — последний

Выбор хоста происходит непосредственно перед отправкой:

  • Failover пробует следующий хост при ошибке
  • Round-robin ротирует между хостами
  • Parallel отправляет на все хосты

Создание кастомного Middleware

Middleware поддерживает как синхронные, так и асинхронные запросы через Promise-based архитектуру (как в Guzzle).

<?php

declare(strict_types=1);

namespace App\Middleware;

use Deeep\ServiceClient\Context\RequestContext;
use Deeep\ServiceClient\Contract\MiddlewareInterface;
use Deeep\ServiceClient\Contract\RequestInterface;
use Deeep\ServiceClient\Contract\ResponseInterface;
use GuzzleHttp\Promise\PromiseInterface;
use Throwable;

final readonly class TimingMiddleware implements MiddlewareInterface
{
    private const int PRIORITY = 85;

    public function __construct(
        private MetricsCollector $metrics,
    ) {}

    public function process(
        RequestInterface $request,
        RequestContext $context,
        callable $next,
    ): ResponseInterface|PromiseInterface {
        $startTime = microtime(true);

        try {
            $result = $next($request, $context);
        } catch (Throwable $e) {
            $this->recordMetrics($request, $startTime, false);
            throw $e;
        }

        if ($result instanceof PromiseInterface) {
            return $result->then(
                function (ResponseInterface $response) use ($request, $startTime): ResponseInterface {
                    $this->recordMetrics($request, $startTime, true);
                    return $response;
                },
                function (Throwable $e) use ($request, $startTime): void {
                    $this->recordMetrics($request, $startTime, false);
                    throw $e;
                },
            );
        }

        $this->recordMetrics($request, $startTime, true);

        return $result;
    }

    public function getPriority(): int
    {
        return self::PRIORITY;
    }

    private function recordMetrics(RequestInterface $request, float $startTime, bool $success): void
    {
        $this->metrics->recordTiming(
            service: $request->getService(),
            duration: microtime(true) - $startTime,
            success: $success,
        );
    }
}

Важно: Promise-based архитектура

  • $next() возвращает ResponseInterface для sync или PromiseInterface для async
  • Для async обработка результата через ->then(onSuccess, onError)
  • Один и тот же middleware работает для синхронных и асинхронных запросов
  • Синхронные исключения ловятся в try-catch, асинхронные — в onError callback

Регистрация Middleware

Автоматическая (рекомендуется)

Реализуйте MiddlewareInterface — Symfony автоматически зарегистрирует middleware:

// src/Middleware/TimingMiddleware.php
// Просто реализуйте MiddlewareInterface - автоконфигурация через instanceof

Ручная регистрация

<?php
// config/services.php

use App\Middleware\TimingMiddleware;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $container): void {
    $services = $container->services();

    $services->set(TimingMiddleware::class)
        ->tag('deeep_service_client.middleware');
};

Рекомендации по приоритетам

Диапазон Назначение Примеры
95-100 Модификация запроса Auth, Request ID, Tenant headers
85-94 Метрики/timing (вне retry) Prometheus metrics, APM
75-89 Внутри retry цикла Логирование, трейсинг
50-74 Защитные паттерны Circuit breaker, rate limiting
1-49 Транспортный уровень Multi-host, caching

Отключение встроенных Middleware

Retry

'services' => [
    'fast_api' => [
        'host' => '...',
        'retry' => [
            'enabled' => false,
        ],
    ],
],

Circuit Breaker

По умолчанию отключен. Для включения:

'services' => [
    'unreliable_api' => [
        'host' => '...',
        'circuit_breaker' => [
            'enabled' => true,
            'failure_threshold' => 5,
            'open_timeout' => 30,
        ],
    ],
],

Logging

'services' => [
    'internal_api' => [
        'host' => '...',
        'logging' => [
            'enabled' => false,
        ],
    ],
],
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle