BMGP — Base Multi Gateway Platform
Шлюз — реверс-прокси под конкретный фронт. Собирает в одно API ручки из нескольких бэков (BMBP), нужных этому фронту. Фронт ходит только в свой шлюз и не знает, сколько за ним бэков и какие они. Состав шлюза определяется со стороны фронта («какие ручки мне нужны»), не со стороны бэка («как я сгруппирован»). Один фронт — один шлюз.
5 минутШлюз — реверс-прокси под конкретный фронт. Собирает в одно API ручки из нескольких бэков (BMBP), нужных этому фронту. Фронт ходит только в свой шлюз и не знает, сколько за ним бэков и какие они. Состав шлюза определяется со стороны фронта («какие ручки мне нужны»), не со стороны бэка («как я сгруппирован»). Один фронт — один шлюз.
Слой опциональный: если фронт работает с одним бэком, шлюз не нужен.
Структура шлюза
Шлюз — папка с конфигурацией прокси и обвязкой запуска. Прикладного кода нет.
<frontend>_gateway/
├── compose # запуск контейнера прокси
├── proxy/
│ ├── container # образ
│ ├── proxy.conf # общие настройки: лимиты, кеш, заголовки
│ └── conf.d/
│ ├── api.conf # маршруты: публичный URL → внутренний URL бэка
│ ├── upstreams.template # список бэков с плейсхолдерами адресов
│ └── cors.conf # CORS-заголовки
├── tools/
│ ├── entrypoint # старт: подставить адреса в upstreams → собрать доки → запустить прокси
│ └── generate-openapi # собрать openapi.json из api.conf
├── docs/
│ ├── openapi.json # сгенерированная спека публичного API
│ └── ui/ # Swagger / Redoc / Scalar — раздаётся на /docs
├── .secrets/ # переменные окружения (адреса бэков), вне VCS
└── READMEТри файла, которые правят при работе:
api.conf— какие публичные URL и куда проксируются;upstreams.template— список бэков;proxy.conf— общие настройки.
Связь файлов в рантайме
Наружу опубликован только шлюз — один публичный порт. Бэки шлюз зовёт по внутренним адресам из
upstreams. Какой именно бэк — определяется маршрутом.
фронт → шлюз :<порт> → один из бэков (BMBP) → api → core → … → БДПорядок старта (entrypoint):
- Подставить адреса бэков из
.envвupstreams.template→upstreams.conf. - Сгенерировать
openapi.jsonизapi.conf. - Запустить прокси.
Как именно сервисы изолированы от прямого доступа (приватная сеть, файрвол, внутренний bind) — обвязка развёртывания, не часть архитектуры. Архитектурно важно одно: фронт ходит через шлюз, сервисы наружу не опубликованы.
Маршруты
Каждый публичный маршрут = один блок в api.conf, проксирующий на внутренний путь операции одного
из бэков (из external/front или external/back BMBP). Разные маршруты идут в разные бэки — это
и есть агрегация:
/api/orders/{id} → orders_service /api/front/orders/{id}
/api/users/me → auth_service /users/me
/api/files/upload → files_service /files/uploadАдреса orders_service, auth_service, files_service — из upstreams.conf, значения — из
переменных окружения.
Для фронта это одно цельное API под одним адресом — без понимания, что за каждой ручкой стоит свой
бэк. Публичные пути не повторяют внутренние; снаружи не видно ни состава бэков, ни их группировки.
Что не описано маршрутом — наружу не идёт (хвостовой location / отвечает 404).
Два смысла слова «шлюз»
Слово применяется к двум разным вещам, и их различают суффиксом в имени:
| Что это | Имя | Роль |
|---|---|---|
| реверс-прокси перед бэками (этот документ) | <продукт>_api_gateway | собирает API для одного фронта |
| самостоятельный фронт-продукт | <продукт>_gateway | сайт или приложение, с которым работает человек |
Правило простое: _api_ в имени означает прокси без прикладного кода. Фронт-шлюз обращается к
своему API-шлюзу, а не к бэкам напрямую.
Настройка upstream
У каждого бэка — свой блок upstream. Кроме адреса задаются параметры устойчивости и
переиспользования соединений:
upstream orders_service {
server ${ORDERS_SERVICE_ADDRESS} max_fails=3 fail_timeout=30s;
keepalive 32;
}max_failsиfail_timeout— после скольких неудач подряд и на какой срок бэк считается недоступным. Без них каждый запрос продолжает ждать таймаута упавшего сервиса.keepalive— сколько соединений держать открытыми. Размер выбирается по нагрузке: для частых коротких запросов больше, для редких тяжёлых меньше. Ноль означает новое TCP-соединение (и новое рукопожатие) на каждый запрос.
Адрес берётся из переменной окружения, а не пишется в конфигурацию: перенос бэка на другой сервер меняет только окружение шлюза.
Служебная ручка /health отвечает на самом шлюзе, без обращения к бэкам — по ней проверяется, что
жив он сам, а не вся система.
Сквозное
Задаётся один раз в proxy.conf / cors.conf, применяется к маршрутам:
- Лимиты запросов — зонами под тип нагрузки: строже для входа/auth, мягче для read-only.
- CORS — целиком на шлюзе, приложение про CORS не знает.
- Кеш read-only — короткий, ключ с учётом
Authorization. - Авторизация — шлюз пробрасывает
Authorizationбез изменений; токен проверяет сам сервис.
Инварианты
- Фронт знает только адрес своего шлюза, не адреса бэков.
- Состав шлюза определяется со стороны фронта; бэки про шлюзы не знают.
- Один фронт = один шлюз; новый фронт — новая папка, существующие не трогаем.
- Каждый внешний маршрут объявлен явно; неизвестный путь —
404. - Шлюз несёт только срез операций своего фронта.
- Лимиты, CORS, кеш — на шлюзе, не в сервисе.
- Шлюз маршрутизирует, не переформатирует ответы — конверт
{ status, data }не меняется.
Добавление шлюза
Начинаем со стороны фронта: какие операции ему нужны и из каких бэков они приходят.
- Скопировать папку существующего шлюза как
<frontend>_gateway/. - В
upstreams.templateперечислить все бэки, из которых фронт берёт ручки. - В
api.confна каждую нужную фронту операцию завести маршрут: публичный URL → внутренний путь соответствующего бэка; подключить зону лимита, при чтении — кеш. - В
.secrets/заполнить адреса бэков. - В
composeуказать порт и среду. - Поднять;
openapi.jsonсоберётся на старте, доки доступны на/docs.
Наш стек
nginx, Docker, docker-compose. envsubst подставляет адреса бэков в upstreams.template; скрипт
на Python собирает openapi.json из api.conf. На другом прокси роли те же — карта маршрутов,
список адресов, общие настройки.
Откройте исходник документа по ссылке «Предложить правку» — там же видно, что и когда в нём менялось.