0Pricing
PHP Academy · Урок

Шлюзы API и обнаружение сервисов

Динамически маршрутизируйте запросы, объединяйте данные и находите сервисы

«Шлюзы API и обнаружение сервисов» — бесплатный урок PHP Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения PHP Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс PHP Academy содержит 4 уроков всего.

Шлюзы и обнаружение

Когда у Вас появляется с десяток сервисов, возникают две проблемы. Клиентам не нужно знать адрес каждого сервиса или вызывать пять сервисов, чтобы отобразить один экран, — это задача шлюза API. А сервисам, которые масштабируются и меняют IP-адреса, нужен способ находить друг друга — для этого служит обнаружение сервисов.

В этом уроке рассматриваются оба вопроса, а на границе шлюза используется PHP.

Что делает шлюз API

Шлюз API — это единая точка входа перед Вашими сервисами. Типичные задачи:

  • Маршрутизация запросов к нужной серверной части.
  • Сквозные задачи: аутентификация, ограничение частоты запросов, CORS, завершение TLS.
  • Агрегация: объединение нескольких вызовов серверной части в один ответ для клиента.
  • Преобразование протоколов: на входе внешний REST, на выходе внутренний gRPC.

Шлюз упрощает клиентов и централизует политики, которые иначе пришлось бы дублировать в каждом сервисе.

Маршрутизация на границе

В основе шлюз сопоставляет входящий путь с вышестоящим сервисом. Промышленные шлюзы (Kong, Traefik, Nginx, AWS API Gateway) настраиваются декларативно, но эту логику достаточно просто показать на PHP.

<?php
$routes = [
    '#^/api/orders#'    => 'http://orders-svc',
    '#^/api/customers#' => 'http://customers-svc',
    '#^/api/catalog#'   => 'http://catalog-svc',
];

function resolveUpstream(string $path, array $routes): ?string {
    foreach ($routes as $pattern => $upstream) {
        if (preg_match($pattern, $path)) {
            return $upstream . $path;
        }
    }
    return null; // 404 at the gateway
}

echo resolveUpstream('/api/orders/42', $routes), "\n";

Централизация аутентификации

Проверяйте вызывающую сторону один раз на шлюзе, а затем передавайте доверенную идентичность дальше, чтобы каждому сервису не приходилось повторно проверять исходный токен. Шлюз проверяет подпись и срок действия JWT и добавляет в заголовки внутреннего запроса такие значения, как X-User-Id (в доверенной сети).

<?php
function authenticate(string $authHeader): ?array {
    if (!str_starts_with($authHeader, 'Bearer ')) return null;
    $jwt = substr($authHeader, 7);
    $claims = verifyJwt($jwt);            // signature + exp check
    if ($claims === null) return null;
    // Forward minimal trusted identity to internal services
    return ['X-User-Id' => $claims['sub'], 'X-Scopes' => implode(',', $claims['scopes'])];
}
function verifyJwt(string $j): ?array { return ['sub' => 'u-7', 'scopes' => ['orders:read']]; }
print_r(authenticate('Bearer abc.def.ghi'));

Агрегация ответов

Для одного экрана мобильного приложения могут потребоваться данные о заказе, клиенте и товарах. Вместо того чтобы заставлять клиента выполнять три вызова, шлюз отправляет запросы одновременно, ждёт ответы и объединяет их. Чтобы сохранить высокую скорость, выполняйте вызовы вышестоящих сервисов параллельно (обещания Guzzle / curl_multi), а не последовательно.

<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Promise\Utils;

$http = new Client(['timeout' => 2.0]);
$promises = [
    'order'    => $http->getAsync('http://orders-svc/orders/42'),
    'customer' => $http->getAsync('http://customers-svc/customers/7'),
    'catalog'  => $http->getAsync('http://catalog-svc/items?order=42'),
];
$results = Utils::settle($promises)->wait(); // run in parallel
// merge the fulfilled bodies into one response for the client

Серверная часть для разных клиентов

Один универсальный шлюз часто не может одинаково хорошо обслуживать веб-приложение, мобильное приложение и партнёров — каждому нужны свои варианты агрегации и формы данных. Паттерн серверной части для клиента (BFF) предоставляет каждому типу клиента собственный тонкий шлюз, настроенный под его потребности, а общие сервисы остаются универсальными.

Это позволяет избежать перегруженного шлюза, который пытается делать всё, и даёт каждой команде клиента возможность работать независимо.

Проблема обнаружения

В динамической среде экземпляры сервисов появляются и исчезают, а их IP-адреса меняются. Жёстко заданный адрес http://10.0.3.14:8080 ненадёжен. Обнаружение сервисов поддерживает актуальный реестр, в котором указано, «какие работоспособные экземпляры сервиса X существуют прямо сейчас», чтобы вызывающая сторона могла во время вызова сопоставить логическое имя с реальным адресом.

Обнаружение на стороне клиента и на стороне сервера

