API gateway: PHP, Node, Go, Rust — gRPC и RabbitMQ
В микросервисной архитектуре под «API gateway» чаще всего имеют в виду публичный периметр: TLS, маршрутизация, аутентификация, лимиты и иногда BFF (backend for frontend), который собирает ответ под веб или мобильное приложение. За этим слоем сервисы общаются иначе — часто через gRPC (синхронный RPC) или брокеры сообщений вроде RabbitMQ (асинхронная доставка). Ни один вариант не является «единственно верным стеком»: различаются стоимость эксплуатации, навыки команды и сценарии отказов.
Связанные материалы: PHP на сервере — FPM, Swoole, воркеры · Sail: RabbitMQ и очереди
Содержание
- Что на самом деле делает шлюз
- PHP как слой шлюза
- Node.js на периметре
- Go для шлюзов и сайдкаров
- Rust, когда важны микросекунды
- Внутренние вызовы: gRPC
- Внутренняя работа: RabbitMQ
- Когда совмещать gRPC и очереди
- Сводная таблица
- Рецепты
Что на самом деле делает шлюз
Типичные обязанности:
- Вход — HTTP/HTTPS из интернета; HTTP/3 часто заканчивается на балансировщике.
- Политики — проверка JWT, API-ключи, списки IP, интеграция с WAF.
- Форма трафика — rate limiting, лимиты размера тела, таймауты.
- Маршрутизация — префиксы путей к кластерам (
/billing/*→ billing). - Агрегация (по желанию) — 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 задержки шлюза.
Дополнительные материалы
- Документация RabbitMQ
- Документация gRPC
- Envoy — когда периметр в основном политика и маршрутизация
- Sail: RabbitMQ и очереди — рецепты на этом сайте
- Очереди Laravel (официальная документация)