API gateway: PHP, Node, Go, Rust — gRPC и RabbitMQ

В микросервисной архитектуре под «API gateway» чаще всего имеют в виду публичный периметр: TLS, маршрутизация, аутентификация, лимиты и иногда BFF (backend for frontend), который собирает ответ под веб или мобильное приложение. За этим слоем сервисы общаются иначе — часто через gRPC (синхронный RPC) или брокеры сообщений вроде RabbitMQ (асинхронная доставка). Ни один вариант не является «единственно верным стеком»: различаются стоимость эксплуатации, навыки команды и сценарии отказов.

Связанные материалы: PHP на сервере — FPM, Swoole, воркеры · Sail: RabbitMQ и очереди

Содержание


Что на самом деле делает шлюз

Типичные обязанности:

  1. Вход — HTTP/HTTPS из интернета; HTTP/3 часто заканчивается на балансировщике.
  2. Политики — проверка JWT, API-ключи, списки IP, интеграция с WAF.
  3. Форма трафика — rate limiting, лимиты размера тела, таймауты.
  4. Маршрутизация — префиксы путей к кластерам (/billing/* → billing).
  5. Агрегация (по желанию) — BFF дергает несколько бэкендов и отдаёт один JSON.

Пункты (1)–(5) можно реализовать в прикладном коде (PHP, Node, Go, Rust) или отдать Envoy, Traefik, Kong, NGINX или облачному API gateway, оставив в языке только BFF. В проде часто смесь: nginx снимает TLS, Kong вешает плагины, небольшой сервис на Go или PHP добавляет доменную авторизацию.


PHP как слой шлюза

Когда уместно

  • Команда уже ведёт Laravel или Symfony; хочется одного репозитория для публичного HTTP и части оркестрации.
  • Шлюз — это не «тупой прокси на миллионах RPS», а место для сессий, OAuth, HTML-ошибок или серверных фрагментов UI.
  • Устраивает модель FPM на запрос (или Octane) и горизонтальное масштабирование за балансировщиком.

Плюсы

  • Быстрые фичи: авторизация, валидация, переводы, бизнес-правила.
  • Экосистема: HTTP-клиенты, OpenAPI, очереди для побочных эффектов.
  • Проще найти людей и проводить ревью, чем при полностью полиглотном периметре.

Минусы

  • У FPM дороже «старт» запроса, чем у крошечного Go-бинарника (лечится Opcache, preload, аккуратным автолоадом).
  • Легко положить пул воркеров тяжёлыми синхронными вызовами в middleware.
  • Массовый WebSocket с одной машины может увести в Swoole/Octane или отдельный прокси.

Мини-рецепт (в духе Laravel)

Группы маршрутов с throttle и auth; наружу — HTTP-клиент:

<?php

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;

Route::middleware(['throttle:api', 'auth:sanctum'])->prefix('v1')->group(function () {
    Route::get('/orders/{id}', function (string $id) {
        $response = Http::timeout(3)
            ->withHeaders(['X-Internal-Token' => config('services.billing.token')])
            ->get(config('services.billing.url')."/orders/{$id}");

        abort_unless($response->successful(), $response->status());

        return $response->json();
    });
});

Память и стабильность: относитесь к шлюзу как к высоконагруженному PHP — не кешируйте без границ данные пользователя в static, везде задавайте таймауты исходящим вызовам, используйте pm.max_requests (FPM) или перезапуск воркеров (Octane), если под нагрузкой «ползёт» RSS.


Node.js на периметре

Когда уместно

  • Нужен тонкий BFF с кучей параллельных HTTP к апстримам.
  • В шлюз лезут фронтендеры; JSON и SSR-инструменты привычны.
  • Нужна огромная экосистема npm (OpenTelemetry, GraphQL, WebSocket).

Плюсы

  • Естественная модель для множества параллельных исходящих HTTP на async/await.
  • Очень быстрый цикл для сборки API и прототипов.

Минусы

  • Нужна дисциплина: блокировка event loop тяжёлым CPU или синхронным I/O бьёт по всем запросам.
  • Дерево зависимостей и риски supply-chain без пинов и аудита.
  • Нативные аддоны и апгрейды рантайма добавляют операционную поверхность.

Мини-рецепт (набросок Fastify)

npm init -y
npm install fastify @fastify/http-proxy
import Fastify from 'fastify';
import proxy from '@fastify/http-proxy';

const app = Fastify({ logger: true });

app.register(proxy, {
  upstream: 'http://billing.internal',
  prefix: '/billing',
  rewritePrefix: '/v1',
});

await app.listen({ port: 3000, host: '0.0.0.0' });

Go для шлюзов и сайдкаров

Когда уместно

  • Нужен один статический бинарник, предсказуемый GC, простой образ для Linux.
  • Периметр или внутренний слой говорит на gRPC или реализует свою балансировку.
  • Платформенная команда стандартизирует библиотеки.

Плюсы

  • Сильные примитивы конкурентности для I/O-bound шлюзов.
  • Культура наблюдаемости (pprof, OpenTelemetry).
  • Зрелые grpc-go и grpc-gateway (HTTP JSON → gRPC).

Минусы

  • Не всем нравятся шаблоны ошибок и многословность по сравнению с PHP/Python.
  • Codegen из protobuf добавляет шаги в CI.

Мини-рецепт (grpcurl)

go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
grpcurl -plaintext localhost:50051 list

Rust, когда важны микросекунды

Когда уместно

  • Жёсткие бюджеты задержки, важны аллокации или парсинг в критичном для безопасности месте.
  • Готовы платить временем компиляции и строгим borrow checker.

Плюсы

  • Предсказуемая производительность и безопасность памяти без истории с GC-паузами.
  • Tonic (gRPC) и Axum (HTTP) — распространённый выбор в новых проектах.

Минусы

  • Онбординг дороже, чем у PHP или Node для типичной веб-команды.
  • Медленнее итерации, если каждый деплой ждёт полной пересборки в CI.

Частый компромисс: Rust или Go для одного сервиса политик/аутентификации, PHP — для основного CRUD.


Внутренние вызовы: gRPC

gRPC обычно означает HTTP/2, контракты Protobuf и сгенерированные заглушки в каждом языке.

Плюсы

  • Жёсткий контракт — поля и типы явны; ломающие изменения видны на этапе codegen.
  • Компактнее JSON на проводе; есть стриминг для больших данных.
  • Метаданные для трейсинга и авторизации на каждый вызов.

Минусы

  • Браузер не говорит «чистый» gRPC — нужен gRPC-Web и прокси.
  • Дебажить сложнее, чем curl к JSON, без grpcurl и хороших логов/метрик.
  • Балансировщики должны корректно маршрутизировать HTTP/2 к gRPC.

PHP: официальное или community-расширение gRPC + сгенерированные классы; явно задавайте таймауты и лимиты ретраев. Для новых внутренних API договоритесь о deadline (grpc-timeout) между сервисами.


Внутренняя работа: RabbitMQ

RabbitMQ — брокер AMQP: издатели шлют в exchange, очереди связываются ключами маршрутизации, потребители делают ack / nack.

Плюсы

  • Развязка по времени — пики буферизуются в брокере.
  • Паттерны: очередь задач, pub/sub, topic, отложенная доставка (с осторожностью и плагинами).
  • Зрелая эксплуатация: кластер, зеркалирование (classic), quorum queues в современных схемах.

Минусы

  • Это не БД — если консьюмеры лежат, очереди растут; нужны мониторинг и DLQ.
  • Ровно один раз сквозь всю систему не гарантируется; проектируйте идемпотентных обработчиков.
  • Разбор «пропало сообщение» без correlation id и структурных логов превращается в квест.

PHP (Laravel): QUEUE_CONNECTION=rabbitmq с пакетом вроде vladimir-yuldashev/laravel-queue-rabbitmq; локально см. гайд по Sail и очередям. Не выставляйте RabbitMQ в интернет без TLS и учётных записей.


Когда совмещать gRPC и очереди

  • Команда: HTTP → шлюз → публикация «OrderPlaced» в RabbitMQ → воркеры исполняют. Клиенту — 202 + id или схема outbox + опрос статуса.
  • Запрос: HTTP → шлюз → gRPC в read-сервис с кэшем — низкая задержка, синхронный ответ.
  • Саги / компенсации: обмен сообщениями с идемпотентными обработчиками и явными таймаутами.

Не превращайте очередь в скрытый RPC без дедлайнов: «отправили и надеемся» плохо переживает частичные отказы.


Сводная таблица

Слой / инструмент Хорошо, когда… Подумайте дважды, когда…
PHP-шлюз Навыки команды, богатая логика на периметре, стек Laravel/Symfony Нужен почти голый прокси на экстремальном RPS
Node-шлюз BFF с кучей параллельного HTTP, JS-организация Тяжёлый CPU в горячем пути
Go-шлюз Маленький бинарник, gRPC-сетка, платформенный стандарт Нет ресурса сопровождать Go
Rust-шлюз Жёсткие SLA по задержке/памяти Команда только на PHP и нужен быстрый MVP
gRPC внутри Типизированные контракты, стриминг, полиглот Публичный браузерный клиент без прослоек
RabbitMQ Поглощение пиков, асинхронные процессы, масштаб консьюмеров На самом деле нужен синхронный запрос-ответ

Рецепты

RabbitMQ локально (Docker)

docker run -d --hostname rabbit --name rabbit \
  -p 5672:5672 -p 15672:15672 \
  -e RABBITMQ_DEFAULT_USER=guest -e RABBITMQ_DEFAULT_PASS=guest \
  rabbitmq:4-management

Веб-интерфейс: http://localhost:15672 (в бою смените учётные данные).

Объявление очереди через CLI

docker exec rabbit rabbitmqadmin declare queue name=orders durable=true

Минимальный Protobuf (иллюстрация)

order.proto:

syntax = "proto3";
package billing.v1;

message GetOrderRequest { string id = 1; }
message GetOrderResponse { string id = 1; string status = 2; }

service Orders {
  rpc Get(GetOrderRequest) returns (GetOrderResponse);
}

Запускайте protoc с grpc_php_plugin (и плагинами других языков) в CI; либо коммитьте сгенерированный код, либо генерируйте в Docker — выберите одно правило для репозитория.

Фрагмент .env Laravel под RabbitMQ

QUEUE_CONNECTION=rabbitmq
RABBITMQ_HOST=rabbit
RABBITMQ_PORT=5672
RABBITMQ_USER=guest
RABBITMQ_PASSWORD=guest
RABBITMQ_QUEUE=default

Против «тихих» сбоев

  • Протаскивайте X-Request-Id от шлюза в метаданные gRPC и заголовки сообщений.
  • Вешайте deadline на gRPC и TTL / DLX на критичные очереди.
  • В одном дашборде держите глубину очередей, загрузку консьюмеров и p95 задержки шлюза.

Дополнительные материалы