Есть две модели:

  • На стороне клиента — вызывающая сторона запрашивает реестр (Consul, etcd), сама выбирает экземпляр и выполняет балансировку нагрузки.
  • На стороне сервера — вызывающая сторона обращается к стабильному виртуальному адресу (балансировщику нагрузки или сервису Kubernetes), который сам определяет экземпляр и распределяет нагрузку.

В Kubernetes обнаружение на стороне сервера обычно доступно сразу: вызовите http://customers-svc, а кластерный DNS и сервис сделают остальное. За пределами Kubernetes часто используются реестры в стиле Consul.

Запрос к реестру

При обнаружении на стороне клиента вызывающая сторона на PHP запрашивает у реестра работоспособные экземпляры и выбирает один из них. Реестр возвращает только экземпляры, прошедшие проверки работоспособности, поэтому неработающие узлы автоматически исключаются.

<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;

function discover(Client $http, string $service): string {
    // Consul: only passing-health instances
    $res = $http->get("http://consul:8500/v1/health/service/$service?passing=true");
    $nodes = json_decode((string) $res->getBody(), true);
    if (!$nodes) throw new \RuntimeException("No healthy $service");
    $pick = $nodes[array_rand($nodes)]['Service']; // simple LB
    return "http://{$pick['Address']}:{$pick['Port']}";
}

Проверки работоспособности и регистрация

Качество обнаружения зависит от качества данных о работоспособности. Каждый сервис предоставляет конечную точку /health, которая проверяет его реальные зависимости (базу данных, кэш), и регистрирует себя при запуске (или регистрируется платформой). Реестр проверяет эту конечную точку и удаляет экземпляры, не прошедшие проверку.

Проверка работоспособности должна быть содержательной: ответ 200 при недоступной базе данных хуже, чем отсутствие ответа, — он направляет трафик на неисправный узел.

<?php
// GET /health
function health(PDO $db, Redis $cache): array {
    $checks = [
        'db'    => safe(fn() => $db->query('SELECT 1') !== false),
        'cache' => safe(fn() => $cache->ping() === '+PONG'),
    ];
    $ok = !in_array(false, $checks, true);
    http_response_code($ok ? 200 : 503);
    return ['status' => $ok ? 'pass' : 'fail', 'checks' => $checks];
}
function safe(callable $c): bool { try { return (bool) $c(); } catch (\Throwable) { return false; } }

Проверка живости и готовности

Одной конечной точки проверки недостаточно — нужно различать два вопроса:

  • Живость: «процесс работает?» Если проверка завершается ошибкой, оркестратор перезапускает контейнер. Проверка должна быть дешёвой и не зависеть от внешних компонентов, иначе нестабильная база данных вызовет бессмысленные перезапуски.
  • Готовность: «может ли сервис прямо сейчас обслуживать трафик?» Если проверка завершается ошибкой, трафик не направляется к сервису, но процесс продолжает работать (например, прогревает кэш или временно не может подключиться к базе данных).

Смешение этих проверок приводит к циклам перезапуска или направлению трафика на ещё не готовые узлы.

<?php
// GET /livez  - is the process itself healthy? (no external deps)
function livez(): void { http_response_code(200); echo 'alive'; }

// GET /readyz - should we receive traffic? (checks dependencies)
function readyz(PDO $db): void {
    try { $db->query('SELECT 1'); http_response_code(200); echo 'ready'; }
    catch (\Throwable) { http_response_code(503); echo 'not ready'; }
}

Быстрая проверка

Сокращение числа обращений клиента.

Итоги

Маршрутизация и поиск сервисов:

  • Шлюз API централизует маршрутизацию, аутентификацию, ограничение частоты запросов, TLS и агрегацию.
  • Выполняйте агрегацию параллельно; используйте BFF, когда потребности клиентов различаются.
  • Обнаружение сервисов сопоставляет логические имена с актуальными работоспособными экземплярами.
  • Обнаружение на стороне клиента (запрос к реестру) отличается от обнаружения на стороне сервера (стабильный балансировщик нагрузки или сервис Kubernetes).
  • Содержательные проверки работоспособности не позволяют направлять трафик на неисправные узлы.

Далее: как сделать все эти вызовы устойчивыми к неизбежным сбоям отдельных компонентов.

Часто задаваемые вопросы

Урок «Шлюзы API и обнаружение сервисов» бесплатный?

Да — полный текст урока «Шлюзы API и обнаружение сервисов» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс PHP Academy, подпишись на CoddyKit PRO. Курс PHP Academy содержит 4 уроков всего.

Чему я научусь в уроке «Шлюзы API и обнаружение сервисов»?

Динамически маршрутизируйте запросы, объединяйте данные и находите сервисы Ты практикуешь PHP Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать PHP Academy?

Предыдущий опыт не требуется. PHP Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Шлюзы API и обнаружение сервисов»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке PHP Academy?

Да. Каждый урок PHP Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. От монолита к микросервисам
  2. Взаимодействие сервисов: REST и gRPC
  3. Шлюзы API и обнаружение сервисов
  4. Отказоустойчивость: размыкатели и повторные попытки
← Назад к PHP Academy